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
↓ peach-browser-3c8d7683.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+11 −1manifest.json+11 −1src/content/passkey-toast/inject.ts+0 −98src/content/passkey-toast/inject.tsx+98 −0manifest.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": [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": {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-});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+});