deepspace 0.35.0 → 0.37.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 +16 -0
- package/dist/{chunk-ESEI3MMY.js → chunk-2CXYYP3K.js} +2 -2
- package/dist/{chunk-ESEI3MMY.js.map → chunk-2CXYYP3K.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.js +1 -1
- package/dist/documentation-default-renderer.cjs +1 -1
- package/dist/documentation-react.js +1 -1
- package/dist/documentation-runtime.js +1 -1
- package/dist/documentation-server-core.js +1 -1
- package/dist/documentation.js +1 -1
- package/dist/documentation.js.map +1 -1
- package/dist/expo.d.ts +22 -1
- package/dist/expo.js +20 -0
- package/dist/expo.js.map +1 -1
- package/dist/server.d.ts +24 -3
- package/dist/server.js +33 -15
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +36 -4
- package/dist/worker.js +85 -17
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ESYXTIV2.js +0 -1957
- package/dist/chunk-ESYXTIV2.js.map +0 -1
package/dist/expo.d.ts
CHANGED
|
@@ -36,10 +36,24 @@ interface DeepSpaceExpoClientOptions {
|
|
|
36
36
|
interface ExpoAuthPaths {
|
|
37
37
|
start: string;
|
|
38
38
|
exchange: string;
|
|
39
|
+
idToken: string;
|
|
39
40
|
token: string;
|
|
40
41
|
me: string;
|
|
41
42
|
signOut: string;
|
|
42
43
|
}
|
|
44
|
+
interface NativeIdTokenOptions {
|
|
45
|
+
/**
|
|
46
|
+
* The raw nonce, if the app gave the provider one (Apple takes its SHA-256
|
|
47
|
+
* as lowercase hex). DeepSpace checks it against the token. Replay is
|
|
48
|
+
* stopped separately: each ID token signs in once.
|
|
49
|
+
*/
|
|
50
|
+
nonce?: string;
|
|
51
|
+
/** Apple shares the person's name only on their first sign-in. */
|
|
52
|
+
name?: {
|
|
53
|
+
firstName?: string | null;
|
|
54
|
+
lastName?: string | null;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
43
57
|
declare class DeepSpaceExpoError extends Error {
|
|
44
58
|
readonly status: number;
|
|
45
59
|
readonly body: unknown;
|
|
@@ -69,6 +83,13 @@ declare class DeepSpaceExpoClient {
|
|
|
69
83
|
getSession(): Promise<DeepSpaceExpoSession | null>;
|
|
70
84
|
signIn(provider?: ExpoAuthProvider, state?: string): Promise<DeepSpaceExpoUser>;
|
|
71
85
|
signInWithGoogle(state?: string): Promise<DeepSpaceExpoUser>;
|
|
86
|
+
/**
|
|
87
|
+
* Signs in with an ID token from the platform's own sign-in SDK (Google
|
|
88
|
+
* Sign-In or Sign in with Apple), so the consent screen shows the app's
|
|
89
|
+
* name. The worker must route `paths.idToken` to `nativeAuthIdToken`, and
|
|
90
|
+
* the plane must list the app's client ids (see the Expo guide).
|
|
91
|
+
*/
|
|
92
|
+
signInWithIdToken(provider: 'google' | 'apple', idToken: string, options?: NativeIdTokenOptions): Promise<DeepSpaceExpoUser>;
|
|
72
93
|
refresh(): Promise<string | null>;
|
|
73
94
|
getAuthToken(): Promise<string | null>;
|
|
74
95
|
me(): Promise<DeepSpaceExpoUser>;
|
|
@@ -80,4 +101,4 @@ declare class DeepSpaceExpoClient {
|
|
|
80
101
|
private fetchWithTimeout;
|
|
81
102
|
}
|
|
82
103
|
|
|
83
|
-
export { DeepSpaceExpoClient, type DeepSpaceExpoClientOptions, DeepSpaceExpoError, type DeepSpaceExpoSession, type DeepSpaceExpoUser, DeepSpaceSignInCancelledError, type ExpoAuthPaths, type ExpoAuthProvider, type ExpoAuthStorage, buildExpoOAuthStartUrl, codeFromExpoRedirect, createDeepSpaceExpoClient, normalizeExpoBaseUrl, stateFromExpoRedirect };
|
|
104
|
+
export { DeepSpaceExpoClient, type DeepSpaceExpoClientOptions, DeepSpaceExpoError, type DeepSpaceExpoSession, type DeepSpaceExpoUser, DeepSpaceSignInCancelledError, type ExpoAuthPaths, type ExpoAuthProvider, type ExpoAuthStorage, type NativeIdTokenOptions, buildExpoOAuthStartUrl, codeFromExpoRedirect, createDeepSpaceExpoClient, normalizeExpoBaseUrl, stateFromExpoRedirect };
|
package/dist/expo.js
CHANGED
|
@@ -55,6 +55,7 @@ var DeepSpaceSignInCancelledError = class extends Error {
|
|
|
55
55
|
var DEFAULT_PATHS = {
|
|
56
56
|
start: "/api/auth/native-start",
|
|
57
57
|
exchange: "/api/auth/native-exchange",
|
|
58
|
+
idToken: "/api/auth/native-id-token",
|
|
58
59
|
token: "/api/auth/native-token",
|
|
59
60
|
me: "/api/auth/native-me",
|
|
60
61
|
signOut: "/api/auth/native-signout"
|
|
@@ -129,6 +130,25 @@ var DeepSpaceExpoClient = class {
|
|
|
129
130
|
signInWithGoogle(state) {
|
|
130
131
|
return this.signIn("google", state);
|
|
131
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Signs in with an ID token from the platform's own sign-in SDK (Google
|
|
135
|
+
* Sign-In or Sign in with Apple), so the consent screen shows the app's
|
|
136
|
+
* name. The worker must route `paths.idToken` to `nativeAuthIdToken`, and
|
|
137
|
+
* the plane must list the app's client ids (see the Expo guide).
|
|
138
|
+
*/
|
|
139
|
+
async signInWithIdToken(provider, idToken, options = {}) {
|
|
140
|
+
const session = await this.json(this.paths.idToken, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
body: JSON.stringify({
|
|
143
|
+
provider,
|
|
144
|
+
idToken,
|
|
145
|
+
...options.nonce ? { nonce: options.nonce } : {},
|
|
146
|
+
...options.name ? { user: { name: options.name } } : {}
|
|
147
|
+
})
|
|
148
|
+
});
|
|
149
|
+
await this.saveSession(session);
|
|
150
|
+
return this.me();
|
|
151
|
+
}
|
|
132
152
|
async refresh() {
|
|
133
153
|
const session = await this.getSession();
|
|
134
154
|
if (!session) return null;
|
package/dist/expo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/expo.ts","../src/expo-url.ts"],"sourcesContent":["/**\n * DeepSpace Expo client.\n *\n * This entry is intentionally separate from `deepspace`: the browser client\n * uses Better Auth cookies and browser globals, while an Expo app needs the\n * system browser, a custom-scheme callback, and SecureStore-backed sessions.\n * The Worker still owns auth and JWT issuance; this package only transports the\n * one-time OAuth code and refreshes the short-lived bearer.\n */\nimport * as Linking from 'expo-linking'\nimport * as Crypto from 'expo-crypto'\nimport * as SecureStore from 'expo-secure-store'\nimport * as WebBrowser from 'expo-web-browser'\nimport {\n buildExpoOAuthStartUrl,\n codeFromExpoRedirect,\n normalizeExpoBaseUrl,\n stateFromExpoRedirect,\n} from './expo-url'\n\nexport type ExpoAuthProvider = 'google' | (string & {})\n\nexport interface DeepSpaceExpoSession {\n sessionToken: string\n accessToken: string\n}\n\nexport interface DeepSpaceExpoUser {\n userId: string\n claims: Record<string, unknown>\n}\n\nexport interface ExpoAuthStorage {\n getItem(key: string): Promise<string | null>\n setItem(key: string, value: string): Promise<void>\n deleteItem(key: string): Promise<void>\n}\n\nexport interface DeepSpaceExpoClientOptions {\n /** Deployed app origin, for example `https://example.app.space`. */\n baseUrl: string\n /** Defaults to Expo's `createURL('auth/callback')`. */\n redirectUri?: string\n /** SecureStore-backed by default; inject a test store when unit testing. */\n storage?: ExpoAuthStorage\n /** Namespaced storage key so multiple apps can share one device. */\n storageKey?: string\n /** Worker endpoint overrides for apps with a custom auth route. */\n paths?: Partial<ExpoAuthPaths>\n fetch?: typeof globalThis.fetch\n /** Abort stalled auth/API requests after this many milliseconds. */\n timeoutMs?: number\n}\n\nexport interface ExpoAuthPaths {\n start: string\n exchange: string\n token: string\n me: string\n signOut: string\n}\n\nexport class DeepSpaceExpoError extends Error {\n readonly status: number\n readonly body: unknown\n\n constructor(message: string, status: number, body: unknown) {\n super(message)\n this.name = 'DeepSpaceExpoError'\n this.status = status\n this.body = body\n }\n}\n\n/**\n * Thrown by `signIn` when the person closes the sign-in sheet. It isn't a\n * failure, so apps usually return to where they were without showing an error.\n * Check `code`: `instanceof` fails if the bundler includes two copies of\n * this package.\n */\nexport class DeepSpaceSignInCancelledError extends Error {\n readonly code = 'sign_in_cancelled' as const\n\n constructor() {\n super('DeepSpace sign-in was cancelled')\n this.name = 'DeepSpaceSignInCancelledError'\n }\n}\n\nconst DEFAULT_PATHS: ExpoAuthPaths = {\n start: '/api/auth/native-start',\n exchange: '/api/auth/native-exchange',\n token: '/api/auth/native-token',\n me: '/api/auth/native-me',\n signOut: '/api/auth/native-signout',\n}\n\nconst defaultStorage: ExpoAuthStorage = {\n getItem: (key) => SecureStore.getItemAsync(key),\n setItem: (key, value) => SecureStore.setItemAsync(key, value),\n deleteItem: (key) => SecureStore.deleteItemAsync(key),\n}\n\nexport function createDeepSpaceExpoClient(options: DeepSpaceExpoClientOptions): DeepSpaceExpoClient {\n return new DeepSpaceExpoClient(options)\n}\n\nexport class DeepSpaceExpoClient {\n private readonly baseUrl: string\n private readonly redirectUri: string\n private readonly storage: ExpoAuthStorage\n private readonly storageKey: string\n private readonly paths: ExpoAuthPaths\n private readonly fetcher: typeof globalThis.fetch\n private readonly timeoutMs: number\n private session: DeepSpaceExpoSession | null | undefined\n\n constructor(options: DeepSpaceExpoClientOptions) {\n if (!options.baseUrl) throw new Error('DeepSpace Expo client requires baseUrl')\n this.baseUrl = normalizeExpoBaseUrl(options.baseUrl)\n this.redirectUri = options.redirectUri ?? Linking.createURL('auth/callback')\n this.storage = options.storage ?? defaultStorage\n this.storageKey = options.storageKey ?? `deepspace.session.${encodeURIComponent(this.baseUrl)}`\n this.paths = { ...DEFAULT_PATHS, ...options.paths }\n this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis)\n this.timeoutMs = options.timeoutMs ?? 15_000\n }\n\n async getSession(): Promise<DeepSpaceExpoSession | null> {\n if (this.session !== undefined) return this.session\n const raw = await this.storage.getItem(this.storageKey)\n if (!raw) return (this.session = null)\n try {\n const value = JSON.parse(raw) as Partial<DeepSpaceExpoSession>\n if (typeof value.sessionToken !== 'string' || typeof value.accessToken !== 'string') throw new Error('invalid session')\n return (this.session = value as DeepSpaceExpoSession)\n } catch {\n await this.storage.deleteItem(this.storageKey)\n return (this.session = null)\n }\n }\n\n async signIn(provider: ExpoAuthProvider = 'google', state?: string): Promise<DeepSpaceExpoUser> {\n const expectedState = state ?? createOAuthState()\n const codeVerifier = createCodeVerifier()\n const codeChallenge = await createCodeChallenge(codeVerifier)\n const startUrl = buildExpoOAuthStartUrl(\n this.baseUrl,\n provider,\n this.redirectUri,\n expectedState,\n this.paths.start,\n codeChallenge,\n )\n const result = await WebBrowser.openAuthSessionAsync(startUrl, this.redirectUri)\n if (result.type === 'cancel' || result.type === 'dismiss') throw new DeepSpaceSignInCancelledError()\n if (result.type !== 'success' || !result.url) throw new Error('DeepSpace sign-in did not finish')\n const code = codeFromExpoRedirect(result.url)\n if (!code) throw new Error('DeepSpace sign-in returned no exchange code')\n if (stateFromExpoRedirect(result.url) !== expectedState) {\n throw new Error('DeepSpace sign-in returned an invalid state')\n }\n const session = await this.json<DeepSpaceExpoSession>(this.paths.exchange, {\n method: 'POST',\n body: JSON.stringify({ code, code_verifier: codeVerifier }),\n })\n await this.saveSession(session)\n return this.me()\n }\n\n signInWithGoogle(state?: string): Promise<DeepSpaceExpoUser> { return this.signIn('google', state) }\n\n async refresh(): Promise<string | null> {\n const session = await this.getSession()\n if (!session) return null\n try {\n const next = await this.json<{ accessToken?: string; token?: string }>(this.paths.token, { method: 'POST', body: JSON.stringify({ sessionToken: session.sessionToken }) })\n const accessToken = next.accessToken ?? next.token\n if (!accessToken) throw new Error('DeepSpace did not issue an access token')\n await this.saveSession({ ...session, accessToken })\n return accessToken\n } catch (error) {\n if (error instanceof DeepSpaceExpoError && error.status === 401) await this.clearSession()\n return null\n }\n }\n\n async getAuthToken(): Promise<string | null> {\n const session = await this.getSession()\n return session?.accessToken || (await this.refresh())\n }\n\n async me(): Promise<DeepSpaceExpoUser> {\n const result = await this.request<DeepSpaceExpoUser>(this.paths.me)\n return result\n }\n\n async request<T>(path: string, init: RequestInit = {}, retried = false): Promise<T> {\n const token = await this.getAuthToken()\n const headers = new Headers(init.headers)\n if (!headers.has('Content-Type') && init.body != null && isJsonBody(init.body)) {\n headers.set('Content-Type', 'application/json')\n }\n if (token) headers.set('Authorization', `Bearer ${token}`)\n const response = await this.fetchWithTimeout(`${this.baseUrl}${path}`, { ...init, headers })\n const body = await response.json().catch(() => ({})) as T & { error?: string }\n if (response.status === 401 && !retried && isReplayableBody(init.body) && (await this.getSession())?.sessionToken) {\n const refreshed = await this.refresh()\n if (refreshed) return this.request<T>(path, init, true)\n }\n if (!response.ok) throw new DeepSpaceExpoError(typeof body.error === 'string' ? body.error : `DeepSpace request failed (${response.status})`, response.status, body)\n return body\n }\n\n async signOut(): Promise<void> {\n const session = await this.getSession()\n if (session) {\n await this.fetchWithTimeout(`${this.baseUrl}${this.paths.signOut}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionToken: session.sessionToken }) }).catch(() => undefined)\n }\n await this.clearSession()\n }\n\n private async json<T>(path: string, init: RequestInit): Promise<T> {\n const response = await this.fetchWithTimeout(`${this.baseUrl}${path}`, { ...init, headers: { 'Content-Type': 'application/json', ...(init.headers ?? {}) } })\n const body = await response.json().catch(() => ({})) as T & { error?: string }\n if (!response.ok) throw new DeepSpaceExpoError(typeof body.error === 'string' ? body.error : `DeepSpace auth failed (${response.status})`, response.status, body)\n return body\n }\n\n private async saveSession(session: DeepSpaceExpoSession) { this.session = session; await this.storage.setItem(this.storageKey, JSON.stringify(session)) }\n private async clearSession() { this.session = null; await this.storage.deleteItem(this.storageKey) }\n\n private async fetchWithTimeout(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n if (!this.timeoutMs || this.timeoutMs <= 0) return this.fetcher(input, init)\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\n const signal = init.signal\n if (signal) {\n if (signal.aborted) controller.abort()\n else signal.addEventListener('abort', () => controller.abort(), { once: true })\n }\n try {\n return await this.fetcher(input, { ...init, signal: controller.signal })\n } finally {\n clearTimeout(timer)\n }\n }\n}\n\nexport {\n buildExpoOAuthStartUrl,\n codeFromExpoRedirect,\n normalizeExpoBaseUrl,\n stateFromExpoRedirect,\n}\n\nfunction createOAuthState(): string {\n return Crypto.randomUUID()\n}\n\nfunction createCodeVerifier(): string {\n return `${Crypto.randomUUID().replaceAll('-', '')}${Crypto.randomUUID().replaceAll('-', '')}`\n}\n\nasync function createCodeChallenge(verifier: string): Promise<string> {\n const digest = await Crypto.digestStringAsync(\n Crypto.CryptoDigestAlgorithm.SHA256,\n verifier,\n { encoding: Crypto.CryptoEncoding.BASE64 },\n )\n return digest.replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')\n}\n\nfunction isJsonBody(body: BodyInit): boolean {\n return typeof body === 'string'\n}\n\nfunction isReplayableBody(body: BodyInit | null | undefined): boolean {\n if (body == null || typeof body === 'string') return true\n if (typeof FormData !== 'undefined' && body instanceof FormData) return true\n if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) return true\n if (typeof Blob !== 'undefined' && body instanceof Blob) return true\n if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return true\n return false\n}\n","/** Pure URL helpers shared by the Expo auth client and its tests. */\nexport function buildExpoOAuthStartUrl(\n baseUrl: string,\n provider: string,\n redirectUri: string,\n state?: string,\n startPath = '/api/auth/native-start',\n codeChallenge?: string,\n): string {\n const url = new URL(startPath, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`)\n url.searchParams.set('provider', provider)\n url.searchParams.set('redirect_uri', redirectUri)\n if (state) url.searchParams.set('state', state)\n if (codeChallenge) {\n url.searchParams.set('code_challenge', codeChallenge)\n url.searchParams.set('code_challenge_method', 'S256')\n }\n return url.toString()\n}\n\nexport function codeFromExpoRedirect(url: string): string | null {\n try {\n return new URL(url).searchParams.get('code')\n } catch {\n return null\n }\n}\n\nexport function stateFromExpoRedirect(url: string): string | null {\n try {\n return new URL(url).searchParams.get('state')\n } catch {\n return null\n }\n}\n\nexport function normalizeExpoBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, '')\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/expo.ts","../src/expo-url.ts"],"sourcesContent":["/**\n * DeepSpace Expo client.\n *\n * This entry is intentionally separate from `deepspace`: the browser client\n * uses Better Auth cookies and browser globals, while an Expo app needs the\n * system browser, a custom-scheme callback, and SecureStore-backed sessions.\n * The Worker still owns auth and JWT issuance; this package only transports the\n * one-time OAuth code and refreshes the short-lived bearer.\n */\nimport * as Linking from 'expo-linking'\nimport * as Crypto from 'expo-crypto'\nimport * as SecureStore from 'expo-secure-store'\nimport * as WebBrowser from 'expo-web-browser'\nimport {\n buildExpoOAuthStartUrl,\n codeFromExpoRedirect,\n normalizeExpoBaseUrl,\n stateFromExpoRedirect,\n} from './expo-url'\n\nexport type ExpoAuthProvider = 'google' | (string & {})\n\nexport interface DeepSpaceExpoSession {\n sessionToken: string\n accessToken: string\n}\n\nexport interface DeepSpaceExpoUser {\n userId: string\n claims: Record<string, unknown>\n}\n\nexport interface ExpoAuthStorage {\n getItem(key: string): Promise<string | null>\n setItem(key: string, value: string): Promise<void>\n deleteItem(key: string): Promise<void>\n}\n\nexport interface DeepSpaceExpoClientOptions {\n /** Deployed app origin, for example `https://example.app.space`. */\n baseUrl: string\n /** Defaults to Expo's `createURL('auth/callback')`. */\n redirectUri?: string\n /** SecureStore-backed by default; inject a test store when unit testing. */\n storage?: ExpoAuthStorage\n /** Namespaced storage key so multiple apps can share one device. */\n storageKey?: string\n /** Worker endpoint overrides for apps with a custom auth route. */\n paths?: Partial<ExpoAuthPaths>\n fetch?: typeof globalThis.fetch\n /** Abort stalled auth/API requests after this many milliseconds. */\n timeoutMs?: number\n}\n\nexport interface ExpoAuthPaths {\n start: string\n exchange: string\n idToken: string\n token: string\n me: string\n signOut: string\n}\n\nexport interface NativeIdTokenOptions {\n /**\n * The raw nonce, if the app gave the provider one (Apple takes its SHA-256\n * as lowercase hex). DeepSpace checks it against the token. Replay is\n * stopped separately: each ID token signs in once.\n */\n nonce?: string\n /** Apple shares the person's name only on their first sign-in. */\n name?: { firstName?: string | null; lastName?: string | null }\n}\n\nexport class DeepSpaceExpoError extends Error {\n readonly status: number\n readonly body: unknown\n\n constructor(message: string, status: number, body: unknown) {\n super(message)\n this.name = 'DeepSpaceExpoError'\n this.status = status\n this.body = body\n }\n}\n\n/**\n * Thrown by `signIn` when the person closes the sign-in sheet. It isn't a\n * failure, so apps usually return to where they were without showing an error.\n * Check `code`: `instanceof` fails if the bundler includes two copies of\n * this package.\n */\nexport class DeepSpaceSignInCancelledError extends Error {\n readonly code = 'sign_in_cancelled' as const\n\n constructor() {\n super('DeepSpace sign-in was cancelled')\n this.name = 'DeepSpaceSignInCancelledError'\n }\n}\n\nconst DEFAULT_PATHS: ExpoAuthPaths = {\n start: '/api/auth/native-start',\n exchange: '/api/auth/native-exchange',\n idToken: '/api/auth/native-id-token',\n token: '/api/auth/native-token',\n me: '/api/auth/native-me',\n signOut: '/api/auth/native-signout',\n}\n\nconst defaultStorage: ExpoAuthStorage = {\n getItem: (key) => SecureStore.getItemAsync(key),\n setItem: (key, value) => SecureStore.setItemAsync(key, value),\n deleteItem: (key) => SecureStore.deleteItemAsync(key),\n}\n\nexport function createDeepSpaceExpoClient(options: DeepSpaceExpoClientOptions): DeepSpaceExpoClient {\n return new DeepSpaceExpoClient(options)\n}\n\nexport class DeepSpaceExpoClient {\n private readonly baseUrl: string\n private readonly redirectUri: string\n private readonly storage: ExpoAuthStorage\n private readonly storageKey: string\n private readonly paths: ExpoAuthPaths\n private readonly fetcher: typeof globalThis.fetch\n private readonly timeoutMs: number\n private session: DeepSpaceExpoSession | null | undefined\n\n constructor(options: DeepSpaceExpoClientOptions) {\n if (!options.baseUrl) throw new Error('DeepSpace Expo client requires baseUrl')\n this.baseUrl = normalizeExpoBaseUrl(options.baseUrl)\n this.redirectUri = options.redirectUri ?? Linking.createURL('auth/callback')\n this.storage = options.storage ?? defaultStorage\n this.storageKey = options.storageKey ?? `deepspace.session.${encodeURIComponent(this.baseUrl)}`\n this.paths = { ...DEFAULT_PATHS, ...options.paths }\n this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis)\n this.timeoutMs = options.timeoutMs ?? 15_000\n }\n\n async getSession(): Promise<DeepSpaceExpoSession | null> {\n if (this.session !== undefined) return this.session\n const raw = await this.storage.getItem(this.storageKey)\n if (!raw) return (this.session = null)\n try {\n const value = JSON.parse(raw) as Partial<DeepSpaceExpoSession>\n if (typeof value.sessionToken !== 'string' || typeof value.accessToken !== 'string') throw new Error('invalid session')\n return (this.session = value as DeepSpaceExpoSession)\n } catch {\n await this.storage.deleteItem(this.storageKey)\n return (this.session = null)\n }\n }\n\n async signIn(provider: ExpoAuthProvider = 'google', state?: string): Promise<DeepSpaceExpoUser> {\n const expectedState = state ?? createOAuthState()\n const codeVerifier = createCodeVerifier()\n const codeChallenge = await createCodeChallenge(codeVerifier)\n const startUrl = buildExpoOAuthStartUrl(\n this.baseUrl,\n provider,\n this.redirectUri,\n expectedState,\n this.paths.start,\n codeChallenge,\n )\n const result = await WebBrowser.openAuthSessionAsync(startUrl, this.redirectUri)\n if (result.type === 'cancel' || result.type === 'dismiss') throw new DeepSpaceSignInCancelledError()\n if (result.type !== 'success' || !result.url) throw new Error('DeepSpace sign-in did not finish')\n const code = codeFromExpoRedirect(result.url)\n if (!code) throw new Error('DeepSpace sign-in returned no exchange code')\n if (stateFromExpoRedirect(result.url) !== expectedState) {\n throw new Error('DeepSpace sign-in returned an invalid state')\n }\n const session = await this.json<DeepSpaceExpoSession>(this.paths.exchange, {\n method: 'POST',\n body: JSON.stringify({ code, code_verifier: codeVerifier }),\n })\n await this.saveSession(session)\n return this.me()\n }\n\n signInWithGoogle(state?: string): Promise<DeepSpaceExpoUser> { return this.signIn('google', state) }\n\n /**\n * Signs in with an ID token from the platform's own sign-in SDK (Google\n * Sign-In or Sign in with Apple), so the consent screen shows the app's\n * name. The worker must route `paths.idToken` to `nativeAuthIdToken`, and\n * the plane must list the app's client ids (see the Expo guide).\n */\n async signInWithIdToken(\n provider: 'google' | 'apple',\n idToken: string,\n options: NativeIdTokenOptions = {},\n ): Promise<DeepSpaceExpoUser> {\n const session = await this.json<DeepSpaceExpoSession>(this.paths.idToken, {\n method: 'POST',\n body: JSON.stringify({\n provider,\n idToken,\n ...(options.nonce ? { nonce: options.nonce } : {}),\n ...(options.name ? { user: { name: options.name } } : {}),\n }),\n })\n await this.saveSession(session)\n return this.me()\n }\n\n async refresh(): Promise<string | null> {\n const session = await this.getSession()\n if (!session) return null\n try {\n const next = await this.json<{ accessToken?: string; token?: string }>(this.paths.token, { method: 'POST', body: JSON.stringify({ sessionToken: session.sessionToken }) })\n const accessToken = next.accessToken ?? next.token\n if (!accessToken) throw new Error('DeepSpace did not issue an access token')\n await this.saveSession({ ...session, accessToken })\n return accessToken\n } catch (error) {\n if (error instanceof DeepSpaceExpoError && error.status === 401) await this.clearSession()\n return null\n }\n }\n\n async getAuthToken(): Promise<string | null> {\n const session = await this.getSession()\n return session?.accessToken || (await this.refresh())\n }\n\n async me(): Promise<DeepSpaceExpoUser> {\n const result = await this.request<DeepSpaceExpoUser>(this.paths.me)\n return result\n }\n\n async request<T>(path: string, init: RequestInit = {}, retried = false): Promise<T> {\n const token = await this.getAuthToken()\n const headers = new Headers(init.headers)\n if (!headers.has('Content-Type') && init.body != null && isJsonBody(init.body)) {\n headers.set('Content-Type', 'application/json')\n }\n if (token) headers.set('Authorization', `Bearer ${token}`)\n const response = await this.fetchWithTimeout(`${this.baseUrl}${path}`, { ...init, headers })\n const body = await response.json().catch(() => ({})) as T & { error?: string }\n if (response.status === 401 && !retried && isReplayableBody(init.body) && (await this.getSession())?.sessionToken) {\n const refreshed = await this.refresh()\n if (refreshed) return this.request<T>(path, init, true)\n }\n if (!response.ok) throw new DeepSpaceExpoError(typeof body.error === 'string' ? body.error : `DeepSpace request failed (${response.status})`, response.status, body)\n return body\n }\n\n async signOut(): Promise<void> {\n const session = await this.getSession()\n if (session) {\n await this.fetchWithTimeout(`${this.baseUrl}${this.paths.signOut}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionToken: session.sessionToken }) }).catch(() => undefined)\n }\n await this.clearSession()\n }\n\n private async json<T>(path: string, init: RequestInit): Promise<T> {\n const response = await this.fetchWithTimeout(`${this.baseUrl}${path}`, { ...init, headers: { 'Content-Type': 'application/json', ...(init.headers ?? {}) } })\n const body = await response.json().catch(() => ({})) as T & { error?: string }\n if (!response.ok) throw new DeepSpaceExpoError(typeof body.error === 'string' ? body.error : `DeepSpace auth failed (${response.status})`, response.status, body)\n return body\n }\n\n private async saveSession(session: DeepSpaceExpoSession) { this.session = session; await this.storage.setItem(this.storageKey, JSON.stringify(session)) }\n private async clearSession() { this.session = null; await this.storage.deleteItem(this.storageKey) }\n\n private async fetchWithTimeout(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n if (!this.timeoutMs || this.timeoutMs <= 0) return this.fetcher(input, init)\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\n const signal = init.signal\n if (signal) {\n if (signal.aborted) controller.abort()\n else signal.addEventListener('abort', () => controller.abort(), { once: true })\n }\n try {\n return await this.fetcher(input, { ...init, signal: controller.signal })\n } finally {\n clearTimeout(timer)\n }\n }\n}\n\nexport {\n buildExpoOAuthStartUrl,\n codeFromExpoRedirect,\n normalizeExpoBaseUrl,\n stateFromExpoRedirect,\n}\n\nfunction createOAuthState(): string {\n return Crypto.randomUUID()\n}\n\nfunction createCodeVerifier(): string {\n return `${Crypto.randomUUID().replaceAll('-', '')}${Crypto.randomUUID().replaceAll('-', '')}`\n}\n\nasync function createCodeChallenge(verifier: string): Promise<string> {\n const digest = await Crypto.digestStringAsync(\n Crypto.CryptoDigestAlgorithm.SHA256,\n verifier,\n { encoding: Crypto.CryptoEncoding.BASE64 },\n )\n return digest.replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')\n}\n\nfunction isJsonBody(body: BodyInit): boolean {\n return typeof body === 'string'\n}\n\nfunction isReplayableBody(body: BodyInit | null | undefined): boolean {\n if (body == null || typeof body === 'string') return true\n if (typeof FormData !== 'undefined' && body instanceof FormData) return true\n if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) return true\n if (typeof Blob !== 'undefined' && body instanceof Blob) return true\n if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return true\n return false\n}\n","/** Pure URL helpers shared by the Expo auth client and its tests. */\nexport function buildExpoOAuthStartUrl(\n baseUrl: string,\n provider: string,\n redirectUri: string,\n state?: string,\n startPath = '/api/auth/native-start',\n codeChallenge?: string,\n): string {\n const url = new URL(startPath, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`)\n url.searchParams.set('provider', provider)\n url.searchParams.set('redirect_uri', redirectUri)\n if (state) url.searchParams.set('state', state)\n if (codeChallenge) {\n url.searchParams.set('code_challenge', codeChallenge)\n url.searchParams.set('code_challenge_method', 'S256')\n }\n return url.toString()\n}\n\nexport function codeFromExpoRedirect(url: string): string | null {\n try {\n return new URL(url).searchParams.get('code')\n } catch {\n return null\n }\n}\n\nexport function stateFromExpoRedirect(url: string): string | null {\n try {\n return new URL(url).searchParams.get('state')\n } catch {\n return null\n }\n}\n\nexport function normalizeExpoBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, '')\n}\n"],"mappings":"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","names":[]}
|
package/dist/server.d.ts
CHANGED
|
@@ -2669,7 +2669,7 @@ declare function armCronRoom(ctx: {
|
|
|
2669
2669
|
* Persistent job queue with serial execution, progress, cancellation and retries.
|
|
2670
2670
|
* Jobs run inside alarms unless listed in `backgroundJobTypes`.
|
|
2671
2671
|
* Use `ctx.continue` to save a checkpoint for the next run.
|
|
2672
|
-
* After a restart, running jobs
|
|
2672
|
+
* After a restart, interrupted running jobs are retried or failed.
|
|
2673
2673
|
*/
|
|
2674
2674
|
|
|
2675
2675
|
type JobStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'canceled';
|
|
@@ -2793,7 +2793,7 @@ declare abstract class JobRoom<E = Record<string, unknown>, P = unknown, R = unk
|
|
|
2793
2793
|
private drainDueJobs;
|
|
2794
2794
|
private executeJob;
|
|
2795
2795
|
private makeContext;
|
|
2796
|
-
private
|
|
2796
|
+
private readRunState;
|
|
2797
2797
|
/** Reauthorize every live event so role revocation does not leave a stale reader. */
|
|
2798
2798
|
protected broadcast(message: ServerMessage, exclude?: WebSocket): void;
|
|
2799
2799
|
private resolveAccess;
|
|
@@ -3670,10 +3670,28 @@ interface DeepSpaceAuthConfig {
|
|
|
3670
3670
|
keyId: string;
|
|
3671
3671
|
privateKey: string;
|
|
3672
3672
|
};
|
|
3673
|
+
/**
|
|
3674
|
+
* Extra OAuth client ids whose ID tokens may sign in through Better Auth's
|
|
3675
|
+
* `POST /sign-in/social` `{ provider, idToken }`: a native app's own Google
|
|
3676
|
+
* iOS/web client ids, or its iOS bundle id for Sign in with Apple. Tokens
|
|
3677
|
+
* are still checked for Google's or Apple's signature, issuer and expiry.
|
|
3678
|
+
* A provider listed here without its web credentials above accepts ID
|
|
3679
|
+
* tokens only; it can't start a browser sign-in.
|
|
3680
|
+
*/
|
|
3681
|
+
nativeIdTokenAudiences?: {
|
|
3682
|
+
google?: string[];
|
|
3683
|
+
apple?: string[];
|
|
3684
|
+
};
|
|
3673
3685
|
/** Enable email/password authentication */
|
|
3674
3686
|
emailAndPassword?: boolean;
|
|
3675
3687
|
/** Trusted origins for CORS */
|
|
3676
3688
|
trustedOrigins?: string[];
|
|
3689
|
+
/**
|
|
3690
|
+
* Where a failed OAuth callback lands when the flow can't be tied back to
|
|
3691
|
+
* its own error URL (for example its state expired). Without it Better Auth
|
|
3692
|
+
* shows its own developer-facing error page.
|
|
3693
|
+
*/
|
|
3694
|
+
errorURL?: string;
|
|
3677
3695
|
/**
|
|
3678
3696
|
* Server-owned fields to add to Better Auth's user model. Use `input: false`
|
|
3679
3697
|
* for values that may only be populated by a database hook, and
|
|
@@ -4714,8 +4732,11 @@ declare function createDeepSpaceAuth(config: DeepSpaceAuthConfig): better_auth.A
|
|
|
4714
4732
|
};
|
|
4715
4733
|
};
|
|
4716
4734
|
} | undefined;
|
|
4717
|
-
socialProviders: Parameters<typeof betterAuth>[0]["socialProviders"];
|
|
4718
4735
|
trustedOrigins: string[];
|
|
4736
|
+
onAPIError?: {
|
|
4737
|
+
errorURL: string;
|
|
4738
|
+
} | undefined;
|
|
4739
|
+
socialProviders: Parameters<typeof betterAuth>[0]["socialProviders"];
|
|
4719
4740
|
user?: {
|
|
4720
4741
|
additionalFields: {
|
|
4721
4742
|
[x: string]: better_auth.DBFieldAttribute;
|
package/dist/server.js
CHANGED
|
@@ -7384,7 +7384,7 @@ var JobRoom = class extends BaseRoom {
|
|
|
7384
7384
|
`);
|
|
7385
7385
|
this.sql.exec(`CREATE INDEX IF NOT EXISTS jobs_status_next_run ON jobs (status, next_run_at)`);
|
|
7386
7386
|
this.sql.exec(`CREATE INDEX IF NOT EXISTS jobs_completed_at ON jobs (completed_at)`);
|
|
7387
|
-
this.recoverStuckRunning();
|
|
7387
|
+
this.recoverStuckRunning(true);
|
|
7388
7388
|
this.scheduleNextAlarm();
|
|
7389
7389
|
}
|
|
7390
7390
|
// ==========================================================================
|
|
@@ -7630,8 +7630,7 @@ var JobRoom = class extends BaseRoom {
|
|
|
7630
7630
|
}
|
|
7631
7631
|
async executeJob(row) {
|
|
7632
7632
|
const startedAt = row.started_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
7633
|
-
const
|
|
7634
|
-
const attempt = attemptsBefore + 1;
|
|
7633
|
+
const attempt = row.attempts + 1;
|
|
7635
7634
|
this.sql.exec(
|
|
7636
7635
|
`UPDATE jobs SET status = 'running', started_at = ?, attempts = ? WHERE id = ?`,
|
|
7637
7636
|
startedAt,
|
|
@@ -7651,7 +7650,7 @@ var JobRoom = class extends BaseRoom {
|
|
|
7651
7650
|
let result;
|
|
7652
7651
|
let errorMessage;
|
|
7653
7652
|
try {
|
|
7654
|
-
const ctx = this.makeContext(row.id, controller.signal);
|
|
7653
|
+
const ctx = this.makeContext(row.id, attempt, controller.signal);
|
|
7655
7654
|
result = await this.onJob(job, ctx);
|
|
7656
7655
|
if (this.continueState) outcome = "continued";
|
|
7657
7656
|
} catch (e) {
|
|
@@ -7661,8 +7660,8 @@ var JobRoom = class extends BaseRoom {
|
|
|
7661
7660
|
} finally {
|
|
7662
7661
|
this.inFlight.delete(row.id);
|
|
7663
7662
|
}
|
|
7664
|
-
const
|
|
7665
|
-
if (
|
|
7663
|
+
const current = this.readRunState(row.id);
|
|
7664
|
+
if (current?.status !== "running" || current.attempts !== attempt) {
|
|
7666
7665
|
this.continueState = null;
|
|
7667
7666
|
return;
|
|
7668
7667
|
}
|
|
@@ -7741,9 +7740,11 @@ var JobRoom = class extends BaseRoom {
|
|
|
7741
7740
|
const updated = this.getJobById(row.id);
|
|
7742
7741
|
if (updated) this.broadcast(serverBuild.jobFailed(updated));
|
|
7743
7742
|
}
|
|
7744
|
-
makeContext(jobId, signal) {
|
|
7743
|
+
makeContext(jobId, attempt, signal) {
|
|
7745
7744
|
return {
|
|
7746
7745
|
progress: (value, message) => {
|
|
7746
|
+
const current = this.readRunState(jobId);
|
|
7747
|
+
if (current?.status !== "running" || current.attempts !== attempt) return;
|
|
7747
7748
|
const clamped = Math.max(0, Math.min(1, value));
|
|
7748
7749
|
this.sql.exec(
|
|
7749
7750
|
`UPDATE jobs SET progress = ?, progress_message = ? WHERE id = ?`,
|
|
@@ -7763,9 +7764,9 @@ var JobRoom = class extends BaseRoom {
|
|
|
7763
7764
|
signal
|
|
7764
7765
|
};
|
|
7765
7766
|
}
|
|
7766
|
-
|
|
7767
|
-
const row = this.sql.exec(`SELECT status FROM jobs WHERE id = ?`, jobId).toArray()[0];
|
|
7768
|
-
return row
|
|
7767
|
+
readRunState(jobId) {
|
|
7768
|
+
const row = this.sql.exec(`SELECT status, attempts FROM jobs WHERE id = ?`, jobId).toArray()[0];
|
|
7769
|
+
return row ?? null;
|
|
7769
7770
|
}
|
|
7770
7771
|
/** Reauthorize every live event so role revocation does not leave a stale reader. */
|
|
7771
7772
|
broadcast(message, exclude) {
|
|
@@ -7823,11 +7824,12 @@ var JobRoom = class extends BaseRoom {
|
|
|
7823
7824
|
if (targets.length === 0) return;
|
|
7824
7825
|
this.state.storage.setAlarm(Math.max(Math.min(...targets), now + 50));
|
|
7825
7826
|
}
|
|
7826
|
-
recoverStuckRunning() {
|
|
7827
|
+
recoverStuckRunning(onStartup = false) {
|
|
7827
7828
|
const cutoff = new Date(Date.now() - RUNNING_STALE_MS).toISOString();
|
|
7828
7829
|
const stuck = this.sql.exec(
|
|
7829
7830
|
`SELECT id, attempts, max_attempts FROM jobs
|
|
7830
|
-
WHERE status = 'running' AND (started_at IS NULL OR started_at <= ?)`,
|
|
7831
|
+
WHERE status = 'running' AND (? = 1 OR started_at IS NULL OR started_at <= ?)`,
|
|
7832
|
+
onStartup ? 1 : 0,
|
|
7831
7833
|
cutoff
|
|
7832
7834
|
).toArray();
|
|
7833
7835
|
for (const r of stuck) {
|
|
@@ -8791,8 +8793,12 @@ import { organization, twoFactor } from "better-auth/plugins";
|
|
|
8791
8793
|
import { SignJWT, importPKCS8 } from "jose";
|
|
8792
8794
|
function createDeepSpaceAuth(config) {
|
|
8793
8795
|
const socialProviders = {};
|
|
8796
|
+
const googleAudiences = config.nativeIdTokenAudiences?.google ?? [];
|
|
8797
|
+
const appleAudiences = config.nativeIdTokenAudiences?.apple ?? [];
|
|
8794
8798
|
if (config.google) {
|
|
8795
|
-
socialProviders.google = config.google;
|
|
8799
|
+
socialProviders.google = googleAudiences.length ? { ...config.google, clientId: [config.google.clientId, ...googleAudiences] } : config.google;
|
|
8800
|
+
} else if (googleAudiences.length) {
|
|
8801
|
+
socialProviders.google = { clientId: googleAudiences, clientSecret: "" };
|
|
8796
8802
|
}
|
|
8797
8803
|
if (config.github) {
|
|
8798
8804
|
socialProviders.github = config.github;
|
|
@@ -8802,16 +8808,27 @@ function createDeepSpaceAuth(config) {
|
|
|
8802
8808
|
}
|
|
8803
8809
|
if (config.apple) {
|
|
8804
8810
|
const apple = config.apple;
|
|
8811
|
+
const audience = appleAudiences.length ? [apple.clientId, ...appleAudiences] : void 0;
|
|
8805
8812
|
socialProviders.apple = async () => {
|
|
8806
8813
|
try {
|
|
8807
|
-
return {
|
|
8814
|
+
return {
|
|
8815
|
+
clientId: apple.clientId,
|
|
8816
|
+
clientSecret: await generateAppleClientSecret(apple),
|
|
8817
|
+
...audience ? { audience } : {}
|
|
8818
|
+
};
|
|
8808
8819
|
} catch (err) {
|
|
8809
8820
|
console.error(
|
|
8810
8821
|
`[deepspace] failed to mint Apple client secret; Apple sign-in disabled: ${loggableError(err)}`
|
|
8811
8822
|
);
|
|
8812
|
-
return { clientId: apple.clientId, clientSecret: "" };
|
|
8823
|
+
return { clientId: apple.clientId, clientSecret: "", ...audience ? { audience } : {} };
|
|
8813
8824
|
}
|
|
8814
8825
|
};
|
|
8826
|
+
} else if (appleAudiences.length) {
|
|
8827
|
+
socialProviders.apple = {
|
|
8828
|
+
clientId: appleAudiences[0],
|
|
8829
|
+
clientSecret: "",
|
|
8830
|
+
audience: appleAudiences
|
|
8831
|
+
};
|
|
8815
8832
|
}
|
|
8816
8833
|
return betterAuth({
|
|
8817
8834
|
database: config.database,
|
|
@@ -8832,6 +8849,7 @@ function createDeepSpaceAuth(config) {
|
|
|
8832
8849
|
},
|
|
8833
8850
|
...config.userAdditionalFields ? { user: { additionalFields: config.userAdditionalFields } } : {},
|
|
8834
8851
|
socialProviders,
|
|
8852
|
+
...config.errorURL ? { onAPIError: { errorURL: config.errorURL } } : {},
|
|
8835
8853
|
trustedOrigins: config.trustedOrigins ?? [
|
|
8836
8854
|
"https://deep.space",
|
|
8837
8855
|
"https://*.deep.space",
|