@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web-crypto.ts"],"names":["error"],"mappings":"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","file":"chunk-FAA2FTF4.js","sourcesContent":["/**\n * The two WebCrypto calls supabase-js's PKCE flow needs, for runtimes that\n * lack them (Hermes in React Native has no `crypto.subtle`).\n *\n * supabase-js makes the PKCE code verifier with `crypto.getRandomValues` and\n * the S256 code challenge with `crypto.subtle.digest(\"SHA-256\", …)`. When\n * `crypto.subtle` is missing it warns and sends the verifier itself as a\n * \"plain\" challenge; when `crypto` is missing altogether it also makes the\n * verifier with `Math.random`. Either way an intercepted redirect is enough to\n * finish someone else's sign-in. This installs both calls on `globalThis.crypto`\n * from `expo-crypto`, which hashes and draws random bytes natively.\n *\n * Platform-neutral: nothing here imports Expo. Pass the module in.\n */\n\n/** The parts of `expo-crypto` the polyfill uses: `import * as ExpoCrypto from \"expo-crypto\"`. */\nexport interface ExpoCryptoLike {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getRandomValues(typedArray: any): any;\n /** `algorithm` is `CryptoDigestAlgorithm`, whose SHA-256 member is the string `\"SHA-256\"`. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n digest(algorithm: any, data: BufferSource): Promise<ArrayBuffer>;\n}\n\n/** What {@link installWebCrypto} found: real WebCrypto left alone, or the polyfill in place. */\nexport type WebCryptoSource = \"native\" | \"polyfill\";\n\ninterface CryptoLike {\n getRandomValues?: (array: ArrayBufferView) => ArrayBufferView;\n subtle?: { digest?: (algorithm: AlgorithmIdentifier, data: BufferSource) => Promise<ArrayBuffer> };\n}\n\nconst POLYFILL = Symbol.for(\"genesis.webCryptoPolyfill\");\n\n/** WebCrypto's quota for one `getRandomValues` call. */\nconst MAX_RANDOM_BYTES = 65536;\n\nfunction algorithmName(algorithm: AlgorithmIdentifier): string {\n const name = typeof algorithm === \"string\" ? algorithm : (algorithm as { name?: unknown } | null)?.name;\n return typeof name === \"string\" ? name.toUpperCase() : \"\";\n}\n\nconst INTEGER_ARRAYS = [\"Int8Array\", \"Uint8Array\", \"Uint8ClampedArray\", \"Int16Array\", \"Uint16Array\", \"Int32Array\", \"Uint32Array\", \"BigInt64Array\", \"BigUint64Array\"];\n\nfunction isIntegerArray(value: unknown): value is ArrayBufferView {\n return ArrayBuffer.isView(value) && INTEGER_ARRAYS.includes(Object.prototype.toString.call(value).slice(8, -1));\n}\n\nfunction cryptoOf(target: object): CryptoLike | undefined {\n return (target as { crypto?: CryptoLike }).crypto;\n}\n\n/**\n * True when `target` has everything supabase-js needs for an S256 PKCE\n * challenge made from secure random bytes.\n *\n * @stability Beta\n */\nexport function hasPkceCrypto(target: object = globalThis): boolean {\n const g = target as { TextEncoder?: unknown; btoa?: unknown };\n const crypto = cryptoOf(target);\n return (\n typeof crypto?.getRandomValues === \"function\" &&\n typeof crypto?.subtle?.digest === \"function\" &&\n typeof g.TextEncoder === \"function\" &&\n typeof g.btoa === \"function\"\n );\n}\n\n/**\n * Give `globalThis.crypto` a `getRandomValues` and a SHA-256\n * `subtle.digest`, backed by `expo-crypto`, when the runtime has none. Real\n * WebCrypto is never replaced: a `crypto` that has one of the two keeps it.\n * Only SHA-256 is offered: `subtle.digest` rejects any other algorithm, and no\n * other `subtle` method is added, so a library that needs more fails instead\n * of getting a stand-in. Safe to call more than once.\n *\n * `createGenesisSupabase({ platform: \"native\", crypto })` calls this for you.\n *\n * @stability Beta\n */\nexport function installWebCrypto(expoCrypto: ExpoCryptoLike, target: object = globalThis): WebCryptoSource {\n if (typeof expoCrypto?.getRandomValues !== \"function\" || typeof expoCrypto?.digest !== \"function\") {\n throw new TypeError('installWebCrypto needs expo-crypto: pass `import * as ExpoCrypto from \"expo-crypto\"`.');\n }\n const existing = cryptoOf(target);\n const realRandom = existing?.getRandomValues;\n const realSubtle = existing?.subtle;\n if (typeof realRandom === \"function\" && typeof realSubtle?.digest === \"function\") {\n return (existing as Record<symbol, unknown>)[POLYFILL] ? \"polyfill\" : \"native\";\n }\n\n const getRandomValues =\n typeof realRandom === \"function\"\n ? realRandom.bind(existing)\n : <T extends ArrayBufferView>(array: T): T => {\n if (!isIntegerArray(array)) throw new TypeError(\"crypto.getRandomValues: expected an integer TypedArray.\");\n if (array.byteLength > MAX_RANDOM_BYTES) {\n throw new RangeError(`crypto.getRandomValues: ${array.byteLength} bytes is over the ${MAX_RANDOM_BYTES}-byte quota.`);\n }\n // Through a byte view, so every element of any TypedArray gets random bits in every byte.\n const bytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);\n const filled: unknown = expoCrypto.getRandomValues(bytes);\n if (filled !== bytes) {\n if (!(filled instanceof Uint8Array) || filled.byteLength !== bytes.byteLength) {\n throw new Error(\"crypto.getRandomValues: expo-crypto returned no random bytes.\");\n }\n bytes.set(filled);\n }\n return array;\n };\n\n const digest = async (algorithm: AlgorithmIdentifier, data: BufferSource): Promise<ArrayBuffer> => {\n const name = algorithmName(algorithm);\n if (name !== \"SHA-256\") throw new Error(`crypto.subtle.digest: only SHA-256 is polyfilled, not ${name || String(algorithm)}.`);\n let bytes: Uint8Array;\n if (ArrayBuffer.isView(data)) bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n else if (data instanceof ArrayBuffer) bytes = new Uint8Array(data);\n else throw new TypeError(\"crypto.subtle.digest: expected an ArrayBuffer or a TypedArray.\");\n // A copy, so the hash reads these bytes even if the caller reuses its buffer.\n return expoCrypto.digest(\"SHA-256\", bytes.slice());\n };\n // A runtime's own `subtle` (without digest) stays the prototype, so none of its methods are lost.\n const subtle = typeof realSubtle?.digest === \"function\" ? realSubtle : Object.assign(Object.create(realSubtle ?? Object.prototype) as object, { digest });\n\n const crypto = Object.assign(Object.create(existing ?? Object.prototype) as object, { getRandomValues, subtle, [POLYFILL]: true });\n Object.defineProperty(target, \"crypto\", { value: crypto, configurable: true, enumerable: false, writable: true });\n return \"polyfill\";\n}\n\n/**\n * True when `globalThis.crypto.subtle` is this polyfill's digest-only stand-in\n * (no `importKey` or `verify`), which supabase-js's `getClaims` can't use.\n */\nexport function isDigestOnlyPolyfill(target: object = globalThis): boolean {\n const crypto = cryptoOf(target) as (CryptoLike & Record<symbol, unknown>) | undefined;\n return Boolean(crypto?.[POLYFILL]) && typeof (crypto?.subtle as { importKey?: unknown } | undefined)?.importKey !== \"function\";\n}\n\n/** The parts of supabase-js's auth client that {@link claimsViaServer} uses. */\nexport interface ClaimsAuthClient {\n getSession(): Promise<{ data: { session: { access_token: string } | null }; error: unknown }>;\n getUser(jwt?: string): Promise<{ error: unknown }>;\n}\n\n/** A JWT's three parts, decoded (header and payload as JSON, the signature as bytes). */\nfunction decodeJwt(token: string): { header: Record<string, unknown>; payload: Record<string, unknown>; signature: Uint8Array } | null {\n const parts = token.split(\".\");\n if (parts.length !== 3 || parts.some((p) => !/^[A-Za-z0-9_-]*$/.test(p))) return null;\n const bytes = (part: string) => {\n const binary = atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((part.length + 3) % 4));\n return Uint8Array.from(binary, (c) => c.charCodeAt(0));\n };\n try {\n const json = (part: string) => JSON.parse(new TextDecoder().decode(bytes(part))) as Record<string, unknown>;\n return { header: json(parts[0]!), payload: json(parts[1]!), signature: bytes(parts[2]!) };\n } catch {\n return null;\n }\n}\n\n/**\n * `auth.getClaims` for a runtime whose `crypto.subtle` is the digest-only\n * polyfill. supabase-js verifies an asymmetric JWT locally whenever\n * `crypto.subtle` exists, and would throw on the missing `importKey`; this\n * always takes supabase-js's own fallback instead: the server checks the\n * token (`getUser`), and the claims are returned only if it accepts it.\n * Same result shape as `getClaims`.\n */\nexport function claimsViaServer(auth: ClaimsAuthClient, invalidJwt: (message: string) => Error) {\n return async (jwt?: string, options: { allowExpired?: boolean } = {}) => {\n let token = jwt;\n if (!token) {\n const { data, error } = await auth.getSession();\n if (error || !data.session) return { data: null, error: error ?? null };\n token = data.session.access_token;\n }\n const decoded = decodeJwt(token);\n if (!decoded) return { data: null, error: invalidJwt(\"Invalid JWT structure\") };\n if (!options.allowExpired) {\n const exp = decoded.payload.exp;\n if (typeof exp !== \"number\") return { data: null, error: invalidJwt(\"Missing exp claim\") };\n if (exp <= Math.floor(Date.now() / 1000)) return { data: null, error: invalidJwt(\"JWT has expired\") };\n }\n const { error } = await auth.getUser(token);\n if (error) return { data: null, error };\n return { data: { claims: decoded.payload, header: decoded.header, signature: decoded.signature }, error: null };\n };\n}\n\ndeclare const __DEV__: boolean | undefined;\n\nfunction isDev(): boolean {\n if (typeof __DEV__ !== \"undefined\") return Boolean(__DEV__);\n return typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\";\n}\n\n/**\n * Fail loudly when PKCE would fall back to \"plain\": throws in development,\n * logs an error in a release build (a crash at launch helps no one there).\n *\n * @stability Beta\n */\nexport function assertPkceCrypto(target: object = globalThis): void {\n if (hasPkceCrypto(target)) return;\n const message =\n 'Genesis Supabase: this runtime has no WebCrypto (crypto.getRandomValues and crypto.subtle.digest), so supabase-js would send PKCE challenges as \"plain\". ' +\n 'Pass expo-crypto: createGenesisSupabase({ platform: \"native\", crypto: ExpoCrypto, … }) with `import * as ExpoCrypto from \"expo-crypto\"`.';\n if (isDev()) throw new Error(message);\n console.error(message);\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineBlock } from '@marktiderman/genesis-features/blocks';
|
|
2
|
+
|
|
3
|
+
// src/blocks.ts
|
|
4
|
+
var PACKAGE = "@marktiderman/genesis-supabase";
|
|
5
|
+
var accountBlock = defineBlock({
|
|
6
|
+
id: "account",
|
|
7
|
+
platforms: ["web", "native"],
|
|
8
|
+
contributes: {
|
|
9
|
+
migrations: [
|
|
10
|
+
{ package: PACKAGE, file: "migrations/0001_genesis_account_deletion.sql", test: "migrations/tests/0001_genesis_account_deletion.test.sql" },
|
|
11
|
+
// With Sign in with Apple, with the genesis-apple-revoke function beside it.
|
|
12
|
+
{ package: PACKAGE, file: "migrations/0003_genesis_apple_token_revocation.sql", test: "migrations/tests/0003_genesis_apple_token_revocation.test.sql" }
|
|
13
|
+
],
|
|
14
|
+
privacy: {
|
|
15
|
+
collects: [
|
|
16
|
+
{ type: "email address", purposes: ["app functionality"], linked: true, tracking: false },
|
|
17
|
+
{ type: "user id", purposes: ["app functionality"], linked: true, tracking: false },
|
|
18
|
+
{ type: "name", purposes: ["app functionality"], linked: true, tracking: false }
|
|
19
|
+
],
|
|
20
|
+
notes: [
|
|
21
|
+
"People delete their account from inside the app; their login and the rows that cascade from it go with it.",
|
|
22
|
+
"With Sign in with Apple, the person's Apple tokens are revoked when the account is deleted."
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
var tenantFeaturesBlock = defineBlock({
|
|
28
|
+
id: "tenant-features",
|
|
29
|
+
contributes: {
|
|
30
|
+
migrations: [{ package: PACKAGE, file: "migrations/0004_genesis_tenant_features.sql", test: "migrations/tests/0004_genesis_tenant_features.test.sql" }],
|
|
31
|
+
privacy: { notes: ["Keeps who changed a tenant's switches; a deleted person's changes stay, without the person."] }
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export { accountBlock, tenantFeaturesBlock };
|
|
36
|
+
//# sourceMappingURL=chunk-T2HGFC4C.js.map
|
|
37
|
+
//# sourceMappingURL=chunk-T2HGFC4C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/blocks.ts"],"names":[],"mappings":";;;AAQA,IAAM,OAAA,GAAU,gCAAA;AAST,IAAM,eAAe,WAAA,CAAY;AAAA,EACtC,EAAA,EAAI,SAAA;AAAA,EACJ,SAAA,EAAW,CAAC,KAAA,EAAO,QAAQ,CAAA;AAAA,EAC3B,WAAA,EAAa;AAAA,IACX,UAAA,EAAY;AAAA,MACV,EAAE,OAAA,EAAS,OAAA,EAAS,IAAA,EAAM,8CAAA,EAAgD,MAAM,yDAAA,EAA0D;AAAA;AAAA,MAE1I,EAAE,OAAA,EAAS,OAAA,EAAS,IAAA,EAAM,oDAAA,EAAsD,MAAM,+DAAA;AAAgE,KACxJ;AAAA,IACA,OAAA,EAAS;AAAA,MACP,QAAA,EAAU;AAAA,QACR,EAAE,IAAA,EAAM,eAAA,EAAiB,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA,EAAM;AAAA,QACxF,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA,EAAM;AAAA,QAClF,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA;AAAM,OACjF;AAAA,MACA,KAAA,EAAO;AAAA,QACL,4GAAA;AAAA,QACA;AAAA;AACF;AACF;AAEJ,CAAC;AAQM,IAAM,sBAAsB,WAAA,CAAY;AAAA,EAC7C,EAAA,EAAI,iBAAA;AAAA,EACJ,WAAA,EAAa;AAAA,IACX,UAAA,EAAY,CAAC,EAAE,OAAA,EAAS,SAAS,IAAA,EAAM,6CAAA,EAA+C,IAAA,EAAM,wDAAA,EAA0D,CAAA;AAAA,IACtJ,OAAA,EAAS,EAAE,KAAA,EAAO,CAAC,6FAA6F,CAAA;AAAE;AAEtH,CAAC","file":"chunk-T2HGFC4C.js","sourcesContent":["/**\n * The blocks this package carries the server half of, as `defineBlock`\n * declarations: what each needs in the database, and what it means for the\n * stores' privacy forms. The functions themselves (`deleteMyAccount`,\n * `tenantFeatures`, …) don't change.\n */\nimport { defineBlock } from \"@marktiderman/genesis-features/blocks\";\n\nconst PACKAGE = \"@marktiderman/genesis-supabase\";\n\n/**\n * The account block: sign-in, profile and in-app account deletion. Its\n * screens are the app's (or genesis-ui-native's `AuthScreen`); this names its\n * migrations and its data.\n *\n * @stability Beta\n */\nexport const accountBlock = defineBlock({\n id: \"account\",\n platforms: [\"web\", \"native\"],\n contributes: {\n migrations: [\n { package: PACKAGE, file: \"migrations/0001_genesis_account_deletion.sql\", test: \"migrations/tests/0001_genesis_account_deletion.test.sql\" },\n // With Sign in with Apple, with the genesis-apple-revoke function beside it.\n { package: PACKAGE, file: \"migrations/0003_genesis_apple_token_revocation.sql\", test: \"migrations/tests/0003_genesis_apple_token_revocation.test.sql\" },\n ],\n privacy: {\n collects: [\n { type: \"email address\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"user id\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"name\", purposes: [\"app functionality\"], linked: true, tracking: false },\n ],\n notes: [\n \"People delete their account from inside the app; their login and the rows that cascade from it go with it.\",\n \"With Sign in with Apple, the person's Apple tokens are revoked when the account is deleted.\",\n ],\n },\n },\n});\n\n/**\n * The tenant features block: per-tenant on/off switches for shipped\n * features, set by the tenant's admins with a reason.\n *\n * @stability Beta\n */\nexport const tenantFeaturesBlock = defineBlock({\n id: \"tenant-features\",\n contributes: {\n migrations: [{ package: PACKAGE, file: \"migrations/0004_genesis_tenant_features.sql\", test: \"migrations/tests/0004_genesis_tenant_features.test.sql\" }],\n privacy: { notes: [\"Keeps who changed a tenant's switches; a deleted person's changes stay, without the person.\"] },\n },\n});\n"]}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { SupabaseClientOptions, SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import './blocks.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The app's side of Sign in with Apple token revocation: hand the sign-in's
|
|
6
|
+
* authorization code to the `genesis-apple-revoke` Edge Function (it keeps
|
|
7
|
+
* Apple's refresh token server-side), and ask it to revoke after the account
|
|
8
|
+
* is deleted. The function and its migration ship in this package; see the
|
|
9
|
+
* README ("Revoking Apple's tokens").
|
|
10
|
+
*/
|
|
11
|
+
/** The Edge Function's default name, as shipped in `functions/genesis-apple-revoke`. */
|
|
12
|
+
declare const APPLE_REVOKE_FUNCTION = "genesis-apple-revoke";
|
|
13
|
+
/** The parts of a Supabase client these helpers use. A `SupabaseClient` fits. */
|
|
14
|
+
interface FunctionsClient {
|
|
15
|
+
functions: {
|
|
16
|
+
invoke: (name: string, options?: any) => PromiseLike<{
|
|
17
|
+
data: unknown;
|
|
18
|
+
error: unknown;
|
|
19
|
+
}>;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** @stability Beta */
|
|
23
|
+
interface StoreAppleTokenOptions {
|
|
24
|
+
/** `authorizationCode` from `signInWithApple`'s result. Single-use, valid for five minutes: send it straight after sign-in. */
|
|
25
|
+
authorizationCode: string;
|
|
26
|
+
/** 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. */
|
|
27
|
+
clientId?: string;
|
|
28
|
+
/** The Edge Function's name. Default `genesis-apple-revoke`. */
|
|
29
|
+
functionName?: string;
|
|
30
|
+
}
|
|
31
|
+
/** @stability Beta */
|
|
32
|
+
type StoreAppleTokenResult = {
|
|
33
|
+
ok: true;
|
|
34
|
+
} | {
|
|
35
|
+
ok: false;
|
|
36
|
+
error: Error;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* After a Sign in with Apple, sends the authorization code to the Edge
|
|
40
|
+
* Function, which exchanges it for Apple's refresh token and keeps it where
|
|
41
|
+
* only the server reads it, so deleting the account can revoke it. Call it
|
|
42
|
+
* with the session from that sign-in. Never throws: the person is signed in
|
|
43
|
+
* either way, so log a failure rather than showing it.
|
|
44
|
+
*
|
|
45
|
+
* @stability Beta
|
|
46
|
+
*/
|
|
47
|
+
declare function storeAppleToken(client: FunctionsClient, { authorizationCode, clientId, functionName }: StoreAppleTokenOptions): Promise<StoreAppleTokenResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Asks the Edge Function to revoke the tokens of deleted accounts now, rather
|
|
50
|
+
* than at its next scheduled run. Never throws and never fails the caller:
|
|
51
|
+
* the account is already gone and the queue keeps the token until a run
|
|
52
|
+
* succeeds.
|
|
53
|
+
*/
|
|
54
|
+
declare function requestAppleRevocation(client: FunctionsClient, functionName?: string): Promise<boolean>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The two WebCrypto calls supabase-js's PKCE flow needs, for runtimes that
|
|
58
|
+
* lack them (Hermes in React Native has no `crypto.subtle`).
|
|
59
|
+
*
|
|
60
|
+
* supabase-js makes the PKCE code verifier with `crypto.getRandomValues` and
|
|
61
|
+
* the S256 code challenge with `crypto.subtle.digest("SHA-256", …)`. When
|
|
62
|
+
* `crypto.subtle` is missing it warns and sends the verifier itself as a
|
|
63
|
+
* "plain" challenge; when `crypto` is missing altogether it also makes the
|
|
64
|
+
* verifier with `Math.random`. Either way an intercepted redirect is enough to
|
|
65
|
+
* finish someone else's sign-in. This installs both calls on `globalThis.crypto`
|
|
66
|
+
* from `expo-crypto`, which hashes and draws random bytes natively.
|
|
67
|
+
*
|
|
68
|
+
* Platform-neutral: nothing here imports Expo. Pass the module in.
|
|
69
|
+
*/
|
|
70
|
+
/** The parts of `expo-crypto` the polyfill uses: `import * as ExpoCrypto from "expo-crypto"`. */
|
|
71
|
+
interface ExpoCryptoLike {
|
|
72
|
+
getRandomValues(typedArray: any): any;
|
|
73
|
+
/** `algorithm` is `CryptoDigestAlgorithm`, whose SHA-256 member is the string `"SHA-256"`. */
|
|
74
|
+
digest(algorithm: any, data: BufferSource): Promise<ArrayBuffer>;
|
|
75
|
+
}
|
|
76
|
+
/** What {@link installWebCrypto} found: real WebCrypto left alone, or the polyfill in place. */
|
|
77
|
+
type WebCryptoSource = "native" | "polyfill";
|
|
78
|
+
/**
|
|
79
|
+
* True when `target` has everything supabase-js needs for an S256 PKCE
|
|
80
|
+
* challenge made from secure random bytes.
|
|
81
|
+
*
|
|
82
|
+
* @stability Beta
|
|
83
|
+
*/
|
|
84
|
+
declare function hasPkceCrypto(target?: object): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Give `globalThis.crypto` a `getRandomValues` and a SHA-256
|
|
87
|
+
* `subtle.digest`, backed by `expo-crypto`, when the runtime has none. Real
|
|
88
|
+
* WebCrypto is never replaced: a `crypto` that has one of the two keeps it.
|
|
89
|
+
* Only SHA-256 is offered: `subtle.digest` rejects any other algorithm, and no
|
|
90
|
+
* other `subtle` method is added, so a library that needs more fails instead
|
|
91
|
+
* of getting a stand-in. Safe to call more than once.
|
|
92
|
+
*
|
|
93
|
+
* `createGenesisSupabase({ platform: "native", crypto })` calls this for you.
|
|
94
|
+
*
|
|
95
|
+
* @stability Beta
|
|
96
|
+
*/
|
|
97
|
+
declare function installWebCrypto(expoCrypto: ExpoCryptoLike, target?: object): WebCryptoSource;
|
|
98
|
+
/**
|
|
99
|
+
* True when `globalThis.crypto.subtle` is this polyfill's digest-only stand-in
|
|
100
|
+
* (no `importKey` or `verify`), which supabase-js's `getClaims` can't use.
|
|
101
|
+
*/
|
|
102
|
+
declare function isDigestOnlyPolyfill(target?: object): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Fail loudly when PKCE would fall back to "plain": throws in development,
|
|
105
|
+
* logs an error in a release build (a crash at launch helps no one there).
|
|
106
|
+
*
|
|
107
|
+
* @stability Beta
|
|
108
|
+
*/
|
|
109
|
+
declare function assertPkceCrypto(target?: object): void;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Tenant features: the client half of `migrations/0004_genesis_tenant_features.sql`.
|
|
113
|
+
* A tenant's admins turn a shipped feature off (or back on) for their tenant,
|
|
114
|
+
* with a reason; its members read which ids are off and hide them.
|
|
115
|
+
*
|
|
116
|
+
* Exposure only: a switch hides a feature, it never grants or denies access
|
|
117
|
+
* to data (that is RLS) and never sells it (that is an entitlement).
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
/** The parts of a Supabase client these helpers use. A `SupabaseClient` fits. */
|
|
121
|
+
interface TenantFeaturesClient {
|
|
122
|
+
rpc: (fn: any, ...rest: any[]) => PromiseLike<{
|
|
123
|
+
data: unknown;
|
|
124
|
+
error: PostgrestErrorLike | null;
|
|
125
|
+
}>;
|
|
126
|
+
}
|
|
127
|
+
/** Which features a tenant has turned off. A feature not in `off` is on. @stability Beta */
|
|
128
|
+
interface TenantFeatures {
|
|
129
|
+
readonly tenantId: string;
|
|
130
|
+
readonly off: ReadonlySet<string>;
|
|
131
|
+
isOn(featureId: string): boolean;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The features `tenantId` has turned off, for one of its members (or
|
|
135
|
+
* admins): `tenant_features_off`. Throws a `SupabaseQueryError` (`42501`)
|
|
136
|
+
* for anyone else, so a refusal is never mistaken for "everything on".
|
|
137
|
+
*
|
|
138
|
+
* ```ts
|
|
139
|
+
* const features = await tenantFeatures(supabase, teamId);
|
|
140
|
+
* if (features.isOn("chat")) showChatTab();
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @stability Beta
|
|
144
|
+
*/
|
|
145
|
+
declare function tenantFeatures(client: TenantFeaturesClient, tenantId: string): Promise<TenantFeatures>;
|
|
146
|
+
/** @stability Beta */
|
|
147
|
+
interface SetTenantFeatureInput {
|
|
148
|
+
tenantId: string;
|
|
149
|
+
featureId: string;
|
|
150
|
+
enabled: boolean;
|
|
151
|
+
/** Why, in at most 500 characters. Required: it goes in the history. */
|
|
152
|
+
reason: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Turn a feature on or off for a tenant: `set_tenant_feature`. Only the
|
|
156
|
+
* tenant's admins (the app's `private.genesis_tenant_admin` hook); throws a
|
|
157
|
+
* `SupabaseQueryError` otherwise (`42501`), for a blank reason (`22023`) or
|
|
158
|
+
* a malformed feature id (`23514`).
|
|
159
|
+
*
|
|
160
|
+
* @stability Beta
|
|
161
|
+
*/
|
|
162
|
+
declare function setTenantFeature(client: TenantFeaturesClient, input: SetTenantFeatureInput): Promise<void>;
|
|
163
|
+
/** One switch a tenant has set. @stability Beta */
|
|
164
|
+
interface TenantFeatureSetting {
|
|
165
|
+
feature_id: string;
|
|
166
|
+
enabled: boolean;
|
|
167
|
+
reason: string;
|
|
168
|
+
updated_by: string | null;
|
|
169
|
+
updated_at: string;
|
|
170
|
+
}
|
|
171
|
+
/** Every switch a tenant has set, with its reason, for its admins: `tenant_feature_settings`. @stability Beta */
|
|
172
|
+
declare function tenantFeatureSettings(client: TenantFeaturesClient, tenantId: string): Promise<TenantFeatureSetting[]>;
|
|
173
|
+
/** One change to a tenant's switches. `enabled` is null when a switch row was deleted (back to on). @stability Beta */
|
|
174
|
+
interface TenantFeatureChange {
|
|
175
|
+
feature_id: string;
|
|
176
|
+
enabled: boolean | null;
|
|
177
|
+
reason: string | null;
|
|
178
|
+
/** The person, or null for the service role, a migration, or a person since deleted. */
|
|
179
|
+
changed_by: string | null;
|
|
180
|
+
changed_role: string;
|
|
181
|
+
changed_at: string;
|
|
182
|
+
}
|
|
183
|
+
/** A tenant's history of changes, newest first (at most `maxRows`, 1 to 1000, default 100), for its admins. @stability Beta */
|
|
184
|
+
declare function tenantFeatureHistory(client: TenantFeaturesClient, tenantId: string, options?: {
|
|
185
|
+
maxRows?: number;
|
|
186
|
+
}): Promise<TenantFeatureChange[]>;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Supabase for Genesis apps. Platform-neutral: web, React Native and the
|
|
190
|
+
* server import it. Native-only helpers (Keychain storage, foreground
|
|
191
|
+
* refresh, reinstall wipe) are on `@marktiderman/genesis-supabase/native`.
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
/** The async key-value store Supabase auth keeps its session in. */
|
|
195
|
+
interface SessionStorage {
|
|
196
|
+
getItem(key: string): Promise<string | null>;
|
|
197
|
+
setItem(key: string, value: string): Promise<void>;
|
|
198
|
+
removeItem(key: string): Promise<void>;
|
|
199
|
+
}
|
|
200
|
+
/** @stability Beta */
|
|
201
|
+
interface GenesisSupabaseOptions {
|
|
202
|
+
url: string;
|
|
203
|
+
/** The publishable (anon) key. Never the secret key in an app. */
|
|
204
|
+
publishableKey: string;
|
|
205
|
+
/** Where the session lives. Native: `keychainStorage(SecureStore)`. Web: leave out for supabase-js's own default. */
|
|
206
|
+
storage?: SessionStorage;
|
|
207
|
+
/** `"native"` sets the React Native defaults (no session from the URL). Default `"web"`. */
|
|
208
|
+
platform?: "web" | "native";
|
|
209
|
+
/**
|
|
210
|
+
* Read a session from the URL after an OAuth redirect. Default: `true` on
|
|
211
|
+
* web, `false` on native, where the app hands the redirect URL to
|
|
212
|
+
* `exchangeCodeForSession` itself.
|
|
213
|
+
*/
|
|
214
|
+
detectSessionInUrl?: boolean;
|
|
215
|
+
/**
|
|
216
|
+
* `expo-crypto` (`import * as ExpoCrypto from "expo-crypto"`), on native.
|
|
217
|
+
* Hermes has no `crypto.subtle`, and without it supabase-js sends the PKCE
|
|
218
|
+
* challenge as "plain"; this installs `crypto.getRandomValues` and a SHA-256
|
|
219
|
+
* `crypto.subtle.digest` from it (real WebCrypto is kept), so the challenge
|
|
220
|
+
* is S256. A PKCE client made where there is neither throws in development
|
|
221
|
+
* and logs an error in a release build. With the polyfill in place,
|
|
222
|
+
* `auth.getClaims` always has the server check the token (`getUser`), as
|
|
223
|
+
* supabase-js does where WebCrypto is missing: the polyfill can hash but
|
|
224
|
+
* not verify a signature.
|
|
225
|
+
*/
|
|
226
|
+
crypto?: ExpoCryptoLike;
|
|
227
|
+
/** Anything else for supabase-js, merged last. */
|
|
228
|
+
options?: SupabaseClientOptions<"public">;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* A Supabase client with Genesis's defaults: PKCE sign-in (the code flow, not
|
|
232
|
+
* tokens in a redirect URL, with an S256 challenge: pass `crypto` on native),
|
|
233
|
+
* a persisted session that refreshes itself, and
|
|
234
|
+
* the session storage you pass.
|
|
235
|
+
*
|
|
236
|
+
* @stability Beta
|
|
237
|
+
*/
|
|
238
|
+
declare function createGenesisSupabase<Database = unknown>({ url, publishableKey, storage, platform, detectSessionInUrl, crypto, options, }: GenesisSupabaseOptions): SupabaseClient<Database>;
|
|
239
|
+
/** What a Supabase or PostgREST error carries. */
|
|
240
|
+
interface PostgrestErrorLike {
|
|
241
|
+
message: string;
|
|
242
|
+
code?: string;
|
|
243
|
+
details?: string | null;
|
|
244
|
+
hint?: string | null;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* A failed query, with the database's `code` (`23505` unique violation,
|
|
248
|
+
* `42501` permission denied, `PGRST116` no rows…), `details` and `hint`, so a
|
|
249
|
+
* caller can tell an RLS refusal from a conflict. The original error is the
|
|
250
|
+
* `cause`.
|
|
251
|
+
*
|
|
252
|
+
* @stability Beta
|
|
253
|
+
*/
|
|
254
|
+
declare class SupabaseQueryError extends Error {
|
|
255
|
+
name: string;
|
|
256
|
+
readonly code?: string;
|
|
257
|
+
readonly details?: string | null;
|
|
258
|
+
readonly hint?: string | null;
|
|
259
|
+
constructor(error: PostgrestErrorLike);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The data, or a thrown {@link SupabaseQueryError}: for query results where an
|
|
263
|
+
* error should fail the caller (a TanStack Query `queryFn`). The return type
|
|
264
|
+
* keeps `null` when the query can return no row (`.maybeSingle()`).
|
|
265
|
+
*
|
|
266
|
+
* @stability Beta
|
|
267
|
+
*/
|
|
268
|
+
declare function must<T>(result: {
|
|
269
|
+
data: T;
|
|
270
|
+
error: null;
|
|
271
|
+
} | {
|
|
272
|
+
data: null;
|
|
273
|
+
error: PostgrestErrorLike;
|
|
274
|
+
}): T;
|
|
275
|
+
/** The parts of a Supabase client {@link deleteMyAccount} uses. A `SupabaseClient` fits. */
|
|
276
|
+
interface DeleteAccountClient {
|
|
277
|
+
rpc: (fn: any, ...rest: any[]) => PromiseLike<{
|
|
278
|
+
data: unknown;
|
|
279
|
+
error: PostgrestErrorLike | null;
|
|
280
|
+
}>;
|
|
281
|
+
auth: {
|
|
282
|
+
signOut(options?: {
|
|
283
|
+
scope?: "global" | "local" | "others";
|
|
284
|
+
}): Promise<unknown>;
|
|
285
|
+
};
|
|
286
|
+
/** Needed only with `revokeAppleTokens`. */
|
|
287
|
+
functions?: FunctionsClient["functions"];
|
|
288
|
+
}
|
|
289
|
+
/** @stability Beta */
|
|
290
|
+
interface DeleteMyAccountOptions {
|
|
291
|
+
/**
|
|
292
|
+
* After the delete, ask the `genesis-apple-revoke` Edge Function to revoke
|
|
293
|
+
* the person's Sign in with Apple tokens now (`true`, or the function's
|
|
294
|
+
* name). Best effort: the delete has already queued them and the
|
|
295
|
+
* function's schedule retries, so a failure here never fails the call.
|
|
296
|
+
*/
|
|
297
|
+
revokeAppleTokens?: boolean | string;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* In-app account deletion (App Store Review Guideline 5.1.1(v)), on web and
|
|
301
|
+
* native: calls the `delete_my_account` RPC (this package's
|
|
302
|
+
* `migrations/0001_genesis_account_deletion.sql`), which deletes the login
|
|
303
|
+
* and, through the app's `on delete cascade` foreign keys, its data; then
|
|
304
|
+
* clears the session on this device. Throws a {@link SupabaseQueryError} when
|
|
305
|
+
* the database refuses (`42501`: not signed in) and keeps the session then.
|
|
306
|
+
*
|
|
307
|
+
* With Sign in with Apple, ship the `genesis-apple-revoke` function and
|
|
308
|
+
* migration 0003 and pass `{ revokeAppleTokens: true }`: App Review requires
|
|
309
|
+
* the person's Apple tokens to be revoked.
|
|
310
|
+
*
|
|
311
|
+
* Put it behind a confirmation; see the README.
|
|
312
|
+
*
|
|
313
|
+
* @stability Beta
|
|
314
|
+
*/
|
|
315
|
+
declare function deleteMyAccount(client: DeleteAccountClient, options?: DeleteMyAccountOptions): Promise<void>;
|
|
316
|
+
|
|
317
|
+
export { APPLE_REVOKE_FUNCTION as A, type DeleteAccountClient as D, type ExpoCryptoLike as E, type FunctionsClient as F, type GenesisSupabaseOptions as G, type PostgrestErrorLike as P, type SessionStorage as S, type TenantFeatureChange as T, type WebCryptoSource as W, assertPkceCrypto as a, isDigestOnlyPolyfill as b, type DeleteMyAccountOptions as c, type SetTenantFeatureInput as d, type StoreAppleTokenOptions as e, type StoreAppleTokenResult as f, SupabaseQueryError as g, hasPkceCrypto as h, installWebCrypto as i, type TenantFeatureSetting as j, type TenantFeatures as k, type TenantFeaturesClient as l, createGenesisSupabase as m, deleteMyAccount as n, must as o, storeAppleToken as p, tenantFeatureSettings as q, requestAppleRevocation as r, setTenantFeature as s, tenantFeatureHistory as t, tenantFeatures as u };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,103 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* server import it. Native-only helpers (Keychain storage, foreground
|
|
6
|
-
* refresh, reinstall wipe) are on `@marktiderman/genesis-supabase/native`.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** The async key-value store Supabase auth keeps its session in. */
|
|
10
|
-
interface SessionStorage {
|
|
11
|
-
getItem(key: string): Promise<string | null>;
|
|
12
|
-
setItem(key: string, value: string): Promise<void>;
|
|
13
|
-
removeItem(key: string): Promise<void>;
|
|
14
|
-
}
|
|
15
|
-
/** @stability Beta */
|
|
16
|
-
interface GenesisSupabaseOptions {
|
|
17
|
-
url: string;
|
|
18
|
-
/** The publishable (anon) key. Never the secret key in an app. */
|
|
19
|
-
publishableKey: string;
|
|
20
|
-
/** Where the session lives. Native: `keychainStorage(SecureStore)`. Web: leave out for supabase-js's own default. */
|
|
21
|
-
storage?: SessionStorage;
|
|
22
|
-
/** `"native"` sets the React Native defaults (no session from the URL). Default `"web"`. */
|
|
23
|
-
platform?: "web" | "native";
|
|
24
|
-
/**
|
|
25
|
-
* Read a session from the URL after an OAuth redirect. Default: `true` on
|
|
26
|
-
* web, `false` on native, where the app hands the redirect URL to
|
|
27
|
-
* `exchangeCodeForSession` itself.
|
|
28
|
-
*/
|
|
29
|
-
detectSessionInUrl?: boolean;
|
|
30
|
-
/** Anything else for supabase-js, merged last. */
|
|
31
|
-
options?: SupabaseClientOptions<"public">;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* A Supabase client with Genesis's defaults: PKCE sign-in (the code flow, not
|
|
35
|
-
* tokens in a redirect URL), a persisted session that refreshes itself, and
|
|
36
|
-
* the session storage you pass.
|
|
37
|
-
*
|
|
38
|
-
* @stability Beta
|
|
39
|
-
*/
|
|
40
|
-
declare function createGenesisSupabase<Database = unknown>({ url, publishableKey, storage, platform, detectSessionInUrl, options, }: GenesisSupabaseOptions): SupabaseClient<Database>;
|
|
41
|
-
/** What a Supabase or PostgREST error carries. */
|
|
42
|
-
interface PostgrestErrorLike {
|
|
43
|
-
message: string;
|
|
44
|
-
code?: string;
|
|
45
|
-
details?: string | null;
|
|
46
|
-
hint?: string | null;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* A failed query, with the database's `code` (`23505` unique violation,
|
|
50
|
-
* `42501` permission denied, `PGRST116` no rows…), `details` and `hint`, so a
|
|
51
|
-
* caller can tell an RLS refusal from a conflict. The original error is the
|
|
52
|
-
* `cause`.
|
|
53
|
-
*
|
|
54
|
-
* @stability Beta
|
|
55
|
-
*/
|
|
56
|
-
declare class SupabaseQueryError extends Error {
|
|
57
|
-
name: string;
|
|
58
|
-
readonly code?: string;
|
|
59
|
-
readonly details?: string | null;
|
|
60
|
-
readonly hint?: string | null;
|
|
61
|
-
constructor(error: PostgrestErrorLike);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* The data, or a thrown {@link SupabaseQueryError}: for query results where an
|
|
65
|
-
* error should fail the caller (a TanStack Query `queryFn`). The return type
|
|
66
|
-
* keeps `null` when the query can return no row (`.maybeSingle()`).
|
|
67
|
-
*
|
|
68
|
-
* @stability Beta
|
|
69
|
-
*/
|
|
70
|
-
declare function must<T>(result: {
|
|
71
|
-
data: T;
|
|
72
|
-
error: null;
|
|
73
|
-
} | {
|
|
74
|
-
data: null;
|
|
75
|
-
error: PostgrestErrorLike;
|
|
76
|
-
}): T;
|
|
77
|
-
/** The parts of a Supabase client {@link deleteMyAccount} uses. A `SupabaseClient` fits. */
|
|
78
|
-
interface DeleteAccountClient {
|
|
79
|
-
rpc: (fn: any, ...rest: any[]) => PromiseLike<{
|
|
80
|
-
data: unknown;
|
|
81
|
-
error: PostgrestErrorLike | null;
|
|
82
|
-
}>;
|
|
83
|
-
auth: {
|
|
84
|
-
signOut(options?: {
|
|
85
|
-
scope?: "global" | "local" | "others";
|
|
86
|
-
}): Promise<unknown>;
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* In-app account deletion (App Store Review Guideline 5.1.1(v)), on web and
|
|
91
|
-
* native: calls the `delete_my_account` RPC (this package's
|
|
92
|
-
* `migrations/0001_genesis_account_deletion.sql`), which deletes the login
|
|
93
|
-
* and, through the app's `on delete cascade` foreign keys, its data; then
|
|
94
|
-
* clears the session on this device. Throws a {@link SupabaseQueryError} when
|
|
95
|
-
* the database refuses (`42501`: not signed in) and keeps the session then.
|
|
96
|
-
*
|
|
97
|
-
* Put it behind a confirmation; see the README.
|
|
98
|
-
*
|
|
99
|
-
* @stability Beta
|
|
100
|
-
*/
|
|
101
|
-
declare function deleteMyAccount(client: DeleteAccountClient): Promise<void>;
|
|
102
|
-
|
|
103
|
-
export { type DeleteAccountClient, type GenesisSupabaseOptions, type PostgrestErrorLike, type SessionStorage, SupabaseQueryError, createGenesisSupabase, deleteMyAccount, must };
|
|
1
|
+
import '@supabase/supabase-js';
|
|
2
|
+
export { A as APPLE_REVOKE_FUNCTION, D as DeleteAccountClient, c as DeleteMyAccountOptions, E as ExpoCryptoLike, F as FunctionsClient, G as GenesisSupabaseOptions, P as PostgrestErrorLike, S as SessionStorage, d as SetTenantFeatureInput, e as StoreAppleTokenOptions, f as StoreAppleTokenResult, g as SupabaseQueryError, T as TenantFeatureChange, j as TenantFeatureSetting, k as TenantFeatures, l as TenantFeaturesClient, m as createGenesisSupabase, n as deleteMyAccount, o as must, r as requestAppleRevocation, s as setTenantFeature, p as storeAppleToken, t as tenantFeatureHistory, q as tenantFeatureSettings, u as tenantFeatures } from './index-BSGz3RGk.js';
|
|
3
|
+
export { accountBlock, tenantFeaturesBlock } from './blocks.js';
|
|
4
|
+
import '@marktiderman/genesis-features/blocks';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,52 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { installWebCrypto, assertPkceCrypto, isDigestOnlyPolyfill, claimsViaServer } from './chunk-FAA2FTF4.js';
|
|
2
|
+
export { accountBlock, tenantFeaturesBlock } from './chunk-T2HGFC4C.js';
|
|
3
|
+
import { createClient, AuthInvalidJwtError } from '@supabase/supabase-js';
|
|
4
|
+
|
|
5
|
+
// src/apple-tokens.ts
|
|
6
|
+
var APPLE_REVOKE_FUNCTION = "genesis-apple-revoke";
|
|
7
|
+
var STORE_TIMEOUT_MS = 2e4;
|
|
8
|
+
var REVOKE_TIMEOUT_MS = 5e3;
|
|
9
|
+
async function storeAppleToken(client, { authorizationCode, clientId, functionName = APPLE_REVOKE_FUNCTION }) {
|
|
10
|
+
try {
|
|
11
|
+
const { error } = await client.functions.invoke(functionName, { body: { action: "store", code: authorizationCode, ...clientId ? { clientId } : {} }, timeout: STORE_TIMEOUT_MS });
|
|
12
|
+
if (error) return { ok: false, error: error instanceof Error ? error : new Error(String(error.message ?? error)) };
|
|
13
|
+
return { ok: true };
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function requestAppleRevocation(client, functionName = APPLE_REVOKE_FUNCTION) {
|
|
19
|
+
try {
|
|
20
|
+
const { error } = await client.functions.invoke(functionName, { body: { action: "revoke" }, timeout: REVOKE_TIMEOUT_MS });
|
|
21
|
+
return !error;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/tenant-features.ts
|
|
28
|
+
async function tenantFeatures(client, tenantId) {
|
|
29
|
+
const data = must(await client.rpc("tenant_features_off", { tenant: tenantId }));
|
|
30
|
+
const off = new Set(Array.isArray(data) ? data.filter((id) => typeof id === "string") : []);
|
|
31
|
+
return Object.freeze({ tenantId, off, isOn: (featureId) => !off.has(featureId) });
|
|
32
|
+
}
|
|
33
|
+
async function setTenantFeature(client, input) {
|
|
34
|
+
must(
|
|
35
|
+
await client.rpc("set_tenant_feature", {
|
|
36
|
+
tenant: input.tenantId,
|
|
37
|
+
feature: input.featureId,
|
|
38
|
+
enabled: input.enabled,
|
|
39
|
+
reason: input.reason
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
async function tenantFeatureSettings(client, tenantId) {
|
|
44
|
+
return must(await client.rpc("tenant_feature_settings", { tenant: tenantId })) ?? [];
|
|
45
|
+
}
|
|
46
|
+
async function tenantFeatureHistory(client, tenantId, options = {}) {
|
|
47
|
+
const args = options.maxRows === void 0 ? { tenant: tenantId } : { tenant: tenantId, max_rows: options.maxRows };
|
|
48
|
+
return must(await client.rpc("tenant_feature_history", args)) ?? [];
|
|
49
|
+
}
|
|
2
50
|
|
|
3
51
|
// src/index.ts
|
|
4
52
|
function createGenesisSupabase({
|
|
@@ -7,8 +55,11 @@ function createGenesisSupabase({
|
|
|
7
55
|
storage,
|
|
8
56
|
platform = "web",
|
|
9
57
|
detectSessionInUrl = platform === "web",
|
|
58
|
+
crypto,
|
|
10
59
|
options = {}
|
|
11
60
|
}) {
|
|
61
|
+
if (crypto) installWebCrypto(crypto);
|
|
62
|
+
if ((options.auth?.flowType ?? "pkce") === "pkce") assertPkceCrypto();
|
|
12
63
|
const client = createClient(url, publishableKey, {
|
|
13
64
|
...options,
|
|
14
65
|
auth: {
|
|
@@ -20,6 +71,10 @@ function createGenesisSupabase({
|
|
|
20
71
|
...options.auth
|
|
21
72
|
}
|
|
22
73
|
});
|
|
74
|
+
if (isDigestOnlyPolyfill()) {
|
|
75
|
+
const auth = client.auth;
|
|
76
|
+
auth.getClaims = claimsViaServer(auth, (message) => new AuthInvalidJwtError(message));
|
|
77
|
+
}
|
|
23
78
|
return client;
|
|
24
79
|
}
|
|
25
80
|
var SupabaseQueryError = class extends Error {
|
|
@@ -38,11 +93,13 @@ function must(result) {
|
|
|
38
93
|
if (result.error) throw new SupabaseQueryError(result.error);
|
|
39
94
|
return result.data;
|
|
40
95
|
}
|
|
41
|
-
async function deleteMyAccount(client) {
|
|
96
|
+
async function deleteMyAccount(client, options = {}) {
|
|
42
97
|
must(await client.rpc("delete_my_account"));
|
|
98
|
+
const revoke = options.revokeAppleTokens;
|
|
99
|
+
if (revoke && client.functions) await requestAppleRevocation({ functions: client.functions }, typeof revoke === "string" ? revoke : APPLE_REVOKE_FUNCTION);
|
|
43
100
|
await client.auth.signOut({ scope: "local" });
|
|
44
101
|
}
|
|
45
102
|
|
|
46
|
-
export { SupabaseQueryError, createGenesisSupabase, deleteMyAccount, must };
|
|
103
|
+
export { APPLE_REVOKE_FUNCTION, SupabaseQueryError, createGenesisSupabase, deleteMyAccount, must, requestAppleRevocation, setTenantFeature, storeAppleToken, tenantFeatureHistory, tenantFeatureSettings, tenantFeatures };
|
|
47
104
|
//# sourceMappingURL=index.js.map
|
|
48
105
|
//# sourceMappingURL=index.js.map
|