@cross-deck/web 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -53,12 +53,36 @@ interface HeartbeatResponse {
53
53
  serverTime: number;
54
54
  }
55
55
  /**
56
- * Configuration for Crossdeck.start. Most fields have sensible defaults
57
- * only `publicKey` is mandatory.
56
+ * Configuration for Crossdeck.init. Three fields are mandatory
57
+ * `appId`, `publicKey`, and `environment` per NorthStar §11.1.
58
+ *
59
+ * The pair of (appId, environment) is what we put on the wire envelope
60
+ * (NorthStar §13.1) so the backend can correlate events against the
61
+ * specific app surface and refuse mismatched env declarations loudly.
58
62
  */
59
63
  interface CrossdeckOptions {
64
+ /**
65
+ * Your Crossdeck App ID (e.g. "app_web_xxx"). Required.
66
+ *
67
+ * Issued in the dashboard when you create an app. Goes on the wire
68
+ * envelope so the backend correlates events with the specific app
69
+ * surface — useful when one project has multiple apps (web + iOS +
70
+ * Android) sharing the same publishable key family.
71
+ */
72
+ appId: string;
60
73
  /** Your Crossdeck publishable key (cd_pub_…). Required. */
61
74
  publicKey: string;
75
+ /**
76
+ * Explicit environment declaration. Required.
77
+ *
78
+ * Must match the publishable key's prefix:
79
+ * cd_pub_test_… → "sandbox"
80
+ * cd_pub_live_… → "production"
81
+ *
82
+ * Mismatch is rejected at init time so a typo'd key can't silently
83
+ * route prod telemetry into sandbox dashboards.
84
+ */
85
+ environment: Environment;
62
86
  /**
63
87
  * Override the API base URL. Default is https://api.cross-deck.com/v1.
64
88
  * Useful for self-hosted setups or pointing at the local emulator
@@ -109,6 +133,12 @@ interface CrossdeckOptions {
109
133
  * Useful for slicing dashboards by build.
110
134
  */
111
135
  appVersion?: string;
136
+ /**
137
+ * Enable verbose diagnostic logging via the NorthStar §16 debug-signal
138
+ * vocabulary. Default: false. Equivalent to calling
139
+ * `Crossdeck.setDebugMode(true)` after init.
140
+ */
141
+ debug?: boolean;
112
142
  }
113
143
  /** Auto-tracking flags. See CrossdeckOptions.autoTrack. */
114
144
  interface AutoTrackOptions {
@@ -158,6 +188,48 @@ interface Diagnostics {
158
188
  };
159
189
  }
160
190
 
191
+ /**
192
+ * Local cache of active entitlements so isEntitled() can answer
193
+ * synchronously after the first read. Cache is updated:
194
+ * - On successful getEntitlements()
195
+ * - On successful purchase()
196
+ * - Manually via setFromList() (used by callers that batch updates)
197
+ *
198
+ * The cache holds only ACTIVE entitlements — inactive ones are excluded
199
+ * by the backend before they hit us. isEntitled returns false for
200
+ * anything not in the set.
201
+ *
202
+ * Reactive listener API
203
+ * ---------------------
204
+ * `subscribe(listener)` registers a callback that fires every time the
205
+ * cache mutates (setFromList or clear). This is the foundation for the
206
+ * `useEntitlement` React hook in `@cross-deck/web/react` and any other
207
+ * framework binding consumers need: SwiftUI's `@Observable`, Vue's
208
+ * `ref()`, Solid's signals, etc.
209
+ *
210
+ * Why we need it: isEntitled() is a sync cache read — but if a React
211
+ * component calls it in a render path, React has no way to know when
212
+ * the cache populates asynchronously after `getEntitlements()` lands.
213
+ * Without a subscribe API the component shows the empty-cache result
214
+ * forever (until something else triggers a re-render). With it, the
215
+ * binding can re-render when the data actually arrives.
216
+ *
217
+ * Listener semantics:
218
+ * - Fired AFTER the cache has been mutated (listener sees fresh state)
219
+ * - Fire-and-forget: thrown errors in a listener don't crash the SDK
220
+ * (they're swallowed; the next listener still runs)
221
+ * - The unsubscribe function returned from subscribe() is idempotent
222
+ * - Listeners are NOT fired on subscribe — caller is expected to
223
+ * read current state synchronously from isEntitled()/list() if it
224
+ * wants the initial render to reflect cached data
225
+ *
226
+ * Thread / re-entrancy safety: this is a synchronous in-memory Set with
227
+ * no I/O. The async paths that update it are serialised through the
228
+ * SDK's request queue — callers won't see torn reads.
229
+ */
230
+
231
+ type EntitlementsListener = (entitlements: PublicEntitlement[]) => void;
232
+
161
233
  /**
162
234
  * Public API surface for @cross-deck/web.
163
235
  *
@@ -165,7 +237,11 @@ interface Diagnostics {
165
237
  *
166
238
  * import { Crossdeck } from "@cross-deck/web";
167
239
  *
168
- * Crossdeck.start({ publicKey: "cd_pub_live_…" });
240
+ * Crossdeck.init({
241
+ * appId: "app_web_xxx",
242
+ * publicKey: "cd_pub_live_…",
243
+ * environment: "production",
244
+ * });
169
245
  *
170
246
  * await Crossdeck.identify("user_847");
171
247
  * const ents = await Crossdeck.getEntitlements();
@@ -177,11 +253,12 @@ interface Diagnostics {
177
253
  *
178
254
  * Usage (Node):
179
255
  *
180
- * import { Crossdeck } from "@cross-deck/web";
181
- * import { MemoryStorage } from "@cross-deck/web";
256
+ * import { Crossdeck, MemoryStorage } from "@cross-deck/web";
182
257
  *
183
- * Crossdeck.start({
258
+ * Crossdeck.init({
259
+ * appId: "app_node_xxx",
184
260
  * publicKey: "cd_pub_test_…",
261
+ * environment: "sandbox",
185
262
  * storage: new MemoryStorage(), // session-only persistence
186
263
  * autoHeartbeat: false, // skip the boot ping in scripts
187
264
  * });
@@ -190,9 +267,19 @@ interface Diagnostics {
190
267
  declare class CrossdeckClient {
191
268
  private state;
192
269
  /**
193
- * Boot the SDK. Idempotent — calling start twice with the same options
270
+ * Boot the SDK. Idempotent — calling init twice with the same options
194
271
  * is a no-op; calling with different options replaces the previous
195
272
  * configuration.
273
+ *
274
+ * NorthStar §11.1: signature is `Crossdeck.init({ appId, publicKey,
275
+ * environment })`. The trio is validated up-front so a typo'd key or a
276
+ * mismatched env fails fast at boot rather than at first event-flush.
277
+ */
278
+ init(options: CrossdeckOptions): void;
279
+ /**
280
+ * @deprecated Use `init()` instead. NorthStar §4 standardised the
281
+ * lifecycle method name across SDKs as `init` (formerly `start` /
282
+ * `configure`). `start` will be removed in a future major version.
196
283
  */
197
284
  start(options: CrossdeckOptions): void;
198
285
  /**
@@ -213,20 +300,74 @@ declare class CrossdeckClient {
213
300
  isEntitled(key: string): boolean;
214
301
  /** Snapshot of the local entitlement cache. */
215
302
  listEntitlements(): PublicEntitlement[];
303
+ /**
304
+ * Subscribe to entitlement-cache changes. Returns an unsubscribe fn.
305
+ *
306
+ * The listener is invoked AFTER the cache mutates — once after a
307
+ * successful `getEntitlements()` warms it, again after `syncPurchases()`
308
+ * delivers fresh entitlements, and once on `reset()` to fire the
309
+ * empty-cache state for logout flows.
310
+ *
311
+ * It is NOT invoked synchronously on subscribe. Callers that need
312
+ * the current state should read it via `isEntitled()` / `listEntitlements()`
313
+ * inline; the listener fires only on FUTURE changes.
314
+ *
315
+ * This is the foundation of the `useEntitlement` React hook in
316
+ * `@cross-deck/web/react` — without it, React (or SwiftUI / Compose
317
+ * / Vue) would have no way to re-render when entitlements arrive
318
+ * asynchronously after init. The naive pattern of calling
319
+ * `Crossdeck.isEntitled("pro")` directly inside a render path
320
+ * shows the empty-cache result forever; binding the result to
321
+ * component state via `onEntitlementsChange` is the correct
322
+ * pattern.
323
+ *
324
+ * Idempotent unsubscribe — calling the returned function multiple
325
+ * times is safe.
326
+ *
327
+ * Listener errors are swallowed (a buggy listener can't crash the
328
+ * SDK or other listeners).
329
+ */
330
+ onEntitlementsChange(listener: EntitlementsListener): () => void;
216
331
  /**
217
332
  * Queue a telemetry event. Returns immediately — the network round-
218
333
  * trip happens in the background. To flush before the page unloads,
219
- * call flushEvents().
334
+ * call flush().
220
335
  */
221
336
  track(name: string, properties?: EventProperties): void;
222
- /** Force-flush queued events. Useful to call from page-unload handlers. */
337
+ /**
338
+ * Force-flush queued events. Useful to call from page-unload handlers.
339
+ *
340
+ * NorthStar §4: standard method name across all Crossdeck SDKs.
341
+ */
342
+ flush(): Promise<void>;
343
+ /** @deprecated Use `flush()` instead. NorthStar §4 standardised the name. */
223
344
  flushEvents(): Promise<void>;
224
- /** Forward an Apple StoreKit 2 transaction for verification + projection. */
345
+ /**
346
+ * Forward purchase evidence to the backend for verification + entitlement
347
+ * projection. NorthStar §4 + §13 canonical name.
348
+ *
349
+ * Today the web SDK only supports Apple StoreKit 2 forwarding (web apps
350
+ * that sit alongside an iOS app). Stripe doesn't need this method —
351
+ * Stripe webhooks deliver evidence server-side without a client round-trip.
352
+ */
353
+ syncPurchases(input: {
354
+ rail?: "apple";
355
+ signedTransactionInfo: string;
356
+ signedRenewalInfo?: string;
357
+ appAccountToken?: string;
358
+ }): Promise<PurchaseResult>;
359
+ /** @deprecated Use `syncPurchases()` instead. NorthStar §4 standardised the name. */
225
360
  purchaseApple(input: {
226
361
  signedTransactionInfo: string;
227
362
  signedRenewalInfo?: string;
228
363
  appAccountToken?: string;
229
364
  }): Promise<PurchaseResult>;
365
+ /**
366
+ * Toggle verbose diagnostic logging — NorthStar §16. When enabled, the
367
+ * SDK emits a fixed vocabulary of debug signals to console.info that the
368
+ * dashboard's onboarding checklist can also surface as live events.
369
+ */
370
+ setDebugMode(enabled: boolean): void;
230
371
  /**
231
372
  * Send the boot heartbeat. Called automatically by start() unless
232
373
  * autoHeartbeat:false. Safe to call manually as a "we're still here" ping.
@@ -328,7 +469,7 @@ declare class MemoryStorage implements KeyValueStorage {
328
469
  * fetch shim, no transitive deps.
329
470
  */
330
471
  declare const SDK_NAME = "@cross-deck/web";
331
- declare const SDK_VERSION = "0.2.0";
472
+ declare const SDK_VERSION = "0.3.0";
332
473
  declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
333
474
 
334
475
  /**
package/dist/index.d.ts CHANGED
@@ -53,12 +53,36 @@ interface HeartbeatResponse {
53
53
  serverTime: number;
54
54
  }
55
55
  /**
56
- * Configuration for Crossdeck.start. Most fields have sensible defaults
57
- * only `publicKey` is mandatory.
56
+ * Configuration for Crossdeck.init. Three fields are mandatory
57
+ * `appId`, `publicKey`, and `environment` per NorthStar §11.1.
58
+ *
59
+ * The pair of (appId, environment) is what we put on the wire envelope
60
+ * (NorthStar §13.1) so the backend can correlate events against the
61
+ * specific app surface and refuse mismatched env declarations loudly.
58
62
  */
59
63
  interface CrossdeckOptions {
64
+ /**
65
+ * Your Crossdeck App ID (e.g. "app_web_xxx"). Required.
66
+ *
67
+ * Issued in the dashboard when you create an app. Goes on the wire
68
+ * envelope so the backend correlates events with the specific app
69
+ * surface — useful when one project has multiple apps (web + iOS +
70
+ * Android) sharing the same publishable key family.
71
+ */
72
+ appId: string;
60
73
  /** Your Crossdeck publishable key (cd_pub_…). Required. */
61
74
  publicKey: string;
75
+ /**
76
+ * Explicit environment declaration. Required.
77
+ *
78
+ * Must match the publishable key's prefix:
79
+ * cd_pub_test_… → "sandbox"
80
+ * cd_pub_live_… → "production"
81
+ *
82
+ * Mismatch is rejected at init time so a typo'd key can't silently
83
+ * route prod telemetry into sandbox dashboards.
84
+ */
85
+ environment: Environment;
62
86
  /**
63
87
  * Override the API base URL. Default is https://api.cross-deck.com/v1.
64
88
  * Useful for self-hosted setups or pointing at the local emulator
@@ -109,6 +133,12 @@ interface CrossdeckOptions {
109
133
  * Useful for slicing dashboards by build.
110
134
  */
111
135
  appVersion?: string;
136
+ /**
137
+ * Enable verbose diagnostic logging via the NorthStar §16 debug-signal
138
+ * vocabulary. Default: false. Equivalent to calling
139
+ * `Crossdeck.setDebugMode(true)` after init.
140
+ */
141
+ debug?: boolean;
112
142
  }
113
143
  /** Auto-tracking flags. See CrossdeckOptions.autoTrack. */
114
144
  interface AutoTrackOptions {
@@ -158,6 +188,48 @@ interface Diagnostics {
158
188
  };
159
189
  }
160
190
 
191
+ /**
192
+ * Local cache of active entitlements so isEntitled() can answer
193
+ * synchronously after the first read. Cache is updated:
194
+ * - On successful getEntitlements()
195
+ * - On successful purchase()
196
+ * - Manually via setFromList() (used by callers that batch updates)
197
+ *
198
+ * The cache holds only ACTIVE entitlements — inactive ones are excluded
199
+ * by the backend before they hit us. isEntitled returns false for
200
+ * anything not in the set.
201
+ *
202
+ * Reactive listener API
203
+ * ---------------------
204
+ * `subscribe(listener)` registers a callback that fires every time the
205
+ * cache mutates (setFromList or clear). This is the foundation for the
206
+ * `useEntitlement` React hook in `@cross-deck/web/react` and any other
207
+ * framework binding consumers need: SwiftUI's `@Observable`, Vue's
208
+ * `ref()`, Solid's signals, etc.
209
+ *
210
+ * Why we need it: isEntitled() is a sync cache read — but if a React
211
+ * component calls it in a render path, React has no way to know when
212
+ * the cache populates asynchronously after `getEntitlements()` lands.
213
+ * Without a subscribe API the component shows the empty-cache result
214
+ * forever (until something else triggers a re-render). With it, the
215
+ * binding can re-render when the data actually arrives.
216
+ *
217
+ * Listener semantics:
218
+ * - Fired AFTER the cache has been mutated (listener sees fresh state)
219
+ * - Fire-and-forget: thrown errors in a listener don't crash the SDK
220
+ * (they're swallowed; the next listener still runs)
221
+ * - The unsubscribe function returned from subscribe() is idempotent
222
+ * - Listeners are NOT fired on subscribe — caller is expected to
223
+ * read current state synchronously from isEntitled()/list() if it
224
+ * wants the initial render to reflect cached data
225
+ *
226
+ * Thread / re-entrancy safety: this is a synchronous in-memory Set with
227
+ * no I/O. The async paths that update it are serialised through the
228
+ * SDK's request queue — callers won't see torn reads.
229
+ */
230
+
231
+ type EntitlementsListener = (entitlements: PublicEntitlement[]) => void;
232
+
161
233
  /**
162
234
  * Public API surface for @cross-deck/web.
163
235
  *
@@ -165,7 +237,11 @@ interface Diagnostics {
165
237
  *
166
238
  * import { Crossdeck } from "@cross-deck/web";
167
239
  *
168
- * Crossdeck.start({ publicKey: "cd_pub_live_…" });
240
+ * Crossdeck.init({
241
+ * appId: "app_web_xxx",
242
+ * publicKey: "cd_pub_live_…",
243
+ * environment: "production",
244
+ * });
169
245
  *
170
246
  * await Crossdeck.identify("user_847");
171
247
  * const ents = await Crossdeck.getEntitlements();
@@ -177,11 +253,12 @@ interface Diagnostics {
177
253
  *
178
254
  * Usage (Node):
179
255
  *
180
- * import { Crossdeck } from "@cross-deck/web";
181
- * import { MemoryStorage } from "@cross-deck/web";
256
+ * import { Crossdeck, MemoryStorage } from "@cross-deck/web";
182
257
  *
183
- * Crossdeck.start({
258
+ * Crossdeck.init({
259
+ * appId: "app_node_xxx",
184
260
  * publicKey: "cd_pub_test_…",
261
+ * environment: "sandbox",
185
262
  * storage: new MemoryStorage(), // session-only persistence
186
263
  * autoHeartbeat: false, // skip the boot ping in scripts
187
264
  * });
@@ -190,9 +267,19 @@ interface Diagnostics {
190
267
  declare class CrossdeckClient {
191
268
  private state;
192
269
  /**
193
- * Boot the SDK. Idempotent — calling start twice with the same options
270
+ * Boot the SDK. Idempotent — calling init twice with the same options
194
271
  * is a no-op; calling with different options replaces the previous
195
272
  * configuration.
273
+ *
274
+ * NorthStar §11.1: signature is `Crossdeck.init({ appId, publicKey,
275
+ * environment })`. The trio is validated up-front so a typo'd key or a
276
+ * mismatched env fails fast at boot rather than at first event-flush.
277
+ */
278
+ init(options: CrossdeckOptions): void;
279
+ /**
280
+ * @deprecated Use `init()` instead. NorthStar §4 standardised the
281
+ * lifecycle method name across SDKs as `init` (formerly `start` /
282
+ * `configure`). `start` will be removed in a future major version.
196
283
  */
197
284
  start(options: CrossdeckOptions): void;
198
285
  /**
@@ -213,20 +300,74 @@ declare class CrossdeckClient {
213
300
  isEntitled(key: string): boolean;
214
301
  /** Snapshot of the local entitlement cache. */
215
302
  listEntitlements(): PublicEntitlement[];
303
+ /**
304
+ * Subscribe to entitlement-cache changes. Returns an unsubscribe fn.
305
+ *
306
+ * The listener is invoked AFTER the cache mutates — once after a
307
+ * successful `getEntitlements()` warms it, again after `syncPurchases()`
308
+ * delivers fresh entitlements, and once on `reset()` to fire the
309
+ * empty-cache state for logout flows.
310
+ *
311
+ * It is NOT invoked synchronously on subscribe. Callers that need
312
+ * the current state should read it via `isEntitled()` / `listEntitlements()`
313
+ * inline; the listener fires only on FUTURE changes.
314
+ *
315
+ * This is the foundation of the `useEntitlement` React hook in
316
+ * `@cross-deck/web/react` — without it, React (or SwiftUI / Compose
317
+ * / Vue) would have no way to re-render when entitlements arrive
318
+ * asynchronously after init. The naive pattern of calling
319
+ * `Crossdeck.isEntitled("pro")` directly inside a render path
320
+ * shows the empty-cache result forever; binding the result to
321
+ * component state via `onEntitlementsChange` is the correct
322
+ * pattern.
323
+ *
324
+ * Idempotent unsubscribe — calling the returned function multiple
325
+ * times is safe.
326
+ *
327
+ * Listener errors are swallowed (a buggy listener can't crash the
328
+ * SDK or other listeners).
329
+ */
330
+ onEntitlementsChange(listener: EntitlementsListener): () => void;
216
331
  /**
217
332
  * Queue a telemetry event. Returns immediately — the network round-
218
333
  * trip happens in the background. To flush before the page unloads,
219
- * call flushEvents().
334
+ * call flush().
220
335
  */
221
336
  track(name: string, properties?: EventProperties): void;
222
- /** Force-flush queued events. Useful to call from page-unload handlers. */
337
+ /**
338
+ * Force-flush queued events. Useful to call from page-unload handlers.
339
+ *
340
+ * NorthStar §4: standard method name across all Crossdeck SDKs.
341
+ */
342
+ flush(): Promise<void>;
343
+ /** @deprecated Use `flush()` instead. NorthStar §4 standardised the name. */
223
344
  flushEvents(): Promise<void>;
224
- /** Forward an Apple StoreKit 2 transaction for verification + projection. */
345
+ /**
346
+ * Forward purchase evidence to the backend for verification + entitlement
347
+ * projection. NorthStar §4 + §13 canonical name.
348
+ *
349
+ * Today the web SDK only supports Apple StoreKit 2 forwarding (web apps
350
+ * that sit alongside an iOS app). Stripe doesn't need this method —
351
+ * Stripe webhooks deliver evidence server-side without a client round-trip.
352
+ */
353
+ syncPurchases(input: {
354
+ rail?: "apple";
355
+ signedTransactionInfo: string;
356
+ signedRenewalInfo?: string;
357
+ appAccountToken?: string;
358
+ }): Promise<PurchaseResult>;
359
+ /** @deprecated Use `syncPurchases()` instead. NorthStar §4 standardised the name. */
225
360
  purchaseApple(input: {
226
361
  signedTransactionInfo: string;
227
362
  signedRenewalInfo?: string;
228
363
  appAccountToken?: string;
229
364
  }): Promise<PurchaseResult>;
365
+ /**
366
+ * Toggle verbose diagnostic logging — NorthStar §16. When enabled, the
367
+ * SDK emits a fixed vocabulary of debug signals to console.info that the
368
+ * dashboard's onboarding checklist can also surface as live events.
369
+ */
370
+ setDebugMode(enabled: boolean): void;
230
371
  /**
231
372
  * Send the boot heartbeat. Called automatically by start() unless
232
373
  * autoHeartbeat:false. Safe to call manually as a "we're still here" ping.
@@ -328,7 +469,7 @@ declare class MemoryStorage implements KeyValueStorage {
328
469
  * fetch shim, no transitive deps.
329
470
  */
330
471
  declare const SDK_NAME = "@cross-deck/web";
331
- declare const SDK_VERSION = "0.2.0";
472
+ declare const SDK_VERSION = "0.3.0";
332
473
  declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
333
474
 
334
475
  /**