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