@atlasauth/capacitor 0.1.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/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @atlasauth/capacitor
2
+
3
+ The official Atlas SDK for **Capacitor / Ionic** — the cross-platform
4
+ (iOS · Android · web) native-auth sibling of [`@atlasauth/electron`](../electron)
5
+ and [`@atlasauth/expo`](../expo). It gives a Capacitor app the **native-session
6
+ sign-in flow**: system browser → deep-link callback → PKCE → a cookie-free Atlas
7
+ session that auto-refreshes and persists in secure storage.
8
+
9
+ A Capacitor app is a web layer (a WebView on device, a real browser on the web
10
+ target) with no cookie against the Atlas FAPI origin, so it cannot carry a
11
+ session the way a web page does. Instead it signs in through the **system
12
+ browser**, catches the **deep-link** callback, completes **PKCE**, trades the
13
+ code for an Atlas session, and carries that session itself as a bearer — wrapping
14
+ the same `@atlasauth/js` `NativeSessionManager` / `exchangeForSession` /
15
+ `refreshNativeSession` core the Electron and Expo SDKs use.
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ npm install @atlasauth/capacitor
21
+ npx cap sync
22
+ # the Capacitor plugins it drives (all optional peers — install what you use):
23
+ npm install @capacitor/browser @capacitor/app @capacitor/preferences
24
+ ```
25
+
26
+ `@capacitor/core`, `@capacitor/browser`, `@capacitor/app` and
27
+ `@capacitor/preferences` are **optional peers**. The package compiles and runs
28
+ without them (falling back gracefully), but the native sign-in flow needs
29
+ `@capacitor/browser` + `@capacitor/app`, and persistence needs a token store.
30
+
31
+ ### Deep-link setup
32
+
33
+ Register your custom scheme so the authorize redirect reopens the app, and route
34
+ `appUrlOpen` to Atlas:
35
+
36
+ - iOS: add the scheme under `CFBundleURLTypes` in `Info.plist`.
37
+ - Android: add an `intent-filter` with your scheme to the main activity.
38
+
39
+ Your `redirectUri` is that scheme, e.g. `com.acme.app://auth`.
40
+
41
+ ## Quick start
42
+
43
+ ```ts
44
+ import { createAtlasCapacitor } from '@atlasauth/capacitor';
45
+
46
+ export const atlas = await createAtlasCapacitor({
47
+ publishableKey: 'pk_live_…',
48
+ frontendApi: 'https://fapi.your-instance.atlasauth.net',
49
+ clientId: 'client_…', // your first-party native OAuth client
50
+ redirectUri: 'com.acme.app://auth',
51
+ });
52
+
53
+ // Sign in (opens the system browser; resolves once the deep link returns).
54
+ await atlas.signIn();
55
+
56
+ // Call your own API with the live, auto-refreshing session.
57
+ const res = await fetch('/api/thing', { headers: await atlas.authHeaders() });
58
+
59
+ atlas.onChange((session) => console.log('session rotated', session.sessionId));
60
+ await atlas.signOut();
61
+ ```
62
+
63
+ `frontendApi` is required — the publishable key encodes the instance id but not a
64
+ host. Use `https://atlasauth.net` before you verify a custom domain, your custom
65
+ domain after.
66
+
67
+ ### The handle
68
+
69
+ | Method | What it does |
70
+ | --- | --- |
71
+ | `signIn({ redirectUri?, scope? })` | System browser → deep-link callback → PKCE → session. On the web target, falls back to the standard redirect (see below). |
72
+ | `handleRedirectCallback(url?)` | Finish a **web** redirect sign-in on app load. |
73
+ | `signOut()` | Forget the session and clear the store. |
74
+ | `getToken()` | The current session JWT, auto-refreshed near expiry. |
75
+ | `authHeaders()` | `{ Authorization, 'x-publishable-key' }` for `/v1/client/me/*` and your API. |
76
+ | `getSession()` | The current `NativeSession`, or null. |
77
+ | `onChange(listener)` | Fires on every session change; returns an unsubscribe. |
78
+
79
+ ## Web target (PWA)
80
+
81
+ On the web, there is no deep link, so `signIn()` falls back to the **standard
82
+ redirect**: it stashes the PKCE verifier and navigates to the authorize URL.
83
+ Finish on the next load:
84
+
85
+ ```ts
86
+ // on app boot, when the URL looks like your callback:
87
+ const session = await atlas.handleRedirectCallback(); // defaults to window.location.href
88
+ ```
89
+
90
+ ## Secure storage
91
+
92
+ By default the session is persisted with `@capacitor/preferences` when that
93
+ plugin is present, else a non-persistent in-memory store.
94
+
95
+ > ⚠️ **`@capacitor/preferences` is plaintext at rest** (UserDefaults /
96
+ > SharedPreferences). For production, back the store with a secure-storage
97
+ > community plugin (e.g. `@capacitor-community/secure-storage`) behind the same
98
+ > `TokenStore` interface:
99
+
100
+ ```ts
101
+ import { createAtlasCapacitor, type TokenStore } from '@atlasauth/capacitor';
102
+
103
+ const secureStore: TokenStore = {
104
+ getToken: () => SecureStorage.get('atlas.session'),
105
+ setToken: (v) => SecureStorage.set('atlas.session', v),
106
+ clearToken: () => SecureStorage.remove('atlas.session'),
107
+ };
108
+
109
+ const atlas = await createAtlasCapacitor({ /* … */ storage: secureStore });
110
+ ```
111
+
112
+ ## Driving the pieces yourself
113
+
114
+ The flow is also exposed à la carte: `createPkcePair`, `buildAuthorizeUrl`,
115
+ `parseAuthCallback`, `redeemAuthorizationCode`, `exchangeAuthCode`,
116
+ `signInWithBrowser`, `completeRedirectSignIn`, plus
117
+ `createNativeSessionManager` / `exchangeForSession` / `refreshNativeSession`
118
+ re-exported from [`@atlasauth/js`](../js).
@@ -0,0 +1,116 @@
1
+ /**
2
+ * `@atlasauth/capacitor` — the Atlas SDK for Capacitor / Ionic.
3
+ *
4
+ * A Capacitor app is a web layer (WebView on iOS/Android, a real browser on the
5
+ * web target) with no cookie against the Atlas FAPI origin, so it cannot carry a
6
+ * session the way a web page does. This package gives it the NATIVE-session flow
7
+ * instead: sign in through the SYSTEM browser, catch the deep-link callback,
8
+ * complete PKCE, trade the code for a cookie-free Atlas session, and keep that
9
+ * session live — auto-refreshing the short-lived JWT and persisting each rotated
10
+ * refresh token into secure storage. It is the Capacitor sibling of
11
+ * `@atlasauth/electron` and `@atlasauth/expo`, wrapping the very same
12
+ * `@atlasauth/js` `NativeSessionManager` / `exchangeForSession` /
13
+ * `refreshNativeSession` core.
14
+ *
15
+ * The one call an app makes is {@link createAtlasCapacitor}; everything else here
16
+ * (the sign-in flow, the token store, the PKCE helpers) is exposed for apps that
17
+ * want to drive the pieces themselves.
18
+ */
19
+ import type { NativeSession, NativeSessionManager } from '@atlasauth/js';
20
+ import { type CapacitorPlugins } from './plugins';
21
+ import { type SignInOutcome } from './sign-in';
22
+ import { type TokenStore } from './token-store';
23
+ export interface CreateAtlasCapacitorConfig {
24
+ /** The instance publishable key (`pk_…`). */
25
+ publishableKey: string;
26
+ /** The instance's FAPI origin, e.g. `https://fapi.acme.atlasauth.net`. */
27
+ frontendApi: string;
28
+ /** The first-party native OAuth client id. Required to {@link AtlasCapacitor.signIn}. */
29
+ clientId?: string;
30
+ /** The app's custom-scheme callback, e.g. `com.acme.app://auth`. A {@link AtlasCapacitor.signIn} default. */
31
+ redirectUri?: string;
32
+ /** Space-delimited OAuth scopes. A {@link AtlasCapacitor.signIn} default. */
33
+ scope?: string;
34
+ /**
35
+ * Where the rotated session is persisted. Defaults to a `@capacitor/preferences`
36
+ * store when the plugin is present, else a NON-persistent in-memory store. Ship
37
+ * a secure-storage-backed {@link TokenStore} in production — Preferences is
38
+ * plaintext at rest.
39
+ */
40
+ storage?: TokenStore;
41
+ /** Where the transient PKCE material is stashed on the WEB redirect path. */
42
+ pkceStore?: TokenStore;
43
+ /** Inject the Capacitor plugins (tests). Defaults to a lazy, guarded load. */
44
+ plugins?: CapacitorPlugins;
45
+ /** Injectable for tests; defaults to the platform `fetch`. */
46
+ fetchImpl?: typeof fetch;
47
+ /** Injectable clock, epoch ms. Defaults to `Date.now`. */
48
+ now?: () => number;
49
+ /** Navigate the page on the web redirect path. @default `window.location.assign` */
50
+ navigate?: (url: string) => void;
51
+ }
52
+ /** Per-call overrides for {@link AtlasCapacitor.signIn}. */
53
+ export interface SignInOptions {
54
+ clientId?: string;
55
+ redirectUri?: string;
56
+ scope?: string;
57
+ }
58
+ /**
59
+ * The Atlas handle a Capacitor app holds. Wraps the `@atlasauth/js`
60
+ * {@link NativeSessionManager}: {@link signIn} runs the browser flow and seeds
61
+ * the manager, {@link getToken} hands out a live (auto-refreshing) JWT, and
62
+ * {@link onChange} fires on every session change.
63
+ */
64
+ export interface AtlasCapacitor {
65
+ /**
66
+ * Run the native-session sign-in: open the system browser, catch the deep-link
67
+ * callback, complete PKCE, exchange for a session, and store it. On the web
68
+ * target it falls back to the standard redirect (navigates away, `pending:
69
+ * true`) — finish with {@link handleRedirectCallback} on the next load.
70
+ */
71
+ signIn(options?: SignInOptions): Promise<SignInOutcome>;
72
+ /**
73
+ * Finish a WEB redirect sign-in on app load: read the callback `url` (defaults
74
+ * to `window.location.href`), complete PKCE, store the session, and return it
75
+ * (or null when there is nothing to complete).
76
+ */
77
+ handleRedirectCallback(url?: string): Promise<NativeSession | null>;
78
+ /** Forget the session and clear the store (sign-out). */
79
+ signOut(): Promise<void>;
80
+ /** The current session JWT, auto-refreshed near expiry; null when signed out. */
81
+ getToken(): Promise<string | null>;
82
+ /** The headers an authenticated `/v1/client/me/*` call needs (bearer + pk). */
83
+ authHeaders(): Promise<Record<string, string>>;
84
+ /** The current session, or null when signed out. Does NOT refresh. */
85
+ getSession(): NativeSession | null;
86
+ /** Register a listener fired with the session on every change; returns unsubscribe. */
87
+ onChange(listener: (session: NativeSession) => void): () => void;
88
+ /** The underlying `@atlasauth/js` manager, for advanced use. */
89
+ readonly manager: NativeSessionManager;
90
+ }
91
+ /**
92
+ * Create the Atlas handle for a Capacitor app.
93
+ *
94
+ * Async because it lazily loads the Capacitor plugins and rehydrates the session
95
+ * manager from the store, so a returning user is signed in before the first
96
+ * request. Call it once at startup and keep the result.
97
+ *
98
+ * ```ts
99
+ * import { createAtlasCapacitor } from '@atlasauth/capacitor';
100
+ *
101
+ * const atlas = await createAtlasCapacitor({
102
+ * publishableKey: 'pk_live_…',
103
+ * frontendApi: 'https://fapi.acme.atlasauth.net',
104
+ * clientId: 'client_…',
105
+ * redirectUri: 'com.acme.app://auth',
106
+ * });
107
+ * await atlas.signIn();
108
+ * const token = await atlas.getToken();
109
+ * ```
110
+ */
111
+ export declare function createAtlasCapacitor(config: CreateAtlasCapacitorConfig): Promise<AtlasCapacitor>;
112
+ export * from './plugins';
113
+ export * from './pkce';
114
+ export * from './token-store';
115
+ export * from './sign-in';
116
+ export { createNativeSessionManager, clearNativeSession, NativeSessionManager, exchangeForSession, refreshNativeSession, type NativeSession, type NativeSessionConfig, } from './native-session';
package/dist/index.js ADDED
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ /**
3
+ * `@atlasauth/capacitor` — the Atlas SDK for Capacitor / Ionic.
4
+ *
5
+ * A Capacitor app is a web layer (WebView on iOS/Android, a real browser on the
6
+ * web target) with no cookie against the Atlas FAPI origin, so it cannot carry a
7
+ * session the way a web page does. This package gives it the NATIVE-session flow
8
+ * instead: sign in through the SYSTEM browser, catch the deep-link callback,
9
+ * complete PKCE, trade the code for a cookie-free Atlas session, and keep that
10
+ * session live — auto-refreshing the short-lived JWT and persisting each rotated
11
+ * refresh token into secure storage. It is the Capacitor sibling of
12
+ * `@atlasauth/electron` and `@atlasauth/expo`, wrapping the very same
13
+ * `@atlasauth/js` `NativeSessionManager` / `exchangeForSession` /
14
+ * `refreshNativeSession` core.
15
+ *
16
+ * The one call an app makes is {@link createAtlasCapacitor}; everything else here
17
+ * (the sign-in flow, the token store, the PKCE helpers) is exposed for apps that
18
+ * want to drive the pieces themselves.
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
32
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.refreshNativeSession = exports.exchangeForSession = exports.NativeSessionManager = exports.clearNativeSession = exports.createNativeSessionManager = void 0;
36
+ exports.createAtlasCapacitor = createAtlasCapacitor;
37
+ const plugins_1 = require("./plugins");
38
+ const sign_in_1 = require("./sign-in");
39
+ const token_store_1 = require("./token-store");
40
+ const native_session_1 = require("./native-session");
41
+ /**
42
+ * Create the Atlas handle for a Capacitor app.
43
+ *
44
+ * Async because it lazily loads the Capacitor plugins and rehydrates the session
45
+ * manager from the store, so a returning user is signed in before the first
46
+ * request. Call it once at startup and keep the result.
47
+ *
48
+ * ```ts
49
+ * import { createAtlasCapacitor } from '@atlasauth/capacitor';
50
+ *
51
+ * const atlas = await createAtlasCapacitor({
52
+ * publishableKey: 'pk_live_…',
53
+ * frontendApi: 'https://fapi.acme.atlasauth.net',
54
+ * clientId: 'client_…',
55
+ * redirectUri: 'com.acme.app://auth',
56
+ * });
57
+ * await atlas.signIn();
58
+ * const token = await atlas.getToken();
59
+ * ```
60
+ */
61
+ async function createAtlasCapacitor(config) {
62
+ const plugins = config.plugins ?? (await (0, plugins_1.loadCapacitorPlugins)());
63
+ const storage = config.storage ??
64
+ (plugins.Preferences
65
+ ? (0, token_store_1.createPreferencesTokenStore)(plugins.Preferences, token_store_1.DEFAULT_SESSION_KEY)
66
+ : (0, token_store_1.createInMemoryTokenStore)());
67
+ const pkceStore = config.pkceStore ??
68
+ (plugins.Preferences
69
+ ? (0, token_store_1.createPreferencesTokenStore)(plugins.Preferences, sign_in_1.PKCE_STASH_KEY)
70
+ : (0, token_store_1.createInMemoryTokenStore)());
71
+ const manager = await (0, native_session_1.createNativeSessionManager)({
72
+ frontendApi: config.frontendApi,
73
+ publishableKey: config.publishableKey,
74
+ clientId: config.clientId,
75
+ storage,
76
+ fetchImpl: config.fetchImpl,
77
+ now: config.now,
78
+ });
79
+ const requireString = (value, name) => {
80
+ if (!value)
81
+ throw new Error(`Atlas: \`${name}\` is required to sign in.`);
82
+ return value;
83
+ };
84
+ return {
85
+ manager,
86
+ async signIn(options = {}) {
87
+ const clientId = requireString(options.clientId ?? config.clientId, 'clientId');
88
+ const redirectUri = requireString(options.redirectUri ?? config.redirectUri, 'redirectUri');
89
+ const outcome = await (0, sign_in_1.signInWithBrowser)({
90
+ frontendApi: config.frontendApi,
91
+ clientId,
92
+ redirectUri,
93
+ scope: options.scope ?? config.scope,
94
+ browser: plugins.Browser,
95
+ app: plugins.App,
96
+ capacitor: plugins.Capacitor,
97
+ pkceStore,
98
+ fetchImpl: config.fetchImpl,
99
+ navigate: config.navigate,
100
+ });
101
+ if (outcome.session)
102
+ manager.setSession(outcome.session);
103
+ return outcome;
104
+ },
105
+ async handleRedirectCallback(url) {
106
+ // Only meaningful on the web redirect path; a native target never lands here.
107
+ if ((0, plugins_1.isNativePlatform)({ Capacitor: plugins.Capacitor }))
108
+ return null;
109
+ const landingUrl = url ?? (typeof window !== 'undefined' ? window.location.href : undefined);
110
+ if (!landingUrl)
111
+ return null;
112
+ const clientId = requireString(config.clientId, 'clientId');
113
+ const redirectUri = requireString(config.redirectUri, 'redirectUri');
114
+ const session = await (0, sign_in_1.completeRedirectSignIn)({
115
+ frontendApi: config.frontendApi,
116
+ clientId,
117
+ redirectUri,
118
+ url: landingUrl,
119
+ pkceStore,
120
+ fetchImpl: config.fetchImpl,
121
+ });
122
+ if (session)
123
+ manager.setSession(session);
124
+ return session;
125
+ },
126
+ async signOut() {
127
+ manager.clear();
128
+ await (0, native_session_1.clearNativeSession)(storage);
129
+ },
130
+ getToken() {
131
+ return manager.getToken();
132
+ },
133
+ authHeaders() {
134
+ return manager.authHeaders();
135
+ },
136
+ getSession() {
137
+ return manager.current;
138
+ },
139
+ onChange(listener) {
140
+ return manager.onChange(listener);
141
+ },
142
+ };
143
+ }
144
+ // ── Re-exports: the pieces, for apps that drive the flow themselves ──────────
145
+ __exportStar(require("./plugins"), exports);
146
+ __exportStar(require("./pkce"), exports);
147
+ __exportStar(require("./token-store"), exports);
148
+ __exportStar(require("./sign-in"), exports);
149
+ var native_session_2 = require("./native-session");
150
+ Object.defineProperty(exports, "createNativeSessionManager", { enumerable: true, get: function () { return native_session_2.createNativeSessionManager; } });
151
+ Object.defineProperty(exports, "clearNativeSession", { enumerable: true, get: function () { return native_session_2.clearNativeSession; } });
152
+ Object.defineProperty(exports, "NativeSessionManager", { enumerable: true, get: function () { return native_session_2.NativeSessionManager; } });
153
+ Object.defineProperty(exports, "exchangeForSession", { enumerable: true, get: function () { return native_session_2.exchangeForSession; } });
154
+ Object.defineProperty(exports, "refreshNativeSession", { enumerable: true, get: function () { return native_session_2.refreshNativeSession; } });
155
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Native session for Capacitor.
3
+ *
4
+ * A thin wiring of the framework-agnostic {@link NativeSessionManager} from
5
+ * `@atlasauth/js` to this package's {@link TokenStore}. The core manager owns the
6
+ * exchange / refresh / rotation logic; this file's only job is to PERSIST each
7
+ * rotated session into the store (so the rotating refresh token survives an app
8
+ * restart) and to rehydrate the manager from it on boot.
9
+ *
10
+ * The whole session is stored as one JSON string under the store's single slot —
11
+ * the refresh token (which rotates and must survive a restart) alongside the
12
+ * session id the refresh call needs and the current JWT. Give the native session
13
+ * its OWN store/key, separate from any other value the app keeps.
14
+ */
15
+ import { NativeSessionManager } from '@atlasauth/js';
16
+ import { type TokenStore } from './token-store';
17
+ export interface NativeSessionConfig {
18
+ /** The instance's FAPI origin, e.g. `https://fapi.acme.atlasauth.net`. */
19
+ frontendApi: string;
20
+ /** The publishable key — sent on the refresh call and in `authHeaders()`. */
21
+ publishableKey: string;
22
+ /** The first-party OAuth client id (for a later in-manager exchange). */
23
+ clientId?: string;
24
+ /**
25
+ * Where the rotated session is persisted. Defaults to a NON-persistent
26
+ * in-memory store — ship a `@capacitor/preferences` (or secure-storage) store
27
+ * (see {@link TokenStore}) in production.
28
+ */
29
+ storage?: TokenStore;
30
+ /** Injectable for tests; defaults to the platform `fetch`. */
31
+ fetchImpl?: typeof fetch;
32
+ /** Injectable clock, epoch ms. Defaults to `Date.now`. */
33
+ now?: () => number;
34
+ }
35
+ /**
36
+ * Build a {@link NativeSessionManager} wired to a Capacitor token store.
37
+ *
38
+ * Async because the store may be async: it reads whatever session the store
39
+ * holds and seeds the manager with it, so a returning user is signed in before
40
+ * the first request. Thereafter every rotation is written straight back to the
41
+ * store via the manager's `persist` hook.
42
+ */
43
+ export declare function createNativeSessionManager(config: NativeSessionConfig): Promise<NativeSessionManager>;
44
+ /** Clear the persisted native session (sign-out). */
45
+ export declare function clearNativeSession(storage: TokenStore): Promise<void>;
46
+ export { NativeSessionManager, exchangeForSession, refreshNativeSession, type NativeSession, } from '@atlasauth/js';
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * Native session for Capacitor.
4
+ *
5
+ * A thin wiring of the framework-agnostic {@link NativeSessionManager} from
6
+ * `@atlasauth/js` to this package's {@link TokenStore}. The core manager owns the
7
+ * exchange / refresh / rotation logic; this file's only job is to PERSIST each
8
+ * rotated session into the store (so the rotating refresh token survives an app
9
+ * restart) and to rehydrate the manager from it on boot.
10
+ *
11
+ * The whole session is stored as one JSON string under the store's single slot —
12
+ * the refresh token (which rotates and must survive a restart) alongside the
13
+ * session id the refresh call needs and the current JWT. Give the native session
14
+ * its OWN store/key, separate from any other value the app keeps.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.refreshNativeSession = exports.exchangeForSession = exports.NativeSessionManager = void 0;
18
+ exports.createNativeSessionManager = createNativeSessionManager;
19
+ exports.clearNativeSession = clearNativeSession;
20
+ const js_1 = require("@atlasauth/js");
21
+ const token_store_1 = require("./token-store");
22
+ /**
23
+ * Build a {@link NativeSessionManager} wired to a Capacitor token store.
24
+ *
25
+ * Async because the store may be async: it reads whatever session the store
26
+ * holds and seeds the manager with it, so a returning user is signed in before
27
+ * the first request. Thereafter every rotation is written straight back to the
28
+ * store via the manager's `persist` hook.
29
+ */
30
+ async function createNativeSessionManager(config) {
31
+ const storage = config.storage ?? (0, token_store_1.createInMemoryTokenStore)();
32
+ const initial = (0, js_1.parseNativeSession)(await storage.getToken()) ?? undefined;
33
+ return new js_1.NativeSessionManager({
34
+ baseUrl: config.frontendApi,
35
+ publishableKey: config.publishableKey,
36
+ clientId: config.clientId,
37
+ initial,
38
+ fetchImpl: config.fetchImpl,
39
+ now: config.now,
40
+ // The manager calls this on every set/rotate; persist the fresh session so
41
+ // the rotated refresh token survives an app restart. Fire-and-forget — the
42
+ // store may be async, and a write failure must not break the live session.
43
+ persist: (session) => {
44
+ void Promise.resolve(storage.setToken((0, js_1.serializeNativeSession)(session))).catch(() => undefined);
45
+ },
46
+ });
47
+ }
48
+ /** Clear the persisted native session (sign-out). */
49
+ async function clearNativeSession(storage) {
50
+ await storage.clearToken();
51
+ }
52
+ var js_2 = require("@atlasauth/js");
53
+ Object.defineProperty(exports, "NativeSessionManager", { enumerable: true, get: function () { return js_2.NativeSessionManager; } });
54
+ Object.defineProperty(exports, "exchangeForSession", { enumerable: true, get: function () { return js_2.exchangeForSession; } });
55
+ Object.defineProperty(exports, "refreshNativeSession", { enumerable: true, get: function () { return js_2.refreshNativeSession; } });
56
+ //# sourceMappingURL=native-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-session.js","sourceRoot":"","sources":["../src/native-session.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAqCH,gEAsBC;AAGD,gDAEC;AA9DD,sCAKuB;AACvB,+CAA0E;AAqB1E;;;;;;;GAOG;AACI,KAAK,UAAU,0BAA0B,CAC9C,MAA2B;IAE3B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,IAAA,sCAAwB,GAAE,CAAC;IAC7D,MAAM,OAAO,GAAG,IAAA,uBAAkB,EAAC,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,SAAS,CAAC;IAE1E,OAAO,IAAI,yBAAoB,CAAC;QAC9B,OAAO,EAAE,MAAM,CAAC,WAAW;QAC3B,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO;QACP,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,2EAA2E;QAC3E,2EAA2E;QAC3E,2EAA2E;QAC3E,OAAO,EAAE,CAAC,OAAsB,EAAE,EAAE;YAClC,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAA,2BAAsB,EAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAC3E,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,qDAAqD;AAC9C,KAAK,UAAU,kBAAkB,CAAC,OAAmB;IAC1D,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;AAC7B,CAAC;AAED,oCAKuB;AAJrB,0GAAA,oBAAoB,OAAA;AACpB,wGAAA,kBAAkB,OAAA;AAClB,0GAAA,oBAAoB,OAAA"}
package/dist/pkce.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * PKCE (RFC 7636, S256) for the native authorization-code flow.
3
+ *
4
+ * A first-party native/Capacitor client is a PUBLIC client — it ships no secret
5
+ * — so it proves possession of its own authorization request with PKCE: it keeps
6
+ * a random `verifier`, sends only `code_challenge = base64url(SHA-256(verifier))`
7
+ * on the authorize URL, and presents the `verifier` when it redeems the code. A
8
+ * code intercepted on the deep-link callback is inert without the verifier, which
9
+ * never leaves the app.
10
+ *
11
+ * Uses only Web Crypto globals (`crypto.getRandomValues`, `crypto.subtle`), which
12
+ * every Capacitor target provides — the iOS/Android system WebView and the web
13
+ * build alike — so there is no `node:crypto` import. Mirrors `createPkcePair`
14
+ * from `@atlasauth/backend`'s handshake, here on the client side of the exchange.
15
+ */
16
+ export interface PkcePair {
17
+ /** Kept in the app; presented only when the code is redeemed, never on the URL. */
18
+ verifier: string;
19
+ /** base64url(SHA-256(verifier)); the only half that rides the authorize URL. */
20
+ challenge: string;
21
+ }
22
+ /**
23
+ * Generate a PKCE S256 pair. The verifier is 32 random bytes, base64url-encoded
24
+ * (43 chars — within RFC 7636's 43–128 range); the challenge is its SHA-256
25
+ * digest, base64url-encoded.
26
+ */
27
+ export declare function createPkcePair(): Promise<PkcePair>;
28
+ /**
29
+ * Mint a random `state` value for CSRF binding of the authorize request. Sent on
30
+ * the authorize URL and checked against the value echoed back on the deep-link
31
+ * callback, so an unsolicited callback (a deep link we did not initiate) is
32
+ * rejected.
33
+ */
34
+ export declare function createState(): string;
package/dist/pkce.js ADDED
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * PKCE (RFC 7636, S256) for the native authorization-code flow.
4
+ *
5
+ * A first-party native/Capacitor client is a PUBLIC client — it ships no secret
6
+ * — so it proves possession of its own authorization request with PKCE: it keeps
7
+ * a random `verifier`, sends only `code_challenge = base64url(SHA-256(verifier))`
8
+ * on the authorize URL, and presents the `verifier` when it redeems the code. A
9
+ * code intercepted on the deep-link callback is inert without the verifier, which
10
+ * never leaves the app.
11
+ *
12
+ * Uses only Web Crypto globals (`crypto.getRandomValues`, `crypto.subtle`), which
13
+ * every Capacitor target provides — the iOS/Android system WebView and the web
14
+ * build alike — so there is no `node:crypto` import. Mirrors `createPkcePair`
15
+ * from `@atlasauth/backend`'s handshake, here on the client side of the exchange.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.createPkcePair = createPkcePair;
19
+ exports.createState = createState;
20
+ /** base64url, no padding — the encoding PKCE and OAuth use for these values. */
21
+ function base64url(bytes) {
22
+ let bin = '';
23
+ for (const b of bytes)
24
+ bin += String.fromCharCode(b);
25
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
26
+ }
27
+ /**
28
+ * Generate a PKCE S256 pair. The verifier is 32 random bytes, base64url-encoded
29
+ * (43 chars — within RFC 7636's 43–128 range); the challenge is its SHA-256
30
+ * digest, base64url-encoded.
31
+ */
32
+ async function createPkcePair() {
33
+ const raw = new Uint8Array(32);
34
+ crypto.getRandomValues(raw);
35
+ const verifier = base64url(raw);
36
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
37
+ return { verifier, challenge: base64url(new Uint8Array(digest)) };
38
+ }
39
+ /**
40
+ * Mint a random `state` value for CSRF binding of the authorize request. Sent on
41
+ * the authorize URL and checked against the value echoed back on the deep-link
42
+ * callback, so an unsolicited callback (a deep link we did not initiate) is
43
+ * rejected.
44
+ */
45
+ function createState() {
46
+ const raw = new Uint8Array(16);
47
+ crypto.getRandomValues(raw);
48
+ return base64url(raw);
49
+ }
50
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAqBH,wCAMC;AAQD,kCAIC;AArCD,gFAAgF;AAChF,SAAS,SAAS,CAAC,KAAiB;IAClC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AASD;;;;GAIG;AACI,KAAK,UAAU,cAAc;IAClC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IAC/B,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,SAAgB,WAAW;IACzB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IAC/B,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC"}