@marktiderman/genesis-supabase 0.2.0 → 0.3.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 +367 -23
- package/dist/blocks.d.ts +19 -0
- package/dist/blocks.js +3 -0
- package/dist/blocks.js.map +1 -0
- package/dist/chunk-FAA2FTF4.js +110 -0
- package/dist/chunk-FAA2FTF4.js.map +1 -0
- package/dist/chunk-T2HGFC4C.js +37 -0
- package/dist/chunk-T2HGFC4C.js.map +1 -0
- package/dist/index-BSGz3RGk.d.ts +317 -0
- package/dist/index.d.ts +4 -103
- package/dist/index.js +60 -3
- package/dist/index.js.map +1 -1
- package/dist/native.d.ts +90 -6
- package/dist/native.js +70 -2
- package/dist/native.js.map +1 -1
- package/functions/genesis-apple-revoke/apple.ts +237 -0
- package/functions/genesis-apple-revoke/handler.ts +227 -0
- package/functions/genesis-apple-revoke/index.ts +55 -0
- package/functions/genesis-push-send/expo.ts +117 -0
- package/functions/genesis-push-send/handler.ts +462 -0
- package/functions/genesis-push-send/index.ts +54 -0
- package/functions/genesis-support-cleanup/handler.ts +80 -0
- package/functions/genesis-support-cleanup/index.ts +39 -0
- package/functions/genesis-support-intake/contract.ts +193 -0
- package/functions/genesis-support-intake/handler.ts +170 -0
- package/functions/genesis-support-intake/index.ts +47 -0
- package/functions/genesis-support-notify/handler.ts +167 -0
- package/functions/genesis-support-notify/index.ts +15 -0
- package/functions/tests/genesis-apple-revoke_test.ts +81 -0
- package/functions/tests/genesis-push-send_test.ts +53 -0
- package/migrations/0003_genesis_apple_token_revocation.sql +205 -0
- package/migrations/0004_genesis_tenant_features.sql +333 -0
- package/migrations/0005_genesis_push.sql +524 -0
- package/migrations/0006_genesis_support.sql +846 -0
- package/migrations/tests/0003_genesis_apple_token_revocation.test.sql +230 -0
- package/migrations/tests/0004_genesis_tenant_features.test.sql +418 -0
- package/migrations/tests/0005_genesis_push.test.sql +407 -0
- package/migrations/tests/0006_genesis_support.test.sql +552 -0
- package/package.json +91 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAwCO,SAAS,qBAAA,CAA0C;AAAA,EACxD,GAAA;AAAA,EACA,cAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,qBAAqB,QAAA,KAAa,KAAA;AAAA,EAClC,UAAU;AACZ,CAAA,EAAqD;AAGnD,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,GAAA,EAAK,cAAA,EAAgB;AAAA,IAC/C,GAAG,OAAA;AAAA,IACH,IAAA,EAAM;AAAA,MACJ,QAAA,EAAU,MAAA;AAAA,MACV,cAAA,EAAgB,IAAA;AAAA,MAChB,gBAAA,EAAkB,IAAA;AAAA,MAClB,kBAAA;AAAA,MACA,GAAI,OAAA,GAAU,EAAE,OAAA,KAAY,EAAC;AAAA,MAC7B,GAAG,OAAA,CAAQ;AAAA;AACb,GACD,CAAA;AACD,EAAA,OAAO,MAAA;AACT;AAkBO,IAAM,kBAAA,GAAN,cAAiC,KAAA,CAAM;AAAA,EACnC,IAAA,GAAO,oBAAA;AAAA,EACP,IAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACT,YAAY,KAAA,EAA2B;AACrC,IAAA,KAAA,CAAM,KAAA,CAAM,OAAA,EAAS,EAAE,KAAA,EAAO,OAAO,CAAA;AACrC,IAAA,IAAA,CAAK,OAAO,KAAA,CAAM,IAAA;AAClB,IAAA,IAAA,CAAK,UAAU,KAAA,CAAM,OAAA;AACrB,IAAA,IAAA,CAAK,OAAO,KAAA,CAAM,IAAA;AAAA,EACpB;AACF;AASO,SAAS,KAAQ,MAAA,EAAiF;AACvG,EAAA,IAAI,OAAO,KAAA,EAAO,MAAM,IAAI,kBAAA,CAAmB,OAAO,KAAK,CAAA;AAC3D,EAAA,OAAO,MAAA,CAAO,IAAA;AAChB;AAuBA,eAAsB,gBAAgB,MAAA,EAA4C;AAChF,EAAA,IAAA,CAAM,MAAM,MAAA,CAAO,GAAA,CAAI,mBAAmB,CAAgF,CAAA;AAE1H,EAAA,MAAM,OAAO,IAAA,CAAK,OAAA,CAAQ,EAAE,KAAA,EAAO,SAAS,CAAA;AAC9C","file":"index.js","sourcesContent":["/**\n * Supabase for Genesis apps. Platform-neutral: web, React Native and the\n * server import it. Native-only helpers (Keychain storage, foreground\n * refresh, reinstall wipe) are on `@marktiderman/genesis-supabase/native`.\n */\nimport { createClient, type SupabaseClient, type SupabaseClientOptions } from \"@supabase/supabase-js\";\n\n/** The async key-value store Supabase auth keeps its session in. */\nexport interface SessionStorage {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\n/** @stability Beta */\nexport interface GenesisSupabaseOptions {\n url: string;\n /** The publishable (anon) key. Never the secret key in an app. */\n publishableKey: string;\n /** Where the session lives. Native: `keychainStorage(SecureStore)`. Web: leave out for supabase-js's own default. */\n storage?: SessionStorage;\n /** `\"native\"` sets the React Native defaults (no session from the URL). Default `\"web\"`. */\n platform?: \"web\" | \"native\";\n /**\n * Read a session from the URL after an OAuth redirect. Default: `true` on\n * web, `false` on native, where the app hands the redirect URL to\n * `exchangeCodeForSession` itself.\n */\n detectSessionInUrl?: boolean;\n /** Anything else for supabase-js, merged last. */\n options?: SupabaseClientOptions<\"public\">;\n}\n\n/**\n * A Supabase client with Genesis's defaults: PKCE sign-in (the code flow, not\n * tokens in a redirect URL), a persisted session that refreshes itself, and\n * the session storage you pass.\n *\n * @stability Beta\n */\nexport function createGenesisSupabase<Database = unknown>({\n url,\n publishableKey,\n storage,\n platform = \"web\",\n detectSessionInUrl = platform === \"web\",\n options = {},\n}: GenesisSupabaseOptions): SupabaseClient<Database> {\n // supabase-js's generics over the schema don't survive being forwarded, so the\n // options are built untyped and the client is typed by the caller's Database.\n const client = createClient(url, publishableKey, {\n ...options,\n auth: {\n flowType: \"pkce\",\n persistSession: true,\n autoRefreshToken: true,\n detectSessionInUrl,\n ...(storage ? { storage } : {}),\n ...options.auth,\n },\n });\n return client as unknown as SupabaseClient<Database>;\n}\n\n/** What a Supabase or PostgREST error carries. */\nexport interface PostgrestErrorLike {\n message: string;\n code?: string;\n details?: string | null;\n hint?: string | null;\n}\n\n/**\n * A failed query, with the database's `code` (`23505` unique violation,\n * `42501` permission denied, `PGRST116` no rows…), `details` and `hint`, so a\n * caller can tell an RLS refusal from a conflict. The original error is the\n * `cause`.\n *\n * @stability Beta\n */\nexport class SupabaseQueryError extends Error {\n override name = \"SupabaseQueryError\";\n readonly code?: string;\n readonly details?: string | null;\n readonly hint?: string | null;\n constructor(error: PostgrestErrorLike) {\n super(error.message, { cause: error });\n this.code = error.code;\n this.details = error.details;\n this.hint = error.hint;\n }\n}\n\n/**\n * The data, or a thrown {@link SupabaseQueryError}: for query results where an\n * error should fail the caller (a TanStack Query `queryFn`). The return type\n * keeps `null` when the query can return no row (`.maybeSingle()`).\n *\n * @stability Beta\n */\nexport function must<T>(result: { data: T; error: null } | { data: null; error: PostgrestErrorLike }): T {\n if (result.error) throw new SupabaseQueryError(result.error);\n return result.data as T;\n}\n\n/** The parts of a Supabase client {@link deleteMyAccount} uses. A `SupabaseClient` fits. */\nexport interface DeleteAccountClient {\n // Loose on purpose: a client typed by an app's Database narrows `rpc`'s\n // function names, and the app's generated types may not list this one yet.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rpc: (fn: any, ...rest: any[]) => PromiseLike<{ data: unknown; error: PostgrestErrorLike | null }>;\n auth: { signOut(options?: { scope?: \"global\" | \"local\" | \"others\" }): Promise<unknown> };\n}\n\n/**\n * In-app account deletion (App Store Review Guideline 5.1.1(v)), on web and\n * native: calls the `delete_my_account` RPC (this package's\n * `migrations/0001_genesis_account_deletion.sql`), which deletes the login\n * and, through the app's `on delete cascade` foreign keys, its data; then\n * clears the session on this device. Throws a {@link SupabaseQueryError} when\n * the database refuses (`42501`: not signed in) and keeps the session then.\n *\n * Put it behind a confirmation; see the README.\n *\n * @stability Beta\n */\nexport async function deleteMyAccount(client: DeleteAccountClient): Promise<void> {\n must((await client.rpc(\"delete_my_account\")) as { data: unknown; error: null } | { data: null; error: PostgrestErrorLike });\n // The login is gone and its refresh tokens with it: only this device's copy is left to clear.\n await client.auth.signOut({ scope: \"local\" });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/apple-tokens.ts","../src/tenant-features.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * The app's side of Sign in with Apple token revocation: hand the sign-in's\n * authorization code to the `genesis-apple-revoke` Edge Function (it keeps\n * Apple's refresh token server-side), and ask it to revoke after the account\n * is deleted. The function and its migration ship in this package; see the\n * README (\"Revoking Apple's tokens\").\n */\n\n/** The Edge Function's default name, as shipped in `functions/genesis-apple-revoke`. */\nexport const APPLE_REVOKE_FUNCTION = \"genesis-apple-revoke\";\n\n/** The store call waits on Apple's token endpoint (10 s timeout in the function). */\nconst STORE_TIMEOUT_MS = 20_000;\n/** The function answers the revoke call at once and works in the background; don't hold the delete screen for longer. */\nconst REVOKE_TIMEOUT_MS = 5_000;\n\n/** The parts of a Supabase client these helpers use. A `SupabaseClient` fits. */\nexport interface FunctionsClient {\n functions: {\n // Loose on purpose, so any supabase-js version's `invoke` fits.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n invoke: (name: string, options?: any) => PromiseLike<{ data: unknown; error: unknown }>;\n };\n}\n\n/** @stability Beta */\nexport interface StoreAppleTokenOptions {\n /** `authorizationCode` from `signInWithApple`'s result. Single-use, valid for five minutes: send it straight after sign-in. */\n authorizationCode: string;\n /** The client id the code was issued for: your bundle id on iOS. Must be in the function's APPLE_CLIENT_IDS; default its first. */\n clientId?: string;\n /** The Edge Function's name. Default `genesis-apple-revoke`. */\n functionName?: string;\n}\n\n/** @stability Beta */\nexport type StoreAppleTokenResult = { ok: true } | { ok: false; error: Error };\n\n/**\n * After a Sign in with Apple, sends the authorization code to the Edge\n * Function, which exchanges it for Apple's refresh token and keeps it where\n * only the server reads it, so deleting the account can revoke it. Call it\n * with the session from that sign-in. Never throws: the person is signed in\n * either way, so log a failure rather than showing it.\n *\n * @stability Beta\n */\nexport async function storeAppleToken(client: FunctionsClient, { authorizationCode, clientId, functionName = APPLE_REVOKE_FUNCTION }: StoreAppleTokenOptions): Promise<StoreAppleTokenResult> {\n try {\n const { error } = await client.functions.invoke(functionName, { body: { action: \"store\", code: authorizationCode, ...(clientId ? { clientId } : {}) }, timeout: STORE_TIMEOUT_MS });\n if (error) return { ok: false, error: error instanceof Error ? error : new Error(String((error as { message?: unknown }).message ?? error)) };\n return { ok: true };\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };\n }\n}\n\n/**\n * Asks the Edge Function to revoke the tokens of deleted accounts now, rather\n * than at its next scheduled run. Never throws and never fails the caller:\n * the account is already gone and the queue keeps the token until a run\n * succeeds.\n */\nexport async function requestAppleRevocation(client: FunctionsClient, functionName = APPLE_REVOKE_FUNCTION): Promise<boolean> {\n try {\n const { error } = await client.functions.invoke(functionName, { body: { action: \"revoke\" }, timeout: REVOKE_TIMEOUT_MS });\n return !error;\n } catch {\n return false;\n }\n}\n","/**\n * Tenant features: the client half of `migrations/0004_genesis_tenant_features.sql`.\n * A tenant's admins turn a shipped feature off (or back on) for their tenant,\n * with a reason; its members read which ids are off and hide them.\n *\n * Exposure only: a switch hides a feature, it never grants or denies access\n * to data (that is RLS) and never sells it (that is an entitlement).\n */\n// A cycle with ./index, used only inside the functions.\nimport { must, type PostgrestErrorLike } from \"./index\";\n\n/** The parts of a Supabase client these helpers use. A `SupabaseClient` fits. */\nexport interface TenantFeaturesClient {\n // Loose on purpose: a client typed by an app's Database narrows `rpc`'s\n // function names, and the app's generated types may not list these yet.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rpc: (fn: any, ...rest: any[]) => PromiseLike<{ data: unknown; error: PostgrestErrorLike | null }>;\n}\n\ntype RpcResult = { data: unknown; error: null } | { data: null; error: PostgrestErrorLike };\n\n/** Which features a tenant has turned off. A feature not in `off` is on. @stability Beta */\nexport interface TenantFeatures {\n readonly tenantId: string;\n readonly off: ReadonlySet<string>;\n isOn(featureId: string): boolean;\n}\n\n/**\n * The features `tenantId` has turned off, for one of its members (or\n * admins): `tenant_features_off`. Throws a `SupabaseQueryError` (`42501`)\n * for anyone else, so a refusal is never mistaken for \"everything on\".\n *\n * ```ts\n * const features = await tenantFeatures(supabase, teamId);\n * if (features.isOn(\"chat\")) showChatTab();\n * ```\n *\n * @stability Beta\n */\nexport async function tenantFeatures(client: TenantFeaturesClient, tenantId: string): Promise<TenantFeatures> {\n const data = must((await client.rpc(\"tenant_features_off\", { tenant: tenantId })) as RpcResult);\n const off = new Set(Array.isArray(data) ? data.filter((id): id is string => typeof id === \"string\") : []);\n return Object.freeze({ tenantId, off, isOn: (featureId: string) => !off.has(featureId) });\n}\n\n/** @stability Beta */\nexport interface SetTenantFeatureInput {\n tenantId: string;\n featureId: string;\n enabled: boolean;\n /** Why, in at most 500 characters. Required: it goes in the history. */\n reason: string;\n}\n\n/**\n * Turn a feature on or off for a tenant: `set_tenant_feature`. Only the\n * tenant's admins (the app's `private.genesis_tenant_admin` hook); throws a\n * `SupabaseQueryError` otherwise (`42501`), for a blank reason (`22023`) or\n * a malformed feature id (`23514`).\n *\n * @stability Beta\n */\nexport async function setTenantFeature(client: TenantFeaturesClient, input: SetTenantFeatureInput): Promise<void> {\n must(\n (await client.rpc(\"set_tenant_feature\", {\n tenant: input.tenantId,\n feature: input.featureId,\n enabled: input.enabled,\n reason: input.reason,\n })) as RpcResult,\n );\n}\n\n/** One switch a tenant has set. @stability Beta */\nexport interface TenantFeatureSetting {\n feature_id: string;\n enabled: boolean;\n reason: string;\n updated_by: string | null;\n updated_at: string;\n}\n\n/** Every switch a tenant has set, with its reason, for its admins: `tenant_feature_settings`. @stability Beta */\nexport async function tenantFeatureSettings(client: TenantFeaturesClient, tenantId: string): Promise<TenantFeatureSetting[]> {\n return (must((await client.rpc(\"tenant_feature_settings\", { tenant: tenantId })) as RpcResult) ?? []) as TenantFeatureSetting[];\n}\n\n/** One change to a tenant's switches. `enabled` is null when a switch row was deleted (back to on). @stability Beta */\nexport interface TenantFeatureChange {\n feature_id: string;\n enabled: boolean | null;\n reason: string | null;\n /** The person, or null for the service role, a migration, or a person since deleted. */\n changed_by: string | null;\n changed_role: string;\n changed_at: string;\n}\n\n/** A tenant's history of changes, newest first (at most `maxRows`, 1 to 1000, default 100), for its admins. @stability Beta */\nexport async function tenantFeatureHistory(\n client: TenantFeaturesClient,\n tenantId: string,\n options: { maxRows?: number } = {},\n): Promise<TenantFeatureChange[]> {\n const args = options.maxRows === undefined ? { tenant: tenantId } : { tenant: tenantId, max_rows: options.maxRows };\n return (must((await client.rpc(\"tenant_feature_history\", args)) as RpcResult) ?? []) as TenantFeatureChange[];\n}\n","/**\n * Supabase for Genesis apps. Platform-neutral: web, React Native and the\n * server import it. Native-only helpers (Keychain storage, foreground\n * refresh, reinstall wipe) are on `@marktiderman/genesis-supabase/native`.\n */\nimport { AuthInvalidJwtError, createClient, type SupabaseClient, type SupabaseClientOptions } from \"@supabase/supabase-js\";\nimport { APPLE_REVOKE_FUNCTION, requestAppleRevocation, type FunctionsClient } from \"./apple-tokens\";\nimport { assertPkceCrypto, claimsViaServer, installWebCrypto, isDigestOnlyPolyfill, type ExpoCryptoLike } from \"./web-crypto\";\n\nexport type { ExpoCryptoLike } from \"./web-crypto\";\nexport {\n APPLE_REVOKE_FUNCTION,\n requestAppleRevocation,\n storeAppleToken,\n type FunctionsClient,\n type StoreAppleTokenOptions,\n type StoreAppleTokenResult,\n} from \"./apple-tokens\";\n\n/** The async key-value store Supabase auth keeps its session in. */\nexport interface SessionStorage {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\n/** @stability Beta */\nexport interface GenesisSupabaseOptions {\n url: string;\n /** The publishable (anon) key. Never the secret key in an app. */\n publishableKey: string;\n /** Where the session lives. Native: `keychainStorage(SecureStore)`. Web: leave out for supabase-js's own default. */\n storage?: SessionStorage;\n /** `\"native\"` sets the React Native defaults (no session from the URL). Default `\"web\"`. */\n platform?: \"web\" | \"native\";\n /**\n * Read a session from the URL after an OAuth redirect. Default: `true` on\n * web, `false` on native, where the app hands the redirect URL to\n * `exchangeCodeForSession` itself.\n */\n detectSessionInUrl?: boolean;\n /**\n * `expo-crypto` (`import * as ExpoCrypto from \"expo-crypto\"`), on native.\n * Hermes has no `crypto.subtle`, and without it supabase-js sends the PKCE\n * challenge as \"plain\"; this installs `crypto.getRandomValues` and a SHA-256\n * `crypto.subtle.digest` from it (real WebCrypto is kept), so the challenge\n * is S256. A PKCE client made where there is neither throws in development\n * and logs an error in a release build. With the polyfill in place,\n * `auth.getClaims` always has the server check the token (`getUser`), as\n * supabase-js does where WebCrypto is missing: the polyfill can hash but\n * not verify a signature.\n */\n crypto?: ExpoCryptoLike;\n /** Anything else for supabase-js, merged last. */\n options?: SupabaseClientOptions<\"public\">;\n}\n\n/**\n * A Supabase client with Genesis's defaults: PKCE sign-in (the code flow, not\n * tokens in a redirect URL, with an S256 challenge: pass `crypto` on native),\n * a persisted session that refreshes itself, and\n * the session storage you pass.\n *\n * @stability Beta\n */\nexport function createGenesisSupabase<Database = unknown>({\n url,\n publishableKey,\n storage,\n platform = \"web\",\n detectSessionInUrl = platform === \"web\",\n crypto,\n options = {},\n}: GenesisSupabaseOptions): SupabaseClient<Database> {\n if (crypto) installWebCrypto(crypto);\n // The PKCE flow needs WebCrypto for an S256 challenge; never let it fall back to \"plain\" quietly.\n if ((options.auth?.flowType ?? \"pkce\") === \"pkce\") assertPkceCrypto();\n // supabase-js's generics over the schema don't survive being forwarded, so the\n // options are built untyped and the client is typed by the caller's Database.\n const client = createClient(url, publishableKey, {\n ...options,\n auth: {\n flowType: \"pkce\",\n persistSession: true,\n autoRefreshToken: true,\n detectSessionInUrl,\n ...(storage ? { storage } : {}),\n ...options.auth,\n },\n });\n if (isDigestOnlyPolyfill()) {\n const auth = client.auth;\n auth.getClaims = claimsViaServer(auth, (message) => new AuthInvalidJwtError(message)) as typeof auth.getClaims;\n }\n return client as unknown as SupabaseClient<Database>;\n}\n\n/** What a Supabase or PostgREST error carries. */\nexport interface PostgrestErrorLike {\n message: string;\n code?: string;\n details?: string | null;\n hint?: string | null;\n}\n\n/**\n * A failed query, with the database's `code` (`23505` unique violation,\n * `42501` permission denied, `PGRST116` no rows…), `details` and `hint`, so a\n * caller can tell an RLS refusal from a conflict. The original error is the\n * `cause`.\n *\n * @stability Beta\n */\nexport class SupabaseQueryError extends Error {\n override name = \"SupabaseQueryError\";\n readonly code?: string;\n readonly details?: string | null;\n readonly hint?: string | null;\n constructor(error: PostgrestErrorLike) {\n super(error.message, { cause: error });\n this.code = error.code;\n this.details = error.details;\n this.hint = error.hint;\n }\n}\n\n/**\n * The data, or a thrown {@link SupabaseQueryError}: for query results where an\n * error should fail the caller (a TanStack Query `queryFn`). The return type\n * keeps `null` when the query can return no row (`.maybeSingle()`).\n *\n * @stability Beta\n */\nexport function must<T>(result: { data: T; error: null } | { data: null; error: PostgrestErrorLike }): T {\n if (result.error) throw new SupabaseQueryError(result.error);\n return result.data as T;\n}\n\n/** The parts of a Supabase client {@link deleteMyAccount} uses. A `SupabaseClient` fits. */\nexport interface DeleteAccountClient {\n // Loose on purpose: a client typed by an app's Database narrows `rpc`'s\n // function names, and the app's generated types may not list this one yet.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rpc: (fn: any, ...rest: any[]) => PromiseLike<{ data: unknown; error: PostgrestErrorLike | null }>;\n auth: { signOut(options?: { scope?: \"global\" | \"local\" | \"others\" }): Promise<unknown> };\n /** Needed only with `revokeAppleTokens`. */\n functions?: FunctionsClient[\"functions\"];\n}\n\n/** @stability Beta */\nexport interface DeleteMyAccountOptions {\n /**\n * After the delete, ask the `genesis-apple-revoke` Edge Function to revoke\n * the person's Sign in with Apple tokens now (`true`, or the function's\n * name). Best effort: the delete has already queued them and the\n * function's schedule retries, so a failure here never fails the call.\n */\n revokeAppleTokens?: boolean | string;\n}\n\n/**\n * In-app account deletion (App Store Review Guideline 5.1.1(v)), on web and\n * native: calls the `delete_my_account` RPC (this package's\n * `migrations/0001_genesis_account_deletion.sql`), which deletes the login\n * and, through the app's `on delete cascade` foreign keys, its data; then\n * clears the session on this device. Throws a {@link SupabaseQueryError} when\n * the database refuses (`42501`: not signed in) and keeps the session then.\n *\n * With Sign in with Apple, ship the `genesis-apple-revoke` function and\n * migration 0003 and pass `{ revokeAppleTokens: true }`: App Review requires\n * the person's Apple tokens to be revoked.\n *\n * Put it behind a confirmation; see the README.\n *\n * @stability Beta\n */\nexport async function deleteMyAccount(client: DeleteAccountClient, options: DeleteMyAccountOptions = {}): Promise<void> {\n must((await client.rpc(\"delete_my_account\")) as { data: unknown; error: null } | { data: null; error: PostgrestErrorLike });\n const revoke = options.revokeAppleTokens;\n // The function needs no login (it works through the queue the delete just filled), so this runs after it.\n if (revoke && client.functions) await requestAppleRevocation({ functions: client.functions }, typeof revoke === \"string\" ? revoke : APPLE_REVOKE_FUNCTION);\n // The login is gone and its refresh tokens with it: only this device's copy is left to clear.\n await client.auth.signOut({ scope: \"local\" });\n}\nexport {\n tenantFeatures,\n setTenantFeature,\n tenantFeatureSettings,\n tenantFeatureHistory,\n type SetTenantFeatureInput,\n type TenantFeatureChange,\n type TenantFeatureSetting,\n type TenantFeatures,\n type TenantFeaturesClient,\n} from \"./tenant-features\";\nexport { accountBlock, tenantFeaturesBlock } from \"./blocks\";\n"]}
|
package/dist/native.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { SessionStorage } from './index.js';
|
|
1
|
+
import { S as SessionStorage } from './index-BSGz3RGk.js';
|
|
2
|
+
export { E as ExpoCryptoLike, W as WebCryptoSource, a as assertPkceCrypto, h as hasPkceCrypto, i as installWebCrypto, b as isDigestOnlyPolyfill } from './index-BSGz3RGk.js';
|
|
2
3
|
import { Session, SupabaseClient, User } from '@supabase/supabase-js';
|
|
4
|
+
import './blocks.js';
|
|
5
|
+
import '@marktiderman/genesis-features/blocks';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
|
-
* Native sign-in onto a Supabase session: finishing an OAuth redirect (PKCE)
|
|
6
|
-
*
|
|
8
|
+
* Native sign-in onto a Supabase session: finishing an OAuth redirect (PKCE),
|
|
9
|
+
* Sign in with Apple and native Google sign-in. Nothing here imports Expo or
|
|
10
|
+
* the Google module: pass the modules in.
|
|
7
11
|
* Exported from `@marktiderman/genesis-supabase/native`.
|
|
8
12
|
*/
|
|
9
13
|
|
|
@@ -12,7 +16,8 @@ import { Session, SupabaseClient, User } from '@supabase/supabase-js';
|
|
|
12
16
|
* (`access_denied`…), Supabase Auth's error code, or one of Genesis's own:
|
|
13
17
|
* `missing_code` (the redirect carried neither a code nor an error),
|
|
14
18
|
* `oauth_error`, `exchange_failed`, `apple_unavailable`, `apple_failed`,
|
|
15
|
-
* `apple_no_token`, `
|
|
19
|
+
* `apple_no_token`, `google_failed`, `google_no_token`,
|
|
20
|
+
* `google_nonce_missing`, `bad_digest`, `sign_in_failed`, `no_session`.
|
|
16
21
|
*
|
|
17
22
|
* @stability Beta
|
|
18
23
|
*/
|
|
@@ -102,7 +107,12 @@ interface SignInWithAppleOptions {
|
|
|
102
107
|
* Apple gets the hash; Supabase gets the raw nonce and checks the token against it.
|
|
103
108
|
*/
|
|
104
109
|
digest(value: string): Promise<string>;
|
|
105
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* A fresh random nonce. Default: 32 bytes from `crypto.getRandomValues`, as
|
|
112
|
+
* hex. On React Native that is the expo-crypto polyfill
|
|
113
|
+
* `createGenesisSupabase({ platform: "native", crypto })` installs; leave
|
|
114
|
+
* this out rather than pass something with less randomness.
|
|
115
|
+
*/
|
|
106
116
|
randomNonce?: () => string;
|
|
107
117
|
}
|
|
108
118
|
/** @stability Beta */
|
|
@@ -141,6 +151,80 @@ type SignInWithAppleResult = {
|
|
|
141
151
|
* @stability Beta
|
|
142
152
|
*/
|
|
143
153
|
declare function signInWithApple(client: SignInClient, apple: AppleAuthenticationLike, { digest, randomNonce }: SignInWithAppleOptions): Promise<SignInWithAppleResult>;
|
|
154
|
+
/**
|
|
155
|
+
* The part of `@react-native-google-signin/google-signin` that
|
|
156
|
+
* {@link signInWithGoogle} uses: its Universal sign-in module,
|
|
157
|
+
* `GoogleOneTapSignIn`, configured (`configure({ webClientId, iosClientId })`)
|
|
158
|
+
* before the first sign-in. Pass the module itself.
|
|
159
|
+
*
|
|
160
|
+
* Only Universal sign-in passes a nonce into the ID token on Android and iOS.
|
|
161
|
+
* The free package's original `GoogleSignin.signIn` can't (its `nonce` is iOS
|
|
162
|
+
* and paid-version only), so its tokens fail the nonce check below.
|
|
163
|
+
*/
|
|
164
|
+
interface GoogleSignInLike {
|
|
165
|
+
authenticate(params?: {
|
|
166
|
+
nonce?: string;
|
|
167
|
+
}): Promise<{
|
|
168
|
+
user?: {
|
|
169
|
+
idToken?: string | null;
|
|
170
|
+
user?: {
|
|
171
|
+
email?: string | null;
|
|
172
|
+
name?: string | null;
|
|
173
|
+
} | null;
|
|
174
|
+
} | null;
|
|
175
|
+
error?: {
|
|
176
|
+
code?: string | number;
|
|
177
|
+
message?: string;
|
|
178
|
+
} | null;
|
|
179
|
+
isCancelled?: boolean;
|
|
180
|
+
}>;
|
|
181
|
+
}
|
|
182
|
+
/** @stability Beta */
|
|
183
|
+
interface SignInWithGoogleOptions {
|
|
184
|
+
/** SHA-256 of a string as lowercase hex, as for {@link signInWithApple}. Google gets the hash; Supabase gets the raw nonce. */
|
|
185
|
+
digest(value: string): Promise<string>;
|
|
186
|
+
/** A fresh random nonce. Default: 32 bytes from `crypto.getRandomValues`, as hex. */
|
|
187
|
+
randomNonce?: () => string;
|
|
188
|
+
}
|
|
189
|
+
/** @stability Beta */
|
|
190
|
+
type SignInWithGoogleResult = {
|
|
191
|
+
session: Session;
|
|
192
|
+
user: User;
|
|
193
|
+
/** The name Google shared, if any. */
|
|
194
|
+
fullName: string | null;
|
|
195
|
+
email: string | null;
|
|
196
|
+
canceled?: undefined;
|
|
197
|
+
error?: undefined;
|
|
198
|
+
} | {
|
|
199
|
+
canceled: true;
|
|
200
|
+
session?: undefined;
|
|
201
|
+
error?: undefined;
|
|
202
|
+
} | {
|
|
203
|
+
error: GenesisAuthError;
|
|
204
|
+
session?: undefined;
|
|
205
|
+
canceled?: undefined;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Native Google sign-in straight into a Supabase session, as Supabase's
|
|
209
|
+
* Google guide describes: a fresh random nonce, its SHA-256 (hex) to Google,
|
|
210
|
+
* which puts it in the ID token, and the raw nonce to `signInWithIdToken`,
|
|
211
|
+
* which checks the two match, so a token minted for another sign-in, which
|
|
212
|
+
* carries that sign-in's nonce, can't be replayed through this app. Before
|
|
213
|
+
* Supabase sees the token it is checked here too: a token whose `nonce` claim
|
|
214
|
+
* isn't this sign-in's hash (a module that ignored the nonce) is
|
|
215
|
+
* `google_nonce_missing`, never sent. Closing Google's sheet is
|
|
216
|
+
* `{ canceled: true }`. Nothing is thrown; every failure is `{ error }`.
|
|
217
|
+
*
|
|
218
|
+
* Keep the Supabase Google provider's "Skip nonce check" off: this helper
|
|
219
|
+
* needs no exception, and turning it on lets a stolen nonce-bearing ID token
|
|
220
|
+
* sign in. What a nonce can't stop: Supabase still accepts an ID token with
|
|
221
|
+
* no nonce claim when the caller sends none, so a stolen nonce-less token for
|
|
222
|
+
* one of your client ids signs in through a direct API call whatever the app
|
|
223
|
+
* does. Keep the "Authorized Client IDs" list to your own apps.
|
|
224
|
+
*
|
|
225
|
+
* @stability Beta
|
|
226
|
+
*/
|
|
227
|
+
declare function signInWithGoogle(client: SignInClient, google: GoogleSignInLike, { digest, randomNonce }: SignInWithGoogleOptions): Promise<SignInWithGoogleResult>;
|
|
144
228
|
/** "Given Middle Family", or null when Apple shared no name. */
|
|
145
229
|
declare function formatFullName(name: AppleFullNameLike | null | undefined): string | null;
|
|
146
230
|
|
|
@@ -225,4 +309,4 @@ declare function wipeSessionOnReinstall({ session, sessionKey, installMarker, ma
|
|
|
225
309
|
/** Split text into pieces of at most `maxBytes` UTF-8 bytes, never inside a character. */
|
|
226
310
|
declare function splitUtf8(text: string, maxBytes: number): string[];
|
|
227
311
|
|
|
228
|
-
export { type AppStateLike, type AppleAuthenticationLike, type AppleFullNameLike, type AutoRefreshAuth, GenesisAuthError, type KeychainStorageOptions, type OAuthRedirectResult, type SecureStoreLike, type SignInClient, type SignInWithAppleOptions, type SignInWithAppleResult, type WipeOnReinstallOptions, completeOAuthRedirect, formatFullName, keychainStorage, redirectParams, refreshWhileActive, signInWithApple, splitUtf8, wipeSessionOnReinstall };
|
|
312
|
+
export { type AppStateLike, type AppleAuthenticationLike, type AppleFullNameLike, type AutoRefreshAuth, GenesisAuthError, type GoogleSignInLike, type KeychainStorageOptions, type OAuthRedirectResult, type SecureStoreLike, type SignInClient, type SignInWithAppleOptions, type SignInWithAppleResult, type SignInWithGoogleOptions, type SignInWithGoogleResult, type WipeOnReinstallOptions, completeOAuthRedirect, formatFullName, keychainStorage, redirectParams, refreshWhileActive, signInWithApple, signInWithGoogle, splitUtf8, wipeSessionOnReinstall };
|
package/dist/native.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { assertPkceCrypto, hasPkceCrypto, installWebCrypto, isDigestOnlyPolyfill } from './chunk-FAA2FTF4.js';
|
|
2
|
+
|
|
1
3
|
// src/sign-in.ts
|
|
2
4
|
var GenesisAuthError = class extends Error {
|
|
3
5
|
name = "GenesisAuthError";
|
|
@@ -115,13 +117,79 @@ async function signInWithApple(client, apple, { digest, randomNonce = defaultNon
|
|
|
115
117
|
return { error: new GenesisAuthError("sign_in_failed", e instanceof Error ? e.message : "Sign-in failed", { cause: e }), fullName };
|
|
116
118
|
}
|
|
117
119
|
}
|
|
120
|
+
async function signInWithGoogle(client, google, { digest, randomNonce = defaultNonce }) {
|
|
121
|
+
let nonce;
|
|
122
|
+
let hashed;
|
|
123
|
+
let response;
|
|
124
|
+
try {
|
|
125
|
+
nonce = randomNonce();
|
|
126
|
+
hashed = await digest(nonce);
|
|
127
|
+
if (!/^[0-9a-f]{64}$/.test(hashed)) {
|
|
128
|
+
return { error: new GenesisAuthError("bad_digest", "signInWithGoogle: digest must return SHA-256 as 64 lowercase hex characters") };
|
|
129
|
+
}
|
|
130
|
+
response = await google.authenticate({ nonce: hashed });
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return { error: new GenesisAuthError("google_failed", e instanceof Error ? e.message : "Google sign-in failed", { cause: e }) };
|
|
133
|
+
}
|
|
134
|
+
if (response.isCancelled) return { canceled: true };
|
|
135
|
+
if (response.error) {
|
|
136
|
+
return { error: new GenesisAuthError("google_failed", response.error.message ?? `Google sign-in failed (${String(response.error.code ?? "unknown")})`, { cause: response.error }) };
|
|
137
|
+
}
|
|
138
|
+
const token = response.user?.idToken;
|
|
139
|
+
if (!token) return { error: new GenesisAuthError("google_no_token", "Google returned no ID token") };
|
|
140
|
+
if (jwtClaim(token, "nonce") !== hashed) {
|
|
141
|
+
return {
|
|
142
|
+
error: new GenesisAuthError(
|
|
143
|
+
"google_nonce_missing",
|
|
144
|
+
"Google's ID token doesn't carry this sign-in's nonce. Use the Universal sign-in module (GoogleOneTapSignIn) of @react-native-google-signin/google-signin; the original GoogleSignin can't pass one."
|
|
145
|
+
)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const { data, error } = await client.auth.signInWithIdToken({ provider: "google", token, nonce });
|
|
150
|
+
if (error) return { error: new GenesisAuthError(error.code ?? "sign_in_failed", error.message, { cause: error }) };
|
|
151
|
+
if (!data.session || !data.user) return { error: new GenesisAuthError("no_session", "Sign-in returned no session") };
|
|
152
|
+
const profile = response.user?.user;
|
|
153
|
+
return { session: data.session, user: data.user, fullName: profile?.name?.trim() || null, email: profile?.email ?? null };
|
|
154
|
+
} catch (e) {
|
|
155
|
+
return { error: new GenesisAuthError("sign_in_failed", e instanceof Error ? e.message : "Sign-in failed", { cause: e }) };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function jwtClaim(token, claim) {
|
|
159
|
+
const payload = token.split(".")[1];
|
|
160
|
+
if (!payload) return void 0;
|
|
161
|
+
try {
|
|
162
|
+
const value = JSON.parse(decodeBase64Url(payload))?.[claim];
|
|
163
|
+
return typeof value === "string" ? value : void 0;
|
|
164
|
+
} catch {
|
|
165
|
+
return void 0;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
169
|
+
function decodeBase64Url(input) {
|
|
170
|
+
let escaped = "";
|
|
171
|
+
let buffer = 0;
|
|
172
|
+
let bits = 0;
|
|
173
|
+
for (const ch of input.replace(/=+$/, "")) {
|
|
174
|
+
const v = B64.indexOf(ch === "+" ? "-" : ch === "/" ? "_" : ch);
|
|
175
|
+
if (v < 0) throw new Error("not base64url");
|
|
176
|
+
buffer = buffer << 6 | v;
|
|
177
|
+
bits += 6;
|
|
178
|
+
if (bits >= 8) {
|
|
179
|
+
bits -= 8;
|
|
180
|
+
escaped += `%${(buffer >> bits & 255).toString(16).padStart(2, "0")}`;
|
|
181
|
+
buffer &= (1 << bits) - 1;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return decodeURIComponent(escaped);
|
|
185
|
+
}
|
|
118
186
|
function formatFullName(name) {
|
|
119
187
|
if (!name) return null;
|
|
120
188
|
return [name.givenName, name.middleName, name.familyName].map((p) => p?.trim()).filter(Boolean).join(" ") || null;
|
|
121
189
|
}
|
|
122
190
|
function defaultNonce() {
|
|
123
191
|
const c = globalThis.crypto;
|
|
124
|
-
if (!c?.getRandomValues) throw new Error("[genesis]
|
|
192
|
+
if (!c?.getRandomValues) throw new Error("[genesis] sign-in nonce: no crypto.getRandomValues here; install react-native-get-random-values or expo-crypto's getRandomValues polyfill, or pass randomNonce returning 32 random bytes");
|
|
125
193
|
return Array.from(c.getRandomValues(new Uint8Array(32)), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
126
194
|
}
|
|
127
195
|
|
|
@@ -233,6 +301,6 @@ function parseFlowIndex(raw) {
|
|
|
233
301
|
}
|
|
234
302
|
}
|
|
235
303
|
|
|
236
|
-
export { GenesisAuthError, completeOAuthRedirect, formatFullName, keychainStorage, redirectParams, refreshWhileActive, signInWithApple, splitUtf8, wipeSessionOnReinstall };
|
|
304
|
+
export { GenesisAuthError, completeOAuthRedirect, formatFullName, keychainStorage, redirectParams, refreshWhileActive, signInWithApple, signInWithGoogle, splitUtf8, wipeSessionOnReinstall };
|
|
237
305
|
//# sourceMappingURL=native.js.map
|
|
238
306
|
//# sourceMappingURL=native.js.map
|
package/dist/native.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sign-in.ts","../src/native.ts"],"names":[],"mappings":"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","file":"native.js","sourcesContent":["/**\n * Native sign-in onto a Supabase session: finishing an OAuth redirect (PKCE)\n * and Sign in with Apple. Nothing here imports Expo: pass the modules in.\n * Exported from `@marktiderman/genesis-supabase/native`.\n */\nimport type { Session, SupabaseClient, User } from \"@supabase/supabase-js\";\n\n/**\n * Why a sign-in didn't produce a session. `code` is the provider's error\n * (`access_denied`…), Supabase Auth's error code, or one of Genesis's own:\n * `missing_code` (the redirect carried neither a code nor an error),\n * `oauth_error`, `exchange_failed`, `apple_unavailable`, `apple_failed`,\n * `apple_no_token`, `bad_digest`, `sign_in_failed`, `no_session`.\n *\n * @stability Beta\n */\nexport class GenesisAuthError extends Error {\n override name = \"GenesisAuthError\";\n readonly code: string;\n /**\n * The provider's own `error_description`, as it came in the redirect.\n * UNTRUSTED: any app or web page can open your callback link with any text\n * here, so never show it as an official message. Log it; show `message`.\n */\n readonly providerMessage?: string;\n constructor(code: string, message: string, options: { cause?: unknown; providerMessage?: string } = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.code = code;\n if (options.providerMessage !== undefined) this.providerMessage = options.providerMessage;\n }\n}\n\n/** Fixed, safe copy for the redirect errors an app is likely to see; anything else gets the fallback. */\nconst REDIRECT_MESSAGES: Record<string, string> = {\n access_denied: \"Sign-in was canceled or not allowed.\",\n otp_expired: \"That sign-in link has expired. Ask for a new one.\",\n flow_state_expired: \"That sign-in took too long. Try again.\",\n flow_state_not_found: \"That sign-in has already been used or has expired. Try again.\",\n bad_oauth_state: \"That sign-in couldn't be checked. Try again.\",\n bad_oauth_callback: \"That sign-in couldn't be checked. Try again.\",\n provider_email_needs_verification: \"Verify your email with the provider, then try again.\",\n server_error: \"The sign-in service had a problem. Try again.\",\n temporarily_unavailable: \"The sign-in service is busy. Try again in a moment.\",\n};\nconst REDIRECT_FALLBACK = \"Sign-in didn't complete. Try again.\";\n\n/** The parts of a Supabase client the sign-in helpers use. A `SupabaseClient` fits. */\nexport interface SignInClient {\n auth: Pick<SupabaseClient[\"auth\"], \"exchangeCodeForSession\" | \"signInWithIdToken\">;\n}\n\n/** @stability Beta */\nexport type OAuthRedirectResult =\n | {\n session: Session;\n /** `\"recovery\"` after a password-reset link (send them to set a new password), otherwise `null`. */\n redirectType: string | null;\n error?: undefined;\n }\n | { session?: undefined; redirectType?: undefined; error: GenesisAuthError };\n\nconst hasOwn = (o: object, key: string) => Object.prototype.hasOwnProperty.call(o, key);\n\n/**\n * The query and fragment parameters of a URL (the query wins for a key in\n * both). Hand-parsed: React Native's `URL` has no reliable `searchParams`.\n */\nexport function redirectParams(url: string): Record<string, string> {\n const params: Record<string, string> = Object.create(null);\n const hash = url.indexOf(\"#\");\n const query = url.indexOf(\"?\");\n const parts = [\n query >= 0 && (hash < 0 || query < hash) ? url.slice(query + 1, hash >= 0 ? hash : undefined) : \"\",\n hash >= 0 ? url.slice(hash + 1) : \"\",\n ];\n for (const part of parts) {\n for (const pair of part.split(\"&\")) {\n if (!pair) continue;\n const eq = pair.indexOf(\"=\");\n const key = decode(eq >= 0 ? pair.slice(0, eq) : pair);\n if (!hasOwn(params, key)) params[key] =decode(eq >= 0 ? pair.slice(eq + 1) : \"\");\n }\n }\n return params;\n}\n\nfunction decode(s: string): string {\n try {\n return decodeURIComponent(s.replace(/\\+/g, \" \"));\n } catch {\n return s;\n }\n}\n\n/**\n * Finish an OAuth sign-in from the URL the browser came back to. With a `code`\n * it exchanges it for a session (PKCE: the verifier is already in the client's\n * storage), passing the redirect's `sb_flow_id` so the right flow's verifier\n * is used; with an `error` it returns that error and makes no request.\n * Nothing is thrown; every failure is `{ error }`.\n *\n * Pass only the URL `WebBrowser.openAuthSessionAsync` resolved with, for the\n * sign-in this app just started. A code is single-use and the exchange uses\n * up the stored verifier whatever happens, so a stray `?code=` from another\n * link spends the sign-in in progress, and handling the same redirect twice\n * (the auth-session result and a `Linking` listener) fails the second time.\n * Handle each redirect once.\n *\n * The error `message` is fixed copy chosen by `code`; the redirect's own text\n * is only in `providerMessage`, which is untrusted.\n *\n * @stability Beta\n */\nexport async function completeOAuthRedirect(client: SignInClient, url: string): Promise<OAuthRedirectResult> {\n const params = redirectParams(url);\n if (params.error || params.error_description || params.error_code) {\n const code = params.error_code || params.error || \"oauth_error\";\n const message = REDIRECT_MESSAGES[code] ?? (params.error ? REDIRECT_MESSAGES[params.error] : undefined) ?? REDIRECT_FALLBACK;\n return { error: new GenesisAuthError(code, message, { providerMessage: params.error_description || undefined }) };\n }\n if (!params.code) return { error: new GenesisAuthError(\"missing_code\", \"The sign-in redirect carried no code\") };\n try {\n const { data, error } = await client.auth.exchangeCodeForSession(\n params.code,\n params.sb_flow_id ? { flowId: params.sb_flow_id } : undefined,\n );\n if (error) return { error: new GenesisAuthError(error.code ?? \"exchange_failed\", error.message, { cause: error }) };\n if (!data.session) return { error: new GenesisAuthError(\"no_session\", \"The code exchange returned no session\") };\n // supabase-js returns redirectType at run time without typing it.\n const redirectType = (data as { redirectType?: string | null }).redirectType ?? null;\n return { session: data.session, redirectType };\n } catch (e) {\n return { error: new GenesisAuthError(\"exchange_failed\", e instanceof Error ? e.message : \"The code exchange failed\", { cause: e }) };\n }\n}\n\n/** A person's name as `expo-apple-authentication` returns it. */\nexport interface AppleFullNameLike {\n givenName?: string | null;\n middleName?: string | null;\n familyName?: string | null;\n}\n\n/** The parts of `expo-apple-authentication` that {@link signInWithApple} uses. Pass the module itself. */\nexport interface AppleAuthenticationLike {\n isAvailableAsync(): Promise<boolean>;\n signInAsync(options: { requestedScopes?: number[]; nonce?: string }): Promise<{\n identityToken: string | null;\n /** Single-use, valid about five minutes: what a server exchanges for the Apple refresh token it revokes on deletion. */\n authorizationCode: string | null;\n fullName: AppleFullNameLike | null;\n email: string | null;\n }>;\n AppleAuthenticationScope: { FULL_NAME: number; EMAIL: number };\n}\n\n/** @stability Beta */\nexport interface SignInWithAppleOptions {\n /**\n * SHA-256 of a string as lowercase hex, e.g.\n * `(s) => Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, s)` from `expo-crypto`.\n * Apple gets the hash; Supabase gets the raw nonce and checks the token against it.\n */\n digest(value: string): Promise<string>;\n /** A fresh random nonce. Default: 32 bytes from `crypto.getRandomValues`, as hex (pass one, e.g. `Crypto.randomUUID`, where that's missing). */\n randomNonce?: () => string;\n}\n\n/** @stability Beta */\nexport type SignInWithAppleResult =\n | {\n session: Session;\n user: User;\n /** The name Apple shared. Apple sends it on the first sign-in only: save it then. */\n fullName: string | null;\n /** The email Apple shared (possibly a private relay address), on the first sign-in only. */\n email: string | null;\n /**\n * Apple's single-use authorization code (valid about five minutes). Send it\n * to your server straight away: it exchanges it for the Apple refresh token\n * that account deletion must revoke. See the README.\n */\n authorizationCode: string | null;\n canceled?: undefined;\n error?: undefined;\n }\n | { canceled: true; session?: undefined; error?: undefined }\n | {\n error: GenesisAuthError;\n /** When Apple shared a name but Supabase then refused: Apple won't send it again, so keep it for the retry. */\n fullName?: string | null;\n session?: undefined;\n canceled?: undefined;\n };\n\n/**\n * Native Sign in with Apple straight into a Supabase session: checks the\n * device supports it, asks for name and email with a SHA-256 hashed nonce, and\n * signs in with the identity token. Closing Apple's sheet is `{ canceled: true }`,\n * not an error. Nothing is thrown; every failure is `{ error }`.\n *\n * @stability Beta\n */\nexport async function signInWithApple(\n client: SignInClient,\n apple: AppleAuthenticationLike,\n { digest, randomNonce = defaultNonce }: SignInWithAppleOptions,\n): Promise<SignInWithAppleResult> {\n const unavailable = (cause?: unknown) => ({\n error: new GenesisAuthError(\"apple_unavailable\", \"Sign in with Apple isn't available on this device\", { cause }),\n });\n try {\n if (!(await apple.isAvailableAsync())) return unavailable();\n } catch (e) {\n return unavailable(e);\n }\n let credential: Awaited<ReturnType<AppleAuthenticationLike[\"signInAsync\"]>>;\n let nonce: string;\n try {\n nonce = randomNonce();\n const hashed = await digest(nonce);\n if (!/^[0-9a-f]{64}$/.test(hashed)) {\n return { error: new GenesisAuthError(\"bad_digest\", \"signInWithApple: digest must return SHA-256 as 64 lowercase hex characters\") };\n }\n credential = await apple.signInAsync({\n requestedScopes: [apple.AppleAuthenticationScope.FULL_NAME, apple.AppleAuthenticationScope.EMAIL],\n nonce: hashed,\n });\n } catch (e) {\n if ((e as { code?: unknown } | null)?.code === \"ERR_REQUEST_CANCELED\") return { canceled: true };\n return { error: new GenesisAuthError(\"apple_failed\", e instanceof Error ? e.message : \"Sign in with Apple failed\", { cause: e }) };\n }\n const fullName = formatFullName(credential.fullName);\n if (!credential.identityToken) return { error: new GenesisAuthError(\"apple_no_token\", \"Apple returned no identity token\"), fullName };\n try {\n const { data, error } = await client.auth.signInWithIdToken({ provider: \"apple\", token: credential.identityToken, nonce });\n if (error) return { error: new GenesisAuthError(error.code ?? \"sign_in_failed\", error.message, { cause: error }), fullName };\n if (!data.session || !data.user) return { error: new GenesisAuthError(\"no_session\", \"Sign-in returned no session\"), fullName };\n return {\n session: data.session,\n user: data.user,\n fullName,\n email: credential.email ?? null,\n authorizationCode: credential.authorizationCode ?? null,\n };\n } catch (e) {\n return { error: new GenesisAuthError(\"sign_in_failed\", e instanceof Error ? e.message : \"Sign-in failed\", { cause: e }), fullName };\n }\n}\n\n/** \"Given Middle Family\", or null when Apple shared no name. */\nexport function formatFullName(name: AppleFullNameLike | null | undefined): string | null {\n if (!name) return null;\n return [name.givenName, name.middleName, name.familyName].map((p) => p?.trim()).filter(Boolean).join(\" \") || null;\n}\n\nfunction defaultNonce(): string {\n const c = (globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => Uint8Array } }).crypto;\n if (!c?.getRandomValues) throw new Error(\"[genesis] signInWithApple: no crypto.getRandomValues here; pass randomNonce (e.g. expo-crypto's randomUUID)\");\n return Array.from(c.getRandomValues(new Uint8Array(32)), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n","/**\n * React Native helpers for a Supabase session. Nothing here imports React\n * Native or Expo: pass in `expo-secure-store`, `AppState`,\n * `expo-apple-authentication` and a plain store, so the package stays light\n * and the helpers are easy to test.\n */\nimport type { SessionStorage } from \"./index\";\n\nexport {\n GenesisAuthError,\n completeOAuthRedirect,\n formatFullName,\n redirectParams,\n signInWithApple,\n type AppleAuthenticationLike,\n type AppleFullNameLike,\n type OAuthRedirectResult,\n type SignInClient,\n type SignInWithAppleOptions,\n type SignInWithAppleResult,\n} from \"./sign-in\";\n\n/** The parts of `expo-secure-store` the Keychain storage uses. */\nexport interface SecureStoreLike {\n getItemAsync(key: string, options?: Record<string, unknown>): Promise<string | null>;\n setItemAsync(key: string, value: string, options?: Record<string, unknown>): Promise<void>;\n deleteItemAsync(key: string, options?: Record<string, unknown>): Promise<void>;\n /** `SecureStore.AFTER_FIRST_UNLOCK`, when available. */\n AFTER_FIRST_UNLOCK?: unknown;\n}\n\n/** @stability Beta */\nexport interface KeychainStorageOptions {\n /** UTF-8 bytes per Keychain item. A Keychain item should stay under about 2 KB; a Supabase session is larger. Default 1800. */\n chunkSize?: number;\n /** Extra options for every SecureStore call. Default: readable after first unlock, so a background refresh works. */\n secureStoreOptions?: Record<string, unknown>;\n}\n\n/**\n * Session storage in the Keychain (iOS) or Keystore (Android).\n *\n * A value is split into numbered chunks. Each write goes to a new generation of\n * chunks, and one manifest item (`<key>.n` = `generation:count`) switches to it\n * only once every chunk is written, so a write cut short leaves the previous\n * session whole. Chunks split between characters (never inside one) and\n * stay under `chunkSize` UTF-8 bytes. A pending marker (`<key>.p`) names a\n * generation while it is being written, so chunks left by a write that was\n * cut short or crashed are swept on the next write or remove. Writes to the\n * same key run one at a time, so two token refreshes can't interleave.\n *\n * @stability Beta\n */\nexport function keychainStorage(secureStore: SecureStoreLike, options: KeychainStorageOptions = {}): SessionStorage {\n const size = options.chunkSize ?? 1800;\n const opts = options.secureStoreOptions ?? (secureStore.AFTER_FIRST_UNLOCK !== undefined ? { keychainAccessible: secureStore.AFTER_FIRST_UNLOCK } : {});\n const chunkKey = (key: string, gen: string, i: number) => `${key}.${gen}.${i}`;\n let counter = 0;\n const queues = new Map<string, Promise<unknown>>();\n\n /** Run `task` after every earlier write to `key` has settled. */\n const serial = <T>(key: string, task: () => Promise<T>): Promise<T> => {\n const run = (queues.get(key) ?? Promise.resolve()).catch(() => undefined).then(task);\n queues.set(key, run);\n return run;\n };\n\n const parse = (raw: string | null) => {\n const [gen, count] = (raw ?? \"\").split(\":\");\n return gen && Number(count) > 0 ? { gen, count: Number(count) } : null;\n };\n const manifest = async (key: string) => parse(await secureStore.getItemAsync(`${key}.n`, opts));\n const pending = async (key: string) => parse(await secureStore.getItemAsync(`${key}.p`, opts));\n const drop = (key: string, gen: string, count: number) =>\n Promise.all(Array.from({ length: count }, (_, i) => secureStore.deleteItemAsync(chunkKey(key, gen, i), opts)));\n\n /** Remove chunks of a write that never completed (unless they are the current generation). */\n const sweepPending = async (key: string, current: string | undefined) => {\n const p = await pending(key);\n if (!p) return;\n if (p.gen !== current) await drop(key, p.gen, p.count);\n await secureStore.deleteItemAsync(`${key}.p`, opts);\n };\n\n return {\n getItem(key) {\n // Reads queue with writes, so a write can't swap the manifest and drop\n // the old chunks halfway through a read.\n return serial(key, async () => {\n const m = await manifest(key);\n if (!m) return null;\n const parts = await Promise.all(Array.from({ length: m.count }, (_, i) => secureStore.getItemAsync(chunkKey(key, m.gen, i), opts)));\n return parts.some((p) => p === null) ? null : parts.join(\"\");\n });\n },\n setItem(key, value) {\n return serial(key, async () => {\n const before = await manifest(key);\n await sweepPending(key, before?.gen);\n const gen = `${Date.now().toString(36)}${(counter++).toString(36)}`;\n const parts = splitUtf8(value, size);\n await secureStore.setItemAsync(`${key}.p`, `${gen}:${parts.length}`, opts);\n // Let every chunk write settle before failing, so a late write can't land\n // after the next operation has swept this generation.\n const written = await Promise.allSettled(parts.map((p, i) => secureStore.setItemAsync(chunkKey(key, gen, i), p, opts)));\n const failed = written.find((r): r is PromiseRejectedResult => r.status === \"rejected\");\n if (failed) throw failed.reason;\n await secureStore.setItemAsync(`${key}.n`, `${gen}:${parts.length}`, opts);\n await secureStore.deleteItemAsync(`${key}.p`, opts);\n if (before) await drop(key, before.gen, before.count);\n });\n },\n removeItem(key) {\n return serial(key, async () => {\n const m = await manifest(key);\n await sweepPending(key, m?.gen);\n // Chunks first: the manifest is what finds them, so it goes last.\n if (m) await drop(key, m.gen, m.count);\n await secureStore.deleteItemAsync(`${key}.n`, opts);\n });\n },\n };\n}\n\n/** The parts of a Supabase client's auth the refresh helper uses. */\nexport interface AutoRefreshAuth {\n auth: { startAutoRefresh(): Promise<void> | void; stopAutoRefresh(): Promise<void> | void };\n}\n\n/** The parts of React Native's `AppState` the refresh helper uses. */\nexport interface AppStateLike {\n currentState?: string | null;\n addEventListener(type: \"change\", listener: (state: string) => void): { remove(): void };\n}\n\n/**\n * Refresh the session only while the app is in front, as Supabase recommends\n * for React Native. Returns the unsubscribe.\n *\n * @stability Beta\n */\nexport function refreshWhileActive(client: AutoRefreshAuth, appState: AppStateLike): () => void {\n const apply = (state: string | null | undefined) => {\n if (state === \"active\") void client.auth.startAutoRefresh();\n else void client.auth.stopAutoRefresh();\n };\n apply(appState.currentState ?? \"active\");\n const sub = appState.addEventListener(\"change\", apply);\n return () => {\n sub.remove();\n void client.auth.stopAutoRefresh();\n };\n}\n\n/** @stability Beta */\nexport interface WipeOnReinstallOptions {\n /** The session storage (usually `keychainStorage`). */\n session: SessionStorage;\n /** The session's storage key. Supabase's default is `sb-<project ref>-auth-token`. */\n sessionKey: string;\n /** A store that an uninstall deletes, e.g. AsyncStorage. The Keychain survives an uninstall; this is how we notice one. */\n installMarker: SessionStorage;\n /** The marker's key. Default `genesis.installed`. */\n markerKey?: string;\n}\n\n/**\n * On iOS the Keychain outlives an uninstall, so a reinstalled app would sign\n * the last person back in. Call this before creating the client: on the first\n * launch after an install it removes any session left in the Keychain, then\n * sets a marker so later launches keep the session. Returns whether it wiped.\n *\n * @stability Beta\n */\nexport async function wipeSessionOnReinstall({ session, sessionKey, installMarker, markerKey = \"genesis.installed\" }: WipeOnReinstallOptions): Promise<boolean> {\n if ((await installMarker.getItem(markerKey)) !== null) return false;\n // The session, plus what supabase-js keeps beside it: the PKCE verifier (the\n // single legacy slot, each indexed flow's slot and the index) and, with\n // userStorage, the user.\n const indexKey = `${sessionKey}-flows-code-verifier`;\n for (const flowId of parseFlowIndex(await session.getItem(indexKey))) await session.removeItem(`${sessionKey}-flow-${flowId}-code-verifier`);\n for (const key of [sessionKey, `${sessionKey}-code-verifier`, indexKey, `${sessionKey}-user`]) await session.removeItem(key);\n await installMarker.setItem(markerKey, \"1\");\n return true;\n}\n\n/** UTF-8 byte length of one code point. */\nconst utf8Length = (codePoint: number) => (codePoint < 0x80 ? 1 : codePoint < 0x800 ? 2 : codePoint < 0x10000 ? 3 : 4);\n\n/** Split text into pieces of at most `maxBytes` UTF-8 bytes, never inside a character. */\nexport function splitUtf8(text: string, maxBytes: number): string[] {\n if (text === \"\") return [\"\"];\n const parts: string[] = [];\n let current = \"\";\n let bytes = 0;\n for (const ch of text) {\n const n = utf8Length(ch.codePointAt(0)!);\n if (bytes + n > maxBytes && current) {\n parts.push(current);\n current = \"\";\n bytes = 0;\n }\n current += ch;\n bytes += n;\n }\n parts.push(current);\n return parts;\n}\n\n/** supabase-js's PKCE flow index: a JSON array of flow ids. Anything else is ignored. */\nfunction parseFlowIndex(raw: string | null): string[] {\n if (!raw) return [];\n try {\n const ids: unknown = JSON.parse(raw);\n return Array.isArray(ids) ? ids.filter((id): id is string => typeof id === \"string\" && /^[\\w-]{1,128}$/.test(id)) : [];\n } catch {\n return [];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/sign-in.ts","../src/native.ts"],"names":[],"mappings":";;;AAkBO,IAAM,gBAAA,GAAN,cAA+B,KAAA,CAAM;AAAA,EACjC,IAAA,GAAO,kBAAA;AAAA,EACP,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAA;AAAA,EACT,WAAA,CAAY,IAAA,EAAc,OAAA,EAAiB,OAAA,GAAyD,EAAC,EAAG;AACtG,IAAA,KAAA,CAAM,OAAA,EAAS,QAAQ,KAAA,KAAU,MAAA,GAAY,SAAY,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAO,CAAA;AACjF,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAI,OAAA,CAAQ,eAAA,KAAoB,MAAA,EAAW,IAAA,CAAK,kBAAkB,OAAA,CAAQ,eAAA;AAAA,EAC5E;AACF;AAGA,IAAM,iBAAA,GAA4C;AAAA,EAChD,aAAA,EAAe,sCAAA;AAAA,EACf,WAAA,EAAa,mDAAA;AAAA,EACb,kBAAA,EAAoB,wCAAA;AAAA,EACpB,oBAAA,EAAsB,+DAAA;AAAA,EACtB,eAAA,EAAiB,8CAAA;AAAA,EACjB,kBAAA,EAAoB,8CAAA;AAAA,EACpB,iCAAA,EAAmC,sDAAA;AAAA,EACnC,YAAA,EAAc,+CAAA;AAAA,EACd,uBAAA,EAAyB;AAC3B,CAAA;AACA,IAAM,iBAAA,GAAoB,qCAAA;AAiB1B,IAAM,MAAA,GAAS,CAAC,CAAA,EAAW,GAAA,KAAgB,OAAO,SAAA,CAAU,cAAA,CAAe,IAAA,CAAK,CAAA,EAAG,GAAG,CAAA;AAM/E,SAAS,eAAe,GAAA,EAAqC;AAClE,EAAA,MAAM,MAAA,mBAAiC,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AACzD,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC5B,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC7B,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,KAAA,IAAS,CAAA,KAAM,IAAA,GAAO,CAAA,IAAK,QAAQ,IAAA,CAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,KAAA,GAAQ,CAAA,EAAG,IAAA,IAAQ,CAAA,GAAI,IAAA,GAAO,MAAS,CAAA,GAAI,EAAA;AAAA,IAChG,QAAQ,CAAA,GAAI,GAAA,CAAI,KAAA,CAAM,IAAA,GAAO,CAAC,CAAA,GAAI;AAAA,GACpC;AACA,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,KAAA,MAAW,IAAA,IAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,EAAG;AAClC,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAC3B,MAAA,MAAM,GAAA,GAAM,OAAO,EAAA,IAAM,CAAA,GAAI,KAAK,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,GAAI,IAAI,CAAA;AACrD,MAAA,IAAI,CAAC,MAAA,CAAO,MAAA,EAAQ,GAAG,CAAA,SAAU,GAAG,CAAA,GAAG,MAAA,CAAO,EAAA,IAAM,IAAI,IAAA,CAAK,KAAA,CAAM,EAAA,GAAK,CAAC,IAAI,EAAE,CAAA;AAAA,IACjF;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,OAAO,CAAA,EAAmB;AACjC,EAAA,IAAI;AACF,IAAA,OAAO,kBAAA,CAAmB,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAC,CAAA;AAAA,EACjD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,CAAA;AAAA,EACT;AACF;AAqBA,eAAsB,qBAAA,CAAsB,QAAsB,GAAA,EAA2C;AAC3G,EAAA,MAAM,MAAA,GAAS,eAAe,GAAG,CAAA;AACjC,EAAA,IAAI,MAAA,CAAO,KAAA,IAAS,MAAA,CAAO,iBAAA,IAAqB,OAAO,UAAA,EAAY;AACjE,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,UAAA,IAAc,MAAA,CAAO,KAAA,IAAS,aAAA;AAClD,IAAA,MAAM,OAAA,GAAU,iBAAA,CAAkB,IAAI,CAAA,KAAM,MAAA,CAAO,QAAQ,iBAAA,CAAkB,MAAA,CAAO,KAAK,CAAA,GAAI,MAAA,CAAA,IAAc,iBAAA;AAC3G,IAAA,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,IAAA,EAAM,OAAA,EAAS,EAAE,eAAA,EAAiB,MAAA,CAAO,iBAAA,IAAqB,MAAA,EAAW,CAAA,EAAE;AAAA,EAClH;AACA,EAAA,IAAI,CAAC,MAAA,CAAO,IAAA,EAAM,OAAO,EAAE,OAAO,IAAI,gBAAA,CAAiB,cAAA,EAAgB,sCAAsC,CAAA,EAAE;AAC/G,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,CAAK,sBAAA;AAAA,MACxC,MAAA,CAAO,IAAA;AAAA,MACP,OAAO,UAAA,GAAa,EAAE,MAAA,EAAQ,MAAA,CAAO,YAAW,GAAI,KAAA;AAAA,KACtD;AACA,IAAA,IAAI,KAAA,EAAO,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,KAAA,CAAM,IAAA,IAAQ,iBAAA,EAAmB,MAAM,OAAA,EAAS,EAAE,KAAA,EAAO,KAAA,EAAO,CAAA,EAAE;AAClH,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,EAAS,OAAO,EAAE,OAAO,IAAI,gBAAA,CAAiB,YAAA,EAAc,uCAAuC,CAAA,EAAE;AAE/G,IAAA,MAAM,YAAA,GAAgB,KAA0C,YAAA,IAAgB,IAAA;AAChF,IAAA,OAAO,EAAE,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,YAAA,EAAa;AAAA,EAC/C,SAAS,CAAA,EAAG;AACV,IAAA,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,mBAAmB,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,0BAAA,EAA4B,EAAE,KAAA,EAAO,CAAA,EAAG,CAAA,EAAE;AAAA,EACrI;AACF;AA0EA,eAAsB,gBACpB,MAAA,EACA,KAAA,EACA,EAAE,MAAA,EAAQ,WAAA,GAAc,cAAa,EACL;AAChC,EAAA,MAAM,WAAA,GAAc,CAAC,KAAA,MAAqB;AAAA,IACxC,OAAO,IAAI,gBAAA,CAAiB,qBAAqB,mDAAA,EAAqD,EAAE,OAAO;AAAA,GACjH,CAAA;AACA,EAAA,IAAI;AACF,IAAA,IAAI,CAAE,MAAM,KAAA,CAAM,gBAAA,EAAiB,SAAW,WAAA,EAAY;AAAA,EAC5D,SAAS,CAAA,EAAG;AACV,IAAA,OAAO,YAAY,CAAC,CAAA;AAAA,EACtB;AACA,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI;AACF,IAAA,KAAA,GAAQ,WAAA,EAAY;AACpB,IAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,KAAK,CAAA;AACjC,IAAA,IAAI,CAAC,gBAAA,CAAiB,IAAA,CAAK,MAAM,CAAA,EAAG;AAClC,MAAA,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,YAAA,EAAc,4EAA4E,CAAA,EAAE;AAAA,IACnI;AACA,IAAA,UAAA,GAAa,MAAM,MAAM,WAAA,CAAY;AAAA,MACnC,iBAAiB,CAAC,KAAA,CAAM,yBAAyB,SAAA,EAAW,KAAA,CAAM,yBAAyB,KAAK,CAAA;AAAA,MAChG,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH,SAAS,CAAA,EAAG;AACV,IAAA,IAAK,GAAiC,IAAA,KAAS,sBAAA,EAAwB,OAAO,EAAE,UAAU,IAAA,EAAK;AAC/F,IAAA,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,gBAAgB,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,2BAAA,EAA6B,EAAE,KAAA,EAAO,CAAA,EAAG,CAAA,EAAE;AAAA,EACnI;AACA,EAAA,MAAM,QAAA,GAAW,cAAA,CAAe,UAAA,CAAW,QAAQ,CAAA;AACnD,EAAA,IAAI,CAAC,UAAA,CAAW,aAAA,EAAe,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,gBAAA,EAAkB,kCAAkC,CAAA,EAAG,QAAA,EAAS;AACpI,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,MAAA,CAAO,IAAA,CAAK,iBAAA,CAAkB,EAAE,UAAU,OAAA,EAAS,KAAA,EAAO,UAAA,CAAW,aAAA,EAAe,OAAO,CAAA;AACzH,IAAA,IAAI,OAAO,OAAO,EAAE,KAAA,EAAO,IAAI,iBAAiB,KAAA,CAAM,IAAA,IAAQ,gBAAA,EAAkB,KAAA,CAAM,SAAS,EAAE,KAAA,EAAO,KAAA,EAAO,GAAG,QAAA,EAAS;AAC3H,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,CAAC,KAAK,IAAA,EAAM,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,YAAA,EAAc,6BAA6B,GAAG,QAAA,EAAS;AAC7H,IAAA,OAAO;AAAA,MACL,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,QAAA;AAAA,MACA,KAAA,EAAO,WAAW,KAAA,IAAS,IAAA;AAAA,MAC3B,iBAAA,EAAmB,WAAW,iBAAA,IAAqB;AAAA,KACrD;AAAA,EACF,SAAS,CAAA,EAAG;AACV,IAAA,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,kBAAkB,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,kBAAkB,EAAE,KAAA,EAAO,CAAA,EAAG,GAAG,QAAA,EAAS;AAAA,EACpI;AACF;AA8DA,eAAsB,iBACpB,MAAA,EACA,MAAA,EACA,EAAE,MAAA,EAAQ,WAAA,GAAc,cAAa,EACJ;AACjC,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,KAAA,GAAQ,WAAA,EAAY;AACpB,IAAA,MAAA,GAAS,MAAM,OAAO,KAAK,CAAA;AAC3B,IAAA,IAAI,CAAC,gBAAA,CAAiB,IAAA,CAAK,MAAM,CAAA,EAAG;AAClC,MAAA,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,YAAA,EAAc,6EAA6E,CAAA,EAAE;AAAA,IACpI;AACA,IAAA,QAAA,GAAW,MAAM,MAAA,CAAO,YAAA,CAAa,EAAE,KAAA,EAAO,QAAQ,CAAA;AAAA,EACxD,SAAS,CAAA,EAAG;AACV,IAAA,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,iBAAiB,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,uBAAA,EAAyB,EAAE,KAAA,EAAO,CAAA,EAAG,CAAA,EAAE;AAAA,EAChI;AACA,EAAA,IAAI,QAAA,CAAS,WAAA,EAAa,OAAO,EAAE,UAAU,IAAA,EAAK;AAClD,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,OAAO,EAAE,OAAO,IAAI,gBAAA,CAAiB,iBAAiB,QAAA,CAAS,KAAA,CAAM,WAAW,CAAA,uBAAA,EAA0B,MAAA,CAAO,SAAS,KAAA,CAAM,IAAA,IAAQ,SAAS,CAAC,CAAA,CAAA,CAAA,EAAK,EAAE,KAAA,EAAO,QAAA,CAAS,KAAA,EAAO,CAAA,EAAE;AAAA,EACpL;AACA,EAAA,MAAM,KAAA,GAAQ,SAAS,IAAA,EAAM,OAAA;AAC7B,EAAA,IAAI,CAAC,OAAO,OAAO,EAAE,OAAO,IAAI,gBAAA,CAAiB,iBAAA,EAAmB,6BAA6B,CAAA,EAAE;AACnG,EAAA,IAAI,QAAA,CAAS,KAAA,EAAO,OAAO,CAAA,KAAM,MAAA,EAAQ;AACvC,IAAA,OAAO;AAAA,MACL,OAAO,IAAI,gBAAA;AAAA,QACT,sBAAA;AAAA,QACA;AAAA;AACF,KACF;AAAA,EACF;AACA,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,MAAA,CAAO,IAAA,CAAK,iBAAA,CAAkB,EAAE,QAAA,EAAU,QAAA,EAAU,KAAA,EAAO,OAAO,CAAA;AAChG,IAAA,IAAI,KAAA,EAAO,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,KAAA,CAAM,IAAA,IAAQ,gBAAA,EAAkB,MAAM,OAAA,EAAS,EAAE,KAAA,EAAO,KAAA,EAAO,CAAA,EAAE;AACjH,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,CAAC,IAAA,CAAK,IAAA,EAAM,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,YAAA,EAAc,6BAA6B,CAAA,EAAE;AACnH,IAAA,MAAM,OAAA,GAAU,SAAS,IAAA,EAAM,IAAA;AAC/B,IAAA,OAAO,EAAE,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,IAAA,EAAM,KAAK,IAAA,EAAM,QAAA,EAAU,OAAA,EAAS,IAAA,EAAM,MAAK,IAAK,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,SAAS,IAAA,EAAK;AAAA,EAC1H,SAAS,CAAA,EAAG;AACV,IAAA,OAAO,EAAE,KAAA,EAAO,IAAI,gBAAA,CAAiB,kBAAkB,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,gBAAA,EAAkB,EAAE,KAAA,EAAO,CAAA,EAAG,CAAA,EAAE;AAAA,EAC1H;AACF;AAGA,SAAS,QAAA,CAAS,OAAe,KAAA,EAAmC;AAClE,EAAA,MAAM,OAAA,GAAU,KAAA,CAAM,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AAClC,EAAA,IAAI,CAAC,SAAS,OAAO,MAAA;AACrB,EAAA,IAAI;AACF,IAAA,MAAM,QAAiB,IAAA,CAAK,KAAA,CAAM,gBAAgB,OAAO,CAAC,IAAI,KAAK,CAAA;AACnE,IAAA,OAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,KAAA,CAAA;AAAA,EAC7C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAEA,IAAM,GAAA,GAAM,kEAAA;AAGZ,SAAS,gBAAgB,KAAA,EAAuB;AAC9C,EAAA,IAAI,OAAA,GAAU,EAAA;AACd,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,MAAW,EAAA,IAAM,KAAA,CAAM,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,EAAG;AACzC,IAAA,MAAM,CAAA,GAAI,IAAI,OAAA,CAAQ,EAAA,KAAO,MAAM,GAAA,GAAM,EAAA,KAAO,GAAA,GAAM,GAAA,GAAM,EAAE,CAAA;AAC9D,IAAA,IAAI,CAAA,GAAI,CAAA,EAAG,MAAM,IAAI,MAAM,eAAe,CAAA;AAC1C,IAAA,MAAA,GAAU,UAAU,CAAA,GAAK,CAAA;AACzB,IAAA,IAAA,IAAQ,CAAA;AACR,IAAA,IAAI,QAAQ,CAAA,EAAG;AACb,MAAA,IAAA,IAAQ,CAAA;AACR,MAAA,OAAA,IAAW,CAAA,CAAA,EAAA,CAAM,MAAA,IAAU,IAAA,GAAQ,GAAA,EAAM,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AACtE,MAAA,MAAA,IAAA,CAAW,KAAK,IAAA,IAAQ,CAAA;AAAA,IAC1B;AAAA,EACF;AACA,EAAA,OAAO,mBAAmB,OAAO,CAAA;AACnC;AAGO,SAAS,eAAe,IAAA,EAA2D;AACxF,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,EAAA,OAAO,CAAC,KAAK,SAAA,EAAW,IAAA,CAAK,YAAY,IAAA,CAAK,UAAU,EAAE,GAAA,CAAI,CAAC,MAAM,CAAA,EAAG,IAAA,EAAM,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA,IAAK,IAAA;AAC/G;AAEA,SAAS,YAAA,GAAuB;AAC9B,EAAA,MAAM,IAAK,UAAA,CAAgF,MAAA;AAE3F,EAAA,IAAI,CAAC,CAAA,EAAG,eAAA,EAAiB,MAAM,IAAI,MAAM,0LAA0L,CAAA;AACnO,EAAA,OAAO,KAAA,CAAM,KAAK,CAAA,CAAE,eAAA,CAAgB,IAAI,UAAA,CAAW,EAAE,CAAC,CAAA,EAAG,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,CAAA;AAC1G;;;AClVO,SAAS,eAAA,CAAgB,WAAA,EAA8B,OAAA,GAAkC,EAAC,EAAmB;AAClH,EAAA,MAAM,IAAA,GAAO,QAAQ,SAAA,IAAa,IAAA;AAClC,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,kBAAA,KAAuB,WAAA,CAAY,kBAAA,KAAuB,MAAA,GAAY,EAAE,kBAAA,EAAoB,WAAA,CAAY,kBAAA,EAAmB,GAAI,EAAC,CAAA;AACrJ,EAAA,MAAM,QAAA,GAAW,CAAC,GAAA,EAAa,GAAA,EAAa,CAAA,KAAc,GAAG,GAAG,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA;AAC5E,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,MAAM,MAAA,uBAAa,GAAA,EAA8B;AAGjD,EAAA,MAAM,MAAA,GAAS,CAAI,GAAA,EAAa,IAAA,KAAuC;AACrE,IAAA,MAAM,GAAA,GAAA,CAAO,MAAA,CAAO,GAAA,CAAI,GAAG,CAAA,IAAK,OAAA,CAAQ,OAAA,EAAQ,EAAG,KAAA,CAAM,MAAM,MAAS,CAAA,CAAE,KAAK,IAAI,CAAA;AACnF,IAAA,MAAA,CAAO,GAAA,CAAI,KAAK,GAAG,CAAA;AACnB,IAAA,OAAO,GAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAA,KAAuB;AACpC,IAAA,MAAM,CAAC,GAAA,EAAK,KAAK,KAAK,GAAA,IAAO,EAAA,EAAI,MAAM,GAAG,CAAA;AAC1C,IAAA,OAAO,GAAA,IAAO,MAAA,CAAO,KAAK,CAAA,GAAI,CAAA,GAAI,EAAE,GAAA,EAAK,KAAA,EAAO,MAAA,CAAO,KAAK,CAAA,EAAE,GAAI,IAAA;AAAA,EACpE,CAAA;AACA,EAAA,MAAM,QAAA,GAAW,OAAO,GAAA,KAAgB,KAAA,CAAM,MAAM,WAAA,CAAY,YAAA,CAAa,CAAA,EAAG,GAAG,CAAA,EAAA,CAAA,EAAM,IAAI,CAAC,CAAA;AAC9F,EAAA,MAAM,OAAA,GAAU,OAAO,GAAA,KAAgB,KAAA,CAAM,MAAM,WAAA,CAAY,YAAA,CAAa,CAAA,EAAG,GAAG,CAAA,EAAA,CAAA,EAAM,IAAI,CAAC,CAAA;AAC7F,EAAA,MAAM,IAAA,GAAO,CAAC,GAAA,EAAa,GAAA,EAAa,KAAA,KACtC,QAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,KAAA,IAAS,CAAC,CAAA,EAAG,CAAA,KAAM,WAAA,CAAY,eAAA,CAAgB,QAAA,CAAS,GAAA,EAAK,GAAA,EAAK,CAAC,CAAA,EAAG,IAAI,CAAC,CAAC,CAAA;AAG/G,EAAA,MAAM,YAAA,GAAe,OAAO,GAAA,EAAa,OAAA,KAAgC;AACvE,IAAA,MAAM,CAAA,GAAI,MAAM,OAAA,CAAQ,GAAG,CAAA;AAC3B,IAAA,IAAI,CAAC,CAAA,EAAG;AACR,IAAA,IAAI,CAAA,CAAE,QAAQ,OAAA,EAAS,MAAM,KAAK,GAAA,EAAK,CAAA,CAAE,GAAA,EAAK,CAAA,CAAE,KAAK,CAAA;AACrD,IAAA,MAAM,WAAA,CAAY,eAAA,CAAgB,CAAA,EAAG,GAAG,MAAM,IAAI,CAAA;AAAA,EACpD,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,QAAQ,GAAA,EAAK;AAGX,MAAA,OAAO,MAAA,CAAO,KAAK,YAAY;AAC7B,QAAA,MAAM,CAAA,GAAI,MAAM,QAAA,CAAS,GAAG,CAAA;AAC5B,QAAA,IAAI,CAAC,GAAG,OAAO,IAAA;AACf,QAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,KAAK,EAAE,MAAA,EAAQ,CAAA,CAAE,KAAA,EAAM,EAAG,CAAC,GAAG,CAAA,KAAM,WAAA,CAAY,YAAA,CAAa,QAAA,CAAS,GAAA,EAAK,CAAA,CAAE,KAAK,CAAC,CAAA,EAAG,IAAI,CAAC,CAAC,CAAA;AAClI,QAAA,OAAO,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,KAAM,IAAI,CAAA,GAAI,IAAA,GAAO,KAAA,CAAM,IAAA,CAAK,EAAE,CAAA;AAAA,MAC7D,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,OAAA,CAAQ,KAAK,KAAA,EAAO;AAClB,MAAA,OAAO,MAAA,CAAO,KAAK,YAAY;AAC7B,QAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,GAAG,CAAA;AACjC,QAAA,MAAM,YAAA,CAAa,GAAA,EAAK,MAAA,EAAQ,GAAG,CAAA;AACnC,QAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CAAC,CAAA,EAAA,CAAI,OAAA,EAAA,EAAW,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA;AACjE,QAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,KAAA,EAAO,IAAI,CAAA;AACnC,QAAA,MAAM,WAAA,CAAY,YAAA,CAAa,CAAA,EAAG,GAAG,CAAA,EAAA,CAAA,EAAM,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,KAAA,CAAM,MAAM,CAAA,CAAA,EAAI,IAAI,CAAA;AAGzE,QAAA,MAAM,UAAU,MAAM,OAAA,CAAQ,WAAW,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,KAAM,YAAY,YAAA,CAAa,QAAA,CAAS,KAAK,GAAA,EAAK,CAAC,GAAG,CAAA,EAAG,IAAI,CAAC,CAAC,CAAA;AACtH,QAAA,MAAM,SAAS,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAkC,CAAA,CAAE,WAAW,UAAU,CAAA;AACtF,QAAA,IAAI,MAAA,QAAc,MAAA,CAAO,MAAA;AACzB,QAAA,MAAM,WAAA,CAAY,YAAA,CAAa,CAAA,EAAG,GAAG,CAAA,EAAA,CAAA,EAAM,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,KAAA,CAAM,MAAM,CAAA,CAAA,EAAI,IAAI,CAAA;AACzE,QAAA,MAAM,WAAA,CAAY,eAAA,CAAgB,CAAA,EAAG,GAAG,MAAM,IAAI,CAAA;AAClD,QAAA,IAAI,QAAQ,MAAM,IAAA,CAAK,KAAK,MAAA,CAAO,GAAA,EAAK,OAAO,KAAK,CAAA;AAAA,MACtD,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,WAAW,GAAA,EAAK;AACd,MAAA,OAAO,MAAA,CAAO,KAAK,YAAY;AAC7B,QAAA,MAAM,CAAA,GAAI,MAAM,QAAA,CAAS,GAAG,CAAA;AAC5B,QAAA,MAAM,YAAA,CAAa,GAAA,EAAK,CAAA,EAAG,GAAG,CAAA;AAE9B,QAAA,IAAI,GAAG,MAAM,IAAA,CAAK,KAAK,CAAA,CAAE,GAAA,EAAK,EAAE,KAAK,CAAA;AACrC,QAAA,MAAM,WAAA,CAAY,eAAA,CAAgB,CAAA,EAAG,GAAG,MAAM,IAAI,CAAA;AAAA,MACpD,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AACF;AAmBO,SAAS,kBAAA,CAAmB,QAAyB,QAAA,EAAoC;AAC9F,EAAA,MAAM,KAAA,GAAQ,CAAC,KAAA,KAAqC;AAClD,IAAA,IAAI,KAAA,KAAU,QAAA,EAAU,KAAK,MAAA,CAAO,KAAK,gBAAA,EAAiB;AAAA,SACrD,KAAK,MAAA,CAAO,IAAA,CAAK,eAAA,EAAgB;AAAA,EACxC,CAAA;AACA,EAAA,KAAA,CAAM,QAAA,CAAS,gBAAgB,QAAQ,CAAA;AACvC,EAAA,MAAM,GAAA,GAAM,QAAA,CAAS,gBAAA,CAAiB,QAAA,EAAU,KAAK,CAAA;AACrD,EAAA,OAAO,MAAM;AACX,IAAA,GAAA,CAAI,MAAA,EAAO;AACX,IAAA,KAAK,MAAA,CAAO,KAAK,eAAA,EAAgB;AAAA,EACnC,CAAA;AACF;AAsBA,eAAsB,uBAAuB,EAAE,OAAA,EAAS,YAAY,aAAA,EAAe,SAAA,GAAY,qBAAoB,EAA6C;AAC9J,EAAA,IAAK,MAAM,aAAA,CAAc,OAAA,CAAQ,SAAS,CAAA,KAAO,MAAM,OAAO,KAAA;AAI9D,EAAA,MAAM,QAAA,GAAW,GAAG,UAAU,CAAA,oBAAA,CAAA;AAC9B,EAAA,KAAA,MAAW,MAAA,IAAU,cAAA,CAAe,MAAM,OAAA,CAAQ,QAAQ,QAAQ,CAAC,CAAA,EAAG,MAAM,QAAQ,UAAA,CAAW,CAAA,EAAG,UAAU,CAAA,MAAA,EAAS,MAAM,CAAA,cAAA,CAAgB,CAAA;AAC3I,EAAA,KAAA,MAAW,GAAA,IAAO,CAAC,UAAA,EAAY,CAAA,EAAG,UAAU,CAAA,cAAA,CAAA,EAAkB,QAAA,EAAU,CAAA,EAAG,UAAU,CAAA,KAAA,CAAO,CAAA,EAAG,MAAM,OAAA,CAAQ,WAAW,GAAG,CAAA;AAC3H,EAAA,MAAM,aAAA,CAAc,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA;AAC1C,EAAA,OAAO,IAAA;AACT;AAGA,IAAM,UAAA,GAAa,CAAC,SAAA,KAAuB,SAAA,GAAY,GAAA,GAAO,CAAA,GAAI,SAAA,GAAY,IAAA,GAAQ,CAAA,GAAI,SAAA,GAAY,KAAA,GAAU,CAAA,GAAI,CAAA;AAG7G,SAAS,SAAA,CAAU,MAAc,QAAA,EAA4B;AAClE,EAAA,IAAI,IAAA,KAAS,EAAA,EAAI,OAAO,CAAC,EAAE,CAAA;AAC3B,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,IAAI,OAAA,GAAU,EAAA;AACd,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,MAAW,MAAM,IAAA,EAAM;AACrB,IAAA,MAAM,CAAA,GAAI,UAAA,CAAW,EAAA,CAAG,WAAA,CAAY,CAAC,CAAE,CAAA;AACvC,IAAA,IAAI,KAAA,GAAQ,CAAA,GAAI,QAAA,IAAY,OAAA,EAAS;AACnC,MAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAClB,MAAA,OAAA,GAAU,EAAA;AACV,MAAA,KAAA,GAAQ,CAAA;AAAA,IACV;AACA,IAAA,OAAA,IAAW,EAAA;AACX,IAAA,KAAA,IAAS,CAAA;AAAA,EACX;AACA,EAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAClB,EAAA,OAAO,KAAA;AACT;AAGA,SAAS,eAAe,GAAA,EAA8B;AACpD,EAAA,IAAI,CAAC,GAAA,EAAK,OAAO,EAAC;AAClB,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAe,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACnC,IAAA,OAAO,MAAM,OAAA,CAAQ,GAAG,CAAA,GAAI,GAAA,CAAI,OAAO,CAAC,EAAA,KAAqB,OAAO,EAAA,KAAO,YAAY,gBAAA,CAAiB,IAAA,CAAK,EAAE,CAAC,IAAI,EAAC;AAAA,EACvH,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AACF","file":"native.js","sourcesContent":["/**\n * Native sign-in onto a Supabase session: finishing an OAuth redirect (PKCE),\n * Sign in with Apple and native Google sign-in. Nothing here imports Expo or\n * the Google module: pass the modules in.\n * Exported from `@marktiderman/genesis-supabase/native`.\n */\nimport type { Session, SupabaseClient, User } from \"@supabase/supabase-js\";\n\n/**\n * Why a sign-in didn't produce a session. `code` is the provider's error\n * (`access_denied`…), Supabase Auth's error code, or one of Genesis's own:\n * `missing_code` (the redirect carried neither a code nor an error),\n * `oauth_error`, `exchange_failed`, `apple_unavailable`, `apple_failed`,\n * `apple_no_token`, `google_failed`, `google_no_token`,\n * `google_nonce_missing`, `bad_digest`, `sign_in_failed`, `no_session`.\n *\n * @stability Beta\n */\nexport class GenesisAuthError extends Error {\n override name = \"GenesisAuthError\";\n readonly code: string;\n /**\n * The provider's own `error_description`, as it came in the redirect.\n * UNTRUSTED: any app or web page can open your callback link with any text\n * here, so never show it as an official message. Log it; show `message`.\n */\n readonly providerMessage?: string;\n constructor(code: string, message: string, options: { cause?: unknown; providerMessage?: string } = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.code = code;\n if (options.providerMessage !== undefined) this.providerMessage = options.providerMessage;\n }\n}\n\n/** Fixed, safe copy for the redirect errors an app is likely to see; anything else gets the fallback. */\nconst REDIRECT_MESSAGES: Record<string, string> = {\n access_denied: \"Sign-in was canceled or not allowed.\",\n otp_expired: \"That sign-in link has expired. Ask for a new one.\",\n flow_state_expired: \"That sign-in took too long. Try again.\",\n flow_state_not_found: \"That sign-in has already been used or has expired. Try again.\",\n bad_oauth_state: \"That sign-in couldn't be checked. Try again.\",\n bad_oauth_callback: \"That sign-in couldn't be checked. Try again.\",\n provider_email_needs_verification: \"Verify your email with the provider, then try again.\",\n server_error: \"The sign-in service had a problem. Try again.\",\n temporarily_unavailable: \"The sign-in service is busy. Try again in a moment.\",\n};\nconst REDIRECT_FALLBACK = \"Sign-in didn't complete. Try again.\";\n\n/** The parts of a Supabase client the sign-in helpers use. A `SupabaseClient` fits. */\nexport interface SignInClient {\n auth: Pick<SupabaseClient[\"auth\"], \"exchangeCodeForSession\" | \"signInWithIdToken\">;\n}\n\n/** @stability Beta */\nexport type OAuthRedirectResult =\n | {\n session: Session;\n /** `\"recovery\"` after a password-reset link (send them to set a new password), otherwise `null`. */\n redirectType: string | null;\n error?: undefined;\n }\n | { session?: undefined; redirectType?: undefined; error: GenesisAuthError };\n\nconst hasOwn = (o: object, key: string) => Object.prototype.hasOwnProperty.call(o, key);\n\n/**\n * The query and fragment parameters of a URL (the query wins for a key in\n * both). Hand-parsed: React Native's `URL` has no reliable `searchParams`.\n */\nexport function redirectParams(url: string): Record<string, string> {\n const params: Record<string, string> = Object.create(null);\n const hash = url.indexOf(\"#\");\n const query = url.indexOf(\"?\");\n const parts = [\n query >= 0 && (hash < 0 || query < hash) ? url.slice(query + 1, hash >= 0 ? hash : undefined) : \"\",\n hash >= 0 ? url.slice(hash + 1) : \"\",\n ];\n for (const part of parts) {\n for (const pair of part.split(\"&\")) {\n if (!pair) continue;\n const eq = pair.indexOf(\"=\");\n const key = decode(eq >= 0 ? pair.slice(0, eq) : pair);\n if (!hasOwn(params, key)) params[key] =decode(eq >= 0 ? pair.slice(eq + 1) : \"\");\n }\n }\n return params;\n}\n\nfunction decode(s: string): string {\n try {\n return decodeURIComponent(s.replace(/\\+/g, \" \"));\n } catch {\n return s;\n }\n}\n\n/**\n * Finish an OAuth sign-in from the URL the browser came back to. With a `code`\n * it exchanges it for a session (PKCE: the verifier is already in the client's\n * storage), passing the redirect's `sb_flow_id` so the right flow's verifier\n * is used; with an `error` it returns that error and makes no request.\n * Nothing is thrown; every failure is `{ error }`.\n *\n * Pass only the URL `WebBrowser.openAuthSessionAsync` resolved with, for the\n * sign-in this app just started. A code is single-use and the exchange uses\n * up the stored verifier whatever happens, so a stray `?code=` from another\n * link spends the sign-in in progress, and handling the same redirect twice\n * (the auth-session result and a `Linking` listener) fails the second time.\n * Handle each redirect once.\n *\n * The error `message` is fixed copy chosen by `code`; the redirect's own text\n * is only in `providerMessage`, which is untrusted.\n *\n * @stability Beta\n */\nexport async function completeOAuthRedirect(client: SignInClient, url: string): Promise<OAuthRedirectResult> {\n const params = redirectParams(url);\n if (params.error || params.error_description || params.error_code) {\n const code = params.error_code || params.error || \"oauth_error\";\n const message = REDIRECT_MESSAGES[code] ?? (params.error ? REDIRECT_MESSAGES[params.error] : undefined) ?? REDIRECT_FALLBACK;\n return { error: new GenesisAuthError(code, message, { providerMessage: params.error_description || undefined }) };\n }\n if (!params.code) return { error: new GenesisAuthError(\"missing_code\", \"The sign-in redirect carried no code\") };\n try {\n const { data, error } = await client.auth.exchangeCodeForSession(\n params.code,\n params.sb_flow_id ? { flowId: params.sb_flow_id } : undefined,\n );\n if (error) return { error: new GenesisAuthError(error.code ?? \"exchange_failed\", error.message, { cause: error }) };\n if (!data.session) return { error: new GenesisAuthError(\"no_session\", \"The code exchange returned no session\") };\n // supabase-js returns redirectType at run time without typing it.\n const redirectType = (data as { redirectType?: string | null }).redirectType ?? null;\n return { session: data.session, redirectType };\n } catch (e) {\n return { error: new GenesisAuthError(\"exchange_failed\", e instanceof Error ? e.message : \"The code exchange failed\", { cause: e }) };\n }\n}\n\n/** A person's name as `expo-apple-authentication` returns it. */\nexport interface AppleFullNameLike {\n givenName?: string | null;\n middleName?: string | null;\n familyName?: string | null;\n}\n\n/** The parts of `expo-apple-authentication` that {@link signInWithApple} uses. Pass the module itself. */\nexport interface AppleAuthenticationLike {\n isAvailableAsync(): Promise<boolean>;\n signInAsync(options: { requestedScopes?: number[]; nonce?: string }): Promise<{\n identityToken: string | null;\n /** Single-use, valid about five minutes: what a server exchanges for the Apple refresh token it revokes on deletion. */\n authorizationCode: string | null;\n fullName: AppleFullNameLike | null;\n email: string | null;\n }>;\n AppleAuthenticationScope: { FULL_NAME: number; EMAIL: number };\n}\n\n/** @stability Beta */\nexport interface SignInWithAppleOptions {\n /**\n * SHA-256 of a string as lowercase hex, e.g.\n * `(s) => Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, s)` from `expo-crypto`.\n * Apple gets the hash; Supabase gets the raw nonce and checks the token against it.\n */\n digest(value: string): Promise<string>;\n /**\n * A fresh random nonce. Default: 32 bytes from `crypto.getRandomValues`, as\n * hex. On React Native that is the expo-crypto polyfill\n * `createGenesisSupabase({ platform: \"native\", crypto })` installs; leave\n * this out rather than pass something with less randomness.\n */\n randomNonce?: () => string;\n}\n\n/** @stability Beta */\nexport type SignInWithAppleResult =\n | {\n session: Session;\n user: User;\n /** The name Apple shared. Apple sends it on the first sign-in only: save it then. */\n fullName: string | null;\n /** The email Apple shared (possibly a private relay address), on the first sign-in only. */\n email: string | null;\n /**\n * Apple's single-use authorization code (valid about five minutes). Send it\n * to your server straight away: it exchanges it for the Apple refresh token\n * that account deletion must revoke. See the README.\n */\n authorizationCode: string | null;\n canceled?: undefined;\n error?: undefined;\n }\n | { canceled: true; session?: undefined; error?: undefined }\n | {\n error: GenesisAuthError;\n /** When Apple shared a name but Supabase then refused: Apple won't send it again, so keep it for the retry. */\n fullName?: string | null;\n session?: undefined;\n canceled?: undefined;\n };\n\n/**\n * Native Sign in with Apple straight into a Supabase session: checks the\n * device supports it, asks for name and email with a SHA-256 hashed nonce, and\n * signs in with the identity token. Closing Apple's sheet is `{ canceled: true }`,\n * not an error. Nothing is thrown; every failure is `{ error }`.\n *\n * @stability Beta\n */\nexport async function signInWithApple(\n client: SignInClient,\n apple: AppleAuthenticationLike,\n { digest, randomNonce = defaultNonce }: SignInWithAppleOptions,\n): Promise<SignInWithAppleResult> {\n const unavailable = (cause?: unknown) => ({\n error: new GenesisAuthError(\"apple_unavailable\", \"Sign in with Apple isn't available on this device\", { cause }),\n });\n try {\n if (!(await apple.isAvailableAsync())) return unavailable();\n } catch (e) {\n return unavailable(e);\n }\n let credential: Awaited<ReturnType<AppleAuthenticationLike[\"signInAsync\"]>>;\n let nonce: string;\n try {\n nonce = randomNonce();\n const hashed = await digest(nonce);\n if (!/^[0-9a-f]{64}$/.test(hashed)) {\n return { error: new GenesisAuthError(\"bad_digest\", \"signInWithApple: digest must return SHA-256 as 64 lowercase hex characters\") };\n }\n credential = await apple.signInAsync({\n requestedScopes: [apple.AppleAuthenticationScope.FULL_NAME, apple.AppleAuthenticationScope.EMAIL],\n nonce: hashed,\n });\n } catch (e) {\n if ((e as { code?: unknown } | null)?.code === \"ERR_REQUEST_CANCELED\") return { canceled: true };\n return { error: new GenesisAuthError(\"apple_failed\", e instanceof Error ? e.message : \"Sign in with Apple failed\", { cause: e }) };\n }\n const fullName = formatFullName(credential.fullName);\n if (!credential.identityToken) return { error: new GenesisAuthError(\"apple_no_token\", \"Apple returned no identity token\"), fullName };\n try {\n const { data, error } = await client.auth.signInWithIdToken({ provider: \"apple\", token: credential.identityToken, nonce });\n if (error) return { error: new GenesisAuthError(error.code ?? \"sign_in_failed\", error.message, { cause: error }), fullName };\n if (!data.session || !data.user) return { error: new GenesisAuthError(\"no_session\", \"Sign-in returned no session\"), fullName };\n return {\n session: data.session,\n user: data.user,\n fullName,\n email: credential.email ?? null,\n authorizationCode: credential.authorizationCode ?? null,\n };\n } catch (e) {\n return { error: new GenesisAuthError(\"sign_in_failed\", e instanceof Error ? e.message : \"Sign-in failed\", { cause: e }), fullName };\n }\n}\n\n/**\n * The part of `@react-native-google-signin/google-signin` that\n * {@link signInWithGoogle} uses: its Universal sign-in module,\n * `GoogleOneTapSignIn`, configured (`configure({ webClientId, iosClientId })`)\n * before the first sign-in. Pass the module itself.\n *\n * Only Universal sign-in passes a nonce into the ID token on Android and iOS.\n * The free package's original `GoogleSignin.signIn` can't (its `nonce` is iOS\n * and paid-version only), so its tokens fail the nonce check below.\n */\nexport interface GoogleSignInLike {\n authenticate(params?: { nonce?: string }): Promise<{\n user?: { idToken?: string | null; user?: { email?: string | null; name?: string | null } | null } | null;\n error?: { code?: string | number; message?: string } | null;\n isCancelled?: boolean;\n }>;\n}\n\n/** @stability Beta */\nexport interface SignInWithGoogleOptions {\n /** SHA-256 of a string as lowercase hex, as for {@link signInWithApple}. Google gets the hash; Supabase gets the raw nonce. */\n digest(value: string): Promise<string>;\n /** A fresh random nonce. Default: 32 bytes from `crypto.getRandomValues`, as hex. */\n randomNonce?: () => string;\n}\n\n/** @stability Beta */\nexport type SignInWithGoogleResult =\n | {\n session: Session;\n user: User;\n /** The name Google shared, if any. */\n fullName: string | null;\n email: string | null;\n canceled?: undefined;\n error?: undefined;\n }\n | { canceled: true; session?: undefined; error?: undefined }\n | { error: GenesisAuthError; session?: undefined; canceled?: undefined };\n\n/**\n * Native Google sign-in straight into a Supabase session, as Supabase's\n * Google guide describes: a fresh random nonce, its SHA-256 (hex) to Google,\n * which puts it in the ID token, and the raw nonce to `signInWithIdToken`,\n * which checks the two match, so a token minted for another sign-in, which\n * carries that sign-in's nonce, can't be replayed through this app. Before\n * Supabase sees the token it is checked here too: a token whose `nonce` claim\n * isn't this sign-in's hash (a module that ignored the nonce) is\n * `google_nonce_missing`, never sent. Closing Google's sheet is\n * `{ canceled: true }`. Nothing is thrown; every failure is `{ error }`.\n *\n * Keep the Supabase Google provider's \"Skip nonce check\" off: this helper\n * needs no exception, and turning it on lets a stolen nonce-bearing ID token\n * sign in. What a nonce can't stop: Supabase still accepts an ID token with\n * no nonce claim when the caller sends none, so a stolen nonce-less token for\n * one of your client ids signs in through a direct API call whatever the app\n * does. Keep the \"Authorized Client IDs\" list to your own apps.\n *\n * @stability Beta\n */\nexport async function signInWithGoogle(\n client: SignInClient,\n google: GoogleSignInLike,\n { digest, randomNonce = defaultNonce }: SignInWithGoogleOptions,\n): Promise<SignInWithGoogleResult> {\n let nonce: string;\n let hashed: string;\n let response: Awaited<ReturnType<GoogleSignInLike[\"authenticate\"]>>;\n try {\n nonce = randomNonce();\n hashed = await digest(nonce);\n if (!/^[0-9a-f]{64}$/.test(hashed)) {\n return { error: new GenesisAuthError(\"bad_digest\", \"signInWithGoogle: digest must return SHA-256 as 64 lowercase hex characters\") };\n }\n response = await google.authenticate({ nonce: hashed });\n } catch (e) {\n return { error: new GenesisAuthError(\"google_failed\", e instanceof Error ? e.message : \"Google sign-in failed\", { cause: e }) };\n }\n if (response.isCancelled) return { canceled: true };\n if (response.error) {\n return { error: new GenesisAuthError(\"google_failed\", response.error.message ?? `Google sign-in failed (${String(response.error.code ?? \"unknown\")})`, { cause: response.error }) };\n }\n const token = response.user?.idToken;\n if (!token) return { error: new GenesisAuthError(\"google_no_token\", \"Google returned no ID token\") };\n if (jwtClaim(token, \"nonce\") !== hashed) {\n return {\n error: new GenesisAuthError(\n \"google_nonce_missing\",\n \"Google's ID token doesn't carry this sign-in's nonce. Use the Universal sign-in module (GoogleOneTapSignIn) of @react-native-google-signin/google-signin; the original GoogleSignin can't pass one.\",\n ),\n };\n }\n try {\n const { data, error } = await client.auth.signInWithIdToken({ provider: \"google\", token, nonce });\n if (error) return { error: new GenesisAuthError(error.code ?? \"sign_in_failed\", error.message, { cause: error }) };\n if (!data.session || !data.user) return { error: new GenesisAuthError(\"no_session\", \"Sign-in returned no session\") };\n const profile = response.user?.user;\n return { session: data.session, user: data.user, fullName: profile?.name?.trim() || null, email: profile?.email ?? null };\n } catch (e) {\n return { error: new GenesisAuthError(\"sign_in_failed\", e instanceof Error ? e.message : \"Sign-in failed\", { cause: e }) };\n }\n}\n\n/** One string claim of a JWT's payload, unverified (Supabase verifies the signature), or undefined. */\nfunction jwtClaim(token: string, claim: string): string | undefined {\n const payload = token.split(\".\")[1];\n if (!payload) return undefined;\n try {\n const value: unknown = JSON.parse(decodeBase64Url(payload))?.[claim];\n return typeof value === \"string\" ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n\n/** base64url to a UTF-8 string, without atob, Buffer or TextDecoder (not on every React Native runtime). */\nfunction decodeBase64Url(input: string): string {\n let escaped = \"\";\n let buffer = 0;\n let bits = 0;\n for (const ch of input.replace(/=+$/, \"\")) {\n const v = B64.indexOf(ch === \"+\" ? \"-\" : ch === \"/\" ? \"_\" : ch);\n if (v < 0) throw new Error(\"not base64url\");\n buffer = (buffer << 6) | v;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n escaped += `%${((buffer >> bits) & 0xff).toString(16).padStart(2, \"0\")}`;\n buffer &= (1 << bits) - 1;\n }\n }\n return decodeURIComponent(escaped);\n}\n\n/** \"Given Middle Family\", or null when Apple shared no name. */\nexport function formatFullName(name: AppleFullNameLike | null | undefined): string | null {\n if (!name) return null;\n return [name.givenName, name.middleName, name.familyName].map((p) => p?.trim()).filter(Boolean).join(\" \") || null;\n}\n\nfunction defaultNonce(): string {\n const c = (globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => Uint8Array } }).crypto;\n // A nonce is a secret: no fallback to anything weaker than the platform's CSPRNG.\n if (!c?.getRandomValues) throw new Error(\"[genesis] sign-in nonce: no crypto.getRandomValues here; install react-native-get-random-values or expo-crypto's getRandomValues polyfill, or pass randomNonce returning 32 random bytes\");\n return Array.from(c.getRandomValues(new Uint8Array(32)), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n","/**\n * React Native helpers for a Supabase session. Nothing here imports React\n * Native or Expo: pass in `expo-secure-store`, `AppState`,\n * `expo-apple-authentication` and a plain store, so the package stays light\n * and the helpers are easy to test.\n */\nimport type { SessionStorage } from \"./index\";\n\nexport {\n assertPkceCrypto,\n hasPkceCrypto,\n installWebCrypto,\n isDigestOnlyPolyfill,\n type ExpoCryptoLike,\n type WebCryptoSource,\n} from \"./web-crypto\";\n\nexport {\n GenesisAuthError,\n completeOAuthRedirect,\n formatFullName,\n redirectParams,\n signInWithApple,\n signInWithGoogle,\n type AppleAuthenticationLike,\n type AppleFullNameLike,\n type GoogleSignInLike,\n type OAuthRedirectResult,\n type SignInClient,\n type SignInWithAppleOptions,\n type SignInWithAppleResult,\n type SignInWithGoogleOptions,\n type SignInWithGoogleResult,\n} from \"./sign-in\";\n\n/** The parts of `expo-secure-store` the Keychain storage uses. */\nexport interface SecureStoreLike {\n getItemAsync(key: string, options?: Record<string, unknown>): Promise<string | null>;\n setItemAsync(key: string, value: string, options?: Record<string, unknown>): Promise<void>;\n deleteItemAsync(key: string, options?: Record<string, unknown>): Promise<void>;\n /** `SecureStore.AFTER_FIRST_UNLOCK`, when available. */\n AFTER_FIRST_UNLOCK?: unknown;\n}\n\n/** @stability Beta */\nexport interface KeychainStorageOptions {\n /** UTF-8 bytes per Keychain item. A Keychain item should stay under about 2 KB; a Supabase session is larger. Default 1800. */\n chunkSize?: number;\n /** Extra options for every SecureStore call. Default: readable after first unlock, so a background refresh works. */\n secureStoreOptions?: Record<string, unknown>;\n}\n\n/**\n * Session storage in the Keychain (iOS) or Keystore (Android).\n *\n * A value is split into numbered chunks. Each write goes to a new generation of\n * chunks, and one manifest item (`<key>.n` = `generation:count`) switches to it\n * only once every chunk is written, so a write cut short leaves the previous\n * session whole. Chunks split between characters (never inside one) and\n * stay under `chunkSize` UTF-8 bytes. A pending marker (`<key>.p`) names a\n * generation while it is being written, so chunks left by a write that was\n * cut short or crashed are swept on the next write or remove. Writes to the\n * same key run one at a time, so two token refreshes can't interleave.\n *\n * @stability Beta\n */\nexport function keychainStorage(secureStore: SecureStoreLike, options: KeychainStorageOptions = {}): SessionStorage {\n const size = options.chunkSize ?? 1800;\n const opts = options.secureStoreOptions ?? (secureStore.AFTER_FIRST_UNLOCK !== undefined ? { keychainAccessible: secureStore.AFTER_FIRST_UNLOCK } : {});\n const chunkKey = (key: string, gen: string, i: number) => `${key}.${gen}.${i}`;\n let counter = 0;\n const queues = new Map<string, Promise<unknown>>();\n\n /** Run `task` after every earlier write to `key` has settled. */\n const serial = <T>(key: string, task: () => Promise<T>): Promise<T> => {\n const run = (queues.get(key) ?? Promise.resolve()).catch(() => undefined).then(task);\n queues.set(key, run);\n return run;\n };\n\n const parse = (raw: string | null) => {\n const [gen, count] = (raw ?? \"\").split(\":\");\n return gen && Number(count) > 0 ? { gen, count: Number(count) } : null;\n };\n const manifest = async (key: string) => parse(await secureStore.getItemAsync(`${key}.n`, opts));\n const pending = async (key: string) => parse(await secureStore.getItemAsync(`${key}.p`, opts));\n const drop = (key: string, gen: string, count: number) =>\n Promise.all(Array.from({ length: count }, (_, i) => secureStore.deleteItemAsync(chunkKey(key, gen, i), opts)));\n\n /** Remove chunks of a write that never completed (unless they are the current generation). */\n const sweepPending = async (key: string, current: string | undefined) => {\n const p = await pending(key);\n if (!p) return;\n if (p.gen !== current) await drop(key, p.gen, p.count);\n await secureStore.deleteItemAsync(`${key}.p`, opts);\n };\n\n return {\n getItem(key) {\n // Reads queue with writes, so a write can't swap the manifest and drop\n // the old chunks halfway through a read.\n return serial(key, async () => {\n const m = await manifest(key);\n if (!m) return null;\n const parts = await Promise.all(Array.from({ length: m.count }, (_, i) => secureStore.getItemAsync(chunkKey(key, m.gen, i), opts)));\n return parts.some((p) => p === null) ? null : parts.join(\"\");\n });\n },\n setItem(key, value) {\n return serial(key, async () => {\n const before = await manifest(key);\n await sweepPending(key, before?.gen);\n const gen = `${Date.now().toString(36)}${(counter++).toString(36)}`;\n const parts = splitUtf8(value, size);\n await secureStore.setItemAsync(`${key}.p`, `${gen}:${parts.length}`, opts);\n // Let every chunk write settle before failing, so a late write can't land\n // after the next operation has swept this generation.\n const written = await Promise.allSettled(parts.map((p, i) => secureStore.setItemAsync(chunkKey(key, gen, i), p, opts)));\n const failed = written.find((r): r is PromiseRejectedResult => r.status === \"rejected\");\n if (failed) throw failed.reason;\n await secureStore.setItemAsync(`${key}.n`, `${gen}:${parts.length}`, opts);\n await secureStore.deleteItemAsync(`${key}.p`, opts);\n if (before) await drop(key, before.gen, before.count);\n });\n },\n removeItem(key) {\n return serial(key, async () => {\n const m = await manifest(key);\n await sweepPending(key, m?.gen);\n // Chunks first: the manifest is what finds them, so it goes last.\n if (m) await drop(key, m.gen, m.count);\n await secureStore.deleteItemAsync(`${key}.n`, opts);\n });\n },\n };\n}\n\n/** The parts of a Supabase client's auth the refresh helper uses. */\nexport interface AutoRefreshAuth {\n auth: { startAutoRefresh(): Promise<void> | void; stopAutoRefresh(): Promise<void> | void };\n}\n\n/** The parts of React Native's `AppState` the refresh helper uses. */\nexport interface AppStateLike {\n currentState?: string | null;\n addEventListener(type: \"change\", listener: (state: string) => void): { remove(): void };\n}\n\n/**\n * Refresh the session only while the app is in front, as Supabase recommends\n * for React Native. Returns the unsubscribe.\n *\n * @stability Beta\n */\nexport function refreshWhileActive(client: AutoRefreshAuth, appState: AppStateLike): () => void {\n const apply = (state: string | null | undefined) => {\n if (state === \"active\") void client.auth.startAutoRefresh();\n else void client.auth.stopAutoRefresh();\n };\n apply(appState.currentState ?? \"active\");\n const sub = appState.addEventListener(\"change\", apply);\n return () => {\n sub.remove();\n void client.auth.stopAutoRefresh();\n };\n}\n\n/** @stability Beta */\nexport interface WipeOnReinstallOptions {\n /** The session storage (usually `keychainStorage`). */\n session: SessionStorage;\n /** The session's storage key. Supabase's default is `sb-<project ref>-auth-token`. */\n sessionKey: string;\n /** A store that an uninstall deletes, e.g. AsyncStorage. The Keychain survives an uninstall; this is how we notice one. */\n installMarker: SessionStorage;\n /** The marker's key. Default `genesis.installed`. */\n markerKey?: string;\n}\n\n/**\n * On iOS the Keychain outlives an uninstall, so a reinstalled app would sign\n * the last person back in. Call this before creating the client: on the first\n * launch after an install it removes any session left in the Keychain, then\n * sets a marker so later launches keep the session. Returns whether it wiped.\n *\n * @stability Beta\n */\nexport async function wipeSessionOnReinstall({ session, sessionKey, installMarker, markerKey = \"genesis.installed\" }: WipeOnReinstallOptions): Promise<boolean> {\n if ((await installMarker.getItem(markerKey)) !== null) return false;\n // The session, plus what supabase-js keeps beside it: the PKCE verifier (the\n // single legacy slot, each indexed flow's slot and the index) and, with\n // userStorage, the user.\n const indexKey = `${sessionKey}-flows-code-verifier`;\n for (const flowId of parseFlowIndex(await session.getItem(indexKey))) await session.removeItem(`${sessionKey}-flow-${flowId}-code-verifier`);\n for (const key of [sessionKey, `${sessionKey}-code-verifier`, indexKey, `${sessionKey}-user`]) await session.removeItem(key);\n await installMarker.setItem(markerKey, \"1\");\n return true;\n}\n\n/** UTF-8 byte length of one code point. */\nconst utf8Length = (codePoint: number) => (codePoint < 0x80 ? 1 : codePoint < 0x800 ? 2 : codePoint < 0x10000 ? 3 : 4);\n\n/** Split text into pieces of at most `maxBytes` UTF-8 bytes, never inside a character. */\nexport function splitUtf8(text: string, maxBytes: number): string[] {\n if (text === \"\") return [\"\"];\n const parts: string[] = [];\n let current = \"\";\n let bytes = 0;\n for (const ch of text) {\n const n = utf8Length(ch.codePointAt(0)!);\n if (bytes + n > maxBytes && current) {\n parts.push(current);\n current = \"\";\n bytes = 0;\n }\n current += ch;\n bytes += n;\n }\n parts.push(current);\n return parts;\n}\n\n/** supabase-js's PKCE flow index: a JSON array of flow ids. Anything else is ignored. */\nfunction parseFlowIndex(raw: string | null): string[] {\n if (!raw) return [];\n try {\n const ids: unknown = JSON.parse(raw);\n return Array.isArray(ids) ? ids.filter((id): id is string => typeof id === \"string\" && /^[\\w-]{1,128}$/.test(id)) : [];\n } catch {\n return [];\n }\n}\n"]}
|