@comfyorg/account-core 1.0.0-alpha.0 → 1.0.0-alpha.1
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.
- package/dist/core/billing/billingContracts.d.ts +29 -14
- package/dist/core/billing/billingErrorBody.d.ts +8 -4
- package/dist/core/billing/billingErrorBody.js +14 -4
- package/dist/core/billing/credentialedTransport.d.ts +23 -7
- package/dist/core/billing/credentialedTransport.js +72 -8
- package/dist/core/billing/events.d.ts +47 -0
- package/dist/core/billing/events.js +43 -0
- package/dist/core/billing/index.d.ts +16 -11
- package/dist/core/billing/index.js +8 -6
- package/dist/core/billing/operationLifecycle.d.ts +34 -1
- package/dist/core/billing/operationLifecycle.js +146 -67
- package/dist/core/billing/operationPointer.d.ts +27 -2
- package/dist/core/billing/operationPointer.js +36 -25
- package/dist/core/billing/operationPolicy.d.ts +44 -7
- package/dist/core/billing/operationPolicy.js +64 -3
- package/dist/core/billing/operationState.d.ts +102 -18
- package/dist/core/billing/operationState.js +78 -8
- package/dist/core/billing/paymentCopy.d.ts +6 -1
- package/dist/core/billing/paymentCopy.js +34 -1
- package/dist/core/billing/paymentProjection.d.ts +14 -2
- package/dist/core/billing/paymentProjection.js +37 -9
- package/dist/core/billing/scopedReader.d.ts +11 -5
- package/dist/core/billing/scopedReader.js +20 -9
- package/dist/core/billing/sharedRead.js +4 -2
- package/dist/core/billing/subscriptionCommands.d.ts +37 -9
- package/dist/core/billing/subscriptionCommands.js +42 -30
- package/dist/core/billing/topup.d.ts +26 -0
- package/dist/core/billing/topup.js +36 -2
- package/dist/core/billing/wireCents.d.ts +8 -0
- package/dist/core/billing/wireCents.js +8 -0
- package/dist/core/billing/workspaceInvites.d.ts +12 -0
- package/dist/core/billing/workspaceInvites.js +26 -0
- package/dist/core/identity.d.ts +6 -6
- package/dist/core/identity.js +4 -10
- package/dist/core/lazyIdentity.d.ts +31 -0
- package/dist/core/lazyIdentity.js +96 -0
- package/dist/core/requestAuth.d.ts +30 -0
- package/dist/core/requestAuth.js +41 -0
- package/dist/core/session.d.ts +12 -18
- package/dist/core/session.js +16 -26
- package/dist/core/sessionContracts.d.ts +39 -2
- package/dist/core/sessionTokenMint.d.ts +33 -0
- package/dist/core/sessionTokenMint.js +205 -0
- package/dist/core/webSession.d.ts +25 -0
- package/dist/core/webSession.js +137 -0
- package/dist/core/webSessionFlag.d.ts +20 -0
- package/dist/core/webSessionFlag.js +62 -0
- package/dist/core/webSessionIdentity.d.ts +291 -0
- package/dist/core/webSessionIdentity.js +496 -0
- package/dist/firebase/configSource.d.ts +54 -0
- package/dist/firebase/configSource.js +111 -0
- package/dist/firebase/index.d.ts +71 -8
- package/dist/firebase/index.js +236 -23
- package/dist/firebase/popupWatch.d.ts +42 -0
- package/dist/firebase/popupWatch.js +140 -0
- package/dist/testing.d.ts +38 -0
- package/dist/testing.js +116 -0
- package/dist/web/crossTabRefresh.d.ts +4 -2
- package/dist/web/crossTabRefresh.js +13 -0
- package/dist/workspaceLink.d.ts +45 -0
- package/dist/workspaceLink.js +78 -0
- package/package.json +30 -6
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the shared web session on ingest (`/api/auth/session*`). It only
|
|
3
|
+
* sends and classifies: retries, boot rules, and sign-out belong to callers.
|
|
4
|
+
* Identity proof is an opaque string from the caller, so no provider leaks in.
|
|
5
|
+
*/
|
|
6
|
+
import { zCreateSessionResponse, zDeleteSessionResponse, zErrorResponse, zGetSessionResponse, zRevokeAllSessionsResponse } from '@comfyorg/ingest-types/zod';
|
|
7
|
+
const UNAUTHORIZED_CODES = {
|
|
8
|
+
no_session: 'NO_SESSION',
|
|
9
|
+
session_expired: 'SESSION_EXPIRED',
|
|
10
|
+
session_revoked: 'SESSION_REVOKED',
|
|
11
|
+
TOKEN_REVOKED: 'SESSION_REVOKED'
|
|
12
|
+
};
|
|
13
|
+
const FORBIDDEN_CODES = {
|
|
14
|
+
csrf_invalid: 'CSRF_STALE',
|
|
15
|
+
workspace_access_denied: 'WORKSPACE_ACCESS_DENIED'
|
|
16
|
+
};
|
|
17
|
+
function failure(code, httpStatus, serverCode) {
|
|
18
|
+
return {
|
|
19
|
+
status: 'error',
|
|
20
|
+
code,
|
|
21
|
+
retryable: code === 'SESSION_UNAVAILABLE',
|
|
22
|
+
...(httpStatus === undefined ? {} : { httpStatus }),
|
|
23
|
+
...(serverCode === undefined ? {} : { serverCode })
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function readJson(response) {
|
|
27
|
+
try {
|
|
28
|
+
return await response.json();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function classifyFailure(response) {
|
|
35
|
+
const { status } = response;
|
|
36
|
+
if (status === 429 || status >= 500) {
|
|
37
|
+
return failure('SESSION_UNAVAILABLE', status);
|
|
38
|
+
}
|
|
39
|
+
const parsed = zErrorResponse.safeParse(await readJson(response));
|
|
40
|
+
if (!parsed.success)
|
|
41
|
+
return failure('SESSION_REQUEST_REFUSED', status);
|
|
42
|
+
const serverCode = parsed.data.code;
|
|
43
|
+
const byServerCode = status === 401
|
|
44
|
+
? (UNAUTHORIZED_CODES[serverCode] ?? 'NO_SESSION')
|
|
45
|
+
: status === 403
|
|
46
|
+
? (FORBIDDEN_CODES[serverCode] ?? 'SESSION_REQUEST_REFUSED')
|
|
47
|
+
: 'SESSION_REQUEST_REFUSED';
|
|
48
|
+
return failure(byServerCode, status, serverCode);
|
|
49
|
+
}
|
|
50
|
+
async function send(options, path, init) {
|
|
51
|
+
let response;
|
|
52
|
+
try {
|
|
53
|
+
response = await options.fetchImpl(`${options.apiBaseUrl.replace(/\/+$/, '')}${path}`, { ...init, credentials: 'include', signal: options.signal });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return failure('SESSION_UNAVAILABLE');
|
|
57
|
+
}
|
|
58
|
+
return response.ok ? { response } : classifyFailure(response);
|
|
59
|
+
}
|
|
60
|
+
export async function readWebSession(options, { expectedUserId } = {}) {
|
|
61
|
+
const sent = await send(options, '/auth/session', {
|
|
62
|
+
method: 'GET',
|
|
63
|
+
cache: 'no-store'
|
|
64
|
+
});
|
|
65
|
+
if (!('response' in sent))
|
|
66
|
+
return sent;
|
|
67
|
+
const { status } = sent.response;
|
|
68
|
+
const parsed = zGetSessionResponse.safeParse(await readJson(sent.response));
|
|
69
|
+
if (!parsed.success)
|
|
70
|
+
return failure('SESSION_UNAVAILABLE', status);
|
|
71
|
+
const { user, csrf_token, expires_at, absolute_expires_at } = parsed.data;
|
|
72
|
+
if (expectedUserId !== undefined && user.id !== expectedUserId) {
|
|
73
|
+
return failure('IDENTITY_CHANGED', status);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
status: 'ok',
|
|
77
|
+
session: {
|
|
78
|
+
user: {
|
|
79
|
+
id: user.id,
|
|
80
|
+
email: user.email,
|
|
81
|
+
name: user.name,
|
|
82
|
+
emailVerified: user.email_verified,
|
|
83
|
+
signInProvider: user.sign_in_provider
|
|
84
|
+
},
|
|
85
|
+
csrfToken: csrf_token,
|
|
86
|
+
expiresAt: Date.parse(expires_at),
|
|
87
|
+
absoluteExpiresAt: Date.parse(absolute_expires_at)
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Creates the session, then reads it back for the user and CSRF token. A
|
|
93
|
+
* failure to produce the proof is the provider's error and propagates.
|
|
94
|
+
*/
|
|
95
|
+
export async function createWebSession(options, getIdentityProof, { expectedUserId } = {}) {
|
|
96
|
+
const proof = await getIdentityProof();
|
|
97
|
+
const sent = await send(options, '/auth/session', {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { Authorization: `Bearer ${proof}` }
|
|
100
|
+
});
|
|
101
|
+
if (!('response' in sent))
|
|
102
|
+
return sent;
|
|
103
|
+
const parsed = zCreateSessionResponse.safeParse(await readJson(sent.response));
|
|
104
|
+
if (!parsed.success || !parsed.data.success) {
|
|
105
|
+
return failure('SESSION_UNAVAILABLE', sent.response.status);
|
|
106
|
+
}
|
|
107
|
+
return readWebSession(options, { expectedUserId });
|
|
108
|
+
}
|
|
109
|
+
/** Sends no CSRF token: sign-out must work against a dead session. */
|
|
110
|
+
export async function deleteWebSession(options) {
|
|
111
|
+
const sent = await send(options, '/auth/session', { method: 'DELETE' });
|
|
112
|
+
if (!('response' in sent))
|
|
113
|
+
return sent;
|
|
114
|
+
const parsed = zDeleteSessionResponse.safeParse(await readJson(sent.response));
|
|
115
|
+
if (!parsed.success || !parsed.data.success) {
|
|
116
|
+
return failure('SESSION_UNAVAILABLE', sent.response.status);
|
|
117
|
+
}
|
|
118
|
+
return { status: 'ok' };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Signs the user out of every device. Only a body the contract recognises is
|
|
122
|
+
* ok: the caller tells the user every device is signed out on that answer.
|
|
123
|
+
*/
|
|
124
|
+
export async function revokeAllWebSessions(options, csrfToken, getIdentityProof) {
|
|
125
|
+
const proof = await getIdentityProof();
|
|
126
|
+
const sent = await send(options, '/auth/sessions/revoke-all', {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
headers: { Authorization: `Bearer ${proof}`, 'X-CSRF-Token': csrfToken }
|
|
129
|
+
});
|
|
130
|
+
if (!('response' in sent))
|
|
131
|
+
return sent;
|
|
132
|
+
const parsed = zRevokeAllSessionsResponse.safeParse(await readJson(sent.response));
|
|
133
|
+
if (!parsed.success) {
|
|
134
|
+
return failure('SESSION_UNAVAILABLE', sent.response.status);
|
|
135
|
+
}
|
|
136
|
+
return { status: 'ok' };
|
|
137
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `unified_web_session` for a site with no sign-in of its own to ask with.
|
|
3
|
+
* The anonymous `/api/features` document's global `web_session_probe` only
|
|
4
|
+
* decides whether to ask; the answer is the credentialed read's
|
|
5
|
+
* `unified_web_session` (cloud#10689). `web_session_probe` is a backend
|
|
6
|
+
* follow-up to cloud#10689 and not served yet. Every failure is `false`.
|
|
7
|
+
*/
|
|
8
|
+
export interface FeaturesReadOptions {
|
|
9
|
+
/** Cloud origin, e.g. `https://cloud.comfy.org`. */
|
|
10
|
+
readonly cloudBaseUrl: string;
|
|
11
|
+
readonly fetchImpl: typeof fetch;
|
|
12
|
+
readonly timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface UnifiedWebSessionOptions extends FeaturesReadOptions {
|
|
15
|
+
/** Settles the anonymous document's `web_session_probe`. */
|
|
16
|
+
readonly probe: () => Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
/** A plain anonymous GET: no cookie, no custom header, so no CORS preflight. */
|
|
19
|
+
export declare function readWebSessionProbe(options: FeaturesReadOptions): Promise<boolean>;
|
|
20
|
+
export declare function resolveUnifiedWebSession({ probe, ...options }: UnifiedWebSessionOptions): Promise<boolean>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `unified_web_session` for a site with no sign-in of its own to ask with.
|
|
3
|
+
* The anonymous `/api/features` document's global `web_session_probe` only
|
|
4
|
+
* decides whether to ask; the answer is the credentialed read's
|
|
5
|
+
* `unified_web_session` (cloud#10689). `web_session_probe` is a backend
|
|
6
|
+
* follow-up to cloud#10689 and not served yet. Every failure is `false`.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
9
|
+
function readsLiteralTrue(body, key) {
|
|
10
|
+
return (typeof body === 'object' && body !== null && Reflect.get(body, key) === true);
|
|
11
|
+
}
|
|
12
|
+
async function readFeatures({ cloudBaseUrl, fetchImpl, timeoutMs = DEFAULT_TIMEOUT_MS }, init) {
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetchImpl(`${cloudBaseUrl}/api/features`, {
|
|
17
|
+
...init,
|
|
18
|
+
signal: controller.signal
|
|
19
|
+
});
|
|
20
|
+
return response.ok ? await response.json() : undefined;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
clearTimeout(timeout);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** A plain anonymous GET: no cookie, no custom header, so no CORS preflight. */
|
|
30
|
+
export async function readWebSessionProbe(options) {
|
|
31
|
+
return readsLiteralTrue(await readFeatures(options, { credentials: 'omit' }), 'web_session_probe');
|
|
32
|
+
}
|
|
33
|
+
async function probeIsOn(probe) {
|
|
34
|
+
try {
|
|
35
|
+
return await probe();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function loadClientHeader() {
|
|
42
|
+
try {
|
|
43
|
+
const { COMFY_CLIENT } = await import('./requestAuth.js');
|
|
44
|
+
return COMFY_CLIENT;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function resolveUnifiedWebSession({ probe, ...options }) {
|
|
51
|
+
if (!(await probeIsOn(probe)))
|
|
52
|
+
return false;
|
|
53
|
+
const client = await loadClientHeader();
|
|
54
|
+
if (client === undefined)
|
|
55
|
+
return false;
|
|
56
|
+
const body = await readFeatures(options, {
|
|
57
|
+
credentials: 'include',
|
|
58
|
+
cache: 'no-store',
|
|
59
|
+
headers: { 'X-Comfy-Client': client }
|
|
60
|
+
});
|
|
61
|
+
return readsLiteralTrue(body, 'unified_web_session');
|
|
62
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session-backed identity source: boots a site from `GET /auth/session`
|
|
3
|
+
* and follows the server's answer. It keeps no record of past sessions, and
|
|
4
|
+
* the remembered provider login is an opaque port, so no provider leaks in.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import type { CrossTabRefreshPort, WebSession, WebSessionCommandResult, WebSessionResult } from './sessionContracts.js';
|
|
8
|
+
import type { WebSessionOptions } from './webSession.js';
|
|
9
|
+
export interface RememberedLogin {
|
|
10
|
+
/** The Comfy user id of the login this browser remembers, or null. */
|
|
11
|
+
currentUserId: () => Promise<string | null>;
|
|
12
|
+
/** Null when the provider has no usable login; a throw is transient. */
|
|
13
|
+
getProof: () => Promise<string | null>;
|
|
14
|
+
signOutLocally: () => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export type WebSessionPrincipal = {
|
|
17
|
+
readonly kind: 'account';
|
|
18
|
+
readonly rememberedLogin: RememberedLogin;
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: 'api_key';
|
|
21
|
+
};
|
|
22
|
+
export type WebSessionBootOutcome = 'signed_in' | 'restored' | 'signed_out' | 'revoked' | 'restore_failed';
|
|
23
|
+
export interface WebSessionBootstrapEvent {
|
|
24
|
+
readonly outcome: WebSessionBootOutcome;
|
|
25
|
+
readonly origin: string;
|
|
26
|
+
}
|
|
27
|
+
type SignedOutOutcome = Exclude<WebSessionBootOutcome, 'signed_in' | 'restored'>;
|
|
28
|
+
/** The account this tab holds changed under it, or went away. */
|
|
29
|
+
export type WebSessionAccountChange = {
|
|
30
|
+
readonly reason: 'user_changed';
|
|
31
|
+
readonly session: WebSession;
|
|
32
|
+
} | {
|
|
33
|
+
readonly reason: 'signed_out';
|
|
34
|
+
readonly outcome: SignedOutOutcome;
|
|
35
|
+
};
|
|
36
|
+
export type WebSessionIdentityState = {
|
|
37
|
+
readonly phase: 'idle';
|
|
38
|
+
} | {
|
|
39
|
+
readonly phase: 'api_key';
|
|
40
|
+
} | {
|
|
41
|
+
readonly phase: 'reading' | 'retry_wait' | 'restoring';
|
|
42
|
+
readonly failures: number;
|
|
43
|
+
} | {
|
|
44
|
+
readonly phase: 'signed_in';
|
|
45
|
+
readonly session: WebSession;
|
|
46
|
+
} | {
|
|
47
|
+
readonly phase: 'signed_out';
|
|
48
|
+
readonly outcome: SignedOutOutcome;
|
|
49
|
+
};
|
|
50
|
+
/** Only a sibling's sign-in is news to a tab that has signed out. */
|
|
51
|
+
export type SessionObservedFrom = 'this_tab' | 'sibling_heartbeat' | 'sibling_sign_in' | 'sibling_sign_out';
|
|
52
|
+
export type WebSessionIdentityEvent = {
|
|
53
|
+
readonly type: 'boot';
|
|
54
|
+
} | {
|
|
55
|
+
readonly type: 'read_answered' | 'restore_answered';
|
|
56
|
+
readonly result: WebSessionResult;
|
|
57
|
+
readonly rememberedUserId: string | null;
|
|
58
|
+
} | {
|
|
59
|
+
readonly type: 'proof_missing' | 'proof_errored' | 'login_errored';
|
|
60
|
+
} | {
|
|
61
|
+
readonly type: 'retry_due';
|
|
62
|
+
}
|
|
63
|
+
/** A heartbeat answer, this tab's or one a sibling tab published. */
|
|
64
|
+
| {
|
|
65
|
+
readonly type: 'session_observed';
|
|
66
|
+
readonly from: SessionObservedFrom;
|
|
67
|
+
readonly result: WebSessionResult;
|
|
68
|
+
readonly rememberedUserId: string | null;
|
|
69
|
+
} | {
|
|
70
|
+
readonly type: 'session_created';
|
|
71
|
+
readonly session: WebSession;
|
|
72
|
+
} | {
|
|
73
|
+
readonly type: 'sign_out_requested';
|
|
74
|
+
};
|
|
75
|
+
export type WebSessionIdentityEffect = {
|
|
76
|
+
readonly type: 'read';
|
|
77
|
+
} | {
|
|
78
|
+
readonly type: 'restore';
|
|
79
|
+
readonly expectedUserId: string;
|
|
80
|
+
} | {
|
|
81
|
+
readonly type: 'schedule_retry';
|
|
82
|
+
readonly failures: number;
|
|
83
|
+
} | {
|
|
84
|
+
readonly type: 'sign_out_locally';
|
|
85
|
+
} | {
|
|
86
|
+
readonly type: 'report';
|
|
87
|
+
readonly outcome: WebSessionBootOutcome;
|
|
88
|
+
} | {
|
|
89
|
+
readonly type: 'account_changed';
|
|
90
|
+
readonly change: WebSessionAccountChange;
|
|
91
|
+
} | {
|
|
92
|
+
readonly type: 'report_signed_out_remotely';
|
|
93
|
+
};
|
|
94
|
+
export interface WebSessionIdentityTransition {
|
|
95
|
+
readonly state: WebSessionIdentityState;
|
|
96
|
+
readonly effects: readonly WebSessionIdentityEffect[];
|
|
97
|
+
}
|
|
98
|
+
/** Pure: an event that means nothing in the current phase changes nothing. */
|
|
99
|
+
export declare function transitionWebSessionIdentity(state: WebSessionIdentityState, event: WebSessionIdentityEvent): WebSessionIdentityTransition;
|
|
100
|
+
export type ScheduleRetry = (run: () => void, delayMs: number) => () => void;
|
|
101
|
+
export interface VisibilityPort {
|
|
102
|
+
isVisible: () => boolean;
|
|
103
|
+
onChange: (listener: (visible: boolean) => void) => () => void;
|
|
104
|
+
}
|
|
105
|
+
export interface WebSessionHeartbeatOptions {
|
|
106
|
+
readonly visibility: VisibilityPort;
|
|
107
|
+
/** Without it every tab reads for itself. */
|
|
108
|
+
readonly crossTab?: CrossTabRefreshPort<WebSessionSharedMessage>;
|
|
109
|
+
readonly intervalMs?: number;
|
|
110
|
+
}
|
|
111
|
+
export interface WebSessionIdentityOptions {
|
|
112
|
+
readonly session: WebSessionOptions;
|
|
113
|
+
readonly principal: WebSessionPrincipal;
|
|
114
|
+
/** The site reporting the boot, e.g. `https://www.comfy.org`. */
|
|
115
|
+
readonly origin: string;
|
|
116
|
+
readonly onBootstrap?: (event: WebSessionBootstrapEvent) => void;
|
|
117
|
+
/** Telemetry for `session_signed_out_remotely`. */
|
|
118
|
+
readonly onSignedOutRemotely?: (event: {
|
|
119
|
+
readonly origin: string;
|
|
120
|
+
}) => void;
|
|
121
|
+
/** The host drops pending actions and user-scoped state here. */
|
|
122
|
+
readonly onAccountChanged?: (change: WebSessionAccountChange & {
|
|
123
|
+
readonly epoch: number;
|
|
124
|
+
}) => void;
|
|
125
|
+
readonly heartbeat?: WebSessionHeartbeatOptions;
|
|
126
|
+
readonly schedule?: ScheduleRetry;
|
|
127
|
+
readonly retryBaseMs?: number;
|
|
128
|
+
readonly retryMaxMs?: number;
|
|
129
|
+
}
|
|
130
|
+
export interface WebSessionIdentity {
|
|
131
|
+
getState: () => WebSessionIdentityState;
|
|
132
|
+
/** Delivers the current state at once, then every change. */
|
|
133
|
+
subscribe: (listener: (state: WebSessionIdentityState) => void) => () => void;
|
|
134
|
+
/** Starts the boot and the heartbeat; a no-op unless idle. */
|
|
135
|
+
boot: () => void;
|
|
136
|
+
/** Only after an interactive sign-in; a token refresh must never call it. */
|
|
137
|
+
signedIn: (getProof: () => Promise<string>) => Promise<WebSessionResult>;
|
|
138
|
+
signOut: () => Promise<WebSessionCommandResult>;
|
|
139
|
+
/** Changes with the account; an answer started under an older epoch is stale. */
|
|
140
|
+
getEpoch: () => number;
|
|
141
|
+
/** Cancels timers, ignores answers in flight, and returns to idle. */
|
|
142
|
+
dispose: () => void;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Every tab of a site reads the same host-only cookie, so a sibling's answer
|
|
146
|
+
* is this tab's own session observed elsewhere; that is what makes adopting
|
|
147
|
+
* it safe. A per-tab session scope would break this assumption.
|
|
148
|
+
*/
|
|
149
|
+
declare const zSharedMessage: z.ZodObject<{
|
|
150
|
+
from: z.ZodEnum<["heartbeat", "sign_in", "sign_out"]>;
|
|
151
|
+
result: z.ZodDiscriminatedUnion<"status", [z.ZodObject<{
|
|
152
|
+
status: z.ZodLiteral<"ok">;
|
|
153
|
+
session: z.ZodObject<{
|
|
154
|
+
user: z.ZodObject<{
|
|
155
|
+
id: z.ZodString;
|
|
156
|
+
email: z.ZodString;
|
|
157
|
+
name: z.ZodOptional<z.ZodString>;
|
|
158
|
+
emailVerified: z.ZodBoolean;
|
|
159
|
+
signInProvider: z.ZodOptional<z.ZodString>;
|
|
160
|
+
}, "strip", z.ZodTypeAny, {
|
|
161
|
+
email: string;
|
|
162
|
+
id: string;
|
|
163
|
+
emailVerified: boolean;
|
|
164
|
+
name?: string | undefined;
|
|
165
|
+
signInProvider?: string | undefined;
|
|
166
|
+
}, {
|
|
167
|
+
email: string;
|
|
168
|
+
id: string;
|
|
169
|
+
emailVerified: boolean;
|
|
170
|
+
name?: string | undefined;
|
|
171
|
+
signInProvider?: string | undefined;
|
|
172
|
+
}>;
|
|
173
|
+
csrfToken: z.ZodString;
|
|
174
|
+
expiresAt: z.ZodNumber;
|
|
175
|
+
absoluteExpiresAt: z.ZodNumber;
|
|
176
|
+
}, "strip", z.ZodTypeAny, {
|
|
177
|
+
user: {
|
|
178
|
+
email: string;
|
|
179
|
+
id: string;
|
|
180
|
+
emailVerified: boolean;
|
|
181
|
+
name?: string | undefined;
|
|
182
|
+
signInProvider?: string | undefined;
|
|
183
|
+
};
|
|
184
|
+
expiresAt: number;
|
|
185
|
+
csrfToken: string;
|
|
186
|
+
absoluteExpiresAt: number;
|
|
187
|
+
}, {
|
|
188
|
+
user: {
|
|
189
|
+
email: string;
|
|
190
|
+
id: string;
|
|
191
|
+
emailVerified: boolean;
|
|
192
|
+
name?: string | undefined;
|
|
193
|
+
signInProvider?: string | undefined;
|
|
194
|
+
};
|
|
195
|
+
expiresAt: number;
|
|
196
|
+
csrfToken: string;
|
|
197
|
+
absoluteExpiresAt: number;
|
|
198
|
+
}>;
|
|
199
|
+
}, "strip", z.ZodTypeAny, {
|
|
200
|
+
status: "ok";
|
|
201
|
+
session: {
|
|
202
|
+
user: {
|
|
203
|
+
email: string;
|
|
204
|
+
id: string;
|
|
205
|
+
emailVerified: boolean;
|
|
206
|
+
name?: string | undefined;
|
|
207
|
+
signInProvider?: string | undefined;
|
|
208
|
+
};
|
|
209
|
+
expiresAt: number;
|
|
210
|
+
csrfToken: string;
|
|
211
|
+
absoluteExpiresAt: number;
|
|
212
|
+
};
|
|
213
|
+
}, {
|
|
214
|
+
status: "ok";
|
|
215
|
+
session: {
|
|
216
|
+
user: {
|
|
217
|
+
email: string;
|
|
218
|
+
id: string;
|
|
219
|
+
emailVerified: boolean;
|
|
220
|
+
name?: string | undefined;
|
|
221
|
+
signInProvider?: string | undefined;
|
|
222
|
+
};
|
|
223
|
+
expiresAt: number;
|
|
224
|
+
csrfToken: string;
|
|
225
|
+
absoluteExpiresAt: number;
|
|
226
|
+
};
|
|
227
|
+
}>, z.ZodObject<{
|
|
228
|
+
status: z.ZodLiteral<"error">;
|
|
229
|
+
code: z.ZodLiteral<"SESSION_REVOKED">;
|
|
230
|
+
retryable: z.ZodLiteral<false>;
|
|
231
|
+
}, "strip", z.ZodTypeAny, {
|
|
232
|
+
code: "SESSION_REVOKED";
|
|
233
|
+
status: "error";
|
|
234
|
+
retryable: false;
|
|
235
|
+
}, {
|
|
236
|
+
code: "SESSION_REVOKED";
|
|
237
|
+
status: "error";
|
|
238
|
+
retryable: false;
|
|
239
|
+
}>]>;
|
|
240
|
+
}, "strip", z.ZodTypeAny, {
|
|
241
|
+
result: {
|
|
242
|
+
status: "ok";
|
|
243
|
+
session: {
|
|
244
|
+
user: {
|
|
245
|
+
email: string;
|
|
246
|
+
id: string;
|
|
247
|
+
emailVerified: boolean;
|
|
248
|
+
name?: string | undefined;
|
|
249
|
+
signInProvider?: string | undefined;
|
|
250
|
+
};
|
|
251
|
+
expiresAt: number;
|
|
252
|
+
csrfToken: string;
|
|
253
|
+
absoluteExpiresAt: number;
|
|
254
|
+
};
|
|
255
|
+
} | {
|
|
256
|
+
code: "SESSION_REVOKED";
|
|
257
|
+
status: "error";
|
|
258
|
+
retryable: false;
|
|
259
|
+
};
|
|
260
|
+
from: "heartbeat" | "sign_in" | "sign_out";
|
|
261
|
+
}, {
|
|
262
|
+
result: {
|
|
263
|
+
status: "ok";
|
|
264
|
+
session: {
|
|
265
|
+
user: {
|
|
266
|
+
email: string;
|
|
267
|
+
id: string;
|
|
268
|
+
emailVerified: boolean;
|
|
269
|
+
name?: string | undefined;
|
|
270
|
+
signInProvider?: string | undefined;
|
|
271
|
+
};
|
|
272
|
+
expiresAt: number;
|
|
273
|
+
csrfToken: string;
|
|
274
|
+
absoluteExpiresAt: number;
|
|
275
|
+
};
|
|
276
|
+
} | {
|
|
277
|
+
code: "SESSION_REVOKED";
|
|
278
|
+
status: "error";
|
|
279
|
+
retryable: false;
|
|
280
|
+
};
|
|
281
|
+
from: "heartbeat" | "sign_in" | "sign_out";
|
|
282
|
+
}>;
|
|
283
|
+
type SharedFrom = z.infer<typeof zSharedMessage>['from'];
|
|
284
|
+
/** What one tab of a site tells its siblings, and why. */
|
|
285
|
+
export interface WebSessionSharedMessage {
|
|
286
|
+
readonly from: SharedFrom;
|
|
287
|
+
readonly result: WebSessionResult;
|
|
288
|
+
}
|
|
289
|
+
/** Inert until `boot()`: constructing it sends no request and arms no timer. */
|
|
290
|
+
export declare function createWebSessionIdentity(options: WebSessionIdentityOptions): WebSessionIdentity;
|
|
291
|
+
export {};
|