deepspace 0.36.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 +6 -0
- package/dist/{chunk-C6TBE6C4.js → chunk-2CXYYP3K.js} +2 -2
- package/dist/{chunk-C6TBE6C4.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 +12 -0
- package/dist/server.js +18 -3
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +24 -1
- package/dist/worker.js +70 -5
- package/dist/worker.js.map +1 -1
- package/package.json +1 -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
|
@@ -3670,6 +3670,18 @@ 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 */
|
package/dist/server.js
CHANGED
|
@@ -8793,8 +8793,12 @@ import { organization, twoFactor } from "better-auth/plugins";
|
|
|
8793
8793
|
import { SignJWT, importPKCS8 } from "jose";
|
|
8794
8794
|
function createDeepSpaceAuth(config) {
|
|
8795
8795
|
const socialProviders = {};
|
|
8796
|
+
const googleAudiences = config.nativeIdTokenAudiences?.google ?? [];
|
|
8797
|
+
const appleAudiences = config.nativeIdTokenAudiences?.apple ?? [];
|
|
8796
8798
|
if (config.google) {
|
|
8797
|
-
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: "" };
|
|
8798
8802
|
}
|
|
8799
8803
|
if (config.github) {
|
|
8800
8804
|
socialProviders.github = config.github;
|
|
@@ -8804,16 +8808,27 @@ function createDeepSpaceAuth(config) {
|
|
|
8804
8808
|
}
|
|
8805
8809
|
if (config.apple) {
|
|
8806
8810
|
const apple = config.apple;
|
|
8811
|
+
const audience = appleAudiences.length ? [apple.clientId, ...appleAudiences] : void 0;
|
|
8807
8812
|
socialProviders.apple = async () => {
|
|
8808
8813
|
try {
|
|
8809
|
-
return {
|
|
8814
|
+
return {
|
|
8815
|
+
clientId: apple.clientId,
|
|
8816
|
+
clientSecret: await generateAppleClientSecret(apple),
|
|
8817
|
+
...audience ? { audience } : {}
|
|
8818
|
+
};
|
|
8810
8819
|
} catch (err) {
|
|
8811
8820
|
console.error(
|
|
8812
8821
|
`[deepspace] failed to mint Apple client secret; Apple sign-in disabled: ${loggableError(err)}`
|
|
8813
8822
|
);
|
|
8814
|
-
return { clientId: apple.clientId, clientSecret: "" };
|
|
8823
|
+
return { clientId: apple.clientId, clientSecret: "", ...audience ? { audience } : {} };
|
|
8815
8824
|
}
|
|
8816
8825
|
};
|
|
8826
|
+
} else if (appleAudiences.length) {
|
|
8827
|
+
socialProviders.apple = {
|
|
8828
|
+
clientId: appleAudiences[0],
|
|
8829
|
+
clientSecret: "",
|
|
8830
|
+
audience: appleAudiences
|
|
8831
|
+
};
|
|
8817
8832
|
}
|
|
8818
8833
|
return betterAuth({
|
|
8819
8834
|
database: config.database,
|