Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
133 commits
Select commit Hold shift + click to select a range
16ce231
DES-21: Pagination Base UI idiom upgrades + optional totalItems (#26918)
jaymantri Apr 30, 2026
aefb119
[grid] add typed wrapper for Origin Button (#26770)
jaymantri Apr 30, 2026
1e24a82
DES-23: Add LoadMore infinite-scroll primitive + useLoadMore hook (#2…
jaymantri Apr 30, 2026
497e6f9
fix(origin): unblock site builds — LoadMoreTriggerProps render overri…
jaymantri Apr 30, 2026
62d30b8
CI update lock file for PR
May 1, 2026
6f9cae0
[grid] Example app to test wallet module (#26717)
carsonp6 May 1, 2026
6f0e85a
CI update lock file for PR
May 1, 2026
4d67f34
[origin] add scoped globals for mixed app routes (#26900)
coreymartin May 1, 2026
de5ffdb
feat(origin/BarChart): anchor non-stacked bars at value 0 when 0 is i…
jamesxu-lightspark May 1, 2026
23405ed
[site] render auth buttons with Origin (#26933)
coreymartin May 1, 2026
4d66177
[js] Restore tests to CI checks (#26962)
coreymartin May 2, 2026
87a690d
[origin] Deploy Storybook to dev.dev.sparkinfra.net (#26994)
coreymartin May 4, 2026
885ae3a
CI update lock file for PR
May 4, 2026
60d92b5
[origin] Add Storybook PR previews (#27001)
coreymartin May 4, 2026
af6bb21
[origin] Lower scoped reset specificity (#27028)
jaymantri May 5, 2026
64a04d8
Simplify Combobox: align with Base UI multi-select pattern (DES-24) (…
jaymantri May 6, 2026
e80a390
[vite] Handle stale dev proxy ALB sessions (#27087)
coreymartin May 6, 2026
f61fff1
Migrate ops DLQ to DataManagerTable (#27080)
bsiaotickchong May 6, 2026
90146eb
[origin] Expose Field root render prop (#27097)
jaymantri May 6, 2026
7a2625d
[origin] Simplify Combobox chip composition (#27141)
jaymantri May 7, 2026
d6887ed
[origin] Add Field label suffix spacing (#27140)
jaymantri May 7, 2026
7046649
[origin] Bound Select popup height (#27142)
jaymantri May 7, 2026
8cf11b0
[origin] Add ButtonLink for link semantics (#27144)
jaymantri May 7, 2026
a3f6b53
[origin] Make Tabs panel unopinionated (#27143)
jaymantri May 7, 2026
27d4aac
DES-22: Add `Pager` direction-based pagination primitive (#26921)
jaymantri May 7, 2026
766561b
[js] Increase npm dependency age gate (#27190)
coreymartin May 8, 2026
e355ae1
[site] surface USDT-Tron in the Grid dashboard payout flow (#27211)
jklein24 May 14, 2026
ba1130e
[site] Speed up local Playwright UI tests (#25379)
coreymartin May 19, 2026
a5a2c99
[origin] Add form composition boundary tests (#27119)
jaymantri May 20, 2026
b2b4f09
fix(treasury): fix symbol case, restructure columns, fix stablecoin f…
k15z May 21, 2026
8cdb5ab
[uma-nage] Add segmented navigation wrapper (#27106)
jaymantri May 22, 2026
9cd70ea
[ui] Use built package imports in private apps (#27024)
coreymartin May 22, 2026
1128b0f
Fix fail-open async UMA validation checks (#524)
SOME-1HING May 25, 2026
32792ca
[js] Update form-data resolution (#27827)
coreymartin May 26, 2026
5bc0a0d
CI update lock file for PR
May 26, 2026
dfc6a8c
[js] Update vite dependency (#27876)
coreymartin May 27, 2026
9c2a3f1
[origin] Update fast-uri dependency (#27874)
coreymartin May 27, 2026
ae2b584
[js] Update axios dependency (#27873)
coreymartin May 27, 2026
1508dc1
CI update lock file for PR
May 27, 2026
b17529c
[js] Update react-router-dom dependency (#27861)
coreymartin May 27, 2026
37493d8
CI update lock file for PR
May 27, 2026
729ded5
DEMO(grid): add internal demo app for hosted KYC/KYB link API (#27615)
jklein24 May 28, 2026
3ae133b
Fix js build by running yarn (#27939)
jklein24 May 28, 2026
795107e
CI update lock file for PR
May 28, 2026
097aee9
DEMO(grid): add embedded Sumsub WebSDK option to demo (#27726)
jklein24 May 28, 2026
d908f6c
Update docs link domains (#27917)
bsiaotickchong May 28, 2026
c78e1b2
[js] Upgrade to React 19 and Next.js 15 (#27901)
coreymartin May 29, 2026
4c18497
CI update lock file for PR
May 29, 2026
2b81c87
[js] Update nodemon dependency (#27856)
coreymartin May 30, 2026
ad4d94f
CI update lock file for PR
May 30, 2026
f680742
[js] Update minimatch dependency (#27849)
coreymartin May 30, 2026
1e16384
CI update lock file for PR
May 30, 2026
6c742e4
[js][gga] Update example app (#28004)
carsonp6 Jun 1, 2026
e784c0a
[js] Update js-cookie dependency (#27866)
coreymartin Jun 1, 2026
fc30cb0
[js] Update Vitest to v4 (#28092)
coreymartin Jun 1, 2026
2c7d6ae
CI update lock file for PR
Jun 1, 2026
5f4cff3
[ui] Build icons from a single entrypoint (#28172)
coreymartin Jun 3, 2026
45b334c
[docs] Replace remark-prism with rehype-prism-plus (#28163)
coreymartin Jun 3, 2026
a2ccd0e
CI update lock file for PR
Jun 3, 2026
581a2f8
Gatekeeper: keyboard row navigation + highlight for the results table…
akanter Jun 4, 2026
821c5e3
[grid] Add read-only Home foundation (#27949)
jaymantri Jun 4, 2026
a327801
[grid] add CNY mobile-wallet payout corridor (AliPay / WeChatPay) (#2…
mohamedwane Jun 4, 2026
1eabc3a
[js] Upgrade libphonenumber-js (#28416)
coreymartin Jun 8, 2026
2b82edb
CI update lock file for PR
Jun 8, 2026
74d4fc8
fix(origin): thin x-axis labels by measured width (#28120)
jamesxu-lightspark Jun 9, 2026
f7e42c2
[origin] Fix long-list dropdown scrolling (#28492)
jaymantri Jun 9, 2026
7ad22a7
[origin] Default combobox clear to active (#28544)
jaymantri Jun 11, 2026
ddf20c0
chore(js): remove unused static logo asset (#28518)
kphurley7 Jun 11, 2026
779ce90
DES-51: add Nage phone input foundation (#28445)
jaymantri Jun 11, 2026
4bd38aa
[site] Fix preview login redirects (#28555)
coreymartin Jun 11, 2026
909a749
[grid] Add Receive/Add Funds money-flow primitives (#27951)
jaymantri Jun 11, 2026
a3b7643
[js] gga example app: V3 secure OTP e2e flow (test harness) (#28154)
carsonp6 Jun 12, 2026
d256b2d
CI update lock file for PR
Jun 12, 2026
004dae2
[js] gga example app: real WebAuthn ceremony + OTP-session caching; e…
carsonp6 Jun 12, 2026
d22cd61
[js] gga example app: split main.ts into config/turnkey/webauthn/api-…
carsonp6 Jun 12, 2026
42dc5e3
[js] gga example app: session.ts + status chip; disable-with-tooltip …
carsonp6 Jun 12, 2026
1ff29c6
[js] gga example app: prune dead flows + stale PR-number comments (#2…
carsonp6 Jun 12, 2026
9b760c4
[js] gga example app: sandbox/production mode split driven by SANDBOX…
carsonp6 Jun 12, 2026
3eb08af
[js] gga example app: guided login/manage flows + Advanced (manual) t…
carsonp6 Jun 12, 2026
88d6ef4
Add shellcheck linting across the repo (#28620)
markatto Jun 12, 2026
2c02a0d
feat(gga): redesign example app as React + Origin (Platform/Customer …
carsonp6 Jun 12, 2026
8fcbeab
CI update lock file for PR
Jun 12, 2026
0c0c505
[js] Upgrade Turbo for worktree cache sharing (#28807)
coreymartin Jun 13, 2026
657cc5f
Enable global Yarn cache (#28798)
coreymartin Jun 13, 2026
d297d00
CI update lock file for PR
Jun 13, 2026
dabbef1
[origin] Fix sidebar navigation roles (#28838)
jaymantri Jun 15, 2026
c7c3755
feat(gga): customer transactions tab (real Grid history) (#28761)
carsonp6 Jun 16, 2026
f16158d
[gga][example] Remove md files (#28995)
carsonp6 Jun 17, 2026
97933dc
[grid] Normalize crypto network and token icons (#29044)
jaymantri Jun 18, 2026
1010f42
Add sorting to treasury flows table (#29232)
k15z Jun 23, 2026
0ff7315
[ops] populate DLQ task args/kwargs via a live 'current' read (#29488)
bsiaotickchong Jun 29, 2026
58790e8
[origin] Retarget Base UI React to 1.6 (#29500)
coreymartin Jun 29, 2026
2455f87
CI update lock file for PR
Jun 29, 2026
f663a11
feat(ops): Grid Billing — Reconciliation page fixes (#29699)
bsiaotickchong Jul 2, 2026
8a5b6a4
[origin] ChipFilter interactive values, icon-token alignment, Field l…
jaymantri Jul 6, 2026
7a1e76d
[site] Wire Vitest tests (#29821)
coreymartin Jul 6, 2026
5b5a407
CI update lock file for PR
Jul 6, 2026
ae13e57
[origin] Give CountrySelect's hidden input its own form identity (#29…
jaymantri Jul 6, 2026
94cb8cd
[site] Remove legacy Cypress UI test setup (#29888)
coreymartin Jul 7, 2026
882e9f7
[origin] Add OTPField component (#29813)
jaymantri Jul 7, 2026
0bdb86a
[gha] Enable GitHub-backed Turbo remote cache (#27018)
coreymartin Jul 7, 2026
3f0b58c
CI update lock file for PR
Jul 7, 2026
40ab90f
origin: add ring variant to Loader (#29996)
jaymantri Jul 8, 2026
b75ac8b
[origin] Add PhoneInput.LockedCountry static leading cap (#30094)
jaymantri Jul 9, 2026
35f62ef
[gha] Speed up public JS Copybara sync (#30096)
coreymartin Jul 9, 2026
9eb63ea
[gql] Generate private typenames per app (#30082)
coreymartin Jul 9, 2026
76923f9
[origin] add label slot to Item title row (#30176)
jaymantri Jul 10, 2026
d6a4b57
[gha] Partially fetch the Copybara origin (#30198)
coreymartin Jul 10, 2026
0ebb882
[origin] pass single props object to useRender in Pagination (#30182)
jaymantri Jul 13, 2026
00f7294
[origin] support render prop on Breadcrumb.Link (#30178)
jaymantri Jul 13, 2026
7ac5d2c
[core] Preserve custom error serializer receivers (#30291)
coreymartin Jul 14, 2026
70e447a
[gha] Reuse shared builds across UI previews (#30263)
coreymartin Jul 14, 2026
b15e1c3
feat(striga): local e2e harness for the Grid switch (#30015)
jklein24 Jul 15, 2026
a7d6022
CI update lock file for PR
Jul 15, 2026
7c214c9
[vite] Support Bolt frontends behind Zeus (#30419)
coreymartin Jul 15, 2026
d7e240e
[js] Add docs and scripts covering private app backend modes (#29734)
coreymartin Jul 15, 2026
f8ce752
Match space-after-shebang scripts in shellcheck filter (#30401)
markatto Jul 15, 2026
543531e
Share Turbo cache through S3 (#30351)
coreymartin Jul 16, 2026
89fc07e
Add BGN, CZK, HUF, NOK, PLN, RON, SEK, CHF to CurrencyUnit enum (#30481)
akanter Jul 16, 2026
cffb9e3
[js] Bump tar to 7.5.20 (#30906)
mhrheaume Jul 22, 2026
92c83c1
CI update lock file for PR
Jul 22, 2026
49448c5
[origin] Add reusable table primitives and provider-free bindings (#3…
jaymantri Jul 22, 2026
2151c8f
feat(ops): link spark transfer id fields in inspector to spark transf…
zhenlu Jul 22, 2026
4b8f2b7
feat(harness): SCA panels for the Striga grid test harness (#30638)
jklein24 Jul 23, 2026
e6eeec7
CI update lock file for PR
Jul 23, 2026
8287bb8
feat(striga-harness): configure the target environment from the UI (#…
jklein24 Jul 29, 2026
a6ba11f
[origin] Improve DataTable sizing and loading behavior (#31303)
jaymantri Jul 29, 2026
97f1c41
Close Add Filter menu after enum selection (#31327)
jaymantri Jul 29, 2026
dd7b63a
[js] Publish titles only in public sync (#31495)
coreymartin Jul 30, 2026
3823b80
[js] Preserve sync review metadata (#31503)
coreymartin Jul 30, 2026
cfb052a
[ui] Clear the InputSubtext fade-out timer on unmount (#31553)
bsiaotickchong Jul 31, 2026
c6cf9e3
[origin] Extend Command palette capabilities (#31001)
jaymantri Jul 31, 2026
1eb5685
[origin] Add noninteractive DataTable cell truncation (#31614)
jaymantri Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ test-config.yml
dist/
build/
lib/
# The GGA example app keeps decoupled source modules under src/lib/ (not a
# compiled-output dir), so it must not be swept up by the `lib/` rule above.
!apps/examples/grid-global-accounts-example-app/src/lib/

# Vim swap files
*.swp
Expand Down Expand Up @@ -126,3 +129,6 @@ stats.html

# Dev proxy cookies (contains ALB session tokens)
.dev-proxy-cookies

# Playwright MCP logs
.playwright-mcp/
2 changes: 1 addition & 1 deletion .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
MISE_NODE_COREPACK = true

[tools]
node = "20.19"
node = "20.19.6"

[tasks.deps]
description = "Install all JS dependencies"
Expand Down
4 changes: 2 additions & 2 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
compressionLevel: mixed

enableGlobalCache: false
enableGlobalCache: true

nodeLinker: node-modules

npmMinimalAgeGate: 1440
npmMinimalAgeGate: 4320 # 3 days

npmPreapprovedPackages:
- "@lightsparkdev/*"
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ yarn workspace @lightsparkdev/ui test
- Use workspace protocol for internal deps: `"@lightsparkdev/ui": "*"`
- Shared configs: `@lightsparkdev/{tsconfig,eslint-config}`

### Changesets
Always prefer `patch` version increments unless a maintainer explicitly calls for `minor`/`major`.

### Enums
Prefer generated TypeScript enums from `src/generated/graphql` rather than raw strings when
available. This ensures type safety and keeps code in sync with the schema.
Expand Down
2 changes: 2 additions & 0 deletions apps/examples/grid-global-accounts-example-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Verification screenshots — captured locally, not tracked.
.screenshots/
12 changes: 12 additions & 0 deletions apps/examples/grid-global-accounts-example-app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Grid Global Accounts</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions apps/examples/grid-global-accounts-example-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@lightsparkdev/grid-global-accounts-example-app",
"private": true,
"version": "0.0.1",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"start": "vite",
"preview": "vite preview",
"test": "vitest run"
},
"devDependencies": {
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.2.0",
"typescript": "^5.6.2",
"vite": "^8.0.14",
"vitest": "^4.1.7"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@lightsparkdev/origin": "*",
"@turnkey/api-key-stamper": "^0.6.5",
"@turnkey/crypto": "^2.8.14",
"@turnkey/encoding": "^0.6.0",
"react": "^19.2.6",
"react-dom": "^19.2.6"
}
}
19 changes: 19 additions & 0 deletions apps/examples/grid-global-accounts-example-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Shell } from "./components/Shell";
import { AppStateProvider, useAppState } from "./state/store";
import { CustomerView } from "./views/customer/CustomerView";
import { PlatformView } from "./views/platform/PlatformView";

export function App() {
return (
<AppStateProvider>
<Shell>
<Router />
</Shell>
</AppStateProvider>
);
}

function Router() {
const { persona } = useAppState();
return persona === "platform" ? <PlatformView /> : <CustomerView />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { afterEach, describe, expect, it } from "vitest";

import {
clearActiveSession,
getAccountId,
getSessionId,
getSessionModel,
hasSessionSigningKey,
resolveSessionKeys,
setAccountId,
setActiveSessionAccount,
setSessionId,
setSessionKeysFromTek,
} from "../session";

// Reset to logged-out between tests so module-level context state can't leak.
afterEach(() => {
setActiveSessionAccount(null);
});

describe("per-customer session isolation", () => {
it("keeps signing keys, model, and account id independent per account key", () => {
// No active context → logged-out, getters return empty/null.
setActiveSessionAccount(null);
expect(hasSessionSigningKey()).toBe(false);
expect(resolveSessionKeys()).toBeNull();
expect(getAccountId()).toBe("");
expect(getSessionModel()).toBe("none");

// Customer A signs in (OTP-TEK model) under its own account key.
setActiveSessionAccount("InternalAccount:A");
setAccountId("InternalAccount:A");
setSessionKeysFromTek({ publicKey: "pubA", privateKey: "privA" });
expect(hasSessionSigningKey()).toBe(true);
expect(getSessionModel()).toBe("otp-tek");
expect(resolveSessionKeys()).toEqual({
apiPublicKey: "pubA",
apiPrivateKey: "privA",
});
expect(getAccountId()).toBe("InternalAccount:A");

// Switch to a fresh customer B: it inherits NOTHING from A.
setActiveSessionAccount("InternalAccount:B");
expect(hasSessionSigningKey()).toBe(false);
expect(resolveSessionKeys()).toBeNull();
expect(getSessionModel()).toBe("none");
expect(getAccountId()).toBe("");

// B establishes its own session with different keys.
setAccountId("InternalAccount:B");
setSessionKeysFromTek({ publicKey: "pubB", privateKey: "privB" });
expect(resolveSessionKeys()).toEqual({
apiPublicKey: "pubB",
apiPrivateKey: "privB",
});
expect(getAccountId()).toBe("InternalAccount:B");

// Switching back to A restores A's cached session, untouched by B.
setActiveSessionAccount("InternalAccount:A");
expect(hasSessionSigningKey()).toBe(true);
expect(getSessionModel()).toBe("otp-tek");
expect(resolveSessionKeys()).toEqual({
apiPublicKey: "pubA",
apiPrivateKey: "privA",
});
expect(getAccountId()).toBe("InternalAccount:A");
});

it("treats null as logged-out with no active context", () => {
setActiveSessionAccount("InternalAccount:A");
setSessionKeysFromTek({ publicKey: "pubA", privateKey: "privA" });
expect(hasSessionSigningKey()).toBe(true);

setActiveSessionAccount(null);
expect(hasSessionSigningKey()).toBe(false);
expect(resolveSessionKeys()).toBeNull();
expect(getAccountId()).toBe("");

// Mutators no-op while logged-out; later re-activation still has A cached.
setSessionKeysFromTek({ publicKey: "pubX", privateKey: "privX" });
setActiveSessionAccount("InternalAccount:A");
expect(resolveSessionKeys()).toEqual({
apiPublicKey: "pubA",
apiPrivateKey: "privA",
});
});

it("clearActiveSession wipes the active context's signing key without touching others", () => {
// Customer A signs in under its own account key.
setActiveSessionAccount("InternalAccount:clearA");
setAccountId("InternalAccount:clearA");
setSessionId("session-A");
setSessionKeysFromTek({ publicKey: "pubA", privateKey: "privA" });

// Customer B signs in under a different key.
setActiveSessionAccount("InternalAccount:clearB");
setAccountId("InternalAccount:clearB");
setSessionId("session-B");
setSessionKeysFromTek({ publicKey: "pubB", privateKey: "privB" });

// Back on A, clearing wipes A's signing key/model/session id but keeps the
// account id so the slot still belongs to A (logged out, can re-auth).
setActiveSessionAccount("InternalAccount:clearA");
expect(hasSessionSigningKey()).toBe(true);
clearActiveSession();
expect(hasSessionSigningKey()).toBe(false);
expect(resolveSessionKeys()).toBeNull();
expect(getSessionModel()).toBe("none");
expect(getSessionId()).toBe("");
expect(getAccountId()).toBe("InternalAccount:clearA");

// B is untouched by clearing A.
setActiveSessionAccount("InternalAccount:clearB");
expect(hasSessionSigningKey()).toBe(true);
expect(getSessionModel()).toBe("otp-tek");
expect(getSessionId()).toBe("session-B");
expect(resolveSessionKeys()).toEqual({
apiPublicKey: "pubB",
apiPrivateKey: "privB",
});
});

it("clearActiveSession is a no-op when logged out", () => {
setActiveSessionAccount(null);
expect(() => clearActiveSession()).not.toThrow();
expect(hasSessionSigningKey()).toBe(false);
});

it("preserves first-account-wins for setAccountId within a fresh context", () => {
// A distinct key so the context is fresh (contexts persist across tests).
setActiveSessionAccount("InternalAccount:C");
setAccountId("first");
setAccountId("second");
expect(getAccountId()).toBe("first");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { describe, expect, it } from "vitest";

import {
buildAllowCredentials,
buildAssertionOptions,
buildCreationOptions,
bytesToB64Url,
SECURITY_KEY_TRANSPORTS,
} from "../webauthn";

const RP = "localhost";

describe("buildCreationOptions — forces a cross-platform security key", () => {
const challenge = new Uint8Array([1, 2, 3]);
const userId = new Uint8Array([9, 9]);
const opts = buildCreationOptions("My key", RP, challenge, userId);

it("requests a cross-platform (roaming) authenticator, not the platform one", () => {
expect(opts.authenticatorSelection?.authenticatorAttachment).toBe(
"cross-platform",
);
});

it("uses security-key-friendly resident-key + UV settings", () => {
expect(opts.authenticatorSelection?.residentKey).toBe("discouraged");
expect(opts.authenticatorSelection?.requireResidentKey).toBe(false);
expect(opts.authenticatorSelection?.userVerification).toBe("preferred");
});

it("offers ES256 (-7) in pubKeyCredParams", () => {
expect(opts.pubKeyCredParams).toContainEqual({
type: "public-key",
alg: -7,
});
});

it("sets the rp id and passes the challenge/user through", () => {
expect(opts.rp.id).toBe(RP);
expect(opts.challenge).toBe(challenge);
expect(opts.user.id).toBe(userId);
});
});

describe("buildAllowCredentials — targets the security key over USB/NFC", () => {
// A valid base64url credential id (decodes cleanly via atob).
const idA = bytesToB64Url(new Uint8Array([10, 20, 30]));
const idB = bytesToB64Url(new Uint8Array([40, 50, 60]));

it("includes every registered id with usb/nfc transports", () => {
const out = buildAllowCredentials([idA, idB]);
expect(out).toHaveLength(2);
for (const d of out) {
expect(d.type).toBe("public-key");
expect(d.transports).toEqual(SECURITY_KEY_TRANSPORTS);
expect(d.transports).toEqual(["usb", "nfc"]);
}
});

it("drops blank and duplicate ids", () => {
const out = buildAllowCredentials([idA, "", " ", idA]);
expect(out).toHaveLength(1);
});

it("returns [] when no ids are known (discoverable-credential fallback)", () => {
expect(buildAllowCredentials([])).toEqual([]);
});
});

describe("buildAssertionOptions", () => {
const challenge = new Uint8Array([7]);
const id = bytesToB64Url(new Uint8Array([1, 2, 3, 4]));

it("wires the rp id, challenge, UV and the allowCredentials", () => {
const opts = buildAssertionOptions(challenge, [id], RP);
expect(opts.rpId).toBe(RP);
expect(opts.challenge).toBe(challenge);
expect(opts.userVerification).toBe("preferred");
expect(opts.allowCredentials).toHaveLength(1);
expect(opts.allowCredentials?.[0].transports).toEqual(["usb", "nfc"]);
});

it("yields an empty allowCredentials when no ids are known", () => {
const opts = buildAssertionOptions(challenge, [], RP);
expect(opts.allowCredentials).toEqual([]);
});
});
Loading
Loading