@cross-deck/node 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/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +238 -0
- package/dist/index.cjs +595 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +187 -0
- package/dist/index.d.ts +187 -0
- package/dist/index.mjs +563 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@cross-deck/node` will be documented here. The
|
|
4
|
+
format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — 2026-05-12
|
|
8
|
+
|
|
9
|
+
Initial server SDK release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Separate `@cross-deck/node` package with no browser assumptions.
|
|
14
|
+
- `CrossdeckServer` constructor with secret-key validation.
|
|
15
|
+
- Secret-key HTTP transport with typed `CrossdeckError` handling.
|
|
16
|
+
- Web-parity sanitisation for traits and event properties, plus a transport
|
|
17
|
+
backstop that converts serialization failures into stable `CrossdeckError`s.
|
|
18
|
+
- `identify()` / `aliasIdentity()` for server-side identity linking.
|
|
19
|
+
- `forget()` for server-side GDPR/CCPA deletion requests.
|
|
20
|
+
- `getEntitlements()` by `customerId`, `userId`, or `anonymousId`.
|
|
21
|
+
- `getCustomerEntitlements(customerId)` server-only direct lookup route.
|
|
22
|
+
- `track()` and `ingest()` for explicit server-side event ingest.
|
|
23
|
+
- `syncPurchases()` for Apple signed purchase forwarding.
|
|
24
|
+
- `grantEntitlement()` and `revokeEntitlement()` server-side manual overrides.
|
|
25
|
+
- `getAuditEntry()` for server-side audit-log reads.
|
|
26
|
+
- Dual ESM/CJS build.
|
|
27
|
+
- Strict TypeScript + Vitest coverage for transport and public method routing.
|
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,238 @@
|
|
|
1
|
+
# @cross-deck/node
|
|
2
|
+
|
|
3
|
+
The Crossdeck server SDK for Node.js.
|
|
4
|
+
|
|
5
|
+
This is the **secret-key** SDK: server-only, no browser assumptions, no
|
|
6
|
+
auto-tracking, no local state. It wraps the real HTTP surface for
|
|
7
|
+
entitlements, identity aliasing, event ingest, purchase forwarding, manual
|
|
8
|
+
entitlement overrides, and audit reads.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @cross-deck/node
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { CrossdeckServer } from "@cross-deck/node";
|
|
18
|
+
|
|
19
|
+
const crossdeck = new CrossdeckServer({
|
|
20
|
+
secretKey: process.env.CROSSDECK_SECRET_KEY!,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const entitlements = await crossdeck.getEntitlements({ userId: "user_847" });
|
|
24
|
+
|
|
25
|
+
await crossdeck.track({
|
|
26
|
+
name: "invoice.retry_started",
|
|
27
|
+
developerUserId: "user_847",
|
|
28
|
+
properties: { invoiceId: "inv_123" },
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## What this SDK is for
|
|
33
|
+
|
|
34
|
+
- **Server-side entitlement reads.** Query by `customerId`, `userId`, or `anonymousId`.
|
|
35
|
+
- **Identity graph writes.** Alias a known `anonymousId` to your stable `userId`.
|
|
36
|
+
- **Telemetry from jobs and backends.** Send events from cron jobs, webhooks, workers, and admin tools.
|
|
37
|
+
- **Fast purchase forwarding.** Push signed Apple purchase evidence directly.
|
|
38
|
+
- **Manual entitlement controls.** Grant or revoke an entitlement with a reason.
|
|
39
|
+
- **Audit lookup.** Read one audit-log entry by event ID.
|
|
40
|
+
|
|
41
|
+
## What this SDK is not
|
|
42
|
+
|
|
43
|
+
- Not for browsers.
|
|
44
|
+
- Not a wrapper around the web SDK.
|
|
45
|
+
- Not a stateful singleton that keeps identity or a queue in memory.
|
|
46
|
+
- Not an auto-tracker. Every server call is explicit.
|
|
47
|
+
|
|
48
|
+
## Constructing the client
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { CrossdeckServer } from "@cross-deck/node";
|
|
52
|
+
|
|
53
|
+
const crossdeck = new CrossdeckServer({
|
|
54
|
+
secretKey: process.env.CROSSDECK_SECRET_KEY!,
|
|
55
|
+
baseUrl: "https://api.cross-deck.com/v1", // optional
|
|
56
|
+
timeoutMs: 15_000, // optional
|
|
57
|
+
appId: "app_web_xxx", // optional informational event envelope field
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`secretKey` must start with `cd_sk_`. The constructor throws immediately on
|
|
62
|
+
an invalid key prefix so a server misconfiguration fails at boot, not under
|
|
63
|
+
load.
|
|
64
|
+
|
|
65
|
+
## Identity
|
|
66
|
+
|
|
67
|
+
### `await crossdeck.identify(userId, anonymousId, options?)`
|
|
68
|
+
|
|
69
|
+
Alias a pre-login `anonymousId` to your stable user ID. This is the same
|
|
70
|
+
identity graph the web SDK uses, just called explicitly from your backend.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
await crossdeck.identify("user_847", "anon_123", {
|
|
74
|
+
email: "wes@example.com",
|
|
75
|
+
traits: { plan: "pro", region: "za" },
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`identify()` is a convenience alias for `aliasIdentity(...)`.
|
|
80
|
+
|
|
81
|
+
Traits are sanitised before send with the same rules as `@cross-deck/web`:
|
|
82
|
+
`BigInt` becomes a string, circular refs become `"[circular]"`, `Map`/`Set`
|
|
83
|
+
normalise to JSON-friendly shapes, and functions/symbols/`undefined` are dropped.
|
|
84
|
+
|
|
85
|
+
### `await crossdeck.forget(hints)`
|
|
86
|
+
|
|
87
|
+
Queue GDPR/CCPA deletion by `customerId`, `userId`, or `anonymousId`.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
await crossdeck.forget({ customerId: "cdcust_123" });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Entitlements
|
|
94
|
+
|
|
95
|
+
### `await crossdeck.getEntitlements(hints)`
|
|
96
|
+
|
|
97
|
+
Read entitlements by any supported identity hint.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const result = await crossdeck.getEntitlements({ userId: "user_847" });
|
|
101
|
+
console.log(result.data.map((e) => e.key));
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `await crossdeck.getCustomerEntitlements(customerId)`
|
|
105
|
+
|
|
106
|
+
Server-only direct lookup by canonical Crossdeck customer ID.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const result = await crossdeck.getCustomerEntitlements("cdcust_123");
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `await crossdeck.grantEntitlement(input)`
|
|
113
|
+
|
|
114
|
+
Manually grant an entitlement.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
await crossdeck.grantEntitlement({
|
|
118
|
+
customerId: "cdcust_123",
|
|
119
|
+
entitlementKey: "pro",
|
|
120
|
+
duration: "P30D",
|
|
121
|
+
reason: "Support recovery after billing incident",
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `await crossdeck.revokeEntitlement(input)`
|
|
126
|
+
|
|
127
|
+
Manually revoke an entitlement.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
await crossdeck.revokeEntitlement({
|
|
131
|
+
customerId: "cdcust_123",
|
|
132
|
+
entitlementKey: "pro",
|
|
133
|
+
reason: "Chargeback",
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Events
|
|
138
|
+
|
|
139
|
+
### `await crossdeck.track(event)`
|
|
140
|
+
|
|
141
|
+
Send one event immediately.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
await crossdeck.track({
|
|
145
|
+
name: "support.refund_issued",
|
|
146
|
+
crossdeckCustomerId: "cdcust_123",
|
|
147
|
+
properties: { ticketId: "ticket_987" },
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Identity is required on every event. Provide at least one of:
|
|
152
|
+
|
|
153
|
+
- `developerUserId`
|
|
154
|
+
- `anonymousId`
|
|
155
|
+
- `crossdeckCustomerId`
|
|
156
|
+
|
|
157
|
+
### `await crossdeck.ingest(events)`
|
|
158
|
+
|
|
159
|
+
Send a batch in one call.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
await crossdeck.ingest([
|
|
163
|
+
{
|
|
164
|
+
name: "job.started",
|
|
165
|
+
developerUserId: "user_847",
|
|
166
|
+
properties: { job: "daily-mrr-reconcile" },
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "job.completed",
|
|
170
|
+
developerUserId: "user_847",
|
|
171
|
+
properties: { job: "daily-mrr-reconcile", durationMs: 842 },
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The SDK auto-mints `eventId` and `timestamp` if you omit them.
|
|
177
|
+
|
|
178
|
+
Event `properties` are sanitised with the same contract as the web SDK before
|
|
179
|
+
they hit the wire, so one bad backend-shaped object cannot crash request
|
|
180
|
+
serialization.
|
|
181
|
+
|
|
182
|
+
## Purchases
|
|
183
|
+
|
|
184
|
+
### `await crossdeck.syncPurchases(input)`
|
|
185
|
+
|
|
186
|
+
Forward Apple signed purchase evidence to Crossdeck.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
await crossdeck.syncPurchases({
|
|
190
|
+
signedTransactionInfo: transactionJws,
|
|
191
|
+
signedRenewalInfo: renewalJws,
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Audit
|
|
196
|
+
|
|
197
|
+
### `await crossdeck.getAuditEntry(eventId)`
|
|
198
|
+
|
|
199
|
+
Read one audit row by event ID.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
const audit = await crossdeck.getAuditEntry("srv_grant_123");
|
|
203
|
+
console.log(audit.decision, audit.reason);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Errors
|
|
207
|
+
|
|
208
|
+
Every non-2xx response is normalised to `CrossdeckError`:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { CrossdeckError } from "@cross-deck/node";
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
await crossdeck.getEntitlements({ userId: "user_847" });
|
|
215
|
+
} catch (err) {
|
|
216
|
+
if (err instanceof CrossdeckError) {
|
|
217
|
+
console.error(err.type, err.code, err.requestId);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The error fields mirror the backend envelope:
|
|
223
|
+
|
|
224
|
+
- `type`
|
|
225
|
+
- `code`
|
|
226
|
+
- `message`
|
|
227
|
+
- `requestId`
|
|
228
|
+
- `status`
|
|
229
|
+
- `retryAfterMs`
|
|
230
|
+
|
|
231
|
+
## Node version
|
|
232
|
+
|
|
233
|
+
Node 18+ is required. The SDK uses the platform `fetch` implementation and
|
|
234
|
+
does not ship an HTTP dependency.
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
MIT
|