diff --git a/apps/learn-card-browser-extension/.gitignore b/apps/learn-card-browser-extension/.gitignore new file mode 100644 index 000000000..a8be2559b --- /dev/null +++ b/apps/learn-card-browser-extension/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/apps/learn-card-browser-extension/README.md b/apps/learn-card-browser-extension/README.md new file mode 100644 index 000000000..744d20742 --- /dev/null +++ b/apps/learn-card-browser-extension/README.md @@ -0,0 +1,244 @@ +# LearnCard Browser Extension (MVP) + +Save digital credentials to your LearnCard with one click. This is a lightweight Manifest V3 extension built with React, Vite, and TypeScript. + +- Minimal permissions, secure-by-default +- Detects credentials on pages (VC JSON-LD, special link schemes) +- Shows a popup with detected credential and a one-click save action + + +## Directory + +``` +apps/learn-card-browser-extension/ +├─ src/ +│ ├─ background/main.ts # MV3 Service Worker: messaging + persistence +│ ├─ content/main.ts # Content script: scans page to detect credentials +│ ├─ popup/ +│ │ ├─ index.html # Popup HTML entry +│ │ ├─ main.tsx # Popup React UI +│ │ └─ style.css # Simple styles +│ ├─ types/messages.ts # Shared message + payload types +│ └─ manifest.json # MV3 manifest +├─ vite.config.ts # Vite + CRX plugin config +├─ tsconfig.json # TS config for extension +├─ package.json # scripts + (dev)deps +└─ project.json # Nx targets (build/dev/test) +``` + + +## Prerequisites + +- Node 18+ +- pnpm 9+ +- Chrome 114+ (MV3) + + +## Quickstart + +From the repo root: + +```bash +pnpm install +pnpm --filter learn-card-browser-extension build +``` + +Load the built extension: + +1. Open `chrome://extensions` +2. Enable "Developer mode" +3. Click "Load unpacked" +4. Select `apps/learn-card-browser-extension/dist` + +You should see the LearnCard extension. Pin it if desired. + + +## Development workflow + +There are two simple ways to iterate locally. + +- __Build + Reload (simple, reliable)__ + - Terminal: `pnpm --filter learn-card-browser-extension build` + - Change code → rebuild → in `chrome://extensions`, click "Reload" or use the "Update" button. + +- __Optional: Continuous build (watch)__ + - Terminal: `pnpm --filter learn-card-browser-extension exec vite build --watch` + - Keeps `dist/` up to date; use "Reload"/"Update" in Chrome after each change. + +Notes: +- Background Service Worker logs: `chrome://extensions` → LearnCard → "Service worker" → Inspect. +- Content script logs: open DevTools on the page you’re testing. +- Popup logs: open the popup → right-click → Inspect. + + +## Testing detection + +The content script currently detects: + +- __Special credential links__ + - Any anchor with `href` starting `dccrequest://` or `msrequest://`. + - Example HTML you can paste into a page via DevTools Console: + ```html + Issue credential + ``` + +- __VC-style JSON-LD__ + - Any ` + ``` + +## Testing + +- __Run tests__ + - From repo root: `pnpm --filter learn-card-browser-extension test` + - Or from the app dir: `pnpm test` + +- __Vitest + jsdom__ + - Tests that touch the DOM (detectors) use Vitest's jsdom environment via a file header: + ```ts + // @vitest-environment jsdom + ``` + - You can then create DOM fixtures by setting `document.body.innerHTML`. + +- __What is covered__ + - Detectors: `src/detectors/__tests__/` validate link extraction, JSON-LD parsing, and de-duping via `runDetectors()`. + - Transformers: `src/transformers/__tests__/` validate JSON-LD pass-through and VC-API/fetch flows via mocked helper functions. + +Example detector test snippet: + +```ts +// @vitest-environment jsdom +import { describe, it, expect, beforeEach } from 'vitest'; +import { linksDetector } from '../../detectors/links'; + +beforeEach(() => { document.body.innerHTML = ''; }); + +describe('linksDetector', () => { + it('extracts normalized HTTPS URLs from protocol links', () => { + document.body.innerHTML = 'Go'; + const out = linksDetector(); + expect(out[0].url).toBe('https://issuer.example/ex'); + }); +}); +``` + +## Plugin architecture + +- __Detectors__ (`src/detectors/`) + - Contract: `type Detector = () => CredentialCandidate[]` + - Add a new file (e.g., `acme.ts`) that returns candidates; register it by pushing its results in `src/detectors/index.ts`. + - Prefer using `src/utils/links.ts` for any protocol/param normalization logic. + +- __Transformers__ (`src/transformers/`) + - Contract: + ```ts + type Transformer = { + id: string; + canTransform: (candidate: CredentialCandidate) => boolean; + transform: (candidate, helpers) => Promise<{ vcs: unknown[] } | null>; + } + ``` + - Add a transformer and register it in `src/transformers/index.ts` (order matters). Keep guards narrow in `canTransform`. + - Use provided helpers: `postJson`, `fetchJson`, and `getDidAuthVp` for VC-API flows. + +## Architecture overview + +- __Messaging__ (`src/types/messages.ts`) + - `credentials-detected`: sent by `content/main.ts` → received by `background/main.ts` + - `get-detected`, `save-credential`, `save-credentials`, `check-claimed`, etc. + +- __Content script__ (`src/content/main.ts`) + - Delegates page scanning to pluggable detectors (`src/detectors/`) + - De-dupes results and notifies background with `credentials-detected` + +- __Detectors__ (`src/detectors/`) + - `links.ts`: finds custom-scheme links and extracts normalized HTTPS exchange URLs via `src/utils/links.ts` + - `jsonld.ts`: finds VC-shaped JSON-LD from ` + + `; + + const out = runDetectors(); + + // Expect 2 unique candidates: 1 link, 1 jsonld + expect(out.length).toBe(2); + const sources = out.map((c) => c.source).sort(); + expect(sources).toEqual(['jsonld', 'link']); + + const urls = out.map((c) => c.url).filter(Boolean); + expect(urls).toContain('https://issuer.example/ex?a=1'); + }); +}); diff --git a/apps/learn-card-browser-extension/src/detectors/index.ts b/apps/learn-card-browser-extension/src/detectors/index.ts new file mode 100644 index 000000000..b3eabdd7b --- /dev/null +++ b/apps/learn-card-browser-extension/src/detectors/index.ts @@ -0,0 +1,39 @@ +export type { Detector } from './types'; + +export * from './links'; +export * from './jsonld'; +export * from './khan'; + +import type { CredentialCandidate } from '../types/messages'; +import { linksDetector } from './links'; +import { jsonldDetector } from './jsonld'; +import { khanDetector } from './khan'; + +// Run all registered detectors and return a de-duplicated list +export const runDetectors = (): CredentialCandidate[] => { + const all: CredentialCandidate[] = []; + + // Order matters only for presentation; detection is deduped below + all.push(...linksDetector()); + all.push(...jsonldDetector()); + all.push(...khanDetector()); + + // De-dupe by URL or raw JSON value + const map = new Map(); + + const keyFor = (c: CredentialCandidate) => { + if (c.url) return `url:${c.url}`; + try { + return `raw:${JSON.stringify(c.raw)}`; + } catch { + return `raw:${String(c.title ?? '')}`; + } + }; + + for (const c of all) { + const k = keyFor(c); + if (!map.has(k)) map.set(k, c); + } + + return Array.from(map.values()); +}; diff --git a/apps/learn-card-browser-extension/src/detectors/jsonld.ts b/apps/learn-card-browser-extension/src/detectors/jsonld.ts new file mode 100644 index 000000000..5e29cd410 --- /dev/null +++ b/apps/learn-card-browser-extension/src/detectors/jsonld.ts @@ -0,0 +1,55 @@ +import type { CredentialCandidate } from '../types/messages'; +import { isVc, getTitleFromVc } from '../utils/vc'; +import { detectPlatformFromHostname } from '../utils/platform'; + +export const jsonldDetector = (): CredentialCandidate[] => { + const platform = detectPlatformFromHostname(location.hostname); + + const results: CredentialCandidate[] = []; + + const addData = (data: unknown) => { + if (Array.isArray(data)) { + for (const item of data) addData(item); + return; + } + + if (isVc(data)) { + results.push({ + source: 'jsonld', + raw: data, + title: getTitleFromVc(data), + platform, + }); + } + }; + + const scripts = Array.from( + document.querySelectorAll('script[type="application/ld+json"]') + ); + + for (const s of scripts) { + try { + const data = JSON.parse(s.textContent || 'null'); + if (!data) continue; + addData(data); + } catch {} + } + + const potentialScripts = Array.from(document.querySelectorAll('pre, code')); + + for (const s of potentialScripts) { + const text = s.textContent; + if (!text) continue; + + if (text.includes('"VerifiableCredential"') && text.includes('"credentialSubject"')) { + try { + const data = JSON.parse(text); + addData(data); + } catch { + // ignore + } + } + } + + return results; +}; diff --git a/apps/learn-card-browser-extension/src/detectors/khan.ts b/apps/learn-card-browser-extension/src/detectors/khan.ts new file mode 100644 index 000000000..b8b0698ef --- /dev/null +++ b/apps/learn-card-browser-extension/src/detectors/khan.ts @@ -0,0 +1,106 @@ +import type { CredentialCandidate } from '../types/messages'; +import { detectPlatformFromHostname } from '../utils/platform'; + +// Heuristic Khan Academy detector +// Looks for clear completion moments like "Unit complete" or "You did it!" +const COMPLETION_PATTERNS: RegExp[] = [ + /\bunit\s+(challenge|test)\b.*\b(complete|completed|finished)\b/i, + /\bunit\s+complete(d)?\b/i, + /\byou\s+did\s+it!?/i, + /\bbadge\s+earned\b/i, + /\bmastery\s+(level|goal)\s+(up|reached|achieved)\b/i +]; + +const getTextNodes = (root: ParentNode): string[] => { + const texts: string[] = []; + const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); + let n: Node | null = walker.nextNode(); + while (n) { + const t = (n.textContent || '').trim(); + if (t) texts.push(t); + n = walker.nextNode(); + } + return texts; +}; + +const matchesCompletion = (): boolean => { + // Limit to common visible containers to reduce load + const containers = Array.from(document.querySelectorAll('h1, h2, h3, [role="dialog"], [role="alert"], .modal, .header, .header-2, .title, .message, .notification, .toast')); + const texts = containers.length ? containers.flatMap((el) => getTextNodes(el)) : getTextNodes(document.body); + return texts.some((txt) => COMPLETION_PATTERNS.some((re) => re.test(txt))); +}; + +const extractTitles = (): { unitTitle?: string; courseTitle?: string } => { + const title = document.title || ''; + const parts = title.split('|').map((s) => s.trim()).filter(Boolean); + let unitTitle: string | undefined; + let courseTitle: string | undefined; + + if (parts.length >= 2) { + // e.g. "Unit test: Intro to HTML/CSS | Khan Academy" or "Intro to SQL | Khan Academy" + const kaIdx = parts.findIndex((p) => /khan\s+academy/i.test(p)); + if (kaIdx > 0) { + unitTitle = parts[0]; + courseTitle = parts[kaIdx - 1]; + } else { + unitTitle = parts[0]; + courseTitle = parts[1]; + } + } else if (parts.length === 1) { + unitTitle = parts[0]; + } + + // Try to refine from visible headings + const h1 = document.querySelector('h1'); + if (h1 && h1.textContent) unitTitle = h1.textContent.trim(); + + return { unitTitle, courseTitle }; +}; + +export const khanDetector = (): CredentialCandidate[] => { + const platform = detectPlatformFromHostname(location.hostname); + if (platform !== 'khanacademy') return []; + + // Only surface a candidate when we believe a completion moment occurred + if (!matchesCompletion()) return []; + + const { unitTitle, courseTitle } = extractTitles(); + + const title = unitTitle + ? `Completed: ${unitTitle}` + : courseTitle + ? `Completed: ${courseTitle}` + : 'Completed Khan Academy activity'; + + const url = location.href; + const now = new Date().toISOString(); + + const raw = { + platform: 'khanacademy' as const, + event: 'completion', + unitTitle, + courseTitle, + completedAt: now, + url, + evidence: [ + { + type: 'Evidence', + name: 'Khan Academy Activity', + description: 'Detected completion event on Khan Academy', + url + } + ] + }; + + const candidate: CredentialCandidate = { + source: 'platform', + platform: 'khanacademy', + title, + url, + raw + }; + + return [candidate]; +}; + +export type {}; diff --git a/apps/learn-card-browser-extension/src/detectors/links.ts b/apps/learn-card-browser-extension/src/detectors/links.ts new file mode 100644 index 000000000..9412712b9 --- /dev/null +++ b/apps/learn-card-browser-extension/src/detectors/links.ts @@ -0,0 +1,33 @@ +import type { CredentialCandidate } from '../types/messages'; +import { getExtractorProtocols, extractExchangeUrlFromLink } from '../utils/links'; +import { detectPlatformFromHostname } from '../utils/platform'; + +export const linksDetector = (): CredentialCandidate[] => { + const protocols = getExtractorProtocols(); + if (protocols.length === 0) return []; + + const selector = protocols.map((p) => `a[href^="${p}:"]`).join(', '); + const anchors = Array.from(document.querySelectorAll(selector)); + + const seen = new Set(); + const platform = detectPlatformFromHostname(location.hostname); + + const results: CredentialCandidate[] = []; + for (const a of anchors) { + const href = a.href; + if (!href) continue; + const extracted = extractExchangeUrlFromLink(href); + // Only include if we could extract a usable HTTP(S) URL + if (!extracted || !/^https?:\/\//i.test(extracted)) continue; + if (seen.has(extracted)) continue; + seen.add(extracted); + results.push({ + source: 'link', + url: extracted, + title: a.textContent?.trim() || document.title, + platform, + }); + } + + return results; +}; diff --git a/apps/learn-card-browser-extension/src/detectors/types.ts b/apps/learn-card-browser-extension/src/detectors/types.ts new file mode 100644 index 000000000..06fa6c303 --- /dev/null +++ b/apps/learn-card-browser-extension/src/detectors/types.ts @@ -0,0 +1,3 @@ +import type { CredentialCandidate } from '../types/messages'; + +export type Detector = () => CredentialCandidate[]; diff --git a/apps/learn-card-browser-extension/src/manifest.json b/apps/learn-card-browser-extension/src/manifest.json new file mode 100644 index 000000000..0858550ea --- /dev/null +++ b/apps/learn-card-browser-extension/src/manifest.json @@ -0,0 +1,48 @@ +{ + "manifest_version": 3, + "name": "LearnCard", + "description": "Save digital credentials to your LearnCard in one click.", + "version": "1.0.0", + "icons": { + "16": "extension_icon16.png", + "32": "extension_icon32.png", + "48": "extension_icon48.png", + "128": "extension_icon128.png" + }, + "action": { + "default_title": "LearnCard", + "default_popup": "src/popup/index.html" + }, + "permissions": [ + "storage", + "tabs", + "clipboardRead", + "identity", + "activeTab", + "scripting", + "offscreen" + ], + "host_permissions": [ + "" + ], + "background": { + "service_worker": "src/background/main.ts", + "type": "module" + }, + "web_accessible_resources": [ + { + "resources": ["assets/*.wasm"], + "matches": [""] + } + ], + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" + }, + "content_scripts": [ + { + "matches": [""], + "js": ["src/content/main.ts"], + "run_at": "document_idle" + } + ] +} diff --git a/apps/learn-card-browser-extension/src/messaging/client.ts b/apps/learn-card-browser-extension/src/messaging/client.ts new file mode 100644 index 000000000..2c0c19dea --- /dev/null +++ b/apps/learn-card-browser-extension/src/messaging/client.ts @@ -0,0 +1,91 @@ +import type { + ExtensionMessage, + // messages + GetDetectedMessage, + SaveCredentialMessage, + SaveCredentialsMessage, + StartAuthMessage, + GetAuthStatusMessage, + LogoutMessage, + RequestScanMessage, + GetProfileMessage, + StartVcApiExchangeMessage, + GetVcApiExchangeStatusMessage, + AcceptVcApiOfferMessage, + CancelVcApiExchangeMessage, + // responses + GetDetectedResponse, + SaveCredentialResponse, + SaveCredentialsResponse, + StartAuthResponse, + GetAuthStatusResponse, + LogoutResponse, + RequestScanResponse, + GetProfileResponse, + StartVcApiExchangeResponse, + GetVcApiExchangeStatusResponse, + AcceptVcApiOfferResponse, + CancelVcApiExchangeResponse, + CredentialsDetectedMessage, + CredentialDetectedMessage, + CredentialsDetectedResponse, + CredentialDetectedResponse, +} from '../types/messages'; + +// Promise wrapper with runtime.lastError safety +const promisifyRuntimeSendMessage = (msg: ExtensionMessage): Promise => + new Promise((resolve) => { + try { + chrome.runtime.sendMessage(msg as any, (resp: T) => { + if (chrome.runtime.lastError) { + resolve({ ok: false, error: chrome.runtime.lastError.message } as unknown as T); + return; + } + resolve(resp); + }); + } catch (e) { + resolve({ ok: false, error: (e as Error).message } as unknown as T); + } + }); + +const promisifyTabsSendMessage = (tabId: number, msg: ExtensionMessage): Promise => + new Promise((resolve) => { + try { + chrome.tabs.sendMessage(tabId, msg as any, (resp: T) => { + if (chrome.runtime.lastError) { + resolve({ ok: false, error: chrome.runtime.lastError.message } as unknown as T); + return; + } + resolve(resp); + }); + } catch (e) { + resolve({ ok: false, error: (e as Error).message } as unknown as T); + } + }); + +// Overloads for runtime messaging +export function sendMessage(msg: GetDetectedMessage): Promise; +export function sendMessage(msg: SaveCredentialMessage): Promise; +export function sendMessage(msg: SaveCredentialsMessage): Promise; +export function sendMessage(msg: StartAuthMessage): Promise; +export function sendMessage(msg: GetAuthStatusMessage): Promise; +export function sendMessage(msg: LogoutMessage): Promise; +export function sendMessage(msg: GetProfileMessage): Promise; +export function sendMessage(msg: StartVcApiExchangeMessage): Promise; +export function sendMessage(msg: GetVcApiExchangeStatusMessage): Promise; +export function sendMessage(msg: AcceptVcApiOfferMessage): Promise; +export function sendMessage(msg: CancelVcApiExchangeMessage): Promise; +export function sendMessage(msg: CredentialsDetectedMessage): Promise; +export function sendMessage(msg: CredentialDetectedMessage): Promise; +// Fallback signature +export function sendMessage(msg: ExtensionMessage): Promise; +export function sendMessage(msg: ExtensionMessage): Promise { + return promisifyRuntimeSendMessage(msg); +} + +// Overloads for tab messaging (content script) +export function sendTabMessage(tabId: number, msg: RequestScanMessage): Promise; +export function sendTabMessage(tabId: number, msg: ExtensionMessage): Promise; +export function sendTabMessage(tabId: number, msg: ExtensionMessage): Promise { + return promisifyTabsSendMessage(tabId, msg); +} diff --git a/apps/learn-card-browser-extension/src/offscreen.html b/apps/learn-card-browser-extension/src/offscreen.html new file mode 100644 index 000000000..d05b1f978 --- /dev/null +++ b/apps/learn-card-browser-extension/src/offscreen.html @@ -0,0 +1,10 @@ + + + + + LearnCard Offscreen + + + + + diff --git a/apps/learn-card-browser-extension/src/offscreen.ts b/apps/learn-card-browser-extension/src/offscreen.ts new file mode 100644 index 000000000..f019da626 --- /dev/null +++ b/apps/learn-card-browser-extension/src/offscreen.ts @@ -0,0 +1,372 @@ +// Offscreen document script for LearnCard initialization and storage +import { initLearnCard } from '@learncard/init'; +import didkitWasmUrl from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm?url'; +import type { CredentialCandidate, CredentialCategory } from './types/messages'; +import { computeCredentialHash } from './utils/hash'; +import { transformCandidate } from './transformers'; +import type { TransformerHelpers } from './transformers/types'; + +type LearnCardLike = { + id: { did: () => string; keypair: (type: string) => { d: string } }; + store: { + uploadEncrypted: (vc: unknown) => Promise; + LearnCloud?: { uploadEncrypted: (vc: unknown) => Promise }; + }; + index?: { + LearnCloud?: { + add: (args: { id?: string; uri: string; category: string }) => Promise; + getCount: (query?: Record) => Promise; + }; + }; + invoke: { + getDidAuthVp: (args: { challenge: string; domain?: string }) => Promise; + hash?: (message: string, algorithm?: string) => Promise; + crypto: () => Crypto; + getProfile?: () => Promise; + }; +}; + +let learnCard: LearnCardLike | null = null; + +async function ensureLearnCard(seed?: string): Promise { + if (learnCard) return learnCard; + const useSeed = seed; + if (!useSeed) throw new Error('Not logged in'); + + const instance = await initLearnCard({ + seed: useSeed, + network: true, + didkit: didkitWasmUrl + }); + // Narrow to methods we use + learnCard = instance as unknown as LearnCardLike; + return learnCard; +} + +async function initializeAndGetDid(seed: string): Promise { + const lc = await ensureLearnCard(seed); + return lc?.id.did(); +} + +async function getProfile(seed: string): Promise { + const lc = await ensureLearnCard(seed); + try { + const prof = await (lc as any)?.invoke?.getProfile?.(); + if (!prof) return undefined; + return prof; + } catch { + // ignore + } + return undefined; +} + +function isObject(x: unknown): x is Record { + return !!x && typeof x === 'object'; +} + +function looksLikeVc(obj: any): boolean { + return ( + isObject(obj) && + (Array.isArray(obj['@context']) || typeof obj['@context'] === 'string') && + (Array.isArray(obj.type) || typeof obj.type === 'string' || Array.isArray(obj['type'])) + ); +} + +async function checkClaimedForVc(lc: LearnCardLike, vc: unknown): Promise { + try { + const canonicalId = await computeCredentialHash(lc as any, vc); + const count = await lc.index?.LearnCloud?.getCount?.({ id: canonicalId }); + return Boolean(count && count > 0); + } catch { + return false; + } +} + +async function handleCheckClaimed( + candidate: CredentialCandidate, + seed: string | undefined +): Promise { + const lc = await ensureLearnCard(seed); + + if (candidate.source === 'jsonld' && candidate.raw) { + const raw = candidate.raw as any; + const vc = typeof raw === 'string' ? JSON.parse(raw) : raw; + return checkClaimedForVc(lc, vc); + } + + if (candidate.url) { + const url = candidate.url; + try { + const resp = await fetch(url, { method: 'GET', headers: { accept: 'application/json' } }); + if (!resp.ok) throw new Error('Fetch failed'); + const data = await resp.json(); + if (looksLikeVc(data)) return checkClaimedForVc(lc, data); + } catch { + // Best-effort only; if we can't fetch/parse, treat as not claimed + } + } + + return false; +} + +async function handleStoreCandidate( + candidate: CredentialCandidate, + seed: string | undefined, + category: CredentialCategory = 'Achievement' +): Promise { + const lc = await ensureLearnCard(seed); + + const helpers: TransformerHelpers = { + postJson: async (url: string, body: unknown) => { + const resp = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(body ?? {}) + }); + if (!resp.ok) { + const text = await resp.text().catch(() => ''); + throw new Error(`POST ${url} failed: ${resp.status} ${text}`); + } + return resp.json().catch(() => ({} as unknown)); + }, + fetchJson: async (url: string) => { + const resp = await fetch(url, { method: 'GET', headers: { accept: 'application/json' } }); + if (!resp.ok) throw new Error(`GET ${url} failed: ${resp.status}`); + return resp.json(); + }, + getDidAuthVp: (args) => lc.invoke.getDidAuthVp(args), + getDid: async () => Promise.resolve(lc.id.did()) + }; + + const transformed = await transformCandidate(candidate, helpers); + if (!transformed || !Array.isArray(transformed.vcs) || transformed.vcs.length === 0) { + throw new Error('Unsupported credential source or failed to retrieve credential'); + } + + let saved = 0; + for (const vc of transformed.vcs) { + try { + const parsed = typeof vc === 'string' ? JSON.parse(vc as string) : vc; + const uri = await lc.store?.LearnCloud?.uploadEncrypted(parsed); + const uriStr = typeof uri === 'string' ? uri : String(uri); + try { + const canonicalId = await computeCredentialHash(lc as any, parsed); + await lc.index?.LearnCloud?.add({ id: canonicalId, uri: uriStr, category }); + } catch { + await lc.index?.LearnCloud?.add({ uri: uriStr, category }); + } + saved += 1; + } catch { + // continue with next VC + } + } + + return saved; +} + +chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { + if (message?.target === 'offscreen') { + if (message?.type === 'vcapi-open') { + const seed = message?.data?.seed as string | undefined; + const url = message?.data?.url as string | undefined; + if (!seed || !url) { + sendResponse({ ok: false, error: 'Missing seed or url' }); + return false; + } + (async () => { + try { + const lc = await ensureLearnCard(seed); + // Initiate VC-API flow + const initResp = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({}) + }); + if (!initResp.ok) { + const text = await initResp.text().catch(() => ''); + throw new Error(`VC-API init failed: ${initResp.status} ${text}`); + } + const initJson = await initResp.json().catch(() => ({})); + const vpr = initJson?.verifiablePresentationRequest ?? initJson; + const challenge = vpr?.challenge ?? vpr?.nonce; + const domain = vpr?.domain; + if (!challenge) throw new Error('Missing VC-API challenge'); + const vp = await lc.invoke.getDidAuthVp({ challenge, domain }); + const finalize = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ verifiablePresentation: vp }) + }); + if (!finalize.ok) { + const text = await finalize.text().catch(() => ''); + throw new Error(`VC-API finalize failed: ${finalize.status} ${text}`); + } + const result = await finalize.json() + if (!result.ok) { + throw new Error(`VC-API auth failed: (${result.code}) ${result.message}.`); + } + const vpOut = (result?.verifiablePresentation ?? result?.vp ?? result) as any; + const vcsRaw = vpOut?.verifiableCredential ?? vpOut?.verifiableCredentials; + const rawList: unknown[] = Array.isArray(vcsRaw) ? vcsRaw : vcsRaw ? [vcsRaw] : []; + const vcs: any[] = rawList.map((vc) => (typeof vc === 'string' ? JSON.parse(vc) : vc)); + sendResponse({ ok: true, vcs }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + sendResponse({ ok: false, error: msg }); + } + })(); + return true; + } + + if (message?.type === 'vcapi-accept') { + const items = (message?.data?.items as { vc: unknown; category?: CredentialCategory }[] | undefined) ?? []; + const seed = message?.data?.seed as string | undefined; + if (!Array.isArray(items) || items.length === 0) { + sendResponse({ ok: false, error: 'Missing items' }); + return false; + } + (async () => { + try { + const lc = await ensureLearnCard(seed); + let saved = 0; + for (const { vc, category } of items) { + try { + const uri = await lc.store?.LearnCloud?.uploadEncrypted(vc); + const uriStr = typeof uri === 'string' ? uri : String(uri); + try { + const canonicalId = await computeCredentialHash(lc as any, vc); + await lc.index?.LearnCloud?.add({ id: canonicalId, uri: uriStr, category: (category as CredentialCategory) ?? 'Achievement' }); + } catch { + await lc.index?.LearnCloud?.add({ uri: uriStr, category: (category as CredentialCategory) ?? 'Achievement' }); + } + saved += 1; + } catch { + // continue + } + } + sendResponse({ ok: true, savedCount: saved }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + sendResponse({ ok: false, error: msg }); + } + })(); + return true; + } + if (message?.type === 'start-learncard-init') { + const seed = message?.data?.seed as string | undefined; + if (!seed) { + sendResponse({ ok: false, error: 'Missing seed' }); + return false; + } + initializeAndGetDid(seed) + .then((did) => sendResponse({ ok: true, did })) + .catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + sendResponse({ ok: false, error: msg }); + }); + return true; + } + + if (message?.type === 'store-credential') { + const candidate = message?.data?.candidate as CredentialCandidate | undefined; + const seed = message?.data?.seed as string | undefined; + const category = message?.data?.category as CredentialCategory | undefined; + if (!candidate) { + sendResponse({ ok: false, error: 'Missing credential candidate' }); + return false; + } + handleStoreCandidate(candidate, seed, category ?? 'Achievement') + .then((savedCount) => sendResponse({ ok: true, savedCount })) + .catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + sendResponse({ ok: false, error: msg }); + }); + return true; + } + + if (message?.type === 'get-profile') { + const seed = message?.data?.seed as string | undefined; + if (!seed) { + sendResponse({ ok: false, error: 'Missing seed' }); + return false; + } + getProfile(seed) + .then((profile) => sendResponse({ ok: true, profile })) + .catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + sendResponse({ ok: false, error: msg }); + }); + return true; + } + + if (message?.type === 'store-credentials') { + const items = (message?.data?.items as { candidate: CredentialCandidate; category?: CredentialCategory }[] | undefined) ?? []; + const seed = message?.data?.seed as string | undefined; + if (!Array.isArray(items) || items.length === 0) { + sendResponse({ ok: false, error: 'Missing selections' }); + return false; + } + (async () => { + try { + let saved = 0; + for (const it of items) { + try { + saved += await handleStoreCandidate(it.candidate, seed, (it.category as CredentialCategory) ?? 'Achievement'); + } catch (e) { + // continue with next + } + } + sendResponse({ ok: true, savedCount: saved }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + sendResponse({ ok: false, error: msg }); + } + })(); + return true; + } + + if (message?.type === 'check-claimed') { + const candidate = message?.data?.candidate as CredentialCandidate | undefined; + const seed = message?.data?.seed as string | undefined; + if (!candidate) { + sendResponse({ ok: false, error: 'Missing credential candidate' }); + return false; + } + handleCheckClaimed(candidate, seed) + .then((claimed) => sendResponse({ ok: true, claimed })) + .catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + sendResponse({ ok: false, error: msg }); + }); + return true; + } + + if (message?.type === 'check-claimed-many') { + const items = (message?.data?.candidates as CredentialCandidate[] | undefined) ?? []; + const seed = message?.data?.seed as string | undefined; + if (!Array.isArray(items) || items.length === 0) { + sendResponse({ ok: false, error: 'Missing candidates' }); + return false; + } + (async () => { + try { + const results: boolean[] = []; + for (const it of items) { + try { + results.push(await handleCheckClaimed(it, seed)); + } catch { + results.push(false); + } + } + sendResponse({ ok: true, results }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + sendResponse({ ok: false, error: msg }); + } + })(); + return true; + } + } + + return false; +}); diff --git a/apps/learn-card-browser-extension/src/popup/components/ActionBar.tsx b/apps/learn-card-browser-extension/src/popup/components/ActionBar.tsx new file mode 100644 index 000000000..bea5e2e35 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/components/ActionBar.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import type { CredentialCandidate } from '../../types/messages'; + +export type ActionBarProps = { + candidates: CredentialCandidate[]; + selected: boolean[]; + setSelected: (next: boolean[]) => void; + saving: boolean; + onBulkSave: () => void; +}; + +export const ActionBar: React.FC = ({ candidates, selected, setSelected, saving, onBulkSave }) => { + const eligibleCount = candidates.filter((c) => !c.claimed && c.source !== 'link').length; + const selectedCount = candidates.reduce((acc, c, i) => acc + (!c.claimed && c.source !== 'link' && selected[i] ? 1 : 0), 0); + const allChecked = eligibleCount > 0 && candidates.every((c, i) => (c.claimed || c.source === 'link' ? true : !!selected[i])); + + if (eligibleCount === 0) return null; + + return ( + <> + + + + ); +}; + +export default ActionBar; diff --git a/apps/learn-card-browser-extension/src/popup/components/CategorySelector.tsx b/apps/learn-card-browser-extension/src/popup/components/CategorySelector.tsx new file mode 100644 index 000000000..84d83af99 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/components/CategorySelector.tsx @@ -0,0 +1,117 @@ +import React, { useEffect, useId, useRef } from 'react'; +import type { CredentialCategory } from '../../types/messages'; +import { CATEGORY_OPTIONS, getCategoryLabel } from '../constants'; + +export type CategorySelectorProps = { + value: CredentialCategory | undefined; + disabled?: boolean; + isOpen: boolean; + onOpen: () => void; + onClose: () => void; + onSelect: (value: CredentialCategory) => void; +}; + +export const CategorySelector: React.FC = ({ + value, + disabled, + isOpen, + onOpen, + onClose, + onSelect, +}) => { + const containerRef = useRef(null); + const triggerRef = useRef(null); + const menuRef = useRef(null); + const menuId = useId(); + + // Close when clicking outside + useEffect(() => { + if (!isOpen) return; + const onDocClick = (e: MouseEvent) => { + if (!containerRef.current) return; + if (!containerRef.current.contains(e.target as Node)) onClose(); + }; + document.addEventListener('mousedown', onDocClick); + return () => document.removeEventListener('mousedown', onDocClick); + }, [isOpen, onClose]); + + // Keyboard interactions + useEffect(() => { + if (!isOpen) return; + // Focus first item when opening + const first = menuRef.current?.querySelector('.menu-item'); + first?.focus(); + }, [isOpen]); + + const onTriggerKeyDown: React.KeyboardEventHandler = (e) => { + if (disabled) return; + if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onOpen(); + } + if (e.key === 'Escape') { + e.preventDefault(); + onClose(); + } + }; + + const onMenuKeyDown: React.KeyboardEventHandler = (e) => { + if (!isOpen) return; + const items = Array.from(menuRef.current?.querySelectorAll('.menu-item') ?? []); + const idx = items.findIndex((el) => el === document.activeElement); + if (e.key === 'ArrowDown') { + e.preventDefault(); + const next = items[(idx + 1) % items.length] ?? items[0]; + next?.focus(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + const prev = items[(idx - 1 + items.length) % items.length] ?? items[0]; + prev?.focus(); + } else if (e.key === 'Escape') { + e.preventDefault(); + onClose(); + triggerRef.current?.focus(); + } + }; + + return ( +
+ + {isOpen && ( + + )} +
+ ); +}; + +export default CategorySelector; diff --git a/apps/learn-card-browser-extension/src/popup/components/HeaderBar.tsx b/apps/learn-card-browser-extension/src/popup/components/HeaderBar.tsx new file mode 100644 index 000000000..ef24d00d8 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/components/HeaderBar.tsx @@ -0,0 +1,122 @@ +import React from 'react'; + +import RefreshIcon from '../icons/refresh.svg'; +import ClipboardIcon from '../icons/paste-from-clipboard.svg'; +import HamburgerIcon from '../icons/hamburger.svg'; + +export type HeaderBarProps = { + authDid: string | null; + tabId: number | null; + rescanBusy: boolean; + analyzeBusy: boolean; + optsOpen: boolean; + menuOpen: boolean; + profileImage: string | null; + hideClaimed: boolean; + authLoading: boolean; + + onRescan: () => void; + onAnalyze: () => void; + onToggleOptions: () => void; + onToggleMenu: () => void; + onToggleHideClaimed: (checked: boolean) => void; + onShowExchange: () => void; + onCopyDid: () => void; + onLogout: () => void; +}; + +export const HeaderBar: React.FC = ({ + authDid, + tabId, + rescanBusy, + analyzeBusy, + optsOpen, + menuOpen, + profileImage, + hideClaimed, + authLoading, + onRescan, + onAnalyze, + onToggleOptions, + onToggleMenu, + onToggleHideClaimed, + onShowExchange, + onCopyDid, + onLogout, +}) => { + return ( +
+
LearnCard
+ {authDid && ( +
+
+ {tabId !== null && ( + + )} + +
+ + {optsOpen && ( +
+
+ +
+
+ +
+
+ )} +
+
+ + {menuOpen && ( +
+
+
Signed in
+
{authDid}
+
+
+ + +
+
+ )} +
+ )} +
+ ); +}; + +export default HeaderBar; diff --git a/apps/learn-card-browser-extension/src/popup/constants.ts b/apps/learn-card-browser-extension/src/popup/constants.ts new file mode 100644 index 000000000..c05f4e775 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/constants.ts @@ -0,0 +1,17 @@ +import type { CredentialCategory } from '../types/messages'; + +export const CATEGORY_OPTIONS: ReadonlyArray<{ value: CredentialCategory; label: string }> = [ + { value: 'Achievement', label: 'Achievement' }, + { value: 'ID', label: 'ID' }, + { value: 'Learning History', label: 'Studies' }, + { value: 'Work History', label: 'Experiences' }, + { value: 'Social Badge', label: 'Boosts' }, + { value: 'Accomplishment', label: 'Portfolio' }, + { value: 'Accommodation', label: 'Assistance' }, +] as const; + +export const getCategoryLabel = (val: CredentialCategory | null | undefined) => { + if (!val) return 'Set Category'; + const found = CATEGORY_OPTIONS.find((o) => o.value === val); + return found?.label ?? val; +}; diff --git a/apps/learn-card-browser-extension/src/popup/hooks/useActiveTabId.ts b/apps/learn-card-browser-extension/src/popup/hooks/useActiveTabId.ts new file mode 100644 index 000000000..327ed1d9e --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/hooks/useActiveTabId.ts @@ -0,0 +1,14 @@ +import { useEffect, useState } from 'react'; + +export const useActiveTabId = () => { + const [tabId, setTabId] = useState(null); + + useEffect(() => { + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + const id = tabs?.[0]?.id ?? null; + setTabId(id ?? null); + }); + }, []); + + return tabId; +}; diff --git a/apps/learn-card-browser-extension/src/popup/hooks/useAuth.ts b/apps/learn-card-browser-extension/src/popup/hooks/useAuth.ts new file mode 100644 index 000000000..48af43cbf --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/hooks/useAuth.ts @@ -0,0 +1,75 @@ +import { useEffect, useState } from 'react'; +import { sendMessage } from '../../messaging/client'; +import type { + GetAuthStatusResponse, + GetProfileResponse, + LogoutResponse, + StartAuthResponse, +} from '../../types/messages'; + +export const useAuth = () => { + const [did, setDid] = useState(null); + + const [profileImage, setProfileImage] = useState(null); + + const [loading, setLoading] = useState(false); + + // Initialize from background on mount + useEffect(() => { + sendMessage({ type: 'get-auth-status' }).then((resp: GetAuthStatusResponse) => { + if (resp?.ok && resp.data) { + setDid(resp.data.did ?? null); + if (resp.data.loggedIn) { + refreshProfile(); + } + } + }); + }, []); + + const refreshProfile = async (): Promise => { + const resp = await sendMessage({ type: 'get-profile' }); + + if (resp?.ok) setProfileImage(resp.profile?.image ?? null); + + return resp as GetProfileResponse; + }; + + const login = async (): Promise => { + setLoading(true); + + const resp = await sendMessage({ type: 'start-auth' }); + + setLoading(false); + + if (resp?.ok) { + setDid(resp.data?.did ?? null); + await refreshProfile(); + } + + return resp as StartAuthResponse; + }; + + const logout = async (): Promise => { + setLoading(true); + + const resp = await sendMessage({ type: 'logout' }); + + setLoading(false); + + if (resp?.ok) { + setDid(null); + setProfileImage(null); + } + + return resp as LogoutResponse; + }; + + return { + did, + profileImage, + loading, + login, + logout, + refreshProfile, + } as const; +}; diff --git a/apps/learn-card-browser-extension/src/popup/hooks/useExchange.ts b/apps/learn-card-browser-extension/src/popup/hooks/useExchange.ts new file mode 100644 index 000000000..2516209a9 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/hooks/useExchange.ts @@ -0,0 +1,189 @@ +import { useCallback, useEffect, useState } from 'react'; +import { sendMessage } from '../../messaging/client'; +import type { + AcceptVcApiOfferResponse, + CancelVcApiExchangeResponse, + CredentialCandidate, + CredentialCategory, + GetVcApiExchangeStatusResponse, + StartVcApiExchangeResponse, + VcApiExchangeState, +} from '../../types/messages'; +import { toErrorString } from '../../utils/errors'; + +export type UseExchangeOptions = { + onStatus?: (s: string) => void; +}; + +export const useExchange = (tabId: number | null, opts: UseExchangeOptions = {}) => { + const { onStatus } = opts; + + const [url, setUrl] = useState(''); + + const [state, setState] = useState('idle'); + + const [offers, setOffers] = useState([]); + + const [selected, setSelected] = useState([]); + + const [categories, setCategories] = useState([]); + + const [openCatIdx, setOpenCatIdx] = useState(null); + + const [busy, setBusy] = useState(false); + + const [error, setError] = useState(null); + + const [autoPrefilled, setAutoPrefilled] = useState(false); + + const [show, setShow] = useState(false); + + const refreshStatus = useCallback(() => { + sendMessage({ type: 'get-vcapi-exchange-status', tabId: tabId ?? undefined }).then((resp) => { + const r = resp as GetVcApiExchangeStatusResponse; + if (!r?.ok) return; + + const sess = (r.data || { state: 'idle' }) as { + state: VcApiExchangeState; + url?: string; + offers?: unknown[]; + error?: string | null; + }; + + setState(sess.state); + if (sess.url) setUrl(sess.url); + setError(sess.state === 'error' ? toErrorString(sess.error ?? 'Unknown error') : null); + + if (Array.isArray(sess.offers)) { + setOffers(sess.offers); + setSelected(sess.offers.map(() => true)); + setCategories(sess.offers.map(() => 'Achievement' as CredentialCategory)); + } else { + setOffers([]); + setSelected([]); + setCategories([]); + } + + if (sess.state === 'contacting' || sess.state === 'saving') { + window.setTimeout(() => refreshStatus(), 600); + } + }); + }, [tabId]); + + // Sync Exchange session from background when tabId becomes available + useEffect(() => { + if (tabId === null) return; + refreshStatus(); + }, [tabId, refreshStatus]); + + const start = async (): Promise => { + const trimmed = url.trim(); + if (!trimmed) return { ok: false, error: 'Missing URL' } as const; + + setBusy(true); + setError(null); + onStatus?.('Starting exchange…'); + + const resp = (await sendMessage({ type: 'start-vcapi-exchange', url: trimmed, tabId: tabId ?? undefined })) as StartVcApiExchangeResponse; + + setBusy(false); + + if (resp?.ok) { + setState('contacting'); + refreshStatus(); + } else { + const err = toErrorString((resp as { error?: string })?.error ?? 'Failed to start'); + setError(err); + setState('error'); + onStatus?.(`Failed to start: ${err}`); + } + + return resp; + }; + + const cancel = async (): Promise => { + const resp = (await sendMessage({ type: 'cancel-vcapi-exchange', tabId: tabId ?? undefined })) as CancelVcApiExchangeResponse; + + setState('idle'); + setOffers([]); + setSelected([]); + setCategories([]); + setOpenCatIdx(null); + setBusy(false); + setError(null); + setShow(false); + + return resp; + }; + + const accept = async (): Promise => { + const selections = selected + .map((v, i) => ({ v, i })) + .filter(({ v }) => v) + .map(({ i }) => ({ index: i, category: categories[i] })); + + if (selections.length === 0) return { ok: false, error: 'No items selected' } as const; + + setBusy(true); + onStatus?.('Claiming…'); + + const resp = (await sendMessage({ type: 'accept-vcapi-offer', selections, tabId: tabId ?? undefined })) as AcceptVcApiOfferResponse; + + setBusy(false); + + if (resp?.ok) { + setState('success'); + const count = resp.savedCount; + onStatus?.(`Saved ${count} credential${count === 1 ? '' : 's'} to LearnCard`); + } else { + const err = toErrorString((resp as { error?: string })?.error ?? 'Unknown error'); + setState('error'); + setError(err); + onStatus?.(`Failed: ${err}`); + } + + return resp; + }; + + const prefillFromCandidates = (cands: CredentialCandidate[]) => { + if (state !== 'idle') return; + if (autoPrefilled) return; + if (url.trim()) return; + + const link = cands.find((c) => c.source === 'link' && !!c.url && !c.claimed); + if (link?.url) { + setUrl(link.url); + setAutoPrefilled(true); + onStatus?.('Detected offer URL from page'); + setShow(true); + } + }; + + return { + // state + url, + state, + offers, + selected, + categories, + openCatIdx, + busy, + error, + autoPrefilled, + show, + + // setters + setUrl, + setSelected, + setCategories, + setOpenCatIdx, + setShow, + + // actions + refreshStatus, + start, + cancel, + accept, + prefillFromCandidates, + } as const; +}; diff --git a/apps/learn-card-browser-extension/src/popup/hooks/useInbox.ts b/apps/learn-card-browser-extension/src/popup/hooks/useInbox.ts new file mode 100644 index 000000000..c6810e852 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/hooks/useInbox.ts @@ -0,0 +1,214 @@ +import { useEffect, useState } from 'react'; +import { sendMessage, sendTabMessage } from '../../messaging/client'; +import type { + CredentialCandidate, + CredentialCategory, + GetDetectedResponse, + SaveCredentialsMessage, + SaveCredentialsResponse, +} from '../../types/messages'; +import { isVc, getTitleFromVc } from '../../utils/vc'; + +export type UseInboxOptions = { + onStatus?: (s: string) => void; +}; + +export const useInbox = (tabId: number | null, opts: UseInboxOptions = {}) => { + const { onStatus } = opts; + + const [candidates, setCandidates] = useState([]); + + const [selected, setSelected] = useState([]); + + const [categories, setCategories] = useState([]); + + const [openCategoryIdx, setOpenCategoryIdx] = useState(null); + + const [hideClaimed, setHideClaimed] = useState(false); + + const [rescanBusy, setRescanBusy] = useState(false); + + const [analyzeBusy, setAnalyzeBusy] = useState(false); + + const [saving, setSaving] = useState(false); + + // Fetch detected credentials for this tab on mount/tab change + useEffect(() => { + const init = async () => { + const resp = (await sendMessage({ type: 'get-detected', tabId: tabId ?? undefined })) as GetDetectedResponse; + const list = resp?.ok && Array.isArray(resp.data) ? (resp.data as CredentialCandidate[]) : []; + setCandidates(list); + + if ((tabId ?? null) !== null && list.length === 0) { + try { + await sendTabMessage(tabId!, { type: 'request-scan' }); + const resp2 = (await sendMessage({ type: 'get-detected', tabId: tabId ?? undefined })) as GetDetectedResponse; + if (resp2?.ok && Array.isArray(resp2.data)) setCandidates(resp2.data as CredentialCandidate[]); + } catch { + // ignore + } + } + }; + + init(); + }, [tabId]); + + // Keep selection and categories arrays in sync with candidates length and claimed flags + useEffect(() => { + setSelected((prev) => { + const next = candidates.map((c, i) => { + if (c.claimed) return false; + if (typeof prev[i] === 'boolean') return prev[i] as boolean; + return true; + }); + return next; + }); + + setCategories((prev) => { + const next = candidates.map((_, i) => (prev[i] ? prev[i] : 'Achievement')); + return next as CredentialCategory[]; + }); + }, [candidates]); + + const refreshDetected = async () => { + const resp = (await sendMessage({ type: 'get-detected', tabId: tabId ?? undefined })) as GetDetectedResponse; + if (resp?.ok && Array.isArray(resp.data)) setCandidates(resp.data as CredentialCandidate[]); + }; + + const dedupe = (list: CredentialCandidate[]) => { + const map = new Map(); + const keyFor = (c: CredentialCandidate) => { + if (c.url) return `url:${c.url}`; + try { + return `raw:${JSON.stringify(c.raw)}`; + } catch { + return `raw:${String(c.title ?? '')}`; + } + }; + for (const c of list) { + const k = keyFor(c); + if (!map.has(k)) map.set(k, c); + } + return Array.from(map.values()); + }; + + const analyzeClipboard = async () => { + setAnalyzeBusy(true); + onStatus?.('Analyzing clipboard…'); + try { + const text = await navigator.clipboard.readText(); + let found: CredentialCandidate[] = []; + + try { + const parsed = JSON.parse(text); + const add = (val: unknown) => { + if (Array.isArray(val)) return val.forEach(add); + if (isVc(val)) found.push({ source: 'jsonld', raw: val, title: getTitleFromVc(val), platform: 'unknown' }); + }; + add(parsed); + } catch {} + + if (found.length === 0) { + const start = text.indexOf('{'); + const end = text.lastIndexOf('}'); + if (start !== -1 && end !== -1 && end > start) { + const snippet = text.slice(start, end + 1); + try { + const parsed = JSON.parse(snippet); + if (isVc(parsed)) found.push({ source: 'jsonld', raw: parsed, title: getTitleFromVc(parsed), platform: 'unknown' }); + } catch {} + } + } + + if (found.length === 0) { + onStatus?.('No credential found in clipboard'); + return; + } + + const merged = dedupe([...found, ...candidates]); + const resp = await sendMessage({ type: 'credentials-detected', payload: merged, tabId: tabId ?? undefined }); + if ((resp as { ok?: boolean })?.ok) { + await refreshDetected(); + onStatus?.(`Found ${found.length} credential${found.length === 1 ? '' : 's'} from clipboard`); + } else { + onStatus?.('Failed to update detections'); + } + } catch { + onStatus?.('Clipboard read failed. Grant clipboard permission and try again.'); + } finally { + setAnalyzeBusy(false); + } + }; + + const rescan = async () => { + if (tabId === null) return; + onStatus?.('Rescanning page…'); + setRescanBusy(true); + try { + await sendTabMessage(tabId, { type: 'request-scan' }); + const resp = (await sendMessage({ type: 'get-detected', tabId })) as GetDetectedResponse; + if (resp?.ok) { + const list = Array.isArray(resp.data) ? (resp.data as CredentialCandidate[]) : []; + setCandidates(list); + onStatus?.(`Scan complete: ${list.length} credential${list.length === 1 ? '' : 's'} found`); + } else { + onStatus?.('Rescan failed'); + } + } finally { + setRescanBusy(false); + } + }; + + const bulkSave = async (): Promise => { + const selections = selected + .map((v, i) => ({ v, i })) + .filter(({ v, i }) => v && !candidates[i]?.claimed && candidates[i]?.source !== 'link') + .map(({ i }) => ({ index: i, category: categories[i] })); + + if (selections.length === 0) return { ok: false, error: 'No items selected' } as const; + + setSaving(true); + + const msg: SaveCredentialsMessage = { + type: 'save-credentials', + tabId: tabId ?? undefined, + selections, + candidates, + }; + + try { + const resp = (await sendMessage(msg)) as SaveCredentialsResponse; + if (resp?.ok) { + await refreshDetected(); + } + return resp; + } finally { + setSaving(false); + } + }; + + return { + // state + candidates, + selected, + categories, + openCategoryIdx, + hideClaimed, + + rescanBusy, + analyzeBusy, + saving, + + // setters + setSelected, + setCategories, + setOpenCategoryIdx, + setHideClaimed, + + // actions + refreshDetected, + analyzeClipboard, + rescan, + bulkSave, + } as const; +}; diff --git a/apps/learn-card-browser-extension/src/popup/hooks/useUi.ts b/apps/learn-card-browser-extension/src/popup/hooks/useUi.ts new file mode 100644 index 000000000..10164ea37 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/hooks/useUi.ts @@ -0,0 +1,20 @@ +import { useState } from 'react'; + +export const useUi = () => { + const [menuOpen, setMenuOpen] = useState(false); + + const [optsOpen, setOptsOpen] = useState(false); + + const [status, setStatus] = useState(null); + + return { + menuOpen, + setMenuOpen, + + optsOpen, + setOptsOpen, + + status, + setStatus, + } as const; +}; diff --git a/apps/learn-card-browser-extension/src/popup/icons/hamburger.svg b/apps/learn-card-browser-extension/src/popup/icons/hamburger.svg new file mode 100644 index 000000000..3ddeb79e9 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/icons/hamburger.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/learn-card-browser-extension/src/popup/icons/paste-from-clipboard.svg b/apps/learn-card-browser-extension/src/popup/icons/paste-from-clipboard.svg new file mode 100644 index 000000000..338b5a54f --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/icons/paste-from-clipboard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/learn-card-browser-extension/src/popup/icons/refresh.svg b/apps/learn-card-browser-extension/src/popup/icons/refresh.svg new file mode 100644 index 000000000..122bfdb7c --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/icons/refresh.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/learn-card-browser-extension/src/popup/index.html b/apps/learn-card-browser-extension/src/popup/index.html new file mode 100644 index 000000000..3598de844 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/index.html @@ -0,0 +1,13 @@ + + + + + + LearnCard + + + +
+ + + diff --git a/apps/learn-card-browser-extension/src/popup/main.tsx b/apps/learn-card-browser-extension/src/popup/main.tsx new file mode 100644 index 000000000..4f51f1767 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/main.tsx @@ -0,0 +1,429 @@ + // Helper: delegate to shared error utility +const toErrorString = (e: unknown): string => toErrorStringUtil(e); + +import { StrictMode, useEffect } from 'react'; +import { createRoot } from 'react-dom/client'; + +import { toErrorString as toErrorStringUtil } from '../utils/errors'; +import { getTitleFromVc, getIssuerNameFromVc, MinimalVc } from '../utils/vc'; +import CategorySelector from './components/CategorySelector'; +import HeaderBar from './components/HeaderBar'; +import ActionBar from './components/ActionBar'; +import { useActiveTabId } from './hooks/useActiveTabId'; +import { useUi } from './hooks/useUi'; +import { useAuth } from './hooks/useAuth'; +import { useInbox } from './hooks/useInbox'; +import { useExchange } from './hooks/useExchange'; + +const App = () => { + const tabId = useActiveTabId(); + + const { menuOpen, setMenuOpen, optsOpen, setOptsOpen, status, setStatus } = useUi(); + + const { did: authDid, profileImage, loading: authLoading, login, logout } = useAuth(); + + const { + candidates, + selected, + categories, + openCategoryIdx, + hideClaimed, + rescanBusy, + analyzeBusy, + saving, + setSelected, + setCategories, + setOpenCategoryIdx, + setHideClaimed, + refreshDetected, + analyzeClipboard, + rescan, + bulkSave, + } = useInbox(tabId, { onStatus: setStatus }); + + const { + url: exchangeUrl, + state: exchangeState, + offers: exchangeOffers, + selected: offerSelected, + categories: offerCategories, + openCatIdx: offerOpenCatIdx, + busy: exchangeBusy, + error: exchangeError, + autoPrefilled, + show: showExchange, + setUrl: setExchangeUrl, + setSelected: setOfferSelected, + setCategories: setOfferCategories, + setOpenCatIdx: setOfferOpenCatIdx, + setShow: setShowExchange, + start: startExchange, + cancel: cancelExchange, + accept: acceptExchange, + prefillFromCandidates, + } = useExchange(tabId, { onStatus: setStatus }); + + // Category options/labels now imported from ./constants + + // Auto-prefill Exchange URL from detected link candidates when logged in + useEffect(() => { + if (!authDid) return; // only prompt when logged in + prefillFromCandidates(candidates); + }, [authDid, candidates]); + + // When exchange URL is auto-prefilled from page, de-select link candidates in inbox + useEffect(() => { + if (!autoPrefilled) return; + setSelected((prev) => + candidates.map((c, i) => { + if (c.claimed) return false; + if (c.source === 'link') return false; + return typeof prev[i] === 'boolean' ? prev[i] : true; + }) + ); + }, [autoPrefilled, candidates]); + + // VC helpers now imported from ../utils/vc + + const onBulkSave = async () => { + const resp = await bulkSave(); + if ((resp as { ok?: boolean })?.ok) { + const count = (resp as { savedCount?: number }).savedCount ?? 0; + setStatus(`Saved ${count} credential${count === 1 ? '' : 's'} to LearnCard`); + } else { + const err = toErrorString((resp as { error?: string })?.error ?? 'Unknown error'); + setStatus(`Failed: ${err}`); + } + }; + + const onLogin = () => { + setStatus(null); + login().then((resp) => { + if ((resp as { ok?: boolean })?.ok) setStatus('Logged in successfully'); + else setStatus(`Login failed: ${(resp as { error?: string })?.error ?? 'Unknown error'}`); + }); + }; + + const onLogout = () => { + logout().then((resp) => { + if ((resp as { ok?: boolean })?.ok) { + setMenuOpen(false); + setStatus('Logged out'); + } else setStatus(`Logout failed: ${(resp as { error?: string })?.error ?? 'Unknown error'}`); + }); + }; + + const copyDid = async () => { + if (!authDid) return; + try { + await navigator.clipboard.writeText(authDid); + setStatus('DID copied to clipboard'); + setMenuOpen(false); + } catch {} + }; + + // VC-API Exchange helpers + const acceptAndRefresh = () => { + acceptExchange().then((resp) => { + if ((resp as { ok?: boolean })?.ok) refreshDetected(); + }); + }; + + return ( +
+ {/* Header */} + setOptsOpen((v) => !v)} + onToggleMenu={() => setMenuOpen((v) => !v)} + onToggleHideClaimed={(checked) => setHideClaimed(checked)} + onShowExchange={() => { + // Prefill from detected link if empty + if (!exchangeUrl.trim()) { + prefillFromCandidates(candidates); + } + setShowExchange(true); + setOptsOpen(false); + }} + onCopyDid={copyDid} + onLogout={onLogout} + /> + + {/* Body */} +
+ {!authDid ? ( +
+

Welcome to LearnCard

+

Sign in to save credentials directly to your LearnCard wallet.

+ +
+ ) : ( + <> + {/* Exchange Card */} + {(showExchange || exchangeState !== 'idle') && ( +
+ {exchangeState === 'idle' && ( +
+

Claim via VC-API

+

Paste a credential offer URL to start an exchange.

+
+ + setExchangeUrl(e.target.value)} + /> +
+
+ + +
+ {exchangeError &&
{exchangeError}
} +
+ )} + + {exchangeState === 'contacting' && ( +
+

Contacting issuer…

+

Fetching credential offers from the issuer.

+
+ +
+
+ )} + + {exchangeState === 'offer' && ( +
+

Select credentials to claim

+
+ {exchangeOffers.map((vc, i) => { + const typedVc = vc as MinimalVc; + const title = getTitleFromVc(typedVc); + const issuer = getIssuerNameFromVc(typedVc); + const isOpen = offerOpenCatIdx === i; + const cat = offerCategories[i] || 'Achievement'; + return ( +
+ { + const next = offerSelected.slice(); + next[i] = e.target.checked; + setOfferSelected(next); + }} + /> +
+ + + +
+
+

{title}

+

{issuer ? `by ${issuer}` : ''}

+
+ setOfferOpenCatIdx(i)} + onClose={() => setOfferOpenCatIdx(null)} + onSelect={(value) => { + const next = offerCategories.slice(); + next[i] = value; + setOfferCategories(next); + setOfferOpenCatIdx(null); + }} + /> +
+ ); + })} +
+
+ + + +
+
+ )} + + {exchangeState === 'saving' && ( +
+

Claiming…

+

Saving credentials to your LearnCard wallet.

+
+ )} + + {exchangeState === 'success' && ( +
+

Success

+

Credentials were added to your inbox below. You can now manage them like other detections.

+
+ +
+
+ )} + + {exchangeState === 'error' && ( +
+

Something went wrong

+
{exchangeError ?? 'Unknown error'}
+
+ +
+
+ )} +
+ )} + + {/* Inbox */} + {!(showExchange || exchangeState !== 'idle') && (( + (hideClaimed + ? candidates.map((_, i) => i).filter((i) => !candidates[i]?.claimed) + : candidates.map((_, i) => i) + ).filter((i) => candidates[i]?.source !== 'link') + ).length > 0 ? ( +
+
+ {( + hideClaimed + ? candidates.map((_, i) => i).filter((i) => !candidates[i]?.claimed) + : candidates.map((_, i) => i) + ).filter((i) => candidates[i]?.source !== 'link').map((i) => { + const c = candidates[i]; + const raw = c.raw as MinimalVc | undefined; + const title = c.title || (raw ? getTitleFromVc(raw) : c.url || 'Credential'); + const issuer = raw ? getIssuerNameFromVc(raw) : (c.platform ? `from ${c.platform}` : ''); + const cat = categories[i] || 'Achievement'; + const isOpen = openCategoryIdx === i; + return ( +
+ {c.claimed ? ( +
+ + + +
+ ) : ( + { + const next = selected.slice(); + next[i] = e.target.checked; + setSelected(next); + }} + /> + )} +
+ + + +
+
+

{title}

+

+ {issuer ? `by ${issuer}` : ''} + {c.claimed ? ( + + Claimed + + ) : null} +

+
+ {!c.claimed && ( + setOpenCategoryIdx(i)} + onClose={() => setOpenCategoryIdx(null)} + onSelect={(value) => { + const next = categories.slice(); + next[i] = value; + setCategories(next); + setOpenCategoryIdx(null); + }} + /> + )} +
+ ); + })} +
+ {status && ( +
+ {status} +
+ )} +
+ ) : ( +
+

No credentials found

+

The extension is active. Try rescanning the page or analyzing your clipboard.

+
+ ))} + + )} +
+ + {/* Footer / Action bar */} +
+ {authDid && !(showExchange || exchangeState !== 'idle') && ( + setSelected(next)} + saving={saving} + onBulkSave={onBulkSave} + /> + )} +
+
+ ); +}; + +const container = document.getElementById('root')!; +createRoot(container).render( + + + +); diff --git a/apps/learn-card-browser-extension/src/popup/style.css b/apps/learn-card-browser-extension/src/popup/style.css new file mode 100644 index 000000000..047335584 --- /dev/null +++ b/apps/learn-card-browser-extension/src/popup/style.css @@ -0,0 +1,186 @@ +:root{color-scheme:light;} +*{box-sizing:border-box} +html,body{margin:0;padding:0} +#root{display:block} + +/* Design tokens */ +:root{ + --bg: #f8fafc; /* slate-50 */ + --panel: #ffffff; + --text: #1f2937; /* gray-800 */ + --muted: #6b7280; /* gray-500 */ + --accent: #4f46e5; /* indigo-600 */ + --primary: #4338ca; /* indigo-700 */ + --primary-hover: #3730a3; /* indigo-800 */ + --ring: #6366f1; /* indigo-500 */ + --border: #e5e7eb; /* gray-200 */ + --shadow: 0 1px 2px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06); + --radius: 12px; +} + +.popup{ + min-width: 340px; + max-width: 380px; + min-height: 220px; + padding: 16px; + background: var(--bg); + color: var(--text); + font: 14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Inter, Helvetica, Arial, sans-serif; + display: grid; + grid-template-rows: auto 1fr auto; + gap: 12px; +} + +.header{ + display: flex; + align-items: center; + justify-content: space-between; +} +.logo{ + font-weight: 800; + letter-spacing: 0.2px; + color: var(--text); +} +.user{ position: relative; } +.header .user{ display: inline-flex; align-items: center; gap: 8px; } +.header .user .actions{ display: inline-flex; align-items: center; gap: 6px; margin-right: 8px; } +.header .user .options{ position: relative; } +.header .user .options > .menu{ width: 200px; } +.btn-icon{ + appearance: none; border: 1px solid var(--border); background: var(--panel); + color: var(--text); border-radius: 999px; padding: 0; width: 32px; height: 32px; + display: inline-flex; align-items: center; justify-content: center; + box-shadow: var(--shadow); cursor: pointer; + transition: transform .12s ease, background-color .12s ease, border-color .12s ease, box-shadow .12s ease; +} +.btn-icon:hover{ background: #f9fafb; } +.btn-icon:active{ transform: scale(0.96); } +.btn-icon:focus-visible{ outline: none; box-shadow: 0 0 0 2px var(--ring), 0 0 0 4px #fff inset; } +.btn-icon:disabled{ opacity: .6; cursor: default; } +.btn-icon.is-busy{ pointer-events: none; } +.btn-icon.is-busy .icon{ animation: spin .9s linear infinite; } + +@keyframes spin { to { transform: rotate(360deg); } } + +@media (prefers-reduced-motion: reduce){ + .btn-icon{ transition: none; } + .btn-icon.is-busy .icon{ animation: none; } +} +.avatar span{ font-weight: 700; font-size: 12px; } +.avatar img{ width: 100%; height: 100%; display: block; border-radius: 999px; object-fit: cover; } + +.menu{ + position: absolute; top: 38px; right: 0; width: 300px; + background: var(--panel); border: 1px solid var(--border); border-radius: 10px; + box-shadow: var(--shadow); padding: 12px; z-index: 20; +} +.menu-row{ display: grid; gap: 6px; } +.menu-title{ font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; } +.menu-did{ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace; font-size: 11px; color: var(--text); word-break: break-all; } +.menu-actions{ display: flex; gap: 8px; margin-top: 10px; } + +.content{ display: grid; gap: 12px; } +.state{ display: grid; gap: 10px; } +.heading{ margin: 0; font-size: 16px; font-weight: 800; color: var(--text); } +.subtext{ margin: 0; font-size: 12px; color: var(--muted); } + +.card{ + background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); + padding: 12px; display: flex; align-items: center; gap: 12px; box-shadow: var(--shadow); +} +.card-icon{ width: 40px; height: 40px; border-radius: 999px; background: #dcfce7; color: #16a34a; display: flex; align-items: center; justify-content: center; } +.icon{ width: 22px; height: 22px; } +.card-body{ display: grid; gap: 2px; min-width: 0; } +.credential-title{ margin: 0; font-weight: 700; font-size: 14px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.credential-issuer{ margin: 0; font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.hint{ font-size: 12px; color: var(--muted); text-align: center; } + +.footer{ display: grid; grid-auto-flow: column; gap: 8px; } + +.btn-primary{ + appearance: none; width: 100%; + border: 1px solid transparent; border-radius: 10px; + background: var(--primary); color: #fff; + padding: 10px 14px; font-weight: 600; cursor: pointer; + box-shadow: 0 1px 2px rgba(0,0,0,.06); +} +.btn-primary:hover{ background: var(--primary-hover); } +.btn-primary:focus{ outline: none; box-shadow: 0 0 0 2px var(--ring), 0 0 0 4px #fff inset; } + +.btn-secondary{ + appearance: none; width: 100%; + border: 1px solid #d1d5db; border-radius: 10px; + background: #fff; color: #374151; + padding: 10px 14px; font-weight: 600; cursor: pointer; box-shadow: 0 1px 2px rgba(0,0,0,.04); +} +.btn-secondary:hover{ background: #f9fafb; } +.btn-secondary:focus{ outline: none; box-shadow: 0 0 0 2px var(--ring), 0 0 0 4px #fff inset; } + +.status{ font-size: 12px; border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; background: var(--panel); } +.status.ok{ border-color: #bbf7d0; background: #ecfdf5; color: #065f46; } +.status.warn{ border-color: #fde68a; background: #fffbeb; color: #92400e; } + +/* Form */ +.field{ display: grid; gap: 6px; } +.label{ font-size: 12px; color: var(--muted); } +.select{ + appearance: none; + width: 100%; + border: 1px solid var(--border); + border-radius: 8px; + background: #fff; + color: var(--text); + padding: 8px 10px; + font-weight: 500; + box-shadow: 0 1px 2px rgba(0,0,0,.04); +} +.select:focus{ outline: none; box-shadow: 0 0 0 2px var(--ring); } + +/* Inbox */ +.tools{ display: flex; gap: 8px; } +.btn-small{ padding: 6px 10px; font-size: 12px; width: auto; } + +.inbox-list{ display: grid; gap: 8px; max-height: 320px; overflow: auto; padding-right: 4px; } +.inbox-card{ + background: var(--panel); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 10px; + display: grid; + grid-template-columns: 18px 40px 1fr auto; + align-items: center; + gap: 10px; + box-shadow: var(--shadow); +} +.inbox-card .check{ width: 16px; height: 16px; } +.inbox-card .claimed-indicator{ width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #16a34a; } +.inbox-card .claimed-indicator .icon{ width: 16px; height: 16px; } +.category{ position: relative; } +.category-menu{ + position: absolute; + right: 0; + top: 36px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 10px; + box-shadow: var(--shadow); + padding: 6px; + display: grid; + gap: 4px; + z-index: 30; + min-width: 200px; +} +.menu-item{ + appearance: none; + background: transparent; + border: 0; + text-align: left; + padding: 8px 10px; + border-radius: 8px; + cursor: pointer; + color: var(--text); +} +.menu-item:hover{ background: #f3f4f6; } + +.select-all{ display: inline-flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); } +.claimed-label{ font-style: italic; color: var(--muted); margin-left: 8px; } diff --git a/apps/learn-card-browser-extension/src/transformers/__tests__/khan.spec.ts b/apps/learn-card-browser-extension/src/transformers/__tests__/khan.spec.ts new file mode 100644 index 000000000..24e9b392e --- /dev/null +++ b/apps/learn-card-browser-extension/src/transformers/__tests__/khan.spec.ts @@ -0,0 +1,61 @@ +import { describe, it, expect } from 'vitest'; +import { transformCandidate } from '..'; +import type { CredentialCandidate } from '../../types/messages'; +import type { TransformerHelpers } from '../types'; + +describe('khanPlatformToObv3 transformer', () => { + it('creates a self-attested OBv3 AchievementCredential from a khanacademy platform candidate', async () => { + const candidate: CredentialCandidate = { + source: 'platform', + platform: 'khanacademy', + title: 'Completed: Intro to HTML/CSS', + url: 'https://www.khanacademy.org/computing/computer-programming/html-css', + raw: { + platform: 'khanacademy', + event: 'completion', + unitTitle: 'Intro to HTML/CSS', + courseTitle: 'Computer programming', + completedAt: '2025-08-19T20:00:00.000Z', + url: 'https://www.khanacademy.org/computing/computer-programming/html-css', + evidence: [ + { + type: 'Evidence', + name: 'Khan Academy Activity', + description: 'Detected completion event on Khan Academy', + url: 'https://www.khanacademy.org/computing/computer-programming/html-css' + } + ] + } + }; + + const helpers: TransformerHelpers = { + postJson: async () => ({}), + fetchJson: async () => ({}), + getDidAuthVp: async () => ({}), + getDid: async () => 'did:test:123' + }; + + const out = await transformCandidate(candidate, helpers); + expect(out).toBeTruthy(); + const [vc] = out!.vcs as Array>; + + // shape + expect(Array.isArray(vc['@context'] as unknown[])).toBe(true); + const ctx = vc['@context'] as string[]; + expect(ctx).toContain('https://w3id.org/openbadges/v3'); + + const types = vc['type'] as string[]; + expect(types).toContain('AchievementCredential'); + + expect(vc['issuer']).toBe('did:test:123'); + + const cs = vc['credentialSubject'] as Record; + expect(cs['id']).toBe('did:test:123'); + const ach = cs['achievement'] as Record; + expect(typeof ach['name']).toBe('string'); + expect(typeof ach['criteria']).toBe('object'); + + const evidence = vc['evidence'] as unknown[]; + expect(Array.isArray(evidence)).toBe(true); + }); +}); diff --git a/apps/learn-card-browser-extension/src/transformers/__tests__/transformers.spec.ts b/apps/learn-card-browser-extension/src/transformers/__tests__/transformers.spec.ts new file mode 100644 index 000000000..ad3c27a15 --- /dev/null +++ b/apps/learn-card-browser-extension/src/transformers/__tests__/transformers.spec.ts @@ -0,0 +1,94 @@ +import { describe, it, expect } from 'vitest'; +import { transformCandidate } from '..'; +import type { CredentialCandidate } from '../../types/messages'; +import type { TransformerHelpers } from '../types'; + +const makeVc = (overrides: Record = {}) => ({ + '@context': ['https://www.w3.org/2018/credentials/v1'], + type: ['VerifiableCredential'], + name: 'Sample', + ...overrides, +}); + +describe('transformCandidate', () => { + it('passes through JSON-LD VC candidates', async () => { + const candidate: CredentialCandidate = { + source: 'jsonld', + raw: makeVc({ name: 'PassThrough' }), + }; + + const helpers: TransformerHelpers = { + postJson: async () => { throw new Error('not used'); }, + fetchJson: async () => { throw new Error('not used'); }, + getDidAuthVp: async () => ({}) + }; + + const out = await transformCandidate(candidate, helpers); + expect(out).toBeTruthy(); + expect(out?.vcs.length).toBe(1); + expect(out?.vcs[0]).toMatchObject({ name: 'PassThrough' }); + }); + + it('performs VC-API handshake for link candidates', async () => { + const candidate: CredentialCandidate = { + source: 'link', + url: 'https://issuer.example/exchange', + }; + + const vc = makeVc({ name: 'From VC-API' }); + + const helpers: TransformerHelpers = { + postJson: async (url, body) => { + // first call: init returns a VP request with challenge + if ((body as any) && Object.keys(body as any).length === 0) { + return { verifiablePresentationRequest: { challenge: 'abc', domain: 'issuer.example' } }; + } + // second call: finalize returns a VP with credentials + return { verifiablePresentation: { verifiableCredential: [vc] } }; + }, + fetchJson: async () => { throw new Error('should not hit fetch fallback'); }, + getDidAuthVp: async () => ({ /* vp-jwt-or-object */ }) + }; + + const out = await transformCandidate(candidate, helpers); + expect(out).toBeTruthy(); + expect(out?.vcs.length).toBe(1); + expect(out?.vcs[0]).toMatchObject({ name: 'From VC-API' }); + }); + + it('falls back to fetching a VC as JSON', async () => { + const candidate: CredentialCandidate = { + source: 'link', + url: 'https://issuer.example/vc.json', + }; + + const vc = makeVc({ name: 'From Fetch' }); + + const helpers: TransformerHelpers = { + postJson: async () => { throw new Error('init/finalize fails'); }, + fetchJson: async () => vc, + getDidAuthVp: async () => ({}) + }; + + const out = await transformCandidate(candidate, helpers); + expect(out).toBeTruthy(); + expect(out?.vcs.length).toBe(1); + expect(out?.vcs[0]).toMatchObject({ name: 'From Fetch' }); + }); + + it('returns null when no transformer can produce VCs', async () => { + const candidate: CredentialCandidate = { + source: 'link', + url: 'https://issuer.example/unknown', + }; + + const helpers: TransformerHelpers = { + postJson: async () => { throw new Error('nope'); }, + fetchJson: async () => ({ not: 'a vc' }), + getDidAuthVp: async () => ({}) + }; + + const out = await transformCandidate(candidate, helpers); + expect(out).toBeNull(); + }); +}); diff --git a/apps/learn-card-browser-extension/src/transformers/index.ts b/apps/learn-card-browser-extension/src/transformers/index.ts new file mode 100644 index 000000000..fbbe93c91 --- /dev/null +++ b/apps/learn-card-browser-extension/src/transformers/index.ts @@ -0,0 +1,126 @@ +import type { CredentialCandidate } from '../types/messages'; +import { isVc } from '../utils/vc'; +import type { Transformer, TransformerHelpers, TransformResult } from './types'; + +// JSON-LD pass-through transformer +const jsonldPassThrough: Transformer = { + id: 'jsonld-pass-through', + canTransform: (c) => c.source === 'jsonld' && !!c.raw && isVc(c.raw), + transform: async (c) => ({ vcs: [c.raw as unknown] }) +}; + +// Generic VC-API + fetch fallback transformer for link candidates +const vcapiOrFetch: Transformer = { + id: 'vcapi-or-fetch', + canTransform: (c) => typeof c.url === 'string' && /^https?:\/\//i.test(c.url), + transform: async (c, helpers: TransformerHelpers): Promise => { + const url = c.url!; + + // Try VC-API handshake first + try { + const initJson = await helpers.postJson(url, {}); + const vpr = initJson?.verifiablePresentationRequest ?? initJson; + const challenge = vpr?.challenge ?? vpr?.nonce; + const domain = vpr?.domain; + if (challenge) { + const vp = await helpers.getDidAuthVp({ challenge, domain }); + const finalize = await helpers.postJson(url, { verifiablePresentation: vp }); + if (finalize && typeof finalize === 'object') { + const result = finalize as any; + if (result.ok === false) throw new Error(result.message || 'VC-API error'); + const vpOut = result?.verifiablePresentation ?? result?.vp ?? result; + const vcsRaw = vpOut?.verifiableCredential ?? vpOut?.verifiableCredentials; + const list: unknown[] = Array.isArray(vcsRaw) ? vcsRaw : vcsRaw ? [vcsRaw] : []; + if (list.length) { + const parsed = list.map((vc) => (typeof vc === 'string' ? JSON.parse(vc) : vc)); + return { vcs: parsed }; + } + } + } + } catch { + // ignore and try fetch fallback + } + + // Fallback: GET JSON and check if it's a VC + try { + const data = await helpers.fetchJson(url); + if (isVc(data)) return { vcs: [data] }; + } catch { + // ignore + } + + return null; + } +}; + +// Khan Academy platform transformer -> self-attested OBv3 VC +const khanPlatformToObv3: Transformer = { + id: 'khan-platform-obv3', + canTransform: (c) => c.source === 'platform' && c.platform === 'khanacademy', + transform: async (c, helpers: TransformerHelpers): Promise => { + const now = new Date().toISOString(); + const did = (await helpers.getDid?.()) || 'did:example:anonymous'; + + const raw = (c.raw as Record) || {}; + const unitTitle = typeof raw['unitTitle'] === 'string' ? raw['unitTitle'] : undefined; + const courseTitle = typeof raw['courseTitle'] === 'string' ? raw['courseTitle'] : undefined; + const completedAt = typeof raw['completedAt'] === 'string' ? raw['completedAt'] : now; + const evidence = Array.isArray(raw['evidence']) ? raw['evidence'] : []; + const url = typeof c.url === 'string' ? c.url : typeof raw['url'] === 'string' ? (raw['url'] as string) : undefined; + + const achievementName = unitTitle || courseTitle || 'Khan Academy Activity'; + const description = unitTitle || courseTitle + ? `Completed Khan Academy activity: ${unitTitle || courseTitle}` + : 'Completed Khan Academy activity'; + + const vc = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/openbadges/v3' + ], + type: ['VerifiableCredential', 'AchievementCredential'], + name: c.title || `Khan Academy: ${achievementName}`, + issuer: did, + issuanceDate: completedAt, + credentialSubject: { + id: did, + achievement: { + type: 'Achievement', + name: achievementName, + description, + criteria: { + type: 'Criteria', + narrative: 'Detected completion event on Khan Academy', + id: url + } + } + }, + evidence + } as const; + + return { vcs: [vc] }; + } +}; + +const REGISTRY: Transformer[] = [ + jsonldPassThrough, + khanPlatformToObv3, + vcapiOrFetch +]; + +export const transformCandidate = async ( + candidate: CredentialCandidate, + helpers: TransformerHelpers +): Promise => { + for (const t of REGISTRY) { + try { + if (t.canTransform(candidate)) { + const res = await t.transform(candidate, helpers); + if (res && Array.isArray(res.vcs) && res.vcs.length > 0) return res; + } + } catch { + // try next transformer + } + } + return null; +}; diff --git a/apps/learn-card-browser-extension/src/transformers/types.ts b/apps/learn-card-browser-extension/src/transformers/types.ts new file mode 100644 index 000000000..3a8e5be03 --- /dev/null +++ b/apps/learn-card-browser-extension/src/transformers/types.ts @@ -0,0 +1,18 @@ +import type { CredentialCandidate } from '../types/messages'; + +export type TransformerHelpers = { + postJson: (url: string, body: unknown) => Promise; + fetchJson: (url: string) => Promise; + getDidAuthVp: (args: { challenge: string; domain?: string }) => Promise; + getDid?: () => Promise; +}; + +export type TransformResult = { + vcs: unknown[]; +}; + +export type Transformer = { + id: string; + canTransform: (candidate: CredentialCandidate) => boolean; + transform: (candidate: CredentialCandidate, helpers: TransformerHelpers) => Promise; +}; diff --git a/apps/learn-card-browser-extension/src/types/assets.d.ts b/apps/learn-card-browser-extension/src/types/assets.d.ts new file mode 100644 index 000000000..80688ed2f --- /dev/null +++ b/apps/learn-card-browser-extension/src/types/assets.d.ts @@ -0,0 +1,6 @@ +/// + +declare module '*.wasm?url' { + const src: string; + export default src; +} diff --git a/apps/learn-card-browser-extension/src/types/messages.ts b/apps/learn-card-browser-extension/src/types/messages.ts new file mode 100644 index 000000000..03e92889a --- /dev/null +++ b/apps/learn-card-browser-extension/src/types/messages.ts @@ -0,0 +1,142 @@ +export type CredentialSource = 'link' | 'jsonld' | 'platform'; + +export type CredentialCandidate = { + source: CredentialSource; + title?: string; + url?: string; + raw?: unknown; + platform?: 'credly' | 'coursera' | 'khanacademy' | 'unknown'; + claimed?: boolean; +}; + +export type CredentialCategory = + | 'Achievement' + | 'ID' + | 'Learning History' + | 'Work History' + | 'Social Badge' + | 'Accomplishment' + | 'Accommodation'; + +// VC-API exchange types +export type VcApiExchangeState = 'idle' | 'contacting' | 'offer' | 'saving' | 'success' | 'error'; + +export type VcApiOffer = { + id?: string; + title: string; + description?: string; +}; + +export type StartVcApiExchangeMessage = { + type: 'start-vcapi-exchange'; + url: string; + tabId?: number; +}; + +export type GetVcApiExchangeStatusMessage = { + type: 'get-vcapi-exchange-status'; + tabId?: number; +}; + +export type AcceptVcApiOfferMessage = { + type: 'accept-vcapi-offer'; + selections: { index: number; category?: CredentialCategory }[]; + tabId?: number; +}; + +export type CancelVcApiExchangeMessage = { + type: 'cancel-vcapi-exchange'; + tabId?: number; +}; + +export type CredentialDetectedMessage = { + type: 'credential-detected'; + payload: CredentialCandidate; +}; + +export type CredentialsDetectedMessage = { + type: 'credentials-detected'; + payload: CredentialCandidate[]; + tabId?: number; +}; + +export type GetDetectedMessage = { + type: 'get-detected'; + tabId?: number; +}; + +export type SaveCredentialMessage = { + type: 'save-credential'; + tabId?: number; + category?: CredentialCategory; +}; + +export type BulkSaveSelection = { + index: number; + category?: CredentialCategory; +}; + +export type SaveCredentialsMessage = { + type: 'save-credentials'; + tabId?: number; + selections: BulkSaveSelection[]; + candidates?: CredentialCandidate[]; +}; + +export type StartAuthMessage = { + type: 'start-auth'; +}; + +export type GetAuthStatusMessage = { + type: 'get-auth-status'; +}; + +export type LogoutMessage = { + type: 'logout'; +}; + +export type RequestScanMessage = { + type: 'request-scan'; +}; + +export type GetProfileMessage = { + type: 'get-profile'; +}; + +export type ExtensionMessage = + | CredentialDetectedMessage + | CredentialsDetectedMessage + | GetDetectedMessage + | SaveCredentialMessage + | SaveCredentialsMessage + | StartAuthMessage + | GetAuthStatusMessage + | LogoutMessage + | RequestScanMessage + | GetProfileMessage + | StartVcApiExchangeMessage + | GetVcApiExchangeStatusMessage + | AcceptVcApiOfferMessage + | CancelVcApiExchangeMessage; + +// Typed response envelopes per message +export type OkResponse = { ok: true }; +export type ErrorResponse = { ok: false; error: string }; + +export type CredentialDetectedResponse = OkResponse | ErrorResponse; +export type CredentialsDetectedResponse = OkResponse | ErrorResponse; +export type GetDetectedResponse = ({ ok: true; data: CredentialCandidate[] } | ErrorResponse); +export type SaveCredentialResponse = OkResponse | ErrorResponse; +export type SaveCredentialsResponse = ({ ok: true; savedCount: number } | ErrorResponse); +export type StartAuthResponse = ({ ok: true; data: { did: string | undefined } } | ErrorResponse); +export type GetAuthStatusResponse = ({ ok: true; data: { loggedIn: boolean; did: string | null } } | ErrorResponse); +export type LogoutResponse = OkResponse | ErrorResponse; +export type GetProfileResponse = ({ ok: true; profile?: { image?: string | null } } | ErrorResponse); +export type RequestScanResponse = OkResponse | ErrorResponse; +export type StartVcApiExchangeResponse = OkResponse | ErrorResponse; +export type GetVcApiExchangeStatusResponse = ({ + ok: true; + data: { state: VcApiExchangeState; url?: string; offers?: unknown[]; error?: string | null }; +} | ErrorResponse); +export type AcceptVcApiOfferResponse = ({ ok: true; savedCount: number } | ErrorResponse); +export type CancelVcApiExchangeResponse = OkResponse | ErrorResponse; diff --git a/apps/learn-card-browser-extension/src/utils/dom.ts b/apps/learn-card-browser-extension/src/utils/dom.ts new file mode 100644 index 000000000..2f90b1d3b --- /dev/null +++ b/apps/learn-card-browser-extension/src/utils/dom.ts @@ -0,0 +1,35 @@ +// DOM-related utilities that can be used by content scripts + +export const debounce = void>(fn: T, wait = 200) => { + let t: number | undefined; + return (...args: Parameters) => { + if (t) window.clearTimeout(t); + t = window.setTimeout(() => fn(...args), wait); + }; +}; + +export const installLocationChangeHook = () => { + // Idempotent + const anyHistory = history as History & { __lcHooked?: boolean }; + if (anyHistory.__lcHooked) return; + + const pushState = history.pushState; + history.pushState = function (this: History, ...args) { + const ret = pushState.apply(this, args as unknown as any); + window.dispatchEvent(new Event('locationchange')); + return ret; + } as typeof history.pushState; + + const replaceState = history.replaceState; + history.replaceState = function (this: History, ...args) { + const ret = replaceState.apply(this, args as unknown as any); + window.dispatchEvent(new Event('locationchange')); + return ret; + } as typeof history.replaceState; + + window.addEventListener('popstate', () => { + window.dispatchEvent(new Event('locationchange')); + }); + + anyHistory.__lcHooked = true; +}; diff --git a/apps/learn-card-browser-extension/src/utils/errors.ts b/apps/learn-card-browser-extension/src/utils/errors.ts new file mode 100644 index 000000000..765ef4325 --- /dev/null +++ b/apps/learn-card-browser-extension/src/utils/errors.ts @@ -0,0 +1,18 @@ +// Error utilities shared across popup/background/content + +export const toErrorString = (e: unknown): string => { + try { + if (!e) return 'Unknown error'; + if (typeof e === 'string') return e; + if (e instanceof Error) return e.message || String(e); + if (typeof e === 'object') { + const anyE = e as any; + if (typeof anyE.message === 'string') return anyE.message; + if (typeof anyE.error === 'string') return anyE.error; + return JSON.stringify(anyE); + } + return String(e); + } catch { + return 'Unknown error'; + } +}; diff --git a/apps/learn-card-browser-extension/src/utils/hash.ts b/apps/learn-card-browser-extension/src/utils/hash.ts new file mode 100644 index 000000000..3a1d46129 --- /dev/null +++ b/apps/learn-card-browser-extension/src/utils/hash.ts @@ -0,0 +1,44 @@ +import stringify from 'json-stringify-deterministic'; +import pbkdf2Hmac from 'pbkdf2-hmac'; + +// Minimal LearnCard-like shape used for hashing +export type LearnCardForHash = { + invoke: { + hash?: (message: string, algorithm?: string) => Promise; + crypto: () => Crypto; + }; + id: { + keypair: (type: string) => { d: string }; + }; +}; + +export const canonicalStringify = (obj: unknown): string => stringify(obj as any); + +export const saltedHash = async (learnCard: LearnCardForHash, message: string): Promise => { + const lcHash = await learnCard?.invoke?.hash?.(message, 'PBKDF2-HMAC-SHA256'); + if (lcHash) return lcHash; + + const crypto = learnCard.invoke.crypto(); + + const pk = learnCard.id.keypair('secp256k1').d; + const hmacKey = await pbkdf2Hmac(pk, 'salt', 1000, 32); + const cryptoKey = await crypto.subtle.importKey( + 'raw', + hmacKey, + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign'] + ); + + const uint8Message = new TextEncoder().encode(message); + const digestBuffer = await crypto.subtle.sign('HMAC', cryptoKey, uint8Message); + const digestArray = Array.from(new Uint8Array(digestBuffer)); + return digestArray.map((byte) => (byte as any).toString(16).padStart(2, '0')).join(''); +}; + +export const computeCredentialHash = async ( + learnCard: LearnCardForHash, + vc: unknown +): Promise => { + return saltedHash(learnCard, canonicalStringify(vc)); +}; diff --git a/apps/learn-card-browser-extension/src/utils/links.ts b/apps/learn-card-browser-extension/src/utils/links.ts new file mode 100644 index 000000000..204d6a67c --- /dev/null +++ b/apps/learn-card-browser-extension/src/utils/links.ts @@ -0,0 +1,49 @@ +// Link extraction utilities for VC-API exchange URLs +// Centralized to keep content/background logic consistent. + +export type LinkExtractor = { + protocols?: string[]; + hosts?: string[]; + paramKeys: string[]; +}; + +export const LINK_EXTRACTORS: readonly LinkExtractor[] = [ + { + // Examples: msprequest://request?vc_request_url=... + // dccrequest://request?vc_request_url=... + // Note: includes https to allow vc_request_url or related params directly on https links + protocols: ['msprequest', 'dccrequest', 'https', 'asuprequest'], + paramKeys: ['vc_request_url', 'request_url', 'exchange_url', 'vc_url'], + }, +]; + +export const DEFAULT_PARAM_KEYS = ['vc_request_url', 'request_uri', 'exchange_url', 'vc_url']; + +export const getExtractorProtocols = () => + Array.from(new Set(LINK_EXTRACTORS.flatMap((e) => e.protocols ?? []))); + +export const extractExchangeUrlFromLink = (href: string): string | null => { + try { + const u = new URL(href); + const proto = u.protocol.replace(':', ''); + const host = u.hostname; + const extractor = LINK_EXTRACTORS.find( + (e) => (e.protocols && e.protocols.includes(proto)) || (e.hosts && e.hosts.includes(host)) + ); + const keys = Array.from(new Set([...(extractor?.paramKeys ?? []), ...DEFAULT_PARAM_KEYS])); + for (const k of keys) { + const val = u.searchParams.get(k); + if (val) { + try { + const dec = decodeURIComponent(val); + return dec; + } catch { + return val; + } + } + } + return null; + } catch { + return null; + } +}; diff --git a/apps/learn-card-browser-extension/src/utils/platform.ts b/apps/learn-card-browser-extension/src/utils/platform.ts new file mode 100644 index 000000000..8634f0c17 --- /dev/null +++ b/apps/learn-card-browser-extension/src/utils/platform.ts @@ -0,0 +1,17 @@ +export type Platform = 'credly' | 'coursera' | 'khanacademy' | 'unknown'; + +export const detectPlatformFromHostname = (hostname: string): Platform => { + if (/credly\.com$/i.test(hostname) || /(^|\.)credly\.com$/i.test(hostname)) return 'credly'; + if (/coursera\.org$/i.test(hostname) || /(^|\.)coursera\.org$/i.test(hostname)) return 'coursera'; + if (/khanacademy\.org$/i.test(hostname) || /(^|\.)khanacademy\.org$/i.test(hostname)) return 'khanacademy'; + return 'unknown'; +}; + +export const detectPlatformFromUrl = (url: string): Platform => { + try { + const { hostname } = new URL(url); + return detectPlatformFromHostname(hostname); + } catch { + return 'unknown'; + } +}; diff --git a/apps/learn-card-browser-extension/src/utils/vc.ts b/apps/learn-card-browser-extension/src/utils/vc.ts new file mode 100644 index 000000000..eac5fba4b --- /dev/null +++ b/apps/learn-card-browser-extension/src/utils/vc.ts @@ -0,0 +1,36 @@ +// VC helpers shared between content and popup + +export type MinimalVc = { + '@context'?: unknown[] | string; + type?: string | string[]; + name?: string; + issuer?: string; + credentialSubject?: { name?: string; issuer?: string } & Record; + boostCredential?: { + name?: string; + issuer?: string; + credentialSubject?: { name?: string; issuer?: string } & Record; + } & Record; +} & Record; + +export const isVc = (data: unknown): data is MinimalVc => { + if (!data || typeof data !== 'object') return false; + const obj = data as Record; + const ctx = obj['@context'] as unknown; + const type = obj['type'] as unknown; + const ctxOk = Array.isArray(ctx) || typeof ctx === 'string'; + const typeOk = Array.isArray(type) || typeof type === 'string'; + return ctxOk && typeOk; +}; + +export const getTitleFromVc = (vc: MinimalVc): string => { + const b = vc.boostCredential; + if (b) return b.name || b.credentialSubject?.name || 'Credential'; + return vc.name || vc.credentialSubject?.name || 'Credential'; +}; + +export const getIssuerNameFromVc = (vc: MinimalVc): string => { + const b = vc.boostCredential; + if (b) return b.issuer || b.credentialSubject?.issuer || 'Unknown'; + return vc.issuer || vc.credentialSubject?.issuer || 'Unknown'; +}; diff --git a/apps/learn-card-browser-extension/src/vite-env.d.ts b/apps/learn-card-browser-extension/src/vite-env.d.ts new file mode 100644 index 000000000..211834eb6 --- /dev/null +++ b/apps/learn-card-browser-extension/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +// Allow importing SVG files as URLs +declare module '*.svg' { + const src: string; + export default src; +} diff --git a/apps/learn-card-browser-extension/tsconfig.json b/apps/learn-card-browser-extension/tsconfig.json new file mode 100644 index 000000000..867c76a4a --- /dev/null +++ b/apps/learn-card-browser-extension/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM"], + "jsx": "react-jsx", + "jsxImportSource": "react", + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["chrome", "react", "react-dom"] + }, + "include": ["src", "vite.config.ts"] +} diff --git a/apps/learn-card-browser-extension/vite.config.ts b/apps/learn-card-browser-extension/vite.config.ts new file mode 100644 index 000000000..ee15c842b --- /dev/null +++ b/apps/learn-card-browser-extension/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { crx } from '@crxjs/vite-plugin'; +import manifest from './src/manifest.json'; + +export default defineConfig({ + plugins: [react(), crx({ manifest })], + build: { + outDir: 'dist', + sourcemap: true, + target: 'es2020', + rollupOptions: { + input: { + offscreen: 'src/offscreen.html' + } + } + } +}); diff --git a/package.json b/package.json index 7c5ec7b83..23c0e2ee6 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "devDependencies": { "@changesets/changelog-github": "^0.4.8", "@changesets/cli": "^2.26.0", + "@nx/react": "16.1.4", + "@nx/vite": "16.1.4", "@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/parser": "^5.54.0", "esbuild-jest": "^0.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d574be71..d35b8d231 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,12 @@ importers: '@changesets/cli': specifier: ^2.26.0 version: 2.28.1 + '@nx/react': + specifier: 16.1.4 + version: 16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2)(webpack@5.98.0(esbuild@0.25.1)) + '@nx/vite': + specifier: 16.1.4 + version: 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2)(vite@5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0))(vitest@1.6.1(@types/node@18.19.83)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0)) '@typescript-eslint/eslint-plugin': specifier: ^5.54.0 version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2) @@ -61,7 +67,7 @@ importers: version: 7.37.4(eslint@8.57.1) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + version: 29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) jest-environment-jsdom: specifier: ^29.5.0 version: 29.7.0 @@ -79,16 +85,62 @@ importers: version: 0.5.5 ts-jest: specifier: ^29.0.5 - version: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.25.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.25.1)(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2) tslib: specifier: ^2.5.0 version: 2.8.1 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.2)(vite@5.4.15(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0)) + version: 4.3.2(typescript@5.6.2)(vite@5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0)) vitest: specifier: ^1.4.0 - version: 1.6.1(@types/node@22.13.14)(happy-dom@14.12.3)(jsdom@20.0.3)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) + version: 1.6.1(@types/node@18.19.83)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) + + apps/learn-card-browser-extension: + dependencies: + '@learncard/init': + specifier: ^2.0.40 + version: 2.0.40(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(serverless@3.39.0)(socks@2.8.4)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8) + json-stringify-deterministic: + specifier: ^1.0.8 + version: 1.0.12 + pbkdf2-hmac: + specifier: ^1.2.1 + version: 1.2.1 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@crxjs/vite-plugin': + specifier: ^2.0.0 + version: 2.1.0 + '@types/chrome': + specifier: ^0.0.278 + version: 0.0.278 + '@types/react': + specifier: ^18.3.11 + version: 18.3.23 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.5(@types/react@18.3.23) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@5.4.15(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0)) + jsdom: + specifier: ^26.1.0 + version: 26.1.0 + typescript: + specifier: 5.6.2 + version: 5.6.2 + vite: + specifier: ^5.4.15 + version: 5.4.15(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) + vitest: + specifier: ^1.6.1 + version: 1.6.1(@types/node@22.13.14)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) docs: dependencies: @@ -97,7 +149,7 @@ importers: version: 2.1.0(@docusaurus/types@2.4.3(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/preset-classic': specifier: 2.1.0 - version: 2.1.0(@algolia/client-search@5.23.0)(@types/react@17.0.84)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2) + version: 2.1.0(@algolia/client-search@5.23.0)(@types/react@18.3.23)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2) '@mdx-js/react': specifier: ^1.6.22 version: 1.6.22(react@18.3.1) @@ -137,7 +189,7 @@ importers: dependencies: '@astrojs/react': specifier: ^3.3.1 - version: 3.6.3(@types/node@22.13.14)(@types/react-dom@18.3.5(@types/react@17.0.84))(@types/react@17.0.84)(less@4.2.2)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.39.0) + version: 3.6.3(@types/node@22.13.14)(@types/react-dom@18.3.5(@types/react@18.3.23))(@types/react@18.3.23)(less@4.2.2)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.39.0) '@astrojs/tailwind': specifier: ^5.1.0 version: 5.1.5(astro@4.16.18(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(rollup@4.37.0)(terser@5.39.0)(typescript@5.6.2))(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) @@ -208,7 +260,7 @@ importers: dependencies: '@astrojs/react': specifier: ^1.1.3 - version: 1.2.2(@types/react-dom@18.3.5(@types/react@17.0.84))(@types/react@17.0.84)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@astrojs/tailwind': specifier: ^2.0.1 version: 2.1.3(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2))) @@ -806,7 +858,7 @@ importers: version: 17.0.45 aqu: specifier: 0.4.3 - version: 0.4.3(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(@types/node@17.0.45)(babel-jest@29.7.0(@babel/core@7.26.10))(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + version: 0.4.3(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(@types/node@17.0.45)(babel-jest@29.7.0(@babel/core@7.26.10))(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) esbuild: specifier: ^0.17.17 version: 0.17.19 @@ -2561,7 +2613,7 @@ importers: version: 10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8) '@metamask/eslint-config-jest': specifier: ^10.0.0 - version: 10.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2))(eslint@8.57.1) + version: 10.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2))(eslint@8.57.1) '@metamask/eslint-config-nodejs': specifier: ^8.0.0 version: 8.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-node@11.1.0(eslint@8.57.1))(eslint@8.57.1) @@ -2805,6 +2857,9 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@astrojs/compiler@0.19.0': resolution: {integrity: sha512-8nvyxZTfCXLyRmYfTttpJT6EPhfBRg0/q4J/Jj3/pNPLzp+vs05ZdktsY6QxAREaOMAnNEtSqcrB4S5DsXOfRg==} @@ -3967,10 +4022,41 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@crxjs/vite-plugin@2.1.0': + resolution: {integrity: sha512-7PkVsg9ar/QyXJvi9aHekrpWpxf6xHOH6bDQPfLbwsyAGzzmDrI5naXsJeNvQw6WPyHexQRk8gSwXmjfOyMmIg==} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@csstools/color-helpers@5.0.2': + resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.0.10': + resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + '@didtools/cacao@1.2.0': resolution: {integrity: sha512-y0nMgV8DL0jgHUq0uhjMqrW9p79PopQnugLWx02tss+iR0ahON2cfal20+eFx2p3kXtvaL8U+iByrjmyuokj+A==} engines: {node: '>=14.14'} @@ -5999,6 +6085,78 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@learncard/chapi-plugin@1.0.69': + resolution: {integrity: sha512-/fv7BvpU/gYxmHu2UJ2elhB5g6RT+QFSG+5L5uwVcKj3qfkOi1vK93CWdu+b3IiBJyGCm+7ZuJFI33DsTWw+JQ==} + + '@learncard/core@9.3.35': + resolution: {integrity: sha512-A5HXfTrnx90jLPQKSY+qx+sVJMbbidnqQL1Fk0Ke0TSDZK6z01r84t4dVfckRvfyz/WLC0AVlfT1RQPPxSBw2A==} + + '@learncard/crypto-plugin@1.0.46': + resolution: {integrity: sha512-b1ueS1gLUjsi0b0W06irBtYaHwMJTsoZCuz/2SqmYZroiL3x/HR4k3bcO/PAM8Byffv6ePzrA3MmPPgyYs7XiA==} + + '@learncard/did-web-plugin@1.0.72': + resolution: {integrity: sha512-pjXqeomKvAEvRkCeq88B2jtgqsWTmNSyLeSmbHogoJoVtp3nWyJ7BkCi5WjidA54lN5WgUHRH4CL+dUXX7QKHg==} + + '@learncard/didkey-plugin@1.0.46': + resolution: {integrity: sha512-/NXe9gLXDnHPmZbePTL0m2JHt7TUsGvRJHW7JqNJdHEojj9/qSsAbufmobdjre42e0hV9UEPfeSq8MobNWI1lA==} + + '@learncard/didkit-plugin@1.5.27': + resolution: {integrity: sha512-89Iqx27MP3Qnomkaub7CoxCU7wYpD0PtkXnO/VlvSTUbOb0127+TwZP4GPZ580kFpDHtjnF9Nw9N1zAiQUKPBg==} + + '@learncard/dynamic-loader-plugin@1.0.42': + resolution: {integrity: sha512-u6CIB7suqcBDeqEZIQ1F/0IE0F7b3Xpm71uMFn+jf75vGidw2yugkPAxt/EOmq4lUkF7Zso3vn5zlqs1lUEb/g==} + + '@learncard/encryption-plugin@1.0.20': + resolution: {integrity: sha512-0wOYNkpk1V821O6VWW+VFUU6lKTS78EZtkhIreE/Vf31CChgkUPuEAQeWR9IAVUi8DMclTmYYkHbvndwziguaA==} + + '@learncard/ethereum-plugin@1.0.47': + resolution: {integrity: sha512-Wge0viarMJPjQ4PMYvsRX97+7FaHEjYZo/AsB65AA64/LBTa3dPLFy2ImaoD68YPGI1kQjau5LWpsH26t6+6eA==} + + '@learncard/expiration-plugin@1.1.57': + resolution: {integrity: sha512-aiTdzQQh4G+fKrwWQGDc7Eugg3QjxyhbCu270VhVrFhaNxRLuvDslUnlwccDENOKYAXBW92OnZeTPjqzTT4vcg==} + + '@learncard/helpers@1.1.23': + resolution: {integrity: sha512-zPqbbG1baeh5Bpxfv2vKPeEr+kU1S1bdsHmuqZ6Bt5u/+wQ8usk4qlj3/jVn5VzNuLziOOl53v1VbxzJGrNenQ==} + + '@learncard/init@2.0.40': + resolution: {integrity: sha512-QsVUGyMnYlx5hG25otXQ8LmLlJ03rYVYvnDjrwlf7JCnGGA88rb8uiwxQP178MomTHqZyo7BhchAf/rSWpuw7A==} + + '@learncard/learn-card-plugin@1.1.55': + resolution: {integrity: sha512-bxOmlC+eW6ORHIj3Bgs3ZXj8xbtLBt0a80+hZOk0jdCGvus0X21l9A0RkxxfcwssvgGLKXDSHNI+tYB7mR0vXQ==} + + '@learncard/learn-cloud-client@1.4.15': + resolution: {integrity: sha512-vNgSMCuXAFUofYHdzWGpiFiGkkyx7JVcN/Xj8+NbBMIzBbuJHjUqkkIN6W6+W2m+Q9ws34aCwkF51OACTKE7ug==} + + '@learncard/learn-cloud-plugin@2.1.40': + resolution: {integrity: sha512-B4+tYFhWyA9I81DVqDfkVNPZgBx3R2PSfbRC+n1DZoChh+1KTlHbkqxl4eT/3d6DrW4UZHXqW+ZdkiMM5FfJ1g==} + + '@learncard/learn-cloud-service@2.3.15': + resolution: {integrity: sha512-fNcOkIJe5n9swzV8Ytkk4lR8HHcBBfqtjTfFpDlMIR2/H6lA+xV83XRaQALr0ypFDvPObEarBRlul5+4PEGjsQ==} + + '@learncard/network-brain-client@2.3.22': + resolution: {integrity: sha512-dHR/1jfwMWAdq4oBweeREP69SpY5aTI66d1nQav0t3aR8HsetsqPWliUj0aJZ8NLk2RoMf3O9FJi6rSEXJAGDQ==} + + '@learncard/network-brain-service@3.5.16': + resolution: {integrity: sha512-KNVYSykv3lBiYPuUL+2+vPBo8tL3jS0O1oGpc+spebJ8M73CGnmrAfTnHrI+LaqC67xXy6civvG6sBXhjc+Wow==} + + '@learncard/network-plugin@2.5.17': + resolution: {integrity: sha512-ygsY9gHHQekeAVtT/1FJ++1e883xeUvrvmbsACuDIIl3m1K1Ti7sbGDd0VmNoXT3A51LPhhFChC6qMG0tdpcvw==} + + '@learncard/types@5.8.3': + resolution: {integrity: sha512-xQ9vUdR3oMF9eLO1oH4oOzoPa43vq7yrdkK+hk3V/DmpXIN+5fIAtN233OzNQhPmyLBCGbIANRWCUcRQZrNS1g==} + + '@learncard/vc-api-plugin@1.0.46': + resolution: {integrity: sha512-rXYc3La3jGMy17J3NPGuvXBMRH23I316z6w5Z+6snZkZFqZ/U/jlk4B2GXPfk1h4rRgPZYEfzIX9RUkaTlF2Xw==} + + '@learncard/vc-plugin@1.1.57': + resolution: {integrity: sha512-vGEsjm7xf8t56tlwPvWWjB8U5oPY6xdhLuaq5NWgfbhZO/7TlMjn0fS1AY1aglMNvWARu8ADHsOgCxUfyRTpoQ==} + + '@learncard/vc-templates-plugin@1.0.60': + resolution: {integrity: sha512-xzJ7w2xC/QYCELr3BCydesYmFY6OVe/5asPvmyo/XDtm3+WwawAW3/sYeVbhPn9GpJ6ybVDkJE1qRLCWtv7v4Q==} + + '@learncard/vpqr-plugin@1.0.46': + resolution: {integrity: sha512-w7IbNJPv5s30IXWpmeRwcQTEK1/8Tru/ZEfhjQxxV+i6AAnH6XcOhncKAGyKld6Su5r4jPGLK4DoKiA2RaYpag==} + '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} @@ -6172,13 +6330,50 @@ packages: peerDependencies: nx: '>= 14.1 <= 16' + '@nrwl/devkit@16.1.4': + resolution: {integrity: sha512-ojFu+fmOja+Kt7TZ7T+GmLkPw9z/sIAbmMx1V6ePIsS2XdB68Tc4UyXiQpx18hCwIpOxFf2BatoeLIgledWsXw==} + + '@nrwl/js@16.1.4': + resolution: {integrity: sha512-U5R61b4CBo65xt2pOGYz0POZ4Xqq5vifdKzzrD/vYycak28tTHlhAkZLexXhzLJiUJh94Dack0Osb+FuZmbYWQ==} + + '@nrwl/linter@16.1.4': + resolution: {integrity: sha512-r0nKnC4EsGFVZO9caYDJ6y8wJLhZMj3TRi+Arf/1ALKEPYTAv8rrqgqf8t+4IiBzxY8DDausIwHfa3e4T3JH4g==} + + '@nrwl/react@16.1.4': + resolution: {integrity: sha512-oIklnal/jWd2v/XulAQfhs3Czg72Z58Si9hk0qWi2XDWrVVjRaX/swqDVaq8cJaRnnPJ04icj0DYquLlfvoyow==} + '@nrwl/tao@16.1.4': resolution: {integrity: sha512-aArX3E7j+foUUzutsrgOc1lh9Rj5LDCAncHlszu0XGgHRO2EHE4qxZHvmgogGEpRf1ojTNAfN72xhVCSjtca/Q==} hasBin: true + '@nrwl/vite@16.1.4': + resolution: {integrity: sha512-NJxMNN4lY36YVqQoxxrERXhstB0l60eNdRRahNVcXtMjdYN1kzRi9avA1evvl9lnk+Q0W21TRetuCnEA9yViJA==} + + '@nrwl/web@16.1.4': + resolution: {integrity: sha512-9x/4GL5fXmpQo3PTmJ4PFiUlRnECh4KKM0PijBx1MfWbrA5jLagUsMbCMs/v1ryjvXvhG2xeaNcOtFGBioIS4Q==} + + '@nrwl/workspace@16.1.4': + resolution: {integrity: sha512-B2qyR2tYykZ5rNeA9klNiEQ7y9rNMGQdnxyp+f6w7nMu8Qtpa1tMtqCyTORhShupRgcwON3oEVn5TznC9T2ZDA==} + '@nuintun/qrcode@3.4.0': resolution: {integrity: sha512-ZsxHR8NLGN/FYiGzyQ/sQEOxCIS1pmHDIGaAisQXibUb0x3LHAvObNJKoXAhaMrNnzEKQj8q9fXYeaJurmNrnw==} + '@nx/devkit@16.1.4': + resolution: {integrity: sha512-tyAnpQShhKhR6FwmT7hJTaT/8B8YxFWhgBW0mLi9PhXYS9xRdgZ+ag8/T3EtJudIGMIdn4JhA1YL2zSuziHABQ==} + peerDependencies: + nx: '>= 15 <= 17' + + '@nx/js@16.1.4': + resolution: {integrity: sha512-LYPKvV7AjYvj25p7SVKny22DKVpGVfZ1y+EkmmqWEMvFSEmf7miOCjZEyZGv30CcxFRVDoZ7JZ16TB3S+Zy/gQ==} + + '@nx/linter@16.1.4': + resolution: {integrity: sha512-wxgyOvQJuBUCU0n6YY7QSgCFGUPTqoGp/zXfrcpDYOZZPYEygENw3Y7NPDrffXvJOcNh2Wb5z0ZaVH5sp2LYNw==} + peerDependencies: + eslint: ^8.0.0 + peerDependenciesMeta: + eslint: + optional: true + '@nx/nx-darwin-arm64@16.1.4': resolution: {integrity: sha512-0eITl+18xLtdiAVlv+LRukCHBLSLk8L8OkMnfLoK286sIblK31p2pzr1jL68ILUWPGGbdgo+nDEaaDTwh4tYRA==} engines: {node: '>= 10'} @@ -6233,6 +6428,21 @@ packages: cpu: [x64] os: [win32] + '@nx/react@16.1.4': + resolution: {integrity: sha512-juQ6xCiChZQMMmSz2iu58zxlj9r8RiJyhCEgKySgaYpeNdO2kxRva2HcpdhngC6/Hvy24tctFTyGA4IBkvIZ0A==} + + '@nx/vite@16.1.4': + resolution: {integrity: sha512-jrRCzGvkW7Qp5cjvS/m6s/kNlWJbZhyPN+iGDgU0IbdH/ZtatNcmSKuxEvS5m+vM0kiDffdg6FnzFgKID5nXCQ==} + peerDependencies: + vite: ^4.3.4 + vitest: '>=0.31.0 <1.0.0' + + '@nx/web@16.1.4': + resolution: {integrity: sha512-xqAgT06GVUS3ZAXsmECQ8PW/s48nabnk9ggtyQgp/7ng6Y41XPJDzaujq2BMlEeoEuK3IltCuX5ULUv5urp/5Q==} + + '@nx/workspace@16.1.4': + resolution: {integrity: sha512-FjzCmbitepS1pH8fqK2I2W3pbJ20+om8rFAQhMUkdJEtio+AXgHdyRfrkjlmNWXME1E0qZc7R0GzD6tILFb0Ew==} + '@octokit/auth-token@2.5.0': resolution: {integrity: sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==} @@ -6375,6 +6585,11 @@ packages: resolution: {integrity: sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==} engines: {node: '>=10.12.0'} + '@phenomnomnominal/tsquery@5.0.1': + resolution: {integrity: sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==} + peerDependencies: + typescript: 5.6.2 + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -8001,6 +8216,9 @@ packages: resolution: {integrity: sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA==} engines: {node: '>=10'} + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@szmarczak/http-timer@1.1.2': resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} engines: {node: '>=6'} @@ -8139,6 +8357,9 @@ packages: '@types/chrome@0.0.136': resolution: {integrity: sha512-XDEiRhLkMd+SB7Iw3ZUIj/fov3wLd4HyTdLltVszkgl1dBfc3Rb7oPMVZ2Mz2TLqnF7Ow+StbR8E7r9lqpb4DA==} + '@types/chrome@0.0.278': + resolution: {integrity: sha512-PDIJodOu7o54PpSOYLybPW/MDZBCjM1TKgf31I3Q/qaEbNpIH09rOM3tSEH3N7Q+FAqb1933LhF8ksUPYeQLNg==} + '@types/connect-history-api-fallback@1.5.4': resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} @@ -8418,6 +8639,9 @@ packages: '@types/react@17.0.84': resolution: {integrity: sha512-DtgToBBNtUTNokPYGCShoDfbEtv2a0XnL1OVnShFU2d8wZ3EfI8nRwzVOeYxKUZdHdl++eX8Fmka7pDr6X+0xw==} + '@types/react@18.3.23': + resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==} + '@types/readdir-glob@1.1.5': resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} @@ -8697,6 +8921,9 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@webcomponents/custom-elements@1.6.0': + resolution: {integrity: sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==} + '@xapi/xapi@3.0.1': resolution: {integrity: sha512-qbgIZcB+yHP26EnQn8vdsdxyPOAnqKOakeq9FBaivhaOATiK3Iw/668bcepBm3ja+3XhwKom3Ay3H3UpBXwNKg==} @@ -9322,6 +9549,11 @@ packages: peerDependencies: '@babel/core': ^7.11.6 + babel-plugin-const-enum@1.2.0: + resolution: {integrity: sha512-o1m/6iyyFnp9MRsK1dHF3bneqyf3AlM2q3A/YbgQr2pCat6B6XJVDv2TXqzfY2RYUi4mak6WAksSBPlyYGx9dg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + babel-plugin-dynamic-import-node@2.3.3: resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} @@ -9344,6 +9576,9 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-macros@2.8.0: + resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==} + babel-plugin-polyfill-corejs2@0.4.13: resolution: {integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==} peerDependencies: @@ -9368,6 +9603,15 @@ packages: babel-plugin-transform-flow-enums@0.0.2: resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} + babel-plugin-transform-typescript-metadata@0.3.2: + resolution: {integrity: sha512-mWEvCQTgXQf48yDqgN7CH50waTyYBeP2Lpqx4nNWab9sxEpdXVeKgfj1qYI2/TgUPQtNFZ85i3PemRtnXVYYJg==} + peerDependencies: + '@babel/core': ^7 + '@babel/traverse': ^7 + peerDependenciesMeta: + '@babel/traverse': + optional: true + babel-preset-current-node-syntax@1.1.0: resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} peerDependencies: @@ -9494,6 +9738,10 @@ packages: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} + basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} @@ -10491,6 +10739,10 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + corser@2.0.1: + resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==} + engines: {node: '>= 0.4.0'} + cosmiconfig@5.2.1: resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==} engines: {node: '>=4'} @@ -10691,6 +10943,10 @@ packages: resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} engines: {node: '>=8'} + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -10726,6 +10982,10 @@ packages: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -13160,6 +13420,10 @@ packages: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + html-entities@2.5.3: resolution: {integrity: sha512-D3AfvN7SjhTgBSA8L1BN4FpPzuEd06uy4lHwSoRWr0lndi9BKaNzPLKGOWZ2ocSGguozr08TTb2jhCLHaemruw==} @@ -13247,6 +13511,11 @@ packages: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} + http-server@14.1.1: + resolution: {integrity: sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==} + engines: {node: '>=12'} + hasBin: true + http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} @@ -14450,6 +14719,15 @@ packages: canvas: optional: true + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsep@1.4.0: resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} engines: {node: '>= 10.16.0'} @@ -16884,6 +17162,10 @@ packages: resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} + portfinder@1.0.37: + resolution: {integrity: sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==} + engines: {node: '>= 10.12'} + posix-character-classes@0.1.1: resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} engines: {node: '>=0.10.0'} @@ -17679,6 +17961,10 @@ packages: peerDependencies: react: ^17.0.2 + react-refresh@0.13.0: + resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==} + engines: {node: '>=0.10.0'} + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -18252,6 +18538,9 @@ packages: resolution: {integrity: sha512-kzk1OflbBckfDBAo8JwsmtQSHzj+6hxRt5G+u8A8ZSmunBw1nhWvRkSq8j1+EvWBqBRLy1aiGLUW5644CZqQtA==} engines: {node: '>=14.14'} + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + rsvp@4.8.5: resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==} engines: {node: 6.* || >= 7.*} @@ -18284,6 +18573,9 @@ packages: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} + rxjs@7.5.7: + resolution: {integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -18409,6 +18701,9 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} + secure-compare@3.0.1: + resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} + secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} @@ -18841,6 +19136,9 @@ packages: source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map-support@0.5.19: + resolution: {integrity: sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -19241,7 +19539,7 @@ packages: superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} @@ -19497,6 +19795,13 @@ packages: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -19572,6 +19877,10 @@ packages: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -19997,6 +20306,10 @@ packages: resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} engines: {node: '>=0.10.0'} + union@0.5.0: + resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} + engines: {node: '>= 0.8.0'} + unique-filename@3.0.0: resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -20160,6 +20473,9 @@ packages: resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} deprecated: Please see https://github.com/lydell/urix#deprecated + url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + url-loader@4.1.1: resolution: {integrity: sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==} engines: {node: '>= 10.13.0'} @@ -20566,6 +20882,10 @@ packages: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + wait-on@6.0.1: resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==} engines: {node: '>=10.0.0'} @@ -20905,6 +21225,10 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xml-parse-from-string@1.0.1: resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} @@ -21259,6 +21583,14 @@ snapshots: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + '@astrojs/compiler@0.19.0': {} '@astrojs/compiler@0.23.5': {} @@ -21384,10 +21716,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/react@3.6.3(@types/node@22.13.14)(@types/react-dom@18.3.5(@types/react@17.0.84))(@types/react@17.0.84)(less@4.2.2)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.39.0)': + '@astrojs/react@1.2.2(@types/react-dom@18.3.5(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@types/react': 17.0.84 - '@types/react-dom': 18.3.5(@types/react@17.0.84) + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.10) + '@types/react': 18.3.23 + '@types/react-dom': 18.3.5(@types/react@18.3.23) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - supports-color + + '@astrojs/react@3.6.3(@types/node@22.13.14)(@types/react-dom@18.3.5(@types/react@18.3.23))(@types/react@18.3.23)(less@4.2.2)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.39.0)': + dependencies: + '@types/react': 18.3.23 + '@types/react-dom': 18.3.5(@types/react@18.3.23) '@vitejs/plugin-react': 4.3.4(vite@5.4.15(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22568,7 +22911,6 @@ snapshots: '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.10) transitivePeerDependencies: - supports-color - optional: true '@babel/plugin-proposal-export-default-from@7.25.9(@babel/core@7.26.10)': dependencies: @@ -22585,7 +22927,7 @@ snapshots: '@babel/plugin-proposal-object-rest-spread@7.12.1(@babel/core@7.12.9)': dependencies: '@babel/core': 7.12.9 - '@babel/helper-plugin-utils': 7.10.4 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.9) '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.12.9) @@ -22635,7 +22977,6 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - optional: true '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.26.10)': dependencies: @@ -23583,10 +23924,51 @@ snapshots: '@colors/colors@1.5.0': optional: true + '@crxjs/vite-plugin@2.1.0': + dependencies: + '@rollup/pluginutils': 4.2.1 + '@webcomponents/custom-elements': 1.6.0 + acorn-walk: 8.3.4 + cheerio: 1.0.0 + convert-source-map: 1.9.0 + debug: 4.4.0(supports-color@8.1.1) + es-module-lexer: 0.10.5 + fast-glob: 3.3.3 + fs-extra: 10.1.0 + jsesc: 3.1.0 + magic-string: 0.30.17 + pathe: 2.0.3 + picocolors: 1.1.1 + react-refresh: 0.13.0 + rollup: 2.79.2 + rxjs: 7.5.7 + transitivePeerDependencies: + - supports-color + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@csstools/color-helpers@5.0.2': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.0.2 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + '@didtools/cacao@1.2.0': dependencies: '@ipld/dag-cbor': 7.0.3 @@ -23947,14 +24329,14 @@ snapshots: '@docsearch/css@3.9.0': {} - '@docsearch/react@3.9.0(@algolia/client-search@5.23.0)(@types/react@17.0.84)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': + '@docsearch/react@3.9.0(@algolia/client-search@5.23.0)(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': dependencies: '@algolia/autocomplete-core': 1.17.9(@algolia/client-search@5.23.0)(algoliasearch@5.23.0)(search-insights@2.17.3) '@algolia/autocomplete-preset-algolia': 1.17.9(@algolia/client-search@5.23.0)(algoliasearch@5.23.0) '@docsearch/css': 3.9.0 algoliasearch: 5.23.0 optionalDependencies: - '@types/react': 17.0.84 + '@types/react': 18.3.23 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) search-insights: 2.17.3 @@ -24343,7 +24725,7 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-debug@2.1.0(@types/react@17.0.84)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)': + '@docusaurus/plugin-debug@2.1.0(@types/react@18.3.23)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)': dependencies: '@docusaurus/core': 2.1.0(@docusaurus/types@2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/types': 2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -24351,7 +24733,7 @@ snapshots: fs-extra: 10.1.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-json-view: 1.21.3(@types/react@17.0.84)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-json-view: 1.21.3(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tslib: 2.8.1 transitivePeerDependencies: - '@parcel/css' @@ -24456,19 +24838,19 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/preset-classic@2.1.0(@algolia/client-search@5.23.0)(@types/react@17.0.84)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2)': + '@docusaurus/preset-classic@2.1.0(@algolia/client-search@5.23.0)(@types/react@18.3.23)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2)': dependencies: '@docusaurus/core': 2.1.0(@docusaurus/types@2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/plugin-content-blog': 2.1.0(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/plugin-content-docs': 2.1.0(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/plugin-content-pages': 2.1.0(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) - '@docusaurus/plugin-debug': 2.1.0(@types/react@17.0.84)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) + '@docusaurus/plugin-debug': 2.1.0(@types/react@18.3.23)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/plugin-google-analytics': 2.1.0(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/plugin-google-gtag': 2.1.0(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/plugin-sitemap': 2.1.0(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/theme-classic': 2.1.0(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/theme-common': 2.1.0(@docusaurus/types@2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) - '@docusaurus/theme-search-algolia': 2.1.0(@algolia/client-search@5.23.0)(@docusaurus/types@2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@17.0.84)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2) + '@docusaurus/theme-search-algolia': 2.1.0(@algolia/client-search@5.23.0)(@docusaurus/types@2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.23)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2) '@docusaurus/types': 2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -24496,7 +24878,7 @@ snapshots: '@docusaurus/react-loadable@5.5.2(react@18.3.1)': dependencies: - '@types/react': 17.0.84 + '@types/react': 18.3.23 prop-types: 15.8.1 react: 18.3.1 @@ -24584,9 +24966,9 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-search-algolia@2.1.0(@algolia/client-search@5.23.0)(@docusaurus/types@2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@17.0.84)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2)': + '@docusaurus/theme-search-algolia@2.1.0(@algolia/client-search@5.23.0)(@docusaurus/types@2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.23)(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2)': dependencies: - '@docsearch/react': 3.9.0(@algolia/client-search@5.23.0)(@types/react@17.0.84)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) + '@docsearch/react': 3.9.0(@algolia/client-search@5.23.0)(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) '@docusaurus/core': 2.1.0(@docusaurus/types@2.1.0(esbuild@0.25.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@docusaurus/logger': 2.1.0 '@docusaurus/plugin-content-docs': 2.1.0(esbuild@0.25.1)(eslint@8.57.1)(lightningcss@1.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) @@ -26379,41 +26761,6 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 18.19.83 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - '@jest/create-cache-key-function@29.7.0': dependencies: '@jest/types': 29.6.3 @@ -26997,6 +27344,420 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@learncard/chapi-plugin@1.0.69': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/didkit-plugin': 1.5.27 + credential-handler-polyfill: 3.2.1 + web-credential-handler: 2.0.2 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/core@9.3.35': + dependencies: + '@learncard/helpers': 1.1.23 + abort-controller: 3.0.0 + core-js: 3.44.0 + isomorphic-webcrypto: 2.3.8(expo@52.0.41(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(react-native@0.78.1(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(react@18.3.1))(react@18.3.1))(react-native@0.78.1(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(react@18.3.1)) + transitivePeerDependencies: + - expo + - react-native + + '@learncard/crypto-plugin@1.0.46': + dependencies: + '@learncard/core': 9.3.35 + isomorphic-webcrypto: 2.3.8(expo@52.0.41(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(react-native@0.78.1(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(react@18.3.1))(react@18.3.1))(react-native@0.78.1(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(react@18.3.1)) + transitivePeerDependencies: + - expo + - react-native + + '@learncard/did-web-plugin@1.0.72': + dependencies: + '@learncard/core': 9.3.35 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/didkey-plugin@1.0.46': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/helpers': 1.1.23 + hex-lite: 1.5.0 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/didkit-plugin@1.5.27': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/types': 5.8.3 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/dynamic-loader-plugin@1.0.42': + dependencies: + '@learncard/core': 9.3.35 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/encryption-plugin@1.0.20': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/types': 5.8.3 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/ethereum-plugin@1.0.47': + dependencies: + '@learncard/core': 9.3.35 + '@uniswap/default-token-list': 4.1.0 + ethers: 5.8.0 + transitivePeerDependencies: + - bufferutil + - expo + - react-native + - utf-8-validate + + '@learncard/expiration-plugin@1.1.57': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/vc-plugin': 1.1.57 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/helpers@1.1.23': + dependencies: + '@learncard/types': 5.8.3 + '@trpc/server': 10.45.2 + + '@learncard/init@2.0.40(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(serverless@3.39.0)(socks@2.8.4)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8)': + dependencies: + '@learncard/chapi-plugin': 1.0.69 + '@learncard/core': 9.3.35 + '@learncard/crypto-plugin': 1.0.46 + '@learncard/did-web-plugin': 1.0.72 + '@learncard/didkey-plugin': 1.0.46 + '@learncard/didkit-plugin': 1.5.27 + '@learncard/dynamic-loader-plugin': 1.0.42 + '@learncard/encryption-plugin': 1.0.20 + '@learncard/ethereum-plugin': 1.0.47 + '@learncard/expiration-plugin': 1.1.57 + '@learncard/helpers': 1.1.23 + '@learncard/learn-card-plugin': 1.1.55 + '@learncard/learn-cloud-plugin': 2.1.40(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(serverless@3.39.0)(socks@2.8.4)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8) + '@learncard/network-plugin': 2.5.17(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8) + '@learncard/types': 5.8.3 + '@learncard/vc-api-plugin': 1.0.46 + '@learncard/vc-plugin': 1.1.57 + '@learncard/vc-templates-plugin': 1.0.60 + '@learncard/vpqr-plugin': 1.0.46 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - '@trpc/server' + - '@types/express' + - '@types/ioredis-mock' + - '@types/node' + - aws-crt + - bufferutil + - debug + - encoding + - expo + - gcp-metadata + - kerberos + - mongodb-client-encryption + - react-native + - serverless + - snappy + - socks + - supports-color + - typescript + - utf-8-validate + - zod + - zod-openapi + + '@learncard/learn-card-plugin@1.1.55': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/didkit-plugin': 1.5.27 + '@learncard/types': 5.8.3 + date-fns: 2.30.0 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/learn-cloud-client@1.4.15(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(serverless@3.39.0)(socks@2.8.4)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8)': + dependencies: + '@learncard/learn-cloud-service': 2.3.15(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(serverless@3.39.0)(socks@2.8.4)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8) + '@trpc/client': 11.3.0(@trpc/server@11.3.0(typescript@5.6.2))(typescript@5.6.2) + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - '@trpc/server' + - '@types/express' + - '@types/ioredis-mock' + - '@types/node' + - aws-crt + - bufferutil + - debug + - encoding + - expo + - gcp-metadata + - kerberos + - mongodb-client-encryption + - react-native + - serverless + - snappy + - socks + - supports-color + - typescript + - utf-8-validate + - zod + - zod-openapi + + '@learncard/learn-cloud-plugin@2.1.40(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(serverless@3.39.0)(socks@2.8.4)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8)': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/didkit-plugin': 1.5.27 + '@learncard/helpers': 1.1.23 + '@learncard/learn-cloud-client': 1.4.15(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(serverless@3.39.0)(socks@2.8.4)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8) + json-stringify-deterministic: 1.0.12 + lodash: 4.17.21 + pbkdf2-hmac: 1.2.1 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - '@trpc/server' + - '@types/express' + - '@types/ioredis-mock' + - '@types/node' + - aws-crt + - bufferutil + - debug + - encoding + - expo + - gcp-metadata + - kerberos + - mongodb-client-encryption + - react-native + - serverless + - snappy + - socks + - supports-color + - typescript + - utf-8-validate + - zod + - zod-openapi + + '@learncard/learn-cloud-service@2.3.15(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(serverless@3.39.0)(socks@2.8.4)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8)': + dependencies: + '@fastify/cors': 9.0.1 + '@fastify/static': 7.0.4 + '@learncard/core': 9.3.35 + '@learncard/crypto-plugin': 1.0.46 + '@learncard/did-web-plugin': 1.0.72 + '@learncard/didkey-plugin': 1.0.46 + '@learncard/didkit-plugin': 1.5.27 + '@learncard/encryption-plugin': 1.0.20 + '@learncard/expiration-plugin': 1.1.57 + '@learncard/helpers': 1.1.23 + '@learncard/learn-card-plugin': 1.1.55 + '@learncard/types': 5.8.3 + '@learncard/vc-plugin': 1.1.57 + '@learncard/vc-templates-plugin': 1.0.60 + '@sentry/esbuild-plugin': 2.5.0 + '@sentry/serverless': 7.61.0 + '@trpc/server': 11.3.0(typescript@5.6.2) + '@types/lodash': 4.17.16 + '@xapi/xapi': 3.0.1 + async: 3.2.6 + bson: 4.7.2 + cors: 2.8.5 + dotenv: 16.4.7 + express: 4.21.2 + fastify: 4.29.0 + ioredis: 5.6.0 + ioredis-mock: 8.9.0(@types/ioredis-mock@8.2.5)(ioredis@5.6.0) + json-stringify-deterministic: 1.0.12 + jsonwebtoken: 9.0.2 + jwt-decode: 3.1.2 + libsodium-wrappers: 0.7.15 + lodash: 4.17.21 + mongodb: 6.15.0(socks@2.8.4) + multiformats: 11.0.2 + neo4j-driver: 4.4.11 + neogma: 1.14.1 + serverless-offline: 12.0.4(serverless@3.39.0) + simple-redis-mutex: 1.4.0(ioredis@5.6.0) + trpc-openapi: 1.2.0(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/node@22.13.14)(zod@3.23.8) + trpc-to-openapi: 2.3.1(@trpc/server@11.3.0(typescript@5.6.2))(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8) + tsc-alias: 1.8.11 + uuid: 9.0.1 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - '@types/express' + - '@types/ioredis-mock' + - '@types/node' + - aws-crt + - bufferutil + - debug + - encoding + - expo + - gcp-metadata + - kerberos + - mongodb-client-encryption + - react-native + - serverless + - snappy + - socks + - supports-color + - typescript + - utf-8-validate + - zod + - zod-openapi + + '@learncard/network-brain-client@2.3.22(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8)': + dependencies: + '@learncard/network-brain-service': 3.5.16(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8) + '@trpc/client': 11.3.0(@trpc/server@11.3.0(typescript@5.6.2))(typescript@5.6.2) + transitivePeerDependencies: + - '@trpc/server' + - '@types/express' + - '@types/ioredis-mock' + - '@types/node' + - aws-crt + - debug + - encoding + - expo + - react-native + - supports-color + - typescript + - zod + - zod-openapi + + '@learncard/network-brain-service@3.5.16(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8)': + dependencies: + '@aws-sdk/client-sqs': 3.775.0 + '@digitalcredentials/issuer-registry-client': 3.2.0-beta.5 + '@fastify/cors': 9.0.1 + '@fastify/static': 7.0.4 + '@fastify/swagger': 8.15.0 + '@fastify/swagger-ui': 4.2.0 + '@learncard/core': 9.3.35 + '@learncard/crypto-plugin': 1.0.46 + '@learncard/did-web-plugin': 1.0.72 + '@learncard/didkey-plugin': 1.0.46 + '@learncard/didkit-plugin': 1.5.27 + '@learncard/encryption-plugin': 1.0.20 + '@learncard/expiration-plugin': 1.1.57 + '@learncard/helpers': 1.1.23 + '@learncard/learn-card-plugin': 1.1.55 + '@learncard/types': 5.8.3 + '@learncard/vc-plugin': 1.1.57 + '@learncard/vc-templates-plugin': 1.0.60 + '@sentry/esbuild-plugin': 2.16.0 + '@sentry/serverless': 7.61.0 + '@trpc/server': 11.3.0(typescript@5.6.2) + '@types/lodash': 4.17.16 + base64url: 3.0.1 + cors: 2.8.5 + dotenv: 16.4.7 + express: 4.21.2 + fastify: 4.29.0 + fastify-plugin: 4.5.1 + ioredis: 5.6.0 + ioredis-mock: 8.9.0(@types/ioredis-mock@8.2.5)(ioredis@5.6.0) + jwt-decode: 3.1.2 + libsodium-wrappers: 0.7.15 + lodash: 4.17.21 + messagebird: 4.0.1 + multiformats: 11.0.2 + neo4j-driver: 5.28.1 + neogma: 1.14.1 + postmark: 4.0.5 + sift: 17.1.3 + trpc-openapi: 1.2.0(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/node@22.13.14)(zod@3.23.8) + trpc-to-openapi: 2.3.1(@trpc/server@11.3.0(typescript@5.6.2))(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8) + tsc-alias: 1.8.11 + twilio: 5.7.1 + uuid: 9.0.1 + transitivePeerDependencies: + - '@types/express' + - '@types/ioredis-mock' + - '@types/node' + - aws-crt + - debug + - encoding + - expo + - react-native + - supports-color + - typescript + - zod + - zod-openapi + + '@learncard/network-plugin@2.5.17(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8)': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/helpers': 1.1.23 + '@learncard/network-brain-client': 2.3.22(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/ioredis-mock@8.2.5)(@types/node@22.13.14)(typescript@5.6.2)(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8) + transitivePeerDependencies: + - '@trpc/server' + - '@types/express' + - '@types/ioredis-mock' + - '@types/node' + - aws-crt + - debug + - encoding + - expo + - react-native + - supports-color + - typescript + - zod + - zod-openapi + + '@learncard/types@5.8.3': {} + + '@learncard/vc-api-plugin@1.0.46': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/types': 5.8.3 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/vc-plugin@1.1.57': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/didkit-plugin': 1.5.27 + '@learncard/types': 5.8.3 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/vc-templates-plugin@1.0.60': + dependencies: + '@learncard/core': 9.3.35 + '@learncard/types': 5.8.3 + transitivePeerDependencies: + - expo + - react-native + + '@learncard/vpqr-plugin@1.0.46': + dependencies: + '@digitalbazaar/vpqr': 3.0.0 + '@learncard/core': 9.3.35 + '@learncard/types': 5.8.3 + transitivePeerDependencies: + - expo + - react-native + '@leichtgewicht/ip-codec@2.0.5': {} '@ljharb/has-package-exports-patterns@0.0.2': {} @@ -27050,7 +27811,7 @@ snapshots: '@mdx-js/react@2.3.0(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 17.0.84 + '@types/react': 18.3.23 react: 18.3.1 '@mdx-js/util@1.6.22': {} @@ -27064,11 +27825,11 @@ snapshots: '@metamask/detect-provider@1.2.0': {} - '@metamask/eslint-config-jest@10.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2))(eslint@8.57.1)': + '@metamask/eslint-config-jest@10.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2))(eslint@8.57.1)': dependencies: '@metamask/eslint-config': 10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8) eslint: 8.57.1 - eslint-plugin-jest: 26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2) + eslint-plugin-jest: 26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2) '@metamask/eslint-config-nodejs@8.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-node@11.1.0(eslint@8.57.1))(eslint@8.57.1)': dependencies: @@ -27236,6 +27997,51 @@ snapshots: tmp: 0.2.3 tslib: 2.8.1 + '@nrwl/devkit@16.1.4(nx@16.1.4)': + dependencies: + '@nx/devkit': 16.1.4(nx@16.1.4) + transitivePeerDependencies: + - nx + + '@nrwl/js@16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2)': + dependencies: + '@nx/js': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2) + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - nx + - supports-color + - typescript + + '@nrwl/linter@16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2)': + dependencies: + '@nx/linter': 16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2) + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - eslint + - nx + - supports-color + - typescript + + '@nrwl/react@16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2)(webpack@5.98.0(esbuild@0.25.1))': + dependencies: + '@nx/react': 16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2)(webpack@5.98.0(esbuild@0.25.1)) + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - eslint + - nx + - supports-color + - typescript + - webpack + '@nrwl/tao@16.1.4': dependencies: nx: 16.1.4 @@ -27244,10 +28050,107 @@ snapshots: - '@swc/core' - debug + '@nrwl/vite@16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2)(vite@5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0))(vitest@1.6.1(@types/node@18.19.83)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0))': + dependencies: + '@nx/vite': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2)(vite@5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0))(vitest@1.6.1(@types/node@18.19.83)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0)) + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - nx + - supports-color + - typescript + - vite + - vitest + + '@nrwl/web@16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2)': + dependencies: + '@nx/web': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2) + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - nx + - supports-color + - typescript + + '@nrwl/workspace@16.1.4': + dependencies: + '@nx/workspace': 16.1.4 + transitivePeerDependencies: + - '@swc-node/register' + - '@swc/core' + - debug + '@nuintun/qrcode@3.4.0': dependencies: tslib: 2.8.1 + '@nx/devkit@16.1.4(nx@16.1.4)': + dependencies: + '@nrwl/devkit': 16.1.4(nx@16.1.4) + ejs: 3.1.10 + ignore: 5.3.2 + nx: 16.1.4 + semver: 7.3.4 + tmp: 0.2.3 + tslib: 2.8.1 + + '@nx/js@16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2)': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.26.10) + '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-runtime': 7.26.10(@babel/core@7.26.10) + '@babel/preset-env': 7.26.9(@babel/core@7.26.10) + '@babel/preset-typescript': 7.27.0(@babel/core@7.26.10) + '@babel/runtime': 7.27.0 + '@nrwl/js': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2) + '@nx/devkit': 16.1.4(nx@16.1.4) + '@nx/workspace': 16.1.4 + '@phenomnomnominal/tsquery': 5.0.1(typescript@5.6.2) + babel-plugin-const-enum: 1.2.0(@babel/core@7.26.10) + babel-plugin-macros: 2.8.0 + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.10)(@babel/traverse@7.27.0) + chalk: 4.1.2 + fast-glob: 3.2.7 + fs-extra: 11.3.0 + ignore: 5.3.2 + js-tokens: 4.0.0 + minimatch: 3.0.5 + source-map-support: 0.5.19 + tree-kill: 1.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - nx + - supports-color + - typescript + + '@nx/linter@16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2)': + dependencies: + '@nrwl/linter': 16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2) + '@nx/devkit': 16.1.4(nx@16.1.4) + '@nx/js': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2) + '@phenomnomnominal/tsquery': 5.0.1(typescript@5.6.2) + tmp: 0.2.3 + tslib: 2.8.1 + optionalDependencies: + eslint: 8.57.1 + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - nx + - supports-color + - typescript + '@nx/nx-darwin-arm64@16.1.4': optional: true @@ -27275,6 +28178,95 @@ snapshots: '@nx/nx-win32-x64-msvc@16.1.4': optional: true + '@nx/react@16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2)(webpack@5.98.0(esbuild@0.25.1))': + dependencies: + '@nrwl/react': 16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2)(webpack@5.98.0(esbuild@0.25.1)) + '@nx/devkit': 16.1.4(nx@16.1.4) + '@nx/js': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2) + '@nx/linter': 16.1.4(@babel/traverse@7.27.0)(eslint@8.57.1)(nx@16.1.4)(typescript@5.6.2) + '@nx/web': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2) + '@phenomnomnominal/tsquery': 5.0.1(typescript@5.6.2) + '@svgr/webpack': 6.5.1 + chalk: 4.1.2 + file-loader: 6.2.0(webpack@5.98.0(esbuild@0.25.1)) + minimatch: 3.0.5 + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - eslint + - nx + - supports-color + - typescript + - webpack + + '@nx/vite@16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2)(vite@5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0))(vitest@1.6.1(@types/node@18.19.83)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0))': + dependencies: + '@nrwl/vite': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2)(vite@5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0))(vitest@1.6.1(@types/node@18.19.83)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0)) + '@nx/devkit': 16.1.4(nx@16.1.4) + '@nx/js': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2) + '@phenomnomnominal/tsquery': 5.0.1(typescript@5.6.2) + '@swc/helpers': 0.5.17 + dotenv: 10.0.0 + enquirer: 2.3.6 + vite: 5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) + vitest: 1.6.1(@types/node@18.19.83)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - nx + - supports-color + - typescript + + '@nx/web@16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2)': + dependencies: + '@nrwl/web': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2) + '@nx/devkit': 16.1.4(nx@16.1.4) + '@nx/js': 16.1.4(@babel/traverse@7.27.0)(nx@16.1.4)(typescript@5.6.2) + chalk: 4.1.2 + chokidar: 3.6.0 + http-server: 14.1.1 + ignore: 5.3.2 + tslib: 2.8.1 + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - nx + - supports-color + - typescript + + '@nx/workspace@16.1.4': + dependencies: + '@nrwl/workspace': 16.1.4 + '@nx/devkit': 16.1.4(nx@16.1.4) + '@parcel/watcher': 2.0.4 + chalk: 4.1.2 + chokidar: 3.6.0 + cli-cursor: 3.1.0 + cli-spinners: 2.6.1 + dotenv: 10.0.0 + figures: 3.2.0 + flat: 5.0.2 + ignore: 5.3.2 + minimatch: 3.0.5 + npm-run-path: 4.0.1 + nx: 16.1.4 + open: 8.4.2 + rxjs: 7.8.2 + tmp: 0.2.3 + tslib: 2.8.1 + yargs: 17.7.2 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - '@swc-node/register' + - '@swc/core' + - debug + '@octokit/auth-token@2.5.0': dependencies: '@octokit/types': 6.41.0 @@ -27437,6 +28429,11 @@ snapshots: tslib: 2.8.1 webcrypto-core: 1.8.1 + '@phenomnomnominal/tsquery@5.0.1(typescript@5.6.2)': + dependencies: + esquery: 1.6.0 + typescript: 5.6.2 + '@pkgjs/parseargs@0.11.0': optional: true @@ -30013,6 +31010,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + '@szmarczak/http-timer@1.1.2': dependencies: defer-to-connect: 1.1.3 @@ -30176,6 +31177,11 @@ snapshots: '@types/filesystem': 0.0.36 '@types/har-format': 1.2.16 + '@types/chrome@0.0.278': + dependencies: + '@types/filesystem': 0.0.36 + '@types/har-format': 1.2.16 + '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 5.0.6 @@ -30471,6 +31477,10 @@ snapshots: dependencies: '@types/react': 17.0.84 + '@types/react-dom@18.3.5(@types/react@18.3.23)': + dependencies: + '@types/react': 18.3.23 + '@types/react-router-config@5.0.11': dependencies: '@types/history': 4.7.11 @@ -30494,6 +31504,11 @@ snapshots: '@types/scheduler': 0.16.8 csstype: 3.1.3 + '@types/react@18.3.23': + dependencies: + '@types/prop-types': 15.7.14 + csstype: 3.1.3 + '@types/readdir-glob@1.1.5': dependencies: '@types/node': 18.19.83 @@ -30871,6 +31886,8 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + '@webcomponents/custom-elements@1.6.0': {} + '@xapi/xapi@3.0.1': dependencies: axios: 1.8.4 @@ -31198,7 +32215,7 @@ snapshots: - supports-color - ts-node - aqu@0.4.3(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(@types/node@17.0.45)(babel-jest@29.7.0(@babel/core@7.26.10))(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): + aqu@0.4.3(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(@types/node@17.0.45)(babel-jest@29.7.0(@babel/core@7.26.10))(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)): dependencies: '@babel/preset-env': 7.26.9(@babel/core@7.26.10) '@babel/preset-react': 7.26.3(@babel/core@7.26.10) @@ -31218,13 +32235,13 @@ snapshots: fs-extra: 10.1.0 github-username: 6.0.0 inquirer: 7.3.3 - jest: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) - jest-watch-typeahead: 2.2.2(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2))) + jest: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) + jest-watch-typeahead: 2.2.2(jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2))) lodash: 4.17.21 ora: 5.4.1 prettier: 2.8.8 rimraf: 3.0.2 - ts-jest: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.15.18)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2) + ts-jest: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.15.18)(jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2) typescript: 5.6.2 webpack-merge: 5.10.0 yup: 0.32.11 @@ -31799,13 +32816,13 @@ snapshots: axios@0.25.0: dependencies: - follow-redirects: 1.15.9(debug@4.4.0) + follow-redirects: 1.15.9(debug@4.3.7) transitivePeerDependencies: - debug axios@1.8.4: dependencies: - follow-redirects: 1.15.9(debug@4.4.0) + follow-redirects: 1.15.9(debug@4.3.7) form-data: 4.0.2 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -31882,6 +32899,15 @@ snapshots: '@babel/helper-plugin-utils': 7.10.4 '@mdx-js/util': 1.6.22 + babel-plugin-const-enum@1.2.0(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + babel-plugin-dynamic-import-node@2.3.3: dependencies: object.assign: 4.1.7 @@ -31921,6 +32947,12 @@ snapshots: '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.7 + babel-plugin-macros@2.8.0: + dependencies: + '@babel/runtime': 7.27.0 + cosmiconfig: 6.0.0 + resolve: 1.22.10 + babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.26.10): dependencies: '@babel/compat-data': 7.26.8 @@ -31960,6 +32992,13 @@ snapshots: - '@babel/core' optional: true + babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.26.10)(@babel/traverse@7.27.0): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + optionalDependencies: + '@babel/traverse': 7.27.0 + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.10): dependencies: '@babel/core': 7.26.10 @@ -32093,6 +33132,10 @@ snapshots: mixin-deep: 1.3.2 pascalcase: 0.1.1 + basic-auth@2.0.1: + dependencies: + safe-buffer: 5.1.2 + basic-ftp@5.0.5: {} batch@0.6.1: {} @@ -32665,7 +33708,7 @@ snapshots: centra@2.7.0: dependencies: - follow-redirects: 1.15.9(debug@4.4.0) + follow-redirects: 1.15.9(debug@4.3.7) transitivePeerDependencies: - debug @@ -33286,6 +34329,8 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + corser@2.0.1: {} + cosmiconfig@5.2.1: dependencies: import-fresh: 2.0.0 @@ -33374,13 +34419,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): + create-jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -33404,21 +34449,6 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - create-require@1.1.1: {} credential-handler-polyfill@3.2.1: @@ -33616,6 +34646,11 @@ snapshots: dependencies: cssom: 0.3.8 + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + csstype@3.1.3: {} culvert@0.1.2: {} @@ -33652,6 +34687,11 @@ snapshots: whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -34955,13 +35995,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2): + eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.6.2) eslint: 8.57.1 optionalDependencies: '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2) - jest: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + jest: 29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) transitivePeerDependencies: - supports-color - typescript @@ -36811,6 +37851,10 @@ snapshots: dependencies: whatwg-encoding: 2.0.0 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-entities@2.5.3: {} html-escaper@2.0.2: {} @@ -36912,11 +37956,30 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.9(debug@4.4.0) + follow-redirects: 1.15.9(debug@4.3.7) requires-port: 1.0.0 transitivePeerDependencies: - debug + http-server@14.1.1: + dependencies: + basic-auth: 2.0.1 + chalk: 4.1.2 + corser: 2.0.1 + he: 1.2.0 + html-encoding-sniffer: 3.0.0 + http-proxy: 1.18.1 + mime: 1.6.0 + minimist: 1.2.8 + opener: 1.5.2 + portfinder: 1.0.37 + secure-compare: 3.0.1 + union: 0.5.0 + url-join: 4.0.1 + transitivePeerDependencies: + - debug + - supports-color + http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 @@ -37807,16 +38870,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): + jest-cli@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + create-jest: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -37845,25 +38908,6 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - jest-config@28.1.3(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)): dependencies: '@babel/core': 7.26.10 @@ -37985,7 +39029,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): + jest-config@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)): dependencies: '@babel/core': 7.26.10 '@jest/test-sequencer': 29.7.0 @@ -38011,7 +39055,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 17.0.45 - ts-node: 10.9.2(@types/node@22.13.14)(typescript@5.6.2) + ts-node: 10.9.2(@types/node@18.19.83)(typescript@5.6.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -38078,68 +39122,6 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): - dependencies: - '@babel/core': 7.26.10 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 18.19.83 - ts-node: 10.9.2(@types/node@22.13.14)(typescript@5.6.2) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): - dependencies: - '@babel/core': 7.26.10 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.13.14 - ts-node: 10.9.2(@types/node@22.13.14)(typescript@5.6.2) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - jest-dev-server@8.0.5: dependencies: chalk: 4.1.2 @@ -38663,22 +39645,22 @@ snapshots: string-length: 5.0.1 strip-ansi: 7.1.0 - jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2))): + jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2))): dependencies: ansi-escapes: 6.2.1 chalk: 5.4.1 - jest: 29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) + jest: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) jest-regex-util: 29.6.3 jest-watcher: 29.7.0 slash: 5.1.0 string-length: 5.0.1 strip-ansi: 7.1.0 - jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2))): + jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2))): dependencies: ansi-escapes: 6.2.1 chalk: 5.4.1 - jest: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + jest: 29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) jest-regex-util: 29.6.3 jest-watcher: 29.7.0 slash: 5.1.0 @@ -38766,12 +39748,12 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): + jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + jest-cli: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -38790,18 +39772,6 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - jet-logger@1.2.2: dependencies: colors: 1.3.0 @@ -39023,6 +39993,33 @@ snapshots: - supports-color - utf-8-validate + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.5.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.19 + parse5: 7.2.1 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.18.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsep@1.4.0: {} jsesc@3.0.2: {} @@ -41277,6 +42274,22 @@ snapshots: '@types/express': 4.17.21 '@types/node': 18.19.83 + node-mocks-http@1.16.2(@types/express@4.17.21)(@types/node@22.13.14): + dependencies: + accepts: 1.3.8 + content-disposition: 0.5.4 + depd: 1.1.2 + fresh: 0.5.2 + merge-descriptors: 1.0.3 + methods: 1.1.2 + mime: 1.6.0 + parseurl: 1.3.3 + range-parser: 1.2.1 + type-is: 1.6.18 + optionalDependencies: + '@types/express': 4.17.21 + '@types/node': 22.13.14 + node-releases@2.0.19: {} node-schedule@2.1.1: @@ -42351,6 +43364,13 @@ snapshots: dependencies: '@babel/runtime': 7.27.0 + portfinder@1.0.37: + dependencies: + async: 3.2.6 + debug: 4.4.0(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + posix-character-classes@0.1.1: {} possible-typed-array-names@1.1.0: {} @@ -43164,14 +44184,14 @@ snapshots: react-is@18.3.1: {} - react-json-view@1.21.3(@types/react@17.0.84)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-json-view@1.21.3(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: flux: 4.0.4(react@18.3.1) react: 18.3.1 react-base16-styling: 0.6.0 react-dom: 18.3.1(react@18.3.1) react-lifecycles-compat: 3.0.4 - react-textarea-autosize: 8.5.8(@types/react@17.0.84)(react@18.3.1) + react-textarea-autosize: 8.5.8(@types/react@18.3.23)(react@18.3.1) transitivePeerDependencies: - '@types/react' - encoding @@ -43251,6 +44271,8 @@ snapshots: react: 18.3.1 scheduler: 0.20.2 + react-refresh@0.13.0: {} + react-refresh@0.14.2: {} react-remove-scroll-bar@2.3.8(@types/react@17.0.84)(react@18.3.1): @@ -43310,12 +44332,12 @@ snapshots: optionalDependencies: '@types/react': 17.0.84 - react-textarea-autosize@8.5.8(@types/react@17.0.84)(react@18.3.1): + react-textarea-autosize@8.5.8(@types/react@18.3.23)(react@18.3.1): dependencies: '@babel/runtime': 7.27.0 react: 18.3.1 - use-composed-ref: 1.4.0(@types/react@17.0.84)(react@18.3.1) - use-latest: 1.3.0(@types/react@17.0.84)(react@18.3.1) + use-composed-ref: 1.4.0(@types/react@18.3.23)(react@18.3.1) + use-latest: 1.3.0(@types/react@18.3.23)(react@18.3.1) transitivePeerDependencies: - '@types/react' @@ -44070,6 +45092,8 @@ snapshots: dependencies: nanoid: 3.3.11 + rrweb-cssom@0.8.0: {} + rsvp@4.8.5: {} rtl-detect@1.1.2: {} @@ -44101,6 +45125,10 @@ snapshots: dependencies: tslib: 1.14.1 + rxjs@7.5.7: + dependencies: + tslib: 2.8.1 + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -44231,6 +45259,8 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 + secure-compare@3.0.1: {} + secure-json-parse@2.7.0: {} seek-bzip@1.0.6: @@ -45053,6 +46083,11 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 + source-map-support@0.5.19: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -45871,6 +46906,12 @@ snapshots: titleize@3.0.0: {} + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -45933,6 +46974,10 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + tr46@0.0.3: {} tr46@3.0.0: @@ -45995,6 +47040,20 @@ snapshots: - '@types/express' - '@types/node' + trpc-openapi@1.2.0(@trpc/server@11.3.0(typescript@5.6.2))(@types/express@4.17.21)(@types/node@22.13.14)(zod@3.23.8): + dependencies: + '@trpc/server': 11.3.0(typescript@5.6.2) + co-body: 6.2.0 + h3: 1.15.1 + lodash.clonedeep: 4.5.0 + node-mocks-http: 1.16.2(@types/express@4.17.21)(@types/node@22.13.14) + openapi-types: 12.1.3 + zod: 3.23.8 + zod-to-json-schema: 3.24.5(zod@3.23.8) + transitivePeerDependencies: + - '@types/express' + - '@types/node' + trpc-to-openapi@2.3.1(@trpc/server@11.3.0(typescript@5.6.2))(zod-openapi@4.2.4(zod@3.23.8))(zod@3.23.8): dependencies: '@trpc/server': 11.3.0(typescript@5.6.2) @@ -46070,12 +47129,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.26.10) esbuild: 0.15.18 - ts-jest@29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.15.18)(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.15.18)(jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) + jest: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -46091,12 +47150,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.26.10) esbuild: 0.15.18 - ts-jest@29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.15.18)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.15.18)(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + jest: 29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -46112,12 +47171,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.26.10) esbuild: 0.15.18 - ts-jest@29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.25.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.25.1)(jest@29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)) + jest: 29.7.0(@types/node@18.19.83)(ts-node@10.9.2(@types/node@18.19.83)(typescript@5.6.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -46512,6 +47571,10 @@ snapshots: is-extendable: 0.1.1 set-value: 2.0.1 + union@0.5.0: + dependencies: + qs: 6.14.0 + unique-filename@3.0.0: dependencies: unique-slug: 4.0.0 @@ -46720,6 +47783,8 @@ snapshots: urix@0.1.0: {} + url-join@4.0.1: {} + url-loader@4.1.1(file-loader@6.2.0(webpack@5.98.0(esbuild@0.25.1)))(webpack@5.98.0(esbuild@0.25.1)): dependencies: loader-utils: 2.0.4 @@ -46755,29 +47820,29 @@ snapshots: optionalDependencies: '@types/react': 17.0.84 - use-composed-ref@1.4.0(@types/react@17.0.84)(react@18.3.1): + use-composed-ref@1.4.0(@types/react@18.3.23)(react@18.3.1): dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 17.0.84 + '@types/react': 18.3.23 use-immer@0.7.0(immer@9.0.21)(react@18.3.1): dependencies: immer: 9.0.21 react: 18.3.1 - use-isomorphic-layout-effect@1.2.0(@types/react@17.0.84)(react@18.3.1): + use-isomorphic-layout-effect@1.2.0(@types/react@18.3.23)(react@18.3.1): dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 17.0.84 + '@types/react': 18.3.23 - use-latest@1.3.0(@types/react@17.0.84)(react@18.3.1): + use-latest@1.3.0(@types/react@18.3.23)(react@18.3.1): dependencies: react: 18.3.1 - use-isomorphic-layout-effect: 1.2.0(@types/react@17.0.84)(react@18.3.1) + use-isomorphic-layout-effect: 1.2.0(@types/react@18.3.23)(react@18.3.1) optionalDependencies: - '@types/react': 17.0.84 + '@types/react': 18.3.23 use-resize-observer@9.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -46927,6 +47992,24 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + vite-node@1.6.1(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0): + dependencies: + cac: 6.7.14 + debug: 4.4.0(supports-color@8.1.1) + pathe: 1.1.2 + picocolors: 1.1.1 + vite: 5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-node@1.6.1(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0): dependencies: cac: 6.7.14 @@ -46945,13 +48028,13 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@4.3.2(typescript@5.6.2)(vite@5.4.15(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0)): + vite-tsconfig-paths@4.3.2(typescript@5.6.2)(vite@5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0)): dependencies: debug: 4.4.0(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.5(typescript@5.6.2) optionalDependencies: - vite: 5.4.15(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) + vite: 5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) transitivePeerDependencies: - supports-color - typescript @@ -46979,6 +48062,18 @@ snapshots: less: 4.2.2 terser: 5.39.0 + vite@5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.37.0 + optionalDependencies: + '@types/node': 18.19.83 + fsevents: 2.3.3 + less: 4.2.2 + lightningcss: 1.27.0 + terser: 5.39.0 + vite@5.4.15(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0): dependencies: esbuild: 0.21.5 @@ -46999,7 +48094,43 @@ snapshots: optionalDependencies: vite: 5.4.15(@types/node@22.13.14)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) - vitest@1.6.1(@types/node@22.13.14)(happy-dom@14.12.3)(jsdom@20.0.3)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0): + vitest@1.6.1(@types/node@18.19.83)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0): + dependencies: + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + acorn-walk: 8.3.4 + chai: 4.5.0 + debug: 4.4.0(supports-color@8.1.1) + execa: 8.0.1 + local-pkg: 0.5.1 + magic-string: 0.30.17 + pathe: 1.1.2 + picocolors: 1.1.1 + std-env: 3.8.1 + strip-literal: 2.1.1 + tinybench: 2.9.0 + tinypool: 0.8.4 + vite: 5.4.15(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) + vite-node: 1.6.1(@types/node@18.19.83)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 18.19.83 + happy-dom: 14.12.3 + jsdom: 26.1.0 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vitest@1.6.1(@types/node@22.13.14)(happy-dom@14.12.3)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.27.0)(terser@5.39.0): dependencies: '@vitest/expect': 1.6.1 '@vitest/runner': 1.6.1 @@ -47024,7 +48155,7 @@ snapshots: optionalDependencies: '@types/node': 22.13.14 happy-dom: 14.12.3 - jsdom: 20.0.3 + jsdom: 26.1.0 transitivePeerDependencies: - less - lightningcss @@ -47105,6 +48236,10 @@ snapshots: dependencies: xml-name-validator: 4.0.0 + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + wait-on@6.0.1: dependencies: axios: 0.25.0 @@ -47551,6 +48686,8 @@ snapshots: xml-name-validator@4.0.0: {} + xml-name-validator@5.0.0: {} + xml-parse-from-string@1.0.1: {} xml2js@0.5.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c27938ba3..6ae36533c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,5 +6,7 @@ packages: - 'packages/plugins/*' - 'services/*' - 'services/learn-card-network/*' + - 'apps/*' + - 'apps/learn-card-browser-extension/*' - 'tests/*' - 'tools/executors/workspace/*'