@cross-deck/web 0.1.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.
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Public types for @cross-deck/web. These mirror the wire format
3
+ * exposed by the v1 backend API. Keep them in lockstep with
4
+ * backend/src/api/v1-types.ts — same field names, same nullability.
5
+ */
6
+ type Environment = "production" | "sandbox";
7
+ type Platform = "ios" | "android" | "web";
8
+ type AuditRail = "apple" | "stripe" | "google" | "manual";
9
+ interface PublicEntitlement {
10
+ object: "entitlement";
11
+ key: string;
12
+ isActive: boolean;
13
+ validUntil?: number | null;
14
+ source: {
15
+ rail: AuditRail;
16
+ productId: string;
17
+ subscriptionId: string;
18
+ };
19
+ updatedAt: number;
20
+ }
21
+ interface EntitlementsListResponse {
22
+ object: "list";
23
+ data: PublicEntitlement[];
24
+ crossdeckCustomerId: string;
25
+ env: Environment;
26
+ }
27
+ interface AliasResult {
28
+ object: "alias_result";
29
+ crossdeckCustomerId: string;
30
+ linked: Array<{
31
+ type: "developer";
32
+ id: string;
33
+ } | {
34
+ type: "anonymous";
35
+ id: string;
36
+ }>;
37
+ mergePending: boolean;
38
+ env: Environment;
39
+ }
40
+ interface PurchaseResult {
41
+ object: "purchase_result";
42
+ crossdeckCustomerId: string;
43
+ env: Environment;
44
+ entitlements: PublicEntitlement[];
45
+ }
46
+ interface HeartbeatResponse {
47
+ object: "heartbeat";
48
+ ok: true;
49
+ projectId: string;
50
+ appId: string;
51
+ platform: Platform;
52
+ env: Environment;
53
+ serverTime: number;
54
+ }
55
+ /**
56
+ * Configuration for Crossdeck.start. Most fields have sensible defaults
57
+ * — only `publicKey` is mandatory.
58
+ */
59
+ interface CrossdeckOptions {
60
+ /** Your Crossdeck publishable key (cd_pub_…). Required. */
61
+ publicKey: string;
62
+ /**
63
+ * Override the API base URL. Default is https://api.cross-deck.com/v1.
64
+ * Useful for self-hosted setups or pointing at the local emulator
65
+ * (e.g. http://localhost:5001/crossdeck-47d8f/us-east4/v1).
66
+ */
67
+ baseUrl?: string;
68
+ /**
69
+ * Persist anonymousId + crossdeckCustomerId across sessions.
70
+ * Default: true in the browser (localStorage), false in Node (in-memory only).
71
+ */
72
+ persistIdentity?: boolean;
73
+ /**
74
+ * Storage adapter. The SDK calls .getItem / .setItem / .removeItem.
75
+ * Defaults to globalThis.localStorage when present. Pass an in-memory
76
+ * adapter for Node runtimes where you want session-only persistence.
77
+ */
78
+ storage?: KeyValueStorage;
79
+ /** Storage key prefix for the SDK's persisted state. Default "crossdeck:". */
80
+ storagePrefix?: string;
81
+ /**
82
+ * Send a heartbeat to /v1/sdk/heartbeat on start(). Default true.
83
+ * Disable for high-frequency boot scenarios where the heartbeat is
84
+ * pure overhead.
85
+ */
86
+ autoHeartbeat?: boolean;
87
+ /** Maximum events buffered before forced flush. Default 20. */
88
+ eventFlushBatchSize?: number;
89
+ /** Idle ms after the last track() before flushing. Default 5000. */
90
+ eventFlushIntervalMs?: number;
91
+ /** Override the SDK version reported on heartbeats. Default: package version. */
92
+ sdkVersion?: string;
93
+ }
94
+ /** Minimal interface for any pluggable key-value persistence. */
95
+ interface KeyValueStorage {
96
+ getItem(key: string): string | null;
97
+ setItem(key: string, value: string): void;
98
+ removeItem(key: string): void;
99
+ }
100
+ /** Identity hint object passed to identify() — at least one field required. */
101
+ interface IdentifyOptions {
102
+ /** Optional email to attach to the customer record. */
103
+ email?: string;
104
+ }
105
+ /** Properties payload for track(). Arbitrary key/value, JSON-serialisable, ≤ 8 KB. */
106
+ type EventProperties = Record<string, unknown>;
107
+ /**
108
+ * Diagnostic snapshot returned by Crossdeck.diagnostics(). Stable shape
109
+ * whether or not start() has been called — callers don't need to narrow
110
+ * on `started` to read `events` or `entitlements`. Pre-start values are
111
+ * sensible empties (zeros, nulls).
112
+ */
113
+ interface Diagnostics {
114
+ started: boolean;
115
+ anonymousId: string | null;
116
+ crossdeckCustomerId: string | null;
117
+ developerUserId: string | null;
118
+ sdkVersion: string | null;
119
+ baseUrl: string | null;
120
+ entitlements: {
121
+ count: number;
122
+ lastUpdated: number;
123
+ };
124
+ events: {
125
+ buffered: number;
126
+ dropped: number;
127
+ inFlight: number;
128
+ lastFlushAt: number;
129
+ lastError: string | null;
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Public API surface for @cross-deck/web.
135
+ *
136
+ * Usage (browser):
137
+ *
138
+ * import { Crossdeck } from "@cross-deck/web";
139
+ *
140
+ * Crossdeck.start({ publicKey: "cd_pub_live_…" });
141
+ *
142
+ * await Crossdeck.identify("user_847");
143
+ * const ents = await Crossdeck.getEntitlements();
144
+ * if (Crossdeck.isEntitled("pro")) {
145
+ * showPro();
146
+ * }
147
+ * Crossdeck.track("paywall_shown", { variant: "v3" });
148
+ *
149
+ *
150
+ * Usage (Node):
151
+ *
152
+ * import { Crossdeck } from "@cross-deck/web";
153
+ * import { MemoryStorage } from "@cross-deck/web";
154
+ *
155
+ * Crossdeck.start({
156
+ * publicKey: "cd_pub_test_…",
157
+ * storage: new MemoryStorage(), // session-only persistence
158
+ * autoHeartbeat: false, // skip the boot ping in scripts
159
+ * });
160
+ */
161
+
162
+ declare class CrossdeckClient {
163
+ private state;
164
+ /**
165
+ * Boot the SDK. Idempotent — calling start twice with the same options
166
+ * is a no-op; calling with different options replaces the previous
167
+ * configuration.
168
+ */
169
+ start(options: CrossdeckOptions): void;
170
+ /**
171
+ * Link the anonymous device to a developer-supplied user ID. Cache
172
+ * the resolved Crossdeck customer for follow-up calls.
173
+ */
174
+ identify(userId: string, _options?: IdentifyOptions): Promise<AliasResult>;
175
+ /**
176
+ * Read the current customer's active entitlements from the server.
177
+ * Updates the local cache so subsequent isEntitled() calls answer
178
+ * synchronously.
179
+ */
180
+ getEntitlements(): Promise<PublicEntitlement[]>;
181
+ /**
182
+ * Synchronous read from the local cache. Returns false if the cache
183
+ * has never been populated (call getEntitlements first to warm it).
184
+ */
185
+ isEntitled(key: string): boolean;
186
+ /** Snapshot of the local entitlement cache. */
187
+ listEntitlements(): PublicEntitlement[];
188
+ /**
189
+ * Queue a telemetry event. Returns immediately — the network round-
190
+ * trip happens in the background. To flush before the page unloads,
191
+ * call flushEvents().
192
+ */
193
+ track(name: string, properties?: EventProperties): void;
194
+ /** Force-flush queued events. Useful to call from page-unload handlers. */
195
+ flushEvents(): Promise<void>;
196
+ /** Forward an Apple StoreKit 2 transaction for verification + projection. */
197
+ purchaseApple(input: {
198
+ signedTransactionInfo: string;
199
+ signedRenewalInfo?: string;
200
+ appAccountToken?: string;
201
+ }): Promise<PurchaseResult>;
202
+ /**
203
+ * Send the boot heartbeat. Called automatically by start() unless
204
+ * autoHeartbeat:false. Safe to call manually as a "we're still here" ping.
205
+ */
206
+ heartbeat(): Promise<HeartbeatResponse>;
207
+ /**
208
+ * Wipe persisted identity + entitlement cache. Use on logout. The
209
+ * next pre-login session generates a fresh anonymousId and starts a
210
+ * new identity-graph entry.
211
+ */
212
+ reset(): void;
213
+ /**
214
+ * Diagnostic: current state + queue stats. Useful for the dashboard's
215
+ * heartbeat row and debugging in dev.
216
+ *
217
+ * Returns a stable shape regardless of whether start() has been called —
218
+ * callers don't need to narrow on `started` to access `events` or
219
+ * `entitlements`. Pre-start values are sensible empties.
220
+ */
221
+ diagnostics(): Diagnostics;
222
+ private requireStarted;
223
+ /**
224
+ * Build the identity query for /v1/entitlements. Priority:
225
+ * crossdeckCustomerId > developerUserId > anonymousId
226
+ * — matches the resolveCrossdeckCustomerId precedence on the server.
227
+ */
228
+ private identityQueryParams;
229
+ /** Pick the right identity hint to embed on a queued event. */
230
+ private identityHintForEvent;
231
+ private mintEventId;
232
+ }
233
+ /**
234
+ * Default singleton — most consumers want one SDK instance per app.
235
+ * Creating extra instances is fine; just `new CrossdeckClient()`.
236
+ */
237
+ declare const Crossdeck: CrossdeckClient;
238
+
239
+ /**
240
+ * Stripe-style error wrapper for @cross-deck/web.
241
+ *
242
+ * Mirrors the wire shape returned by the v1 backend (see
243
+ * backend/src/api/v1-errors.ts) so SDK consumers can `catch`
244
+ * with consistent fields:
245
+ *
246
+ * try {
247
+ * await crossdeck.identify("user_847");
248
+ * } catch (err) {
249
+ * if (err instanceof CrossdeckError && err.code === "invalid_api_key") {
250
+ * // ...
251
+ * }
252
+ * }
253
+ */
254
+ type CrossdeckErrorType = "authentication_error" | "permission_error" | "invalid_request_error" | "rate_limit_error" | "internal_error" | "network_error" | "configuration_error";
255
+ interface CrossdeckErrorPayload {
256
+ type: CrossdeckErrorType;
257
+ code: string;
258
+ message: string;
259
+ /** Server-issued request ID. Echoed in support tickets. */
260
+ requestId?: string;
261
+ /** HTTP status code if the error came from an API response. */
262
+ status?: number;
263
+ }
264
+ declare class CrossdeckError extends Error {
265
+ readonly type: CrossdeckErrorType;
266
+ readonly code: string;
267
+ readonly requestId?: string;
268
+ readonly status?: number;
269
+ constructor(payload: CrossdeckErrorPayload);
270
+ }
271
+
272
+ /**
273
+ * Storage adapters for SDK-persisted state.
274
+ *
275
+ * Two flavours:
276
+ * - browser localStorage (default in browsers)
277
+ * - in-memory (default in Node, or as an explicit fallback)
278
+ *
279
+ * Detection is at construction time, not at every call — picking the
280
+ * adapter once means we don't hit `typeof window` checks on hot paths.
281
+ */
282
+
283
+ /**
284
+ * In-memory storage. Cleared on process exit. Useful for Node runtimes
285
+ * where you want session-scoped identity that doesn't persist to disk.
286
+ */
287
+ declare class MemoryStorage implements KeyValueStorage {
288
+ private store;
289
+ getItem(key: string): string | null;
290
+ setItem(key: string, value: string): void;
291
+ removeItem(key: string): void;
292
+ }
293
+
294
+ /**
295
+ * HTTP transport for the SDK. Single fetch wrapper used by every endpoint
296
+ * call. Adds the Bearer token and SDK version header, parses responses,
297
+ * normalises errors to CrossdeckError.
298
+ *
299
+ * Uses platform-native fetch (browser + Node 18+). No axios, no isomorphic-
300
+ * fetch shim, no transitive deps.
301
+ */
302
+ declare const SDK_NAME = "@cross-deck/web";
303
+ declare const SDK_VERSION = "0.1.0";
304
+ declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
305
+
306
+ export { type AliasResult, type AuditRail, Crossdeck, CrossdeckClient, CrossdeckError, type CrossdeckErrorPayload, type CrossdeckErrorType, type CrossdeckOptions, DEFAULT_BASE_URL, type Diagnostics, type EntitlementsListResponse, type Environment, type EventProperties, type HeartbeatResponse, type IdentifyOptions, type KeyValueStorage, MemoryStorage, type Platform, type PublicEntitlement, type PurchaseResult, SDK_NAME, SDK_VERSION };