@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/CHANGELOG.md +62 -0
- package/README.md +82 -27
- package/dist/{index.js → index.cjs} +269 -17
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +152 -11
- package/dist/index.d.ts +152 -11
- package/dist/index.mjs +268 -16
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +1226 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.mts +68 -0
- package/dist/react.d.ts +68 -0
- package/dist/react.mjs +1200 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +17 -1
- package/dist/index.js.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -46,7 +46,7 @@ function typeMapForStatus(status) {
|
|
|
46
46
|
|
|
47
47
|
// src/http.ts
|
|
48
48
|
var SDK_NAME = "@cross-deck/web";
|
|
49
|
-
var SDK_VERSION = "0.
|
|
49
|
+
var SDK_VERSION = "0.3.0";
|
|
50
50
|
var DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
51
51
|
var HttpClient = class {
|
|
52
52
|
constructor(config) {
|
|
@@ -200,6 +200,7 @@ var EntitlementCache = class {
|
|
|
200
200
|
this.active = /* @__PURE__ */ new Set();
|
|
201
201
|
this.all = [];
|
|
202
202
|
this.lastUpdated = 0;
|
|
203
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
203
204
|
}
|
|
204
205
|
/** Sync read — true iff the entitlement key is currently active. */
|
|
205
206
|
isEntitled(key) {
|
|
@@ -217,20 +218,57 @@ var EntitlementCache = class {
|
|
|
217
218
|
* Replace the cache with a fresh server response. The backend already
|
|
218
219
|
* filters to active + env-matching, so we don't re-filter — just trust
|
|
219
220
|
* what we got.
|
|
221
|
+
*
|
|
222
|
+
* Fires listeners AFTER the mutation so each listener sees the new state.
|
|
220
223
|
*/
|
|
221
224
|
setFromList(entitlements) {
|
|
222
225
|
this.all = entitlements.slice();
|
|
223
226
|
this.active = new Set(entitlements.filter((e) => e.isActive).map((e) => e.key));
|
|
224
227
|
this.lastUpdated = Date.now();
|
|
228
|
+
this.notify();
|
|
225
229
|
}
|
|
226
230
|
/**
|
|
227
231
|
* Wipe — used on reset() (logout). The SDK forgets everything until
|
|
228
232
|
* the next identify + read.
|
|
233
|
+
*
|
|
234
|
+
* Fires listeners so React/SwiftUI/etc bindings re-render to the
|
|
235
|
+
* logged-out state immediately.
|
|
229
236
|
*/
|
|
230
237
|
clear() {
|
|
231
238
|
this.active.clear();
|
|
232
239
|
this.all = [];
|
|
233
240
|
this.lastUpdated = 0;
|
|
241
|
+
this.notify();
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Subscribe to cache mutations. Returns an unsubscribe function.
|
|
245
|
+
*
|
|
246
|
+
* The listener is invoked AFTER setFromList() or clear() with the
|
|
247
|
+
* current snapshot. Throwing inside a listener is non-fatal — the
|
|
248
|
+
* error is swallowed and subsequent listeners still run.
|
|
249
|
+
*
|
|
250
|
+
* Used by `@cross-deck/web/react`'s `useEntitlement` hook to
|
|
251
|
+
* trigger re-renders when entitlements change.
|
|
252
|
+
*/
|
|
253
|
+
subscribe(listener) {
|
|
254
|
+
this.listeners.add(listener);
|
|
255
|
+
let unsubscribed = false;
|
|
256
|
+
return () => {
|
|
257
|
+
if (unsubscribed) return;
|
|
258
|
+
unsubscribed = true;
|
|
259
|
+
this.listeners.delete(listener);
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
notify() {
|
|
263
|
+
if (this.listeners.size === 0) return;
|
|
264
|
+
const snapshot = this.all.slice();
|
|
265
|
+
const listenersSnapshot = [...this.listeners];
|
|
266
|
+
for (const listener of listenersSnapshot) {
|
|
267
|
+
try {
|
|
268
|
+
listener(snapshot);
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
}
|
|
234
272
|
}
|
|
235
273
|
};
|
|
236
274
|
|
|
@@ -245,6 +283,7 @@ var EventQueue = class {
|
|
|
245
283
|
this.lastFlushAt = 0;
|
|
246
284
|
this.lastError = null;
|
|
247
285
|
this.cancelTimer = null;
|
|
286
|
+
this.firstFlushFired = false;
|
|
248
287
|
}
|
|
249
288
|
enqueue(event) {
|
|
250
289
|
this.buffer.push(event);
|
|
@@ -271,12 +310,25 @@ var EventQueue = class {
|
|
|
271
310
|
const batch = this.buffer.splice(0);
|
|
272
311
|
this.inFlight += batch.length;
|
|
273
312
|
try {
|
|
313
|
+
const env = this.cfg.envelope();
|
|
274
314
|
const result = await this.cfg.http.request("POST", "/events", {
|
|
275
|
-
body: {
|
|
315
|
+
body: {
|
|
316
|
+
// NorthStar §13.1 batch envelope. The backend validates these
|
|
317
|
+
// against the API-key-resolved app and rejects mismatches loudly
|
|
318
|
+
// (env_mismatch).
|
|
319
|
+
appId: env.appId,
|
|
320
|
+
environment: env.environment,
|
|
321
|
+
sdk: env.sdk,
|
|
322
|
+
events: batch
|
|
323
|
+
}
|
|
276
324
|
});
|
|
277
325
|
this.lastFlushAt = Date.now();
|
|
278
326
|
this.lastError = null;
|
|
279
327
|
this.inFlight -= batch.length;
|
|
328
|
+
if (!this.firstFlushFired) {
|
|
329
|
+
this.firstFlushFired = true;
|
|
330
|
+
this.cfg.onFirstFlushSuccess?.();
|
|
331
|
+
}
|
|
280
332
|
return result;
|
|
281
333
|
} catch (err) {
|
|
282
334
|
this.buffer.unshift(...batch);
|
|
@@ -595,29 +647,104 @@ function mintSessionId() {
|
|
|
595
647
|
return `sess_${ts}${randomChars(10)}`;
|
|
596
648
|
}
|
|
597
649
|
|
|
650
|
+
// src/debug.ts
|
|
651
|
+
var SENSITIVE_KEY_PATTERNS = [
|
|
652
|
+
/^email$/i,
|
|
653
|
+
/^password$/i,
|
|
654
|
+
/^token$/i,
|
|
655
|
+
/^secret$/i,
|
|
656
|
+
/^card$/i,
|
|
657
|
+
/^phone$/i,
|
|
658
|
+
/password/i,
|
|
659
|
+
/credit_?card/i
|
|
660
|
+
];
|
|
661
|
+
function findSensitivePropertyKeys(properties) {
|
|
662
|
+
if (!properties) return [];
|
|
663
|
+
const hits = [];
|
|
664
|
+
for (const k of Object.keys(properties)) {
|
|
665
|
+
if (SENSITIVE_KEY_PATTERNS.some((re) => re.test(k))) hits.push(k);
|
|
666
|
+
}
|
|
667
|
+
return hits;
|
|
668
|
+
}
|
|
669
|
+
var ConsoleDebugLogger = class {
|
|
670
|
+
constructor() {
|
|
671
|
+
this.enabled = false;
|
|
672
|
+
this.seen = /* @__PURE__ */ new Set();
|
|
673
|
+
}
|
|
674
|
+
emit(signal, message, context) {
|
|
675
|
+
if (!this.enabled) return;
|
|
676
|
+
if (ONCE_SIGNALS.has(signal)) {
|
|
677
|
+
if (this.seen.has(signal)) return;
|
|
678
|
+
this.seen.add(signal);
|
|
679
|
+
}
|
|
680
|
+
const ctx = context ? ` ${safeJson(context)}` : "";
|
|
681
|
+
console.info(`[crossdeck:${signal}] ${message}${ctx}`);
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
var ONCE_SIGNALS = /* @__PURE__ */ new Set([
|
|
685
|
+
"sdk.configured",
|
|
686
|
+
"sdk.first_event_sent",
|
|
687
|
+
"sdk.environment_mismatch"
|
|
688
|
+
]);
|
|
689
|
+
function safeJson(obj) {
|
|
690
|
+
try {
|
|
691
|
+
return JSON.stringify(obj);
|
|
692
|
+
} catch {
|
|
693
|
+
return "[unserialisable context]";
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
598
697
|
// src/crossdeck.ts
|
|
599
698
|
var CrossdeckClient = class {
|
|
600
699
|
constructor() {
|
|
601
700
|
this.state = null;
|
|
602
701
|
}
|
|
603
702
|
/**
|
|
604
|
-
* Boot the SDK. Idempotent — calling
|
|
703
|
+
* Boot the SDK. Idempotent — calling init twice with the same options
|
|
605
704
|
* is a no-op; calling with different options replaces the previous
|
|
606
705
|
* configuration.
|
|
706
|
+
*
|
|
707
|
+
* NorthStar §11.1: signature is `Crossdeck.init({ appId, publicKey,
|
|
708
|
+
* environment })`. The trio is validated up-front so a typo'd key or a
|
|
709
|
+
* mismatched env fails fast at boot rather than at first event-flush.
|
|
607
710
|
*/
|
|
608
|
-
|
|
711
|
+
init(options) {
|
|
609
712
|
if (!options.publicKey || !options.publicKey.startsWith("cd_pub_")) {
|
|
610
713
|
throw new CrossdeckError({
|
|
611
714
|
type: "configuration_error",
|
|
612
715
|
code: "invalid_public_key",
|
|
613
|
-
message: "Crossdeck.
|
|
716
|
+
message: "Crossdeck.init requires a publishable key starting with cd_pub_."
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
if (!options.appId) {
|
|
720
|
+
throw new CrossdeckError({
|
|
721
|
+
type: "configuration_error",
|
|
722
|
+
code: "missing_app_id",
|
|
723
|
+
message: "Crossdeck.init requires an appId. Find yours in the Crossdeck dashboard."
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
if (options.environment !== "production" && options.environment !== "sandbox") {
|
|
727
|
+
throw new CrossdeckError({
|
|
728
|
+
type: "configuration_error",
|
|
729
|
+
code: "invalid_environment",
|
|
730
|
+
message: 'Crossdeck.init requires environment: "production" | "sandbox".'
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
const keyEnv = inferEnvFromKey(options.publicKey);
|
|
734
|
+
if (keyEnv && keyEnv !== options.environment) {
|
|
735
|
+
throw new CrossdeckError({
|
|
736
|
+
type: "configuration_error",
|
|
737
|
+
code: "environment_mismatch",
|
|
738
|
+
message: `Crossdeck.init: environment "${options.environment}" disagrees with key prefix (${keyEnv}). Reconcile the publishable key with the environment declaration.`
|
|
614
739
|
});
|
|
615
740
|
}
|
|
616
741
|
const storage = options.storage ?? detectDefaultStorage();
|
|
617
742
|
const persistIdentity = options.persistIdentity ?? true;
|
|
618
743
|
const autoTrack = resolveAutoTrack(options.autoTrack);
|
|
619
744
|
const opts = {
|
|
745
|
+
appId: options.appId,
|
|
620
746
|
publicKey: options.publicKey,
|
|
747
|
+
environment: options.environment,
|
|
621
748
|
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
|
|
622
749
|
persistIdentity,
|
|
623
750
|
storagePrefix: options.storagePrefix ?? "crossdeck:",
|
|
@@ -628,6 +755,8 @@ var CrossdeckClient = class {
|
|
|
628
755
|
autoTrack,
|
|
629
756
|
appVersion: options.appVersion ?? null
|
|
630
757
|
};
|
|
758
|
+
const debug = new ConsoleDebugLogger();
|
|
759
|
+
debug.enabled = options.debug === true;
|
|
631
760
|
const http = new HttpClient({
|
|
632
761
|
publicKey: opts.publicKey,
|
|
633
762
|
baseUrl: opts.baseUrl,
|
|
@@ -639,7 +768,19 @@ var CrossdeckClient = class {
|
|
|
639
768
|
const events = new EventQueue({
|
|
640
769
|
http,
|
|
641
770
|
batchSize: opts.eventFlushBatchSize,
|
|
642
|
-
intervalMs: opts.eventFlushIntervalMs
|
|
771
|
+
intervalMs: opts.eventFlushIntervalMs,
|
|
772
|
+
envelope: () => ({
|
|
773
|
+
appId: opts.appId,
|
|
774
|
+
environment: opts.environment,
|
|
775
|
+
sdk: { name: SDK_NAME, version: opts.sdkVersion }
|
|
776
|
+
}),
|
|
777
|
+
onFirstFlushSuccess: () => {
|
|
778
|
+
debug.emit(
|
|
779
|
+
"sdk.first_event_sent",
|
|
780
|
+
"First telemetry event received. View it in Live Events.",
|
|
781
|
+
{ appId: opts.appId, environment: opts.environment }
|
|
782
|
+
);
|
|
783
|
+
}
|
|
643
784
|
});
|
|
644
785
|
const deviceInfo = autoTrack.deviceInfo ? collectDeviceInfo({ appVersion: opts.appVersion ?? void 0 }) : opts.appVersion ? { appVersion: opts.appVersion } : {};
|
|
645
786
|
this.state = {
|
|
@@ -650,8 +791,14 @@ var CrossdeckClient = class {
|
|
|
650
791
|
autoTracker: null,
|
|
651
792
|
deviceInfo,
|
|
652
793
|
options: opts,
|
|
794
|
+
debug,
|
|
653
795
|
developerUserId: null
|
|
654
796
|
};
|
|
797
|
+
debug.emit("sdk.configured", `Crossdeck connected to ${opts.appId} in ${opts.environment} mode.`, {
|
|
798
|
+
appId: opts.appId,
|
|
799
|
+
environment: opts.environment,
|
|
800
|
+
sdkVersion: opts.sdkVersion
|
|
801
|
+
});
|
|
655
802
|
if (autoTrack.sessions || autoTrack.pageViews) {
|
|
656
803
|
const tracker = new AutoTracker(
|
|
657
804
|
autoTrack,
|
|
@@ -664,6 +811,19 @@ var CrossdeckClient = class {
|
|
|
664
811
|
void this.heartbeat().catch(() => void 0);
|
|
665
812
|
}
|
|
666
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* @deprecated Use `init()` instead. NorthStar §4 standardised the
|
|
816
|
+
* lifecycle method name across SDKs as `init` (formerly `start` /
|
|
817
|
+
* `configure`). `start` will be removed in a future major version.
|
|
818
|
+
*/
|
|
819
|
+
start(options) {
|
|
820
|
+
if (typeof console !== "undefined") {
|
|
821
|
+
console.warn(
|
|
822
|
+
"[crossdeck] Crossdeck.start() is deprecated \u2014 use Crossdeck.init() instead. The signature is the same."
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
this.init(options);
|
|
826
|
+
}
|
|
667
827
|
/**
|
|
668
828
|
* Link the anonymous device to a developer-supplied user ID. Cache
|
|
669
829
|
* the resolved Crossdeck customer for follow-up calls.
|
|
@@ -716,10 +876,41 @@ var CrossdeckClient = class {
|
|
|
716
876
|
const s = this.requireStarted();
|
|
717
877
|
return s.entitlements.list();
|
|
718
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* Subscribe to entitlement-cache changes. Returns an unsubscribe fn.
|
|
881
|
+
*
|
|
882
|
+
* The listener is invoked AFTER the cache mutates — once after a
|
|
883
|
+
* successful `getEntitlements()` warms it, again after `syncPurchases()`
|
|
884
|
+
* delivers fresh entitlements, and once on `reset()` to fire the
|
|
885
|
+
* empty-cache state for logout flows.
|
|
886
|
+
*
|
|
887
|
+
* It is NOT invoked synchronously on subscribe. Callers that need
|
|
888
|
+
* the current state should read it via `isEntitled()` / `listEntitlements()`
|
|
889
|
+
* inline; the listener fires only on FUTURE changes.
|
|
890
|
+
*
|
|
891
|
+
* This is the foundation of the `useEntitlement` React hook in
|
|
892
|
+
* `@cross-deck/web/react` — without it, React (or SwiftUI / Compose
|
|
893
|
+
* / Vue) would have no way to re-render when entitlements arrive
|
|
894
|
+
* asynchronously after init. The naive pattern of calling
|
|
895
|
+
* `Crossdeck.isEntitled("pro")` directly inside a render path
|
|
896
|
+
* shows the empty-cache result forever; binding the result to
|
|
897
|
+
* component state via `onEntitlementsChange` is the correct
|
|
898
|
+
* pattern.
|
|
899
|
+
*
|
|
900
|
+
* Idempotent unsubscribe — calling the returned function multiple
|
|
901
|
+
* times is safe.
|
|
902
|
+
*
|
|
903
|
+
* Listener errors are swallowed (a buggy listener can't crash the
|
|
904
|
+
* SDK or other listeners).
|
|
905
|
+
*/
|
|
906
|
+
onEntitlementsChange(listener) {
|
|
907
|
+
const s = this.requireStarted();
|
|
908
|
+
return s.entitlements.subscribe(listener);
|
|
909
|
+
}
|
|
719
910
|
/**
|
|
720
911
|
* Queue a telemetry event. Returns immediately — the network round-
|
|
721
912
|
* trip happens in the background. To flush before the page unloads,
|
|
722
|
-
* call
|
|
913
|
+
* call flush().
|
|
723
914
|
*/
|
|
724
915
|
track(name, properties) {
|
|
725
916
|
const s = this.requireStarted();
|
|
@@ -730,6 +921,22 @@ var CrossdeckClient = class {
|
|
|
730
921
|
message: "track(name) requires a non-empty name."
|
|
731
922
|
});
|
|
732
923
|
}
|
|
924
|
+
if (s.debug.enabled && properties) {
|
|
925
|
+
const flagged = findSensitivePropertyKeys(properties);
|
|
926
|
+
if (flagged.length > 0) {
|
|
927
|
+
s.debug.emit(
|
|
928
|
+
"sdk.sensitive_property_warning",
|
|
929
|
+
`Event "${name}" has potentially sensitive property names: ${flagged.join(", ")}. Crossdeck is privacy-first \u2014 avoid sending PII unless intentional.`,
|
|
930
|
+
{ eventName: name, flagged }
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (s.debug.enabled && !s.developerUserId && !s.identity.crossdeckCustomerId) {
|
|
935
|
+
s.debug.emit(
|
|
936
|
+
"sdk.no_identity",
|
|
937
|
+
"Using anonymous user until identify(userId) is called."
|
|
938
|
+
);
|
|
939
|
+
}
|
|
733
940
|
const enriched = { ...s.deviceInfo };
|
|
734
941
|
const sessionId = s.autoTracker?.currentSessionId;
|
|
735
942
|
if (sessionId) enriched.sessionId = sessionId;
|
|
@@ -743,28 +950,68 @@ var CrossdeckClient = class {
|
|
|
743
950
|
Object.assign(event, this.identityHintForEvent());
|
|
744
951
|
s.events.enqueue(event);
|
|
745
952
|
}
|
|
746
|
-
/**
|
|
747
|
-
|
|
953
|
+
/**
|
|
954
|
+
* Force-flush queued events. Useful to call from page-unload handlers.
|
|
955
|
+
*
|
|
956
|
+
* NorthStar §4: standard method name across all Crossdeck SDKs.
|
|
957
|
+
*/
|
|
958
|
+
async flush() {
|
|
748
959
|
const s = this.requireStarted();
|
|
749
960
|
await s.events.flush();
|
|
750
961
|
}
|
|
751
|
-
/**
|
|
752
|
-
async
|
|
962
|
+
/** @deprecated Use `flush()` instead. NorthStar §4 standardised the name. */
|
|
963
|
+
async flushEvents() {
|
|
964
|
+
return this.flush();
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Forward purchase evidence to the backend for verification + entitlement
|
|
968
|
+
* projection. NorthStar §4 + §13 canonical name.
|
|
969
|
+
*
|
|
970
|
+
* Today the web SDK only supports Apple StoreKit 2 forwarding (web apps
|
|
971
|
+
* that sit alongside an iOS app). Stripe doesn't need this method —
|
|
972
|
+
* Stripe webhooks deliver evidence server-side without a client round-trip.
|
|
973
|
+
*/
|
|
974
|
+
async syncPurchases(input) {
|
|
753
975
|
const s = this.requireStarted();
|
|
754
976
|
if (!input.signedTransactionInfo) {
|
|
755
977
|
throw new CrossdeckError({
|
|
756
978
|
type: "invalid_request_error",
|
|
757
979
|
code: "missing_signed_transaction_info",
|
|
758
|
-
message: "
|
|
980
|
+
message: "syncPurchases requires a signedTransactionInfo string from StoreKit 2."
|
|
759
981
|
});
|
|
760
982
|
}
|
|
761
|
-
const result = await s.http.request("POST", "/purchases", {
|
|
762
|
-
body: { rail: "apple", ...input }
|
|
983
|
+
const result = await s.http.request("POST", "/purchases/sync", {
|
|
984
|
+
body: { rail: input.rail ?? "apple", ...input }
|
|
763
985
|
});
|
|
764
986
|
s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);
|
|
765
987
|
s.entitlements.setFromList(result.entitlements);
|
|
988
|
+
s.debug.emit(
|
|
989
|
+
"sdk.purchase_evidence_sent",
|
|
990
|
+
"StoreKit transaction forwarded. Waiting for backend verification.",
|
|
991
|
+
{ rail: input.rail ?? "apple" }
|
|
992
|
+
);
|
|
766
993
|
return result;
|
|
767
994
|
}
|
|
995
|
+
/** @deprecated Use `syncPurchases()` instead. NorthStar §4 standardised the name. */
|
|
996
|
+
async purchaseApple(input) {
|
|
997
|
+
return this.syncPurchases({ rail: "apple", ...input });
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Toggle verbose diagnostic logging — NorthStar §16. When enabled, the
|
|
1001
|
+
* SDK emits a fixed vocabulary of debug signals to console.info that the
|
|
1002
|
+
* dashboard's onboarding checklist can also surface as live events.
|
|
1003
|
+
*/
|
|
1004
|
+
setDebugMode(enabled) {
|
|
1005
|
+
const s = this.requireStarted();
|
|
1006
|
+
s.debug.enabled = enabled;
|
|
1007
|
+
if (enabled) {
|
|
1008
|
+
s.debug.emit(
|
|
1009
|
+
"sdk.configured",
|
|
1010
|
+
`Debug mode enabled for ${s.options.appId} in ${s.options.environment} mode.`,
|
|
1011
|
+
{ appId: s.options.appId, environment: s.options.environment }
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
768
1015
|
/**
|
|
769
1016
|
* Send the boot heartbeat. Called automatically by start() unless
|
|
770
1017
|
* autoHeartbeat:false. Safe to call manually as a "we're still here" ping.
|
|
@@ -841,8 +1088,8 @@ var CrossdeckClient = class {
|
|
|
841
1088
|
if (!this.state) {
|
|
842
1089
|
throw new CrossdeckError({
|
|
843
1090
|
type: "configuration_error",
|
|
844
|
-
code: "
|
|
845
|
-
message: "Call Crossdeck.
|
|
1091
|
+
code: "not_initialized",
|
|
1092
|
+
message: "Call Crossdeck.init({ appId, publicKey, environment }) before any other method."
|
|
846
1093
|
});
|
|
847
1094
|
}
|
|
848
1095
|
return this.state;
|
|
@@ -875,6 +1122,11 @@ var CrossdeckClient = class {
|
|
|
875
1122
|
}
|
|
876
1123
|
};
|
|
877
1124
|
var Crossdeck = new CrossdeckClient();
|
|
1125
|
+
function inferEnvFromKey(publicKey) {
|
|
1126
|
+
if (publicKey.startsWith("cd_pub_test_")) return "sandbox";
|
|
1127
|
+
if (publicKey.startsWith("cd_pub_live_")) return "production";
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
878
1130
|
function resolveAutoTrack(input) {
|
|
879
1131
|
if (input === false) {
|
|
880
1132
|
return { sessions: false, pageViews: false, deviceInfo: false };
|