@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.
package/dist/index.js ADDED
@@ -0,0 +1,655 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Crossdeck: () => Crossdeck,
24
+ CrossdeckClient: () => CrossdeckClient,
25
+ CrossdeckError: () => CrossdeckError,
26
+ DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
27
+ MemoryStorage: () => MemoryStorage,
28
+ SDK_NAME: () => SDK_NAME,
29
+ SDK_VERSION: () => SDK_VERSION
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/errors.ts
34
+ var CrossdeckError = class _CrossdeckError extends Error {
35
+ constructor(payload) {
36
+ super(payload.message);
37
+ this.name = "CrossdeckError";
38
+ this.type = payload.type;
39
+ this.code = payload.code;
40
+ this.requestId = payload.requestId;
41
+ this.status = payload.status;
42
+ Object.setPrototypeOf(this, _CrossdeckError.prototype);
43
+ }
44
+ };
45
+ async function crossdeckErrorFromResponse(res) {
46
+ const requestId = res.headers.get("x-request-id") ?? void 0;
47
+ let body;
48
+ try {
49
+ body = await res.json();
50
+ } catch {
51
+ body = null;
52
+ }
53
+ const envelope = body?.error;
54
+ if (envelope && typeof envelope.type === "string" && typeof envelope.code === "string") {
55
+ return new CrossdeckError({
56
+ type: envelope.type,
57
+ code: envelope.code,
58
+ message: envelope.message ?? `HTTP ${res.status}`,
59
+ requestId: envelope.request_id ?? requestId,
60
+ status: res.status
61
+ });
62
+ }
63
+ return new CrossdeckError({
64
+ type: typeMapForStatus(res.status),
65
+ code: `http_${res.status}`,
66
+ message: `HTTP ${res.status} ${res.statusText || ""}`.trim(),
67
+ requestId,
68
+ status: res.status
69
+ });
70
+ }
71
+ function typeMapForStatus(status) {
72
+ if (status === 401) return "authentication_error";
73
+ if (status === 403) return "permission_error";
74
+ if (status === 429) return "rate_limit_error";
75
+ if (status >= 400 && status < 500) return "invalid_request_error";
76
+ return "internal_error";
77
+ }
78
+
79
+ // src/http.ts
80
+ var SDK_NAME = "@cross-deck/web";
81
+ var SDK_VERSION = "0.1.0";
82
+ var DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
83
+ var HttpClient = class {
84
+ constructor(config) {
85
+ this.config = config;
86
+ }
87
+ /**
88
+ * Issue a request. `path` is relative to the configured baseUrl
89
+ * ("/entitlements", "/identity/alias", etc.).
90
+ *
91
+ * Throws CrossdeckError on:
92
+ * - Network failure (`type: "network_error"`)
93
+ * - Non-2xx response (typed from the body envelope)
94
+ * - JSON parse failure on a 2xx (treated as `internal_error`)
95
+ */
96
+ async request(method, path, options = {}) {
97
+ const url = this.buildUrl(path, options.query);
98
+ const headers = {
99
+ Authorization: `Bearer ${this.config.publicKey}`,
100
+ "Crossdeck-Sdk-Version": `${SDK_NAME}@${this.config.sdkVersion}`,
101
+ Accept: "application/json"
102
+ };
103
+ let bodyInit;
104
+ if (options.body !== void 0) {
105
+ headers["Content-Type"] = "application/json";
106
+ bodyInit = JSON.stringify(options.body);
107
+ }
108
+ let response;
109
+ try {
110
+ response = await fetch(url, {
111
+ method,
112
+ headers,
113
+ body: bodyInit
114
+ });
115
+ } catch (err) {
116
+ throw new CrossdeckError({
117
+ type: "network_error",
118
+ code: "fetch_failed",
119
+ message: err instanceof Error ? err.message : "fetch failed"
120
+ });
121
+ }
122
+ if (!response.ok) {
123
+ throw await crossdeckErrorFromResponse(response);
124
+ }
125
+ if (response.status === 204) return void 0;
126
+ try {
127
+ return await response.json();
128
+ } catch (err) {
129
+ throw new CrossdeckError({
130
+ type: "internal_error",
131
+ code: "invalid_json_response",
132
+ message: "Server returned a 2xx with an unparseable body.",
133
+ requestId: response.headers.get("x-request-id") ?? void 0,
134
+ status: response.status
135
+ });
136
+ }
137
+ }
138
+ buildUrl(path, query) {
139
+ const base = this.config.baseUrl.replace(/\/+$/, "");
140
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
141
+ let url = base + cleanPath;
142
+ if (query) {
143
+ const params = new URLSearchParams();
144
+ for (const [k, v] of Object.entries(query)) {
145
+ if (typeof v === "string" && v.length > 0) params.append(k, v);
146
+ }
147
+ const qs = params.toString();
148
+ if (qs) url += (url.includes("?") ? "&" : "?") + qs;
149
+ }
150
+ return url;
151
+ }
152
+ };
153
+
154
+ // src/identity.ts
155
+ var KEY_ANON = "anon_id";
156
+ var KEY_CDCUST = "cdcust_id";
157
+ var IdentityStore = class {
158
+ constructor(storage, prefix) {
159
+ this.storage = storage;
160
+ this.prefix = prefix;
161
+ const stored = {
162
+ anon: storage.getItem(prefix + KEY_ANON),
163
+ cdcust: storage.getItem(prefix + KEY_CDCUST)
164
+ };
165
+ this.state = {
166
+ anonymousId: stored.anon ?? this.mintAnonymousId(),
167
+ crossdeckCustomerId: stored.cdcust
168
+ };
169
+ if (!stored.anon) {
170
+ storage.setItem(prefix + KEY_ANON, this.state.anonymousId);
171
+ }
172
+ }
173
+ /** Return the persisted anonymous device ID (always set). */
174
+ get anonymousId() {
175
+ return this.state.anonymousId;
176
+ }
177
+ /** Return the resolved cross­deckCustomerId once we have one, else null. */
178
+ get crossdeckCustomerId() {
179
+ return this.state.crossdeckCustomerId;
180
+ }
181
+ /** Persist a newly-resolved Crossdeck customer ID. */
182
+ setCrossdeckCustomerId(value) {
183
+ this.state.crossdeckCustomerId = value;
184
+ this.storage.setItem(this.prefix + KEY_CDCUST, value);
185
+ }
186
+ /**
187
+ * Wipe persisted identity. Called by reset() — used when an end-user
188
+ * logs out. After reset the SDK mints a new anonymousId so the next
189
+ * pre-login session is a fresh customer in the identity graph.
190
+ */
191
+ reset() {
192
+ this.storage.removeItem(this.prefix + KEY_ANON);
193
+ this.storage.removeItem(this.prefix + KEY_CDCUST);
194
+ this.state = {
195
+ anonymousId: this.mintAnonymousId(),
196
+ crossdeckCustomerId: null
197
+ };
198
+ this.storage.setItem(this.prefix + KEY_ANON, this.state.anonymousId);
199
+ }
200
+ /**
201
+ * Generate an anonymousId. Crockford-ish base32 timestamp + random
202
+ * suffix. Same shape Stripe / Segment / others use — sortable, log-
203
+ * friendly, no PII.
204
+ */
205
+ mintAnonymousId() {
206
+ const ts = Date.now().toString(36);
207
+ const rand = randomChars(10);
208
+ return `anon_${ts}${rand}`;
209
+ }
210
+ };
211
+ function randomChars(count) {
212
+ const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
213
+ const out = [];
214
+ const cryptoApi = globalThis.crypto;
215
+ if (cryptoApi?.getRandomValues) {
216
+ const buf = new Uint8Array(count);
217
+ cryptoApi.getRandomValues(buf);
218
+ for (let i = 0; i < count; i++) {
219
+ out.push(alphabet[buf[i] % alphabet.length] ?? "0");
220
+ }
221
+ } else {
222
+ for (let i = 0; i < count; i++) {
223
+ out.push(alphabet[Math.floor(Math.random() * alphabet.length)] ?? "0");
224
+ }
225
+ }
226
+ return out.join("");
227
+ }
228
+
229
+ // src/entitlement-cache.ts
230
+ var EntitlementCache = class {
231
+ constructor() {
232
+ this.active = /* @__PURE__ */ new Set();
233
+ this.all = [];
234
+ this.lastUpdated = 0;
235
+ }
236
+ /** Sync read — true iff the entitlement key is currently active. */
237
+ isEntitled(key) {
238
+ return this.active.has(key);
239
+ }
240
+ /** Full snapshot for callers that need source / validUntil details. */
241
+ list() {
242
+ return this.all.slice();
243
+ }
244
+ /** When the cache was last refreshed. 0 means "never". */
245
+ get freshness() {
246
+ return this.lastUpdated;
247
+ }
248
+ /**
249
+ * Replace the cache with a fresh server response. The backend already
250
+ * filters to active + env-matching, so we don't re-filter — just trust
251
+ * what we got.
252
+ */
253
+ setFromList(entitlements) {
254
+ this.all = entitlements.slice();
255
+ this.active = new Set(entitlements.filter((e) => e.isActive).map((e) => e.key));
256
+ this.lastUpdated = Date.now();
257
+ }
258
+ /**
259
+ * Wipe — used on reset() (logout). The SDK forgets everything until
260
+ * the next identify + read.
261
+ */
262
+ clear() {
263
+ this.active.clear();
264
+ this.all = [];
265
+ this.lastUpdated = 0;
266
+ }
267
+ };
268
+
269
+ // src/event-queue.ts
270
+ var HARD_BUFFER_CAP = 1e3;
271
+ var EventQueue = class {
272
+ constructor(cfg) {
273
+ this.cfg = cfg;
274
+ this.buffer = [];
275
+ this.dropped = 0;
276
+ this.inFlight = 0;
277
+ this.lastFlushAt = 0;
278
+ this.lastError = null;
279
+ this.cancelTimer = null;
280
+ }
281
+ enqueue(event) {
282
+ this.buffer.push(event);
283
+ if (this.buffer.length > HARD_BUFFER_CAP) {
284
+ const overflow = this.buffer.length - HARD_BUFFER_CAP;
285
+ this.buffer.splice(0, overflow);
286
+ this.dropped += overflow;
287
+ this.cfg.onDrop?.(overflow);
288
+ }
289
+ if (this.buffer.length >= this.cfg.batchSize) {
290
+ void this.flush();
291
+ } else {
292
+ this.scheduleIdleFlush();
293
+ }
294
+ }
295
+ /**
296
+ * Flush the buffer to /v1/events. Resolves when the network call
297
+ * completes (success or failure). On failure, events stay in the
298
+ * buffer for the next flush attempt.
299
+ */
300
+ async flush() {
301
+ if (this.buffer.length === 0) return null;
302
+ this.cancelTimerIfSet();
303
+ const batch = this.buffer.splice(0);
304
+ this.inFlight += batch.length;
305
+ try {
306
+ const result = await this.cfg.http.request("POST", "/events", {
307
+ body: { events: batch }
308
+ });
309
+ this.lastFlushAt = Date.now();
310
+ this.lastError = null;
311
+ this.inFlight -= batch.length;
312
+ return result;
313
+ } catch (err) {
314
+ this.buffer.unshift(...batch);
315
+ this.inFlight -= batch.length;
316
+ this.lastError = err instanceof Error ? err.message : String(err);
317
+ this.scheduleIdleFlush();
318
+ return null;
319
+ }
320
+ }
321
+ /** Cancel any pending timer and clear in-memory state. */
322
+ reset() {
323
+ this.cancelTimerIfSet();
324
+ this.buffer = [];
325
+ this.dropped = 0;
326
+ this.inFlight = 0;
327
+ this.lastError = null;
328
+ }
329
+ getStats() {
330
+ return {
331
+ buffered: this.buffer.length,
332
+ dropped: this.dropped,
333
+ inFlight: this.inFlight,
334
+ lastFlushAt: this.lastFlushAt,
335
+ lastError: this.lastError
336
+ };
337
+ }
338
+ scheduleIdleFlush() {
339
+ this.cancelTimerIfSet();
340
+ const sched = this.cfg.scheduler ?? defaultScheduler;
341
+ this.cancelTimer = sched(() => {
342
+ void this.flush();
343
+ }, this.cfg.intervalMs);
344
+ }
345
+ cancelTimerIfSet() {
346
+ if (this.cancelTimer) {
347
+ this.cancelTimer();
348
+ this.cancelTimer = null;
349
+ }
350
+ }
351
+ };
352
+ function defaultScheduler(fn, ms) {
353
+ const id = setTimeout(fn, ms);
354
+ if (typeof id.unref === "function") {
355
+ try {
356
+ id.unref();
357
+ } catch {
358
+ }
359
+ }
360
+ return () => clearTimeout(id);
361
+ }
362
+
363
+ // src/storage.ts
364
+ var MemoryStorage = class {
365
+ constructor() {
366
+ this.store = /* @__PURE__ */ new Map();
367
+ }
368
+ getItem(key) {
369
+ return this.store.get(key) ?? null;
370
+ }
371
+ setItem(key, value) {
372
+ this.store.set(key, value);
373
+ }
374
+ removeItem(key) {
375
+ this.store.delete(key);
376
+ }
377
+ };
378
+ function detectDefaultStorage() {
379
+ try {
380
+ const ls = globalThis.localStorage;
381
+ if (ls) {
382
+ const probe = "__crossdeck_probe__";
383
+ ls.setItem(probe, "1");
384
+ ls.removeItem(probe);
385
+ return ls;
386
+ }
387
+ } catch {
388
+ }
389
+ return new MemoryStorage();
390
+ }
391
+
392
+ // src/crossdeck.ts
393
+ var CrossdeckClient = class {
394
+ constructor() {
395
+ this.state = null;
396
+ }
397
+ /**
398
+ * Boot the SDK. Idempotent — calling start twice with the same options
399
+ * is a no-op; calling with different options replaces the previous
400
+ * configuration.
401
+ */
402
+ start(options) {
403
+ if (!options.publicKey || !options.publicKey.startsWith("cd_pub_")) {
404
+ throw new CrossdeckError({
405
+ type: "configuration_error",
406
+ code: "invalid_public_key",
407
+ message: "Crossdeck.start requires a publishable key starting with cd_pub_."
408
+ });
409
+ }
410
+ const storage = options.storage ?? detectDefaultStorage();
411
+ const persistIdentity = options.persistIdentity ?? true;
412
+ const opts = {
413
+ publicKey: options.publicKey,
414
+ baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
415
+ persistIdentity,
416
+ storagePrefix: options.storagePrefix ?? "crossdeck:",
417
+ autoHeartbeat: options.autoHeartbeat ?? true,
418
+ eventFlushBatchSize: options.eventFlushBatchSize ?? 20,
419
+ eventFlushIntervalMs: options.eventFlushIntervalMs ?? 5e3,
420
+ sdkVersion: options.sdkVersion ?? SDK_VERSION
421
+ };
422
+ const http = new HttpClient({
423
+ publicKey: opts.publicKey,
424
+ baseUrl: opts.baseUrl,
425
+ sdkVersion: opts.sdkVersion
426
+ });
427
+ const effectiveStorage = persistIdentity ? storage : new MemoryStorage();
428
+ const identity = new IdentityStore(effectiveStorage, opts.storagePrefix);
429
+ const entitlements = new EntitlementCache();
430
+ const events = new EventQueue({
431
+ http,
432
+ batchSize: opts.eventFlushBatchSize,
433
+ intervalMs: opts.eventFlushIntervalMs
434
+ });
435
+ this.state = {
436
+ http,
437
+ identity,
438
+ entitlements,
439
+ events,
440
+ options: opts,
441
+ developerUserId: null
442
+ };
443
+ if (opts.autoHeartbeat) {
444
+ void this.heartbeat().catch(() => void 0);
445
+ }
446
+ }
447
+ /**
448
+ * Link the anonymous device to a developer-supplied user ID. Cache
449
+ * the resolved Crossdeck customer for follow-up calls.
450
+ */
451
+ async identify(userId, _options) {
452
+ const s = this.requireStarted();
453
+ if (!userId) {
454
+ throw new CrossdeckError({
455
+ type: "invalid_request_error",
456
+ code: "missing_user_id",
457
+ message: "identify(userId) requires a non-empty userId."
458
+ });
459
+ }
460
+ const result = await s.http.request("POST", "/identity/alias", {
461
+ body: { userId, anonymousId: s.identity.anonymousId }
462
+ });
463
+ s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);
464
+ s.developerUserId = userId;
465
+ return result;
466
+ }
467
+ /**
468
+ * Read the current customer's active entitlements from the server.
469
+ * Updates the local cache so subsequent isEntitled() calls answer
470
+ * synchronously.
471
+ */
472
+ async getEntitlements() {
473
+ const s = this.requireStarted();
474
+ const query = this.identityQueryParams();
475
+ const result = await s.http.request(
476
+ "GET",
477
+ "/entitlements",
478
+ { query }
479
+ );
480
+ if (result.crossdeckCustomerId) {
481
+ s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);
482
+ }
483
+ s.entitlements.setFromList(result.data);
484
+ return result.data;
485
+ }
486
+ /**
487
+ * Synchronous read from the local cache. Returns false if the cache
488
+ * has never been populated (call getEntitlements first to warm it).
489
+ */
490
+ isEntitled(key) {
491
+ const s = this.requireStarted();
492
+ return s.entitlements.isEntitled(key);
493
+ }
494
+ /** Snapshot of the local entitlement cache. */
495
+ listEntitlements() {
496
+ const s = this.requireStarted();
497
+ return s.entitlements.list();
498
+ }
499
+ /**
500
+ * Queue a telemetry event. Returns immediately — the network round-
501
+ * trip happens in the background. To flush before the page unloads,
502
+ * call flushEvents().
503
+ */
504
+ track(name, properties) {
505
+ const s = this.requireStarted();
506
+ if (!name) {
507
+ throw new CrossdeckError({
508
+ type: "invalid_request_error",
509
+ code: "missing_event_name",
510
+ message: "track(name) requires a non-empty name."
511
+ });
512
+ }
513
+ const event = {
514
+ eventId: this.mintEventId(),
515
+ name,
516
+ timestamp: Date.now(),
517
+ properties: properties ?? {}
518
+ };
519
+ Object.assign(event, this.identityHintForEvent());
520
+ s.events.enqueue(event);
521
+ }
522
+ /** Force-flush queued events. Useful to call from page-unload handlers. */
523
+ async flushEvents() {
524
+ const s = this.requireStarted();
525
+ await s.events.flush();
526
+ }
527
+ /** Forward an Apple StoreKit 2 transaction for verification + projection. */
528
+ async purchaseApple(input) {
529
+ const s = this.requireStarted();
530
+ if (!input.signedTransactionInfo) {
531
+ throw new CrossdeckError({
532
+ type: "invalid_request_error",
533
+ code: "missing_signed_transaction_info",
534
+ message: "purchaseApple requires a signedTransactionInfo string from StoreKit 2."
535
+ });
536
+ }
537
+ const result = await s.http.request("POST", "/purchases", {
538
+ body: { rail: "apple", ...input }
539
+ });
540
+ s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);
541
+ s.entitlements.setFromList(result.entitlements);
542
+ return result;
543
+ }
544
+ /**
545
+ * Send the boot heartbeat. Called automatically by start() unless
546
+ * autoHeartbeat:false. Safe to call manually as a "we're still here" ping.
547
+ */
548
+ async heartbeat() {
549
+ const s = this.requireStarted();
550
+ return await s.http.request("GET", "/sdk/heartbeat");
551
+ }
552
+ /**
553
+ * Wipe persisted identity + entitlement cache. Use on logout. The
554
+ * next pre-login session generates a fresh anonymousId and starts a
555
+ * new identity-graph entry.
556
+ */
557
+ reset() {
558
+ if (!this.state) return;
559
+ this.state.identity.reset();
560
+ this.state.entitlements.clear();
561
+ this.state.events.reset();
562
+ this.state.developerUserId = null;
563
+ }
564
+ /**
565
+ * Diagnostic: current state + queue stats. Useful for the dashboard's
566
+ * heartbeat row and debugging in dev.
567
+ *
568
+ * Returns a stable shape regardless of whether start() has been called —
569
+ * callers don't need to narrow on `started` to access `events` or
570
+ * `entitlements`. Pre-start values are sensible empties.
571
+ */
572
+ diagnostics() {
573
+ if (!this.state) {
574
+ return {
575
+ started: false,
576
+ anonymousId: null,
577
+ crossdeckCustomerId: null,
578
+ developerUserId: null,
579
+ sdkVersion: null,
580
+ baseUrl: null,
581
+ entitlements: { count: 0, lastUpdated: 0 },
582
+ events: {
583
+ buffered: 0,
584
+ dropped: 0,
585
+ inFlight: 0,
586
+ lastFlushAt: 0,
587
+ lastError: null
588
+ }
589
+ };
590
+ }
591
+ const s = this.state;
592
+ return {
593
+ started: true,
594
+ anonymousId: s.identity.anonymousId,
595
+ crossdeckCustomerId: s.identity.crossdeckCustomerId,
596
+ developerUserId: s.developerUserId,
597
+ sdkVersion: s.options.sdkVersion,
598
+ baseUrl: s.options.baseUrl,
599
+ entitlements: {
600
+ count: s.entitlements.list().length,
601
+ lastUpdated: s.entitlements.freshness
602
+ },
603
+ events: s.events.getStats()
604
+ };
605
+ }
606
+ // ---------- private helpers ----------
607
+ requireStarted() {
608
+ if (!this.state) {
609
+ throw new CrossdeckError({
610
+ type: "configuration_error",
611
+ code: "not_started",
612
+ message: "Call Crossdeck.start({ publicKey }) before any other method."
613
+ });
614
+ }
615
+ return this.state;
616
+ }
617
+ /**
618
+ * Build the identity query for /v1/entitlements. Priority:
619
+ * crossdeckCustomerId > developerUserId > anonymousId
620
+ * — matches the resolveCrossdeckCustomerId precedence on the server.
621
+ */
622
+ identityQueryParams() {
623
+ const s = this.requireStarted();
624
+ if (s.identity.crossdeckCustomerId) {
625
+ return { customerId: s.identity.crossdeckCustomerId };
626
+ }
627
+ if (s.developerUserId) return { userId: s.developerUserId };
628
+ return { anonymousId: s.identity.anonymousId };
629
+ }
630
+ /** Pick the right identity hint to embed on a queued event. */
631
+ identityHintForEvent() {
632
+ const s = this.requireStarted();
633
+ if (s.identity.crossdeckCustomerId) {
634
+ return { crossdeckCustomerId: s.identity.crossdeckCustomerId };
635
+ }
636
+ if (s.developerUserId) return { developerUserId: s.developerUserId };
637
+ return { anonymousId: s.identity.anonymousId };
638
+ }
639
+ mintEventId() {
640
+ const ts = Date.now().toString(36);
641
+ return `evt_${ts}${randomChars(8)}`;
642
+ }
643
+ };
644
+ var Crossdeck = new CrossdeckClient();
645
+ // Annotate the CommonJS export names for ESM import in node:
646
+ 0 && (module.exports = {
647
+ Crossdeck,
648
+ CrossdeckClient,
649
+ CrossdeckError,
650
+ DEFAULT_BASE_URL,
651
+ MemoryStorage,
652
+ SDK_NAME,
653
+ SDK_VERSION
654
+ });
655
+ //# sourceMappingURL=index.js.map