palbase 0.1.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.
Files changed (50) hide show
  1. package/dist/ai/index.js +2 -2
  2. package/dist/backend/engine.js +2 -2
  3. package/dist/backend/env.js +1 -1
  4. package/dist/backend/index.js +3 -3
  5. package/dist/backend/stack.js +1 -1
  6. package/dist/backend/test.js +2 -2
  7. package/dist/chunks/{3VASGTIN.js → 4A6S7JKW.js} +2 -2
  8. package/dist/chunks/{JAWWIVZH.js → 4LGRTNOK.js} +2 -2
  9. package/dist/chunks/6BUQ7N4I.js +2 -0
  10. package/dist/chunks/7D5J3M3D.js +87 -0
  11. package/dist/chunks/A4VJFSSX.js +2 -0
  12. package/dist/chunks/AR6WQDU5.js +2 -0
  13. package/dist/chunks/{45J7YXRB.js → BPWQDLXP.js} +1 -1
  14. package/dist/chunks/{DAOLIUNZ.js → IQIXNAI4.js} +1 -1
  15. package/dist/chunks/{UAY56IQR.js → LLBV62FM.js} +2 -2
  16. package/dist/chunks/{E5Q4VTW4.js → MJ35SVVY.js} +2 -2
  17. package/dist/chunks/{QXUMLO7O.js → NDJAXNM6.js} +2 -2
  18. package/dist/chunks/RK6M2LNY.js +2 -0
  19. package/dist/chunks/{6FWCNTAM.js → UNOLIHKD.js} +1 -1
  20. package/dist/chunks/{3OGEBGYZ.js → W2GV6UU3.js} +2 -2
  21. package/dist/chunks/{LYEFPLJZ.js → YQYMP55L.js} +1 -1
  22. package/dist/cli.js +90 -90
  23. package/dist/refuse/ai.js +1 -1
  24. package/dist/refuse/backend.js +1 -1
  25. package/dist/refuse/server.js +1 -1
  26. package/dist/server/index.js +2 -2
  27. package/dist/server/testing.js +2 -2
  28. package/dist/web/index.js +2 -2
  29. package/dist/web/palbe.js +2 -2
  30. package/dist/web/paywall.js +2 -0
  31. package/dist/web/vite.js +2 -2
  32. package/package.json +6 -2
  33. package/types/internal/src/modules/ai.d.ts +811 -0
  34. package/types/internal/src/modules/events.d.ts +2 -2
  35. package/types/internal/src/modules/purchases.d.ts +2609 -133
  36. package/types/internal/src/modules/storage.d.ts +1 -1
  37. package/types/web/src/client.d.ts +6 -0
  38. package/types/web/src/index.d.ts +3 -0
  39. package/types/web/src/paywall/assets.d.ts +51 -0
  40. package/types/web/src/paywall/attributes.d.ts +13 -0
  41. package/types/web/src/paywall/icons.d.ts +5 -0
  42. package/types/web/src/paywall/index.d.ts +7 -0
  43. package/types/web/src/paywall/placement.d.ts +99 -0
  44. package/types/web/src/paywall/prices.d.ts +40 -0
  45. package/types/web/src/paywall/render.d.ts +93 -0
  46. package/types/web/src/paywall/resolve.d.ts +184 -0
  47. package/types/web/src/purchases.d.ts +34 -2
  48. package/dist/chunks/3XT4L2XV.js +0 -2
  49. package/dist/chunks/HKJU2FKE.js +0 -2
  50. package/dist/chunks/IKVOXHEE.js +0 -2
@@ -262,7 +262,7 @@ export type StorageObject = {
262
262
  */
263
263
  export type StorageStats = {
264
264
  /**
265
- * Every bucket of the project, by name; an empty one with zeros.
265
+ * Every bucket of the project, by name; an empty one with zeros. A platform module's own bucket is listed with its share too, though the project does not reach it otherwise: _ai holds the pictures its agents' tools returned, deleted with their chat (or once a one-shot answer is answered) — counted in the project's storage as its other files are.
266
266
  */
267
267
  buckets: StorageStatsBucket[];
268
268
  /**
@@ -30,6 +30,12 @@ export interface ClientOptions {
30
30
  * Off unless given; events the app captures (`analytics.capture`) go either way.
31
31
  */
32
32
  analytics?: boolean | AnalyticsOptions;
33
+ /**
34
+ * The app's version, as its users see it ("2.4.1"): said with a paywall's
35
+ * request, for a placement's targeting rules that read it. Nothing is said
36
+ * without one.
37
+ */
38
+ appVersion?: string;
33
39
  }
34
40
  /**
35
41
  * The app's client. A backend's generated Palbe module (palbase apps:sync)
@@ -4,6 +4,9 @@ export { localStore, memoryStore, SessionChangedError, type AuthEvent, type Sess
4
4
  export { Channel, RealtimeSocket, type PushReply } from './socket.js';
5
5
  export { Analytics, type AnalyticsOptions } from './analytics.js';
6
6
  export { Purchases, type CustomerInfo, type CustomerInfoListener, type MergeGrant, type Offerings } from './purchases.js';
7
+ export type { Paywall, PaywallRenderOptions } from './paywall/placement.js';
8
+ export type { PaywallAttribute } from './paywall/attributes.js';
9
+ export type { PaywallPackage, PaywallView } from './paywall/render.js';
7
10
  export { PalbaseError } from '../../internal/src/transport.js';
8
11
  export { BackendError, isBackendError, type BackendErrorKind, type FieldError } from './backend-error.js';
9
12
  export type { BackendBase, CallOptions, UploadOptions, UploadProgress } from './backends.js';
@@ -0,0 +1,51 @@
1
+ /** An asset as the answer lists it (PaywallAssetRef). */
2
+ export interface PaywallAssetRef {
3
+ id: string;
4
+ kind: 'image' | 'video' | 'font' | 'html';
5
+ url: string;
6
+ sha256: string;
7
+ bytes: number;
8
+ }
9
+ /** An asset ready to draw. */
10
+ export interface PreparedAsset {
11
+ kind: PaywallAssetRef['kind'];
12
+ /** Where its checked bytes are: an object URL (or, unchecked, the https address Studio's preview draws from). */
13
+ url: string;
14
+ /** A font's family, registered with the page's fonts. */
15
+ family?: string;
16
+ /** An HTML block's document, every file of its zip inside it. */
17
+ html?: string;
18
+ }
19
+ /** The family a font asset is registered under. */
20
+ export declare const fontFamily: (id: string) => string;
21
+ /** The id an `asset:<id>` names; undefined for anything else. */
22
+ export declare function assetId(src: unknown): string | undefined;
23
+ export interface PrepareOptions {
24
+ fetch?: (input: string, init?: RequestInit) => Promise<Response>;
25
+ signal?: AbortSignal;
26
+ /** Where a font is registered: the page's fonts unless given. */
27
+ fonts?: FontFaceSet;
28
+ }
29
+ /**
30
+ * Downloads and checks every asset; those that pass are ready to draw, by
31
+ * id. Done when the paywall is fetched, before it is shown: what is shown
32
+ * then draws from memory.
33
+ */
34
+ export declare function prepareAssets(refs: readonly PaywallAssetRef[], opts?: PrepareOptions): Promise<Record<string, PreparedAsset>>;
35
+ /**
36
+ * What an asset's bytes are, among the formats its kind takes — the type its
37
+ * object URL is given: an image is PNG, JPEG or WebP; a video MP4, QuickTime
38
+ * or WebM; a font WOFF2, WOFF, TrueType or OpenType; an HTML block a zip.
39
+ * Undefined for anything else: the asset is not drawn.
40
+ */
41
+ export declare function formatOf(kind: PaywallAssetRef['kind'], b: Uint8Array): string | undefined;
42
+ /** The files of a zip, by path: stored and deflated entries (what the server takes). */
43
+ export declare function unzip(bytes: Uint8Array): Promise<Map<string, Uint8Array>>;
44
+ /** The content security of an HTML block: it loads what is inside its document, and nothing from anywhere else. */
45
+ export declare const HTML_BLOCK_CSP = "default-src 'none'; img-src data:; media-src data:; font-src data:; style-src 'unsafe-inline' data:; script-src 'unsafe-inline' data:";
46
+ /**
47
+ * An HTML block's document: its index.html with every file it names — a
48
+ * src, an href, a url() in its styles — written into it, and a policy that
49
+ * lets it load nothing else. Undefined without an index.html.
50
+ */
51
+ export declare function htmlDocument(files: Map<string, Uint8Array>): string | undefined;
@@ -0,0 +1,13 @@
1
+ /** An attribute's value: a text, a number or a yes/no. */
2
+ export type PaywallAttribute = string | number | boolean;
3
+ export declare const MAX_ATTRIBUTES = 20;
4
+ export declare const MAX_ATTRIBUTES_BYTES = 2048;
5
+ export declare const MAX_ATTRIBUTE_TEXT_BYTES = 256;
6
+ /**
7
+ * The attributes as the header carries them; undefined for none. Throws
8
+ * `invalid_attributes` for what the module would refuse: more than 20, a key
9
+ * that is not a letter then letters, digits and `_ . -` (40 at most), a text
10
+ * over 256 bytes, a number that is not finite, a value of another kind, or
11
+ * more than 2 KiB of them together.
12
+ */
13
+ export declare function attributesHeader(attributes: Readonly<Record<string, PaywallAttribute>> | undefined): string | undefined;
@@ -0,0 +1,5 @@
1
+ export declare const ICONS: Readonly<Record<string, readonly string[]>>;
2
+ /** The close button's mark: not one of the schema's icons. */
3
+ export declare const CLOSE: string[];
4
+ /** An icon as an inline SVG, sized in CSS pixels and colored by its text color; hidden from assistive technology. */
5
+ export declare function icon(doc: Document, paths: readonly string[], size: number): SVGSVGElement;
@@ -0,0 +1,7 @@
1
+ export { PAYWALL_COMPONENTS, PAYWALL_SCHEMA, countdownValues, packagesOf, resolution as resolvePaywallWithPaths, resolve as resolvePaywall, type Appearance, type PackageValues, type PaywallDefinition, type Platform, type ResolvedNode, type ResolvedPaywall, type ResolvedStyle, type Resolution, type ResolveOptions, } from './resolve.js';
2
+ export { renderPaywall, type PaywallHandlers, type PaywallInput, type PaywallPackage, type PaywallState, type PaywallView } from './render.js';
3
+ export { assetId, fontFamily, htmlDocument, prepareAssets, unzip, type PaywallAssetRef, type PreparedAsset } from './assets.js';
4
+ export { attributesHeader, type PaywallAttribute } from './attributes.js';
5
+ export { formatPeriod, formatPrice, packageValues, parseDuration, type PricedPackage } from './prices.js';
6
+ export { ICONS as PAYWALL_ICONS } from './icons.js';
7
+ export { Paywall, defaultPaywall, type PaywallRenderOptions } from './placement.js';
@@ -0,0 +1,99 @@
1
+ import type { purchases as purchasesModule } from '../../../internal/src/modules/index.js';
2
+ import { type PreparedAsset } from './assets.js';
3
+ import { type PaywallAttribute } from './attributes.js';
4
+ import { type PricedPackage } from './prices.js';
5
+ import { type PaywallHandlers, type PaywallPackage, type PaywallView } from './render.js';
6
+ import { type Appearance, type PackageValues, type PaywallDefinition } from './resolve.js';
7
+ type Offerings = purchasesModule.Offerings;
8
+ type Api = ReturnType<typeof purchasesModule.client>;
9
+ export interface PaywallRenderOptions {
10
+ /** Stripe sends the customer back here after buying (one of the project's registered apps). */
11
+ successUrl: string;
12
+ /** … and here when they turn back. */
13
+ cancelUrl: string;
14
+ /** The system's, and kept with it as it changes, unless given. */
15
+ appearance?: Appearance;
16
+ /** The browser's language unless given. */
17
+ locale?: string;
18
+ /** The app's terms and privacy policy (https), for the definition's `terms` and `privacy` buttons. */
19
+ links?: {
20
+ terms?: string;
21
+ privacy?: string;
22
+ };
23
+ /** The close button; without one the paywall draws none. */
24
+ onClose?(): void;
25
+ /** A link the person opened; without one, the browser opens it in a new tab. */
26
+ onLink?(url: string): void;
27
+ /** Stripe Checkout's page for the package bought: the page goes there unless the app opens it itself. */
28
+ onCheckout?(url: string, pkg: PaywallPackage): void;
29
+ /** A purchase that could not start (no one signed in, a guest, the network). Without one it is thrown. */
30
+ onError?(error: unknown, pkg: PaywallPackage): void;
31
+ labels?: PaywallHandlers['labels'];
32
+ }
33
+ interface PaywallDeps {
34
+ checkout(opts: {
35
+ product: string;
36
+ plan: string;
37
+ successUrl: string;
38
+ cancelUrl: string;
39
+ }): Promise<string>;
40
+ expose(paywall: Paywall): Promise<void>;
41
+ }
42
+ /** What a placement shows: the paywall, where it came from, and what it sells. */
43
+ export declare class Paywall {
44
+ readonly placement: string;
45
+ /** The server's answer now, the copy kept from before when it could not be asked, or the SDK's own from the catalog (nothing published there). */
46
+ readonly source: 'server' | 'cache' | 'default';
47
+ readonly definition: PaywallDefinition;
48
+ /** The paywall's assets, downloaded and checked: those that passed, by id. */
49
+ readonly assets: Record<string, PreparedAsset>;
50
+ /** What the paywall sells on the web, priced as the catalog prices it. */
51
+ readonly offerings: Offerings;
52
+ /** The paywall and the version shown, the experiment's variant or the placement's rule that chose it; absent for the SDK's own. */
53
+ readonly shown: {
54
+ paywallId: string;
55
+ version: number;
56
+ variant?: string;
57
+ ruleId?: string;
58
+ } | undefined;
59
+ private deps;
60
+ private exposed;
61
+ constructor(placement: string, source: Paywall['source'], definition: PaywallDefinition, offerings: Offerings, shown: Paywall['shown'], deps: PaywallDeps, assets?: Record<string, PreparedAsset>);
62
+ /** The store's values of each package the paywall sells, in a language: what its texts' variables read. */
63
+ packages(locale: string): Record<string, PackageValues>;
64
+ /**
65
+ * Draws the paywall into a container and counts it shown — once, however
66
+ * often it is drawn. The purchase button opens Stripe Checkout for the
67
+ * package selected; the web has no restore button (a web purchase is the
68
+ * account's, wherever it signs in).
69
+ */
70
+ render(container: HTMLElement, opts: PaywallRenderOptions): PaywallView;
71
+ }
72
+ /** The packages the web sells of those named, priced as its catalog prices them. */
73
+ export declare function webPackages(offerings: Offerings, keys: readonly string[], locale: string): PricedPackage[];
74
+ /**
75
+ * The SDK's own paywall, where nothing is published: every plan the web
76
+ * sells (the first six) as a card — its product, period and price —, the
77
+ * purchase button and the legal links; plain, in the system's colors. Null
78
+ * when the web sells nothing.
79
+ */
80
+ export declare function defaultPaywall(offerings: Offerings): PaywallDefinition | null;
81
+ /** This browser's installation id: who a paywall is shown to before anyone signs in — the same experiment variant every time. */
82
+ export declare function installationId(ref: string): string;
83
+ /** The placement's paywall (see Purchases.paywall). */
84
+ export declare function loadPaywall(api: Api, ref: string, placement: string, checkout: (opts: {
85
+ product: string;
86
+ plan: string;
87
+ successUrl: string;
88
+ cancelUrl: string;
89
+ }) => Promise<string>, ask?: {
90
+ signal?: AbortSignal;
91
+ fetch?: (input: string, init?: RequestInit) => Promise<Response>;
92
+ /** What the app says of the person, for the placement's targeting rules. */
93
+ attributes?: Readonly<Record<string, PaywallAttribute>>;
94
+ /** The language the app shows, when it is not the browser's. */
95
+ locale?: string;
96
+ /** The app's version, when the client was given one. */
97
+ appVersion?: string;
98
+ }): Promise<Paywall | null>;
99
+ export {};
@@ -0,0 +1,40 @@
1
+ import type { PackageValues } from './resolve.js';
2
+ /** A package as a channel sells it: its product's name there, its price and, for a subscription, its period and trial. */
3
+ export interface PricedPackage {
4
+ /** `<product>.<plan>` */
5
+ key: string;
6
+ productName: string;
7
+ /** In the currency's minor units (999 is 9.99), as the catalog holds it. */
8
+ price: {
9
+ amount: number;
10
+ currency: string;
11
+ };
12
+ /** A subscription's billing period, ISO 8601 (P1M, P1Y …). */
13
+ period?: string;
14
+ /** An introductory offer; a free trial (mode free_trial) is the `{{trial}}` variable. */
15
+ intro?: {
16
+ mode: string;
17
+ duration: string;
18
+ };
19
+ }
20
+ interface Duration {
21
+ count: number;
22
+ unit: 'day' | 'week' | 'month' | 'year';
23
+ /** How many months it is, for a price per month. */
24
+ months: number;
25
+ }
26
+ /** P1Y, P3M, P1W, P7D — one unit, as the stores sell; null for anything else. */
27
+ export declare function parseDuration(iso: string | undefined): Duration | null;
28
+ /** An amount in minor units, in its currency as the language writes it. */
29
+ export declare function formatPrice(amount: number, currency: string, locale: string): string;
30
+ /** "month" for one, "3 months" for more — the language's own words (Intl's units). */
31
+ export declare function formatPeriod(d: Duration, locale: string): string;
32
+ /**
33
+ * The variables of each package, by key. `{{savings}}` is what a
34
+ * subscription saves a month against the dearest a month of the packages
35
+ * given in its currency (none for the dearest, nor below 1%); a package
36
+ * that is not a subscription has no `{{period}}`, `{{price_per_month}}` or
37
+ * `{{savings}}`, and one without a free trial no `{{trial}}`.
38
+ */
39
+ export declare function packageValues(packages: readonly PricedPackage[], locale: string): Record<string, PackageValues>;
40
+ export {};
@@ -0,0 +1,93 @@
1
+ import { type PreparedAsset } from './assets.js';
2
+ import { type PaywallDefinition, type Platform, type Appearance, type PackageValues } from './resolve.js';
3
+ /** A package of the catalog: what the purchase button buys. */
4
+ export interface PaywallPackage {
5
+ product: string;
6
+ plan: string;
7
+ }
8
+ /** What a paywall is drawn from. */
9
+ export interface PaywallInput {
10
+ definition: PaywallDefinition;
11
+ /** The reader's language (`tr-TR`). */
12
+ locale: string;
13
+ appearance: Appearance;
14
+ /** Where it is drawn: the web, unless a preview draws another platform's. */
15
+ platform?: Platform;
16
+ /** The store's values of each package, by `<product>.<plan>`; a package without any is drawn, not sold. */
17
+ packages: Record<string, PackageValues>;
18
+ /** The packages whose introductory offer the customer can still take. */
19
+ eligible?: readonly string[];
20
+ /** The app's own addresses of `terms` and `privacy` (https); a button or link to one is not drawn without it. */
21
+ links?: {
22
+ terms?: string;
23
+ privacy?: string;
24
+ };
25
+ /** The definition's assets ready to draw, by id (prepareAssets): one missing is drawn without. */
26
+ assets?: Record<string, PreparedAsset>;
27
+ /** The state to draw: a preview sets it; an app leaves it to the person. */
28
+ state?: PaywallState;
29
+ /** Entrances as components appear (true); never when the system asks for less motion. */
30
+ animate?: boolean;
31
+ /** Draws as if the system asked for less motion (a preview's choice); the system's own otherwise. */
32
+ reduceMotion?: boolean;
33
+ }
34
+ /** What the person has done to the paywall, and its clock. */
35
+ export interface PaywallState {
36
+ /** The package the person selected. */
37
+ selected?: string;
38
+ /** Each tabs component's tab, and each switch's side, by the component's path. */
39
+ tabs?: Record<string, string>;
40
+ switches?: Record<string, boolean>;
41
+ /** The sheet open, by its id. */
42
+ sheet?: string | null;
43
+ /** When the paywall opened (ms since the epoch): a countdown from its opening counts from here. */
44
+ openedAt?: number;
45
+ /** A moment to draw, held still (ms since the epoch); the clock runs when absent. */
46
+ now?: number;
47
+ }
48
+ export interface PaywallHandlers {
49
+ /** The purchase button, for its package (the one selected, unless it names one); the button waits while a promise it returns runs. */
50
+ onPurchase(pkg: PaywallPackage): void | Promise<void>;
51
+ /** A restore button; without this one is not drawn (the web restores nothing: Stripe's purchases are the account's). */
52
+ onRestore?(): void | Promise<void>;
53
+ /** A close button; without this one is not drawn (a close inside a sheet closes the sheet). */
54
+ onClose?(): void;
55
+ /** A link the person opened; without this the browser opens it in a new tab. */
56
+ onLink?(url: string): void;
57
+ /** Told when the person picks another package. */
58
+ onSelect?(pkg: PaywallPackage): void;
59
+ /** The paywall's own words, which the definition does not hold: the app's, in its language. */
60
+ labels?: {
61
+ packages?: string;
62
+ close?: string;
63
+ restore?: string;
64
+ };
65
+ /** An offer code button: the web has none to redeem, so without this one is not drawn. */
66
+ onOfferCode?(): void | Promise<void>;
67
+ /** Told whenever the state changes: a preview keeps its controls with it. */
68
+ onState?(state: PaywallState): void;
69
+ /** A designer's canvas: a click picks the component under it (its definition path) instead of acting. */
70
+ editor?: {
71
+ onPick(path: string): void;
72
+ };
73
+ }
74
+ export interface PaywallView {
75
+ /** The package selected now. */
76
+ readonly selected: PaywallPackage | null;
77
+ /** Selects a package (`<product>.<plan>`) as the person would. */
78
+ select(key: string): void;
79
+ /** Removes it from the page. */
80
+ destroy(): void;
81
+ /** Draws it again from what changed: a new definition, the other appearance, another language, new prices or assets, a state to show. */
82
+ update(input: Partial<PaywallInput>): void;
83
+ /** The state drawn now. */
84
+ readonly state: PaywallState;
85
+ /** Marks a component (by path) as a designer's canvas does; null clears it. */
86
+ highlight(path: string | null): void;
87
+ /** Plays every entrance again. */
88
+ replay(): void;
89
+ }
90
+ /** Draws a paywall into a container (its content is replaced). */
91
+ export declare function renderPaywall(container: HTMLElement, input: PaywallInput, handlers: PaywallHandlers): PaywallView;
92
+ /** The paywall's styles in its shadow root: a constructed sheet where the browser has them, a style element elsewhere. */
93
+ export declare function adoptStyle(doc: Document, shadow: ShadowRoot, css: string): void;
@@ -0,0 +1,184 @@
1
+ import type { purchases } from '../../../internal/src/modules/index.js';
2
+ export type PaywallDefinition = purchases.PaywallDefinition;
3
+ export type Platform = 'ios' | 'android' | 'web';
4
+ export type Appearance = 'light' | 'dark';
5
+ /** What the store gave a package, as the variables of its texts: `{{price}}` … */
6
+ export interface PackageValues {
7
+ price?: string;
8
+ price_per_month?: string;
9
+ period?: string;
10
+ trial?: string;
11
+ savings?: string;
12
+ product_name?: string;
13
+ }
14
+ /** The schema this renderer draws: what the SDK asks the server for (X-Palbase-Paywall-Schema). */
15
+ export declare const PAYWALL_SCHEMA = 2;
16
+ export interface ResolveOptions {
17
+ /** The app's language (`tr-TR`). */
18
+ locale: string;
19
+ appearance: Appearance;
20
+ platform: Platform;
21
+ /** The store's values of each package, by `<product>.<plan>`. */
22
+ packages: Record<string, PackageValues>;
23
+ /** The packages whose introductory offer (a trial) the customer can still take. */
24
+ eligible?: readonly string[];
25
+ /** The package the customer selected; absent, the paywall's own selection. */
26
+ selected?: string;
27
+ /** The tab shown in every tabs component, and each switch's side — the paywall's defaults when absent. */
28
+ tab?: string;
29
+ switch?: 'on' | 'off';
30
+ /** One tabs component's tab, or one switch's side, by the component's path (`root.children[3]`): what the person chose. */
31
+ tabs?: Record<string, string>;
32
+ switches?: Record<string, boolean>;
33
+ /** A countdown's time left: how long ago the paywall opened, and the moment it is drawn (ms since the epoch). */
34
+ openedSecondsAgo?: number;
35
+ now?: number;
36
+ }
37
+ /** A component resolved: its type, its fields with every token and default resolved. */
38
+ export type ResolvedNode = {
39
+ type: string;
40
+ id?: string;
41
+ style?: ResolvedStyle;
42
+ animation?: ResolvedAnimation;
43
+ [field: string]: unknown;
44
+ };
45
+ export interface ResolvedEdges {
46
+ top: number;
47
+ bottom: number;
48
+ leading: number;
49
+ trailing: number;
50
+ }
51
+ export interface ResolvedCorners {
52
+ top_leading: number;
53
+ top_trailing: number;
54
+ bottom_leading: number;
55
+ bottom_trailing: number;
56
+ }
57
+ export type ResolvedStop = {
58
+ color: string;
59
+ at: number;
60
+ };
61
+ export type ResolvedFill = {
62
+ type: 'color';
63
+ color: string;
64
+ } | {
65
+ type: 'linear';
66
+ angle: number;
67
+ stops: ResolvedStop[];
68
+ } | {
69
+ type: 'radial';
70
+ center_x: number;
71
+ center_y: number;
72
+ radius: number;
73
+ stops: ResolvedStop[];
74
+ } | {
75
+ type: 'image';
76
+ src: string;
77
+ fit: 'fit' | 'fill';
78
+ } | {
79
+ type: 'video';
80
+ src: string;
81
+ poster?: string;
82
+ loop: boolean;
83
+ };
84
+ export interface ResolvedStyle {
85
+ padding?: ResolvedEdges;
86
+ margin?: ResolvedEdges;
87
+ width?: number | string;
88
+ height?: number | string;
89
+ max_width?: number;
90
+ background?: ResolvedFill;
91
+ border?: {
92
+ width: number;
93
+ color: string;
94
+ };
95
+ corners?: ResolvedCorners;
96
+ shadow?: {
97
+ color: string;
98
+ radius: number;
99
+ x: number;
100
+ y: number;
101
+ };
102
+ opacity?: number;
103
+ }
104
+ export interface ResolvedAnimation {
105
+ type: 'fade' | 'slide_up' | 'slide_down' | 'slide_leading' | 'slide_trailing' | 'scale';
106
+ duration_ms: number;
107
+ delay_ms: number;
108
+ easing: 'ease_out' | 'ease_in_out' | 'spring';
109
+ }
110
+ export interface ResolvedTypeStyle {
111
+ font: string;
112
+ size: number;
113
+ weight: number;
114
+ line_height?: number;
115
+ letter_spacing: number;
116
+ text_style: string;
117
+ }
118
+ /** The definition as drawn, the contract's resolved tree. */
119
+ export interface ResolvedPaywall {
120
+ selected: string;
121
+ theme: {
122
+ colors: Record<string, string>;
123
+ typography: Record<string, ResolvedTypeStyle>;
124
+ radii: Record<string, number>;
125
+ spacing: Record<string, number>;
126
+ };
127
+ header?: ResolvedNode;
128
+ root?: ResolvedNode;
129
+ footer?: ResolvedNode;
130
+ sheets?: Record<string, ResolvedNode>;
131
+ }
132
+ /** Beside the tree: where in the definition each node is (its path, as the server's problems name it), and what is not part of the tree. */
133
+ export interface Resolution {
134
+ tree: ResolvedPaywall;
135
+ /** A node's path in the definition: `root.children[2]`, `sheets.offer.root`. */
136
+ pathOf(node: object): string | undefined;
137
+ /** The tab shown in each tabs component and each switch's side, by path. */
138
+ tabs: Record<string, string>;
139
+ switches: Record<string, boolean>;
140
+ /** Each sheet's title, in the language drawn. */
141
+ sheetTitles: Record<string, string>;
142
+ /** The time a counting countdown has left, by path: the renderer ticks while any does. */
143
+ countdowns: Record<string, number>;
144
+ /** The language the definition is drawn in. */
145
+ language: string;
146
+ }
147
+ type Value = 'plain' | 'color' | 'textColor' | 'spacing' | 'radius';
148
+ type Field = {
149
+ kind: 'value';
150
+ value: Value;
151
+ def?: unknown;
152
+ required?: boolean;
153
+ } | {
154
+ kind: 'text';
155
+ required?: boolean;
156
+ } | {
157
+ kind: 'list';
158
+ item: string;
159
+ required?: boolean;
160
+ } | {
161
+ kind: 'object';
162
+ item: string;
163
+ required?: boolean;
164
+ } | {
165
+ kind: 'component';
166
+ required?: boolean;
167
+ } | {
168
+ kind: 'components';
169
+ } | {
170
+ kind: 'edges' | 'corners' | 'fill';
171
+ };
172
+ type Spec = Record<string, Field>;
173
+ export declare const COMPONENT_FIELDS: Record<string, Spec>;
174
+ /** The components a definition has, by type. */
175
+ export declare const PAYWALL_COMPONENTS: readonly string[];
176
+ /** {{countdown}} and its parts for a time left, in seconds. */
177
+ export declare function countdownValues(left: number): Record<string, string>;
178
+ /** Every package a definition sells, `<product>.<plan>`, in the order it names them: the header, the root, the footer, then its sheets. */
179
+ export declare function packagesOf(definition: PaywallDefinition): string[];
180
+ /** A definition as drawn (contracts/paywall/README.md). */
181
+ export declare function resolve(definition: PaywallDefinition, opts: ResolveOptions): ResolvedPaywall;
182
+ /** The same, with each node's path in the definition and the state the renderer keeps. */
183
+ export declare function resolution(definition: PaywallDefinition, opts: ResolveOptions): Resolution;
184
+ export {};
@@ -1,7 +1,9 @@
1
1
  import { purchases as purchasesModule } from '../../internal/src/modules/index.js';
2
- import type { Transport } from '../../internal/src/transport.js';
2
+ import type { Fetch, Transport } from '../../internal/src/transport.js';
3
3
  import type { SessionManager } from './session.js';
4
4
  import type { RealtimeSocket } from './socket.js';
5
+ import type { PaywallAttribute } from './paywall/attributes.js';
6
+ import { type Paywall } from './paywall/placement.js';
5
7
  type P = typeof purchasesModule;
6
8
  export type CustomerInfo = purchasesModule.CustomerInfo;
7
9
  export type Offerings = purchasesModule.Offerings;
@@ -14,16 +16,46 @@ export declare class Purchases {
14
16
  private session;
15
17
  private socket;
16
18
  private ref;
19
+ /** What a paywall's assets are downloaded with: the app's fetch, when it gave one. */
20
+ private fetch;
21
+ /** The app's version, when it gave one: a paywall's request says it. */
22
+ private appVersion;
17
23
  private listeners;
18
24
  private watch;
19
25
  private following;
20
26
  /** What listeners were last told: undefined until the first read (or sign-out) is known. */
21
27
  private last;
22
- constructor(transport: Transport, session: SessionManager, socket: RealtimeSocket, ref: string);
28
+ constructor(transport: Transport, session: SessionManager, socket: RealtimeSocket, ref: string, fetch?: Fetch, appVersion?: string);
23
29
  /** Everything the signed-in customer has, read now (the first read makes the user a customer). */
24
30
  me(): Promise<CustomerInfo>;
25
31
  /** The paywall's catalog: each product's text and price on a channel — the web's unless another is named. */
26
32
  offerings(channel?: 'ios' | 'android' | 'web'): Promise<Offerings>;
33
+ /**
34
+ * The paywall the project shows at a placement — the place in the app it
35
+ * names (`onboarding`, `settings`) — to draw with render(), its assets
36
+ * downloaded and checked first. Its version is the newest published in a
37
+ * schema this SDK draws (an experiment's
38
+ * variant while one runs there, the same for this person every time); the
39
+ * browser keeps it, to ask again with its ETag and to show offline. With
40
+ * nothing published there it is the SDK's own, of every plan the web
41
+ * sells; null when the web sells nothing.
42
+ *
43
+ * Which paywall a placement shows may depend on its targeting rules: they
44
+ * read what the request says of the app (the web, the SDK, the version the
45
+ * client was made with, where it is asked from), its language — the
46
+ * browser's, or `locale` when the app shows another — and `attributes`,
47
+ * what the app says of the person
48
+ * (`{ plan: 'free', sessions: 12 }`: texts, numbers, yes/no). An attribute
49
+ * the module would refuse throws `invalid_attributes` before anything is
50
+ * asked. Targeting picks what to show; it is not access control: the
51
+ * country, the attributes and the versions are what the app says of
52
+ * itself, so no price or region entitlement may hang on them.
53
+ */
54
+ paywall(placement: string, opts?: {
55
+ attributes?: Readonly<Record<string, PaywallAttribute>>;
56
+ locale?: string;
57
+ signal?: AbortSignal;
58
+ }): Promise<Paywall | null>;
27
59
  /**
28
60
  * Opens Stripe Checkout for the signed-in customer (not a guest) to buy a
29
61
  * product's plan sold on the web: the URL to send them to. Stripe sends
@@ -1,2 +0,0 @@
1
- /*! palbase 0.1.0 — Palbase SDK License (see LICENSE) */
2
- import{a as D}from"./6FWCNTAM.js";import{a as A,b as R,c as b,d as x,e as O,f as j,g as U,h as C,i as L,j as M,k as K,l as $}from"./IKVOXHEE.js";import{b as v,d as w,f as N,g as I}from"./HKJU2FKE.js";var ae=/^pb_[a-z0-9]+_s/;function E(n){if(D())throw new TypeError("palbase/server runs on a server: its secret key never goes into a browser or a mobile app \u2014 apps use palbase/web (or the native SDKs) with the publishable key");if(typeof n.secretKey!="string"||!ae.test(n.secretKey))throw new TypeError("secretKey is the project secret key (pb_<ref>_s\u2026); a publishable key belongs in palbase/web");let e=I(w({baseUrl:n.url,credentials:{secretKey:n.secretKey},fetch:n.fetch,solveProofOfWork:n.solveProofOfWork??!1}),n.retry??N);return{ai:A.client(e),analytics:R.client(e),auth:b.client(e),cache:x.client(e),flags:O.client(e),machines:j.client(e),notify:U.client(e),purchases:C.client(e),realtime:L.client(e),secrets:M.client(e),storage:K.client(e),stores:$.client(e),transport:e,url:new URL(n.url).origin}}var f=class extends Error{code;constructor(e,t){super(t),this.name="WebhookSignatureError",this.code=e}},ue=300,de=/^[0-9]{1,12}$/,le=/^[0-9a-f]{64}$/,W=8,fe=/^whsec_[0-9A-Za-z]{32,}$/,he=1024*1024;async function B(n){if(typeof n.secret!="string"||!fe.test(n.secret))throw new TypeError("secret is the webhook endpoint secret (whsec_\u2026), not an API key");await F(n);let e;try{e=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(z(n.body)))}catch{throw new f("invalid_body","the signed body is not JSON")}if(!be(e))throw new f("invalid_body","the signed body is not a Palbase event (id, type, project_id, created_at, data)");return e}async function F(n){if(typeof n.secret!="string"||n.secret==="")throw new TypeError("secret is the key the request was signed with");let e=z(n.body),{t,v1:r}=H(n),s=await crypto.subtle.importKey("raw",new TextEncoder().encode(n.secret),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=new Uint8Array(await crypto.subtle.sign("HMAC",s,we(new TextEncoder().encode(`${t}.`),e))),i=!1;for(let u of r)me(o,ge(u))&&(i=!0);if(!i)throw new f("signature_mismatch","no v1 in Palbase-Signature is this body signed with this secret")}function H(n){let e=n.toleranceSeconds??ue;if(!Number.isFinite(e)||e<0)throw new TypeError(`toleranceSeconds is a number of seconds, 0 or more (got ${String(n.toleranceSeconds)})`);let t=(n.now??Date.now)();if(!Number.isFinite(t))throw new TypeError(`now() returns epoch milliseconds (got ${String(t)})`);let r=n.signature;if(typeof r!="string"||r.trim()==="")throw new f("missing_signature","the request carries no Palbase-Signature header");let s,o=[];for(let i of r.split(",")){let u=i.indexOf("=");if(u<=0)throw new f("malformed_signature","Palbase-Signature is t=<seconds>,v1=<hex>");let c=i.slice(0,u).trim(),a=i.slice(u+1).trim();if(c==="t"){if(s!==void 0||!de.test(a))throw new f("malformed_signature","Palbase-Signature carries one t, in unix seconds");s=a}else if(c==="v1"){if(!le.test(a))throw new f("malformed_signature","a v1 is 64 lowercase hex characters");if(o.length===W)throw new f("malformed_signature",`Palbase-Signature carries at most ${W} v1`);o.push(a)}}if(s===void 0||o.length===0)throw new f("malformed_signature","Palbase-Signature is t=<seconds>,v1=<hex>");if(Math.abs(t/1e3-Number(s))>e)throw new f("stale_timestamp",`the signature's time is more than ${e} s from now: an old delivery sent again is not taken`);return{t:s,v1:o}}async function pe(n,e,t={}){let r=await ye(n,t.maxBytes??he);return B({toleranceSeconds:t.toleranceSeconds,now:t.now,secret:e,signature:n.headers.get("palbase-signature"),body:r})}async function ye(n,e){let t=()=>new f("body_too_large",`the body is larger than ${e} bytes`),r=Number(n.headers.get("content-length")??"NaN");if(Number.isFinite(r)&&r>e)throw t();if(!n.body)return new Uint8Array(0);let s=n.body.getReader(),o=[],i=0;for(;;){let{done:a,value:l}=await s.read();if(a)break;if(i+=l.byteLength,i>e)throw await s.cancel().catch(()=>{}),t();o.push(l)}let u=new Uint8Array(i),c=0;for(let a of o)u.set(a,c),c+=a.byteLength;return u}function z(n){if(typeof n=="string")return new TextEncoder().encode(n);if(n instanceof Uint8Array)return n;if(n instanceof ArrayBuffer)return new Uint8Array(n);throw new TypeError("body is the raw request body (string or bytes): a parsed object is not what was signed \u2014 read the body before any JSON middleware")}function ge(n){let e=new Uint8Array(n.length/2);for(let t=0;t<e.length;t++)e[t]=parseInt(n.slice(t*2,t*2+2),16);return e}function me(n,e){if(n.byteLength!==e.byteLength)return!1;let t=0;for(let r=0;r<n.byteLength;r++)t|=n[r]^e[r];return t===0}function we(n,e){let t=new Uint8Array(n.byteLength+e.byteLength);return t.set(n,0),t.set(e,n.byteLength),t}function be(n){if(typeof n!="object"||n===null)return!1;let e=n;return typeof e.id=="string"&&typeof e.type=="string"&&typeof e.project_id=="string"&&typeof e.created_at=="string"&&"data"in e}var V=60,ke=60,Se=8*1024,ve=1e5;function _(n){let e=n instanceof Uint8Array?n:new Uint8Array(n),t="";for(let r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Y(n){let e=n.replace(/[\s=]+/g,"");if(!/^[A-Za-z0-9+/_-]*$/.test(e)||e.length%4===1)return;let t=atob(e.replace(/-/g,"+").replace(/_/g,"/").padEnd(Math.ceil(e.length/4)*4,"=")),r=new Uint8Array(t.length);for(let s=0;s<t.length;s++)r[s]=t.charCodeAt(s);return r}function J(n){let e=Y(n);if(e)try{let t=JSON.parse(new TextDecoder().decode(e));return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:void 0}catch{return}}var k=class{byKey=new Map;held=0;sweptAt=Number.NEGATIVE_INFINITY;max;constructor(e=ve){this.max=e}spend(e,t){if(t>=e.until)throw new d("proof","the DPoP proof is not taken: it was checked too long ago");let r=this.byKey.get(e.jkt);if(r){this.forgetOld(e.jkt,r,t);let s=r.get(e.jti);if(s!==void 0&&s>t)throw new d("proof","the DPoP proof is not taken: it was used before");s!==void 0&&r.delete(e.jti)&&this.held--}this.held>=this.max&&this.makeRoom(t),r=this.byKey.get(e.jkt),r||(r=new Map,this.byKey.set(e.jkt,r)),r.set(e.jti,e.until),this.held++}forgetOld(e,t,r){for(let[s,o]of t){if(o>r)break;t.delete(s),this.held--}t.size===0&&this.byKey.delete(e)}makeRoom(e){if(e-this.sweptAt>=1e3){this.sweptAt=e;for(let[r,s]of this.byKey){for(let[o,i]of s)i>e||(s.delete(o),this.held--);s.size===0&&this.byKey.delete(r)}this.held=0;for(let r of this.byKey.values())this.held+=r.size}if(this.held<this.max)return;let t=[...this.byKey].sort((r,s)=>s[1].size-r[1].size);for(let[r,s]of t){if(this.held<=this.max*.9)break;this.byKey.delete(r),this.held-=s.size}}};async function Ee(n,e){let t=`{"crv":"P-256","kty":"EC","x":"${n}","y":"${e}"}`;return _(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)))}function q(n){try{let e=new URL(n);return e.username!==""||e.password!==""?void 0:`${e.protocol}//${e.host}${e.pathname}`}catch{return}}async function G(n,e,t,r){let s=ie=>{throw new d("proof",`the DPoP proof is not taken: ${ie}`)},o=t.proof;if(!o)return s("there is none (a DPoP-bound token comes with a DPoP header)");if(o.length>Se)return s("it is too long");let i=o.split(".");if(i.length!==3)return s("it is not a JWS of three parts");let u=J(i[0]),c=J(i[1]);if(!u||!c)return s("its header or claims are not base64url JSON");if(u.typ!=="dpop+jwt")return s("its typ is not dpop+jwt");if(u.alg!=="ES256")return s(`it is signed with ${String(u.alg)}; only ES256 is taken`);let a=u.jwk;if(!a||typeof a!="object"||a.kty!=="EC"||a.crv!=="P-256"||typeof a.x!="string"||typeof a.y!="string")return s("its header carries no P-256 public key");if("d"in a)return s("its header carries a private key");if(await Ee(a.x,a.y)!==e)return s("it is signed by a key the token does not name");let l;try{l=await crypto.subtle.importKey("jwk",{kty:"EC",crv:"P-256",x:a.x,y:a.y,ext:!0},{name:"ECDSA",namedCurve:"P-256"},!1,["verify"])}catch{return s("its key is not a P-256 point")}let p=Y(i[2]);if(!p||!await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},l,p,new TextEncoder().encode(`${i[0]}.${i[1]}`)).catch(()=>!1))return s("its signature does not verify");if(typeof c.htm!="string"||c.htm.toUpperCase()!==t.method.toUpperCase())return s(`it is for ${String(c.htm)}, not ${t.method}`);let h=typeof c.htu=="string"?q(c.htu):void 0,y=q(t.url);if(!h||!y||h!==y)return s(`it is for ${String(c.htu)}, not ${y??t.url}`);let re=r/1e3;if(typeof c.iat!="number"||Math.abs(c.iat-re)>V)return s("it was not made within a minute of now");let se=_(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(n)));if(c.ath!==se)return s("it is not for this token (ath)");if(typeof c.jti!="string"||c.jti===""||c.jti.length>256)return s("it has no jti (or one longer than 256)");let oe=_(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(c.jti)));return{jkt:e,jti:oe,until:(c.iat+V+ke)*1e3+1}}var d=class extends Error{reason;constructor(e,t){super(t),this.name="TokenError",this.reason=e}},g=class extends Error{constructor(e,t){super(e,t),this.name="AuthUnavailable"}};function _e(n){return async e=>{let t=b.client(w({baseUrl:n.url,credentials:{secretKey:n.secretKey,accessToken:()=>e},fetch:n.fetch}));try{return await t.verifyTokenUser(),!0}catch(r){if(r instanceof v&&r.status===401)return!1;throw r}}}var T={ES256:{kty:"EC",import:{name:"ECDSA",namedCurve:"P-256"},verify:{name:"ECDSA",hash:"SHA-256"}},PS256:{kty:"RSA",import:{name:"RSA-PSS",hash:"SHA-256"},verify:{name:"RSA-PSS",saltLength:32}}},X=60,Te=16*1024,Pe=10*6e4,Ae=5e4,Z=n=>typeof n=="string"&&Object.hasOwn(T,n),P=class{issuer;projectId;fetchImpl;now;maxKeyAgeMs;refetchIntervalMs;fetchTimeoutMs;standing;standingTtlMs;standings=new Map;asking=new Map;keys=new Map;keysUntil=0;lastFetch=Number.NEGATIVE_INFINITY;lastFailure=void 0;inflight=null;seenProofs=new k;constructor(e){let t=new URL(e.issuer);if(t.origin!==e.issuer.replace(/\/$/,""))throw new TypeError(`issuer is the project's origin (https://<ref>.<domain>), not ${e.issuer}`);if(typeof e.projectId!="string"||e.projectId==="")throw new TypeError("projectId is the project id the tokens carry");if(this.issuer=t.origin,this.projectId=e.projectId,this.fetchImpl=e.fetch??globalThis.fetch,this.now=e.now??Date.now,this.maxKeyAgeMs=e.maxKeyAgeMs??60*6e4,this.refetchIntervalMs=e.refetchIntervalMs??3e4,this.fetchTimeoutMs=e.fetchTimeoutMs??5e3,typeof e.standing!="function")throw new TypeError("standing is how the verifier asks whether a token's session stands");this.standing=e.standing,this.standingTtlMs=e.standingTtlMs??15e3}async verify(e,t){if(typeof e!="string"||e.length===0||e.length>Te)throw new d("malformed","the token is not a JWT");let r=e.split(".");if(r.length!==3)throw new d("malformed","the token is not a JWS of three parts");let s=Q(r[0]),o=Q(r[1]);if(!s||!o)throw new d("malformed","the token header or claims are not base64url JSON");let i=s.alg;if(!Z(i))throw new d("algorithm",`the token is signed with ${String(i)}; only ES256 and PS256 are taken`);let u=s.kid;if(typeof u!="string"||u==="")throw new d("malformed","the token header names no kid");let c=await this.key(u);if(!c)throw new d("unknown_key",`no key ${u} in the project's key set`);if(c.alg!==i)throw new d("algorithm",`key ${u} is ${c.alg}, the token says ${i}`);let a=te(r[2]);if(!a)throw new d("malformed","the signature is not base64url");let l=new TextEncoder().encode(`${r[0]}.${r[1]}`);if(!await crypto.subtle.verify(T[c.alg].verify,c.key,a,l).catch(()=>!1))throw new d("signature","the signature does not verify");let m=this.check(o),h=o.cnf,y;if(h!==void 0){if(!t)throw new d("bound","the token is bound to a key (DPoP): it comes with the DPoP scheme and a proof, not as a Bearer");if(typeof h!="object"||h===null||typeof h.jkt!="string"||h.jkt==="")throw new d("bound","the token is bound to a key it does not name (cnf.jkt)");y=await G(e,h.jkt,t,this.now())}else if(t)throw new d("not_bound","the DPoP scheme came with a token bound to no key");if(!await this.stands(e,m.sessionId))throw new d("session_ended","the token's session has ended (signed out, revoked, or its user banned)");return y&&this.seenProofs.spend(y,this.now()),m}async verifyRequest(e){let t=e.headers instanceof Headers?e.headers:xe(e.headers),r=t.get("authorization")??"";if(r==="")return null;let s=/^(bearer|dpop)\s+(\S+)$/i.exec(r);if(!s)throw new d("malformed","the Authorization header is not a Bearer or DPoP token");let o=s[1].toLowerCase()==="dpop",i=new URL(e.url,`http://${t.get("host")??"localhost"}`);return this.verify(s[2],o?{proof:t.get("dpop"),method:e.method,url:ee(t,i)}:void 0)}async stands(e,t){let r=this.now(),s=this.standings.get(t);if(s&&r<s.until)return s.live;let o=this.asking.get(t);o||(o=this.standing(e,t).finally(()=>this.asking.delete(t)),this.asking.set(t,o));let i;try{i=await o}catch(u){throw new g("a session's standing cannot be read now",{cause:u})}return this.standings.delete(t),this.standings.set(t,{live:i,until:this.now()+this.standingTtlMs}),this.standings.size>Ae&&this.standings.delete(this.standings.keys().next().value),i}check(e){if(e.iss!==this.issuer)throw new d("issuer",`the token was issued by ${String(e.iss)}, not this project`);if(!(Array.isArray(e.aud)?e.aud:[e.aud]).includes(this.issuer))throw new d("audience","the token is not meant for this project");if(e.project_id!==this.projectId)throw new d("project","the token names another project");let r=this.now()/1e3;if(typeof e.exp!="number"||e.exp+X<=r)throw new d("expired","the token has expired");if(e.nbf!==void 0&&(typeof e.nbf!="number"||e.nbf-X>r))throw new d("not_yet_valid","the token is not valid yet");if(typeof e.sub!="string"||e.sub===""||typeof e.auth_time!="number")throw new d("not_a_user","the token is not a user's");if(typeof e.session_id!="string"||e.session_id==="")throw new d("not_a_session","the token is no session's");let s=e.custom_claims;if(s!==void 0&&(typeof s!="object"||s===null||s.type!==void 0))throw new d("not_a_session","the token is a custom one (an exchange or an approval), not a session's");return{id:e.sub,email:typeof e.email=="string"?e.email:null,emailVerified:e.email_verified===!0,role:typeof e.role=="string"?e.role:null,sessionId:typeof e.session_id=="string"?e.session_id:null,claims:Object.freeze({...e})}}async key(e){let t=this.now();if(t>=this.keysUntil){let s=this.keys.size>0&&t<this.keysUntil+Pe;if(this.lastFailure!==void 0&&t-this.lastFetch<this.refetchIntervalMs){if(!s)throw new g("the project's key set cannot be read now",{cause:this.lastFailure})}else try{await this.refresh()}catch(o){if(!s)throw o}}let r=this.keys.get(e);return!r&&t-this.lastFetch>=this.refetchIntervalMs&&(await this.refresh(),r=this.keys.get(e)),r}refresh(){if(this.inflight)return this.inflight;let e=this.load().finally(()=>{this.inflight=null});return this.inflight=e,e}async load(){this.lastFetch=this.now();let e,t;try{if(t=await this.fetchImpl(`${this.issuer}/.well-known/jwks.json`,{redirect:"error",headers:{accept:"application/json"},signal:AbortSignal.timeout(this.fetchTimeoutMs)}),!t.ok)throw new Error(`the project's key set answered ${t.status}`);e=await t.json()}catch(o){throw this.lastFailure=o,new g("the project's key set cannot be read now",{cause:o})}this.lastFailure=void 0;let r=new Map;for(let o of Array.isArray(e.keys)?e.keys:[]){let i=o.alg;if(typeof o.kid!="string"||!Z(i))continue;let u=T[i];if(o.kty!==u.kty||o.use!==void 0&&o.use!=="sig")continue;let{d:c,...a}=o;try{r.set(o.kid,{alg:i,key:await crypto.subtle.importKey("jwk",a,u.import,!1,["verify"])})}catch{}}this.keys=r;let s=Oe(t.headers.get("cache-control"))??this.maxKeyAgeMs;this.keysUntil=this.now()+Math.min(Math.max(s,this.refetchIntervalMs),this.maxKeyAgeMs)}};function ee(n,e){let t=n.get("x-forwarded-proto")?.split(",")[0]?.trim().toLowerCase(),r=t==="https"||t==="http"?t:e.protocol.slice(0,-1),s=n.get("host")??e.host;return Re.test(s)?`${r}://${s}${e.pathname}`:""}var Re=/^(?:[a-z0-9-]+(?:\.[a-z0-9-]+)*|\[[0-9a-f:.]+\])(?::[0-9]{1,5})?$/i;function xe(n){let e=new Headers;for(let[t,r]of Object.entries(n))if(r!==void 0)for(let s of Array.isArray(r)?r:[r])e.append(t,s);return e}function Oe(n){let e=/(?:^|,)\s*max-age=(\d+)/i.exec(n??"");return e?Number(e[1])*1e3:void 0}function te(n){if(!/^[A-Za-z0-9_-]*$/.test(n)||n.length%4===1)return null;let e=atob(n.replace(/-/g,"+").replace(/_/g,"/").padEnd(Math.ceil(n.length/4)*4,"=")),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}function Q(n){let e=te(n);if(!e)return null;try{let t=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(e));return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:null}catch{return null}}function je(n,e,t=Date.now()){let r=Object.hasOwn(n.entitlements,e)?n.entitlements[e]:void 0;return r?.active?r.expires_at===null?!0:Date.parse(r.expires_at)>(typeof t=="number"?t:t.getTime()):!1}var S=class extends Error{receipt;constructor(e){super(`the reservation ${JSON.stringify(e.idempotency_key)} is ${e.status} already: its job is not run again (a new one is a new Idempotency-Key)`),this.name="ReservationSettled",this.receipt=e}};async function Ue(n,e,t,r){let{idempotencyKey:s,amount:o,ttlSeconds:i,metadata:u}=t;if(typeof s!="string"||s==="")throw new TypeError("idempotencyKey names the reservation for good: a retry with it is the same hold, never a second");let c={amount:o,...t.credits!==void 0?{credits:t.credits}:{limit:t.limit},...i!==void 0?{ttl_seconds:i}:{},...u!==void 0?{metadata:u}:{}},a=(await n.purchases.holdSpend({path:{customer_id:e},headers:{"Idempotency-Key":s},body:c})).data;if(a.status!=="held")throw new S(a);let l={customer_id:a.customer,spend_id:a.id},p;try{p=await r(a)}catch(m){throw await n.purchases.releaseSpend({path:l}).catch(()=>{}),m}return await n.purchases.commitSpend({path:l}),p}async function*Ce(n){let e,t=new Set;do{let r=(await n(e)).data;yield r;let s=r.next_cursor??void 0;if(s!==void 0&&t.has(s))throw new Error(`the listing answered a cursor it had answered before (${s})`);s!==void 0&&t.add(s),e=s}while(e)}var Le=/^[A-Za-z_][A-Za-z0-9_]{0,127}$/,Me=/^(PALBASE_|NODE_|BUN_|NPM_|LD_|DYLD_|GIT_|SSH_)|^(PATH|HOME|SHELL|USER|TMPDIR|TMP|TEMP|PWD|IFS|ENV|BASH_ENV|SSL_CERT_FILE|SSL_CERT_DIR|CURL_CA_BUNDLE|REQUESTS_CA_BUNDLE|NO_PROXY|ALL_PROXY|HTTP_PROXY|HTTPS_PROXY|PG\w*)$/i;function ne(n){return!Le.test(n)||Me.test(n)||/_PROXY$/i.test(n)}var Ke=/^(PORT|PALBASE_(URL|PROJECT_ID|REPLICAS|SECRET_KEY|WEBHOOK_SECRET|PLATFORM_SECRET|CONTRACT_OUT|SECRETS|DB_[A-Z0-9_]+))$/;function $e(n){return Ke.test(n)}async function Ne(n={}){let e=n.env??process.env,t=e.PALBASE_SECRETS;if(t===void 0||t==="")return;if(t!=="development"&&t!=="production")throw new TypeError(`PALBASE_SECRETS is development or production, not ${t}`);let r=n.url??e.PALBASE_URL??"",s=n.secretKey??e.PALBASE_SECRET_KEY??"";if(r===""||s==="")throw new TypeError("PALBASE_SECRETS reads the project's Secrets with PALBASE_URL and PALBASE_SECRET_KEY: give both (palbase env:pull writes them)");let o=E({url:new URL(r).origin,secretKey:s,fetch:n.fetch}),{data:i}=await o.secrets.getEnvironmentValues({path:{environment:t}}),u=[],c=[],a=[];for(let[l,p]of Object.entries(i.values)){if(ne(l)){a.push(l);continue}if(e[l]!==void 0&&e[l]!==""){c.push(l);continue}e[l]=p,u.push(l)}return{set:t,loaded:u.sort(),kept:c.sort(),refused:a.sort()}}export{E as a,f as b,B as c,F as d,H as e,pe as f,d as g,g as h,_e as i,P as j,ee as k,je as l,S as m,Ue as n,Ce as o,ne as p,$e as q,Ne as r};