@cross-deck/web 1.6.3 → 1.6.4
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 +21 -0
- package/dist/contracts.json +12 -12
- package/dist/crossdeck.umd.min.js +2 -2
- package/dist/crossdeck.umd.min.js.map +1 -1
- package/dist/error-codes.json +1 -1
- package/dist/index.cjs +156 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.mjs +156 -52
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +145 -41
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +145 -41
- package/dist/react.mjs.map +1 -1
- package/dist/vue.cjs +145 -41
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.mjs +145 -41
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.cjs
CHANGED
|
@@ -93,7 +93,7 @@ function typeMapForStatus(status) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// src/_version.ts
|
|
96
|
-
var SDK_VERSION = "1.6.
|
|
96
|
+
var SDK_VERSION = "1.6.4";
|
|
97
97
|
var SDK_NAME = "@cross-deck/web";
|
|
98
98
|
|
|
99
99
|
// src/http.ts
|
|
@@ -2859,6 +2859,15 @@ var VerifierReporter = class {
|
|
|
2859
2859
|
constructor(ctx) {
|
|
2860
2860
|
this.ctx = ctx;
|
|
2861
2861
|
this.reentrancyDepth = 0;
|
|
2862
|
+
/** The live verifier set — attached by the SDK right after init so the
|
|
2863
|
+
* emit path can schema-lock its OWN outgoing telemetry against the real
|
|
2864
|
+
* payload. Null until attached (boot-time reports still work without it). */
|
|
2865
|
+
this.verifiers = null;
|
|
2866
|
+
}
|
|
2867
|
+
/** Hand the reporter the live verifier set so `reportFail` can run the
|
|
2868
|
+
* `onReportContractFailure` hook on the exact payload it's about to emit. */
|
|
2869
|
+
attachVerifiers(verifiers) {
|
|
2870
|
+
this.verifiers = verifiers;
|
|
2862
2871
|
}
|
|
2863
2872
|
/**
|
|
2864
2873
|
* Report a verifier result. Pass `operation` for hot-path results
|
|
@@ -2887,7 +2896,7 @@ var VerifierReporter = class {
|
|
|
2887
2896
|
if (this.reentrancyDepth > 0) return;
|
|
2888
2897
|
this.reentrancyDepth += 1;
|
|
2889
2898
|
try {
|
|
2890
|
-
|
|
2899
|
+
const payload = {
|
|
2891
2900
|
contract_id: result.contractId,
|
|
2892
2901
|
sdk_version: this.ctx.sdkVersion,
|
|
2893
2902
|
sdk_platform: "web",
|
|
@@ -2895,7 +2904,13 @@ var VerifierReporter = class {
|
|
|
2895
2904
|
run_context: this.ctx.runContext,
|
|
2896
2905
|
run_id: this.ctx.runId,
|
|
2897
2906
|
verification_phase: phase
|
|
2898
|
-
}
|
|
2907
|
+
};
|
|
2908
|
+
if (this.verifiers) {
|
|
2909
|
+
runOnReportContractFailure(this.verifiers, this, this.ctx, {
|
|
2910
|
+
outgoingPayload: payload
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
this.ctx.emitTelemetry(payload);
|
|
2899
2914
|
} finally {
|
|
2900
2915
|
this.reentrancyDepth -= 1;
|
|
2901
2916
|
}
|
|
@@ -3185,9 +3200,31 @@ var VERIFIER_FLUSH_INTERVAL_PARITY = {
|
|
|
3185
3200
|
"eventFlushIntervalMs default = 2000ms (Web/Node/RN/Swift/Android parity)",
|
|
3186
3201
|
nowMs() - t0
|
|
3187
3202
|
);
|
|
3203
|
+
},
|
|
3204
|
+
// Hot-path hook — on every real track() we re-affirm the LIVE configured
|
|
3205
|
+
// flush interval (carried on the observation), so the parity guarantee is
|
|
3206
|
+
// verified against the running SDK in the customer flow, not just a
|
|
3207
|
+
// canonical constant at boot. It can't change per-event, but observing it
|
|
3208
|
+
// per-event is what makes the contract genuinely runtime-verified.
|
|
3209
|
+
hooks: {
|
|
3210
|
+
onTrack(obs) {
|
|
3211
|
+
const t0 = nowMs();
|
|
3212
|
+
const CANONICAL_DEFAULT_MS = 2e3;
|
|
3213
|
+
const ms = obs.flushIntervalMs;
|
|
3214
|
+
if (typeof ms !== "number" || ms < 100 || ms > 6e4) {
|
|
3215
|
+
return fail(
|
|
3216
|
+
"flush-interval-parity",
|
|
3217
|
+
`live eventFlushIntervalMs=${ms} outside reasonable bounds [100, 60000]`,
|
|
3218
|
+
nowMs() - t0
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
return pass(
|
|
3222
|
+
"flush-interval-parity",
|
|
3223
|
+
ms === CANONICAL_DEFAULT_MS ? "live eventFlushIntervalMs = 2000ms (canonical parity value)" : `live eventFlushIntervalMs = ${ms}ms (permitted override; canonical default 2000ms)`,
|
|
3224
|
+
nowMs() - t0
|
|
3225
|
+
);
|
|
3226
|
+
}
|
|
3188
3227
|
}
|
|
3189
|
-
// No hot-path hook — the flush interval is set once at start() and
|
|
3190
|
-
// never changes per-operation.
|
|
3191
3228
|
};
|
|
3192
3229
|
function buildFlushIntervalVerifier(configuredIntervalMs) {
|
|
3193
3230
|
return {
|
|
@@ -3314,6 +3351,26 @@ var CONTRACT_FAILED_FORBIDDEN_FIELDS = [
|
|
|
3314
3351
|
"referrer",
|
|
3315
3352
|
"deviceId"
|
|
3316
3353
|
];
|
|
3354
|
+
function contractFailedSchemaViolation(keys) {
|
|
3355
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
3356
|
+
...CONTRACT_FAILED_REQUIRED_FIELDS,
|
|
3357
|
+
...CONTRACT_FAILED_OPTIONAL_FIELDS
|
|
3358
|
+
]);
|
|
3359
|
+
const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
|
|
3360
|
+
for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
|
|
3361
|
+
if (!keys.includes(required)) return `missing required field: ${required}`;
|
|
3362
|
+
}
|
|
3363
|
+
for (const k of keys) {
|
|
3364
|
+
if (forbidden.has(k)) return `forbidden field on wire: ${k}`;
|
|
3365
|
+
}
|
|
3366
|
+
for (const k of keys) {
|
|
3367
|
+
if (!allowed.has(k)) {
|
|
3368
|
+
return `unrecognised field on wire: ${k} (not in required \u222A optional)`;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
return null;
|
|
3372
|
+
}
|
|
3373
|
+
var CONTRACT_FAILED_OK_EVIDENCE = `fields \u2286 required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) \u222A optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`;
|
|
3317
3374
|
var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
|
|
3318
3375
|
contractId: "contract-failed-payload-schema-lock",
|
|
3319
3376
|
bootTest() {
|
|
@@ -3328,43 +3385,35 @@ var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
|
|
|
3328
3385
|
verification_phase: "boot"
|
|
3329
3386
|
};
|
|
3330
3387
|
const keys = Object.keys(syntheticPayload);
|
|
3331
|
-
const
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
]);
|
|
3335
|
-
const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
|
|
3336
|
-
for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
|
|
3337
|
-
if (!keys.includes(required)) {
|
|
3338
|
-
return fail(
|
|
3339
|
-
"contract-failed-payload-schema-lock",
|
|
3340
|
-
`missing required field: ${required}`,
|
|
3341
|
-
nowMs() - t0
|
|
3342
|
-
);
|
|
3343
|
-
}
|
|
3344
|
-
}
|
|
3345
|
-
for (const k of keys) {
|
|
3346
|
-
if (forbidden.has(k)) {
|
|
3347
|
-
return fail(
|
|
3348
|
-
"contract-failed-payload-schema-lock",
|
|
3349
|
-
`forbidden field on wire: ${k}`,
|
|
3350
|
-
nowMs() - t0
|
|
3351
|
-
);
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
|
-
for (const k of keys) {
|
|
3355
|
-
if (!allowed.has(k)) {
|
|
3356
|
-
return fail(
|
|
3357
|
-
"contract-failed-payload-schema-lock",
|
|
3358
|
-
`unrecognised field on wire: ${k} (not in required \u222A optional)`,
|
|
3359
|
-
nowMs() - t0
|
|
3360
|
-
);
|
|
3361
|
-
}
|
|
3388
|
+
const violation = contractFailedSchemaViolation(keys);
|
|
3389
|
+
if (violation) {
|
|
3390
|
+
return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
|
|
3362
3391
|
}
|
|
3363
3392
|
return pass(
|
|
3364
3393
|
"contract-failed-payload-schema-lock",
|
|
3365
|
-
`${keys.length}
|
|
3394
|
+
`${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
|
|
3366
3395
|
nowMs() - t0
|
|
3367
3396
|
);
|
|
3397
|
+
},
|
|
3398
|
+
// Hot-path hook — fires on the REAL reportFail emit (VerifierReporter,
|
|
3399
|
+
// inside the re-entrancy guard) against the exact payload about to go on
|
|
3400
|
+
// the wire. This is the assertion the guard at reportFail was built in
|
|
3401
|
+
// anticipation of: every reliability-channel write is schema-locked in the
|
|
3402
|
+
// field, against real data, not just the synthetic boot mirror.
|
|
3403
|
+
hooks: {
|
|
3404
|
+
onReportContractFailure(obs) {
|
|
3405
|
+
const t0 = nowMs();
|
|
3406
|
+
const keys = Object.keys(obs.outgoingPayload);
|
|
3407
|
+
const violation = contractFailedSchemaViolation(keys);
|
|
3408
|
+
if (violation) {
|
|
3409
|
+
return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
|
|
3410
|
+
}
|
|
3411
|
+
return pass(
|
|
3412
|
+
"contract-failed-payload-schema-lock",
|
|
3413
|
+
`${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
|
|
3414
|
+
nowMs() - t0
|
|
3415
|
+
);
|
|
3416
|
+
}
|
|
3368
3417
|
}
|
|
3369
3418
|
};
|
|
3370
3419
|
var BACKEND_WIRE_CODES = Object.freeze([
|
|
@@ -3415,6 +3464,39 @@ var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
|
|
|
3415
3464
|
nowMs() - t0
|
|
3416
3465
|
);
|
|
3417
3466
|
}
|
|
3467
|
+
},
|
|
3468
|
+
// Hot-path hook — when the SDK parses a real wire error, assert the code
|
|
3469
|
+
// the customer actually hit carries usable remediation in the shipped
|
|
3470
|
+
// catalogue. Scoped to BACKEND_WIRE_CODES (the contract's set); other
|
|
3471
|
+
// codes are out of scope and pass. This turns the catalogue from a
|
|
3472
|
+
// boot-only completeness claim into a per-error field assertion: a backend
|
|
3473
|
+
// code that ships without remediation is caught the first time a customer
|
|
3474
|
+
// hits it, not only if the off-by-default boot self-test ran.
|
|
3475
|
+
hooks: {
|
|
3476
|
+
onErrorParse(obs) {
|
|
3477
|
+
const t0 = nowMs();
|
|
3478
|
+
const code = obs.errorCode;
|
|
3479
|
+
if (!code || !BACKEND_WIRE_CODES.includes(code)) {
|
|
3480
|
+
return pass(
|
|
3481
|
+
"sdk-error-codes-catalogue",
|
|
3482
|
+
`wire code "${code || "(none)"}" is out of the backend-catalogue scope`,
|
|
3483
|
+
nowMs() - t0
|
|
3484
|
+
);
|
|
3485
|
+
}
|
|
3486
|
+
const entry = getErrorCode(code);
|
|
3487
|
+
if (!entry || !entry.description || entry.description.trim().length === 0 || !entry.resolution || entry.resolution.trim().length === 0) {
|
|
3488
|
+
return fail(
|
|
3489
|
+
"sdk-error-codes-catalogue",
|
|
3490
|
+
`backend wire code "${code}" hit in the field has no description+resolution in the shipped catalogue`,
|
|
3491
|
+
nowMs() - t0
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
return pass(
|
|
3495
|
+
"sdk-error-codes-catalogue",
|
|
3496
|
+
`backend wire code "${code}" carries description + resolution`,
|
|
3497
|
+
nowMs() - t0
|
|
3498
|
+
);
|
|
3499
|
+
}
|
|
3418
3500
|
}
|
|
3419
3501
|
};
|
|
3420
3502
|
var STATIC_VERIFIERS = Object.freeze([
|
|
@@ -3538,6 +3620,24 @@ function runOnErrorParse(verifiers, reporter, ctx, obs) {
|
|
|
3538
3620
|
reporter.report(result, "hot_path", "errorParse");
|
|
3539
3621
|
}
|
|
3540
3622
|
}
|
|
3623
|
+
function runOnReportContractFailure(verifiers, reporter, ctx, obs) {
|
|
3624
|
+
if (ctx.disableContractAssertions) return;
|
|
3625
|
+
for (const verifier of verifiers) {
|
|
3626
|
+
const hook = verifier.hooks?.onReportContractFailure;
|
|
3627
|
+
if (!hook) continue;
|
|
3628
|
+
let result;
|
|
3629
|
+
try {
|
|
3630
|
+
result = hook(obs);
|
|
3631
|
+
} catch (err) {
|
|
3632
|
+
result = fail(
|
|
3633
|
+
verifier.contractId,
|
|
3634
|
+
`hook threw: ${err.message?.slice(0, 80) ?? "unknown"}`,
|
|
3635
|
+
0
|
|
3636
|
+
);
|
|
3637
|
+
}
|
|
3638
|
+
reporter.report(result, "hot_path", "reportContractFailure");
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3541
3641
|
function defaultDebugModeFlag() {
|
|
3542
3642
|
try {
|
|
3543
3643
|
if (typeof process !== "undefined" && process.env) {
|
|
@@ -4370,6 +4470,9 @@ var CrossdeckClient = class {
|
|
|
4370
4470
|
this.verifiers = null;
|
|
4371
4471
|
this.verifierReporter = null;
|
|
4372
4472
|
this.verifierCtx = null;
|
|
4473
|
+
// The live configured event-flush interval, surfaced to the track-path
|
|
4474
|
+
// verifier so flush-interval-parity validates the real cadence per event.
|
|
4475
|
+
this.flushIntervalMs = 2e3;
|
|
4373
4476
|
}
|
|
4374
4477
|
/**
|
|
4375
4478
|
* Boot the SDK. Idempotent — calling init twice with the same options
|
|
@@ -4649,6 +4752,8 @@ var CrossdeckClient = class {
|
|
|
4649
4752
|
this.verifierReporter = new VerifierReporter(this.verifierCtx);
|
|
4650
4753
|
const flushVerifier = buildFlushIntervalVerifier(opts.eventFlushIntervalMs);
|
|
4651
4754
|
this.verifiers = Object.freeze([...STATIC_VERIFIERS, flushVerifier]);
|
|
4755
|
+
this.flushIntervalMs = opts.eventFlushIntervalMs;
|
|
4756
|
+
this.verifierReporter.attachVerifiers(this.verifiers);
|
|
4652
4757
|
if (localDevMode) {
|
|
4653
4758
|
const verifyAtBootLocal = options.verifyContractsAtBoot ?? debugDefault;
|
|
4654
4759
|
if (verifyAtBootLocal && this.verifierReporter) {
|
|
@@ -5223,9 +5328,7 @@ var CrossdeckClient = class {
|
|
|
5223
5328
|
}
|
|
5224
5329
|
}
|
|
5225
5330
|
const supers = s.superProps.getSuperProperties();
|
|
5226
|
-
|
|
5227
|
-
if (!(k in enriched)) enriched[k] = supers[k];
|
|
5228
|
-
}
|
|
5331
|
+
Object.assign(enriched, supers);
|
|
5229
5332
|
const groupIds = s.superProps.getGroupIds();
|
|
5230
5333
|
if (Object.keys(groupIds).length > 0) {
|
|
5231
5334
|
enriched.$groups = groupIds;
|
|
@@ -5245,7 +5348,8 @@ var CrossdeckClient = class {
|
|
|
5245
5348
|
callerProperties: validation.properties,
|
|
5246
5349
|
superProperties: supers,
|
|
5247
5350
|
deviceProperties: s.deviceInfo,
|
|
5248
|
-
mergedProperties: enriched
|
|
5351
|
+
mergedProperties: enriched,
|
|
5352
|
+
flushIntervalMs: this.flushIntervalMs
|
|
5249
5353
|
});
|
|
5250
5354
|
}
|
|
5251
5355
|
if (!isError && !isWebVital) {
|