@cross-deck/node 1.9.1 → 1.10.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 +23 -0
- package/README.md +2 -2
- package/dist/auto-events/index.d.mts +1 -1
- package/dist/auto-events/index.d.ts +1 -1
- package/dist/{crossdeck-server-Q3CYJC4Z.d.mts → crossdeck-server-C4nWPQzH.d.mts} +113 -3
- package/dist/{crossdeck-server-Q3CYJC4Z.d.ts → crossdeck-server-C4nWPQzH.d.ts} +113 -3
- package/dist/index.cjs +138 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +138 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.10.0] — 2026-06-30
|
|
10
|
+
|
|
11
|
+
**Added — the blocking surface (Crossdeck Trust, v2 preview).** Blocking is
|
|
12
|
+
"entitlements, inverted": `getEntitlements()` answers *can they access Pro?*; the new
|
|
13
|
+
methods answer *should they be here at all?* — the same `/v1/resolve` plumbing, read for
|
|
14
|
+
`blocked`. Additive and non-breaking.
|
|
15
|
+
|
|
16
|
+
- **`resolve(input)`** → `ResolveResult` — the block verdict plus identity/entitlement
|
|
17
|
+
context in one call. **Fail-open by contract:** never throws for the verdict and never
|
|
18
|
+
returns `blocked: true` on uncertainty; any error → `{ blocked: false, degraded: true }`,
|
|
19
|
+
so a glitch can never lock out a real user (the mirror of entitlements, which fail
|
|
20
|
+
closed). Forward the end-user's `ip` (server-to-server callers otherwise expose only
|
|
21
|
+
their own server IP, so ip-rules can't match) and the verified identity
|
|
22
|
+
(`userId` + `idToken`) so domain/email rules can match.
|
|
23
|
+
- **`isBlocked(input)`** → `boolean` — convenience over `resolve()`; same fail-open.
|
|
24
|
+
- **`getOwnerStatus(input)`** → `BlockVerdict` — the public-page path: *"is the owner of
|
|
25
|
+
this page blocked?"*, no session token, for cache invalidation on link-in-bio / content
|
|
26
|
+
platforms (Pattern B).
|
|
27
|
+
|
|
28
|
+
New exported types: `ResolveInput`, `ResolveResult`, `BlockVerdict`, `OwnerStatusInput`.
|
|
29
|
+
All marked `@experimental` — Crossdeck Trust ships with Crossdeck v2; the shape is stable,
|
|
30
|
+
the surface is not yet GA.
|
|
31
|
+
|
|
9
32
|
## [1.9.1] — 2026-06-30
|
|
10
33
|
|
|
11
34
|
**Docs — knowledge-backbone governance release.** No runtime API change. This
|
package/README.md
CHANGED
|
@@ -614,8 +614,8 @@ CrossdeckContracts.byId("idempotency-key-deterministic");
|
|
|
614
614
|
CrossdeckContracts.byPillar("revenue");
|
|
615
615
|
CrossdeckContracts.withStatus("proposed");
|
|
616
616
|
CrossdeckContracts.findByTestName("rail namespacing prevents cross-rail collisions");
|
|
617
|
-
CrossdeckContracts.sdkVersion; // "1.
|
|
618
|
-
CrossdeckContracts.bundledIn; // "@cross-deck/node@1.
|
|
617
|
+
CrossdeckContracts.sdkVersion; // "1.10.0"
|
|
618
|
+
CrossdeckContracts.bundledIn; // "@cross-deck/node@1.10.0"
|
|
619
619
|
```
|
|
620
620
|
|
|
621
621
|
The `Contract` type is exported alongside; the binary-stability promise is documented in [`contracts/README.md`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/README.md).
|
|
@@ -183,8 +183,8 @@ declare const CrossdeckContracts: {
|
|
|
183
183
|
readonly byId: (id: string) => Contract | undefined;
|
|
184
184
|
readonly byPillar: (pillar: ContractPillar) => readonly Contract[];
|
|
185
185
|
readonly withStatus: (status: ContractStatus) => readonly Contract[];
|
|
186
|
-
readonly sdkVersion: "1.
|
|
187
|
-
readonly bundledIn: "@cross-deck/node@1.
|
|
186
|
+
readonly sdkVersion: "1.10.0";
|
|
187
|
+
readonly bundledIn: "@cross-deck/node@1.10.0";
|
|
188
188
|
/**
|
|
189
189
|
* Resolve a failing test back to the contract it exercises.
|
|
190
190
|
* Used by test-framework hooks to find the contract id of a
|
|
@@ -438,6 +438,82 @@ interface EntitlementsListResponse {
|
|
|
438
438
|
crossdeckCustomerId: string;
|
|
439
439
|
env: Environment;
|
|
440
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Identity for {@link CrossdeckServer.resolve} / {@link CrossdeckServer.isBlocked}.
|
|
443
|
+
* Send the END USER's identity, not your server's.
|
|
444
|
+
*
|
|
445
|
+
* @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Shape is stable; the
|
|
446
|
+
* surface is not yet GA.
|
|
447
|
+
*/
|
|
448
|
+
interface ResolveInput {
|
|
449
|
+
/** Your signed-in user id. */
|
|
450
|
+
userId?: string;
|
|
451
|
+
/** A SIGNED proof of that user (e.g. their Firebase ID token). Required alongside `userId` for the verified path. */
|
|
452
|
+
idToken?: string;
|
|
453
|
+
/** Anonymous device id (the Web SDK's `getAnonymousId()`). Folds a pre-login device. */
|
|
454
|
+
anonymousId?: string;
|
|
455
|
+
/**
|
|
456
|
+
* The END USER's IP. Forward it — you call resolve server-to-server, so without it
|
|
457
|
+
* Crossdeck sees YOUR server's IP and an ip-rule can't match the visitor.
|
|
458
|
+
*/
|
|
459
|
+
ip?: string;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* The block verdict. Fail-open: on any error these come back `{ blocked: false }`,
|
|
463
|
+
* never an exception — a glitch can never lock out a real user (the mirror of
|
|
464
|
+
* entitlements, which fail closed).
|
|
465
|
+
*
|
|
466
|
+
* @experimental Preview — see {@link ResolveInput}.
|
|
467
|
+
*/
|
|
468
|
+
interface BlockVerdict {
|
|
469
|
+
/** True ONLY when Crossdeck holds an explicit block on this identity. */
|
|
470
|
+
blocked: boolean;
|
|
471
|
+
/** Why, when blocked: e.g. `"blocklist:domain"` | `"blocklist:ip"` | `"blocked"`. */
|
|
472
|
+
blockReason: string | null;
|
|
473
|
+
/** The exact rule that fired: `"domain:spartan.net"` | `"ip:1.2.3.4"` | `"manual"` | null. */
|
|
474
|
+
blockedKey: string | null;
|
|
475
|
+
/**
|
|
476
|
+
* True when this is the fail-open default (Crossdeck unreachable / identity unresolved).
|
|
477
|
+
* `blocked` is `false`; log it for observability, never treat it as a real "not blocked".
|
|
478
|
+
*/
|
|
479
|
+
degraded?: boolean;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* The full {@link CrossdeckServer.resolve} result — the block verdict plus the identity /
|
|
483
|
+
* entitlement context, so one call answers "should they be here?" and "what can they
|
|
484
|
+
* access?" together.
|
|
485
|
+
*
|
|
486
|
+
* @experimental Preview — see {@link ResolveInput}.
|
|
487
|
+
*/
|
|
488
|
+
interface ResolveResult extends BlockVerdict {
|
|
489
|
+
/** `"active"` | `"anonymous"` | … the resolved identity status. */
|
|
490
|
+
status: string;
|
|
491
|
+
/** Active entitlement keys for this identity (e.g. `["pro"]`). */
|
|
492
|
+
entitlements: string[];
|
|
493
|
+
/** The canonical Crossdeck customer id, when resolved. */
|
|
494
|
+
crossdeckCustomerId: string | null;
|
|
495
|
+
/** The anonymous id Crossdeck resolved / assigned. */
|
|
496
|
+
anonymousId: string | null;
|
|
497
|
+
/**
|
|
498
|
+
* Set when a verified identity was sent but couldn't be checked (e.g. the auth issuer
|
|
499
|
+
* isn't registered). If you sent `userId`+`idToken` and `status` is `"anonymous"`, read this.
|
|
500
|
+
*/
|
|
501
|
+
identityError: {
|
|
502
|
+
reason: string;
|
|
503
|
+
} | null;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Owner identifiers for {@link CrossdeckServer.getOwnerStatus} — the public-page path.
|
|
507
|
+
* Pass any subset; for a page-offline check the `ip` is the owner's CREATION ip.
|
|
508
|
+
*
|
|
509
|
+
* @experimental Preview — see {@link ResolveInput}.
|
|
510
|
+
*/
|
|
511
|
+
interface OwnerStatusInput {
|
|
512
|
+
userId?: string;
|
|
513
|
+
email?: string;
|
|
514
|
+
domain?: string;
|
|
515
|
+
ip?: string;
|
|
516
|
+
}
|
|
441
517
|
/**
|
|
442
518
|
* Snapshot of one customer's last-known-good entitlements, as written
|
|
443
519
|
* to / read from a durable `EntitlementStore`. Versioned for forward-
|
|
@@ -1456,6 +1532,40 @@ declare class CrossdeckServer extends EventEmitter {
|
|
|
1456
1532
|
*/
|
|
1457
1533
|
getEntitlements(hints: IdentityHints, options?: RequestOptions): Promise<EntitlementsListResponse>;
|
|
1458
1534
|
getCustomerEntitlements(customerId: string, options?: RequestOptions): Promise<EntitlementsListResponse>;
|
|
1535
|
+
/**
|
|
1536
|
+
* Resolve a user's BLOCK verdict (plus identity / entitlement context) — the same
|
|
1537
|
+
* `/v1/resolve` you'd call for entitlements, read for `blocked`. Wire it into signup
|
|
1538
|
+
* and every visit; on `blocked`, sign the user out (and, for public content, take
|
|
1539
|
+
* their pages offline — see the Blocking developer guide, Pattern B).
|
|
1540
|
+
*
|
|
1541
|
+
* **Fail-open is the contract.** This never throws for the block decision and never
|
|
1542
|
+
* returns `blocked: true` on uncertainty. Any error — Crossdeck unreachable, timeout,
|
|
1543
|
+
* unresolved identity — yields `{ blocked: false, degraded: true }`, so a glitch can
|
|
1544
|
+
* never lock out a real user.
|
|
1545
|
+
*
|
|
1546
|
+
* Forward the END USER's `ip` (you call this server-to-server, so otherwise Crossdeck
|
|
1547
|
+
* sees your server's IP and an ip-rule can't match the visitor), and the verified
|
|
1548
|
+
* identity (`userId` + `idToken`) so a `domain`/`email` rule can match the email.
|
|
1549
|
+
*
|
|
1550
|
+
* @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Stable shape, not yet GA.
|
|
1551
|
+
*/
|
|
1552
|
+
resolve(input: ResolveInput, options?: RequestOptions): Promise<ResolveResult>;
|
|
1553
|
+
/**
|
|
1554
|
+
* Convenience over {@link resolve}: just the boolean. Fail-open (false on any error).
|
|
1555
|
+
*
|
|
1556
|
+
* @experimental Preview — see {@link resolve}.
|
|
1557
|
+
*/
|
|
1558
|
+
isBlocked(input: ResolveInput, options?: RequestOptions): Promise<boolean>;
|
|
1559
|
+
/**
|
|
1560
|
+
* The OWNER's block verdict for the public-page path (Pattern B / §5b of the Blocking
|
|
1561
|
+
* developer guide) — "is the owner of this page blocked?", no session token required.
|
|
1562
|
+
* Pass the owner's recorded identifiers (`userId`/`email`/`domain`/`ip`; the `ip` is
|
|
1563
|
+
* their CREATION ip, not a live visitor's). Poll on a short TTL to invalidate your
|
|
1564
|
+
* public-page cache. Fail-open — any error → `{ blocked: false, degraded: true }`.
|
|
1565
|
+
*
|
|
1566
|
+
* @experimental Preview — see {@link resolve}.
|
|
1567
|
+
*/
|
|
1568
|
+
getOwnerStatus(input: OwnerStatusInput, options?: RequestOptions): Promise<BlockVerdict>;
|
|
1459
1569
|
/**
|
|
1460
1570
|
* Synchronous entitlement check. Returns `true` iff the customer
|
|
1461
1571
|
* has the entitlement AND the cache entry is fresh (within
|
|
@@ -1935,4 +2045,4 @@ declare class CrossdeckServer extends EventEmitter {
|
|
|
1935
2045
|
private normalizeIngestEvent;
|
|
1936
2046
|
}
|
|
1937
2047
|
|
|
1938
|
-
export { type
|
|
2048
|
+
export { type OwnerStatusInput as $, type AliasIdentityInput as A, type BlockVerdict as B, CROSSDECK_API_VERSION as C, DEFAULT_BASE_URL as D, DEFAULT_TIMEOUT_MS as E, type Diagnostics as F, type EntitlementCacheOptions as G, type EntitlementMutationResult as H, type EntitlementStore as I, type EntitlementsListResponse as J, type EntitlementsListener as K, type Environment as L, type ErrorCaptureConfig as M, type ErrorLevel as N, type EventProperties as O, type ForgetResult as P, type GrantDuration as Q, type GrantEntitlementInput as R, type GroupMembership as S, type HeartbeatResponse as T, type HttpRequestInfo as U, type HttpResponseInfo as V, type HttpRetriesConfig as W, type IdentifyOptions as X, type IdentityHints as Y, type IngestOptions as Z, type IngestResponse as _, type AliasResult as a, type PublicEntitlement as a0, type PurchaseResult as a1, type RequestOptions as a2, type ResolveInput as a3, type ResolveResult as a4, type RevokeEntitlementInput as a5, type RuntimeHost as a6, type RuntimeInfo as a7, type ServerEvent as a8, type StackFrame as a9, type StoredEntitlements as aa, type SyncPurchaseInput as ab, makeCrossdeckError as ac, type AuditDecision as b, type AuditEntry as c, type Breadcrumb as d, type BreadcrumbCategory as e, type BreadcrumbLevel as f, type CapturedError as g, type Contract as h, type ContractAppliesTo as i, type ContractFailureInput as j, type ContractPillar as k, type ContractStatus as l, type ContractTestRef as m, CrossdeckAuthenticationError as n, CrossdeckConfigurationError as o, CrossdeckContracts as p, CrossdeckError as q, type CrossdeckErrorPayload as r, type CrossdeckErrorType as s, CrossdeckInternalError as t, CrossdeckNetworkError as u, CrossdeckPermissionError as v, CrossdeckRateLimitError as w, CrossdeckServer as x, type CrossdeckServerOptions as y, CrossdeckValidationError as z };
|
|
@@ -183,8 +183,8 @@ declare const CrossdeckContracts: {
|
|
|
183
183
|
readonly byId: (id: string) => Contract | undefined;
|
|
184
184
|
readonly byPillar: (pillar: ContractPillar) => readonly Contract[];
|
|
185
185
|
readonly withStatus: (status: ContractStatus) => readonly Contract[];
|
|
186
|
-
readonly sdkVersion: "1.
|
|
187
|
-
readonly bundledIn: "@cross-deck/node@1.
|
|
186
|
+
readonly sdkVersion: "1.10.0";
|
|
187
|
+
readonly bundledIn: "@cross-deck/node@1.10.0";
|
|
188
188
|
/**
|
|
189
189
|
* Resolve a failing test back to the contract it exercises.
|
|
190
190
|
* Used by test-framework hooks to find the contract id of a
|
|
@@ -438,6 +438,82 @@ interface EntitlementsListResponse {
|
|
|
438
438
|
crossdeckCustomerId: string;
|
|
439
439
|
env: Environment;
|
|
440
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Identity for {@link CrossdeckServer.resolve} / {@link CrossdeckServer.isBlocked}.
|
|
443
|
+
* Send the END USER's identity, not your server's.
|
|
444
|
+
*
|
|
445
|
+
* @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Shape is stable; the
|
|
446
|
+
* surface is not yet GA.
|
|
447
|
+
*/
|
|
448
|
+
interface ResolveInput {
|
|
449
|
+
/** Your signed-in user id. */
|
|
450
|
+
userId?: string;
|
|
451
|
+
/** A SIGNED proof of that user (e.g. their Firebase ID token). Required alongside `userId` for the verified path. */
|
|
452
|
+
idToken?: string;
|
|
453
|
+
/** Anonymous device id (the Web SDK's `getAnonymousId()`). Folds a pre-login device. */
|
|
454
|
+
anonymousId?: string;
|
|
455
|
+
/**
|
|
456
|
+
* The END USER's IP. Forward it — you call resolve server-to-server, so without it
|
|
457
|
+
* Crossdeck sees YOUR server's IP and an ip-rule can't match the visitor.
|
|
458
|
+
*/
|
|
459
|
+
ip?: string;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* The block verdict. Fail-open: on any error these come back `{ blocked: false }`,
|
|
463
|
+
* never an exception — a glitch can never lock out a real user (the mirror of
|
|
464
|
+
* entitlements, which fail closed).
|
|
465
|
+
*
|
|
466
|
+
* @experimental Preview — see {@link ResolveInput}.
|
|
467
|
+
*/
|
|
468
|
+
interface BlockVerdict {
|
|
469
|
+
/** True ONLY when Crossdeck holds an explicit block on this identity. */
|
|
470
|
+
blocked: boolean;
|
|
471
|
+
/** Why, when blocked: e.g. `"blocklist:domain"` | `"blocklist:ip"` | `"blocked"`. */
|
|
472
|
+
blockReason: string | null;
|
|
473
|
+
/** The exact rule that fired: `"domain:spartan.net"` | `"ip:1.2.3.4"` | `"manual"` | null. */
|
|
474
|
+
blockedKey: string | null;
|
|
475
|
+
/**
|
|
476
|
+
* True when this is the fail-open default (Crossdeck unreachable / identity unresolved).
|
|
477
|
+
* `blocked` is `false`; log it for observability, never treat it as a real "not blocked".
|
|
478
|
+
*/
|
|
479
|
+
degraded?: boolean;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* The full {@link CrossdeckServer.resolve} result — the block verdict plus the identity /
|
|
483
|
+
* entitlement context, so one call answers "should they be here?" and "what can they
|
|
484
|
+
* access?" together.
|
|
485
|
+
*
|
|
486
|
+
* @experimental Preview — see {@link ResolveInput}.
|
|
487
|
+
*/
|
|
488
|
+
interface ResolveResult extends BlockVerdict {
|
|
489
|
+
/** `"active"` | `"anonymous"` | … the resolved identity status. */
|
|
490
|
+
status: string;
|
|
491
|
+
/** Active entitlement keys for this identity (e.g. `["pro"]`). */
|
|
492
|
+
entitlements: string[];
|
|
493
|
+
/** The canonical Crossdeck customer id, when resolved. */
|
|
494
|
+
crossdeckCustomerId: string | null;
|
|
495
|
+
/** The anonymous id Crossdeck resolved / assigned. */
|
|
496
|
+
anonymousId: string | null;
|
|
497
|
+
/**
|
|
498
|
+
* Set when a verified identity was sent but couldn't be checked (e.g. the auth issuer
|
|
499
|
+
* isn't registered). If you sent `userId`+`idToken` and `status` is `"anonymous"`, read this.
|
|
500
|
+
*/
|
|
501
|
+
identityError: {
|
|
502
|
+
reason: string;
|
|
503
|
+
} | null;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Owner identifiers for {@link CrossdeckServer.getOwnerStatus} — the public-page path.
|
|
507
|
+
* Pass any subset; for a page-offline check the `ip` is the owner's CREATION ip.
|
|
508
|
+
*
|
|
509
|
+
* @experimental Preview — see {@link ResolveInput}.
|
|
510
|
+
*/
|
|
511
|
+
interface OwnerStatusInput {
|
|
512
|
+
userId?: string;
|
|
513
|
+
email?: string;
|
|
514
|
+
domain?: string;
|
|
515
|
+
ip?: string;
|
|
516
|
+
}
|
|
441
517
|
/**
|
|
442
518
|
* Snapshot of one customer's last-known-good entitlements, as written
|
|
443
519
|
* to / read from a durable `EntitlementStore`. Versioned for forward-
|
|
@@ -1456,6 +1532,40 @@ declare class CrossdeckServer extends EventEmitter {
|
|
|
1456
1532
|
*/
|
|
1457
1533
|
getEntitlements(hints: IdentityHints, options?: RequestOptions): Promise<EntitlementsListResponse>;
|
|
1458
1534
|
getCustomerEntitlements(customerId: string, options?: RequestOptions): Promise<EntitlementsListResponse>;
|
|
1535
|
+
/**
|
|
1536
|
+
* Resolve a user's BLOCK verdict (plus identity / entitlement context) — the same
|
|
1537
|
+
* `/v1/resolve` you'd call for entitlements, read for `blocked`. Wire it into signup
|
|
1538
|
+
* and every visit; on `blocked`, sign the user out (and, for public content, take
|
|
1539
|
+
* their pages offline — see the Blocking developer guide, Pattern B).
|
|
1540
|
+
*
|
|
1541
|
+
* **Fail-open is the contract.** This never throws for the block decision and never
|
|
1542
|
+
* returns `blocked: true` on uncertainty. Any error — Crossdeck unreachable, timeout,
|
|
1543
|
+
* unresolved identity — yields `{ blocked: false, degraded: true }`, so a glitch can
|
|
1544
|
+
* never lock out a real user.
|
|
1545
|
+
*
|
|
1546
|
+
* Forward the END USER's `ip` (you call this server-to-server, so otherwise Crossdeck
|
|
1547
|
+
* sees your server's IP and an ip-rule can't match the visitor), and the verified
|
|
1548
|
+
* identity (`userId` + `idToken`) so a `domain`/`email` rule can match the email.
|
|
1549
|
+
*
|
|
1550
|
+
* @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Stable shape, not yet GA.
|
|
1551
|
+
*/
|
|
1552
|
+
resolve(input: ResolveInput, options?: RequestOptions): Promise<ResolveResult>;
|
|
1553
|
+
/**
|
|
1554
|
+
* Convenience over {@link resolve}: just the boolean. Fail-open (false on any error).
|
|
1555
|
+
*
|
|
1556
|
+
* @experimental Preview — see {@link resolve}.
|
|
1557
|
+
*/
|
|
1558
|
+
isBlocked(input: ResolveInput, options?: RequestOptions): Promise<boolean>;
|
|
1559
|
+
/**
|
|
1560
|
+
* The OWNER's block verdict for the public-page path (Pattern B / §5b of the Blocking
|
|
1561
|
+
* developer guide) — "is the owner of this page blocked?", no session token required.
|
|
1562
|
+
* Pass the owner's recorded identifiers (`userId`/`email`/`domain`/`ip`; the `ip` is
|
|
1563
|
+
* their CREATION ip, not a live visitor's). Poll on a short TTL to invalidate your
|
|
1564
|
+
* public-page cache. Fail-open — any error → `{ blocked: false, degraded: true }`.
|
|
1565
|
+
*
|
|
1566
|
+
* @experimental Preview — see {@link resolve}.
|
|
1567
|
+
*/
|
|
1568
|
+
getOwnerStatus(input: OwnerStatusInput, options?: RequestOptions): Promise<BlockVerdict>;
|
|
1459
1569
|
/**
|
|
1460
1570
|
* Synchronous entitlement check. Returns `true` iff the customer
|
|
1461
1571
|
* has the entitlement AND the cache entry is fresh (within
|
|
@@ -1935,4 +2045,4 @@ declare class CrossdeckServer extends EventEmitter {
|
|
|
1935
2045
|
private normalizeIngestEvent;
|
|
1936
2046
|
}
|
|
1937
2047
|
|
|
1938
|
-
export { type
|
|
2048
|
+
export { type OwnerStatusInput as $, type AliasIdentityInput as A, type BlockVerdict as B, CROSSDECK_API_VERSION as C, DEFAULT_BASE_URL as D, DEFAULT_TIMEOUT_MS as E, type Diagnostics as F, type EntitlementCacheOptions as G, type EntitlementMutationResult as H, type EntitlementStore as I, type EntitlementsListResponse as J, type EntitlementsListener as K, type Environment as L, type ErrorCaptureConfig as M, type ErrorLevel as N, type EventProperties as O, type ForgetResult as P, type GrantDuration as Q, type GrantEntitlementInput as R, type GroupMembership as S, type HeartbeatResponse as T, type HttpRequestInfo as U, type HttpResponseInfo as V, type HttpRetriesConfig as W, type IdentifyOptions as X, type IdentityHints as Y, type IngestOptions as Z, type IngestResponse as _, type AliasResult as a, type PublicEntitlement as a0, type PurchaseResult as a1, type RequestOptions as a2, type ResolveInput as a3, type ResolveResult as a4, type RevokeEntitlementInput as a5, type RuntimeHost as a6, type RuntimeInfo as a7, type ServerEvent as a8, type StackFrame as a9, type StoredEntitlements as aa, type SyncPurchaseInput as ab, makeCrossdeckError as ac, type AuditDecision as b, type AuditEntry as c, type Breadcrumb as d, type BreadcrumbCategory as e, type BreadcrumbLevel as f, type CapturedError as g, type Contract as h, type ContractAppliesTo as i, type ContractFailureInput as j, type ContractPillar as k, type ContractStatus as l, type ContractTestRef as m, CrossdeckAuthenticationError as n, CrossdeckConfigurationError as o, CrossdeckContracts as p, CrossdeckError as q, type CrossdeckErrorPayload as r, type CrossdeckErrorType as s, CrossdeckInternalError as t, CrossdeckNetworkError as u, CrossdeckPermissionError as v, CrossdeckRateLimitError as w, CrossdeckServer as x, type CrossdeckServerOptions as y, CrossdeckValidationError as z };
|
package/dist/index.cjs
CHANGED
|
@@ -387,7 +387,7 @@ function byteLength(s) {
|
|
|
387
387
|
var https = __toESM(require("https"));
|
|
388
388
|
|
|
389
389
|
// src/_version.ts
|
|
390
|
-
var SDK_VERSION = "1.
|
|
390
|
+
var SDK_VERSION = "1.10.0";
|
|
391
391
|
var SDK_NAME = "@cross-deck/node";
|
|
392
392
|
|
|
393
393
|
// src/_diagnostic-telemetry.ts
|
|
@@ -3149,6 +3149,130 @@ var CrossdeckServer = class extends import_node_events.EventEmitter {
|
|
|
3149
3149
|
this.populateEntitlementCache({ customerId }, response);
|
|
3150
3150
|
return response;
|
|
3151
3151
|
}
|
|
3152
|
+
// ── Blocking — "entitlements, inverted" (Crossdeck Trust, v2 preview) ───────
|
|
3153
|
+
//
|
|
3154
|
+
// getEntitlements() answers "can they access Pro?"; resolve() answers "should
|
|
3155
|
+
// they be here at all?". Same call, opposite question. Both read Crossdeck live.
|
|
3156
|
+
// The safety inversion: entitlements fail CLOSED (protect revenue); blocking
|
|
3157
|
+
// fails OPEN (protect real users) — these methods NEVER throw for the verdict
|
|
3158
|
+
// and NEVER return blocked:true on uncertainty.
|
|
3159
|
+
/**
|
|
3160
|
+
* Resolve a user's BLOCK verdict (plus identity / entitlement context) — the same
|
|
3161
|
+
* `/v1/resolve` you'd call for entitlements, read for `blocked`. Wire it into signup
|
|
3162
|
+
* and every visit; on `blocked`, sign the user out (and, for public content, take
|
|
3163
|
+
* their pages offline — see the Blocking developer guide, Pattern B).
|
|
3164
|
+
*
|
|
3165
|
+
* **Fail-open is the contract.** This never throws for the block decision and never
|
|
3166
|
+
* returns `blocked: true` on uncertainty. Any error — Crossdeck unreachable, timeout,
|
|
3167
|
+
* unresolved identity — yields `{ blocked: false, degraded: true }`, so a glitch can
|
|
3168
|
+
* never lock out a real user.
|
|
3169
|
+
*
|
|
3170
|
+
* Forward the END USER's `ip` (you call this server-to-server, so otherwise Crossdeck
|
|
3171
|
+
* sees your server's IP and an ip-rule can't match the visitor), and the verified
|
|
3172
|
+
* identity (`userId` + `idToken`) so a `domain`/`email` rule can match the email.
|
|
3173
|
+
*
|
|
3174
|
+
* @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Stable shape, not yet GA.
|
|
3175
|
+
*/
|
|
3176
|
+
async resolve(input, options) {
|
|
3177
|
+
const failOpen = () => ({
|
|
3178
|
+
blocked: false,
|
|
3179
|
+
blockReason: null,
|
|
3180
|
+
blockedKey: null,
|
|
3181
|
+
degraded: true,
|
|
3182
|
+
status: "anonymous",
|
|
3183
|
+
entitlements: [],
|
|
3184
|
+
crossdeckCustomerId: null,
|
|
3185
|
+
anonymousId: input?.anonymousId ?? null,
|
|
3186
|
+
identityError: null
|
|
3187
|
+
});
|
|
3188
|
+
if (!input || !input.anonymousId && !(input.userId && input.idToken)) {
|
|
3189
|
+
this.debug.emit(
|
|
3190
|
+
"sdk.resolve_missing_identity",
|
|
3191
|
+
"resolve() needs anonymousId OR (userId + idToken); returning fail-open blocked:false."
|
|
3192
|
+
);
|
|
3193
|
+
return failOpen();
|
|
3194
|
+
}
|
|
3195
|
+
try {
|
|
3196
|
+
const raw = await this.http.request("POST", "/resolve", {
|
|
3197
|
+
body: {
|
|
3198
|
+
...input.userId ? { userId: input.userId } : {},
|
|
3199
|
+
...input.idToken ? { idToken: input.idToken } : {},
|
|
3200
|
+
...input.anonymousId ? { anonymousId: input.anonymousId } : {},
|
|
3201
|
+
...input.ip ? { ip: input.ip } : {}
|
|
3202
|
+
},
|
|
3203
|
+
signal: options?.signal,
|
|
3204
|
+
timeoutMs: options?.timeoutMs
|
|
3205
|
+
});
|
|
3206
|
+
return {
|
|
3207
|
+
blocked: raw.blocked === true,
|
|
3208
|
+
blockReason: raw.blockReason ?? null,
|
|
3209
|
+
blockedKey: raw.blockedKey ?? null,
|
|
3210
|
+
status: typeof raw.status === "string" ? raw.status : "active",
|
|
3211
|
+
entitlements: Array.isArray(raw.entitlements) ? raw.entitlements.filter((e) => typeof e === "string") : [],
|
|
3212
|
+
crossdeckCustomerId: raw.crossdeckCustomerId ?? raw.user?.id ?? null,
|
|
3213
|
+
anonymousId: raw.anonymousId ?? input.anonymousId ?? null,
|
|
3214
|
+
identityError: raw.identityError ?? null
|
|
3215
|
+
};
|
|
3216
|
+
} catch (err) {
|
|
3217
|
+
this.debug.emit(
|
|
3218
|
+
"sdk.resolve_failed",
|
|
3219
|
+
`resolve() failed \u2014 fail-open blocked:false. ${err instanceof Error ? err.message : String(err)}`,
|
|
3220
|
+
{ error: err instanceof Error ? err.message : String(err) }
|
|
3221
|
+
);
|
|
3222
|
+
return failOpen();
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
/**
|
|
3226
|
+
* Convenience over {@link resolve}: just the boolean. Fail-open (false on any error).
|
|
3227
|
+
*
|
|
3228
|
+
* @experimental Preview — see {@link resolve}.
|
|
3229
|
+
*/
|
|
3230
|
+
async isBlocked(input, options) {
|
|
3231
|
+
return (await this.resolve(input, options)).blocked;
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* The OWNER's block verdict for the public-page path (Pattern B / §5b of the Blocking
|
|
3235
|
+
* developer guide) — "is the owner of this page blocked?", no session token required.
|
|
3236
|
+
* Pass the owner's recorded identifiers (`userId`/`email`/`domain`/`ip`; the `ip` is
|
|
3237
|
+
* their CREATION ip, not a live visitor's). Poll on a short TTL to invalidate your
|
|
3238
|
+
* public-page cache. Fail-open — any error → `{ blocked: false, degraded: true }`.
|
|
3239
|
+
*
|
|
3240
|
+
* @experimental Preview — see {@link resolve}.
|
|
3241
|
+
*/
|
|
3242
|
+
async getOwnerStatus(input, options) {
|
|
3243
|
+
const query = {
|
|
3244
|
+
userId: input?.userId || void 0,
|
|
3245
|
+
email: input?.email || void 0,
|
|
3246
|
+
domain: input?.domain || void 0,
|
|
3247
|
+
ip: input?.ip || void 0
|
|
3248
|
+
};
|
|
3249
|
+
if (!query.userId && !query.email && !query.domain && !query.ip) {
|
|
3250
|
+
this.debug.emit(
|
|
3251
|
+
"sdk.owner_status_missing_identity",
|
|
3252
|
+
"getOwnerStatus() needs at least one of userId/email/domain/ip; returning fail-open blocked:false."
|
|
3253
|
+
);
|
|
3254
|
+
return { blocked: false, blockReason: null, blockedKey: null, degraded: true };
|
|
3255
|
+
}
|
|
3256
|
+
try {
|
|
3257
|
+
const raw = await this.http.request("GET", "/trust/status", {
|
|
3258
|
+
query,
|
|
3259
|
+
signal: options?.signal,
|
|
3260
|
+
timeoutMs: options?.timeoutMs
|
|
3261
|
+
});
|
|
3262
|
+
return {
|
|
3263
|
+
blocked: raw.blocked === true,
|
|
3264
|
+
blockReason: raw.blockReason ?? null,
|
|
3265
|
+
blockedKey: raw.blockedKey ?? null
|
|
3266
|
+
};
|
|
3267
|
+
} catch (err) {
|
|
3268
|
+
this.debug.emit(
|
|
3269
|
+
"sdk.owner_status_failed",
|
|
3270
|
+
`getOwnerStatus() failed \u2014 fail-open blocked:false. ${err instanceof Error ? err.message : String(err)}`,
|
|
3271
|
+
{ error: err instanceof Error ? err.message : String(err) }
|
|
3272
|
+
);
|
|
3273
|
+
return { blocked: false, blockReason: null, blockedKey: null, degraded: true };
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3152
3276
|
/**
|
|
3153
3277
|
* Synchronous entitlement check. Returns `true` iff the customer
|
|
3154
3278
|
* has the entitlement AND the cache entry is fresh (within
|
|
@@ -4596,8 +4720,8 @@ function normaliseSecrets(input) {
|
|
|
4596
4720
|
}
|
|
4597
4721
|
|
|
4598
4722
|
// src/_contracts-bundled.ts
|
|
4599
|
-
var BUNDLED_IN = "@cross-deck/node@1.
|
|
4600
|
-
var SDK_VERSION2 = "1.
|
|
4723
|
+
var BUNDLED_IN = "@cross-deck/node@1.10.0";
|
|
4724
|
+
var SDK_VERSION2 = "1.10.0";
|
|
4601
4725
|
var BUNDLED_CONTRACTS = Object.freeze([
|
|
4602
4726
|
{
|
|
4603
4727
|
"id": "contract-failed-payload-schema-lock",
|
|
@@ -4719,7 +4843,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4719
4843
|
"legal/security/index.html#diagnostic",
|
|
4720
4844
|
"legal/sdk-data/index.html#b-diagnostic"
|
|
4721
4845
|
],
|
|
4722
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4846
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
4723
4847
|
},
|
|
4724
4848
|
{
|
|
4725
4849
|
"id": "documentation-honesty",
|
|
@@ -4751,7 +4875,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4751
4875
|
],
|
|
4752
4876
|
"registeredAt": "2026-05-26",
|
|
4753
4877
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 7.1",
|
|
4754
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4878
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
4755
4879
|
},
|
|
4756
4880
|
{
|
|
4757
4881
|
"id": "error-envelope-shape",
|
|
@@ -4790,7 +4914,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4790
4914
|
],
|
|
4791
4915
|
"registeredAt": "2026-05-26",
|
|
4792
4916
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
|
|
4793
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4917
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
4794
4918
|
},
|
|
4795
4919
|
{
|
|
4796
4920
|
"id": "flush-interval-parity",
|
|
@@ -4835,7 +4959,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4835
4959
|
],
|
|
4836
4960
|
"registeredAt": "2026-05-26",
|
|
4837
4961
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
|
|
4838
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4962
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
4839
4963
|
},
|
|
4840
4964
|
{
|
|
4841
4965
|
"id": "idempotency-key-deterministic",
|
|
@@ -4940,7 +5064,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4940
5064
|
],
|
|
4941
5065
|
"registeredAt": "2026-05-26",
|
|
4942
5066
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
|
|
4943
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5067
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
4944
5068
|
},
|
|
4945
5069
|
{
|
|
4946
5070
|
"id": "invalid-input-rejected-natively",
|
|
@@ -4996,7 +5120,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4996
5120
|
],
|
|
4997
5121
|
"registeredAt": "2026-06-11",
|
|
4998
5122
|
"firstRegisteredIn": "swift trap-on-input class fix \u2014 first machine-tested Swift release",
|
|
4999
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5123
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
5000
5124
|
},
|
|
5001
5125
|
{
|
|
5002
5126
|
"id": "node-pii-scrubber",
|
|
@@ -5035,7 +5159,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5035
5159
|
],
|
|
5036
5160
|
"registeredAt": "2026-05-26",
|
|
5037
5161
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.1",
|
|
5038
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5162
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
5039
5163
|
},
|
|
5040
5164
|
{
|
|
5041
5165
|
"id": "node-shutdown-awaits-flush",
|
|
@@ -5068,7 +5192,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5068
5192
|
],
|
|
5069
5193
|
"registeredAt": "2026-05-26",
|
|
5070
5194
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.4",
|
|
5071
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5195
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
5072
5196
|
},
|
|
5073
5197
|
{
|
|
5074
5198
|
"id": "sdk-error-codes-catalogue",
|
|
@@ -5113,7 +5237,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5113
5237
|
],
|
|
5114
5238
|
"registeredAt": "2026-05-26",
|
|
5115
5239
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
|
|
5116
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5240
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
5117
5241
|
},
|
|
5118
5242
|
{
|
|
5119
5243
|
"id": "sync-purchases-funnel-parity",
|
|
@@ -5146,7 +5270,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5146
5270
|
],
|
|
5147
5271
|
"registeredAt": "2026-05-26",
|
|
5148
5272
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
|
|
5149
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5273
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
5150
5274
|
},
|
|
5151
5275
|
{
|
|
5152
5276
|
"id": "verifier-timestamp-mandatory",
|
|
@@ -5200,7 +5324,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5200
5324
|
],
|
|
5201
5325
|
"registeredAt": "2026-05-26",
|
|
5202
5326
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 7.2",
|
|
5203
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5327
|
+
"bundledIn": "@cross-deck/node@1.10.0"
|
|
5204
5328
|
}
|
|
5205
5329
|
]);
|
|
5206
5330
|
|