@cross-deck/node 1.11.0 → 1.12.1

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 CHANGED
@@ -6,6 +6,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.12.1] — 2026-07-23
10
+
11
+ - **Reliability telemetry re-pointed to the live project.** The internal `crossdeck.contract_failed` self-check channel used a retired reliability-workspace key that had begun returning `401 invalid_api_key`, silently dropping failures; it now targets the live "Crossdeck Health Check" project. Internal SDK-health only — never touches your dashboard or your app. (No API changes; the anonymous-`reset()` hardening in the other SDKs does not apply — the Node SDK has no persistent device identity to churn.)
12
+
13
+ ## [1.12.0] — 2026-07-01
14
+
15
+ **Added — `blockEventId`: the Crossdeck-hosted block page (v2 preview).**
16
+ On a block, `resolve()` and `gate()` now return `blockEventId` — the id of the
17
+ canonical block interstitial Crossdeck hosts for that exact verdict. The
18
+ recommended dead-end becomes one line:
19
+
20
+ ```ts
21
+ if (blocked && blockEventId) {
22
+ res.redirect(`https://api.cross-deck.com/v1/trust/page/${blockEventId}`);
23
+ }
24
+ ```
25
+
26
+ Crossdeck serves the branded "Access paused" page — a verification receipt
27
+ (application name, timestamp, the quotable `reference`) and a **Contact
28
+ support** action that mails the project owner's real recorded email — so you
29
+ never hand-build, fork, or drift the block screen. The id is opaque
30
+ (`cd_blk_…`, no PII in the URL) and present only on an explicit block;
31
+ fail-open behaviour is unchanged (never redirect on error/timeout).
32
+ `ResolveResult` and `GateVerdict` carry the new optional field — additive,
33
+ non-breaking. Still `@experimental` until the Crossdeck v2 launch.
34
+
9
35
  ## [1.11.0] — 2026-06-30
10
36
 
11
37
  **Added — block `reference` and `supportEmail` on the trust surface (still v2 preview).**
package/README.md CHANGED
@@ -223,16 +223,29 @@ Blocking is **entitlements, inverted**: `getEntitlements()` asks *"can this user
223
223
  There are three doors. Pick by who's at it:
224
224
 
225
225
  ```ts
226
+ // The Crossdeck-hosted block page — the recommended dead-end for doors 1 + 2. One
227
+ // constant today; swaps to https://block.cross-deck.com when the alias goes live.
228
+ const BLOCK_PAGE = "https://api.cross-deck.com/v1/trust/page";
229
+
226
230
  // 1. A user you ALREADY KNOW — a bare userId rides the backbone (no token, no setup).
227
231
  // Crossdeck matches on the email it already holds. The common case.
228
- const { blocked, blockReason } = await crossdeck.resolve({ userId, ip: req.ip });
229
- if (blocked) { await signOut(); return showSuspended(blockReason); }
232
+ const { blocked, blockEventId } = await crossdeck.resolve({ userId, ip: req.ip });
233
+ if (blocked) {
234
+ await signOut();
235
+ return blockEventId
236
+ ? res.redirect(`${BLOCK_PAGE}/${blockEventId}`) // Crossdeck serves the branded page
237
+ : res.redirect("/suspended"); // rare: mint unavailable → your fallback
238
+ }
230
239
  // or just the boolean: if (await crossdeck.isBlocked({ userId })) …
231
240
 
232
241
  // 2. A BRAND-NEW signup Crossdeck has never seen — block by the two strings you have
233
242
  // at signup, their email + ip. Call it BEFORE you create the account.
234
243
  const gate = await crossdeck.gate({ email, ip: req.ip });
235
- if (gate.action === "block") return rejectSignup(gate.blockReason);
244
+ if (gate.action === "block") {
245
+ return gate.blockEventId
246
+ ? res.redirect(`${BLOCK_PAGE}/${gate.blockEventId}`)
247
+ : rejectSignup(gate.blockReason);
248
+ }
236
249
 
237
250
  // 3. A PUBLIC PAGE — "is the owner of this page blocked?" (no session token). Poll on a
238
251
  // short TTL to invalidate your cache; the ip is the owner's CREATION ip.
@@ -240,6 +253,15 @@ const owner = await crossdeck.getOwnerStatus({ userId: ownerId });
240
253
  if (owner.blocked) await takePageOffline(ownerId);
241
254
  ```
242
255
 
256
+ **Why redirect instead of building your own screen?** Crossdeck hosts the canonical block
257
+ interstitial — "Access paused", a verification receipt (application name, timestamp, an
258
+ opaque `reference` the person can quote to support), and a **Contact support** action that
259
+ mails the project owner's real recorded email. One page, centrally maintained, updated for
260
+ every integrator at once — you never fork it, restyle it, or let it drift. Redirect **only
261
+ on an explicit block** (`blocked === true` / `action === "block"`): on error or timeout the
262
+ SDK fails open and you let the user through — never send a real user to a block page over
263
+ a network blip.
264
+
243
265
  **Recommended server pattern (no issuer registration):** if your backend runs Firebase
244
266
  Admin, verify the ID token **locally** for the authoritative uid, then send that as a bare
245
267
  `userId` — you keep the cryptographic proof on your side and Crossdeck needs zero setup:
@@ -1,4 +1,4 @@
1
- import { x as CrossdeckServer } from '../crossdeck-server-DAQX_7rm.mjs';
1
+ import { x as CrossdeckServer } from '../crossdeck-server-CuTGwrwx.mjs';
2
2
  import 'node:events';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { x as CrossdeckServer } from '../crossdeck-server-DAQX_7rm.js';
1
+ import { x as CrossdeckServer } from '../crossdeck-server-CuTGwrwx.js';
2
2
  import 'node:events';
3
3
 
4
4
  /**
@@ -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.11.0";
187
- readonly bundledIn: "@cross-deck/node@1.11.0";
186
+ readonly sdkVersion: "1.12.0";
187
+ readonly bundledIn: "@cross-deck/node@1.12.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
@@ -484,6 +484,14 @@ interface BlockVerdict {
484
484
  * real operator by default. Override with a dedicated address if you have one.
485
485
  */
486
486
  supportEmail?: string | null;
487
+ /**
488
+ * The id of the CD-hosted block interstitial minted for THIS verdict — present only when
489
+ * blocked. The recommended dead-end is one line:
490
+ * `redirect(\`https://api.cross-deck.com/v1/trust/page/${blockEventId}\`)` — Crossdeck
491
+ * serves the canonical branded page (receipt + reference + faithful support contact), so
492
+ * you never hand-build or fork the block screen. Opaque (`cd_blk_…`), no PII in the URL.
493
+ */
494
+ blockEventId?: string | null;
487
495
  /**
488
496
  * True when this is the fail-open default (Crossdeck unreachable / identity unresolved).
489
497
  * `blocked` is `false`; log it for observability, never treat it as a real "not blocked".
@@ -562,6 +570,8 @@ interface GateVerdict {
562
570
  reference?: string | null;
563
571
  /** Recorded support contact (project owner's signup email), present only on `block` — the blocked screen's "Contact support" default. */
564
572
  supportEmail?: string | null;
573
+ /** CD-hosted block-page id (`cd_blk_…`), present only on `block` — redirect the rejected signup to `https://api.cross-deck.com/v1/trust/page/${blockEventId}` and Crossdeck serves the canonical branded dead-end. */
574
+ blockEventId?: string | null;
565
575
  /** True when this is the fail-open default (Crossdeck unreachable). `allow` is `true`. */
566
576
  degraded?: boolean;
567
577
  }
@@ -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.11.0";
187
- readonly bundledIn: "@cross-deck/node@1.11.0";
186
+ readonly sdkVersion: "1.12.0";
187
+ readonly bundledIn: "@cross-deck/node@1.12.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
@@ -484,6 +484,14 @@ interface BlockVerdict {
484
484
  * real operator by default. Override with a dedicated address if you have one.
485
485
  */
486
486
  supportEmail?: string | null;
487
+ /**
488
+ * The id of the CD-hosted block interstitial minted for THIS verdict — present only when
489
+ * blocked. The recommended dead-end is one line:
490
+ * `redirect(\`https://api.cross-deck.com/v1/trust/page/${blockEventId}\`)` — Crossdeck
491
+ * serves the canonical branded page (receipt + reference + faithful support contact), so
492
+ * you never hand-build or fork the block screen. Opaque (`cd_blk_…`), no PII in the URL.
493
+ */
494
+ blockEventId?: string | null;
487
495
  /**
488
496
  * True when this is the fail-open default (Crossdeck unreachable / identity unresolved).
489
497
  * `blocked` is `false`; log it for observability, never treat it as a real "not blocked".
@@ -562,6 +570,8 @@ interface GateVerdict {
562
570
  reference?: string | null;
563
571
  /** Recorded support contact (project owner's signup email), present only on `block` — the blocked screen's "Contact support" default. */
564
572
  supportEmail?: string | null;
573
+ /** CD-hosted block-page id (`cd_blk_…`), present only on `block` — redirect the rejected signup to `https://api.cross-deck.com/v1/trust/page/${blockEventId}` and Crossdeck serves the canonical branded dead-end. */
574
+ blockEventId?: string | null;
565
575
  /** True when this is the fail-open default (Crossdeck unreachable). `allow` is `true`. */
566
576
  degraded?: boolean;
567
577
  }
package/dist/index.cjs CHANGED
@@ -387,12 +387,12 @@ function byteLength(s) {
387
387
  var https = __toESM(require("https"));
388
388
 
389
389
  // src/_version.ts
390
- var SDK_VERSION = "1.11.0";
390
+ var SDK_VERSION = "1.12.1";
391
391
  var SDK_NAME = "@cross-deck/node";
392
392
 
393
393
  // src/_diagnostic-telemetry.ts
394
394
  var DIAGNOSTIC_TELEMETRY_ENDPOINT = "https://api.cross-deck.com/v1/sdk/diagnostic";
395
- var DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY = "cd_pub_live_9490e7aa029c432abf";
395
+ var DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY = "cd_pub_live_eea2f84dbb834a45b7";
396
396
  function isDiagnosticTelemetryEnabled() {
397
397
  return !DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY.startsWith(
398
398
  "cd_pub_RELIABILITY_PLACEHOLDER"
@@ -3209,6 +3209,7 @@ var CrossdeckServer = class extends import_node_events.EventEmitter {
3209
3209
  blockedKey: raw.blockedKey ?? null,
3210
3210
  reference: raw.reference ?? null,
3211
3211
  supportEmail: raw.supportEmail ?? null,
3212
+ blockEventId: raw.blockEventId ?? null,
3212
3213
  status: typeof raw.status === "string" ? raw.status : "active",
3213
3214
  entitlements: Array.isArray(raw.entitlements) ? raw.entitlements.filter((e) => typeof e === "string") : [],
3214
3215
  crossdeckCustomerId: raw.crossdeckCustomerId ?? raw.user?.id ?? null,
@@ -3320,7 +3321,8 @@ var CrossdeckServer = class extends import_node_events.EventEmitter {
3320
3321
  blockReason: raw.blockReason ?? null,
3321
3322
  blockedKey: raw.blockedKey ?? null,
3322
3323
  reference: raw.reference ?? null,
3323
- supportEmail: raw.supportEmail ?? null
3324
+ supportEmail: raw.supportEmail ?? null,
3325
+ blockEventId: raw.blockEventId ?? null
3324
3326
  };
3325
3327
  } catch (err) {
3326
3328
  this.debug.emit(
@@ -4778,8 +4780,8 @@ function normaliseSecrets(input) {
4778
4780
  }
4779
4781
 
4780
4782
  // src/_contracts-bundled.ts
4781
- var BUNDLED_IN = "@cross-deck/node@1.11.0";
4782
- var SDK_VERSION2 = "1.11.0";
4783
+ var BUNDLED_IN = "@cross-deck/node@1.12.0";
4784
+ var SDK_VERSION2 = "1.12.0";
4783
4785
  var BUNDLED_CONTRACTS = Object.freeze([
4784
4786
  {
4785
4787
  "id": "contract-failed-payload-schema-lock",
@@ -4901,7 +4903,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
4901
4903
  "legal/security/index.html#diagnostic",
4902
4904
  "legal/sdk-data/index.html#b-diagnostic"
4903
4905
  ],
4904
- "bundledIn": "@cross-deck/node@1.11.0"
4906
+ "bundledIn": "@cross-deck/node@1.12.0"
4905
4907
  },
4906
4908
  {
4907
4909
  "id": "documentation-honesty",
@@ -4933,7 +4935,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
4933
4935
  ],
4934
4936
  "registeredAt": "2026-05-26",
4935
4937
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 7.1",
4936
- "bundledIn": "@cross-deck/node@1.11.0"
4938
+ "bundledIn": "@cross-deck/node@1.12.0"
4937
4939
  },
4938
4940
  {
4939
4941
  "id": "error-envelope-shape",
@@ -4972,7 +4974,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
4972
4974
  ],
4973
4975
  "registeredAt": "2026-05-26",
4974
4976
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
4975
- "bundledIn": "@cross-deck/node@1.11.0"
4977
+ "bundledIn": "@cross-deck/node@1.12.0"
4976
4978
  },
4977
4979
  {
4978
4980
  "id": "flush-interval-parity",
@@ -5017,7 +5019,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5017
5019
  ],
5018
5020
  "registeredAt": "2026-05-26",
5019
5021
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
5020
- "bundledIn": "@cross-deck/node@1.11.0"
5022
+ "bundledIn": "@cross-deck/node@1.12.0"
5021
5023
  },
5022
5024
  {
5023
5025
  "id": "idempotency-key-deterministic",
@@ -5122,7 +5124,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5122
5124
  ],
5123
5125
  "registeredAt": "2026-05-26",
5124
5126
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
5125
- "bundledIn": "@cross-deck/node@1.11.0"
5127
+ "bundledIn": "@cross-deck/node@1.12.0"
5126
5128
  },
5127
5129
  {
5128
5130
  "id": "invalid-input-rejected-natively",
@@ -5178,7 +5180,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5178
5180
  ],
5179
5181
  "registeredAt": "2026-06-11",
5180
5182
  "firstRegisteredIn": "swift trap-on-input class fix \u2014 first machine-tested Swift release",
5181
- "bundledIn": "@cross-deck/node@1.11.0"
5183
+ "bundledIn": "@cross-deck/node@1.12.0"
5182
5184
  },
5183
5185
  {
5184
5186
  "id": "node-pii-scrubber",
@@ -5217,7 +5219,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5217
5219
  ],
5218
5220
  "registeredAt": "2026-05-26",
5219
5221
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.1",
5220
- "bundledIn": "@cross-deck/node@1.11.0"
5222
+ "bundledIn": "@cross-deck/node@1.12.0"
5221
5223
  },
5222
5224
  {
5223
5225
  "id": "node-shutdown-awaits-flush",
@@ -5250,7 +5252,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5250
5252
  ],
5251
5253
  "registeredAt": "2026-05-26",
5252
5254
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.4",
5253
- "bundledIn": "@cross-deck/node@1.11.0"
5255
+ "bundledIn": "@cross-deck/node@1.12.0"
5254
5256
  },
5255
5257
  {
5256
5258
  "id": "sdk-error-codes-catalogue",
@@ -5295,7 +5297,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5295
5297
  ],
5296
5298
  "registeredAt": "2026-05-26",
5297
5299
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
5298
- "bundledIn": "@cross-deck/node@1.11.0"
5300
+ "bundledIn": "@cross-deck/node@1.12.0"
5299
5301
  },
5300
5302
  {
5301
5303
  "id": "sync-purchases-funnel-parity",
@@ -5328,7 +5330,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5328
5330
  ],
5329
5331
  "registeredAt": "2026-05-26",
5330
5332
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
5331
- "bundledIn": "@cross-deck/node@1.11.0"
5333
+ "bundledIn": "@cross-deck/node@1.12.0"
5332
5334
  },
5333
5335
  {
5334
5336
  "id": "verifier-timestamp-mandatory",
@@ -5382,7 +5384,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5382
5384
  ],
5383
5385
  "registeredAt": "2026-05-26",
5384
5386
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 7.2",
5385
- "bundledIn": "@cross-deck/node@1.11.0"
5387
+ "bundledIn": "@cross-deck/node@1.12.0"
5386
5388
  }
5387
5389
  ]);
5388
5390