Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5f1f660
feat(deps): adopt React-agnostic @agicash/opensecret 1.0.0-rc.0
pmilic021 Jul 9, 2026
099e5ac
refactor(utils): move setLongTimeout/clearLongTimeout to @agicash/utils
pmilic021 Jul 9, 2026
27bdd2f
feat(wallet-sdk): settle the step-5 contract types (auth storage, Aut…
pmilic021 Jul 9, 2026
cd2acd8
feat(wallet-sdk): add the typed wallet event emitter
pmilic021 Jul 9, 2026
9935c81
feat(wallet-sdk): add port-backed guest-account storage and CSPRNG pa…
pmilic021 Jul 9, 2026
69e5a54
feat(wallet-sdk): add AuthService with session snapshot and expiry ma…
pmilic021 Jul 9, 2026
c095748
feat(wallet-sdk): add the SDK-internal Supabase client and session to…
pmilic021 Jul 9, 2026
32bbf9d
refactor(wallet-sdk): free WriteUserRepository from the accounts grap…
pmilic021 Jul 9, 2026
2930e9d
feat(wallet-sdk): add the AgicashSdk runtime with auth, user, and eve…
pmilic021 Jul 9, 2026
ad1be0b
feat(web-wallet): construct the wallet SDK instance and move Open Sec…
pmilic021 Jul 9, 2026
50d2d25
refactor(web-wallet): route auth flows through sdk.auth
pmilic021 Jul 9, 2026
3b3e98c
refactor(web-wallet): route user reads and writes through sdk.user
pmilic021 Jul 9, 2026
6c5b6ae
fix(wallet-sdk): fence stale restore applies and skip restore on unde…
pmilic021 Jul 10, 2026
7b1e4cc
fix(web-wallet): catch up on session expiry missed before mount and a…
pmilic021 Jul 10, 2026
10cbac7
refactor(wallet-sdk): split the sdk.ts contract into per-namespace files
pmilic021 Jul 10, 2026
d5cdb75
refactor(wallet-sdk): declare AgicashSdk's contract conformance via P…
pmilic021 Jul 10, 2026
8dc1c80
feat(wallet-sdk): require the logger port and export nullLogger
pmilic021 Jul 10, 2026
1780f59
fix(wallet-sdk): make AuthService.teardown terminal for the expiry ma…
pmilic021 Jul 10, 2026
4a8afe5
fix(web-wallet): return the dispose promise to Vite's HMR hook
pmilic021 Jul 10, 2026
337e878
refactor(web-wallet): extract the shared session-end cleanup from sig…
pmilic021 Jul 10, 2026
ef3402f
refactor(utils): extract safeJwtDecode for stored-token reads
pmilic021 Jul 10, 2026
b4ba28e
docs(web-wallet): mark the Open Secret storage-key reads as a tempora…
pmilic021 Jul 10, 2026
937e23a
revert(wallet-sdk): keep master's password generator sampling
pmilic021 Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 3 additions & 16 deletions apps/web-wallet/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { configure } from '@agicash/opensecret';
import {
configureFeatureFlags,
ensureBreezWasm,
Expand All @@ -15,6 +14,9 @@ import { HydratedRouter } from 'react-router/dom';
import { getEnvironment, isServedLocally } from './environment';
import { agicashDbClient } from './features/agicash-db/database.client';
import { loadFeatureFlags } from './features/shared/feature-flags';
// Importing the module constructs the SDK, which configures Open Secret as an
// import-evaluation side effect — before any body code below runs.
import './features/shared/sdk.client';
import { registerMoneyDevToolsFormatter } from './lib/money-devtools-formatter';
import { getTracesSampleRate, sanitizeUrl } from './tracing-utils';

Expand All @@ -23,21 +25,6 @@ if (process.env.NODE_ENV === 'development') {
registerMoneyDevToolsFormatter();
}

const openSecretApiUrl = import.meta.env.VITE_OPEN_SECRET_API_URL ?? '';
if (!openSecretApiUrl) {
throw new Error('VITE_OPEN_SECRET_API_URL is not set');
}

const openSecretClientId = import.meta.env.VITE_OPEN_SECRET_CLIENT_ID ?? '';
if (!openSecretClientId) {
throw new Error('VITE_OPEN_SECRET_CLIENT_ID is not set');
}

configure({
apiUrl: openSecretApiUrl,
clientId: openSecretClientId,
});

// Start Breez WASM fetch/compile as early as possible so it overlaps with
// hydration, Sentry init, and the auth query — by the time the _protected
// middleware awaits it, init is often already done.
Expand Down
4 changes: 2 additions & 2 deletions apps/web-wallet/app/features/agicash-db/database.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ const getSupabaseUrl = () => {
return supabaseUrl;
};

const supabaseUrl = getSupabaseUrl();
export const supabaseUrl = getSupabaseUrl();

const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY ?? '';
export const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY ?? '';
if (!supabaseAnonKey) {
throw new Error('VITE_SUPABASE_ANON_KEY is not set');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import {
sumProofs,
} from '@agicash/cashu';
import type { Money } from '@agicash/money';
import {
type LongTimeout,
clearLongTimeout,
setLongTimeout,
} from '@agicash/utils';
import type {
CashuAccount,
CashuReceiveQuote,
Expand Down Expand Up @@ -34,11 +39,6 @@ import {
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useOnMeltQuoteStateChange } from '~/lib/cashu/melt-quote-subscription';
import { MintQuoteSubscriptionManager } from '~/lib/cashu/mint-quote-subscription-manager';
import {
type LongTimeout,
clearLongTimeout,
setLongTimeout,
} from '~/lib/timeout';
import { useLatest } from '~/lib/use-latest';
import { withRetry } from '~/lib/with-retry';
import {
Expand Down
15 changes: 11 additions & 4 deletions apps/web-wallet/app/features/shared/auth.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { jwtDecode } from 'jwt-decode';
import { safeJwtDecode } from '@agicash/utils';

/** Check if the user is logged in by verifying localStorage tokens. */
/**
* Check if the user is logged in by verifying localStorage tokens. A corrupt
* stored token counts as logged out — this feeds the DB client's token
* getter, so it must never throw into every query.
*
* Temporary leak: reads Open Secret's storage keys directly until the late
* SDK-migration steps retire the web's own DB client.
*/
export const isLoggedIn = (): boolean => {
const accessToken = window.localStorage.getItem('access_token');
const refreshToken = window.localStorage.getItem('refresh_token');
if (!accessToken || !refreshToken) {
return false;
}
const decoded = jwtDecode(refreshToken);
return !!decoded.exp && decoded.exp * 1000 > Date.now();
const decoded = safeJwtDecode(refreshToken);
return !!decoded?.exp && decoded.exp * 1000 > Date.now();
};
57 changes: 57 additions & 0 deletions apps/web-wallet/app/features/shared/sdk.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { browserStorage } from '@agicash/opensecret';
import { AgicashSdk } from '@agicash/wallet-sdk';
import {
supabaseAnonKey,
supabaseUrl,
} from '~/features/agicash-db/database.client';
import { breezApiKey } from '~/lib/breez';

const openSecretApiUrl = import.meta.env.VITE_OPEN_SECRET_API_URL ?? '';
if (!openSecretApiUrl) {
throw new Error('VITE_OPEN_SECRET_API_URL is not set');
}

const openSecretClientId = import.meta.env.VITE_OPEN_SECRET_CLIENT_ID ?? '';
if (!openSecretClientId) {
throw new Error('VITE_OPEN_SECRET_CLIENT_ID is not set');
}

const consoleLogger = {
debug: (message: string, meta?: unknown) =>
meta === undefined ? console.debug(message) : console.debug(message, meta),
info: (message: string, meta?: unknown) =>
meta === undefined ? console.info(message) : console.info(message, meta),
warn: (message: string, meta?: unknown) =>
meta === undefined ? console.warn(message) : console.warn(message, meta),
error: (message: string, meta?: unknown) =>
meta === undefined ? console.error(message) : console.error(message, meta),
};

export const sdk = AgicashSdk.create({
db: {
url: supabaseUrl,
anonKey: supabaseAnonKey,
},
auth: {
apiUrl: openSecretApiUrl,
clientId: openSecretClientId,
storage: browserStorage,
// e2e bridge: the Playwright fixture arms window.getMockPassword; in
// production it's absent, so this resolves null and the SDK generates.
generateGuestPassword: async () =>
(await window.getMockPassword?.()) ?? null,
},
spark: {
breezApiKey,
network: 'MAINNET',
},
lightningAddressDomain: window.location.host,
logger: consoleLogger,
});

if (import.meta.hot) {
// A hot reload of this module constructs a second SDK; dispose the old one
// so its expiry timer doesn't leak. Returning the promise makes Vite await
// the teardown before evaluating the replacement module.
import.meta.hot.dispose(() => sdk.dispose());
}
4 changes: 2 additions & 2 deletions apps/web-wallet/app/features/signup/verify-email.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { verifyEmail as osVerifyEmail } from '@agicash/opensecret';
import type { FullUser } from '@agicash/wallet-sdk';
import { shouldVerifyEmail } from '@agicash/wallet-sdk';
import { useState } from 'react';
import { createContext, redirect } from 'react-router';
import { sdk } from '~/features/shared/sdk.client';
import { useToast } from '~/hooks/use-toast';
import type { Route } from '../../routes/+types/_protected.verify-email.($code)';
import { invalidateAuthQueries } from '../user/auth';
Expand Down Expand Up @@ -37,7 +37,7 @@ export const verifyEmail = async (
code: string,
): Promise<{ verified: true } | { verified: false; error: Error }> => {
try {
await osVerifyEmail(code);
await sdk.auth.verifyEmail(code);
await invalidateAuthQueries();
return { verified: true };
} catch (e) {
Expand Down
Loading
Loading