@comfyorg/account-core 1.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/LICENSE +21 -0
  2. package/dist/core/billing/balanceWatch.d.ts +35 -0
  3. package/dist/core/billing/balanceWatch.js +83 -0
  4. package/dist/core/billing/billingContracts.d.ts +123 -0
  5. package/dist/core/billing/billingContracts.js +11 -0
  6. package/dist/core/billing/billingErrorBody.d.ts +8 -0
  7. package/dist/core/billing/billingErrorBody.js +11 -0
  8. package/dist/core/billing/billingScope.d.ts +39 -0
  9. package/dist/core/billing/billingScope.js +45 -0
  10. package/dist/core/billing/capabilities.d.ts +70 -0
  11. package/dist/core/billing/capabilities.js +137 -0
  12. package/dist/core/billing/capabilityDenials.d.ts +26 -0
  13. package/dist/core/billing/capabilityDenials.js +43 -0
  14. package/dist/core/billing/challengeDriver.d.ts +22 -0
  15. package/dist/core/billing/challengeDriver.js +38 -0
  16. package/dist/core/billing/credentialedTransport.d.ts +35 -0
  17. package/dist/core/billing/credentialedTransport.js +53 -0
  18. package/dist/core/billing/credits.d.ts +43 -0
  19. package/dist/core/billing/credits.js +29 -0
  20. package/dist/core/billing/httpStatus.d.ts +7 -0
  21. package/dist/core/billing/httpStatus.js +25 -0
  22. package/dist/core/billing/index.d.ts +52 -0
  23. package/dist/core/billing/index.js +23 -0
  24. package/dist/core/billing/operationLifecycle.d.ts +77 -0
  25. package/dist/core/billing/operationLifecycle.js +547 -0
  26. package/dist/core/billing/operationPointer.d.ts +48 -0
  27. package/dist/core/billing/operationPointer.js +89 -0
  28. package/dist/core/billing/operationPolicy.d.ts +26 -0
  29. package/dist/core/billing/operationPolicy.js +51 -0
  30. package/dist/core/billing/operationState.d.ts +128 -0
  31. package/dist/core/billing/operationState.js +187 -0
  32. package/dist/core/billing/paymentCopy.d.ts +18 -0
  33. package/dist/core/billing/paymentCopy.js +47 -0
  34. package/dist/core/billing/paymentMethods.d.ts +40 -0
  35. package/dist/core/billing/paymentMethods.js +37 -0
  36. package/dist/core/billing/paymentProjection.d.ts +34 -0
  37. package/dist/core/billing/paymentProjection.js +56 -0
  38. package/dist/core/billing/plans.d.ts +44 -0
  39. package/dist/core/billing/plans.js +29 -0
  40. package/dist/core/billing/presentation.d.ts +20 -0
  41. package/dist/core/billing/presentation.js +10 -0
  42. package/dist/core/billing/scopedReader.d.ts +61 -0
  43. package/dist/core/billing/scopedReader.js +108 -0
  44. package/dist/core/billing/sharedRead.d.ts +33 -0
  45. package/dist/core/billing/sharedRead.js +59 -0
  46. package/dist/core/billing/status.d.ts +27 -0
  47. package/dist/core/billing/status.js +21 -0
  48. package/dist/core/billing/subscriptionCommands.d.ts +367 -0
  49. package/dist/core/billing/subscriptionCommands.js +263 -0
  50. package/dist/core/billing/topup.d.ts +113 -0
  51. package/dist/core/billing/topup.js +209 -0
  52. package/dist/core/billing/transport.d.ts +14 -0
  53. package/dist/core/billing/transport.js +106 -0
  54. package/dist/core/billing/transportExchange.d.ts +24 -0
  55. package/dist/core/billing/transportExchange.js +79 -0
  56. package/dist/core/boundedOperation.d.ts +16 -0
  57. package/dist/core/boundedOperation.js +12 -0
  58. package/dist/core/credentialCache.d.ts +41 -0
  59. package/dist/core/credentialCache.js +97 -0
  60. package/dist/core/customerRecovery.d.ts +39 -0
  61. package/dist/core/customerRecovery.js +79 -0
  62. package/dist/core/exchange.d.ts +50 -0
  63. package/dist/core/exchange.js +137 -0
  64. package/dist/core/identity.d.ts +21 -0
  65. package/dist/core/identity.js +16 -0
  66. package/dist/core/mintCoordinator.d.ts +18 -0
  67. package/dist/core/mintCoordinator.js +31 -0
  68. package/dist/core/refreshScheduler.d.ts +46 -0
  69. package/dist/core/refreshScheduler.js +262 -0
  70. package/dist/core/session.d.ts +146 -0
  71. package/dist/core/session.js +312 -0
  72. package/dist/core/sessionContracts.d.ts +135 -0
  73. package/dist/core/sessionContracts.js +9 -0
  74. package/dist/core/sessionState.d.ts +118 -0
  75. package/dist/core/sessionState.js +235 -0
  76. package/dist/firebase/index.d.ts +53 -0
  77. package/dist/firebase/index.js +74 -0
  78. package/dist/firebaseAuthError.d.ts +52 -0
  79. package/dist/firebaseAuthError.js +88 -0
  80. package/dist/loadExternalScript.d.ts +7 -0
  81. package/dist/loadExternalScript.js +88 -0
  82. package/dist/provisioning.d.ts +53 -0
  83. package/dist/provisioning.js +76 -0
  84. package/dist/redirect.d.ts +13 -0
  85. package/dist/redirect.js +38 -0
  86. package/dist/signInSchemas.d.ts +88 -0
  87. package/dist/signInSchemas.js +71 -0
  88. package/dist/telemetry.d.ts +39 -0
  89. package/dist/telemetry.js +26 -0
  90. package/dist/testing.d.ts +7 -0
  91. package/dist/testing.js +3 -0
  92. package/dist/turnstile.d.ts +14 -0
  93. package/dist/turnstile.js +17 -0
  94. package/dist/turnstileScript.d.ts +18 -0
  95. package/dist/turnstileScript.js +6 -0
  96. package/dist/web/crossTabRefresh.d.ts +18 -0
  97. package/dist/web/crossTabRefresh.js +121 -0
  98. package/dist/webviewDetection.d.ts +5 -0
  99. package/dist/webviewDetection.js +62 -0
  100. package/package.json +114 -0
@@ -0,0 +1,262 @@
1
+ import { isPermanentSessionError } from './sessionContracts.js';
2
+ export const DEFAULT_BUFFER_MS = 5 * 60 * 1000;
3
+ function disposeQuietly(dispose, label) {
4
+ try {
5
+ dispose?.();
6
+ }
7
+ catch (error) {
8
+ console.warn(`Cross-tab refresh ${label} teardown failed:`, error);
9
+ }
10
+ }
11
+ export function createRefreshScheduler(options, host) {
12
+ const bufferMs = options.bufferMs ?? DEFAULT_BUFFER_MS;
13
+ const retryBaseMs = options.retryBaseMs ?? 5000;
14
+ const maxRetries = options.maxRetries ?? 3;
15
+ const reportOutcome = options.onScheduledOutcome;
16
+ const crossTab = options.crossTab;
17
+ const followerJitterMs = crossTab?.followerJitterMs ?? 15_000;
18
+ let scheduledTimer;
19
+ /**
20
+ * The hard fail-close for a credential whose refresh chain died. Its own
21
+ * timer on purpose: re-arming the refresh (a retry, a promotion) must not
22
+ * cancel it; only a committed credential or a teardown does.
23
+ */
24
+ let expiryTimer;
25
+ let scheduledRetryCount = 0;
26
+ /** Without coordination every tab is its own leader. */
27
+ let isRefreshLeader = crossTab === undefined;
28
+ let coordinationKey;
29
+ /**
30
+ * Bumped on every coordination teardown. A leadership grant landing on an
31
+ * abandoned request carries the generation it was issued under — the key
32
+ * string alone cannot distinguish an abandoned request from its same-key
33
+ * successor after a sign-out/sign-in round trip.
34
+ */
35
+ let coordinationGeneration = 0;
36
+ let armingScheduledRefresh = false;
37
+ let releaseLeadership;
38
+ let stopCredentialFeed;
39
+ function stopScheduledRefresh() {
40
+ if (scheduledTimer !== undefined) {
41
+ clearTimeout(scheduledTimer);
42
+ scheduledTimer = undefined;
43
+ }
44
+ }
45
+ function clearExpiry() {
46
+ if (expiryTimer !== undefined) {
47
+ clearTimeout(expiryTimer);
48
+ expiryTimer = undefined;
49
+ }
50
+ }
51
+ function teardownCoordination() {
52
+ coordinationGeneration += 1;
53
+ disposeQuietly(releaseLeadership, 'leadership release');
54
+ releaseLeadership = undefined;
55
+ disposeQuietly(stopCredentialFeed, 'credential feed');
56
+ stopCredentialFeed = undefined;
57
+ coordinationKey = undefined;
58
+ isRefreshLeader = crossTab === undefined;
59
+ }
60
+ function ensureCoordination() {
61
+ const credential = host.getCredential();
62
+ if (crossTab === undefined || credential === undefined)
63
+ return;
64
+ // Keyed by the SERVER-RESOLVED workspace, never the requested target: a
65
+ // personal {} mint resolves to a concrete workspace the target string
66
+ // cannot name, and tabs on one workspace must share one lease however
67
+ // they reached it.
68
+ const key = `comfy-account-refresh:${credential.uid}:${credential.workspace.id}`;
69
+ if (key === coordinationKey)
70
+ return;
71
+ teardownCoordination();
72
+ coordinationKey = key;
73
+ const generationAtRequest = coordinationGeneration;
74
+ // Cross-tab coordination is optional: a port that throws during setup
75
+ // must never propagate past a committed credential. Tear down whatever
76
+ // partial wiring landed and lead this tab on its own schedule.
77
+ try {
78
+ stopCredentialFeed = crossTab.port.onCredential(key, adoptPublishedCredential);
79
+ releaseLeadership = crossTab.port.requestLeadership(key, () => {
80
+ // A grant is honored only for the coordination generation that issued
81
+ // the request: abandoned requests can still win the grant race in the
82
+ // lock manager, and after a same-user round trip their key is
83
+ // byte-identical to the live request's.
84
+ if (generationAtRequest !== coordinationGeneration)
85
+ return;
86
+ isRefreshLeader = true;
87
+ // Promotion retakes the schedule unconditionally — the follower timer
88
+ // may be jittered, mid-retry, or already dead from exhausted retries.
89
+ // The latch skips the redundant re-arm when the grant fires
90
+ // synchronously inside armScheduledRefresh itself.
91
+ const live = host.getCredential();
92
+ if (live !== undefined && !armingScheduledRefresh) {
93
+ armScheduledRefresh(live.expiresAt, host.now());
94
+ }
95
+ });
96
+ }
97
+ catch (error) {
98
+ console.warn('Cross-tab refresh coordination setup failed:', error);
99
+ teardownCoordination();
100
+ isRefreshLeader = true;
101
+ }
102
+ }
103
+ /**
104
+ * Every committed mint is published once a coordination key exists —
105
+ * leadership gates adoption, never publication — so the reactive 401
106
+ * re-mint and a leaderless follower's fallback reach siblings too: those
107
+ * are exactly the rotations they must not miss. Adoption itself never
108
+ * republishes, and the monotonic-expiry guard makes redelivery a no-op,
109
+ * so the channel cannot loop.
110
+ */
111
+ function publishToSiblings(session) {
112
+ if (coordinationKey === undefined)
113
+ return;
114
+ // Publication is optional: a throwing port must not unwind a mint that
115
+ // already committed and persisted its credential.
116
+ try {
117
+ crossTab?.port.publishCredential(coordinationKey, session);
118
+ }
119
+ catch (error) {
120
+ console.warn('Cross-tab refresh credential publish failed:', error);
121
+ }
122
+ }
123
+ function adoptPublishedCredential(message) {
124
+ // Any tab adopts a strictly newer credential, the leader included: a
125
+ // leader whose own chain died recovers from a sibling's fallback mint,
126
+ // and the monotonic-expiry check keeps the channel from looping.
127
+ const next = host.parseAdopted(message);
128
+ if (next === undefined)
129
+ return;
130
+ if (host.getCurrentUser()?.uid !== next.uid)
131
+ return;
132
+ // Scope check: the channel key cannot fully encode the workspace (the
133
+ // personal target is server-resolved), so a same-user credential minted
134
+ // for a DIFFERENT workspace must never switch this tab.
135
+ const credential = host.getCredential();
136
+ if (credential === undefined)
137
+ return;
138
+ if (next.workspace.id !== credential.workspace.id)
139
+ return;
140
+ if (next.expiresAt <= credential.expiresAt)
141
+ return;
142
+ clearExpiry();
143
+ host.commitAdopted(next);
144
+ armScheduledRefresh(next.expiresAt, host.now());
145
+ crossTab?.onCredentialAdopted?.(next);
146
+ }
147
+ function armScheduledRefresh(expiresAt, now) {
148
+ stopScheduledRefresh();
149
+ scheduledRetryCount = 0;
150
+ armingScheduledRefresh = true;
151
+ try {
152
+ ensureCoordination();
153
+ }
154
+ finally {
155
+ armingScheduledRefresh = false;
156
+ }
157
+ // A follower holds past the leader's refresh point by bounded random
158
+ // jitter; when no published credential has arrived by then, the leader
159
+ // is gone and this tab refreshes for itself. Never tighter than one
160
+ // retry interval: a token already inside the buffer (a host buffer at
161
+ // or above the TTL, a skewed clock) would otherwise re-mint in a loop.
162
+ const jitter = isRefreshLeader ? 0 : Math.random() * followerJitterMs;
163
+ scheduledTimer = setTimeout(() => {
164
+ scheduledTimer = undefined;
165
+ void runScheduledRefresh();
166
+ }, Math.max(retryBaseMs, expiresAt - bufferMs - now) + jitter);
167
+ }
168
+ /**
169
+ * Retries are spent and the credential still has time on it: keep serving
170
+ * it until its expiry instant, then fail closed and tell the host, as the
171
+ * cloud store's clear-at-expiry does. A dead scheduler must never leave an
172
+ * expired token in circulation.
173
+ */
174
+ function armClearAtExpiry(expiring) {
175
+ const now = host.now();
176
+ clearExpiry();
177
+ expiryTimer = setTimeout(() => {
178
+ expiryTimer = undefined;
179
+ const committed = host.commitExpired(expiring);
180
+ if (!committed)
181
+ return;
182
+ stopScheduledRefresh();
183
+ reportOutcome?.({ outcome: 'expired', failure: committed });
184
+ }, Math.max(0, expiring.expiresAt - now));
185
+ }
186
+ /**
187
+ * The scheduled re-mint mirrors the cloud store's refresh semantics: a
188
+ * transient failure keeps the still-valid credential and retries with
189
+ * doubling backoff; a permanent failure commits the error; exhausted
190
+ * retries keep the credential until it expires, then fail closed. Every
191
+ * commit runs publish() before reporting its outcome — host outcome
192
+ * handlers read state the publish just wrote.
193
+ */
194
+ async function runScheduledRefresh() {
195
+ const user = host.getCurrentUser();
196
+ if (!user)
197
+ return;
198
+ const guards = host.captureGuards();
199
+ // Refresh with the target that produced the live credential, so a
200
+ // scheduled refresh reproduces the same session AND coalesces with any
201
+ // concurrent reactive re-mint for it.
202
+ const { mintId, response } = host.mint(user);
203
+ let result;
204
+ try {
205
+ result = await response;
206
+ }
207
+ catch {
208
+ result = { status: 'error', code: 'TOKEN_EXCHANGE_FAILED' };
209
+ }
210
+ if (!host.guardsHold(guards, user, mintId))
211
+ return;
212
+ if (result.status === 'ok') {
213
+ const rejected = host.commitRefreshed(result.session, mintId);
214
+ if (rejected) {
215
+ reportOutcome?.({ outcome: 'permanent_failure', failure: rejected });
216
+ return;
217
+ }
218
+ clearExpiry();
219
+ armScheduledRefresh(result.session.expiresAt, host.now());
220
+ publishToSiblings(result.session);
221
+ reportOutcome?.({ outcome: 'succeeded' });
222
+ return;
223
+ }
224
+ if (isPermanentSessionError(result.code)) {
225
+ host.commitPermanentFailure(result);
226
+ reportOutcome?.({ outcome: 'permanent_failure', failure: result });
227
+ return;
228
+ }
229
+ if (scheduledRetryCount >= maxRetries) {
230
+ const live = host.getCredential();
231
+ if (live !== undefined)
232
+ armClearAtExpiry(live);
233
+ // A leader with a dead chain must not sit on the lease: release it so
234
+ // a sibling can lead, and queue again as a follower so this tab can
235
+ // adopt what that sibling mints or be promoted back if nobody does.
236
+ teardownCoordination();
237
+ ensureCoordination();
238
+ reportOutcome?.({ outcome: 'retries_exhausted' });
239
+ return;
240
+ }
241
+ const delay = retryBaseMs * 2 ** scheduledRetryCount;
242
+ scheduledRetryCount += 1;
243
+ stopScheduledRefresh();
244
+ scheduledTimer = setTimeout(() => {
245
+ scheduledTimer = undefined;
246
+ void runScheduledRefresh();
247
+ }, delay);
248
+ reportOutcome?.({ outcome: 'retry_scheduled' });
249
+ }
250
+ return {
251
+ armAfterCommit(session, now) {
252
+ clearExpiry();
253
+ armScheduledRefresh(session.expiresAt, now);
254
+ publishToSiblings(session);
255
+ },
256
+ stop() {
257
+ stopScheduledRefresh();
258
+ clearExpiry();
259
+ teardownCoordination();
260
+ }
261
+ };
262
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * The workspace session: a short-lived JWT minted from the signed-in
3
+ * identity, which is what actually authorizes runs and balance reads.
4
+ *
5
+ * The exchange contract, response schema, and error taxonomy are extracted
6
+ * from the cloud app's production implementation of this same POST
7
+ * /auth/token call (`requestToken` in
8
+ * src/platform/workspace/stores/workspaceAuthStore.ts) — keep the two in
9
+ * step. Only the freshness strategy differs: valid-on-read (callers await
10
+ * `ensureFresh` at the moment they need a token; it never resolves with
11
+ * less than `freshMarginMs` of validity) rather than a proactive refresh
12
+ * timer, because background tabs throttle timers and this package's callers
13
+ * can await. A host that needs proactive refresh wraps this client with its
14
+ * own scheduler.
15
+ *
16
+ * The credential cache sits behind the host's storage adapter keyed to the
17
+ * signed-in uid, so a token survives a reload but can never be served to a
18
+ * different signed-in user.
19
+ */
20
+ import type { CredentialStorage } from './credentialCache.js';
21
+ import type { AccountIdentity } from './identity.js';
22
+ import type { AccountCredential, AccountUser, RefreshSchedulerOptions, SessionErrorCode, SessionFailure, SessionResult } from './sessionContracts.js';
23
+ export type { AccountIdentity } from './identity.js';
24
+ export type { AccountCredential, AccountUser, CrossTabRefreshPort, MintHandle, RefreshSchedulerOptions, ScheduledRefreshReport, SessionErrorCode, SessionFailure, SessionRefreshOutcome, SessionResult } from './sessionContracts.js';
25
+ export { isPermanentSessionError } from './sessionContracts.js';
26
+ export type { CredentialStorage } from './credentialCache.js';
27
+ export { isCredentialFresh } from './credentialCache.js';
28
+ /**
29
+ * The session error codes, keys only. Hosts own the copy (the cloud app's
30
+ * workspaceAuth.errors.*); the package ships the vocabulary so a host can
31
+ * narrow an arbitrary error code to one it has a line for. Exhaustive by
32
+ * type: a new SessionErrorCode is a compile error until it is listed here.
33
+ */
34
+ export declare const SESSION_ERROR_CODES: Readonly<Record<SessionErrorCode, true>>;
35
+ export { SESSION_TELEMETRY_EVENT } from '../telemetry.js';
36
+ export interface SessionRequestOptions {
37
+ readonly fetchImpl?: typeof fetch;
38
+ readonly now?: () => number;
39
+ readonly signal?: AbortSignal;
40
+ readonly timeoutMs?: number;
41
+ /** Mint for this workspace instead of the server-resolved personal one. */
42
+ readonly workspaceId?: string;
43
+ /**
44
+ * On a transient remint failure, keep the currently published credential
45
+ * instead of committing the error — for hosts whose still-valid token
46
+ * must survive a failed proactive or reactive refresh. Permanent
47
+ * failures always commit.
48
+ */
49
+ readonly preserveCredentialOnTransientFailure?: boolean;
50
+ }
51
+ export interface SessionClientOptions extends SessionRequestOptions {
52
+ readonly exchangeUrl: string;
53
+ readonly storage: CredentialStorage;
54
+ readonly freshMarginMs?: number;
55
+ readonly refreshScheduler?: RefreshSchedulerOptions;
56
+ /** Applies to the identity passed at construction; see `AttachIdentityOptions.autoMint`. */
57
+ readonly autoMint?: boolean;
58
+ }
59
+ /**
60
+ * `pending` is the initial phase, before the attached identity has delivered
61
+ * even once, so a host can tell "Firebase has not answered yet" (pending)
62
+ * from "nobody is signed in" (a delivered null) without wrapping the port.
63
+ */
64
+ export type SessionSnapshot<TUser extends AccountUser = AccountUser> = {
65
+ readonly phase: 'pending';
66
+ readonly user: null;
67
+ readonly session: undefined;
68
+ } | {
69
+ readonly phase: 'signed-out';
70
+ readonly user: null;
71
+ readonly session: undefined;
72
+ } | {
73
+ readonly phase: 'minting';
74
+ readonly user: TUser;
75
+ readonly session: undefined;
76
+ } | {
77
+ readonly phase: 'authenticated';
78
+ readonly user: TUser;
79
+ readonly session: AccountCredential;
80
+ } | {
81
+ readonly phase: 'error';
82
+ readonly user: TUser;
83
+ readonly session: undefined;
84
+ readonly failure: SessionFailure;
85
+ };
86
+ export interface AttachIdentityOptions {
87
+ /**
88
+ * When false, an identity event sets the user and publishes without
89
+ * starting a warm-up mint — for hosts that drive every mint explicitly.
90
+ */
91
+ readonly autoMint?: boolean;
92
+ }
93
+ export interface SessionClient<TUser extends AccountUser = AccountUser> {
94
+ /** @deprecated Transitional Pinia-adapter seam; pass the identity as `createSessionClient`'s second argument instead (FE-2171, PoC #16639). */
95
+ attachIdentity: (identity: AccountIdentity<TUser>, options?: AttachIdentityOptions) => () => void;
96
+ /**
97
+ * Detaches the current identity; the persisted credential stays until
98
+ * `clearStoredCredential`. Not terminal, like a detach: an explicit-user
99
+ * mint issued after `dispose()` still commits and can re-arm the scheduler
100
+ * and cross-tab lease, so dispose and then stop calling the client.
101
+ */
102
+ dispose: () => void;
103
+ getSnapshot: () => SessionSnapshot<TUser>;
104
+ subscribe: (listener: (snapshot: SessionSnapshot<TUser>) => void) => () => void;
105
+ /** The current credential's token, uid-guarded and withheld once expired (zero margin); no `freshMarginMs` or proactive-refresh promise. */
106
+ getToken: () => string | undefined;
107
+ /**
108
+ * The valid-on-read entry point: resolves with a session that has more
109
+ * than `freshMarginMs` of validity, minting inside the call when the
110
+ * cache cannot promise that. Resolves undefined when nobody is signed in,
111
+ * when the identity changed while the mint was in flight, or when a
112
+ * concurrent mint for a different target superseded this call.
113
+ *
114
+ * Concurrent callers for one user share one in-flight mint, which runs
115
+ * with the first caller's `timeoutMs`; a later caller's own `signal`
116
+ * still releases that caller (with a transient failure) without
117
+ * cancelling the shared mint.
118
+ *
119
+ * An explicit-user call made before the identity port has ever fired
120
+ * (the popup path) resolves with the result, and the credential is
121
+ * cached — but the snapshot and getToken() stay signed-out until the
122
+ * port delivers that user, since the snapshot's user is the port's.
123
+ */
124
+ ensureFresh: (requestedUser?: AccountUser, options?: SessionRequestOptions) => Promise<SessionResult | undefined>;
125
+ /** A mint that ignores the cache — for the one 401-retry a read allows. */
126
+ remint: (requestedUser?: AccountUser, options?: SessionRequestOptions) => Promise<SessionResult | undefined>;
127
+ /**
128
+ * Fail closed on a host scope change: drop the published credential,
129
+ * cancel scheduled work, and invalidate in-flight mints. The identity
130
+ * stays, because it belongs to the port, so a targeted re-mint can
131
+ * follow at once. Identity changes fail closed through the port itself.
132
+ */
133
+ invalidate: () => void;
134
+ /**
135
+ * Drops only the persisted copy of the credential. The published session
136
+ * stays live; a host that must end it detaches or invalidates as well.
137
+ */
138
+ clearStoredCredential: () => void;
139
+ }
140
+ /**
141
+ * The status→code mapping from `requestToken`: 401/403/404 are permanent
142
+ * failures with their own codes; everything else — 5xx, network failure,
143
+ * abort, unparseable body — collapses to TOKEN_EXCHANGE_FAILED, matching
144
+ * production's default branch.
145
+ */
146
+ export declare function createSessionClient<TUser extends AccountUser = AccountUser>(clientOptions: SessionClientOptions, identity?: AccountIdentity<TUser>): SessionClient<TUser>;