1import { render } from 'solid-js/web';2import { PasskeyToast } from './PasskeyToast';345import styles from './passkey-toast.css?inline';6import { ACCENT_COLORS } from '../../shared/types';7import { notifyPasskeyToastClosed } from './lifecycle';89let currentDispose: (() => void) | null = null;10let currentRequestId: string | null = null;11let toastContainer: HTMLElement | null = null;12let shadowRoot: ShadowRoot | null = null;1314function ensureContainer(): ShadowRoot {15 if (!toastContainer) {16 toastContainer = document.createElement('div');17 toastContainer.id = 'peach-passkey-toast-root';18 shadowRoot = toastContainer.attachShadow({ mode: 'closed' });19 document.body.appendChild(toastContainer);2021 const style = document.createElement('style');22 style.textContent = styles;23 shadowRoot.appendChild(style);24 }25 return shadowRoot!;26}2728function applyAccentToShadow(): void {29 if (!shadowRoot) return;30 chrome.runtime.sendMessage({ type: 'GET_SETTINGS' }).then((resp: unknown) => {31 const name = (resp as { ok?: boolean; data?: { accentColor?: string } } | null)?.data?.accentColor;32 if (!name || !shadowRoot) return;33 const color = ACCENT_COLORS.find(c => c.name === name);34 if (!color) return;35 let el = shadowRoot.querySelector<HTMLStyleElement>('#peach-accent-var');36 if (!el) {37 el = document.createElement('style');38 el.id = 'peach-accent-var';39 shadowRoot.appendChild(el);40 }41 el.textContent = `* { --peach-accent: ${color.hex}; }`;42 }).catch(() => {});43}4445export function showPasskeyToast(payload: {46 site: string;47 displayName: string;48 username: string;49 requestId: string;50 toastToken: string;51}) {52 53 if (currentDispose) {54 dismissPasskeyToast(currentRequestId ?? undefined);55 }5657 const root = ensureContainer();58 applyAccentToShadow();59 const wrapper = document.createElement('div');60 root.appendChild(wrapper);6162 let dispose: (() => void) | null = null;6364 dispose = render(() => (65 <PasskeyToast66 {...payload}67 onSignIn={() => {68 chrome.runtime.sendMessage({69 type: 'PASSKEY_AUTH_RESPONSE',70 payload: { requestId: payload.requestId, approved: true },71 });72 cleanup();73 }}74 onDeny={() => {75 chrome.runtime.sendMessage({76 type: 'PASSKEY_AUTH_RESPONSE',77 payload: { requestId: payload.requestId, approved: false },78 });79 cleanup();80 }}81 />82 ), wrapper);8384 currentDispose = dispose;85 currentRequestId = payload.requestId;8687 function cleanup() {88 const ownedDispose = dispose;89 if (ownedDispose) {90 ownedDispose();91 dispose = null;92 }93 wrapper.remove();94 if (currentDispose === ownedDispose) {95 currentDispose = null;96 currentRequestId = null;97 }98 notifyPasskeyToastClosed(payload.toastToken);99 }100}101102export function dismissPasskeyToast(denyRequestId?: string) {103 if (denyRequestId) {104 chrome.runtime.sendMessage({105 type: 'PASSKEY_AUTH_RESPONSE',106 payload: { requestId: denyRequestId, approved: false },107 });108 }109 if (currentDispose) {110 currentDispose();111 currentDispose = null;112 currentRequestId = null;113 }114}115116chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {117 if (msg.type === 'SHOW_PASSKEY_TOAST') {118 showPasskeyToast(msg.payload);119 sendResponse({ ok: true });120 }121 if (msg.type === 'DISMISS_PASSKEY_TOAST') {122 dismissPasskeyToast();123 sendResponse({ ok: true });124 }125});