@oxyhq/core 3.14.0 → 3.16.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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/mixins/OxyServices.applications.js +43 -0
- package/dist/cjs/mixins/OxyServices.assets.js +14 -35
- package/dist/cjs/mixins/OxyServices.auth.js +12 -1
- package/dist/cjs/mixins/OxyServices.links.js +68 -0
- package/dist/cjs/mixins/OxyServices.nodes.js +175 -0
- package/dist/cjs/mixins/index.js +8 -0
- package/dist/cjs/utils/ssoBounce.js +48 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/mixins/OxyServices.applications.js +43 -0
- package/dist/esm/mixins/OxyServices.assets.js +14 -35
- package/dist/esm/mixins/OxyServices.auth.js +12 -1
- package/dist/esm/mixins/OxyServices.links.js +65 -0
- package/dist/esm/mixins/OxyServices.nodes.js +172 -0
- package/dist/esm/mixins/index.js +8 -0
- package/dist/esm/utils/ssoBounce.js +46 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/mixins/OxyServices.applications.d.ts +51 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +8 -29
- package/dist/types/mixins/OxyServices.auth.d.ts +8 -0
- package/dist/types/mixins/OxyServices.links.d.ts +102 -0
- package/dist/types/mixins/OxyServices.nodes.d.ts +242 -0
- package/dist/types/mixins/index.d.ts +3 -1
- package/dist/types/utils/ssoBounce.d.ts +61 -0
- package/package.json +1 -1
- package/src/index.ts +5 -0
- package/src/mixins/OxyServices.applications.ts +79 -0
- package/src/mixins/OxyServices.assets.ts +14 -44
- package/src/mixins/OxyServices.auth.ts +35 -1
- package/src/mixins/OxyServices.links.ts +103 -0
- package/src/mixins/OxyServices.nodes.ts +348 -0
- package/src/mixins/__tests__/OxyServices.links.test.ts +154 -0
- package/src/mixins/__tests__/OxyServices.nodes.test.ts +341 -0
- package/src/mixins/__tests__/commonsSignIn.test.ts +41 -16
- package/src/mixins/__tests__/connectedApps.test.ts +123 -0
- package/src/mixins/__tests__/getFileDownloadUrl.test.ts +10 -24
- package/src/mixins/index.ts +10 -0
- package/src/utils/__tests__/ssoBounce.test.ts +28 -0
- package/src/utils/ssoBounce.ts +69 -0
package/dist/types/index.d.ts
CHANGED
|
@@ -41,13 +41,14 @@ export { getCanonicalUserHandle, getNormalizedUserHandle, } from './utils/userHa
|
|
|
41
41
|
export type { CanonicalUserHandleInput, UserHandleInput } from './utils/userHandle';
|
|
42
42
|
export { normalizeProfileLinks } from './utils/profileLinks';
|
|
43
43
|
export type { ProfileLink, ProfileLinkMetadata } from './utils/profileLinks';
|
|
44
|
-
export type { Application, PublicApplication, ApplicationMember, ApplicationCredential, ApplicationRole, ApplicationType, ApplicationStatus, ApplicationMemberStatus, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, InviteApplicationMemberInput, UpdateApplicationMemberInput, TransferApplicationOwnershipInput, CreateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, ApplicationSuccessResult, } from './mixins/OxyServices.applications';
|
|
44
|
+
export type { Application, PublicApplication, ConnectedApp, ApplicationMember, ApplicationCredential, ApplicationRole, ApplicationType, ApplicationStatus, ApplicationMemberStatus, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, InviteApplicationMemberInput, UpdateApplicationMemberInput, TransferApplicationOwnershipInput, CreateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, ApplicationSuccessResult, } from './mixins/OxyServices.applications';
|
|
45
45
|
export type { Workspace, WorkspaceMember, WorkspaceRole, WorkspaceType, WorkspaceStatus, WorkspaceMemberStatus, CreateWorkspaceInput, UpdateWorkspaceInput, InviteWorkspaceMemberInput, UpdateWorkspaceMemberInput, TransferWorkspaceOwnershipInput, WorkspaceSuccessResult, } from './mixins/OxyServices.workspaces';
|
|
46
46
|
export type { ReputationCategory, TrustTier, ReputationTransactionStatus, ReputationTargetEntityType, ReputationDisputeStatus, ReputationInfluenceContext, ReputationTransaction, ReputationBalanceBreakdown, ReputationInfluence, ReputationReliability, ReputationBalance, ReputationDispute, ReputationRule, ReputationLeaderboardEntry, ReputationInfluenceResult, ReverseReputationTransactionResult, AwardReputationInput, CreateReputationDisputeInput, ResolveReputationDisputeInput, UpsertReputationRuleInput, ReverseReputationTransactionInput, } from './mixins/OxyServices.reputation';
|
|
47
47
|
export { buildUserDid } from './mixins/OxyServices.identity';
|
|
48
48
|
export type { IdentityRecordType, UnlinkableAuthMethodType, LinkAuthMethodResult, PublishRecordResult, VerifyRecordResult, VerifyDomainResult, RemoveDomainResult, } from './mixins/OxyServices.identity';
|
|
49
49
|
export { parseIdPayload, parseAttestPayload, verifyPublicCardAttestation, } from './mixins/OxyServices.civic';
|
|
50
50
|
export type { CivicCardResult, IdCardRef, AttestQrPayload, ParsedAttestPayload, SubmitRealLifeAttestationInput, DenyValidationResult, VouchForPersonInput, WithdrawVouchResult, IssueCredentialInput, RevokeCredentialResult, } from './mixins/OxyServices.civic';
|
|
51
|
+
export type { UserNodeStatus, UserNodeMode, UserNodeController, UserNodeLivenessStatus, RegisterNodeInput, RemoveNodeResult } from './mixins/OxyServices.nodes';
|
|
51
52
|
export { SessionSyncRequiredError, AuthenticationFailedError, ensureValidToken, isAuthenticationError, withAuthErrorHandling, authenticatedApiCall, } from './utils/authHelpers';
|
|
52
53
|
export type { HandleApiErrorOptions } from './utils/authHelpers';
|
|
53
54
|
export { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual, } from './utils/sessionUtils';
|
|
@@ -93,7 +94,8 @@ export { CENTRAL_AUTH_URL, CENTRAL_IDP_APEX, resolveCentralAuthUrl } from './uti
|
|
|
93
94
|
export { parseSsoReturnFragment, consumeSsoReturn } from './utils/ssoReturn';
|
|
94
95
|
export type { SsoReturnKind, SsoReturnResult, ConsumeSsoReturnDeps } from './utils/ssoReturn';
|
|
95
96
|
export { generateSsoState } from './mixins/OxyServices.sso';
|
|
96
|
-
export { SSO_CALLBACK_PATH, SSO_GUARD_TTL_MS, ssoStateKey, ssoGuardKey, ssoDestKey, ssoNoSessionKey, ssoAttemptedKey, ssoCallbackBootstrapKey, ssoNavigate, getSsoCallbackBootstrapScript, buildSsoBounceUrl, isCentralIdPOrigin, guardActive, } from './utils/ssoBounce';
|
|
97
|
+
export { SSO_CALLBACK_PATH, SSO_GUARD_TTL_MS, ssoStateKey, ssoGuardKey, ssoDestKey, ssoNoSessionKey, ssoAttemptedKey, ssoPriorSessionKey, ssoCallbackBootstrapKey, ssoNavigate, getSsoCallbackBootstrapScript, buildSsoBounceUrl, isCentralIdPOrigin, guardActive, allowSsoBounce, } from './utils/ssoBounce';
|
|
98
|
+
export type { SsoBounceGate } from './utils/ssoBounce';
|
|
97
99
|
export { runColdBoot } from './utils/coldBoot';
|
|
98
100
|
export type { ColdBootStep, ColdBootStepResult, ColdBootSession, ColdBootSkip, ColdBootOutcome, RunColdBootOptions, } from './utils/coldBoot';
|
|
99
101
|
export { packageInfo } from './constants/version';
|
|
@@ -151,6 +151,31 @@ export interface PublicApplication {
|
|
|
151
151
|
/** Optional display name of the developer/owner organisation. */
|
|
152
152
|
developerName?: string;
|
|
153
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* A connected (OAuth-authorized) application from the current user's point of
|
|
156
|
+
* view: an application the user has granted access to via the consent flow.
|
|
157
|
+
*
|
|
158
|
+
* Returned by `GET /auth/grants` and rendered in the user-facing "Connected
|
|
159
|
+
* apps" management surface. Keyed by `applicationId` (the application's Mongo
|
|
160
|
+
* `_id`) rather than a credential/client id, so the grant — and a subsequent
|
|
161
|
+
* {@link OxyServicesApplicationsMixin.revokeAppGrant} — survive credential
|
|
162
|
+
* rotation. This is a display shape: it carries the application's name/logo and
|
|
163
|
+
* the granted scopes, never any membership or credential material.
|
|
164
|
+
*/
|
|
165
|
+
export interface ConnectedApp {
|
|
166
|
+
/** The connected application's Mongo `_id`. Use this to revoke the grant. */
|
|
167
|
+
applicationId: string;
|
|
168
|
+
/** Human-readable application name shown to the user. */
|
|
169
|
+
name: string;
|
|
170
|
+
/** Optional logo URL for the application. */
|
|
171
|
+
logoUrl?: string;
|
|
172
|
+
/** OAuth scopes the user has granted to the application. */
|
|
173
|
+
scopes: string[];
|
|
174
|
+
/** ISO timestamp of when the user first authorized the application. */
|
|
175
|
+
firstGrantedAt: string;
|
|
176
|
+
/** ISO timestamp of when the grant was last exercised. */
|
|
177
|
+
lastUsedAt: string;
|
|
178
|
+
}
|
|
154
179
|
/** Input accepted by `createApplication`. Staff-only fields are not settable here. */
|
|
155
180
|
export interface CreateApplicationInput {
|
|
156
181
|
name: string;
|
|
@@ -265,6 +290,32 @@ export declare function OxyServicesApplicationsMixin<T extends typeof OxyService
|
|
|
265
290
|
* key). URL-encoded before being placed in the path.
|
|
266
291
|
*/
|
|
267
292
|
getPublicApplication(clientId: string): Promise<PublicApplication>;
|
|
293
|
+
/**
|
|
294
|
+
* List the OAuth-authorized applications the current user has connected —
|
|
295
|
+
* the third-party apps the user granted access to via the consent flow.
|
|
296
|
+
* Each entry is a {@link ConnectedApp} carrying the application's display
|
|
297
|
+
* identity, the granted scopes, and when the grant was first made and last
|
|
298
|
+
* exercised. Requires an authenticated session.
|
|
299
|
+
*
|
|
300
|
+
* Backed by `GET /auth/grants`. The response is briefly cached
|
|
301
|
+
* (identity-scoped); {@link revokeAppGrant} busts that cache so a revoke is
|
|
302
|
+
* reflected on the next read.
|
|
303
|
+
*/
|
|
304
|
+
listConnectedApps(): Promise<ConnectedApp[]>;
|
|
305
|
+
/**
|
|
306
|
+
* Revoke the current user's grant for a connected application, identified by
|
|
307
|
+
* its application `_id` (a {@link ConnectedApp.applicationId}, NOT a
|
|
308
|
+
* credential/client id — keyed by application so the revocation survives
|
|
309
|
+
* credential rotation). After this the application can no longer act on the
|
|
310
|
+
* user's behalf until it is re-authorized.
|
|
311
|
+
*
|
|
312
|
+
* Backed by `DELETE /auth/grants/:applicationId`. On success the cached
|
|
313
|
+
* connected-apps list (`GET:/auth/grants`) is invalidated so the next
|
|
314
|
+
* {@link listConnectedApps} read reflects the removal.
|
|
315
|
+
*
|
|
316
|
+
* @param applicationId - The connected application's Mongo `_id`.
|
|
317
|
+
*/
|
|
318
|
+
revokeAppGrant(applicationId: string): Promise<void>;
|
|
268
319
|
/**
|
|
269
320
|
* List applications the current user is an active member of.
|
|
270
321
|
*
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { AccountStorageUsageResponse, AssetUploadInput, AssetUrlResponse, AssetVariant } from '../models/interfaces';
|
|
2
2
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
3
|
-
interface FileDownloadUrlOptions {
|
|
4
|
-
/** Omit bearer access tokens from generated URLs, even when authenticated. */
|
|
5
|
-
omitToken?: boolean;
|
|
6
|
-
}
|
|
7
3
|
export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
8
4
|
new (...args: any[]): {
|
|
9
5
|
/**
|
|
@@ -11,32 +7,16 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
|
|
|
11
7
|
*/
|
|
12
8
|
deleteFile(fileId: string): Promise<any>;
|
|
13
9
|
/**
|
|
14
|
-
* Build a synchronous file URL from an Oxy asset id.
|
|
10
|
+
* Build a synchronous, `<img src>`-ready file URL from an Oxy asset id.
|
|
15
11
|
*
|
|
16
|
-
* This
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* `${cloudURL}/<id>[?variant=...]` (e.g. `https://cloud.oxy.so/<id>?variant=thumb`).
|
|
23
|
-
* CloudFront resolves the id against the public media origin. No token,
|
|
24
|
-
* `fallback`, or origin query params are emitted — these URLs are cacheable
|
|
25
|
-
* and shareable.
|
|
26
|
-
* - **Signed / private asset** — an access token is present on the client OR
|
|
27
|
-
* `expiresIn` was passed (the caller explicitly wants an expiring/authorized
|
|
28
|
-
* URL) → keeps the authenticated origin form
|
|
29
|
-
* `${baseURL}/assets/<id>/stream?...&token=...`. Private assets are NOT on
|
|
30
|
-
* the public CDN, so they must go through the API origin that can authorize
|
|
31
|
-
* the request.
|
|
32
|
-
*
|
|
33
|
-
* `cloudURL` (default `https://cloud.oxy.so`) is configured once on the
|
|
34
|
-
* `OxyServices` constructor and read via `getCloudURL()`; the API origin is
|
|
35
|
-
* `getBaseURL()` (e.g. `https://api.oxy.so`).
|
|
36
|
-
*
|
|
37
|
-
* For a CDN-signed URL fetched from the API, use {@link getFileDownloadUrlAsync}.
|
|
12
|
+
* This method must never embed the caller's general access token in the
|
|
13
|
+
* returned URL. The URL is commonly rendered into DOM attributes, browser
|
|
14
|
+
* network panels, caches, and logs. Public asset URLs use the clean CDN
|
|
15
|
+
* origin; callers that need authorized/private access should use
|
|
16
|
+
* {@link getFileDownloadUrlAsync}, which asks the API for a scoped download
|
|
17
|
+
* URL instead of exposing the in-memory bearer token in a query string.
|
|
38
18
|
*/
|
|
39
|
-
getFileDownloadUrl(fileId: string, variant?: string, expiresIn?: number
|
|
19
|
+
getFileDownloadUrl(fileId: string, variant?: string, expiresIn?: number): string;
|
|
40
20
|
/**
|
|
41
21
|
* Get file download URL asynchronously (returns signed URL directly from CDN)
|
|
42
22
|
*/
|
|
@@ -172,4 +152,3 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
|
|
|
172
152
|
}>;
|
|
173
153
|
};
|
|
174
154
|
} & T;
|
|
175
|
-
export {};
|
|
@@ -94,6 +94,14 @@ export interface CommonsApprovalInfo {
|
|
|
94
94
|
scopes: string[];
|
|
95
95
|
/** The origin the session is bound to (the RP web origin), when applicable. */
|
|
96
96
|
boundOrigin?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Server-authoritative anti-phishing flag: `true` only when this device-flow
|
|
99
|
+
* sign-in was started from a verified, registered origin of a trusted app.
|
|
100
|
+
* The approver (Commons) shows a warning when this is `false`. Always present
|
|
101
|
+
* — a missing/non-boolean server value is coerced to `false` (fail-safe to
|
|
102
|
+
* "not verified") by {@link OxyServicesAuthMixin.getCommonsApprovalInfo}.
|
|
103
|
+
*/
|
|
104
|
+
originVerified: boolean;
|
|
97
105
|
/** Server-authoritative expiry (epoch milliseconds). */
|
|
98
106
|
expiresAt: number;
|
|
99
107
|
/** Session lifecycle status. */
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Link Preview (unfurl) Mixin
|
|
3
|
+
*
|
|
4
|
+
* Resolves link previews ("unfurls") through the Oxy API so every Oxy app
|
|
5
|
+
* stops scraping link metadata locally. The API owns resolution and re-hosts
|
|
6
|
+
* the preview `image`/`favicon` on Oxy media (`cloud.oxy.so/<fileId>`), so
|
|
7
|
+
* consumers render the returned URLs directly with no per-app proxy.
|
|
8
|
+
*
|
|
9
|
+
* Wire shapes (`LinkPreview`, `LinkPreviewBatchResponse`) are the single source
|
|
10
|
+
* of truth in `@oxyhq/contracts`; this mixin imports them rather than
|
|
11
|
+
* redefining them so producer (oxy-api) and consumers cannot drift.
|
|
12
|
+
*
|
|
13
|
+
* Caching note: these GET/POST reads are NOT cached at the SDK layer (`cache:
|
|
14
|
+
* false`). A preview can be returned `'pending'` first and `'resolved'` on a
|
|
15
|
+
* later read, so an SDK GET cache would pin the stale `'pending'` snapshot.
|
|
16
|
+
* App-side caching (React Query / stores) owns this responsibility.
|
|
17
|
+
*/
|
|
18
|
+
import type { LinkPreview } from '@oxyhq/contracts';
|
|
19
|
+
import type { OxyServicesBase } from '../OxyServices.base';
|
|
20
|
+
export declare function OxyServicesLinksMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
21
|
+
new (...args: any[]): {
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a single link preview via `GET /links/preview?url=<encoded>&wait=0|1`.
|
|
24
|
+
*
|
|
25
|
+
* @param url - The URL to unfurl. Sent percent-encoded in the query string.
|
|
26
|
+
* @param opts.wait - When `true`, asks the server to resolve synchronously
|
|
27
|
+
* (`wait=1`) instead of returning a `'pending'` placeholder for a
|
|
28
|
+
* first-seen URL. Defaults to `false` (`wait=0`).
|
|
29
|
+
*
|
|
30
|
+
* Not cached at the SDK layer: a `'pending'` result can become `'resolved'`
|
|
31
|
+
* on a later read, so caching here would serve the stale placeholder.
|
|
32
|
+
*/
|
|
33
|
+
getLinkPreview(url: string, opts?: {
|
|
34
|
+
wait?: boolean;
|
|
35
|
+
}): Promise<LinkPreview>;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve multiple link previews via `POST /links/previews` (body `{ urls }`).
|
|
38
|
+
*
|
|
39
|
+
* Inputs are de-duplicated and split into chunks of {@link LINK_PREVIEWS_CHUNK_SIZE}
|
|
40
|
+
* (the server-side cap). Chunks run concurrently and their `data` maps are
|
|
41
|
+
* merged into a single result keyed by the REQUESTED url (the exact string
|
|
42
|
+
* passed in `urls`) — matching the batch contract — so a caller can always
|
|
43
|
+
* look its own input back up.
|
|
44
|
+
*
|
|
45
|
+
* An empty / whitespace-only input resolves immediately with `{}` and
|
|
46
|
+
* performs no network call. A failure in any chunk surfaces (via
|
|
47
|
+
* `handleError`) rather than being swallowed.
|
|
48
|
+
*/
|
|
49
|
+
getLinkPreviews(urls: string[]): Promise<Record<string, LinkPreview>>;
|
|
50
|
+
httpService: import("../HttpService").HttpService;
|
|
51
|
+
cloudURL: string;
|
|
52
|
+
config: import("../OxyServices.base").OxyConfig;
|
|
53
|
+
__resetTokensForTests(): void;
|
|
54
|
+
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
55
|
+
getBaseURL(): string;
|
|
56
|
+
getSessionBaseUrl(): string;
|
|
57
|
+
getClient(): import("../HttpService").HttpService;
|
|
58
|
+
createLinkedClient(config: import("../OxyServices.base").OxyConfig): import("..").LinkedHttpClient;
|
|
59
|
+
getMetrics(): {
|
|
60
|
+
totalRequests: number;
|
|
61
|
+
successfulRequests: number;
|
|
62
|
+
failedRequests: number;
|
|
63
|
+
cacheHits: number;
|
|
64
|
+
cacheMisses: number;
|
|
65
|
+
averageResponseTime: number;
|
|
66
|
+
};
|
|
67
|
+
clearCache(): void;
|
|
68
|
+
clearCacheEntry(key: string): void;
|
|
69
|
+
clearCacheByPrefix(prefix: string): number;
|
|
70
|
+
getCacheStats(): {
|
|
71
|
+
size: number;
|
|
72
|
+
hits: number;
|
|
73
|
+
misses: number;
|
|
74
|
+
hitRate: number;
|
|
75
|
+
};
|
|
76
|
+
getCloudURL(): string;
|
|
77
|
+
setTokens(accessToken: string): void;
|
|
78
|
+
clearTokens(): void;
|
|
79
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
80
|
+
_cachedUserId: string | null | undefined;
|
|
81
|
+
_cachedAccessToken: string | null;
|
|
82
|
+
getCurrentUserId(): string | null;
|
|
83
|
+
hasValidToken(): boolean;
|
|
84
|
+
getAccessToken(): string | null;
|
|
85
|
+
setActingAs(userId: string | null): void;
|
|
86
|
+
getActingAs(): string | null;
|
|
87
|
+
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
88
|
+
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
89
|
+
maxRetries?: number;
|
|
90
|
+
retryDelay?: number;
|
|
91
|
+
authTimeoutMs?: number;
|
|
92
|
+
}): Promise<T_1>;
|
|
93
|
+
validate(): Promise<boolean>;
|
|
94
|
+
handleError(error: unknown): Error;
|
|
95
|
+
healthCheck(): Promise<{
|
|
96
|
+
status: string;
|
|
97
|
+
users?: number;
|
|
98
|
+
timestamp?: string;
|
|
99
|
+
[key: string]: any;
|
|
100
|
+
}>;
|
|
101
|
+
};
|
|
102
|
+
} & T;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-Node Methods Mixin (self-sovereign identity layer — Fase 5 user nodes)
|
|
3
|
+
*
|
|
4
|
+
* The client surface for a user's personal data NODE — the decentralised store
|
|
5
|
+
* that holds an authentic copy of their signed-record chain. Commons drives all
|
|
6
|
+
* of this:
|
|
7
|
+
*
|
|
8
|
+
* - {@link OxyServicesNodesMixin.registerNode} registers (or re-registers) a
|
|
9
|
+
* SELF-HOSTED node. Registration is NOT a bespoke endpoint — it is a signed
|
|
10
|
+
* `type:'node'` v2 record (`collection: 'app.oxy.node'`, `rkey: 'self'`,
|
|
11
|
+
* last-writer-wins) published through the EXISTING `POST /identity/records`
|
|
12
|
+
* path; the server verifies it and materializes the operational
|
|
13
|
+
* {@link UserNodeStatus} cache as a side effect, so the registration's
|
|
14
|
+
* authority is the user's own signature, never an Oxy grant.
|
|
15
|
+
* - {@link OxyServicesNodesMixin.getMyNode} reads the caller's cached node
|
|
16
|
+
* status (`GET /nodes/me`) — the fast, stale-but-instant projection plus the
|
|
17
|
+
* live liveness badge Oxy maintains with background probes.
|
|
18
|
+
* - {@link OxyServicesNodesMixin.removeMyNode} revokes the registration
|
|
19
|
+
* (`DELETE /nodes/me`) so the node leaves the DID document and the liveness
|
|
20
|
+
* sweeps.
|
|
21
|
+
* - {@link OxyServicesNodesMixin.provisionManagedVault} asks Oxy to operate a
|
|
22
|
+
* MANAGED vault on the caller's behalf (`POST /nodes/managed`) — the
|
|
23
|
+
* "Create your vault" convenience for non-technical users (Oxy custodial-signs
|
|
24
|
+
* the node record; `managed:true, controller:'oxy'`).
|
|
25
|
+
* - {@link OxyServicesNodesMixin.notifyNodeIngest} sends an unauthenticated
|
|
26
|
+
* HINT (`POST /nodes/ingest/notify/:userId`) that a user's node has new
|
|
27
|
+
* records; the server fully re-verifies before ingesting, so the hint can
|
|
28
|
+
* never inject data.
|
|
29
|
+
*
|
|
30
|
+
* `registerNode` signs on the caller's per-subject hash chain with the on-device
|
|
31
|
+
* identity key (reusing {@link SignatureService.signRecordV2} — the same
|
|
32
|
+
* `ES256K-DER-SHA256` scheme + {@link signedRecordSigningInput} the identity and
|
|
33
|
+
* civic mixins use), so it is NATIVE-ONLY: it throws on web (where `KeyManager`
|
|
34
|
+
* has no key) and when no user is authenticated. Reading the node status,
|
|
35
|
+
* revoking, provisioning a managed vault, and sending an ingest hint are plain
|
|
36
|
+
* authenticated/public requests with no signing.
|
|
37
|
+
*
|
|
38
|
+
* The wire shapes here are API-INTERNAL (the F5 user-node surface is not yet a
|
|
39
|
+
* published `@oxyhq/contracts` schema), so {@link UserNodeStatus} mirrors the
|
|
40
|
+
* server's `serializeNode` projection exactly. Dates cross the wire as ISO
|
|
41
|
+
* strings.
|
|
42
|
+
*/
|
|
43
|
+
import type { OxyServicesBase } from '../OxyServices.base';
|
|
44
|
+
/** How Oxy and the node move records: the node pulls (default), or Oxy pushes. */
|
|
45
|
+
export type UserNodeMode = 'pull' | 'push';
|
|
46
|
+
/**
|
|
47
|
+
* Who operates the node:
|
|
48
|
+
* - `self` — the user self-hosts the node (registered by their own signed
|
|
49
|
+
* `type:'node'` record).
|
|
50
|
+
* - `oxy` — Oxy operates a MANAGED vault on the user's behalf (custodial-signed
|
|
51
|
+
* `type:'node'` record; the `controller:[OXY_DID]` model).
|
|
52
|
+
*/
|
|
53
|
+
export type UserNodeController = 'self' | 'oxy';
|
|
54
|
+
/**
|
|
55
|
+
* Liveness badge of a node, maintained ONLY by Oxy's background probes:
|
|
56
|
+
* - `active` — the last probe reached the node's liveness manifest.
|
|
57
|
+
* - `unreachable` — the last probe failed (DNS/connect/timeout/non-2xx); the
|
|
58
|
+
* cached row is still served, only the badge changes.
|
|
59
|
+
* - `revoked` — the user removed the registration; excluded from the DID
|
|
60
|
+
* document and from liveness sweeps.
|
|
61
|
+
*/
|
|
62
|
+
export type UserNodeLivenessStatus = 'active' | 'unreachable' | 'revoked';
|
|
63
|
+
/**
|
|
64
|
+
* The caller's registered node, as projected by the server's `serializeNode`
|
|
65
|
+
* (`GET /nodes/me`, `POST /nodes/managed`). A denormalised, fast-to-read copy of
|
|
66
|
+
* the authoritative signed `type:'node'` record plus the live liveness state Oxy
|
|
67
|
+
* maintains in the background.
|
|
68
|
+
*
|
|
69
|
+
* `mode` / `managed` / `controller` / `status` are always present (server fields
|
|
70
|
+
* with defaults); the probe/sync fields and `nodeDid` are present only once set.
|
|
71
|
+
* The `Date` fields cross the wire as ISO-8601 strings.
|
|
72
|
+
*/
|
|
73
|
+
export interface UserNodeStatus {
|
|
74
|
+
/** Optional DID the node advertises for itself (informational). */
|
|
75
|
+
nodeDid?: string;
|
|
76
|
+
/** The node's public HTTPS base URL (where its liveness manifest lives). */
|
|
77
|
+
endpoint: string;
|
|
78
|
+
/** The node's secp256k1 public key (hex) — records it signs verify against this. */
|
|
79
|
+
nodePublicKey: string;
|
|
80
|
+
/** Transport direction. `pull` (the node paces its own sync) by default. */
|
|
81
|
+
mode: UserNodeMode;
|
|
82
|
+
/** Whether Oxy operates this node on the user's behalf (managed vault). */
|
|
83
|
+
managed: boolean;
|
|
84
|
+
/** Operator of the node — `self` (user self-hosts) or `oxy` (managed vault). */
|
|
85
|
+
controller: UserNodeController;
|
|
86
|
+
/** Liveness badge — maintained only by background probes, never a read handler. */
|
|
87
|
+
status: UserNodeLivenessStatus;
|
|
88
|
+
/** Last time a probe reached the node successfully (ISO-8601). */
|
|
89
|
+
lastSeenAt?: string;
|
|
90
|
+
/** Last time a probe ran, success or failure (ISO-8601). */
|
|
91
|
+
lastProbeAt?: string;
|
|
92
|
+
/** Human-readable reason the last probe OR ingest failed (cleared on success). */
|
|
93
|
+
lastError?: string;
|
|
94
|
+
/** Last synced chain `seq` for two-way sync (advanced only by the ingest worker). */
|
|
95
|
+
cursor?: number;
|
|
96
|
+
/** Last time the ingest worker ran a pull for this node (ISO-8601). */
|
|
97
|
+
lastSyncedAt?: string;
|
|
98
|
+
/** When the node was first registered (ISO-8601). */
|
|
99
|
+
createdAt: string;
|
|
100
|
+
/** When the node row was last updated (ISO-8601). */
|
|
101
|
+
updatedAt: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Input for {@link OxyServicesNodesMixin.registerNode} — the operational facts of
|
|
105
|
+
* the user's self-hosted node that go into the signed `type:'node'` record.
|
|
106
|
+
*/
|
|
107
|
+
export interface RegisterNodeInput {
|
|
108
|
+
/** The node's public HTTPS base URL (where its liveness manifest is served). */
|
|
109
|
+
endpoint: string;
|
|
110
|
+
/** The node's secp256k1 public key (hex) — records the node signs verify against this. */
|
|
111
|
+
nodePublicKey: string;
|
|
112
|
+
/** Transport direction; defaults to `'pull'` when omitted. */
|
|
113
|
+
mode?: UserNodeMode;
|
|
114
|
+
}
|
|
115
|
+
/** Result of {@link OxyServicesNodesMixin.removeMyNode} (`DELETE /nodes/me`). */
|
|
116
|
+
export interface RemoveNodeResult {
|
|
117
|
+
/** `true` when an active registration was flipped to `revoked`. */
|
|
118
|
+
revoked: boolean;
|
|
119
|
+
}
|
|
120
|
+
export declare function OxyServicesNodesMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
121
|
+
new (...args: any[]): {
|
|
122
|
+
/**
|
|
123
|
+
* Register (or re-register) the caller's SELF-HOSTED personal data node.
|
|
124
|
+
*
|
|
125
|
+
* Builds the `{ endpoint, nodePublicKey, mode }` node record, signs a v2
|
|
126
|
+
* envelope on the caller's own per-subject hash chain (fetching the current
|
|
127
|
+
* chain head first so `seq`/`prev` are never stale), and publishes it through
|
|
128
|
+
* the EXISTING `POST /identity/records` path — which verifies the signature
|
|
129
|
+
* and materializes the operational node cache as a side effect. The signed
|
|
130
|
+
* record (not this call) is the authority; re-registering over-writes the
|
|
131
|
+
* single `self` record (last-writer-wins).
|
|
132
|
+
*
|
|
133
|
+
* NATIVE-ONLY: signs with the on-device identity key (throws on web / when no
|
|
134
|
+
* identity or no authenticated user — the guard fires before any network).
|
|
135
|
+
* `mode` defaults to `'pull'`. After a successful publish the node + `/users/me`
|
|
136
|
+
* GET caches are swept, then the freshly-materialized status is returned.
|
|
137
|
+
*
|
|
138
|
+
* Throws if the chain record stored but the server skipped materialization
|
|
139
|
+
* (e.g. a malformed endpoint the server rejected) — an unexpected state rather
|
|
140
|
+
* than a silent `null`.
|
|
141
|
+
*
|
|
142
|
+
* @param input - The node's endpoint, public key, and optional transport mode.
|
|
143
|
+
*/
|
|
144
|
+
registerNode(input: RegisterNodeInput): Promise<UserNodeStatus>;
|
|
145
|
+
/**
|
|
146
|
+
* Read the caller's registered node status (`GET /nodes/me`), or `null` when
|
|
147
|
+
* the caller has no node. Auth required; short-TTL cached (the liveness badge
|
|
148
|
+
* is background-maintained) and swept after the caller's own
|
|
149
|
+
* register / revoke / managed-provision.
|
|
150
|
+
*/
|
|
151
|
+
getMyNode(): Promise<UserNodeStatus | null>;
|
|
152
|
+
/**
|
|
153
|
+
* Revoke the caller's node registration (`DELETE /nodes/me`). The node flips
|
|
154
|
+
* to `revoked` server-side (leaving the DID document and liveness sweeps).
|
|
155
|
+
* Auth required; the node + `/users/me` GET caches are swept on success.
|
|
156
|
+
*
|
|
157
|
+
* Maps the server's `{ success }` to the SDK's `{ revoked }` semantic.
|
|
158
|
+
*/
|
|
159
|
+
removeMyNode(): Promise<RemoveNodeResult>;
|
|
160
|
+
/**
|
|
161
|
+
* Provision (or refresh) an Oxy-operated MANAGED vault for the caller
|
|
162
|
+
* (`POST /nodes/managed`) — the "Create your vault" convenience for
|
|
163
|
+
* non-technical users. Oxy custodial-signs the node registration onto the
|
|
164
|
+
* caller's chain and returns the materialized node (`managed:true,
|
|
165
|
+
* controller:'oxy'`). Idempotent server-side. Auth required; the owner id is
|
|
166
|
+
* resolved from the session, never the body. The node + `/users/me` GET caches
|
|
167
|
+
* are swept on success.
|
|
168
|
+
*/
|
|
169
|
+
provisionManagedVault(): Promise<UserNodeStatus>;
|
|
170
|
+
/**
|
|
171
|
+
* Send an ingest HINT that a user's node has new records
|
|
172
|
+
* (`POST /nodes/ingest/notify/:userId`). Unauthenticated by design and
|
|
173
|
+
* fire-and-forget on the server (it only schedules a background re-pull of the
|
|
174
|
+
* named user's OWN node, then fully re-verifies — a notify can never inject
|
|
175
|
+
* data), so this resolves once the 202 hint is accepted and returns nothing.
|
|
176
|
+
*
|
|
177
|
+
* @param userId - The user whose node may have new records. URL-encoded.
|
|
178
|
+
*/
|
|
179
|
+
notifyNodeIngest(userId: string): Promise<void>;
|
|
180
|
+
/**
|
|
181
|
+
* Sweep the GET caches a node mutation invalidates: every node read
|
|
182
|
+
* (`GET:/nodes/`) so a re-read reflects the new node / its absence, and
|
|
183
|
+
* `/users/me` because the user's derived DID document embeds an `#oxy-node`
|
|
184
|
+
* service entry that changes on register / revoke / manage. Public rather
|
|
185
|
+
* than `private` because mixins compose into an exported anonymous class
|
|
186
|
+
* where TypeScript cannot represent a private member in the emitted
|
|
187
|
+
* declaration file (TS4094) — mirrors the civic / identity cache sweepers.
|
|
188
|
+
*/
|
|
189
|
+
_sweepNodeCaches(): void;
|
|
190
|
+
httpService: import("../HttpService").HttpService;
|
|
191
|
+
cloudURL: string;
|
|
192
|
+
config: import("../OxyServices.base").OxyConfig;
|
|
193
|
+
__resetTokensForTests(): void;
|
|
194
|
+
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
195
|
+
getBaseURL(): string;
|
|
196
|
+
getSessionBaseUrl(): string;
|
|
197
|
+
getClient(): import("../HttpService").HttpService;
|
|
198
|
+
createLinkedClient(config: import("../OxyServices.base").OxyConfig): import("..").LinkedHttpClient;
|
|
199
|
+
getMetrics(): {
|
|
200
|
+
totalRequests: number;
|
|
201
|
+
successfulRequests: number;
|
|
202
|
+
failedRequests: number;
|
|
203
|
+
cacheHits: number;
|
|
204
|
+
cacheMisses: number;
|
|
205
|
+
averageResponseTime: number;
|
|
206
|
+
};
|
|
207
|
+
clearCache(): void;
|
|
208
|
+
clearCacheEntry(key: string): void;
|
|
209
|
+
clearCacheByPrefix(prefix: string): number;
|
|
210
|
+
getCacheStats(): {
|
|
211
|
+
size: number;
|
|
212
|
+
hits: number;
|
|
213
|
+
misses: number;
|
|
214
|
+
hitRate: number;
|
|
215
|
+
};
|
|
216
|
+
getCloudURL(): string;
|
|
217
|
+
setTokens(accessToken: string): void;
|
|
218
|
+
clearTokens(): void;
|
|
219
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
220
|
+
_cachedUserId: string | null | undefined;
|
|
221
|
+
_cachedAccessToken: string | null;
|
|
222
|
+
getCurrentUserId(): string | null;
|
|
223
|
+
hasValidToken(): boolean;
|
|
224
|
+
getAccessToken(): string | null;
|
|
225
|
+
setActingAs(userId: string | null): void;
|
|
226
|
+
getActingAs(): string | null;
|
|
227
|
+
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
228
|
+
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
229
|
+
maxRetries?: number;
|
|
230
|
+
retryDelay?: number;
|
|
231
|
+
authTimeoutMs?: number;
|
|
232
|
+
}): Promise<T_1>;
|
|
233
|
+
validate(): Promise<boolean>;
|
|
234
|
+
handleError(error: unknown): Error;
|
|
235
|
+
healthCheck(): Promise<{
|
|
236
|
+
status: string;
|
|
237
|
+
users?: number;
|
|
238
|
+
timestamp?: string;
|
|
239
|
+
[key: string]: any;
|
|
240
|
+
}>;
|
|
241
|
+
};
|
|
242
|
+
} & T;
|
|
@@ -30,6 +30,8 @@ import { OxyServicesManagedAccountsMixin } from './OxyServices.managedAccounts';
|
|
|
30
30
|
import { OxyServicesContactsMixin } from './OxyServices.contacts';
|
|
31
31
|
import { OxyServicesAppDataMixin } from './OxyServices.appData';
|
|
32
32
|
import { OxyServicesCivicMixin } from './OxyServices.civic';
|
|
33
|
+
import { OxyServicesNodesMixin } from './OxyServices.nodes';
|
|
34
|
+
import { OxyServicesLinksMixin } from './OxyServices.links';
|
|
33
35
|
/**
|
|
34
36
|
* Instance shape of every mixin in the pipeline, intersected. The runtime
|
|
35
37
|
* `composeOxyServices()` produces a class whose instances expose all of
|
|
@@ -39,7 +41,7 @@ import { OxyServicesCivicMixin } from './OxyServices.civic';
|
|
|
39
41
|
* If you add a new mixin to `MIXIN_PIPELINE`, add it here too so its methods
|
|
40
42
|
* are visible without a cast.
|
|
41
43
|
*/
|
|
42
|
-
type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFedCMMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSilentAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesRedirectAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSsoMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesReputationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesApplicationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesWorkspacesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesManagedAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesCivicMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
|
|
44
|
+
type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFedCMMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSilentAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesRedirectAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSsoMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesReputationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesApplicationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesWorkspacesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesManagedAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesCivicMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNodesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLinksMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
|
|
43
45
|
/**
|
|
44
46
|
* Constructor type for the fully composed mixin pipeline. Each mixin returns
|
|
45
47
|
* a new constructor that augments its input; reducing across the pipeline
|
|
@@ -79,6 +79,21 @@ export declare function ssoNoSessionKey(origin: string): string;
|
|
|
79
79
|
* centrally) can probe again.
|
|
80
80
|
*/
|
|
81
81
|
export declare function ssoAttemptedKey(origin: string): string;
|
|
82
|
+
/**
|
|
83
|
+
* Per-origin DURABLE "this device/origin has had a signed-in Oxy session
|
|
84
|
+
* before" hint.
|
|
85
|
+
*
|
|
86
|
+
* Unlike every other key in this module — which lives in per-tab
|
|
87
|
+
* `sessionStorage` — this hint is written to DURABLE storage (web
|
|
88
|
+
* `localStorage`; the services provider uses its own `storageKeyPrefix`-scoped
|
|
89
|
+
* key in `@oxyhq/services`). It is set whenever a session is established or
|
|
90
|
+
* restored and survives a session expiring; it is cleared ONLY on an explicit
|
|
91
|
+
* full sign-out. It exists purely to drive {@link allowSsoBounce}: a returning
|
|
92
|
+
* visitor (hint present) whose local session has lapsed still gets ONE terminal
|
|
93
|
+
* `/sso` establish bounce to recover a session that lives only at the central
|
|
94
|
+
* IdP, while a truly first-time anonymous visitor is never force-bounced.
|
|
95
|
+
*/
|
|
96
|
+
export declare function ssoPriorSessionKey(origin: string): string;
|
|
82
97
|
/**
|
|
83
98
|
* Per-origin marker written by the pre-hydration callback bootstrap.
|
|
84
99
|
*
|
|
@@ -159,3 +174,49 @@ export declare function isCentralIdPOrigin(origin: string): boolean;
|
|
|
159
174
|
* `Date.now()`.
|
|
160
175
|
*/
|
|
161
176
|
export declare function guardActive(storage: Pick<Storage, 'getItem'>, origin: string, now?: number): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Inputs to the smart {@link allowSsoBounce} gate.
|
|
179
|
+
*/
|
|
180
|
+
export interface SsoBounceGate {
|
|
181
|
+
/**
|
|
182
|
+
* Whether this device/origin has had a signed-in Oxy session before (the
|
|
183
|
+
* durable {@link ssoPriorSessionKey} hint). Set whenever a session is
|
|
184
|
+
* established or restored; survives session expiry; cleared only on explicit
|
|
185
|
+
* full sign-out. `true` ⇒ a returning visitor.
|
|
186
|
+
*/
|
|
187
|
+
readonly hasPriorSession: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Whether a local/stored session was recovered earlier this cold boot. At the
|
|
190
|
+
* terminal bounce gate this is effectively always `false` (an earlier step
|
|
191
|
+
* would have won and short-circuited), but it is part of the contract — "no
|
|
192
|
+
* prior hint AND no local session" — so it is passed explicitly for fidelity
|
|
193
|
+
* and robustness.
|
|
194
|
+
*/
|
|
195
|
+
readonly hasLocalSession: boolean;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Decide whether the terminal `/sso` establish-bounce is ALLOWED for this
|
|
199
|
+
* visitor (the smart `enabled` gate for the `sso-bounce` cold-boot step).
|
|
200
|
+
*
|
|
201
|
+
* The terminal bounce is the ONLY cold-boot step that can recover a session
|
|
202
|
+
* that lives SOLELY at the central IdP — the cross-apex Relying-Party case
|
|
203
|
+
* (e.g. `mention.earth`, a different apex from `oxy.so`) whose device-local
|
|
204
|
+
* session has expired and whose `Domain=oxy.so` refresh cookie never reaches
|
|
205
|
+
* `api.<apex>`. It is also what plants the first-party per-apex `fedcm_session`
|
|
206
|
+
* cookie that the EARLIER `silent-iframe` step later relies on. So it must fire
|
|
207
|
+
* for a RETURNING user, yet it must NOT force a truly first-time anonymous
|
|
208
|
+
* visitor off to the IdP.
|
|
209
|
+
*
|
|
210
|
+
* - ALLOW when there is a prior-signed-in hint OR a local session was
|
|
211
|
+
* recovered this boot (a returning user) — so a central-only cross-domain
|
|
212
|
+
* session recovers via ONE bounce, after which the per-apex cookie is
|
|
213
|
+
* planted and subsequent loads restore silently with no bounce.
|
|
214
|
+
* - else (no hint, no local session) SUPPRESS — a first-time anonymous
|
|
215
|
+
* visitor browses without a forced redirect.
|
|
216
|
+
*
|
|
217
|
+
* This is the smart DEFAULT and the ONLY behaviour: apps never configure it.
|
|
218
|
+
* It is also the GATE DECISION ONLY — callers still apply the per-tab loop
|
|
219
|
+
* guards (`ssoAttemptedKey`, `ssoNoSessionKey`, {@link guardActive}) so an
|
|
220
|
+
* allowed bounce still fires at most once per cold boot.
|
|
221
|
+
*/
|
|
222
|
+
export declare function allowSsoBounce(gate: SsoBounceGate): boolean;
|
package/package.json
CHANGED