@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/dist/index.mjs ADDED
@@ -0,0 +1,563 @@
1
+ // src/crossdeck-server.ts
2
+ import { randomUUID } from "crypto";
3
+
4
+ // src/errors.ts
5
+ var CrossdeckError = class _CrossdeckError extends Error {
6
+ type;
7
+ code;
8
+ requestId;
9
+ status;
10
+ retryAfterMs;
11
+ constructor(payload) {
12
+ super(payload.message);
13
+ this.name = "CrossdeckError";
14
+ this.type = payload.type;
15
+ this.code = payload.code;
16
+ this.requestId = payload.requestId;
17
+ this.status = payload.status;
18
+ this.retryAfterMs = payload.retryAfterMs;
19
+ Object.setPrototypeOf(this, _CrossdeckError.prototype);
20
+ }
21
+ };
22
+ async function crossdeckErrorFromResponse(res) {
23
+ const requestId = res.headers.get("x-request-id") ?? void 0;
24
+ const retryAfterMs = parseRetryAfterHeader(res.headers.get("retry-after"));
25
+ let body;
26
+ try {
27
+ body = await res.json();
28
+ } catch {
29
+ body = null;
30
+ }
31
+ const envelope = body?.error;
32
+ if (envelope && typeof envelope.type === "string" && typeof envelope.code === "string") {
33
+ return new CrossdeckError({
34
+ type: envelope.type,
35
+ code: envelope.code,
36
+ message: envelope.message ?? `HTTP ${res.status}`,
37
+ requestId: envelope.request_id ?? requestId,
38
+ status: res.status,
39
+ retryAfterMs
40
+ });
41
+ }
42
+ return new CrossdeckError({
43
+ type: typeMapForStatus(res.status),
44
+ code: `http_${res.status}`,
45
+ message: `HTTP ${res.status} ${res.statusText || ""}`.trim(),
46
+ requestId,
47
+ status: res.status,
48
+ retryAfterMs
49
+ });
50
+ }
51
+ function parseRetryAfterHeader(value) {
52
+ if (!value) return void 0;
53
+ const trimmed = value.trim();
54
+ if (!trimmed) return void 0;
55
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
56
+ const secs = Number(trimmed);
57
+ if (!Number.isFinite(secs) || secs < 0) return void 0;
58
+ return Math.round(secs * 1e3);
59
+ }
60
+ if (!/[a-zA-Z,/:]/.test(trimmed)) return void 0;
61
+ const target = Date.parse(trimmed);
62
+ if (!Number.isFinite(target)) return void 0;
63
+ const delta = target - Date.now();
64
+ return delta > 0 ? delta : 0;
65
+ }
66
+ function typeMapForStatus(status) {
67
+ if (status === 401) return "authentication_error";
68
+ if (status === 403) return "permission_error";
69
+ if (status === 429) return "rate_limit_error";
70
+ if (status >= 400 && status < 500) return "invalid_request_error";
71
+ return "internal_error";
72
+ }
73
+
74
+ // src/event-validation.ts
75
+ var DEFAULT_MAX_STRING = 1024;
76
+ var DEFAULT_MAX_BYTES = 8 * 1024;
77
+ var DEFAULT_MAX_DEPTH = 5;
78
+ function validateEventProperties(input, options = {}) {
79
+ const warnings = [];
80
+ if (!input) return { properties: {}, warnings };
81
+ const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING;
82
+ const maxBatchPropertyBytes = options.maxBatchPropertyBytes ?? DEFAULT_MAX_BYTES;
83
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
84
+ const seen = /* @__PURE__ */ new WeakSet();
85
+ const visit = (value, key, depth) => {
86
+ if (depth > maxDepth) {
87
+ warnings.push({ kind: "depth_exceeded", key });
88
+ return { keep: true, value: "[depth-exceeded]" };
89
+ }
90
+ if (value === null) return { keep: true, value: null };
91
+ const t = typeof value;
92
+ if (t === "string") {
93
+ const s = value;
94
+ if (s.length > maxStringLength) {
95
+ warnings.push({ kind: "truncated_string", key });
96
+ return { keep: true, value: s.slice(0, maxStringLength - 1) + "\u2026" };
97
+ }
98
+ return { keep: true, value: s };
99
+ }
100
+ if (t === "number") {
101
+ if (!Number.isFinite(value)) {
102
+ warnings.push({ kind: "non_serialisable", key });
103
+ return { keep: true, value: null };
104
+ }
105
+ return { keep: true, value };
106
+ }
107
+ if (t === "boolean") return { keep: true, value };
108
+ if (t === "bigint") {
109
+ warnings.push({ kind: "coerced_bigint", key });
110
+ return { keep: true, value: value.toString() };
111
+ }
112
+ if (t === "function") {
113
+ warnings.push({ kind: "dropped_function", key });
114
+ return { keep: false, value: void 0 };
115
+ }
116
+ if (t === "symbol") {
117
+ warnings.push({ kind: "dropped_symbol", key });
118
+ return { keep: false, value: void 0 };
119
+ }
120
+ if (t === "undefined") {
121
+ warnings.push({ kind: "dropped_undefined", key });
122
+ return { keep: false, value: void 0 };
123
+ }
124
+ if (value instanceof Date) {
125
+ warnings.push({ kind: "coerced_date", key });
126
+ const iso = Number.isFinite(value.getTime()) ? value.toISOString() : null;
127
+ return { keep: true, value: iso };
128
+ }
129
+ if (value instanceof Error) {
130
+ warnings.push({ kind: "coerced_error", key });
131
+ return {
132
+ keep: true,
133
+ value: {
134
+ name: value.name,
135
+ message: value.message,
136
+ stack: typeof value.stack === "string" ? value.stack.slice(0, maxStringLength) : void 0
137
+ }
138
+ };
139
+ }
140
+ if (value instanceof Map) {
141
+ warnings.push({ kind: "coerced_map", key });
142
+ const obj = {};
143
+ for (const [k, v] of value.entries()) {
144
+ const subKey = typeof k === "string" ? k : String(k);
145
+ const result = visit(v, `${key}.${subKey}`, depth + 1);
146
+ if (result.keep) obj[subKey] = result.value;
147
+ }
148
+ return { keep: true, value: obj };
149
+ }
150
+ if (value instanceof Set) {
151
+ warnings.push({ kind: "coerced_set", key });
152
+ const arr = [];
153
+ let i = 0;
154
+ for (const v of value.values()) {
155
+ const result = visit(v, `${key}[${i}]`, depth + 1);
156
+ if (result.keep) arr.push(result.value);
157
+ i++;
158
+ }
159
+ return { keep: true, value: arr };
160
+ }
161
+ if (Array.isArray(value)) {
162
+ if (seen.has(value)) {
163
+ warnings.push({ kind: "circular_reference", key });
164
+ return { keep: true, value: "[circular]" };
165
+ }
166
+ seen.add(value);
167
+ const out = [];
168
+ for (let i = 0; i < value.length; i++) {
169
+ const result = visit(value[i], `${key}[${i}]`, depth + 1);
170
+ if (result.keep) out.push(result.value);
171
+ }
172
+ return { keep: true, value: out };
173
+ }
174
+ if (t === "object") {
175
+ const obj = value;
176
+ if (seen.has(obj)) {
177
+ warnings.push({ kind: "circular_reference", key });
178
+ return { keep: true, value: "[circular]" };
179
+ }
180
+ seen.add(obj);
181
+ const out = {};
182
+ for (const k of Object.keys(obj)) {
183
+ const result = visit(obj[k], `${key}.${k}`, depth + 1);
184
+ if (result.keep) out[k] = result.value;
185
+ }
186
+ return { keep: true, value: out };
187
+ }
188
+ warnings.push({ kind: "non_serialisable", key });
189
+ try {
190
+ return { keep: true, value: String(value) };
191
+ } catch {
192
+ return { keep: false, value: void 0 };
193
+ }
194
+ };
195
+ const cleaned = {};
196
+ for (const k of Object.keys(input)) {
197
+ const result = visit(input[k], k, 0);
198
+ if (result.keep) cleaned[k] = result.value;
199
+ }
200
+ const serialised = safeStringify(cleaned);
201
+ if (serialised && byteLength(serialised) > maxBatchPropertyBytes) {
202
+ warnings.push({ kind: "size_cap_exceeded", key: "*" });
203
+ const sizes = Object.keys(cleaned).map((k) => ({ k, size: byteLength(safeStringify(cleaned[k]) ?? "") })).sort((a, b) => b.size - a.size);
204
+ let currentSize = byteLength(serialised);
205
+ for (const { k } of sizes) {
206
+ if (currentSize <= maxBatchPropertyBytes) break;
207
+ currentSize -= sizes.find((s) => s.k === k).size;
208
+ delete cleaned[k];
209
+ }
210
+ cleaned.__truncated = true;
211
+ }
212
+ return { properties: cleaned, warnings };
213
+ }
214
+ function safeStringify(v) {
215
+ try {
216
+ return JSON.stringify(v) ?? null;
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ function byteLength(s) {
222
+ if (typeof TextEncoder !== "undefined") {
223
+ return new TextEncoder().encode(s).length;
224
+ }
225
+ return s.length * 4;
226
+ }
227
+
228
+ // src/http.ts
229
+ var SDK_NAME = "@cross-deck/node";
230
+ var SDK_VERSION = "0.1.0";
231
+ var DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
232
+ var DEFAULT_TIMEOUT_MS = 15e3;
233
+ var HttpClient = class {
234
+ constructor(config) {
235
+ this.config = config;
236
+ }
237
+ config;
238
+ async request(method, path, options = {}) {
239
+ const url = this.buildUrl(path, options.query);
240
+ const headers = {
241
+ Authorization: `Bearer ${this.config.secretKey}`,
242
+ "Crossdeck-Sdk-Version": `${SDK_NAME}@${this.config.sdkVersion}`,
243
+ Accept: "application/json"
244
+ };
245
+ if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
246
+ let bodyInit;
247
+ if (options.body !== void 0) {
248
+ headers["Content-Type"] = "application/json";
249
+ bodyInit = serializeRequestBody(options.body);
250
+ }
251
+ const effectiveTimeout = options.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
252
+ const controller = typeof AbortController !== "undefined" && effectiveTimeout > 0 ? new AbortController() : null;
253
+ let timeoutHandle = null;
254
+ if (controller && effectiveTimeout > 0) {
255
+ timeoutHandle = setTimeout(() => controller.abort(), effectiveTimeout);
256
+ }
257
+ let response;
258
+ try {
259
+ response = await fetch(url, {
260
+ method,
261
+ headers,
262
+ body: bodyInit,
263
+ signal: controller?.signal
264
+ });
265
+ } catch (err) {
266
+ const aborted = controller?.signal?.aborted === true;
267
+ throw new CrossdeckError({
268
+ type: "network_error",
269
+ code: aborted ? "request_timeout" : "fetch_failed",
270
+ message: aborted ? `Request to ${path} aborted after ${effectiveTimeout}ms` : err instanceof Error ? err.message : "fetch failed"
271
+ });
272
+ } finally {
273
+ if (timeoutHandle !== null) clearTimeout(timeoutHandle);
274
+ }
275
+ if (!response.ok) {
276
+ throw await crossdeckErrorFromResponse(response);
277
+ }
278
+ if (response.status === 204) return void 0;
279
+ try {
280
+ return await response.json();
281
+ } catch {
282
+ throw new CrossdeckError({
283
+ type: "internal_error",
284
+ code: "invalid_json_response",
285
+ message: "Server returned a 2xx with an unparseable body.",
286
+ requestId: response.headers.get("x-request-id") ?? void 0,
287
+ status: response.status
288
+ });
289
+ }
290
+ }
291
+ buildUrl(path, query) {
292
+ const base = this.config.baseUrl.replace(/\/+$/, "");
293
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
294
+ let url = base + cleanPath;
295
+ if (query) {
296
+ const params = new URLSearchParams();
297
+ for (const [k, v] of Object.entries(query)) {
298
+ if (typeof v === "string" && v.length > 0) params.append(k, v);
299
+ }
300
+ const qs = params.toString();
301
+ if (qs) url += (url.includes("?") ? "&" : "?") + qs;
302
+ }
303
+ return url;
304
+ }
305
+ };
306
+ function serializeRequestBody(body) {
307
+ try {
308
+ const direct = JSON.stringify(body);
309
+ if (typeof direct === "string") return direct;
310
+ } catch {
311
+ }
312
+ try {
313
+ const wrapped = validateEventProperties(
314
+ { __body: body },
315
+ {
316
+ maxStringLength: 1e6,
317
+ maxBatchPropertyBytes: 10 * 1024 * 1024,
318
+ maxDepth: 20
319
+ }
320
+ ).properties.__body;
321
+ const serialized = JSON.stringify(wrapped);
322
+ if (typeof serialized === "string") return serialized;
323
+ } catch {
324
+ }
325
+ throw new CrossdeckError({
326
+ type: "invalid_request_error",
327
+ code: "serialization_failed",
328
+ message: "Request body could not be serialized."
329
+ });
330
+ }
331
+
332
+ // src/crossdeck-server.ts
333
+ var CrossdeckServer = class {
334
+ http;
335
+ sdkVersion;
336
+ appId;
337
+ constructor(options) {
338
+ if (!options.secretKey || !options.secretKey.startsWith("cd_sk_")) {
339
+ throw new CrossdeckError({
340
+ type: "configuration_error",
341
+ code: "invalid_secret_key",
342
+ message: "CrossdeckServer requires a secret key starting with cd_sk_."
343
+ });
344
+ }
345
+ this.sdkVersion = options.sdkVersion ?? SDK_VERSION;
346
+ this.appId = options.appId;
347
+ this.http = new HttpClient({
348
+ secretKey: options.secretKey,
349
+ baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
350
+ sdkVersion: this.sdkVersion,
351
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS
352
+ });
353
+ }
354
+ async identify(userId, anonymousId, options) {
355
+ return this.aliasIdentity({ userId, anonymousId, ...options });
356
+ }
357
+ async aliasIdentity(input) {
358
+ if (!input.userId) {
359
+ throw new CrossdeckError({
360
+ type: "invalid_request_error",
361
+ code: "missing_user_id",
362
+ message: "aliasIdentity requires a non-empty userId."
363
+ });
364
+ }
365
+ if (!input.anonymousId) {
366
+ throw new CrossdeckError({
367
+ type: "invalid_request_error",
368
+ code: "missing_anonymous_id",
369
+ message: "aliasIdentity requires a non-empty anonymousId."
370
+ });
371
+ }
372
+ const traits = sanitizePropertyBag(input.traits, "traits");
373
+ const body = {
374
+ userId: input.userId,
375
+ anonymousId: input.anonymousId
376
+ };
377
+ if (input.email) body.email = input.email;
378
+ if (traits && Object.keys(traits).length > 0) body.traits = traits;
379
+ return this.http.request("POST", "/identity/alias", { body });
380
+ }
381
+ async forget(hints) {
382
+ const body = this.identityPayload(hints);
383
+ return this.http.request("POST", "/identity/forget", { body });
384
+ }
385
+ async getEntitlements(hints) {
386
+ return this.http.request("GET", "/entitlements", {
387
+ query: this.identityPayload(hints)
388
+ });
389
+ }
390
+ async getCustomerEntitlements(customerId) {
391
+ if (!customerId) {
392
+ throw new CrossdeckError({
393
+ type: "invalid_request_error",
394
+ code: "missing_customer_id",
395
+ message: "getCustomerEntitlements requires a customerId."
396
+ });
397
+ }
398
+ return this.http.request(
399
+ "GET",
400
+ `/server/customers/${encodeURIComponent(customerId)}/entitlements`
401
+ );
402
+ }
403
+ async track(event, options = {}) {
404
+ return this.ingest([event], options);
405
+ }
406
+ async ingest(events, options = {}) {
407
+ if (!Array.isArray(events) || events.length === 0) {
408
+ throw new CrossdeckError({
409
+ type: "invalid_request_error",
410
+ code: "missing_events",
411
+ message: "ingest requires at least one event."
412
+ });
413
+ }
414
+ const normalized = events.map((event) => this.normalizeEvent(event));
415
+ const body = {
416
+ events: normalized,
417
+ sdk: { name: SDK_NAME, version: this.sdkVersion }
418
+ };
419
+ if (this.appId) body.appId = this.appId;
420
+ return this.http.request("POST", "/events", {
421
+ body,
422
+ idempotencyKey: options.idempotencyKey ?? this.mintBatchId()
423
+ });
424
+ }
425
+ async syncPurchases(input) {
426
+ if (!input.signedTransactionInfo) {
427
+ throw new CrossdeckError({
428
+ type: "invalid_request_error",
429
+ code: "missing_signed_transaction_info",
430
+ message: "syncPurchases requires a signedTransactionInfo string."
431
+ });
432
+ }
433
+ return this.http.request("POST", "/purchases/sync", {
434
+ body: { rail: input.rail ?? "apple", ...input }
435
+ });
436
+ }
437
+ async grantEntitlement(input) {
438
+ if (!input.customerId) {
439
+ throw new CrossdeckError({
440
+ type: "invalid_request_error",
441
+ code: "missing_customer_id",
442
+ message: "grantEntitlement requires a customerId."
443
+ });
444
+ }
445
+ return this.http.request(
446
+ "POST",
447
+ `/server/customers/${encodeURIComponent(input.customerId)}/grant`,
448
+ {
449
+ body: {
450
+ entitlementKey: input.entitlementKey,
451
+ duration: input.duration,
452
+ reason: input.reason
453
+ }
454
+ }
455
+ );
456
+ }
457
+ async revokeEntitlement(input) {
458
+ if (!input.customerId) {
459
+ throw new CrossdeckError({
460
+ type: "invalid_request_error",
461
+ code: "missing_customer_id",
462
+ message: "revokeEntitlement requires a customerId."
463
+ });
464
+ }
465
+ return this.http.request(
466
+ "POST",
467
+ `/server/customers/${encodeURIComponent(input.customerId)}/revoke`,
468
+ {
469
+ body: {
470
+ entitlementKey: input.entitlementKey,
471
+ reason: input.reason
472
+ }
473
+ }
474
+ );
475
+ }
476
+ async getAuditEntry(eventId) {
477
+ if (!eventId) {
478
+ throw new CrossdeckError({
479
+ type: "invalid_request_error",
480
+ code: "missing_event_id",
481
+ message: "getAuditEntry requires an eventId."
482
+ });
483
+ }
484
+ const result = await this.http.request(
485
+ "GET",
486
+ `/server/audit/${encodeURIComponent(eventId)}`
487
+ );
488
+ return result.data;
489
+ }
490
+ identityPayload(hints) {
491
+ const payload = {};
492
+ if (typeof hints.customerId === "string" && hints.customerId) {
493
+ payload.customerId = hints.customerId;
494
+ }
495
+ if (typeof hints.userId === "string" && hints.userId) {
496
+ payload.userId = hints.userId;
497
+ }
498
+ if (typeof hints.anonymousId === "string" && hints.anonymousId) {
499
+ payload.anonymousId = hints.anonymousId;
500
+ }
501
+ if (Object.keys(payload).length === 0) {
502
+ throw new CrossdeckError({
503
+ type: "invalid_request_error",
504
+ code: "missing_identity",
505
+ message: "Provide at least one of customerId, userId, or anonymousId."
506
+ });
507
+ }
508
+ return payload;
509
+ }
510
+ normalizeEvent(event) {
511
+ if (!event.name) {
512
+ throw new CrossdeckError({
513
+ type: "invalid_request_error",
514
+ code: "missing_event_name",
515
+ message: "Each event requires a non-empty name."
516
+ });
517
+ }
518
+ const hasIdentity = Boolean(event.developerUserId) || Boolean(event.anonymousId) || Boolean(event.crossdeckCustomerId);
519
+ if (!hasIdentity) {
520
+ throw new CrossdeckError({
521
+ type: "invalid_request_error",
522
+ code: "missing_identity",
523
+ message: "Each event requires at least one of developerUserId, anonymousId, or crossdeckCustomerId."
524
+ });
525
+ }
526
+ const properties = sanitizePropertyBag(event.properties, "event properties");
527
+ return {
528
+ ...event,
529
+ properties,
530
+ eventId: event.eventId ?? this.mintEventId(),
531
+ timestamp: event.timestamp ?? Date.now()
532
+ };
533
+ }
534
+ mintEventId() {
535
+ const ts = Date.now().toString(36);
536
+ return `evt_${ts}${randomUUID().replace(/-/g, "").slice(0, 8)}`;
537
+ }
538
+ mintBatchId() {
539
+ const ts = Date.now().toString(36);
540
+ return `batch_${ts}${randomUUID().replace(/-/g, "").slice(0, 8)}`;
541
+ }
542
+ };
543
+ function sanitizePropertyBag(input, fieldName) {
544
+ if (input === void 0) return void 0;
545
+ try {
546
+ return validateEventProperties(input).properties;
547
+ } catch {
548
+ throw new CrossdeckError({
549
+ type: "invalid_request_error",
550
+ code: "serialization_failed",
551
+ message: `${fieldName} could not be serialized.`
552
+ });
553
+ }
554
+ }
555
+ export {
556
+ CrossdeckError,
557
+ CrossdeckServer,
558
+ DEFAULT_BASE_URL,
559
+ DEFAULT_TIMEOUT_MS,
560
+ SDK_NAME,
561
+ SDK_VERSION
562
+ };
563
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/crossdeck-server.ts","../src/errors.ts","../src/event-validation.ts","../src/http.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nimport { CrossdeckError } from \"./errors\";\nimport { validateEventProperties } from \"./event-validation\";\nimport {\n DEFAULT_BASE_URL,\n DEFAULT_TIMEOUT_MS,\n HttpClient,\n SDK_NAME,\n SDK_VERSION,\n} from \"./http\";\nimport type {\n AliasIdentityInput,\n AliasResult,\n AuditEntry,\n AuditEntryResponse,\n CrossdeckServerOptions,\n EntitlementMutationResult,\n EntitlementsListResponse,\n ForgetResult,\n GrantEntitlementInput,\n IdentityHints,\n IdentifyOptions,\n IngestOptions,\n IngestResponse,\n PurchaseResult,\n RevokeEntitlementInput,\n ServerEvent,\n SyncPurchaseInput,\n} from \"./types\";\n\nexport class CrossdeckServer {\n private readonly http: HttpClient;\n private readonly sdkVersion: string;\n private readonly appId?: string;\n\n constructor(options: CrossdeckServerOptions) {\n if (!options.secretKey || !options.secretKey.startsWith(\"cd_sk_\")) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"invalid_secret_key\",\n message: \"CrossdeckServer requires a secret key starting with cd_sk_.\",\n });\n }\n\n this.sdkVersion = options.sdkVersion ?? SDK_VERSION;\n this.appId = options.appId;\n this.http = new HttpClient({\n secretKey: options.secretKey,\n baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,\n sdkVersion: this.sdkVersion,\n timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n });\n }\n\n async identify(\n userId: string,\n anonymousId: string,\n options?: IdentifyOptions,\n ): Promise<AliasResult> {\n return this.aliasIdentity({ userId, anonymousId, ...options });\n }\n\n async aliasIdentity(input: AliasIdentityInput): Promise<AliasResult> {\n if (!input.userId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_user_id\",\n message: \"aliasIdentity requires a non-empty userId.\",\n });\n }\n if (!input.anonymousId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_anonymous_id\",\n message: \"aliasIdentity requires a non-empty anonymousId.\",\n });\n }\n\n const traits = sanitizePropertyBag(input.traits, \"traits\");\n const body: Record<string, unknown> = {\n userId: input.userId,\n anonymousId: input.anonymousId,\n };\n if (input.email) body.email = input.email;\n if (traits && Object.keys(traits).length > 0) body.traits = traits;\n\n return this.http.request<AliasResult>(\"POST\", \"/identity/alias\", { body });\n }\n\n async forget(hints: IdentityHints): Promise<ForgetResult> {\n const body = this.identityPayload(hints);\n return this.http.request<ForgetResult>(\"POST\", \"/identity/forget\", { body });\n }\n\n async getEntitlements(hints: IdentityHints): Promise<EntitlementsListResponse> {\n return this.http.request<EntitlementsListResponse>(\"GET\", \"/entitlements\", {\n query: this.identityPayload(hints),\n });\n }\n\n async getCustomerEntitlements(customerId: string): Promise<EntitlementsListResponse> {\n if (!customerId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_customer_id\",\n message: \"getCustomerEntitlements requires a customerId.\",\n });\n }\n return this.http.request<EntitlementsListResponse>(\n \"GET\",\n `/server/customers/${encodeURIComponent(customerId)}/entitlements`,\n );\n }\n\n async track(event: ServerEvent, options: IngestOptions = {}): Promise<IngestResponse> {\n return this.ingest([event], options);\n }\n\n async ingest(events: ServerEvent[], options: IngestOptions = {}): Promise<IngestResponse> {\n if (!Array.isArray(events) || events.length === 0) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_events\",\n message: \"ingest requires at least one event.\",\n });\n }\n\n const normalized = events.map((event) => this.normalizeEvent(event));\n const body: Record<string, unknown> = {\n events: normalized,\n sdk: { name: SDK_NAME, version: this.sdkVersion },\n };\n if (this.appId) body.appId = this.appId;\n\n return this.http.request<IngestResponse>(\"POST\", \"/events\", {\n body,\n idempotencyKey: options.idempotencyKey ?? this.mintBatchId(),\n });\n }\n\n async syncPurchases(input: SyncPurchaseInput): Promise<PurchaseResult> {\n if (!input.signedTransactionInfo) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_signed_transaction_info\",\n message: \"syncPurchases requires a signedTransactionInfo string.\",\n });\n }\n return this.http.request<PurchaseResult>(\"POST\", \"/purchases/sync\", {\n body: { rail: input.rail ?? \"apple\", ...input },\n });\n }\n\n async grantEntitlement(\n input: GrantEntitlementInput,\n ): Promise<EntitlementMutationResult> {\n if (!input.customerId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_customer_id\",\n message: \"grantEntitlement requires a customerId.\",\n });\n }\n\n return this.http.request<EntitlementMutationResult>(\n \"POST\",\n `/server/customers/${encodeURIComponent(input.customerId)}/grant`,\n {\n body: {\n entitlementKey: input.entitlementKey,\n duration: input.duration,\n reason: input.reason,\n },\n },\n );\n }\n\n async revokeEntitlement(\n input: RevokeEntitlementInput,\n ): Promise<EntitlementMutationResult> {\n if (!input.customerId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_customer_id\",\n message: \"revokeEntitlement requires a customerId.\",\n });\n }\n\n return this.http.request<EntitlementMutationResult>(\n \"POST\",\n `/server/customers/${encodeURIComponent(input.customerId)}/revoke`,\n {\n body: {\n entitlementKey: input.entitlementKey,\n reason: input.reason,\n },\n },\n );\n }\n\n async getAuditEntry(eventId: string): Promise<AuditEntry> {\n if (!eventId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_id\",\n message: \"getAuditEntry requires an eventId.\",\n });\n }\n\n const result = await this.http.request<AuditEntryResponse>(\n \"GET\",\n `/server/audit/${encodeURIComponent(eventId)}`,\n );\n return result.data;\n }\n\n private identityPayload(hints: IdentityHints): Record<string, string> {\n const payload: Record<string, string> = {};\n if (typeof hints.customerId === \"string\" && hints.customerId) {\n payload.customerId = hints.customerId;\n }\n if (typeof hints.userId === \"string\" && hints.userId) {\n payload.userId = hints.userId;\n }\n if (typeof hints.anonymousId === \"string\" && hints.anonymousId) {\n payload.anonymousId = hints.anonymousId;\n }\n if (Object.keys(payload).length === 0) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_identity\",\n message: \"Provide at least one of customerId, userId, or anonymousId.\",\n });\n }\n return payload;\n }\n\n private normalizeEvent(event: ServerEvent): ServerEvent {\n if (!event.name) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_name\",\n message: \"Each event requires a non-empty name.\",\n });\n }\n const hasIdentity =\n Boolean(event.developerUserId) ||\n Boolean(event.anonymousId) ||\n Boolean(event.crossdeckCustomerId);\n if (!hasIdentity) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_identity\",\n message:\n \"Each event requires at least one of developerUserId, anonymousId, or crossdeckCustomerId.\",\n });\n }\n const properties = sanitizePropertyBag(event.properties, \"event properties\");\n return {\n ...event,\n properties,\n eventId: event.eventId ?? this.mintEventId(),\n timestamp: event.timestamp ?? Date.now(),\n };\n }\n\n private mintEventId(): string {\n const ts = Date.now().toString(36);\n return `evt_${ts}${randomUUID().replace(/-/g, \"\").slice(0, 8)}`;\n }\n\n private mintBatchId(): string {\n const ts = Date.now().toString(36);\n return `batch_${ts}${randomUUID().replace(/-/g, \"\").slice(0, 8)}`;\n }\n}\n\nfunction sanitizePropertyBag(\n input: Record<string, unknown> | undefined,\n fieldName: string,\n): Record<string, unknown> | undefined {\n if (input === undefined) return undefined;\n try {\n return validateEventProperties(input).properties;\n } catch {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"serialization_failed\",\n message: `${fieldName} could not be serialized.`,\n });\n }\n}\n","export type CrossdeckErrorType =\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n | \"internal_error\"\n | \"network_error\"\n | \"configuration_error\";\n\nexport interface CrossdeckErrorPayload {\n type: CrossdeckErrorType;\n code: string;\n message: string;\n requestId?: string;\n status?: number;\n retryAfterMs?: number;\n}\n\nexport class CrossdeckError extends Error {\n public readonly type: CrossdeckErrorType;\n public readonly code: string;\n public readonly requestId?: string;\n public readonly status?: number;\n public readonly retryAfterMs?: number;\n\n constructor(payload: CrossdeckErrorPayload) {\n super(payload.message);\n this.name = \"CrossdeckError\";\n this.type = payload.type;\n this.code = payload.code;\n this.requestId = payload.requestId;\n this.status = payload.status;\n this.retryAfterMs = payload.retryAfterMs;\n Object.setPrototypeOf(this, CrossdeckError.prototype);\n }\n}\n\nexport async function crossdeckErrorFromResponse(res: Response): Promise<CrossdeckError> {\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const retryAfterMs = parseRetryAfterHeader(res.headers.get(\"retry-after\"));\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n body = null;\n }\n const envelope = (body as { error?: Partial<CrossdeckErrorPayload> & { request_id?: string } })?.error;\n if (envelope && typeof envelope.type === \"string\" && typeof envelope.code === \"string\") {\n return new CrossdeckError({\n type: envelope.type as CrossdeckErrorType,\n code: envelope.code,\n message: envelope.message ?? `HTTP ${res.status}`,\n requestId: envelope.request_id ?? requestId,\n status: res.status,\n retryAfterMs,\n });\n }\n return new CrossdeckError({\n type: typeMapForStatus(res.status),\n code: `http_${res.status}`,\n message: `HTTP ${res.status} ${res.statusText || \"\"}`.trim(),\n requestId,\n status: res.status,\n retryAfterMs,\n });\n}\n\nexport function parseRetryAfterHeader(value: string | null): number | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n if (/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n const secs = Number(trimmed);\n if (!Number.isFinite(secs) || secs < 0) return undefined;\n return Math.round(secs * 1000);\n }\n if (!/[a-zA-Z,/:]/.test(trimmed)) return undefined;\n const target = Date.parse(trimmed);\n if (!Number.isFinite(target)) return undefined;\n const delta = target - Date.now();\n return delta > 0 ? delta : 0;\n}\n\nfunction typeMapForStatus(status: number): CrossdeckErrorType {\n if (status === 401) return \"authentication_error\";\n if (status === 403) return \"permission_error\";\n if (status === 429) return \"rate_limit_error\";\n if (status >= 400 && status < 500) return \"invalid_request_error\";\n return \"internal_error\";\n}\n","/**\n * Property validation + coercion for Node SDK payloads.\n *\n * Ported directly from `@cross-deck/web` so web + node share the same\n * sanitisation contract for traits and event property bags.\n */\n\ntype EventProperties = Record<string, unknown>;\n\nexport interface ValidationOptions {\n maxStringLength?: number;\n maxBatchPropertyBytes?: number;\n maxDepth?: number;\n}\n\nexport interface ValidationWarning {\n kind:\n | \"dropped_function\"\n | \"dropped_symbol\"\n | \"dropped_undefined\"\n | \"coerced_date\"\n | \"coerced_bigint\"\n | \"coerced_error\"\n | \"coerced_map\"\n | \"coerced_set\"\n | \"truncated_string\"\n | \"circular_reference\"\n | \"depth_exceeded\"\n | \"non_serialisable\"\n | \"size_cap_exceeded\";\n key: string;\n}\n\nexport interface ValidationResult {\n properties: EventProperties;\n warnings: ValidationWarning[];\n}\n\nconst DEFAULT_MAX_STRING = 1024;\nconst DEFAULT_MAX_BYTES = 8 * 1024;\nconst DEFAULT_MAX_DEPTH = 5;\n\nexport function validateEventProperties(\n input: EventProperties | undefined,\n options: ValidationOptions = {},\n): ValidationResult {\n const warnings: ValidationWarning[] = [];\n if (!input) return { properties: {}, warnings };\n\n const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING;\n const maxBatchPropertyBytes = options.maxBatchPropertyBytes ?? DEFAULT_MAX_BYTES;\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n\n const seen = new WeakSet<object>();\n\n const visit = (\n value: unknown,\n key: string,\n depth: number,\n ): { keep: boolean; value: unknown } => {\n if (depth > maxDepth) {\n warnings.push({ kind: \"depth_exceeded\", key });\n return { keep: true, value: \"[depth-exceeded]\" };\n }\n if (value === null) return { keep: true, value: null };\n const t = typeof value;\n if (t === \"string\") {\n const s = value as string;\n if (s.length > maxStringLength) {\n warnings.push({ kind: \"truncated_string\", key });\n return { keep: true, value: s.slice(0, maxStringLength - 1) + \"…\" };\n }\n return { keep: true, value: s };\n }\n if (t === \"number\") {\n if (!Number.isFinite(value as number)) {\n warnings.push({ kind: \"non_serialisable\", key });\n return { keep: true, value: null };\n }\n return { keep: true, value };\n }\n if (t === \"boolean\") return { keep: true, value };\n if (t === \"bigint\") {\n warnings.push({ kind: \"coerced_bigint\", key });\n return { keep: true, value: (value as bigint).toString() };\n }\n if (t === \"function\") {\n warnings.push({ kind: \"dropped_function\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"symbol\") {\n warnings.push({ kind: \"dropped_symbol\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"undefined\") {\n warnings.push({ kind: \"dropped_undefined\", key });\n return { keep: false, value: undefined };\n }\n\n if (value instanceof Date) {\n warnings.push({ kind: \"coerced_date\", key });\n const iso = Number.isFinite(value.getTime()) ? value.toISOString() : null;\n return { keep: true, value: iso };\n }\n if (value instanceof Error) {\n warnings.push({ kind: \"coerced_error\", key });\n return {\n keep: true,\n value: {\n name: value.name,\n message: value.message,\n stack:\n typeof value.stack === \"string\"\n ? value.stack.slice(0, maxStringLength)\n : undefined,\n },\n };\n }\n if (value instanceof Map) {\n warnings.push({ kind: \"coerced_map\", key });\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value.entries()) {\n const subKey = typeof k === \"string\" ? k : String(k);\n const result = visit(v, `${key}.${subKey}`, depth + 1);\n if (result.keep) obj[subKey] = result.value;\n }\n return { keep: true, value: obj };\n }\n if (value instanceof Set) {\n warnings.push({ kind: \"coerced_set\", key });\n const arr: unknown[] = [];\n let i = 0;\n for (const v of value.values()) {\n const result = visit(v, `${key}[${i}]`, depth + 1);\n if (result.keep) arr.push(result.value);\n i++;\n }\n return { keep: true, value: arr };\n }\n\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(value);\n const out: unknown[] = [];\n for (let i = 0; i < value.length; i++) {\n const result = visit(value[i], `${key}[${i}]`, depth + 1);\n if (result.keep) out.push(result.value);\n }\n return { keep: true, value: out };\n }\n\n if (t === \"object\") {\n const obj = value as Record<string, unknown>;\n if (seen.has(obj)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(obj);\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(obj)) {\n const result = visit(obj[k], `${key}.${k}`, depth + 1);\n if (result.keep) out[k] = result.value;\n }\n return { keep: true, value: out };\n }\n\n warnings.push({ kind: \"non_serialisable\", key });\n try {\n return { keep: true, value: String(value) };\n } catch {\n return { keep: false, value: undefined };\n }\n };\n\n const cleaned: Record<string, unknown> = {};\n for (const k of Object.keys(input)) {\n const result = visit(input[k], k, 0);\n if (result.keep) cleaned[k] = result.value;\n }\n\n const serialised = safeStringify(cleaned);\n if (serialised && byteLength(serialised) > maxBatchPropertyBytes) {\n warnings.push({ kind: \"size_cap_exceeded\", key: \"*\" });\n const sizes = Object.keys(cleaned)\n .map((k) => ({ k, size: byteLength(safeStringify(cleaned[k]) ?? \"\") }))\n .sort((a, b) => b.size - a.size);\n let currentSize = byteLength(serialised);\n for (const { k } of sizes) {\n if (currentSize <= maxBatchPropertyBytes) break;\n currentSize -= sizes.find((s) => s.k === k)!.size;\n delete cleaned[k];\n }\n cleaned.__truncated = true;\n }\n\n return { properties: cleaned, warnings };\n}\n\nfunction safeStringify(v: unknown): string | null {\n try {\n return JSON.stringify(v) ?? null;\n } catch {\n return null;\n }\n}\n\nfunction byteLength(s: string): number {\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(s).length;\n }\n return s.length * 4;\n}\n","import { CrossdeckError, crossdeckErrorFromResponse } from \"./errors\";\nimport { validateEventProperties } from \"./event-validation\";\n\nexport const SDK_NAME = \"@cross-deck/node\";\nexport const SDK_VERSION = \"0.1.0\";\nexport const DEFAULT_BASE_URL = \"https://api.cross-deck.com/v1\";\nexport const DEFAULT_TIMEOUT_MS = 15_000;\n\nexport interface HttpClientConfig {\n secretKey: string;\n baseUrl: string;\n sdkVersion: string;\n timeoutMs?: number;\n}\n\nexport interface HttpRequestOptions {\n body?: unknown;\n query?: Record<string, string | undefined>;\n timeoutMs?: number;\n idempotencyKey?: string;\n}\n\nexport class HttpClient {\n constructor(private readonly config: HttpClientConfig) {}\n\n async request<T>(\n method: \"GET\" | \"POST\",\n path: string,\n options: HttpRequestOptions = {},\n ): Promise<T> {\n const url = this.buildUrl(path, options.query);\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.config.secretKey}`,\n \"Crossdeck-Sdk-Version\": `${SDK_NAME}@${this.config.sdkVersion}`,\n Accept: \"application/json\",\n };\n if (options.idempotencyKey) headers[\"Idempotency-Key\"] = options.idempotencyKey;\n\n let bodyInit: RequestInit[\"body\"] | undefined;\n if (options.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n bodyInit = serializeRequestBody(options.body);\n }\n\n const effectiveTimeout = options.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const controller =\n typeof AbortController !== \"undefined\" && effectiveTimeout > 0\n ? new AbortController()\n : null;\n let timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n if (controller && effectiveTimeout > 0) {\n timeoutHandle = setTimeout(() => controller.abort(), effectiveTimeout);\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body: bodyInit,\n signal: controller?.signal,\n });\n } catch (err) {\n const aborted = controller?.signal?.aborted === true;\n throw new CrossdeckError({\n type: \"network_error\",\n code: aborted ? \"request_timeout\" : \"fetch_failed\",\n message: aborted\n ? `Request to ${path} aborted after ${effectiveTimeout}ms`\n : err instanceof Error\n ? err.message\n : \"fetch failed\",\n });\n } finally {\n if (timeoutHandle !== null) clearTimeout(timeoutHandle);\n }\n\n if (!response.ok) {\n throw await crossdeckErrorFromResponse(response);\n }\n\n if (response.status === 204) return undefined as T;\n\n try {\n return (await response.json()) as T;\n } catch {\n throw new CrossdeckError({\n type: \"internal_error\",\n code: \"invalid_json_response\",\n message: \"Server returned a 2xx with an unparseable body.\",\n requestId: response.headers.get(\"x-request-id\") ?? undefined,\n status: response.status,\n });\n }\n }\n\n private buildUrl(path: string, query?: Record<string, string | undefined>): string {\n const base = this.config.baseUrl.replace(/\\/+$/, \"\");\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n let url = base + cleanPath;\n if (query) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (typeof v === \"string\" && v.length > 0) params.append(k, v);\n }\n const qs = params.toString();\n if (qs) url += (url.includes(\"?\") ? \"&\" : \"?\") + qs;\n }\n return url;\n }\n}\n\nfunction serializeRequestBody(body: unknown): string {\n try {\n const direct = JSON.stringify(body);\n if (typeof direct === \"string\") return direct;\n } catch {\n // Fall through to the sanitising backstop.\n }\n\n try {\n const wrapped = validateEventProperties(\n { __body: body },\n {\n maxStringLength: 1_000_000,\n maxBatchPropertyBytes: 10 * 1024 * 1024,\n maxDepth: 20,\n },\n ).properties.__body;\n const serialized = JSON.stringify(wrapped);\n if (typeof serialized === \"string\") return serialized;\n } catch {\n // Surface a stable SDK error instead of leaking the runtime's raw\n // JSON.stringify message.\n }\n\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"serialization_failed\",\n message: \"Request body could not be serialized.\",\n });\n}\n"],"mappings":"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","names":[]}