@palbase/web 7.2.2 → 7.3.1

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.
Files changed (44) hide show
  1. package/dist/{analytics-facade-DLfnVVwL.d.ts → analytics-facade-BER0EyYT.d.ts} +8 -3
  2. package/dist/{analytics-facade-jyXv7Cu4.d.cts → analytics-facade-CQXCQSoF.d.cts} +8 -3
  3. package/dist/{chunk-ACCJV6FV.js → chunk-53TN7KYR.js} +58 -5
  4. package/dist/chunk-53TN7KYR.js.map +1 -0
  5. package/dist/{chunk-ZC6Q3GPX.js → chunk-CBN3PWNL.js} +2 -2
  6. package/dist/{chunk-IREKQSHT.js → chunk-RRHJV6YD.js} +22 -15
  7. package/dist/chunk-RRHJV6YD.js.map +1 -0
  8. package/dist/gen/cli.cjs +6 -12
  9. package/dist/gen/cli.cjs.map +1 -1
  10. package/dist/gen/cli.js +6 -12
  11. package/dist/gen/cli.js.map +1 -1
  12. package/dist/index.cjs +49 -4
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +3 -3
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +2 -2
  17. package/dist/internal.cjs +73 -17
  18. package/dist/internal.cjs.map +1 -1
  19. package/dist/internal.d.cts +3 -3
  20. package/dist/internal.d.ts +3 -3
  21. package/dist/internal.js +2 -2
  22. package/dist/next/client.cjs +81 -25
  23. package/dist/next/client.cjs.map +1 -1
  24. package/dist/next/client.js +6 -4
  25. package/dist/next/client.js.map +1 -1
  26. package/dist/next/index.cjs +87 -31
  27. package/dist/next/index.cjs.map +1 -1
  28. package/dist/next/index.d.cts +2 -22
  29. package/dist/next/index.d.ts +2 -22
  30. package/dist/next/index.js +7 -6
  31. package/dist/next/index.js.map +1 -1
  32. package/dist/next/proxy.js +2 -2
  33. package/dist/{pb-DdzpEkPY.d.ts → pb-BRlmBIAm.d.ts} +1 -1
  34. package/dist/{pb-CfYQGEn0.d.cts → pb-WAQvwCfM.d.cts} +1 -1
  35. package/dist/react/index.cjs +48 -3
  36. package/dist/react/index.cjs.map +1 -1
  37. package/dist/react/index.d.cts +1 -1
  38. package/dist/react/index.d.ts +1 -1
  39. package/dist/react/index.js +2 -2
  40. package/package.json +13 -13
  41. package/LICENSE +0 -21
  42. package/dist/chunk-ACCJV6FV.js.map +0 -1
  43. package/dist/chunk-IREKQSHT.js.map +0 -1
  44. /package/dist/{chunk-ZC6Q3GPX.js.map → chunk-CBN3PWNL.js.map} +0 -0
@@ -653,10 +653,8 @@ interface PalbeOAuthConfig {
653
653
  interface PalbeConfig {
654
654
  url: string;
655
655
  apiKey: string;
656
- /** Registered app identity persisted by `palbase web link`. */
656
+ /** Registered app identity persisted by `palbase link`. */
657
657
  appId: string;
658
- /** Stable reference of the linked Palbase Environment. */
659
- environmentRef: string;
660
658
  oauth?: PalbeOAuthConfig;
661
659
  /**
662
660
  * Refresh-token persistence. Default: environment-scoped localStorage in
@@ -1640,6 +1638,13 @@ declare class PalbeRealtime {
1640
1638
 
1641
1639
  interface PalbeRuntime {
1642
1640
  config: PalbeConfig;
1641
+ /**
1642
+ * The project this runtime talks to, read from the publishable key.
1643
+ *
1644
+ * Exposed here so every consumer takes it from ONE derivation. It used to be
1645
+ * a field on the config that had to equal the key's — see buildRuntime.
1646
+ */
1647
+ projectRef: string;
1643
1648
  http: HttpClient$1;
1644
1649
  tokenManager: TokenManager$1;
1645
1650
  authClient: AuthClient;
@@ -653,10 +653,8 @@ interface PalbeOAuthConfig {
653
653
  interface PalbeConfig {
654
654
  url: string;
655
655
  apiKey: string;
656
- /** Registered app identity persisted by `palbase web link`. */
656
+ /** Registered app identity persisted by `palbase link`. */
657
657
  appId: string;
658
- /** Stable reference of the linked Palbase Environment. */
659
- environmentRef: string;
660
658
  oauth?: PalbeOAuthConfig;
661
659
  /**
662
660
  * Refresh-token persistence. Default: environment-scoped localStorage in
@@ -1640,6 +1638,13 @@ declare class PalbeRealtime {
1640
1638
 
1641
1639
  interface PalbeRuntime {
1642
1640
  config: PalbeConfig;
1641
+ /**
1642
+ * The project this runtime talks to, read from the publishable key.
1643
+ *
1644
+ * Exposed here so every consumer takes it from ONE derivation. It used to be
1645
+ * a field on the config that had to equal the key's — see buildRuntime.
1646
+ */
1647
+ projectRef: string;
1643
1648
  http: HttpClient$1;
1644
1649
  tokenManager: TokenManager$1;
1645
1650
  authClient: AuthClient;
@@ -12,6 +12,45 @@ var PalbaseError = class extends Error {
12
12
  this.details = details;
13
13
  }
14
14
  };
15
+ var POW_CHALLENGE_ID_HEADER = "X-PoW-Challenge-ID";
16
+ var POW_NONCE_HEADER = "X-PoW-Nonce";
17
+ function asPowChallenge(details) {
18
+ if (typeof details !== "object" || details === null) return null;
19
+ const env = details;
20
+ if (env.error !== "pow_required") return null;
21
+ const c = env.challenge;
22
+ if (typeof c !== "object" || c === null) return null;
23
+ const { id, prefix, difficulty } = c;
24
+ if (typeof id !== "string" || typeof prefix !== "string") return null;
25
+ if (typeof difficulty !== "number" || !Number.isInteger(difficulty) || difficulty < 0) return null;
26
+ return { id, prefix, difficulty };
27
+ }
28
+ var encoder = new TextEncoder();
29
+ function leadingZeroBits(hash) {
30
+ let bits = 0;
31
+ for (const byte of hash) {
32
+ if (byte === 0) {
33
+ bits += 8;
34
+ continue;
35
+ }
36
+ return bits + Math.clz32(byte) - 24;
37
+ }
38
+ return bits;
39
+ }
40
+ async function solvePowChallenge(challenge, maxIterations = 1 << 24) {
41
+ for (let nonce = 0; nonce < maxIterations; nonce++) {
42
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(challenge.prefix + nonce));
43
+ if (leadingZeroBits(new Uint8Array(digest)) >= challenge.difficulty) {
44
+ return {
45
+ [POW_CHALLENGE_ID_HEADER]: challenge.id,
46
+ [POW_NONCE_HEADER]: String(nonce)
47
+ };
48
+ }
49
+ }
50
+ throw new Error(
51
+ `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`
52
+ );
53
+ }
15
54
  function detectPlatform() {
16
55
  if (typeof Deno !== "undefined") {
17
56
  return "deno";
@@ -148,9 +187,9 @@ var HttpClient = class _HttpClient {
148
187
  }
149
188
  return headers;
150
189
  }
151
- async executeWithRetry(method, path, options, attempt) {
190
+ async executeWithRetry(method, path, options, attempt, earned) {
152
191
  const url = `${this.getBaseUrl()}${path}`;
153
- const headers = this.buildHeaders(options);
192
+ const headers = { ...this.buildHeaders(options), ...earned };
154
193
  for (const interceptor of this.interceptors) {
155
194
  await interceptor({ headers, method, path });
156
195
  }
@@ -169,7 +208,7 @@ var HttpClient = class _HttpClient {
169
208
  if (attempt < MAX_RETRIES - 1) {
170
209
  const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;
171
210
  await this.delay(backoff);
172
- return this.executeWithRetry(method, path, options, attempt + 1);
211
+ return this.executeWithRetry(method, path, options, attempt + 1, earned);
173
212
  }
174
213
  throw new PalbaseError(
175
214
  "network_error",
@@ -183,7 +222,7 @@ var HttpClient = class _HttpClient {
183
222
  const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;
184
223
  const delayMs = Number.isNaN(parsed) ? INITIAL_BACKOFF_MS * 2 ** attempt : Math.min(parsed * 1e3, MAX_RETRY_DELAY_MS);
185
224
  await this.delay(delayMs);
186
- return this.executeWithRetry(method, path, options, attempt + 1);
225
+ return this.executeWithRetry(method, path, options, attempt + 1, earned);
187
226
  }
188
227
  }
189
228
  let data = null;
@@ -197,6 +236,18 @@ var HttpClient = class _HttpClient {
197
236
  errorBody = body;
198
237
  }
199
238
  }
239
+ if (response.status === 403 && !earned) {
240
+ const challenge = asPowChallenge(errorBody);
241
+ if (challenge) {
242
+ return this.executeWithRetry(
243
+ method,
244
+ path,
245
+ options,
246
+ attempt,
247
+ await solvePowChallenge(challenge)
248
+ );
249
+ }
250
+ }
200
251
  if (!response.ok) {
201
252
  return {
202
253
  data: null,
@@ -399,6 +450,8 @@ function unwrap(res) {
399
450
 
400
451
  export {
401
452
  PalbaseError,
453
+ asPowChallenge,
454
+ solvePowChallenge,
402
455
  HttpClient,
403
456
  TokenManager,
404
457
  BackendError,
@@ -408,4 +461,4 @@ export {
408
461
  asPalbaseError,
409
462
  unwrap
410
463
  };
411
- //# sourceMappingURL=chunk-ACCJV6FV.js.map
464
+ //# sourceMappingURL=chunk-53TN7KYR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../core/src/config.ts","../../core/src/errors.ts","../../core/src/pow.ts","../../core/src/platform.ts","../../core/src/http.ts","../../core/src/token.ts","../src/errors.ts"],"sourcesContent":["import type { HttpClient } from './http.js';\nimport type { ProjectConfig } from './types.js';\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ConfigFetcher {\n protected readonly httpClient: HttpClient;\n private cachedConfig: ProjectConfig | null = null;\n private cacheTimestamp = 0;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n async getConfig(): Promise<ProjectConfig | null> {\n const now = Date.now();\n\n if (this.cachedConfig && now - this.cacheTimestamp < CACHE_TTL_MS) {\n return this.cachedConfig;\n }\n\n try {\n const response = await this.httpClient.request<ProjectConfig>('GET', '/v1/config');\n\n if (response.error || !response.data) {\n return null;\n }\n\n this.cachedConfig = response.data;\n this.cacheTimestamp = now;\n\n return this.cachedConfig;\n } catch {\n return null;\n }\n }\n}\n","export class PalbaseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: string, message: string, status: number, details?: unknown) {\n super(message);\n this.name = 'PalbaseError';\n this.code = code;\n this.status = status;\n this.details = details;\n }\n}\n","// Proof-of-work: the bot gate in front of /auth/signup and /auth/login.\n//\n// The server answers an unsolved request with 403 and a challenge in the body:\n//\n// { \"error\": \"pow_required\", \"challenge\": { \"id\", \"prefix\", \"difficulty\" } }\n//\n// A client finds any nonce whose SHA-256(prefix + nonce) begins with\n// `difficulty` zero bits, then repeats the request carrying the id and nonce as\n// headers. The work is the point: a person signing up pays it once and does not\n// notice, a script signing up ten thousand times pays it ten thousand times.\n//\n// # Why this lives in core, and not one layer up\n//\n// Until 2026-08-14 nothing shipped could solve it: the gate was written with the\n// server and its own integration harness, and every real client sent requests\n// without the headers and got 403. It was then solved in @palbase/web's own\n// request path — which covers `pb.call` and the module facades and NOT\n// `pb.auth.*`, because those go through @palbase/auth's client and from there\n// into core's HttpClient. So the fix landed everywhere except the two endpoints\n// the gate actually guards, and `npm i @palbase/web` still could not sign a\n// person in. Measured 2026-08-18 against a real stack, on the published 7.3.0.\n//\n// The lesson is where a retry belongs: at the layer that ISSUES the request.\n// Core owns fetch for every client in this repo, so core owns the challenge.\n//\n// WebCrypto rather than a hashing dependency: `crypto.subtle` is present in\n// browsers and in Node 18+, which is the same floor the rest of the SDK sets.\n// Measured at the server's default difficulty of 16: ~330ms, ~65k digests.\n\n/** The challenge a `pow_required` response carries. */\nexport interface PowChallenge {\n id: string;\n prefix: string;\n difficulty: number;\n}\n\n/** Header names the retry must carry. Mirrors the server's constants. */\nexport const POW_CHALLENGE_ID_HEADER = 'X-PoW-Challenge-ID';\nexport const POW_NONCE_HEADER = 'X-PoW-Nonce';\n\n/**\n * Reads a challenge out of an error envelope, or returns null when the envelope\n * is not a `pow_required` one.\n *\n * The whole wire envelope is stored on the error, so the challenge arrives\n * without the HTTP layer having to know about proof-of-work at all.\n */\nexport function asPowChallenge(details: unknown): PowChallenge | null {\n if (typeof details !== 'object' || details === null) return null;\n const env = details as Record<string, unknown>;\n if (env.error !== 'pow_required') return null;\n const c = env.challenge;\n if (typeof c !== 'object' || c === null) return null;\n const { id, prefix, difficulty } = c as Record<string, unknown>;\n if (typeof id !== 'string' || typeof prefix !== 'string') return null;\n if (typeof difficulty !== 'number' || !Number.isInteger(difficulty) || difficulty < 0) return null;\n return { id, prefix, difficulty };\n}\n\nconst encoder = new TextEncoder();\n\n/** Counts leading zero bits, stopping at the first byte that has a one. */\nfunction leadingZeroBits(hash: Uint8Array): number {\n let bits = 0;\n for (const byte of hash) {\n if (byte === 0) {\n bits += 8;\n continue;\n }\n // clz32 counts across 32 bits; a byte occupies the low 8, so the first 24\n // are always zero and get subtracted back off.\n return bits + Math.clz32(byte) - 24;\n }\n return bits;\n}\n\n/**\n * Finds a nonce satisfying the challenge and returns the headers a retry needs.\n *\n * `maxIterations` bounds the search so a difficulty nobody can pay fails as an\n * error rather than as a hang. The default carries the server's default (16)\n * with room to spare — the expected cost is 2^difficulty and the tail is long,\n * so the bound is generous rather than tight.\n */\nexport async function solvePowChallenge(\n challenge: PowChallenge,\n maxIterations = 1 << 24,\n): Promise<Record<string, string>> {\n for (let nonce = 0; nonce < maxIterations; nonce++) {\n const digest = await crypto.subtle.digest('SHA-256', encoder.encode(challenge.prefix + nonce));\n if (leadingZeroBits(new Uint8Array(digest)) >= challenge.difficulty) {\n return {\n [POW_CHALLENGE_ID_HEADER]: challenge.id,\n [POW_NONCE_HEADER]: String(nonce),\n };\n }\n }\n throw new Error(\n `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`,\n );\n}\n","export type Platform = 'browser' | 'node' | 'react-native' | 'deno' | 'bun';\n\ndeclare const Deno: unknown;\ndeclare const process: { versions: Record<string, string> } | undefined;\n\nexport function detectPlatform(): Platform {\n if (typeof Deno !== 'undefined') {\n return 'deno';\n }\n\n if (process?.versions) {\n if ('bun' in process.versions) {\n return 'bun';\n }\n if ('node' in process.versions) {\n return 'node';\n }\n }\n\n if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {\n return 'react-native';\n }\n\n return 'browser';\n}\n\n/**\n * The platform word this SDK puts on the wire (`X-Platform`), which the server\n * reads to target flags and to label telemetry.\n *\n * It is NOT `detectPlatform()`'s value verbatim: that reports the JS host\n * (\"browser\"), while the wire wants the platform. iOS sends \"ios\", not the name\n * of its runtime, and a condition author writes `client.platform == 'web'` —\n * the word every other flag vendor uses too. Server hosts keep their own names,\n * where the distinction is the useful part.\n */\nexport function wirePlatform(): string {\n const host = detectPlatform();\n return host === 'browser' ? 'web' : host;\n}\n","import { PalbaseError } from './errors.js';\nimport { asPowChallenge, solvePowChallenge } from './pow.js';\nimport { wirePlatform } from './platform.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\n/**\n * Default production host. Dev / staging / local callers override via\n * `options.url`. Apex-style routing is the only supported production path;\n * Kong resolves Environment identity from the API key.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse the Environment ref from a Palbase API key.\n *\n * Canonical shape: `pb_{environment_ref}_c{random}`, where the Environment\n * ref is 4-24 lowercase ASCII alphanumeric characters and random is 20 base62\n * chars. See\n * docs/MODULE_HEADER_CONTRACT.md §\"API key format\" (palbase repo) for\n * the full spec.\n *\n * Returns the Environment ref on match; `null` otherwise.\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;\n\nfunction parseEnvironmentRef(apiKey: string): string | null {\n return API_KEY_RE.exec(apiKey)?.[1] ?? null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n/**\n * Upper bound on a single 429 retry sleep. A server may return a long\n * Retry-After (a locked account can send minutes/hours); honoring it verbatim\n * would HANG the request for that whole window. Cap each retry at 10s — after\n * MAX_RETRIES the 429 envelope surfaces to the caller (fail fast, don't sleep\n * minutes). The clamp never skips a retry; it only bounds how long each waits.\n */\nconst MAX_RETRY_DELAY_MS = 10_000;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport type RequestInterceptor = (request: {\n headers: Record<string, string>;\n method: string;\n path: string;\n}) => void | Promise<void>;\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | null = null;\n\n /**\n * Admin JWT used for platform admin endpoints (/admin/*).\n * When set, takes precedence over tokenManager access token in the\n * Authorization header.\n */\n adminToken: string | null = null;\n\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(apiKey: string, options?: HttpClientOptions) {\n this.apiKey = apiKey;\n this.options = options;\n }\n\n /** Set (or clear) the admin JWT used on admin endpoints. */\n setAdminToken(token: string | null): void {\n this.adminToken = token;\n }\n\n /**\n * Create a scoped HttpClient that adds the given extra headers to every\n * request. The returned client shares the admin token and token manager\n * with the parent at runtime — later changes on the parent propagate to\n * the scope and vice versa.\n *\n * Typical use: adding an Environment-routing header for an admin call.\n */\n withHeaders(extra: Record<string, string>): HttpClient {\n const mergedHeaders = { ...(this.options?.headers ?? {}), ...extra };\n\n const scoped: HttpClient = new HttpClient(this.apiKey, {\n ...this.options,\n headers: mergedHeaders,\n });\n scoped.tokenManager = this.tokenManager;\n // Delegate adminToken reads + writes to the parent so the scope always\n // sees the latest token, and setAdminToken on the scope affects the parent.\n Object.defineProperty(scoped, 'adminToken', {\n get: () => this.adminToken,\n set: (v: string | null) => {\n this.adminToken = v;\n },\n configurable: true,\n });\n return scoped;\n }\n\n /** Add a request interceptor. Runs before every request. */\n addInterceptor(interceptor: RequestInterceptor): void {\n this.interceptors.push(interceptor);\n }\n\n async request<T>(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<PalbaseResponse<T>> {\n // If token is expired and refresh is available, refresh before making the request\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead (revoked/expired/forbidden).\n // Clear the session (listeners persist the sign-out) and proceed\n // unauthenticated — the endpoint will 401 into the normal error\n // envelope instead of bricking every subsequent call including\n // the recovery sign-in.\n this.tokenManager.clearSession();\n } else {\n throw e; // network/5xx: transient, stay loud\n }\n }\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, staging, test rigs).\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Validate the key shape up front so apex-routed callers still\n // fail loud on a malformed key instead of hitting the gateway\n // with bad credentials.\n if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{environment_ref}_c{20_base62_chars}. For dev/staging pass `url: \"https://api.dev.palbase.studio\"` via options.',\n 0,\n );\n }\n\n return `https://${PALBASE_DEFAULT_HOST}`;\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n // Client identity, the web counterpart of the iOS SDK's\n // ClientInfo.augment(). The server reads these to resolve flag targeting\n // conditions and to label telemetry, so an app declares nothing and calls\n // nothing — whatever the SDK can know, it sends.\n 'X-Platform': wirePlatform(),\n };\n // The host app's own version is not knowable on the web (no bundle to read\n // it from), so it is opt-in; when given it fills the same header iOS fills\n // from CFBundleShortVersionString.\n const appVersion = this.options?.appVersion?.trim();\n if (appVersion) {\n headers['X-Palbase-Client-Version'] = appVersion;\n }\n\n // Palbase Environment keys live in the `apikey` header — never in\n // `Authorization` — because Kong's key-auth resolves them on that\n // header and the gateway's pre-function plugin stamps the downstream\n // identity.\n const effectiveKey = this.apiKey;\n if (effectiveKey) {\n headers['apikey'] = effectiveKey;\n }\n\n // User session token, if any. Kong's pre-function plugin strips\n // Authorization on /v1/* routes anyway (PostgREST has no JWT\n // secret and would crash on a Bearer it can't decode), but\n // sending it preserves the contract for /auth/* endpoints that\n // do consume the bearer (e.g. session refresh).\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // adminToken (platform admin JWT) takes precedence — used by the\n // @palbase/admin internal flows that hit /admin/* routes; those\n // routes verify the bearer themselves and aren't subject to the\n // /v1/* Authorization-strip rule.\n if (this.adminToken) {\n headers['Authorization'] = `Bearer ${this.adminToken}`;\n }\n\n // Merge global custom headers\n if (this.options?.headers) {\n Object.assign(headers, this.options.headers);\n }\n\n // Merge per-request headers\n if (options?.headers) {\n Object.assign(headers, options.headers);\n }\n\n return headers;\n }\n\n private async executeWithRetry<T>(\n method: string,\n path: string,\n options: RequestOptions | undefined,\n attempt: number,\n // Headers a PREVIOUS attempt earned and this one has to carry. Today that\n // is only the solved proof-of-work pair; it is a parameter rather than a\n // field because it belongs to one request's second try, and a field would\n // leak it onto every later call made through this client.\n earned?: Record<string, string>,\n ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), ...earned };\n\n // Run interceptors\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n signal: options?.signal,\n };\n\n if (options?.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, fetchOptions);\n } catch (error) {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;\n await this.delay(backoff);\n return this.executeWithRetry<T>(method, path, options, attempt + 1, earned);\n }\n\n // All retries exhausted — throw PalbaseError\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n // Handle 429 Too Many Requests — retry with Retry-After or backoff;\n // if retries exhausted, fall through to normal error response handling below\n if (response.status === 429) {\n if (attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;\n // Clamp the server-requested wait: a long Retry-After (locked account)\n // must not hang the request — cap each sleep, exhaust MAX_RETRIES, then\n // fall through to surface the 429 envelope below.\n const delayMs = Number.isNaN(parsed)\n ? INITIAL_BACKOFF_MS * 2 ** attempt\n : Math.min(parsed * 1000, MAX_RETRY_DELAY_MS);\n await this.delay(delayMs);\n return this.executeWithRetry<T>(method, path, options, attempt + 1, earned);\n }\n }\n\n // Parse response body\n let data: T | null = null;\n let errorBody: { error?: string; error_description?: string; status?: number } | undefined;\n\n // HEAD responses have no body by spec — skip parsing.\n const contentType = response.headers.get('Content-Type');\n if (method !== 'HEAD' && contentType?.includes('json')) {\n const body = (await response.json()) as Record<string, unknown>;\n if (response.ok) {\n data = body as T;\n } else {\n errorBody = body as typeof errorBody;\n }\n }\n\n // Proof-of-work: /auth/signup and /auth/token sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it and repeat the request carrying the two headers; the caller never\n // learns the gate is there.\n //\n // HERE, in core, because this is the layer that issues the request for every\n // client in the repo — @palbase/auth's sign-in, @palbase/web's facades, the\n // server SDK. The same retry lived one layer up in @palbase/web until\n // 2026-08-18 and covered everything EXCEPT `pb.auth.*`, which reaches the\n // network through this method; so the gate stayed unsatisfiable on exactly\n // the two endpoints it guards.\n //\n // ONE retry, and only when the body really carries a challenge: `earned`\n // being set already means this IS the second try. A 403 that says\n // pow_required without a challenge is a server the client cannot satisfy,\n // and looping on it would turn a broken gate into a hang.\n if (response.status === 403 && !earned) {\n const challenge = asPowChallenge(errorBody);\n if (challenge) {\n return this.executeWithRetry<T>(\n method,\n path,\n options,\n attempt,\n await solvePowChallenge(challenge),\n );\n }\n }\n\n if (!response.ok) {\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'unknown_error',\n errorBody?.error_description ?? response.statusText,\n response.status,\n errorBody,\n ),\n status: response.status,\n };\n }\n\n // Parse PostgREST Content-Range for count queries (e.g. \"0-9/42\" or \"*/42\").\n const contentRange = response.headers.get('Content-Range');\n let count: number | undefined;\n if (contentRange) {\n const slash = contentRange.lastIndexOf('/');\n if (slash >= 0) {\n const totalPart = contentRange.slice(slash + 1);\n if (totalPart !== '*') {\n const parsed = Number.parseInt(totalPart, 10);\n if (!Number.isNaN(parsed)) {\n count = parsed;\n }\n }\n }\n }\n\n return {\n data,\n error: null,\n status: response.status,\n ...(count !== undefined ? { count } : {}),\n };\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { AuthStateCallback, Session, Unsubscribe } from './types.js';\n\nexport class TokenManager {\n private session: Session | null = null;\n private listeners: Set<AuthStateCallback> = new Set();\n private refreshPromise: Promise<void> | null = null;\n private refreshing = false;\n\n refreshFunction: ((refreshToken: string) => Promise<Session>) | null = null;\n\n setSession(session: Session): void {\n this.session = session;\n this.notify('SESSION_SET', session);\n }\n\n getAccessToken(): string | null {\n return this.session?.accessToken ?? null;\n }\n\n getRefreshToken(): string | null {\n return this.session?.refreshToken ?? null;\n }\n\n clearSession(): void {\n this.session = null;\n this.notify('SESSION_CLEARED', null);\n }\n\n isExpired(): boolean {\n if (!this.session) return true;\n return Date.now() >= this.session.expiresAt;\n }\n\n async refreshSession(): Promise<void> {\n if (!this.session?.refreshToken || !this.refreshFunction) {\n return;\n }\n\n // Collapse concurrent refresh calls into a single request\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // Re-entrancy guard: the wired refreshFunction issues its own HTTP request\n // (POST /auth/token/refresh) through HttpClient, whose pre-flight calls\n // refreshSession() again SYNCHRONOUSLY — before `refreshPromise` below is\n // assigned (the whole chain runs before the first real await). Without\n // this flag that recursion is unbounded (stack overflow). Returning early\n // lets the refresh request itself proceed unauthenticated — it carries\n // the refresh token in its body, not the Bearer header.\n if (this.refreshing) {\n return;\n }\n\n this.refreshing = true;\n this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshing = false;\n }\n }\n\n onAuthStateChange(callback: AuthStateCallback): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n private async executeRefresh(refreshToken: string): Promise<void> {\n if (!this.refreshFunction) return;\n const newSession = await this.refreshFunction(refreshToken);\n this.setSession(newSession);\n }\n\n private notify(event: 'SESSION_SET' | 'SESSION_CLEARED', session: Session | null): void {\n for (const listener of this.listeners) {\n listener(event, session);\n }\n }\n}\n","import { PalbaseError, type PalbaseResponse } from '@palbase/core';\n\nexport interface FieldError {\n field: string;\n message: string;\n}\n\nexport type BackendErrorKind =\n | 'notConfigured'\n | 'validation'\n | 'unauthorized'\n | 'rateLimited'\n | 'server'\n | 'network'\n | 'decode';\n\ninterface BackendErrorParams {\n code: string;\n message: string;\n status?: number;\n requestId?: string;\n fields?: FieldError[];\n retryAfter?: number;\n data?: unknown;\n}\n\nexport class BackendError extends Error {\n readonly kind: BackendErrorKind;\n readonly code: string;\n readonly status: number;\n readonly requestId?: string;\n readonly fields?: FieldError[];\n readonly retryAfter?: number;\n readonly data?: unknown;\n\n constructor(kind: BackendErrorKind, params: BackendErrorParams) {\n super(params.message);\n this.name = 'BackendError';\n this.kind = kind;\n this.code = params.code;\n this.status = params.status ?? 0;\n this.requestId = params.requestId;\n this.fields = params.fields;\n this.retryAfter = params.retryAfter;\n this.data = params.data;\n }\n\n static notConfigured(): BackendError {\n return new BackendError('notConfigured', {\n code: 'not_configured',\n message:\n \"Palbe is not configured. Run 'palbase web link' in your project and make sure palbe.gen.ts is imported once at app startup.\",\n });\n }\n}\n\nfunction isFieldErrorArray(value: unknown): value is FieldError[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every(\n (v) =>\n typeof v === 'object' &&\n v !== null &&\n typeof (v as Record<string, unknown>).field === 'string' &&\n typeof (v as Record<string, unknown>).message === 'string',\n )\n );\n}\n\nfunction pickField(value: unknown, key: string): unknown {\n if (typeof value === 'object' && value !== null) {\n return (value as Record<string, unknown>)[key];\n }\n return undefined;\n}\n\nfunction pickNumber(value: unknown, key: string): number | undefined {\n const n = pickField(value, key);\n return typeof n === 'number' ? n : undefined;\n}\n\nfunction pickString(value: unknown, key: string): string | undefined {\n const s = pickField(value, key);\n return typeof s === 'string' ? s : undefined;\n}\n\nexport function fromPalbaseError(err: PalbaseError): BackendError {\n // HttpClient stores the WHOLE wire envelope as err.details:\n // { error, error_description, status, request_id, retry_after?, details?, data? }\n const base: BackendErrorParams = {\n code: err.code,\n message: err.message,\n status: err.status,\n requestId: pickString(err.details, 'request_id'),\n data: pickField(err.details, 'data'),\n };\n if (err.code === 'network_error') return new BackendError('network', base);\n if (err.status === 401) return new BackendError('unauthorized', base);\n if (err.status === 429)\n return new BackendError('rateLimited', {\n ...base,\n retryAfter: pickNumber(err.details, 'retry_after'),\n });\n // Field-error array lives at the envelope's nested `details` key.\n const nested = pickField(err.details, 'details');\n if (err.status === 400 && isFieldErrorArray(nested))\n return new BackendError('validation', { ...base, fields: nested });\n // Fallthrough also covers status-0 non-network errors (e.g. auth client's\n // synthetic 'no_refresh_token' with status 0) — those intentionally map to 'server'.\n return new BackendError('server', base);\n}\n\n/** Decode a raw wire body (used by paths that bypass HttpClient, e.g. upload). */\nexport function fromEnvelope(status: number, body: unknown): BackendError {\n const code = pickString(body, 'error') ?? 'http_error';\n const message = pickString(body, 'error_description') ?? `HTTP ${status}`;\n const requestId = pickString(body, 'request_id');\n const details = pickField(body, 'details');\n const params: BackendErrorParams = {\n code,\n message,\n status,\n requestId,\n data: pickField(body, 'data'),\n };\n if (status === 401) return new BackendError('unauthorized', params);\n if (status === 429)\n return new BackendError('rateLimited', {\n ...params,\n // Real 429 wire body has TOP-LEVEL retry_after; nested details is a fallback.\n retryAfter: pickNumber(body, 'retry_after') ?? pickNumber(details, 'retry_after'),\n });\n if (status === 400 && isFieldErrorArray(details))\n return new BackendError('validation', { ...params, fields: details });\n return new BackendError('server', params);\n}\n\n/**\n * Type guard for BackendError that survives module-identity splits.\n * This package ships dual ESM + CJS builds; if both end up loaded (or the\n * package is installed twice), two distinct BackendError classes coexist and\n * `instanceof` fails for errors thrown by \"the other\" copy. Falls back to a\n * structural check on `name` + `kind`.\n */\nexport function isBackendError(e: unknown): e is BackendError {\n return (\n e instanceof BackendError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as Record<string, unknown>).name === 'BackendError' &&\n typeof (e as Record<string, unknown>).kind === 'string')\n );\n}\n\n/**\n * Structural fallback after instanceof for PalbaseError — same dual ESM+CJS\n * identity-split rationale as isBackendError above: two loaded PalbaseError\n * classes break `instanceof` across copies; the name check bridges that.\n * Returns the error as PalbaseError, or null when it isn't one.\n */\nexport function asPalbaseError(e: unknown): PalbaseError | null {\n if (e instanceof PalbaseError) return e;\n if (e instanceof Error && e.name === 'PalbaseError') return e as PalbaseError;\n return null;\n}\n\n/** Convert an internal {data,error} envelope into data-or-throw. */\nexport function unwrap<T>(res: PalbaseResponse<T>): T {\n if (res.error) throw fromPalbaseError(res.error);\n return res.data as T;\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BackendError
3
- } from "./chunk-ACCJV6FV.js";
3
+ } from "./chunk-53TN7KYR.js";
4
4
 
5
5
  // src/next/shared.ts
6
6
  var nextServerModule;
@@ -21,4 +21,4 @@ async function importNextServer(caller) {
21
21
  export {
22
22
  importNextServer
23
23
  };
24
- //# sourceMappingURL=chunk-ZC6Q3GPX.js.map
24
+ //# sourceMappingURL=chunk-CBN3PWNL.js.map
@@ -4,11 +4,13 @@ import {
4
4
  PalbaseError,
5
5
  TokenManager,
6
6
  asPalbaseError,
7
+ asPowChallenge,
7
8
  fromEnvelope,
8
9
  fromPalbaseError,
9
10
  isBackendError,
11
+ solvePowChallenge,
10
12
  unwrap
11
- } from "./chunk-ACCJV6FV.js";
13
+ } from "./chunk-53TN7KYR.js";
12
14
  import {
13
15
  environmentRefFromPublishableApiKey
14
16
  } from "./chunk-CFDU23TB.js";
@@ -56,11 +58,11 @@ async function palbeRequest(rt, method, path, spec = {}) {
56
58
  if (MUTATING.has(method) && !callerHasKey) {
57
59
  headers["Idempotency-Key"] = crypto.randomUUID();
58
60
  }
59
- const attempt = async () => {
61
+ const attempt = async (extra) => {
60
62
  try {
61
63
  return await rt.http.request(method, path, {
62
64
  body: spec.body,
63
- headers,
65
+ headers: extra ? { ...headers, ...extra } : headers,
64
66
  signal: spec.signal
65
67
  });
66
68
  } catch (e) {
@@ -95,6 +97,12 @@ async function palbeRequest(rt, method, path, spec = {}) {
95
97
  }
96
98
  res = await attempt();
97
99
  }
100
+ if (res.error?.status === 403 && res.error.code === "pow_required") {
101
+ const challenge = asPowChallenge(res.error.details);
102
+ if (challenge) {
103
+ res = await attempt(await solvePowChallenge(challenge));
104
+ }
105
+ }
98
106
  } catch (e) {
99
107
  if (!isAbort(e)) record(asPalbaseError(e)?.status ?? 0);
100
108
  throw e;
@@ -835,7 +843,7 @@ var AnalyticsState = class {
835
843
  idKey;
836
844
  optOutKey;
837
845
  constructor(rt) {
838
- const ref = rt.config.environmentRef;
846
+ const ref = rt.projectRef;
839
847
  this.idKey = ref ? `palbe.analytics.id.${ref}` : "palbe.analytics.id";
840
848
  this.optOutKey = ref ? `palbe.analytics.optout.${ref}` : "palbe.analytics.optout";
841
849
  rt.auth.onAuthEvent((event) => {
@@ -2474,7 +2482,7 @@ var PalbeFlags = class {
2474
2482
  constructor(rt) {
2475
2483
  this.transport = new FlagsClient(rt.http);
2476
2484
  const browser = typeof document !== "undefined";
2477
- const ref = rt.config.environmentRef;
2485
+ const ref = rt.projectRef;
2478
2486
  const options = {
2479
2487
  auth: palbeAuthAdapter(rt.auth),
2480
2488
  ...ref ? { storageKey: `palbe.flags.${ref}` } : {},
@@ -8873,15 +8881,13 @@ function defaultSessionStorage(key) {
8873
8881
  }
8874
8882
 
8875
8883
  // src/version.ts
8876
- var VERSION = "7.2.2";
8884
+ var VERSION = "7.3.1";
8877
8885
 
8878
8886
  // src/runtime.ts
8879
8887
  function buildRuntime(config) {
8880
- const keyEnvironmentRef = environmentRefFromPublishableApiKey(config.apiKey);
8881
- if (keyEnvironmentRef === "") {
8882
- throw new Error("PalbeConfig.apiKey does not contain a valid publishable Environment identity");
8883
- } else if (config.environmentRef !== keyEnvironmentRef) {
8884
- throw new Error("PalbeConfig.environmentRef must match the Environment identity in apiKey");
8888
+ const projectRef = environmentRefFromPublishableApiKey(config.apiKey);
8889
+ if (projectRef === "") {
8890
+ throw new Error("PalbeConfig.apiKey does not contain a valid publishable project identity");
8885
8891
  }
8886
8892
  let runtimeURL;
8887
8893
  try {
@@ -8889,8 +8895,8 @@ function buildRuntime(config) {
8889
8895
  } catch {
8890
8896
  throw new Error("PalbeConfig.url must be a valid URL");
8891
8897
  }
8892
- if (runtimeURL.hostname.endsWith(".palbase.studio") && runtimeURL.hostname.split(".")[0] !== config.environmentRef) {
8893
- throw new Error("PalbeConfig.url must match environmentRef for palbase.studio");
8898
+ if (runtimeURL.hostname.endsWith(".palbase.studio") && runtimeURL.hostname.split(".")[0] !== projectRef) {
8899
+ throw new Error("PalbeConfig.url must match the project identity in apiKey for palbase.studio");
8894
8900
  }
8895
8901
  const http = new HttpClient(config.apiKey, {
8896
8902
  url: config.url,
@@ -8900,7 +8906,7 @@ function buildRuntime(config) {
8900
8906
  const tokenManager = new TokenManager();
8901
8907
  http.tokenManager = tokenManager;
8902
8908
  const authClient = new AuthClient(http, tokenManager);
8903
- const ref = config.environmentRef;
8909
+ const ref = projectRef;
8904
8910
  const storage = config.storage ?? defaultSessionStorage(ref ? `palbe.session.${ref}` : void 0);
8905
8911
  const persisted = storage.load();
8906
8912
  if (persisted) {
@@ -8945,6 +8951,7 @@ function buildRuntime(config) {
8945
8951
  let perf;
8946
8952
  const rt = {
8947
8953
  config,
8954
+ projectRef,
8948
8955
  http,
8949
8956
  tokenManager,
8950
8957
  authClient,
@@ -9298,4 +9305,4 @@ export {
9298
9305
  pb,
9299
9306
  createBoundClient
9300
9307
  };
9301
- //# sourceMappingURL=chunk-IREKQSHT.js.map
9308
+ //# sourceMappingURL=chunk-RRHJV6YD.js.map