@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.mjs
CHANGED
|
@@ -67,7 +67,7 @@ function typeMapForStatus(status) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// src/_version.ts
|
|
70
|
-
var SDK_VERSION = "1.6.
|
|
70
|
+
var SDK_VERSION = "1.6.4";
|
|
71
71
|
var SDK_NAME = "@cross-deck/web";
|
|
72
72
|
|
|
73
73
|
// src/http.ts
|
|
@@ -2833,6 +2833,15 @@ var VerifierReporter = class {
|
|
|
2833
2833
|
constructor(ctx) {
|
|
2834
2834
|
this.ctx = ctx;
|
|
2835
2835
|
this.reentrancyDepth = 0;
|
|
2836
|
+
/** The live verifier set — attached by the SDK right after init so the
|
|
2837
|
+
* emit path can schema-lock its OWN outgoing telemetry against the real
|
|
2838
|
+
* payload. Null until attached (boot-time reports still work without it). */
|
|
2839
|
+
this.verifiers = null;
|
|
2840
|
+
}
|
|
2841
|
+
/** Hand the reporter the live verifier set so `reportFail` can run the
|
|
2842
|
+
* `onReportContractFailure` hook on the exact payload it's about to emit. */
|
|
2843
|
+
attachVerifiers(verifiers) {
|
|
2844
|
+
this.verifiers = verifiers;
|
|
2836
2845
|
}
|
|
2837
2846
|
/**
|
|
2838
2847
|
* Report a verifier result. Pass `operation` for hot-path results
|
|
@@ -2861,7 +2870,7 @@ var VerifierReporter = class {
|
|
|
2861
2870
|
if (this.reentrancyDepth > 0) return;
|
|
2862
2871
|
this.reentrancyDepth += 1;
|
|
2863
2872
|
try {
|
|
2864
|
-
|
|
2873
|
+
const payload = {
|
|
2865
2874
|
contract_id: result.contractId,
|
|
2866
2875
|
sdk_version: this.ctx.sdkVersion,
|
|
2867
2876
|
sdk_platform: "web",
|
|
@@ -2869,7 +2878,13 @@ var VerifierReporter = class {
|
|
|
2869
2878
|
run_context: this.ctx.runContext,
|
|
2870
2879
|
run_id: this.ctx.runId,
|
|
2871
2880
|
verification_phase: phase
|
|
2872
|
-
}
|
|
2881
|
+
};
|
|
2882
|
+
if (this.verifiers) {
|
|
2883
|
+
runOnReportContractFailure(this.verifiers, this, this.ctx, {
|
|
2884
|
+
outgoingPayload: payload
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
this.ctx.emitTelemetry(payload);
|
|
2873
2888
|
} finally {
|
|
2874
2889
|
this.reentrancyDepth -= 1;
|
|
2875
2890
|
}
|
|
@@ -3159,9 +3174,31 @@ var VERIFIER_FLUSH_INTERVAL_PARITY = {
|
|
|
3159
3174
|
"eventFlushIntervalMs default = 2000ms (Web/Node/RN/Swift/Android parity)",
|
|
3160
3175
|
nowMs() - t0
|
|
3161
3176
|
);
|
|
3177
|
+
},
|
|
3178
|
+
// Hot-path hook — on every real track() we re-affirm the LIVE configured
|
|
3179
|
+
// flush interval (carried on the observation), so the parity guarantee is
|
|
3180
|
+
// verified against the running SDK in the customer flow, not just a
|
|
3181
|
+
// canonical constant at boot. It can't change per-event, but observing it
|
|
3182
|
+
// per-event is what makes the contract genuinely runtime-verified.
|
|
3183
|
+
hooks: {
|
|
3184
|
+
onTrack(obs) {
|
|
3185
|
+
const t0 = nowMs();
|
|
3186
|
+
const CANONICAL_DEFAULT_MS = 2e3;
|
|
3187
|
+
const ms = obs.flushIntervalMs;
|
|
3188
|
+
if (typeof ms !== "number" || ms < 100 || ms > 6e4) {
|
|
3189
|
+
return fail(
|
|
3190
|
+
"flush-interval-parity",
|
|
3191
|
+
`live eventFlushIntervalMs=${ms} outside reasonable bounds [100, 60000]`,
|
|
3192
|
+
nowMs() - t0
|
|
3193
|
+
);
|
|
3194
|
+
}
|
|
3195
|
+
return pass(
|
|
3196
|
+
"flush-interval-parity",
|
|
3197
|
+
ms === CANONICAL_DEFAULT_MS ? "live eventFlushIntervalMs = 2000ms (canonical parity value)" : `live eventFlushIntervalMs = ${ms}ms (permitted override; canonical default 2000ms)`,
|
|
3198
|
+
nowMs() - t0
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3162
3201
|
}
|
|
3163
|
-
// No hot-path hook — the flush interval is set once at start() and
|
|
3164
|
-
// never changes per-operation.
|
|
3165
3202
|
};
|
|
3166
3203
|
function buildFlushIntervalVerifier(configuredIntervalMs) {
|
|
3167
3204
|
return {
|
|
@@ -3288,6 +3325,26 @@ var CONTRACT_FAILED_FORBIDDEN_FIELDS = [
|
|
|
3288
3325
|
"referrer",
|
|
3289
3326
|
"deviceId"
|
|
3290
3327
|
];
|
|
3328
|
+
function contractFailedSchemaViolation(keys) {
|
|
3329
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
3330
|
+
...CONTRACT_FAILED_REQUIRED_FIELDS,
|
|
3331
|
+
...CONTRACT_FAILED_OPTIONAL_FIELDS
|
|
3332
|
+
]);
|
|
3333
|
+
const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
|
|
3334
|
+
for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
|
|
3335
|
+
if (!keys.includes(required)) return `missing required field: ${required}`;
|
|
3336
|
+
}
|
|
3337
|
+
for (const k of keys) {
|
|
3338
|
+
if (forbidden.has(k)) return `forbidden field on wire: ${k}`;
|
|
3339
|
+
}
|
|
3340
|
+
for (const k of keys) {
|
|
3341
|
+
if (!allowed.has(k)) {
|
|
3342
|
+
return `unrecognised field on wire: ${k} (not in required \u222A optional)`;
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
return null;
|
|
3346
|
+
}
|
|
3347
|
+
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`;
|
|
3291
3348
|
var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
|
|
3292
3349
|
contractId: "contract-failed-payload-schema-lock",
|
|
3293
3350
|
bootTest() {
|
|
@@ -3302,43 +3359,35 @@ var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
|
|
|
3302
3359
|
verification_phase: "boot"
|
|
3303
3360
|
};
|
|
3304
3361
|
const keys = Object.keys(syntheticPayload);
|
|
3305
|
-
const
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
]);
|
|
3309
|
-
const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
|
|
3310
|
-
for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
|
|
3311
|
-
if (!keys.includes(required)) {
|
|
3312
|
-
return fail(
|
|
3313
|
-
"contract-failed-payload-schema-lock",
|
|
3314
|
-
`missing required field: ${required}`,
|
|
3315
|
-
nowMs() - t0
|
|
3316
|
-
);
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
for (const k of keys) {
|
|
3320
|
-
if (forbidden.has(k)) {
|
|
3321
|
-
return fail(
|
|
3322
|
-
"contract-failed-payload-schema-lock",
|
|
3323
|
-
`forbidden field on wire: ${k}`,
|
|
3324
|
-
nowMs() - t0
|
|
3325
|
-
);
|
|
3326
|
-
}
|
|
3327
|
-
}
|
|
3328
|
-
for (const k of keys) {
|
|
3329
|
-
if (!allowed.has(k)) {
|
|
3330
|
-
return fail(
|
|
3331
|
-
"contract-failed-payload-schema-lock",
|
|
3332
|
-
`unrecognised field on wire: ${k} (not in required \u222A optional)`,
|
|
3333
|
-
nowMs() - t0
|
|
3334
|
-
);
|
|
3335
|
-
}
|
|
3362
|
+
const violation = contractFailedSchemaViolation(keys);
|
|
3363
|
+
if (violation) {
|
|
3364
|
+
return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
|
|
3336
3365
|
}
|
|
3337
3366
|
return pass(
|
|
3338
3367
|
"contract-failed-payload-schema-lock",
|
|
3339
|
-
`${keys.length}
|
|
3368
|
+
`${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
|
|
3340
3369
|
nowMs() - t0
|
|
3341
3370
|
);
|
|
3371
|
+
},
|
|
3372
|
+
// Hot-path hook — fires on the REAL reportFail emit (VerifierReporter,
|
|
3373
|
+
// inside the re-entrancy guard) against the exact payload about to go on
|
|
3374
|
+
// the wire. This is the assertion the guard at reportFail was built in
|
|
3375
|
+
// anticipation of: every reliability-channel write is schema-locked in the
|
|
3376
|
+
// field, against real data, not just the synthetic boot mirror.
|
|
3377
|
+
hooks: {
|
|
3378
|
+
onReportContractFailure(obs) {
|
|
3379
|
+
const t0 = nowMs();
|
|
3380
|
+
const keys = Object.keys(obs.outgoingPayload);
|
|
3381
|
+
const violation = contractFailedSchemaViolation(keys);
|
|
3382
|
+
if (violation) {
|
|
3383
|
+
return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
|
|
3384
|
+
}
|
|
3385
|
+
return pass(
|
|
3386
|
+
"contract-failed-payload-schema-lock",
|
|
3387
|
+
`${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
|
|
3388
|
+
nowMs() - t0
|
|
3389
|
+
);
|
|
3390
|
+
}
|
|
3342
3391
|
}
|
|
3343
3392
|
};
|
|
3344
3393
|
var BACKEND_WIRE_CODES = Object.freeze([
|
|
@@ -3389,6 +3438,39 @@ var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
|
|
|
3389
3438
|
nowMs() - t0
|
|
3390
3439
|
);
|
|
3391
3440
|
}
|
|
3441
|
+
},
|
|
3442
|
+
// Hot-path hook — when the SDK parses a real wire error, assert the code
|
|
3443
|
+
// the customer actually hit carries usable remediation in the shipped
|
|
3444
|
+
// catalogue. Scoped to BACKEND_WIRE_CODES (the contract's set); other
|
|
3445
|
+
// codes are out of scope and pass. This turns the catalogue from a
|
|
3446
|
+
// boot-only completeness claim into a per-error field assertion: a backend
|
|
3447
|
+
// code that ships without remediation is caught the first time a customer
|
|
3448
|
+
// hits it, not only if the off-by-default boot self-test ran.
|
|
3449
|
+
hooks: {
|
|
3450
|
+
onErrorParse(obs) {
|
|
3451
|
+
const t0 = nowMs();
|
|
3452
|
+
const code = obs.errorCode;
|
|
3453
|
+
if (!code || !BACKEND_WIRE_CODES.includes(code)) {
|
|
3454
|
+
return pass(
|
|
3455
|
+
"sdk-error-codes-catalogue",
|
|
3456
|
+
`wire code "${code || "(none)"}" is out of the backend-catalogue scope`,
|
|
3457
|
+
nowMs() - t0
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
const entry = getErrorCode(code);
|
|
3461
|
+
if (!entry || !entry.description || entry.description.trim().length === 0 || !entry.resolution || entry.resolution.trim().length === 0) {
|
|
3462
|
+
return fail(
|
|
3463
|
+
"sdk-error-codes-catalogue",
|
|
3464
|
+
`backend wire code "${code}" hit in the field has no description+resolution in the shipped catalogue`,
|
|
3465
|
+
nowMs() - t0
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
return pass(
|
|
3469
|
+
"sdk-error-codes-catalogue",
|
|
3470
|
+
`backend wire code "${code}" carries description + resolution`,
|
|
3471
|
+
nowMs() - t0
|
|
3472
|
+
);
|
|
3473
|
+
}
|
|
3392
3474
|
}
|
|
3393
3475
|
};
|
|
3394
3476
|
var STATIC_VERIFIERS = Object.freeze([
|
|
@@ -3512,6 +3594,24 @@ function runOnErrorParse(verifiers, reporter, ctx, obs) {
|
|
|
3512
3594
|
reporter.report(result, "hot_path", "errorParse");
|
|
3513
3595
|
}
|
|
3514
3596
|
}
|
|
3597
|
+
function runOnReportContractFailure(verifiers, reporter, ctx, obs) {
|
|
3598
|
+
if (ctx.disableContractAssertions) return;
|
|
3599
|
+
for (const verifier of verifiers) {
|
|
3600
|
+
const hook = verifier.hooks?.onReportContractFailure;
|
|
3601
|
+
if (!hook) continue;
|
|
3602
|
+
let result;
|
|
3603
|
+
try {
|
|
3604
|
+
result = hook(obs);
|
|
3605
|
+
} catch (err) {
|
|
3606
|
+
result = fail(
|
|
3607
|
+
verifier.contractId,
|
|
3608
|
+
`hook threw: ${err.message?.slice(0, 80) ?? "unknown"}`,
|
|
3609
|
+
0
|
|
3610
|
+
);
|
|
3611
|
+
}
|
|
3612
|
+
reporter.report(result, "hot_path", "reportContractFailure");
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3515
3615
|
function defaultDebugModeFlag() {
|
|
3516
3616
|
try {
|
|
3517
3617
|
if (typeof process !== "undefined" && process.env) {
|
|
@@ -4344,6 +4444,9 @@ var CrossdeckClient = class {
|
|
|
4344
4444
|
this.verifiers = null;
|
|
4345
4445
|
this.verifierReporter = null;
|
|
4346
4446
|
this.verifierCtx = null;
|
|
4447
|
+
// The live configured event-flush interval, surfaced to the track-path
|
|
4448
|
+
// verifier so flush-interval-parity validates the real cadence per event.
|
|
4449
|
+
this.flushIntervalMs = 2e3;
|
|
4347
4450
|
}
|
|
4348
4451
|
/**
|
|
4349
4452
|
* Boot the SDK. Idempotent — calling init twice with the same options
|
|
@@ -4623,6 +4726,8 @@ var CrossdeckClient = class {
|
|
|
4623
4726
|
this.verifierReporter = new VerifierReporter(this.verifierCtx);
|
|
4624
4727
|
const flushVerifier = buildFlushIntervalVerifier(opts.eventFlushIntervalMs);
|
|
4625
4728
|
this.verifiers = Object.freeze([...STATIC_VERIFIERS, flushVerifier]);
|
|
4729
|
+
this.flushIntervalMs = opts.eventFlushIntervalMs;
|
|
4730
|
+
this.verifierReporter.attachVerifiers(this.verifiers);
|
|
4626
4731
|
if (localDevMode) {
|
|
4627
4732
|
const verifyAtBootLocal = options.verifyContractsAtBoot ?? debugDefault;
|
|
4628
4733
|
if (verifyAtBootLocal && this.verifierReporter) {
|
|
@@ -5197,9 +5302,7 @@ var CrossdeckClient = class {
|
|
|
5197
5302
|
}
|
|
5198
5303
|
}
|
|
5199
5304
|
const supers = s.superProps.getSuperProperties();
|
|
5200
|
-
|
|
5201
|
-
if (!(k in enriched)) enriched[k] = supers[k];
|
|
5202
|
-
}
|
|
5305
|
+
Object.assign(enriched, supers);
|
|
5203
5306
|
const groupIds = s.superProps.getGroupIds();
|
|
5204
5307
|
if (Object.keys(groupIds).length > 0) {
|
|
5205
5308
|
enriched.$groups = groupIds;
|
|
@@ -5219,7 +5322,8 @@ var CrossdeckClient = class {
|
|
|
5219
5322
|
callerProperties: validation.properties,
|
|
5220
5323
|
superProperties: supers,
|
|
5221
5324
|
deviceProperties: s.deviceInfo,
|
|
5222
|
-
mergedProperties: enriched
|
|
5325
|
+
mergedProperties: enriched,
|
|
5326
|
+
flushIntervalMs: this.flushIntervalMs
|
|
5223
5327
|
});
|
|
5224
5328
|
}
|
|
5225
5329
|
if (!isError && !isWebVital) {
|