inject.tsx

3.5 KB · raw

1import { render } from 'solid-js/web';2import { 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.5import styles from './passkey-toast.css?inline';6import { ACCENT_COLORS } from '../../shared/types';78let currentDispose: (() => void) | null = null;9let currentRequestId: string | null = null;10let toastContainer: HTMLElement | null = null;11let shadowRoot: ShadowRoot | null = null;1213function ensureContainer(): ShadowRoot {14  if (!toastContainer) {15    toastContainer = document.createElement('div');16    toastContainer.id = 'peach-passkey-toast-root';17    shadowRoot = toastContainer.attachShadow({ mode: 'closed' });18    document.body.appendChild(toastContainer);1920    const style = document.createElement('style');21    style.textContent = styles;22    shadowRoot.appendChild(style);23  }24  return shadowRoot!;25}2627function applyAccentToShadow(): void {28  if (!shadowRoot) return;29  chrome.runtime.sendMessage({ type: 'GET_SETTINGS' }).then((resp: unknown) => {30    const name = (resp as { ok?: boolean; data?: { accentColor?: string } } | null)?.data?.accentColor;31    if (!name || !shadowRoot) return;32    const color = ACCENT_COLORS.find(c => c.name === name);33    if (!color) return;34    let el = shadowRoot.querySelector<HTMLStyleElement>('#peach-accent-var');35    if (!el) {36      el = document.createElement('style');37      el.id = 'peach-accent-var';38      shadowRoot.appendChild(el);39    }40    el.textContent = `* { --peach-accent: ${color.hex}; }`;41  }).catch(() => {});42}4344export function showPasskeyToast(payload: {45  site: string;46  displayName: string;47  username: string;48  requestId: string;49}) {50  // Dismiss any existing toast, actively denying the request it holds.51  if (currentDispose) {52    dismissPasskeyToast(currentRequestId ?? undefined);53  }5455  const root = ensureContainer();56  applyAccentToShadow();57  const wrapper = document.createElement('div');58  root.appendChild(wrapper);5960  let dispose: (() => void) | null = null;6162  dispose = render(() => (63    <PasskeyToast64      {...payload}65      onSignIn={() => {66        chrome.runtime.sendMessage({67          type: 'PASSKEY_AUTH_RESPONSE',68          payload: { requestId: payload.requestId, approved: true },69        });70        cleanup();71      }}72      onDeny={() => {73        chrome.runtime.sendMessage({74          type: 'PASSKEY_AUTH_RESPONSE',75          payload: { requestId: payload.requestId, approved: false },76        });77        cleanup();78      }}79    />80  ), wrapper);8182  currentDispose = dispose;83  currentRequestId = payload.requestId;8485  function cleanup() {86    if (dispose) {87      dispose();88      dispose = null;89    }90    wrapper.remove();91    currentDispose = null;92    currentRequestId = null;93    // Tell index.ts the toast is gone so it can re-enable autofill.94    window.postMessage({ source: 'peach-passkey-toast', type: 'toast-closed' }, '*');95  }96}9798export function dismissPasskeyToast(denyRequestId?: string) {99  if (denyRequestId) {100    chrome.runtime.sendMessage({101      type: 'PASSKEY_AUTH_RESPONSE',102      payload: { requestId: denyRequestId, approved: false },103    });104  }105  if (currentDispose) {106    currentDispose();107    currentDispose = null;108    currentRequestId = null;109  }110}111112chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {113  if (msg.type === 'SHOW_PASSKEY_TOAST') {114    showPasskeyToast(msg.payload);115    sendResponse({ ok: true });116  }117  if (msg.type === 'DISMISS_PASSKEY_TOAST') {118    dismissPasskeyToast();119    sendResponse({ ok: true });120  }121});