@base44-preview/sdk 0.8.46-pr.275.68973a5 → 0.8.46-pr.276.702f602
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actor.d.ts +7 -7
- package/dist/actor.js +6 -6
- package/dist/client.js +1 -1
- package/dist/client.types.d.ts +2 -2
- package/dist/iap/account-token.d.ts +14 -0
- package/dist/iap/account-token.js +43 -0
- package/dist/iap/config.d.ts +19 -0
- package/dist/iap/config.js +105 -0
- package/dist/iap/errors.d.ts +89 -0
- package/dist/iap/errors.js +66 -0
- package/dist/iap/errors.types.d.ts +74 -0
- package/dist/iap/errors.types.js +7 -0
- package/dist/iap/events/emitter.d.ts +28 -0
- package/dist/iap/events/emitter.js +33 -0
- package/dist/iap/events/events.types.d.ts +124 -0
- package/dist/iap/events/events.types.js +1 -0
- package/dist/iap/iap.types.d.ts +632 -0
- package/dist/iap/iap.types.js +1 -0
- package/dist/iap/index.d.ts +120 -0
- package/dist/iap/index.js +145 -0
- package/dist/iap/ingest/device.d.ts +7 -0
- package/dist/iap/ingest/device.js +191 -0
- package/dist/iap/ingest/device.types.d.ts +80 -0
- package/dist/iap/ingest/device.types.js +1 -0
- package/dist/iap/ingest/mappers.d.ts +66 -0
- package/dist/iap/ingest/mappers.js +248 -0
- package/dist/iap/ingest/matrix.d.ts +55 -0
- package/dist/iap/ingest/matrix.js +202 -0
- package/dist/iap/ingest/notifications.d.ts +70 -0
- package/dist/iap/ingest/notifications.js +231 -0
- package/dist/iap/read/derive.d.ts +40 -0
- package/dist/iap/read/derive.js +112 -0
- package/dist/iap/read/read.d.ts +47 -0
- package/dist/iap/read/read.js +215 -0
- package/dist/iap/read/read.types.d.ts +174 -0
- package/dist/iap/read/read.types.js +1 -0
- package/dist/iap/runtime/base64.d.ts +34 -0
- package/dist/iap/runtime/base64.js +131 -0
- package/dist/iap/runtime/clock.d.ts +13 -0
- package/dist/iap/runtime/clock.js +11 -0
- package/dist/iap/runtime/webcrypto.d.ts +14 -0
- package/dist/iap/runtime/webcrypto.js +66 -0
- package/dist/iap/server-api/client.d.ts +16 -0
- package/dist/iap/server-api/client.js +140 -0
- package/dist/iap/server-api/jwt.d.ts +3 -0
- package/dist/iap/server-api/jwt.js +70 -0
- package/dist/iap/server-api/server-api.types.d.ts +155 -0
- package/dist/iap/server-api/server-api.types.js +12 -0
- package/dist/iap/store/collapse.d.ts +27 -0
- package/dist/iap/store/collapse.js +85 -0
- package/dist/iap/store/descriptors.d.ts +46 -0
- package/dist/iap/store/descriptors.js +90 -0
- package/dist/iap/store/entities-store.d.ts +43 -0
- package/dist/iap/store/entities-store.js +348 -0
- package/dist/iap/store/rows.types.d.ts +233 -0
- package/dist/iap/store/rows.types.js +1 -0
- package/dist/iap/store/schemas.d.ts +68 -0
- package/dist/iap/store/schemas.js +214 -0
- package/dist/iap/store/store-errors.d.ts +52 -0
- package/dist/iap/store/store-errors.js +103 -0
- package/dist/iap/store/store.types.d.ts +193 -0
- package/dist/iap/store/store.types.js +1 -0
- package/dist/iap/verify/apple-roots.d.ts +18 -0
- package/dist/iap/verify/apple-roots.js +121 -0
- package/dist/iap/verify/apple-verifier.d.ts +13 -0
- package/dist/iap/verify/apple-verifier.js +154 -0
- package/dist/iap/verify/asn1.d.ts +102 -0
- package/dist/iap/verify/asn1.js +247 -0
- package/dist/iap/verify/chain.d.ts +40 -0
- package/dist/iap/verify/chain.js +112 -0
- package/dist/iap/verify/ecdsa.d.ts +16 -0
- package/dist/iap/verify/ecdsa.js +86 -0
- package/dist/iap/verify/jws.d.ts +50 -0
- package/dist/iap/verify/jws.js +159 -0
- package/dist/iap/verify/payload-checks.d.ts +36 -0
- package/dist/iap/verify/payload-checks.js +76 -0
- package/dist/iap/verify/verifier.d.ts +30 -0
- package/dist/iap/verify/verifier.js +114 -0
- package/dist/iap/verify/verify.types.d.ts +292 -0
- package/dist/iap/verify/verify.types.js +15 -0
- package/dist/iap/verify/x509.d.ts +83 -0
- package/dist/iap/verify/x509.js +156 -0
- package/dist/iap/version.d.ts +13 -0
- package/dist/iap/version.js +13 -0
- package/dist/index.d.ts +11 -0
- package/dist/modules/actors.d.ts +1 -1
- package/dist/modules/actors.js +5 -5
- package/dist/modules/actors.types.d.ts +7 -7
- package/dist/modules/analytics.js +2 -2
- package/dist/modules/app.types.d.ts +5 -5
- package/dist/modules/auth.js +1 -1
- package/dist/modules/auth.types.d.ts +2 -6
- package/dist/modules/connectors.js +2 -2
- package/dist/modules/connectors.types.d.ts +5 -7
- package/dist/modules/entities.js +1 -1
- package/dist/modules/integrations.types.d.ts +0 -2
- package/dist/utils/common.js +1 -1
- package/package.json +31 -1
package/dist/actor.d.ts
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* export class MyActor extends Actor { ... }
|
|
7
7
|
*
|
|
8
8
|
* At deploy time the bundler replaces this import with the compiled
|
|
9
|
-
* Cloudflare Durable Object implementation
|
|
9
|
+
* Cloudflare Durable Object implementation — this file provides types only.
|
|
10
10
|
*/
|
|
11
11
|
import type { Base44Client } from "./client";
|
|
12
12
|
/**
|
|
13
13
|
* A single client connection. `Send` is the message type this connection accepts
|
|
14
|
-
* via {@link send}
|
|
14
|
+
* via {@link send} — the actor's *outgoing* (server→client) messages.
|
|
15
15
|
*/
|
|
16
16
|
export interface Conn<Send = unknown> {
|
|
17
17
|
/** Unique per-connection id (one per socket/tab), the same value the client
|
|
@@ -33,9 +33,9 @@ export interface Storage {
|
|
|
33
33
|
* Base class for an Actor.
|
|
34
34
|
*
|
|
35
35
|
* @typeParam Incoming - messages this actor *receives* from clients
|
|
36
|
-
* (`handleMessage`'s `msg`)
|
|
36
|
+
* (`handleMessage`'s `msg`) — the schema's `toServer` section.
|
|
37
37
|
* @typeParam Outgoing - messages this actor *sends* to clients
|
|
38
|
-
* (`conn.send`/`broadcast`)
|
|
38
|
+
* (`conn.send`/`broadcast`) — the schema's `toClient` section.
|
|
39
39
|
*
|
|
40
40
|
* With a generated `schema.jsonc`, wire both from the registry so they can't drift
|
|
41
41
|
* from the client's types:
|
|
@@ -51,7 +51,7 @@ export declare abstract class Actor<Incoming = unknown, Outgoing = unknown> {
|
|
|
51
51
|
abstract handleTick(): void | Promise<void>;
|
|
52
52
|
/**
|
|
53
53
|
* Optional wake hook: runs once when the instance starts, before any
|
|
54
|
-
* connection is handled
|
|
54
|
+
* connection is handled — safe to load persisted state here.
|
|
55
55
|
*/
|
|
56
56
|
handleStart(): void | Promise<void>;
|
|
57
57
|
/**
|
|
@@ -71,7 +71,7 @@ export declare abstract class Actor<Incoming = unknown, Outgoing = unknown> {
|
|
|
71
71
|
/**
|
|
72
72
|
* Managed ticker (opt-in). Override {@link shouldTick} and the platform runs
|
|
73
73
|
* {@link handleTick} on a timer of {@link tickIntervalMs} while it returns true,
|
|
74
|
-
* and stops (letting the Durable Object hibernate
|
|
74
|
+
* and stops (letting the Durable Object hibernate — no compute cost) when it
|
|
75
75
|
* returns false. The platform owns scheduling, rescheduling, self-heal, and
|
|
76
76
|
* error-safety.
|
|
77
77
|
*
|
|
@@ -85,7 +85,7 @@ export declare abstract class Actor<Incoming = unknown, Outgoing = unknown> {
|
|
|
85
85
|
protected get instanceId(): string;
|
|
86
86
|
protected get storage(): Storage;
|
|
87
87
|
/**
|
|
88
|
-
* Anonymous Base44 client scoped to this actor instance
|
|
88
|
+
* Anonymous Base44 client scoped to this actor instance — no user or service
|
|
89
89
|
* auth, so entity access is RLS-gated (same as a logged-out visitor). Always
|
|
90
90
|
* operates on production data: an actor runs server-side with no per-connection
|
|
91
91
|
* identity, so a Test DB preview selected in the editor does not apply here.
|
package/dist/actor.js
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
* export class MyActor extends Actor { ... }
|
|
7
7
|
*
|
|
8
8
|
* At deploy time the bundler replaces this import with the compiled
|
|
9
|
-
* Cloudflare Durable Object implementation
|
|
9
|
+
* Cloudflare Durable Object implementation — this file provides types only.
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
12
12
|
* Base class for an Actor.
|
|
13
13
|
*
|
|
14
14
|
* @typeParam Incoming - messages this actor *receives* from clients
|
|
15
|
-
* (`handleMessage`'s `msg`)
|
|
15
|
+
* (`handleMessage`'s `msg`) — the schema's `toServer` section.
|
|
16
16
|
* @typeParam Outgoing - messages this actor *sends* to clients
|
|
17
|
-
* (`conn.send`/`broadcast`)
|
|
17
|
+
* (`conn.send`/`broadcast`) — the schema's `toClient` section.
|
|
18
18
|
*
|
|
19
19
|
* With a generated `schema.jsonc`, wire both from the registry so they can't drift
|
|
20
20
|
* from the client's types:
|
|
@@ -28,7 +28,7 @@ export class Actor {
|
|
|
28
28
|
/**
|
|
29
29
|
* Managed ticker (opt-in). Override {@link shouldTick} and the platform runs
|
|
30
30
|
* {@link handleTick} on a timer of {@link tickIntervalMs} while it returns true,
|
|
31
|
-
* and stops (letting the Durable Object hibernate
|
|
31
|
+
* and stops (letting the Durable Object hibernate — no compute cost) when it
|
|
32
32
|
* returns false. The platform owns scheduling, rescheduling, self-heal, and
|
|
33
33
|
* error-safety.
|
|
34
34
|
*
|
|
@@ -39,7 +39,7 @@ export class Actor {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Optional wake hook: runs once when the instance starts, before any
|
|
42
|
-
* connection is handled
|
|
42
|
+
* connection is handled — safe to load persisted state here.
|
|
43
43
|
*/
|
|
44
44
|
handleStart() { }
|
|
45
45
|
/**
|
|
@@ -73,7 +73,7 @@ export class Actor {
|
|
|
73
73
|
throw new Error("Actor.storage is only available inside a deployed actor");
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
|
-
* Anonymous Base44 client scoped to this actor instance
|
|
76
|
+
* Anonymous Base44 client scoped to this actor instance — no user or service
|
|
77
77
|
* auth, so entity access is RLS-gated (same as a logged-out visitor). Always
|
|
78
78
|
* operates on production data: an actor runs server-side with no per-connection
|
|
79
79
|
* identity, so a Test DB preview selected in the editor does not apply here.
|
package/dist/client.js
CHANGED
|
@@ -113,7 +113,7 @@ export function createClient(config) {
|
|
|
113
113
|
});
|
|
114
114
|
// Dedicated client for actor connection-token mints: no onError (a legacy
|
|
115
115
|
// actor answers every mint with an expected 409 before the proxy fallback,
|
|
116
|
-
// which must not reach the app's error handler
|
|
116
|
+
// which must not reach the app's error handler — the actors module forwards
|
|
117
117
|
// genuine failures itself via onMintError) and no constructor token
|
|
118
118
|
// (auth is per-request so a login/logout is picked up on every reconnect).
|
|
119
119
|
const actorsAxiosClient = createAxiosClient({
|
package/dist/client.types.d.ts
CHANGED
|
@@ -18,14 +18,14 @@ export interface CreateClientOptions {
|
|
|
18
18
|
* Optional error handler that will be called whenever an API error occurs.
|
|
19
19
|
*
|
|
20
20
|
* Also receives {@link ActorsModule | actors} connection failures. Errors
|
|
21
|
-
* are usually {@linkcode Base44Error} instances
|
|
21
|
+
* are usually {@linkcode Base44Error} instances — check `error.status`.
|
|
22
22
|
*/
|
|
23
23
|
onError?: (error: Error) => void;
|
|
24
24
|
/**
|
|
25
25
|
* Forces the actors transport. `"auto"` (default) connects directly to the
|
|
26
26
|
* actor and falls back to the platform proxy when the app's actors don't
|
|
27
27
|
* support direct connections; `"proxy"` always uses the platform proxy
|
|
28
|
-
* (ops rollback
|
|
28
|
+
* (ops rollback — no connection-token calls); `"direct"` disables the
|
|
29
29
|
* fallback (validation environments).
|
|
30
30
|
* @internal
|
|
31
31
|
*/
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The namespace every Base44 in-app purchase account token is derived under.
|
|
3
|
+
*
|
|
4
|
+
* A fixed, arbitrary version-4 UUID. It must never change: the native shell
|
|
5
|
+
* derives the same value independently, and Apple has already signed the
|
|
6
|
+
* results into transactions that will keep renewing.
|
|
7
|
+
*/
|
|
8
|
+
export declare const IAP_APP_ACCOUNT_TOKEN_NAMESPACE = "8f2b6a1e-4c5d-4e7a-9b3f-6d1a2c8e5f04";
|
|
9
|
+
/**
|
|
10
|
+
* Derives the account token for a Base44 user id.
|
|
11
|
+
*
|
|
12
|
+
* @throws {TypeError} when the user id is empty.
|
|
13
|
+
*/
|
|
14
|
+
export declare function appAccountTokenFor(base44UserId: string): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapping a Base44 user to the UUID Apple signs into their purchases.
|
|
3
|
+
*
|
|
4
|
+
* StoreKit accepts a UUID at purchase time (`appAccountToken`) and Apple
|
|
5
|
+
* returns the same value in every resulting transaction — including renewals
|
|
6
|
+
* years later. That signed round trip is the entire attribution mechanism: it
|
|
7
|
+
* is the only way to know whose purchase a transaction is, without trusting
|
|
8
|
+
* anything the client claims.
|
|
9
|
+
*
|
|
10
|
+
* The mapping is a deterministic version-5 UUID of the user id under a fixed
|
|
11
|
+
* namespace, so it is a pure function. The shell, the web app and the backend
|
|
12
|
+
* all derive the same UUID from the same user id with no lookup table to keep
|
|
13
|
+
* in sync, and nothing to migrate.
|
|
14
|
+
*
|
|
15
|
+
* Two consequences worth knowing:
|
|
16
|
+
*
|
|
17
|
+
* - The namespace is part of the contract. Changing it orphans every purchase
|
|
18
|
+
* already attributed under the old one.
|
|
19
|
+
* - The mapping is one-way in practice. Recovering a user id means deriving
|
|
20
|
+
* the UUID for a candidate user and comparing, not inverting the hash.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
import { v5 as uuidv5 } from "uuid";
|
|
25
|
+
/**
|
|
26
|
+
* The namespace every Base44 in-app purchase account token is derived under.
|
|
27
|
+
*
|
|
28
|
+
* A fixed, arbitrary version-4 UUID. It must never change: the native shell
|
|
29
|
+
* derives the same value independently, and Apple has already signed the
|
|
30
|
+
* results into transactions that will keep renewing.
|
|
31
|
+
*/
|
|
32
|
+
export const IAP_APP_ACCOUNT_TOKEN_NAMESPACE = "8f2b6a1e-4c5d-4e7a-9b3f-6d1a2c8e5f04";
|
|
33
|
+
/**
|
|
34
|
+
* Derives the account token for a Base44 user id.
|
|
35
|
+
*
|
|
36
|
+
* @throws {TypeError} when the user id is empty.
|
|
37
|
+
*/
|
|
38
|
+
export function appAccountTokenFor(base44UserId) {
|
|
39
|
+
if (typeof base44UserId !== "string" || base44UserId.length === 0) {
|
|
40
|
+
throw new TypeError("a Base44 user id is required to derive an account token");
|
|
41
|
+
}
|
|
42
|
+
return uuidv5(base44UserId, IAP_APP_ACCOUNT_TOKEN_NAMESPACE);
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IapConfig, IapProductConfig } from "./iap.types.js";
|
|
2
|
+
import type { IapServerApiConfig } from "./server-api/server-api.types.js";
|
|
3
|
+
/** A validated configuration, with every default filled in. */
|
|
4
|
+
export interface ResolvedIapConfig {
|
|
5
|
+
readonly bundleId: string;
|
|
6
|
+
readonly appAppleId: number;
|
|
7
|
+
readonly products: Readonly<Record<string, IapProductConfig>>;
|
|
8
|
+
readonly testMode: boolean;
|
|
9
|
+
readonly allowLocalTesting: boolean;
|
|
10
|
+
readonly serverApi?: IapServerApiConfig;
|
|
11
|
+
readonly verifier: "apple" | "builtin";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validates a configuration and fills in defaults.
|
|
15
|
+
*
|
|
16
|
+
* @throws {IapConfigError} `IAP_INVALID_CONFIG` for anything malformed, or
|
|
17
|
+
* `IAP_ONLINE_CHECKS_UNSUPPORTED` when online certificate checks are asked for.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveConfig(config: IapConfig): ResolvedIapConfig;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration validation.
|
|
3
|
+
*
|
|
4
|
+
* Runs once, when the client is created, and refuses to build a client it
|
|
5
|
+
* cannot operate correctly. Failing at deploy time is the whole point: the
|
|
6
|
+
* alternative is discovering a missing `appAppleId` when Apple sends the first
|
|
7
|
+
* refund notification.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
import { IapConfigError } from "./errors.js";
|
|
12
|
+
const PRODUCT_TYPES = new Set([
|
|
13
|
+
"consumable",
|
|
14
|
+
"nonConsumable",
|
|
15
|
+
"nonRenewingSubscription",
|
|
16
|
+
"autoRenewableSubscription",
|
|
17
|
+
]);
|
|
18
|
+
function invalid(message) {
|
|
19
|
+
throw new IapConfigError("IAP_INVALID_CONFIG", message);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validates a configuration and fills in defaults.
|
|
23
|
+
*
|
|
24
|
+
* @throws {IapConfigError} `IAP_INVALID_CONFIG` for anything malformed, or
|
|
25
|
+
* `IAP_ONLINE_CHECKS_UNSUPPORTED` when online certificate checks are asked for.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveConfig(config) {
|
|
28
|
+
var _a, _b;
|
|
29
|
+
if (!config || typeof config !== "object") {
|
|
30
|
+
invalid("an in-app purchase configuration object is required");
|
|
31
|
+
}
|
|
32
|
+
if (typeof config.bundleId !== "string" || config.bundleId.trim().length === 0) {
|
|
33
|
+
invalid("'bundleId' is required, e.g. \"com.example.app\"");
|
|
34
|
+
}
|
|
35
|
+
// A very common mix-up: passing the bundle id where the numeric id belongs.
|
|
36
|
+
if (typeof config.appAppleId === "string") {
|
|
37
|
+
invalid("'appAppleId' must be a number — the numeric App Store id from App Store " +
|
|
38
|
+
"Connect under App Information, not the bundle id");
|
|
39
|
+
}
|
|
40
|
+
if (typeof config.appAppleId !== "number" ||
|
|
41
|
+
!Number.isInteger(config.appAppleId) ||
|
|
42
|
+
config.appAppleId <= 0) {
|
|
43
|
+
invalid("'appAppleId' must be a positive integer, e.g. 1234567890");
|
|
44
|
+
}
|
|
45
|
+
if (!config.products || typeof config.products !== "object") {
|
|
46
|
+
invalid("'products' is required, keyed by product identifier");
|
|
47
|
+
}
|
|
48
|
+
for (const [productId, product] of Object.entries(config.products)) {
|
|
49
|
+
if (!product || typeof product !== "object") {
|
|
50
|
+
invalid(`product ${JSON.stringify(productId)} must be an object`);
|
|
51
|
+
}
|
|
52
|
+
if (!PRODUCT_TYPES.has(product.type)) {
|
|
53
|
+
invalid(`product ${JSON.stringify(productId)} has type ${JSON.stringify(product.type)}; expected one of ${[...PRODUCT_TYPES].join(", ")}`);
|
|
54
|
+
}
|
|
55
|
+
// Apple never expires a non-renewing subscription, so this number is the
|
|
56
|
+
// only thing that decides when access ends. Missing it would silently
|
|
57
|
+
// grant the product forever.
|
|
58
|
+
if (product.type === "nonRenewingSubscription") {
|
|
59
|
+
const days = product.nonRenewingDurationDays;
|
|
60
|
+
if (typeof days !== "number" || !Number.isFinite(days) || days <= 0) {
|
|
61
|
+
invalid(`product ${JSON.stringify(productId)} is a nonRenewingSubscription, so it ` +
|
|
62
|
+
"needs 'nonRenewingDurationDays' — Apple does not expire these, so the " +
|
|
63
|
+
"app decides how long they last");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// `null` is treated as absent, not as invalid: a secret that was never set
|
|
68
|
+
// arrives that way, and the right answer then is "the API is not configured"
|
|
69
|
+
// — which the call itself reports clearly — rather than refusing to start.
|
|
70
|
+
if (config.verifier !== undefined &&
|
|
71
|
+
config.verifier !== "apple" &&
|
|
72
|
+
config.verifier !== "builtin") {
|
|
73
|
+
invalid(`'verifier' must be "apple" or "builtin"; got ${JSON.stringify(config.verifier)}`);
|
|
74
|
+
}
|
|
75
|
+
if (config.serverApi !== undefined && config.serverApi !== null) {
|
|
76
|
+
const api = config.serverApi;
|
|
77
|
+
if (typeof api !== "object") {
|
|
78
|
+
invalid("'serverApi' must be an object, or left out entirely");
|
|
79
|
+
}
|
|
80
|
+
for (const field of ["keyId", "issuerId", "privateKeyP8"]) {
|
|
81
|
+
if (typeof api[field] !== "string" || api[field].trim().length === 0) {
|
|
82
|
+
invalid(`'serverApi.${field}' is required when serverApi is supplied`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!api.privateKeyP8.includes("PRIVATE KEY")) {
|
|
86
|
+
invalid("'serverApi.privateKeyP8' does not look like a .p8 file. Pass its whole " +
|
|
87
|
+
"contents, including the BEGIN and END lines.");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (config.onlineChecks === true) {
|
|
91
|
+
throw new IapConfigError("IAP_ONLINE_CHECKS_UNSUPPORTED", "'onlineChecks' asks for certificate revocation lookups, which this version " +
|
|
92
|
+
"does not implement. Leave it unset. Certificate validity is evaluated at " +
|
|
93
|
+
"each payload's own signedDate, which is what Apple's own library does with " +
|
|
94
|
+
"online checks off.");
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
bundleId: config.bundleId,
|
|
98
|
+
appAppleId: config.appAppleId,
|
|
99
|
+
products: { ...config.products },
|
|
100
|
+
testMode: config.testMode === true,
|
|
101
|
+
allowLocalTesting: config.allowLocalTesting === true,
|
|
102
|
+
serverApi: (_a = config.serverApi) !== null && _a !== void 0 ? _a : undefined,
|
|
103
|
+
verifier: (_b = config.verifier) !== null && _b !== void 0 ? _b : "apple",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classes for the in-app purchase module.
|
|
3
|
+
*
|
|
4
|
+
* Each class carries a `code` from `errors.types.ts`. Nothing here imports the
|
|
5
|
+
* runtime helpers, so the runtime layer can throw these freely.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
import type { IapApiErrorCode, IapConfigErrorCode, IapSetupErrorCode, IapStoreErrorCode, IapVerificationErrorCode } from "./errors.types.js";
|
|
10
|
+
/** Base class, so a caller can catch every error this module raises at once. */
|
|
11
|
+
export declare class IapError extends Error {
|
|
12
|
+
/** Stable machine-readable code. */
|
|
13
|
+
readonly code: string;
|
|
14
|
+
constructor(code: string, message: string, options?: {
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A signed Apple token was rejected.
|
|
20
|
+
*
|
|
21
|
+
* Deny by default: anything that raises this must be treated as "not
|
|
22
|
+
* entitled", never as "probably fine".
|
|
23
|
+
*/
|
|
24
|
+
export declare class IapVerificationError extends IapError {
|
|
25
|
+
readonly code: IapVerificationErrorCode;
|
|
26
|
+
constructor(code: IapVerificationErrorCode, message: string, options?: {
|
|
27
|
+
cause?: unknown;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** The module cannot start, or was asked for something its configuration does not allow. */
|
|
31
|
+
export declare class IapConfigError extends IapError {
|
|
32
|
+
readonly code: IapConfigErrorCode;
|
|
33
|
+
constructor(code: IapConfigErrorCode, message: string, options?: {
|
|
34
|
+
cause?: unknown;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The app's Base44 setup is not usable — an entity is missing, or its schema
|
|
39
|
+
* has drifted from what the SDK writes.
|
|
40
|
+
*
|
|
41
|
+
* This is loud on purpose. A silently dropped field means purchase data is
|
|
42
|
+
* being lost, which is worse than an outage.
|
|
43
|
+
*/
|
|
44
|
+
export declare class IapSetupError extends IapError {
|
|
45
|
+
readonly code: IapSetupErrorCode;
|
|
46
|
+
/** The entity the problem was found on. */
|
|
47
|
+
readonly entityName?: string;
|
|
48
|
+
/** Fields the entity dropped, when the code is `IAP_ENTITY_SCHEMA_DRIFT`. */
|
|
49
|
+
readonly missingFields?: string[];
|
|
50
|
+
constructor(code: IapSetupErrorCode, message: string, details?: {
|
|
51
|
+
entityName?: string;
|
|
52
|
+
missingFields?: string[];
|
|
53
|
+
cause?: unknown;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** A read or write against the app's entities failed. */
|
|
57
|
+
export declare class IapStoreError extends IapError {
|
|
58
|
+
readonly code: IapStoreErrorCode;
|
|
59
|
+
/** The entity being read or written. */
|
|
60
|
+
readonly entityName?: string;
|
|
61
|
+
constructor(code: IapStoreErrorCode, message: string, details?: {
|
|
62
|
+
entityName?: string;
|
|
63
|
+
cause?: unknown;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** An App Store Server API call failed. */
|
|
67
|
+
export declare class IapApiError extends IapError {
|
|
68
|
+
readonly code: IapApiErrorCode;
|
|
69
|
+
/** The HTTP status Apple returned. */
|
|
70
|
+
readonly httpStatus?: number;
|
|
71
|
+
/** Apple's own numeric error code from the response body, e.g. `4040010`. */
|
|
72
|
+
readonly appleErrorCode?: number;
|
|
73
|
+
/** Apple's own error message from the response body. */
|
|
74
|
+
readonly appleErrorMessage?: string;
|
|
75
|
+
/**
|
|
76
|
+
* When rate-limited: the value of Apple's `Retry-After` header.
|
|
77
|
+
*
|
|
78
|
+
* Apple sends an **absolute UNIX millisecond timestamp** here, not a delay.
|
|
79
|
+
* Compare it against `Date.now()`; do not use it as a duration.
|
|
80
|
+
*/
|
|
81
|
+
readonly retryAfter?: number;
|
|
82
|
+
constructor(code: IapApiErrorCode, message: string, details?: {
|
|
83
|
+
httpStatus?: number;
|
|
84
|
+
appleErrorCode?: number;
|
|
85
|
+
appleErrorMessage?: string;
|
|
86
|
+
retryAfter?: number;
|
|
87
|
+
cause?: unknown;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Base class, so a caller can catch every error this module raises at once. */
|
|
2
|
+
export class IapError extends Error {
|
|
3
|
+
constructor(code, message, options) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "IapError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
// `cause` is set by hand rather than passed to super: the emit target is
|
|
8
|
+
// es2018, whose Error constructor takes no options bag.
|
|
9
|
+
if (options && "cause" in options) {
|
|
10
|
+
this.cause = options.cause;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A signed Apple token was rejected.
|
|
16
|
+
*
|
|
17
|
+
* Deny by default: anything that raises this must be treated as "not
|
|
18
|
+
* entitled", never as "probably fine".
|
|
19
|
+
*/
|
|
20
|
+
export class IapVerificationError extends IapError {
|
|
21
|
+
constructor(code, message, options) {
|
|
22
|
+
super(code, message, options);
|
|
23
|
+
this.name = "IapVerificationError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** The module cannot start, or was asked for something its configuration does not allow. */
|
|
27
|
+
export class IapConfigError extends IapError {
|
|
28
|
+
constructor(code, message, options) {
|
|
29
|
+
super(code, message, options);
|
|
30
|
+
this.name = "IapConfigError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The app's Base44 setup is not usable — an entity is missing, or its schema
|
|
35
|
+
* has drifted from what the SDK writes.
|
|
36
|
+
*
|
|
37
|
+
* This is loud on purpose. A silently dropped field means purchase data is
|
|
38
|
+
* being lost, which is worse than an outage.
|
|
39
|
+
*/
|
|
40
|
+
export class IapSetupError extends IapError {
|
|
41
|
+
constructor(code, message, details) {
|
|
42
|
+
super(code, message, { cause: details === null || details === void 0 ? void 0 : details.cause });
|
|
43
|
+
this.name = "IapSetupError";
|
|
44
|
+
this.entityName = details === null || details === void 0 ? void 0 : details.entityName;
|
|
45
|
+
this.missingFields = details === null || details === void 0 ? void 0 : details.missingFields;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** A read or write against the app's entities failed. */
|
|
49
|
+
export class IapStoreError extends IapError {
|
|
50
|
+
constructor(code, message, details) {
|
|
51
|
+
super(code, message, { cause: details === null || details === void 0 ? void 0 : details.cause });
|
|
52
|
+
this.name = "IapStoreError";
|
|
53
|
+
this.entityName = details === null || details === void 0 ? void 0 : details.entityName;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** An App Store Server API call failed. */
|
|
57
|
+
export class IapApiError extends IapError {
|
|
58
|
+
constructor(code, message, details) {
|
|
59
|
+
super(code, message, { cause: details === null || details === void 0 ? void 0 : details.cause });
|
|
60
|
+
this.name = "IapApiError";
|
|
61
|
+
this.httpStatus = details === null || details === void 0 ? void 0 : details.httpStatus;
|
|
62
|
+
this.appleErrorCode = details === null || details === void 0 ? void 0 : details.appleErrorCode;
|
|
63
|
+
this.appleErrorMessage = details === null || details === void 0 ? void 0 : details.appleErrorMessage;
|
|
64
|
+
this.retryAfter = details === null || details === void 0 ? void 0 : details.retryAfter;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes the in-app purchase module can raise.
|
|
3
|
+
*
|
|
4
|
+
* Every code is a stable string. Generated code and support tooling branch on
|
|
5
|
+
* these, so a code is never renamed — a new situation gets a new code.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Why a signed Apple token was rejected.
|
|
9
|
+
*
|
|
10
|
+
* The first eight come straight from the verification algorithm Apple
|
|
11
|
+
* describes; the last two are this SDK's own.
|
|
12
|
+
*/
|
|
13
|
+
export type IapVerificationErrorCode =
|
|
14
|
+
/** The token is not three base64url segments separated by dots. */
|
|
15
|
+
"INVALID_JWS_FORMAT"
|
|
16
|
+
/** The JWS header named an algorithm other than `ES256`. */
|
|
17
|
+
| "UNSUPPORTED_ALG"
|
|
18
|
+
/** The `x5c` header did not carry exactly three certificates. */
|
|
19
|
+
| "INVALID_CHAIN_LENGTH"
|
|
20
|
+
/** A certificate failed to parse, was outside its validity window, or lacked a required marker. */
|
|
21
|
+
| "INVALID_CERTIFICATE"
|
|
22
|
+
/** A certificate was revoked. Only reachable with online checks, which v1 does not support. */
|
|
23
|
+
| "CERTIFICATE_REVOKED"
|
|
24
|
+
/** The signature did not verify against the leaf certificate's public key. */
|
|
25
|
+
| "INVALID_SIGNATURE"
|
|
26
|
+
/** The payload's `bundleId`, or its `appAppleId` in production, did not match the configuration. */
|
|
27
|
+
| "INVALID_APP_IDENTIFIER"
|
|
28
|
+
/** The payload's `environment` was not one this app accepts. */
|
|
29
|
+
| "INVALID_ENVIRONMENT"
|
|
30
|
+
/** A transient failure. Retrying may succeed. */
|
|
31
|
+
| "RETRYABLE_VERIFICATION_FAILURE"
|
|
32
|
+
/**
|
|
33
|
+
* The chain anchored at a root this SDK cannot verify against — an RSA root.
|
|
34
|
+
*
|
|
35
|
+
* Current App Store tokens chain to Apple Root CA - G3, which is ECDSA
|
|
36
|
+
* P-384. Apple Root CA - G2 and Apple Inc. Root are RSA and are pinned for
|
|
37
|
+
* completeness, but v1 verifies ECDSA signatures only. This is never
|
|
38
|
+
* downgraded to a pass.
|
|
39
|
+
*/
|
|
40
|
+
| "UNSUPPORTED_CERT_ALGORITHM";
|
|
41
|
+
/** Why the module refused to start, or could not use its configuration. */
|
|
42
|
+
export type IapConfigErrorCode =
|
|
43
|
+
/** A required configuration field was missing or malformed. */
|
|
44
|
+
"IAP_INVALID_CONFIG"
|
|
45
|
+
/** `onlineChecks: true` was requested. Certificate revocation lookups are not implemented in v1. */
|
|
46
|
+
| "IAP_ONLINE_CHECKS_UNSUPPORTED"
|
|
47
|
+
/** The runtime has no WebCrypto. Needs Deno, Node 18 or later, or a secure browser context. */
|
|
48
|
+
| "IAP_WEBCRYPTO_UNAVAILABLE"
|
|
49
|
+
/** The runtime has no `fetch`. Needs Deno, Node 18 or later, or a browser. */
|
|
50
|
+
| "IAP_FETCH_UNAVAILABLE"
|
|
51
|
+
/** An App Store Server API call was made without `serverApi` credentials configured. */
|
|
52
|
+
| "IAP_SERVER_API_NOT_CONFIGURED";
|
|
53
|
+
/** Why the app's own Base44 setup is not usable for in-app purchases. */
|
|
54
|
+
export type IapSetupErrorCode =
|
|
55
|
+
/** One of the four required entities does not exist in this app. */
|
|
56
|
+
"IAP_ENTITY_MISSING"
|
|
57
|
+
/** An entity exists but silently dropped a field the SDK wrote. Its schema has drifted. */
|
|
58
|
+
| "IAP_ENTITY_SCHEMA_DRIFT"
|
|
59
|
+
/** The client was created without service-role credentials, so it cannot write. */
|
|
60
|
+
| "IAP_SERVICE_ROLE_REQUIRED";
|
|
61
|
+
/** Why a read or write against the app's entities failed. */
|
|
62
|
+
export type IapStoreErrorCode =
|
|
63
|
+
/** The write did not land. Callers must not report success to Apple or finish a transaction. */
|
|
64
|
+
"IAP_WRITE_FAILED"
|
|
65
|
+
/** The read did not complete. */
|
|
66
|
+
| "IAP_READ_FAILED";
|
|
67
|
+
/** Why an App Store Server API call failed. */
|
|
68
|
+
export type IapApiErrorCode =
|
|
69
|
+
/** Apple answered with an error status. Inspect `appleErrorCode` for which. */
|
|
70
|
+
"IAP_API_ERROR"
|
|
71
|
+
/** Apple rate-limited the call. `retryAfter` carries the absolute timestamp to retry after. */
|
|
72
|
+
| "IAP_API_RATE_LIMITED"
|
|
73
|
+
/** The transaction id exists in neither the production nor the sandbox environment. */
|
|
74
|
+
| "IAP_API_TRANSACTION_NOT_FOUND";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivering events to the app's handlers.
|
|
3
|
+
*
|
|
4
|
+
* Two rules, both about not letting an app's own code break Apple's contract:
|
|
5
|
+
*
|
|
6
|
+
* 1. **A handler runs after the write, never before.** An event means the data
|
|
7
|
+
* behind it is already stored.
|
|
8
|
+
* 2. **A handler can never change the outcome.** If one throws, the throw is
|
|
9
|
+
* swallowed and reported; the HTTP response Apple sees is unaffected.
|
|
10
|
+
*
|
|
11
|
+
* Events are dispatched *synchronously* before the response is returned, not
|
|
12
|
+
* after it. A Base44 backend function has no way to run work once it has
|
|
13
|
+
* answered — there is no `waitUntil` — so anything deferred until after the
|
|
14
|
+
* response is simply lost when the isolate stops.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
import type { IapEvent, IapEventHandler } from "./events.types.js";
|
|
19
|
+
/** Reports a handler that threw. Defaults to a single console line. */
|
|
20
|
+
export type IapHandlerFailureReporter = (event: IapEvent, error: unknown) => void;
|
|
21
|
+
/** The event bus for one client. */
|
|
22
|
+
export interface IapEmitter {
|
|
23
|
+
/** Registers a handler. Returns a function that removes it. */
|
|
24
|
+
onEvent(handler: IapEventHandler): () => void;
|
|
25
|
+
/** Delivers events to every handler, swallowing their failures. */
|
|
26
|
+
emit(events: readonly IapEvent[]): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare function createEmitter(reportFailure?: IapHandlerFailureReporter): IapEmitter;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function defaultReporter(event, error) {
|
|
2
|
+
// Deliberately one line and not re-thrown. A broken handler must not take
|
|
3
|
+
// the webhook down with it, and it must not be silent either.
|
|
4
|
+
console.error(`[base44 iap] an onEvent handler failed for ${event.type}:`, error instanceof Error ? error.message : error);
|
|
5
|
+
}
|
|
6
|
+
export function createEmitter(reportFailure = defaultReporter) {
|
|
7
|
+
const handlers = new Set();
|
|
8
|
+
return {
|
|
9
|
+
onEvent(handler) {
|
|
10
|
+
handlers.add(handler);
|
|
11
|
+
return () => {
|
|
12
|
+
handlers.delete(handler);
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
async emit(events) {
|
|
16
|
+
if (handlers.size === 0 || events.length === 0)
|
|
17
|
+
return;
|
|
18
|
+
// A snapshot, so a handler that registers another one during dispatch
|
|
19
|
+
// does not change what this round delivers.
|
|
20
|
+
const current = [...handlers];
|
|
21
|
+
for (const event of events) {
|
|
22
|
+
for (const handler of current) {
|
|
23
|
+
try {
|
|
24
|
+
await handler(event);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
reportFailure(event, error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|