@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/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/index.d.mts +306 -0
- package/dist/index.d.ts +306 -0
- package/dist/index.js +655 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +622 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Crossdeck
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# @cross-deck/web
|
|
2
|
+
|
|
3
|
+
The Crossdeck SDK for browsers and Node.js. One package, one mental model, every Crossdeck client API in five lines of setup.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @cross-deck/web
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Crossdeck } from "@cross-deck/web";
|
|
13
|
+
|
|
14
|
+
// 1. Boot once at app start
|
|
15
|
+
Crossdeck.start({ publicKey: "cd_pub_live_…" });
|
|
16
|
+
|
|
17
|
+
// 2. After the user logs in, link the device to your user ID
|
|
18
|
+
await Crossdeck.identify("user_847");
|
|
19
|
+
|
|
20
|
+
// 3. Read entitlements (warms the local cache)
|
|
21
|
+
await Crossdeck.getEntitlements();
|
|
22
|
+
|
|
23
|
+
// 4. Sync access checks (microsecond reads from cache)
|
|
24
|
+
if (Crossdeck.isEntitled("pro")) {
|
|
25
|
+
showProFeatures();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 5. Telemetry — fire-and-forget, batched in the background
|
|
29
|
+
Crossdeck.track("paywall_viewed", { variant: "v3" });
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
That's the full happy path.
|
|
33
|
+
|
|
34
|
+
## What it does
|
|
35
|
+
|
|
36
|
+
- **One identity for every device + user.** Pre-login events get an `anonymousId`. After login, `identify()` links them to your user ID through Crossdeck's identity graph. The SDK persists both so subsequent app launches resume where you left off.
|
|
37
|
+
- **Synchronous entitlement reads.** `getEntitlements()` populates a local cache. `isEntitled("pro")` is a Set lookup — no network call, no waiting.
|
|
38
|
+
- **Batched telemetry.** `track()` queues events in memory; the SDK flushes every 5 seconds (configurable) or when the buffer hits 20 events. Network failures re-queue the batch — events aren't lost on a flaky connection.
|
|
39
|
+
- **Boot heartbeat.** On `start()` the SDK pings `/v1/sdk/heartbeat` so the dashboard's Apps page can show you "last seen" per install. Disable with `autoHeartbeat: false`.
|
|
40
|
+
- **Stripe-style errors.** Every async method throws `CrossdeckError` with `type`, `code`, `requestId`, and `status` — same shape as Stripe's SDKs, so generic error handlers transfer.
|
|
41
|
+
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
### `Crossdeck.start(options)`
|
|
45
|
+
|
|
46
|
+
Boot the client. Idempotent — calling twice with the same options is fine.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
Crossdeck.start({
|
|
50
|
+
publicKey: "cd_pub_live_…", // required
|
|
51
|
+
baseUrl: "https://api.cross-deck.com/v1", // override for self-host or emulator
|
|
52
|
+
autoHeartbeat: true, // default; set false for high-frequency boots
|
|
53
|
+
eventFlushBatchSize: 20, // default
|
|
54
|
+
eventFlushIntervalMs: 5_000, // default
|
|
55
|
+
storage: customStorage, // override the persistence adapter
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The publishable key is safe to ship in client code. Crossdeck enforces origin allowlists (web), bundle-ID binding (mobile), and rate limits at the edge — see [docs/api-keys](https://cross-deck.com/docs/api-keys/) for the full security model.
|
|
60
|
+
|
|
61
|
+
### `await Crossdeck.identify(userId, options?)`
|
|
62
|
+
|
|
63
|
+
Link the anonymous device to a developer-supplied user ID. Persists the resolved Crossdeck customer ID for follow-up reads. Returns the `AliasResult`:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
{
|
|
67
|
+
object: "alias_result",
|
|
68
|
+
crossdeckCustomerId: "cdcust_…",
|
|
69
|
+
linked: [
|
|
70
|
+
{ type: "developer", id: "user_847" },
|
|
71
|
+
{ type: "anonymous", id: "anon_…" },
|
|
72
|
+
],
|
|
73
|
+
mergePending: false, // see "Merge candidates" below
|
|
74
|
+
env: "production",
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If `mergePending: true`, both identifiers already pointed at different customers. Crossdeck **never silently merges** — your dashboard's operations queue surfaces the merge for human confirmation.
|
|
79
|
+
|
|
80
|
+
### `await Crossdeck.getEntitlements()`
|
|
81
|
+
|
|
82
|
+
Fetch the current customer's active entitlements. Returns an array of `PublicEntitlement` and updates the local cache.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const ents = await Crossdeck.getEntitlements();
|
|
86
|
+
// [{ object: "entitlement", key: "pro", isActive: true, validUntil: 1717891200, source: {...}, updatedAt: ... }]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `Crossdeck.isEntitled(key) → boolean`
|
|
90
|
+
|
|
91
|
+
Synchronous read from the local cache. Returns `false` until you've called `getEntitlements()` once (or `purchaseApple()` resolved). After that, it's a Set lookup — call as often as you want.
|
|
92
|
+
|
|
93
|
+
### `Crossdeck.track(name, properties?)`
|
|
94
|
+
|
|
95
|
+
Queue a telemetry event. Returns immediately. Events flush in batches; force a flush with `flushEvents()`:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
Crossdeck.track("checkout_started", { product: "annual_pro" });
|
|
99
|
+
// …later, e.g. before page unload:
|
|
100
|
+
window.addEventListener("beforeunload", () => {
|
|
101
|
+
void Crossdeck.flushEvents();
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Event names match `[A-Za-z0-9_.\-:]+`, max 128 chars. Properties are JSON-serialisable, max 8 KB per event after JSON encoding.
|
|
106
|
+
|
|
107
|
+
### `await Crossdeck.purchaseApple(input)`
|
|
108
|
+
|
|
109
|
+
Forward a StoreKit 2 transaction directly to Crossdeck for verification — closes the purchase-to-entitled latency from seconds to milliseconds (faster than waiting for the App Store webhook).
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
await Crossdeck.purchaseApple({
|
|
113
|
+
signedTransactionInfo: transaction.jsonRepresentation, // from StoreKit 2
|
|
114
|
+
signedRenewalInfo: subscription.signedRenewalInfo, // optional
|
|
115
|
+
appAccountToken: "uuid-…", // optional
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Stripe and Google purchases are verified via webhooks (Stripe Connect platform endpoint, Google Play RTDN) — there's no SDK-side push for those.
|
|
120
|
+
|
|
121
|
+
### `await Crossdeck.heartbeat()`
|
|
122
|
+
|
|
123
|
+
Manually send a heartbeat. Called automatically by `start()` unless `autoHeartbeat: false`. Returns the readiness summary the dashboard uses to display SDK installation status.
|
|
124
|
+
|
|
125
|
+
### `Crossdeck.reset()`
|
|
126
|
+
|
|
127
|
+
Wipe persisted identity + entitlement cache + queued events. Call on logout. The next session generates a fresh `anonymousId` and starts a clean identity-graph entry.
|
|
128
|
+
|
|
129
|
+
### `Crossdeck.flushEvents()`
|
|
130
|
+
|
|
131
|
+
Force-flush the in-memory event queue. Useful before page unload or when shutting down a script.
|
|
132
|
+
|
|
133
|
+
### `Crossdeck.diagnostics()`
|
|
134
|
+
|
|
135
|
+
Diagnostic snapshot — useful for development consoles and bug reports:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
{
|
|
139
|
+
started: true,
|
|
140
|
+
anonymousId: "anon_…",
|
|
141
|
+
crossdeckCustomerId: "cdcust_…" | null,
|
|
142
|
+
developerUserId: "user_…" | null,
|
|
143
|
+
sdkVersion: "0.1.0",
|
|
144
|
+
baseUrl: "https://api.cross-deck.com/v1",
|
|
145
|
+
entitlements: { count: 2, lastUpdated: 1717891200000 },
|
|
146
|
+
events: { buffered: 0, dropped: 0, inFlight: 0, lastFlushAt: 1717891200000, lastError: null },
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Errors
|
|
151
|
+
|
|
152
|
+
Every async method can throw `CrossdeckError`. Synchronous methods throw on configuration mistakes (calling before `start()`, invalid key prefix).
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { CrossdeckError } from "@cross-deck/web";
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
await Crossdeck.identify("user_847");
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (err instanceof CrossdeckError) {
|
|
161
|
+
console.error(err.type, err.code, err.requestId);
|
|
162
|
+
if (err.code === "invalid_api_key") {
|
|
163
|
+
// ...
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Error fields:
|
|
170
|
+
|
|
171
|
+
| Field | What it is |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `type` | One of `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `internal_error`, `network_error`, `configuration_error`. Same vocabulary the backend uses. |
|
|
174
|
+
| `code` | Specific machine-readable code, e.g. `invalid_api_key`, `origin_not_allowed`, `rate_limited`, `network_error`. |
|
|
175
|
+
| `message` | Human-readable description. |
|
|
176
|
+
| `requestId` | Server-issued ID. Echo it in support tickets — we'll have a one-line log entry that explains the decision. |
|
|
177
|
+
| `status` | HTTP status code if the error came from an API response. |
|
|
178
|
+
|
|
179
|
+
## Node usage
|
|
180
|
+
|
|
181
|
+
The SDK works the same way in Node 18+:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { Crossdeck, MemoryStorage } from "@cross-deck/web";
|
|
185
|
+
|
|
186
|
+
Crossdeck.start({
|
|
187
|
+
publicKey: process.env.CROSSDECK_PUBLIC_KEY!,
|
|
188
|
+
storage: new MemoryStorage(), // session-only, no localStorage
|
|
189
|
+
autoHeartbeat: false, // skip the boot ping in scripts
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
For server-side flows where you need to read any customer's state (not just the caller's), use the **secret key** path — that ships when the `/v1/server/*` endpoints land.
|
|
194
|
+
|
|
195
|
+
## Security
|
|
196
|
+
|
|
197
|
+
Publishable keys aren't secrets — they're identifiers, safe to ship in client code. See [`docs/api-keys`](https://cross-deck.com/docs/api-keys/) for the full security model: how keys are stored, how requests are verified, what's enforced where. Highlights:
|
|
198
|
+
|
|
199
|
+
- **Origin allowlists** on web keys (configured in the Crossdeck dashboard) reject requests from unauthorised origins.
|
|
200
|
+
- **Tenant isolation** — a leaked key can read its own project's customer data only, never another tenant's.
|
|
201
|
+
- **Env partition** — a `cd_pub_live_…` key cannot read `cd_pub_test_…` data and vice versa.
|
|
202
|
+
- **No raw payment credentials** ever pass through this SDK or sit in a Crossdeck database. Apple `.p8`s, Stripe secret keys, Google service-account JSON — all in Google Cloud Secret Manager, runtime-only access from the Crossdeck backend.
|
|
203
|
+
|
|
204
|
+
## Versioning
|
|
205
|
+
|
|
206
|
+
This package follows [semver](https://semver.org). The wire-format types (`PublicEntitlement`, `AliasResult`, etc.) are duplicated from the backend's `v1-types.ts` — they're the stable contract, not a shared module. Breaking changes to those types only ship in major versions.
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
MIT.
|
package/dist/index.d.mts
ADDED
|
@@ -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 };
|