3c8d768311ba

add passkey-toast content script to manifests, bump to v2.39.3

time
2026-05-13 15:06 UTC · 5 months ago
machine
koh2
parent
ae31fd7fa22f
4 files manifest.firefox.json+11 −1manifest.json+11 −1src/content/passkey-toast/inject.ts+0 −98src/content/passkey-toast/inject.tsx+98 −0
manifest.firefox.json+11 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.39.2",4+  "version": "2.39.3",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {@@ -29,6 +29,16 @@2929         "src/content/index.ts"3030       ],3131       "run_at": "document_idle"32+    },33+    {34+      "matches": [35+        "<all_urls>"36+      ],37+      "js": [38+        "src/content/passkey-toast/inject.tsx"39+      ],40+      "run_at": "document_idle",41+      "all_frames": false3242     },3343     {3444       "matches": [

view file

manifest.json+11 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.39.2",4+  "version": "2.39.3",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",@@ -24,6 +24,16 @@2424         "src/content/index.ts"2525       ],2626       "run_at": "document_idle"27+    },28+    {29+      "matches": [30+        "<all_urls>"31+      ],32+      "js": [33+        "src/content/passkey-toast/inject.tsx"34+      ],35+      "run_at": "document_idle",36+      "all_frames": false2737     }2838   ],2939   "commands": {

view file

src/content/passkey-toast/inject.ts+0 −98
@@ -1,98 +0,0 @@1-import { render } from 'solid-js/web';2-import { PasskeyToast } from './PasskeyToast';3-// ?inline imports the CSS as a string so we can inject it into the shadow root4-// rather than letting Vite/crx inject it into the page document head.5-import styles from './passkey-toast.css?inline';6-7-let currentDispose: (() => void) | null = null;8-let currentRequestId: string | null = null;9-let toastContainer: HTMLElement | null = null;10-let shadowRoot: ShadowRoot | null = null;11-12-function ensureContainer(): ShadowRoot {13-  if (!toastContainer) {14-    toastContainer = document.createElement('div');15-    toastContainer.id = 'peach-passkey-toast-root';16-    shadowRoot = toastContainer.attachShadow({ mode: 'closed' });17-    document.body.appendChild(toastContainer);18-19-    const style = document.createElement('style');20-    style.textContent = styles;21-    shadowRoot.appendChild(style);22-  }23-  return shadowRoot!;24-}25-26-export function showPasskeyToast(payload: {27-  site: string;28-  displayName: string;29-  username: string;30-  requestId: string;31-}) {32-  // Dismiss any existing toast, actively denying the request it holds.33-  if (currentDispose) {34-    dismissPasskeyToast(currentRequestId ?? undefined);35-  }36-37-  const root = ensureContainer();38-  const wrapper = document.createElement('div');39-  root.appendChild(wrapper);40-41-  let dispose: (() => void) | null = null;42-43-  dispose = render(() => (44-    <PasskeyToast45-      {...payload}46-      onSignIn={() => {47-        chrome.runtime.sendMessage({48-          type: 'PASSKEY_AUTH_RESPONSE',49-          payload: { requestId: payload.requestId, approved: true },50-        });51-        cleanup();52-      }}53-      onDeny={() => {54-        chrome.runtime.sendMessage({55-          type: 'PASSKEY_AUTH_RESPONSE',56-          payload: { requestId: payload.requestId, approved: false },57-        });58-        cleanup();59-      }}60-    />61-  ), wrapper);62-63-  currentDispose = dispose;64-  currentRequestId = payload.requestId;65-66-  function cleanup() {67-    if (dispose) {68-      dispose();69-      dispose = null;70-    }71-    wrapper.remove();72-    currentDispose = null;73-    currentRequestId = null;74-  }75-}76-77-export function dismissPasskeyToast(denyRequestId?: string) {78-  if (denyRequestId) {79-    chrome.runtime.sendMessage({80-      type: 'PASSKEY_AUTH_RESPONSE',81-      payload: { requestId: denyRequestId, approved: false },82-    });83-  }84-  if (currentDispose) {85-    currentDispose();86-    currentDispose = null;87-    currentRequestId = null;88-  }89-}90-91-chrome.runtime.onMessage.addListener((msg) => {92-  if (msg.type === 'SHOW_PASSKEY_TOAST') {93-    showPasskeyToast(msg.payload);94-  }95-  if (msg.type === 'DISMISS_PASSKEY_TOAST') {96-    dismissPasskeyToast();97-  }98-});

view file at the parent

src/content/passkey-toast/inject.tsx+98 −0
@@ -0,0 +1,98 @@1+import { render } from 'solid-js/web';2+import { PasskeyToast } from './PasskeyToast';3+// ?inline imports the CSS as a string so we can inject it into the shadow root4+// rather than letting Vite/crx inject it into the page document head.5+import styles from './passkey-toast.css?inline';6+7+let currentDispose: (() => void) | null = null;8+let currentRequestId: string | null = null;9+let toastContainer: HTMLElement | null = null;10+let shadowRoot: ShadowRoot | null = null;11+12+function ensureContainer(): ShadowRoot {13+  if (!toastContainer) {14+    toastContainer = document.createElement('div');15+    toastContainer.id = 'peach-passkey-toast-root';16+    shadowRoot = toastContainer.attachShadow({ mode: 'closed' });17+    document.body.appendChild(toastContainer);18+19+    const style = document.createElement('style');20+    style.textContent = styles;21+    shadowRoot.appendChild(style);22+  }23+  return shadowRoot!;24+}25+26+export function showPasskeyToast(payload: {27+  site: string;28+  displayName: string;29+  username: string;30+  requestId: string;31+}) {32+  // Dismiss any existing toast, actively denying the request it holds.33+  if (currentDispose) {34+    dismissPasskeyToast(currentRequestId ?? undefined);35+  }36+37+  const root = ensureContainer();38+  const wrapper = document.createElement('div');39+  root.appendChild(wrapper);40+41+  let dispose: (() => void) | null = null;42+43+  dispose = render(() => (44+    <PasskeyToast45+      {...payload}46+      onSignIn={() => {47+        chrome.runtime.sendMessage({48+          type: 'PASSKEY_AUTH_RESPONSE',49+          payload: { requestId: payload.requestId, approved: true },50+        });51+        cleanup();52+      }}53+      onDeny={() => {54+        chrome.runtime.sendMessage({55+          type: 'PASSKEY_AUTH_RESPONSE',56+          payload: { requestId: payload.requestId, approved: false },57+        });58+        cleanup();59+      }}60+    />61+  ), wrapper);62+63+  currentDispose = dispose;64+  currentRequestId = payload.requestId;65+66+  function cleanup() {67+    if (dispose) {68+      dispose();69+      dispose = null;70+    }71+    wrapper.remove();72+    currentDispose = null;73+    currentRequestId = null;74+  }75+}76+77+export function dismissPasskeyToast(denyRequestId?: string) {78+  if (denyRequestId) {79+    chrome.runtime.sendMessage({80+      type: 'PASSKEY_AUTH_RESPONSE',81+      payload: { requestId: denyRequestId, approved: false },82+    });83+  }84+  if (currentDispose) {85+    currentDispose();86+    currentDispose = null;87+    currentRequestId = null;88+  }89+}90+91+chrome.runtime.onMessage.addListener((msg) => {92+  if (msg.type === 'SHOW_PASSKEY_TOAST') {93+    showPasskeyToast(msg.payload);94+  }95+  if (msg.type === 'DISMISS_PASSKEY_TOAST') {96+    dismissPasskeyToast();97+  }98+});

view file