@atlasauth/js 0.6.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # @atlasauth/js changelog
2
2
 
3
+ ## 0.8.0
4
+ - **New: `SignOutGuard`** — the safer sign-out every framework provider now drives, so they all behave like
5
+ `@atlasauth/react`: `new SignOutGuard({ request, frontendApi?, store?, tombstones?, retryDelaysMs?,
6
+ frontChannelLogout? })` (`SignOutGuardOptions`). Methods: `revoke(sessionId, { frontchannel?, request? })` →
7
+ `SignOutAttempt` (`{ sessionId, outcome }`; finds the cookie session with `GET /v1/client` when the id is unknown,
8
+ tombstones it before revoking, revokes with one retry, runs front-channel logout, and re-tries a failed revoke in
9
+ the background after 15 s, 1 min and 5 min); `settle(attempt, { strict? })` → `SignOutResult` (throws
10
+ `SignOutError` under `strict`); `promoted(attempt)` → the server-promoted session id to adopt, or null;
11
+ `refuse(sessionId)` → whether a booted session must not be adopted (re-tries its revoke in the background and
12
+ removes the tombstone once it lands); `isSignedOut(id)`; `signal(attempt)` / `learn(message)` for the cross-tab
13
+ `{ signedOut: string[] }` message; `watch(current, onSignedOut)` (another tab signed out of this session);
14
+ `ready`; `dispose()`; `tombstones`. **Added types** `FapiRequest`, `SignOutGuardOptions`, `SignOutAttempt`,
15
+ `SignedOutSignal`.
16
+ - **Tombstones on platforms without `localStorage`.** `SessionTombstonesOptions.store?: TombstoneStore` (new type:
17
+ `{ getItem(key), setItem(key, value) }`, sync or async) keeps the tombstones in the platform's own store
18
+ (AsyncStorage, `expo-secure-store`, `chrome.storage`, Capacitor Preferences…). **Added**
19
+ `SessionTombstones.ready: Promise<void>` (settles once the store has been read; immediate for `localStorage`).
20
+ - **Email delivery.** `InstanceSettings.emailDelivery: EmailDelivery` (new type: `{ available, passwordReset,
21
+ passwordSignUp }`, read from `email_delivery` on `GET /v1/appearance`; all `true` on a server that does not send
22
+ it). **New helpers:** `emailDeliveryFrom(body)` (an environment / appearance body or the object itself),
23
+ `offeredFirstFactors(strategies, delivery)` (drops `email_code` / `email_link` while email can't be delivered),
24
+ and the constants `EMAIL_DELIVERY_UNKNOWN`, `EMAIL_UNAVAILABLE_MESSAGE`, `EMAIL_FIRST_FACTORS`.
25
+ - **New: typed client environment.** `fetchClientEnvironment(client)` → `ClientEnvironment | null` and
26
+ `clientEnvironmentFrom(body)` for `GET /v1/client/environment`: `{ firstFactors, strategies, emailDelivery,
27
+ socialProviders }`, with the new `EnvironmentSocialProvider` type (`provider`, `native`, `mode`, `label`,
28
+ `display_name`, `icon`, `allow_sign_in`, `allow_sign_up`, `client_id?`, `bot_username?`).
29
+
30
+ ## 0.7.0
31
+ - **Popup OAuth completes when the opener page sends `Cross-Origin-Opener-Policy: same-origin`.** That
32
+ header puts the provider popup in another browsing-context group: `popup.closed` reads `true` at once and
33
+ the popup has no `window.opener`, so the result could never be posted back and the flow settled as a
34
+ cancel while the popup stayed open. Now the popup lands back on your page with the result in the URL
35
+ fragment (`#atlas_popup_result=…`, never the query string), the SDK there hands it to the opener over
36
+ `BroadcastChannel('atlas:oauth:<nonce>')` (a `localStorage` `storage` event where BroadcastChannel is
37
+ missing) and closes the popup.
38
+ - **Changed `startOauthPopup` cancel semantics.** `popup.closed` turning true within 1.5 s of opening means
39
+ the opener link was severed, not that the user closed it: the flow keeps waiting, probing `GET /v1/client`
40
+ with back-off, for up to 5 minutes, and settles as `popup_closed` only on that timeout, an explicit error
41
+ result, `signal` aborting, or a newer popup flow starting. A close after 1.5 s is a cancel after one final
42
+ session probe (as before).
43
+ - **Changed `startOauthPopup` request.** It now sends `popup_nonce` and `opener_origin`
44
+ (`window.location.origin`) with `mode: 'popup'`, and opens the popup with the window name `atlas:oauth`.
45
+ - **Added** to `startOauthPopup`: `input.signal?: AbortSignal`, `handlers.onSevered?: () => void`,
46
+ `PopupStartResult.severed?: boolean`; new `OAuthPopupInput` type; `POPUP_SEVERED_WINDOW_MS` (1500) and
47
+ `POPUP_SEVERED_TIMEOUT_MS` (300000); `mapOAuthPopupPayload(data)` (the payload half of
48
+ `parseOAuthPopupMessage`).
49
+ - **Added popup channel helpers:** `completeOAuthPopupLanding({ win?, ackTimeoutMs? })` →
50
+ `PopupLanding | null` (`{ payload, delivered }`; reads and scrubs the fragment, relays the result and
51
+ closes once the opener acknowledges — call it on startup), `closeOAuthPopupAfterSignIn({ win?, ackTimeoutMs? })`
52
+ (a page that finished a sign-in inside the Atlas popup closes itself when an opener is waiting),
53
+ `listenForPopupResult(nonce, { onResult, onSignedIn? })`, `readPopupLanding(hash)`, `popupChannelName(nonce)`,
54
+ `createPopupNonce()`, `PopupRelayMessage`, and the constants `OAUTH_POPUP_WINDOW_NAME`
55
+ (`'atlas:oauth'`), `POPUP_RESULT_FRAGMENT` (`'atlas_popup_result'`), `POPUP_SIGNAL_CHANNEL`.
56
+ - **Sign-out tombstones are shared by every tab.** `SessionTombstones` moved from `sessionStorage` to
57
+ `localStorage`, so after a failed revoke a newly opened tab no longer re-adopts the session. Entries are
58
+ `{ id, at }`, at most 20, kept 30 days, pruned on every read, and keyed per Frontend API origin
59
+ (`atlas:signed-out-sessions:<origin>`). `sessionStorage` is used only when `localStorage` throws; tombstones
60
+ an earlier version kept in `sessionStorage` are carried over.
61
+ - **Changed** `new SessionTombstones(options?)`: accepts `{ frontendApi?, storageKey?, now? }`
62
+ (`SessionTombstonesOptions`); a bare storage-key string is still accepted. **Added** `remove(...ids)` (forget
63
+ a tombstone once a later revoke is confirmed) and `subscribe(listener)` (another tab's changes, via the
64
+ `storage` event). `has()`/`list()` re-read storage, so other tabs' changes are seen.
65
+
3
66
  ## 0.6.0
4
67
  - **Per-flow captcha widget rendering**, shared by the prebuilt sign-in / sign-up UIs:
5
68
  `renderCaptchaWidget(flow, container)` → `{ token: Promise<string>, remove() }`,
@@ -0,0 +1,79 @@
1
+ import type { FapiClient } from './fapi';
2
+ /**
3
+ * The client environment (`GET /v1/client/environment`) and the email-delivery
4
+ * facts both it and `GET /v1/appearance` publish.
5
+ *
6
+ * An app that cannot deliver email (a production instance with no email
7
+ * provider) cannot send a reset code, an email sign-in code or link, or a
8
+ * sign-up verification code. The server already stops offering email code / link
9
+ * and refuses those requests; `email_delivery` lets a sign-in UI say so up front
10
+ * instead: hide "Forgot password?" and email-code options, and show a "can't
11
+ * send email" state where a sign-up would need one.
12
+ */
13
+ /** `email_delivery`: what works given whether the app can deliver email. */
14
+ export interface EmailDelivery {
15
+ /** The app can send email (its own provider, or the shared development sender). */
16
+ available: boolean;
17
+ /** "Forgot password?" can work: password sign-in is on and reset emails can be delivered. */
18
+ passwordReset: boolean;
19
+ /** Email + password sign-up can complete (email can be delivered, or verification is not required). */
20
+ passwordSignUp: boolean;
21
+ }
22
+ /**
23
+ * What an older server (no `email_delivery`) is taken to mean: everything works.
24
+ * The server still refuses whatever it cannot deliver.
25
+ */
26
+ export declare const EMAIL_DELIVERY_UNKNOWN: Readonly<EmailDelivery>;
27
+ /** The default English copy for the "can't send email" state. */
28
+ export declare const EMAIL_UNAVAILABLE_MESSAGE = "This app can't send email right now. Try another sign-in method or contact the app owner.";
29
+ /** The first factors that need the app to deliver email. */
30
+ export declare const EMAIL_FIRST_FACTORS: readonly string[];
31
+ /**
32
+ * Read `email_delivery` from a `/v1/client/environment` or `/v1/appearance`
33
+ * body — or from the `email_delivery` object itself. A missing object or field
34
+ * reads as available (see {@link EMAIL_DELIVERY_UNKNOWN}).
35
+ */
36
+ export declare function emailDeliveryFrom(body: unknown): EmailDelivery;
37
+ /**
38
+ * The first factors a sign-in UI should offer: `strategies` without email code /
39
+ * link while the app cannot deliver email. A capability of the app, not of the
40
+ * user, so it never narrows the server's uniform list per identifier. With no
41
+ * `delivery` (not loaded yet) the list is returned unchanged.
42
+ */
43
+ export declare function offeredFirstFactors(strategies: readonly string[], delivery: EmailDelivery | null | undefined): string[];
44
+ /** One entry of the environment's `social_providers`. */
45
+ export interface EnvironmentSocialProvider {
46
+ provider: string;
47
+ /** Whether the provider signs in natively (One Tap etc.) on this instance. */
48
+ native: boolean;
49
+ mode: 'native' | 'redirect';
50
+ /** The full button text, or null for the SDK's own label. */
51
+ label: string | null;
52
+ display_name: string | null;
53
+ icon: {
54
+ type: 'url' | 'svg';
55
+ value: string;
56
+ } | null;
57
+ allow_sign_in: boolean;
58
+ allow_sign_up: boolean;
59
+ /** Public client id, for native providers only. */
60
+ client_id?: string;
61
+ /** Telegram's bot username. */
62
+ bot_username?: string;
63
+ }
64
+ /** `GET /v1/client/environment`, typed. */
65
+ export interface ClientEnvironment {
66
+ /** The enabled first factors (`password`, `email_code`, `passkey`, …). */
67
+ firstFactors: string[];
68
+ /**
69
+ * The offered strategies, camelCase as the server sends them (`password`,
70
+ * `emailCode`, `emailLink`, `passkey`, `phoneCode`, …, `oauthProviders`).
71
+ */
72
+ strategies: Record<string, unknown>;
73
+ emailDelivery: EmailDelivery;
74
+ socialProviders: EnvironmentSocialProvider[];
75
+ }
76
+ /** Read a `/v1/client/environment` body. Defensive: malformed parts read as empty. */
77
+ export declare function clientEnvironmentFrom(body: unknown): ClientEnvironment;
78
+ /** Fetch the client environment. Null when the server can't be reached or errors. */
79
+ export declare function fetchClientEnvironment(client: FapiClient): Promise<ClientEnvironment | null>;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EMAIL_FIRST_FACTORS = exports.EMAIL_UNAVAILABLE_MESSAGE = exports.EMAIL_DELIVERY_UNKNOWN = void 0;
4
+ exports.emailDeliveryFrom = emailDeliveryFrom;
5
+ exports.offeredFirstFactors = offeredFirstFactors;
6
+ exports.clientEnvironmentFrom = clientEnvironmentFrom;
7
+ exports.fetchClientEnvironment = fetchClientEnvironment;
8
+ /**
9
+ * What an older server (no `email_delivery`) is taken to mean: everything works.
10
+ * The server still refuses whatever it cannot deliver.
11
+ */
12
+ exports.EMAIL_DELIVERY_UNKNOWN = Object.freeze({
13
+ available: true,
14
+ passwordReset: true,
15
+ passwordSignUp: true,
16
+ });
17
+ /** The default English copy for the "can't send email" state. */
18
+ exports.EMAIL_UNAVAILABLE_MESSAGE = "This app can't send email right now. Try another sign-in method or contact the app owner.";
19
+ /** The first factors that need the app to deliver email. */
20
+ exports.EMAIL_FIRST_FACTORS = ['email_code', 'email_link'];
21
+ const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
22
+ /**
23
+ * Read `email_delivery` from a `/v1/client/environment` or `/v1/appearance`
24
+ * body — or from the `email_delivery` object itself. A missing object or field
25
+ * reads as available (see {@link EMAIL_DELIVERY_UNKNOWN}).
26
+ */
27
+ function emailDeliveryFrom(body) {
28
+ const raw = isRecord(body) && isRecord(body.email_delivery) ? body.email_delivery : body;
29
+ if (!isRecord(raw) || !('available' in raw || 'password_reset' in raw || 'password_sign_up' in raw)) {
30
+ return { ...exports.EMAIL_DELIVERY_UNKNOWN };
31
+ }
32
+ const flag = (v) => (typeof v === 'boolean' ? v : true);
33
+ return {
34
+ available: flag(raw.available),
35
+ passwordReset: flag(raw.password_reset),
36
+ passwordSignUp: flag(raw.password_sign_up),
37
+ };
38
+ }
39
+ /**
40
+ * The first factors a sign-in UI should offer: `strategies` without email code /
41
+ * link while the app cannot deliver email. A capability of the app, not of the
42
+ * user, so it never narrows the server's uniform list per identifier. With no
43
+ * `delivery` (not loaded yet) the list is returned unchanged.
44
+ */
45
+ function offeredFirstFactors(strategies, delivery) {
46
+ if (!delivery || delivery.available)
47
+ return [...strategies];
48
+ return strategies.filter((s) => !exports.EMAIL_FIRST_FACTORS.includes(s));
49
+ }
50
+ /** Read a `/v1/client/environment` body. Defensive: malformed parts read as empty. */
51
+ function clientEnvironmentFrom(body) {
52
+ const root = isRecord(body) ? body : {};
53
+ return {
54
+ firstFactors: Array.isArray(root.first_factors)
55
+ ? root.first_factors.filter((f) => typeof f === 'string')
56
+ : [],
57
+ strategies: isRecord(root.strategies) ? { ...root.strategies } : {},
58
+ emailDelivery: emailDeliveryFrom(root),
59
+ socialProviders: Array.isArray(root.social_providers)
60
+ ? root.social_providers.filter((p) => isRecord(p) && typeof p.provider === 'string')
61
+ : [],
62
+ };
63
+ }
64
+ /** Fetch the client environment. Null when the server can't be reached or errors. */
65
+ async function fetchClientEnvironment(client) {
66
+ try {
67
+ const response = await client.get('/v1/client/environment');
68
+ return response.ok ? clientEnvironmentFrom(response.data) : null;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ //# sourceMappingURL=environment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.js","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"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"}
@@ -26,7 +26,7 @@ export interface SignOutOptions {
26
26
  * Reject with a `SignOutError` when the Frontend API could not revoke the
27
27
  * session (after one retry). The local sign-out still happens either way.
28
28
  * Default `false`: `signOut()` resolves and reports `revoked: false` instead.
29
- * Honoured by `@atlasauth/react`.
29
+ * Honoured by every framework provider.
30
30
  */
31
31
  strict?: boolean;
32
32
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export * from './config';
2
2
  export * from './sign-out';
3
+ export * from './sign-out-guard';
3
4
  export * from './token-cache';
4
5
  export * from './tab-election';
5
6
  export * from './attempt';
6
7
  export * from './redirect';
8
+ export * from './popup-channel';
7
9
  export * from './fapi';
8
10
  export * from './native';
9
11
  export * from './native-session';
@@ -21,4 +23,5 @@ export * from './captcha';
21
23
  export * from './captcha-widget';
22
24
  export * from './captcha-controller';
23
25
  export * from './instance-settings';
26
+ export * from './environment';
24
27
  export { type AuthzClaims, type ProtectCondition, type ProtectOutcome, type ProtectReason, evaluate, hasFromClaims, protectFromClaims, ForbiddenError, ROLE_KEY_PATTERN, PERMISSION_KEY_PATTERN, isRoleKey, isPermissionKey, } from '@atlasauth/authz';
package/dist/index.js CHANGED
@@ -18,10 +18,14 @@ exports.isPermissionKey = exports.isRoleKey = exports.PERMISSION_KEY_PATTERN = e
18
18
  // Provider configuration checks (frontendApi) shared by every framework SDK.
19
19
  __exportStar(require("./config"), exports);
20
20
  __exportStar(require("./sign-out"), exports);
21
+ // The safer sign-out every framework provider drives (tombstones, background revoke).
22
+ __exportStar(require("./sign-out-guard"), exports);
21
23
  __exportStar(require("./token-cache"), exports);
22
24
  __exportStar(require("./tab-election"), exports);
23
25
  __exportStar(require("./attempt"), exports);
24
26
  __exportStar(require("./redirect"), exports);
27
+ // §oauth popup mode: the same-origin hand-back for a popup whose opener was severed.
28
+ __exportStar(require("./popup-channel"), exports);
25
29
  __exportStar(require("./fapi"), exports);
26
30
  __exportStar(require("./native"), exports);
27
31
  __exportStar(require("./native-session"), exports);
@@ -47,6 +51,8 @@ __exportStar(require("./captcha-widget"), exports);
47
51
  // sign-in / sign-up settings (`/v1/appearance`) they are driven by.
48
52
  __exportStar(require("./captcha-controller"), exports);
49
53
  __exportStar(require("./instance-settings"), exports);
54
+ // `GET /v1/client/environment`, typed, and the `email_delivery` facts.
55
+ __exportStar(require("./environment"), exports);
50
56
  // Framework-agnostic authorization primitive — `has()`/`evaluate` for any
51
57
  // non-React/Next consumer reading org_role/org_permissions off its session.
52
58
  var authz_1 = require("@atlasauth/authz");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6EAA6E;AAC7E,2CAAyB;AACzB,6CAA2B;AAC3B,gDAA8B;AAC9B,iDAA+B;AAC/B,4CAA0B;AAC1B,6CAA2B;AAC3B,yCAAuB;AACvB,2CAAyB;AACzB,mDAAiC;AACjC,4CAA0B;AAC1B,6CAA2B;AAC3B,yCAAuB;AACvB,8CAA4B;AAC5B,2CAAyB;AACzB,4DAA4D;AAC5D,6CAA2B;AAC3B,4CAA0B;AAC1B,mDAAiC;AACjC,kDAAgC;AAChC,sDAAsD;AACtD,wDAAsC;AACtC,+EAA+E;AAC/E,2EAA2E;AAC3E,4CAA0B;AAC1B,kFAAkF;AAClF,mDAAiC;AACjC,kFAAkF;AAClF,yEAAyE;AACzE,oEAAoE;AACpE,uDAAqC;AACrC,sDAAoC;AACpC,0EAA0E;AAC1E,4EAA4E;AAC5E,0CAa0B;AARxB,iGAAA,QAAQ,OAAA;AACR,sGAAA,aAAa,OAAA;AACb,0GAAA,iBAAiB,OAAA;AACjB,uGAAA,cAAc,OAAA;AACd,yGAAA,gBAAgB,OAAA;AAChB,+GAAA,sBAAsB,OAAA;AACtB,kGAAA,SAAS,OAAA;AACT,wGAAA,eAAe,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6EAA6E;AAC7E,2CAAyB;AACzB,6CAA2B;AAC3B,sFAAsF;AACtF,mDAAiC;AACjC,gDAA8B;AAC9B,iDAA+B;AAC/B,4CAA0B;AAC1B,6CAA2B;AAC3B,qFAAqF;AACrF,kDAAgC;AAChC,yCAAuB;AACvB,2CAAyB;AACzB,mDAAiC;AACjC,4CAA0B;AAC1B,6CAA2B;AAC3B,yCAAuB;AACvB,8CAA4B;AAC5B,2CAAyB;AACzB,4DAA4D;AAC5D,6CAA2B;AAC3B,4CAA0B;AAC1B,mDAAiC;AACjC,kDAAgC;AAChC,sDAAsD;AACtD,wDAAsC;AACtC,+EAA+E;AAC/E,2EAA2E;AAC3E,4CAA0B;AAC1B,kFAAkF;AAClF,mDAAiC;AACjC,kFAAkF;AAClF,yEAAyE;AACzE,oEAAoE;AACpE,uDAAqC;AACrC,sDAAoC;AACpC,uEAAuE;AACvE,gDAA8B;AAC9B,0EAA0E;AAC1E,4EAA4E;AAC5E,0CAa0B;AARxB,iGAAA,QAAQ,OAAA;AACR,sGAAA,aAAa,OAAA;AACb,0GAAA,iBAAiB,OAAA;AACjB,uGAAA,cAAc,OAAA;AACd,yGAAA,gBAAgB,OAAA;AAChB,+GAAA,sBAAsB,OAAA;AACtB,kGAAA,SAAS,OAAA;AACT,wGAAA,eAAe,OAAA"}
@@ -1,4 +1,5 @@
1
1
  import type { CaptchaFlowKey, FlowCaptcha } from './captcha-widget';
2
+ import { type EmailDelivery } from './environment';
2
3
  import type { FapiClient } from './fapi';
3
4
  /**
4
5
  * The instance's public sign-in / sign-up settings, read from
@@ -19,6 +20,14 @@ export interface InstanceSettings {
19
20
  privacyUrl: string | null;
20
21
  required: boolean;
21
22
  };
23
+ /**
24
+ * Whether the app can deliver email, and what that allows: hide "Forgot
25
+ * password?" when `passwordReset` is false, email code / link options when
26
+ * `available` is false, and show a "can't send email" state instead of the
27
+ * password sign-up form when `passwordSignUp` is false. All true on an older
28
+ * server that does not say.
29
+ */
30
+ emailDelivery: EmailDelivery;
22
31
  }
23
32
  /** One configurable sign-up field, exactly as `/v1/appearance` resolves it. */
24
33
  export interface PublicSignUpField {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.instanceSettingsFrom = instanceSettingsFrom;
4
4
  exports.fetchInstanceSettings = fetchInstanceSettings;
5
5
  exports.toSignUpInput = toSignUpInput;
6
+ const environment_1 = require("./environment");
6
7
  const NO_LEGAL = { termsUrl: null, privacyUrl: null, required: false };
7
8
  const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
8
9
  /**
@@ -40,7 +41,7 @@ function instanceSettingsFrom(body) {
40
41
  required: legalRaw.required === true,
41
42
  }
42
43
  : NO_LEGAL;
43
- return { captchaFlows, signUpFields, legal };
44
+ return { captchaFlows, signUpFields, legal, emailDelivery: (0, environment_1.emailDeliveryFrom)(root) };
44
45
  }
45
46
  /**
46
47
  * Fetch the instance's public settings (`GET /v1/appearance`). Null when the
@@ -1 +1 @@
1
- {"version":3,"file":"instance-settings.js","sourceRoot":"","sources":["../src/instance-settings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instance-settings.js","sourceRoot":"","sources":["../src/instance-settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * §oauth popup mode — the same-origin hand-back for a popup whose
3
+ * `window.opener` was severed.
4
+ *
5
+ * When the page that opens the sign-in popup sends a restrictive
6
+ * Cross-Origin-Opener-Policy (`same-origin`), the browser puts the cross-origin
7
+ * popup in another browsing-context group: `popup.closed` reads `true` to the
8
+ * opener almost at once and `window.opener` is `null` inside the popup, so the
9
+ * Frontend API's `postMessage` can never arrive. The Frontend API then sends the
10
+ * popup back to the OPENER's own page with the result in the URL fragment
11
+ * (`#atlas_popup_result=…`, never the query). That page runs the SDK, which:
12
+ *
13
+ * 1. reads and scrubs the fragment ({@link completeOAuthPopupLanding}),
14
+ * 2. relays the result to the opener over
15
+ * `BroadcastChannel('atlas:oauth:<nonce>')` (a `localStorage` `storage`
16
+ * event where BroadcastChannel is missing) — both are same-origin, so
17
+ * they connect where `window.opener` cannot,
18
+ * 3. closes the popup once the opener acknowledges.
19
+ *
20
+ * Nothing here carries a token: the result is the same one-time ticket (or
21
+ * continuation) the redirect flow puts in its URL, and the opener exchanges it.
22
+ */
23
+ /** The window name {@link startOauthPopup} opens the popup with. */
24
+ export declare const OAUTH_POPUP_WINDOW_NAME = "atlas:oauth";
25
+ /** The fragment key the Frontend API lands the popup result in. */
26
+ export declare const POPUP_RESULT_FRAGMENT = "atlas_popup_result";
27
+ /** The channel a page that finished sign-in inside the popup announces on. */
28
+ export declare const POPUP_SIGNAL_CHANNEL = "atlas:oauth";
29
+ /** The per-flow channel: `atlas:oauth:<nonce>`. */
30
+ export declare function popupChannelName(nonce: string): string;
31
+ /** A fresh base64url channel nonce (22 characters). */
32
+ export declare function createPopupNonce(): string;
33
+ /** The messages on the popup channels. */
34
+ export type PopupRelayMessage = {
35
+ type: 'atlas:oauth:result';
36
+ id: string;
37
+ result: Record<string, unknown>;
38
+ } | {
39
+ type: 'atlas:oauth:signed_in';
40
+ id: string;
41
+ } | {
42
+ type: 'atlas:oauth:ack';
43
+ id: string;
44
+ };
45
+ /**
46
+ * Opener side: listen on the flow's channel (and the shared signal channel) for
47
+ * the popup's result. Every message is acknowledged so the popup can close.
48
+ * Returns the stop function.
49
+ */
50
+ export declare function listenForPopupResult(nonce: string, handlers: {
51
+ /** The landing page relayed the Frontend API's result payload. */
52
+ onResult: (payload: Record<string, unknown>) => void;
53
+ /** A page in the popup finished the sign-in itself (cookies are set). */
54
+ onSignedIn?: () => void;
55
+ }): () => void;
56
+ /** The popup result the Frontend API put in a URL fragment, or null. */
57
+ export declare function readPopupLanding(hash: string): {
58
+ nonce: string;
59
+ payload: Record<string, unknown>;
60
+ } | null;
61
+ /** What {@link completeOAuthPopupLanding} did with a landed popup result. */
62
+ export interface PopupLanding {
63
+ /** The Frontend API's result payload (`type`, `ticket`, `attemptId`, …). */
64
+ payload: Record<string, unknown>;
65
+ /**
66
+ * True when the opener acknowledged the result: it completes the sign-in and
67
+ * this window is being closed. False when nobody was listening (the opener tab
68
+ * was closed, or is on another origin) — the caller should complete the
69
+ * sign-in on this page instead, exactly like a redirect.
70
+ */
71
+ delivered: boolean;
72
+ }
73
+ /**
74
+ * Popup side, run on startup: if this page is the landing of a severed popup
75
+ * (`#atlas_popup_result=…` in the URL — normally with `window.name ===
76
+ * 'atlas:oauth'`, though a browser may clear the name when the opener's COOP
77
+ * switched browsing-context groups, so the fragment alone is decisive), scrub
78
+ * the fragment, relay the result to the opener and close this window once the
79
+ * opener acknowledges. Returns null when this is not such a landing.
80
+ */
81
+ export declare function completeOAuthPopupLanding(options?: {
82
+ win?: Window;
83
+ ackTimeoutMs?: number;
84
+ }): Promise<PopupLanding | null>;
85
+ /**
86
+ * Popup side, after a sign-in completed ON this page (the redirect flow ran in
87
+ * the popup — e.g. an SDK or provider path without the channel): when this is
88
+ * the Atlas popup window, tell the waiting opener and close. It closes only when
89
+ * an opener is there to take over (it acknowledged, or `window.opener` is live),
90
+ * never stranding a user whose opener tab is gone. Resolves whether it closed.
91
+ */
92
+ export declare function closeOAuthPopupAfterSignIn(options?: {
93
+ win?: Window;
94
+ ackTimeoutMs?: number;
95
+ }): Promise<boolean>;