@a4anthony/proctorkit-sdk 0.1.1 → 0.2.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/README.md +13 -3
- package/dist/delivery-readiness.d.ts +3 -0
- package/dist/delivery-readiness.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +258 -95
- package/dist/preflight/checks.d.ts +22 -17
- package/dist/preflight/checks.d.ts.map +1 -1
- package/dist/preflight/system-check.d.ts +16 -5
- package/dist/preflight/system-check.d.ts.map +1 -1
- package/dist/recording-delivery-readiness.d.ts +24 -2
- package/dist/recording-delivery-readiness.d.ts.map +1 -1
- package/dist/worker.js +5 -2
- package/package.json +2 -2
- package/src/delivery-readiness.ts +8 -1
- package/src/index.ts +7 -0
- package/src/preflight/checks.ts +36 -87
- package/src/preflight/system-check.ts +149 -85
- package/src/recording-delivery-readiness.ts +231 -12
|
@@ -93,14 +93,13 @@ export interface RequiredCapabilitiesConfig {
|
|
|
93
93
|
*/
|
|
94
94
|
export interface ThresholdsConfig {
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* Default: 2.
|
|
96
|
+
* Download-bandwidth reference used for internal quality telemetry. Browser
|
|
97
|
+
* Mbps estimates do not decide candidate eligibility. Default: 2.
|
|
99
98
|
*/
|
|
100
99
|
minBandwidthMbps?: number;
|
|
101
100
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
101
|
+
* Upload-bandwidth reference used for internal quality telemetry. Recording
|
|
102
|
+
* readiness is derived from the enabled media workload instead. Default: 2.
|
|
104
103
|
*/
|
|
105
104
|
minUploadBandwidthMbps?: number;
|
|
106
105
|
/**
|
|
@@ -133,6 +132,13 @@ export interface ThresholdsConfig {
|
|
|
133
132
|
*/
|
|
134
133
|
allowMobile?: boolean;
|
|
135
134
|
}
|
|
135
|
+
export interface ConnectionReadinessCheckResult {
|
|
136
|
+
mode: "direct" | "segments" | "post";
|
|
137
|
+
payloadBytes: number;
|
|
138
|
+
completionWindowMs: number;
|
|
139
|
+
sampleDurationsMs: number[];
|
|
140
|
+
medianCompletionMs: number;
|
|
141
|
+
}
|
|
136
142
|
export interface SystemCheckOptions {
|
|
137
143
|
/**
|
|
138
144
|
* Mode controls the row list and the short-circuit behaviour:
|
|
@@ -191,6 +197,11 @@ export interface SystemCheckOptions {
|
|
|
191
197
|
* SDK consumers.
|
|
192
198
|
*/
|
|
193
199
|
connectionTest?: HttpConnectionProbeOptions & ConnectionQualityOptions;
|
|
200
|
+
/**
|
|
201
|
+
* Optional workload-aware recording-path check. When supplied, this is the
|
|
202
|
+
* candidate eligibility signal; numeric speed tests remain telemetry only.
|
|
203
|
+
*/
|
|
204
|
+
connectionReadiness?: () => Promise<ConnectionReadinessCheckResult>;
|
|
194
205
|
}
|
|
195
206
|
/** JSON-serialisable final report. No class instances, safe to log + persist. */
|
|
196
207
|
export interface PreflightReport {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-check.d.ts","sourceRoot":"","sources":["../../src/preflight/system-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EASL,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,QAAQ,EACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,yBAAyB,CAAC;AACjC,OAAO,
|
|
1
|
+
{"version":3,"file":"system-check.d.ts","sourceRoot":"","sources":["../../src/preflight/system-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EASL,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,QAAQ,EACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEzF,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;AAE1E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5D,mFAAmF;AACnF,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,kBAAkB;IACjC,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qBAAqB;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qBAAqB;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,4EAA4E;AAC5E,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAwCD,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,oEAAoE;IACpE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,8DAA8D;IAC9D,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD,qFAAqF;IACrF,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;;;;OAIG;IACH,UAAU,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,eAAe,CAAC,CAAC;IAC3D;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,0BAA0B,GAAG,wBAAwB,CAAC;IACvE;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,8BAA8B,CAAC,CAAC;CACrE;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,6CAA6C;AAC7C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;AAmB1E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,gBAAgB,CAOzD,CAAC;AA0BF,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,GAAE,kBAAuB;IAuB5C;;;OAGG;IACH,KAAK,IAAI,IAAI;IAKb;;;;;;OAMG;IACH,OAAO,IAAI,aAAa,CAAC,QAAQ,CAAC;IAMlC;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,IAAI;IAQpD;;;;;OAKG;IACI,GAAG,IAAI,cAAc,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC;IA4D5E,0EAA0E;IACpE,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC;IAYzC;;;;OAIG;IACG,YAAY,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC;YAgD/D,OAAO;YAmCP,MAAM;YAON,aAAa;IAkH3B;;;;;OAKG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAQrC;;;;;;OAMG;YACW,sBAAsB;IA+BpC,wEAAwE;IACxE,OAAO,CAAC,WAAW;IAInB,kEAAkE;IAClE,OAAO,CAAC,WAAW;IASnB;;;;;;OAMG;IACG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhD;;;;;OAKG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IASnC;;;;OAIG;IACG,WAAW,CACf,QAAQ,CAAC,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE;QAAE,YAAY,EAAE,gBAAgB,CAAA;KAAE,GAClD,OAAO,CAAC;QACT;;;;;;WAMG;QACH,QAAQ,EAAE,OAAO,wBAAwB,EAAE,iBAAiB,EAAE,CAAC;KAChE,CAAC;IAaF;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,gBAAgB,CAAC,QAAQ,GAAE,OAAc,GAAG,OAAO,CAAC,IAAI,CAAC;YA8BjD,QAAQ;IAoDtB;;;;OAIG;YACW,kBAAkB;IAuFhC,OAAO,CAAC,gBAAgB;IAwCxB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,KAAK;CAId;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAU,UAAS,kBAAuB,KAAG,OAAO,CAAC,eAAe,CAC3D,CAAC"}
|
|
@@ -1,9 +1,31 @@
|
|
|
1
|
-
interface RecordingReadinessConfig {
|
|
1
|
+
export interface RecordingReadinessConfig {
|
|
2
2
|
sessionId: string;
|
|
3
3
|
ingestUrl: string;
|
|
4
4
|
appId?: string;
|
|
5
5
|
}
|
|
6
|
+
export interface RecordingDeliveryMeasurementConfig extends RecordingReadinessConfig {
|
|
7
|
+
/** Representative bytes produced by the enabled recorders in one workload window. */
|
|
8
|
+
payloadBytes: number;
|
|
9
|
+
/** Maximum acceptable upload completion time for the representative payload. */
|
|
10
|
+
completionWindowMs: number;
|
|
11
|
+
/** Number of scored uploads. Default: 3; bounded to 1..5. */
|
|
12
|
+
samples?: number;
|
|
13
|
+
/** Injectable monotonic clock for deterministic tests. */
|
|
14
|
+
now?: () => number;
|
|
15
|
+
}
|
|
16
|
+
export interface RecordingDeliveryMeasurement {
|
|
17
|
+
mode: "direct" | "segments" | "post";
|
|
18
|
+
payloadBytes: number;
|
|
19
|
+
completionWindowMs: number;
|
|
20
|
+
sampleDurationsMs: number[];
|
|
21
|
+
medianCompletionMs: number;
|
|
22
|
+
}
|
|
6
23
|
/** Verify the server-selected recording path. No host-facing controls exist. */
|
|
7
24
|
export declare function verifyRecordingDelivery(config: RecordingReadinessConfig): Promise<"direct" | "segments" | "post">;
|
|
8
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Measure whether the real recording destination can absorb the configured
|
|
27
|
+
* media workload. A disposable warm-up is excluded, then the median of the
|
|
28
|
+
* scored uploads is compared with the workload completion window.
|
|
29
|
+
*/
|
|
30
|
+
export declare function measureRecordingDeliveryReadiness(config: RecordingDeliveryMeasurementConfig): Promise<RecordingDeliveryMeasurement>;
|
|
9
31
|
//# sourceMappingURL=recording-delivery-readiness.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recording-delivery-readiness.d.ts","sourceRoot":"","sources":["../src/recording-delivery-readiness.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recording-delivery-readiness.d.ts","sourceRoot":"","sources":["../src/recording-delivery-readiness.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kCAAmC,SAAQ,wBAAwB;IAClF,qFAAqF;IACrF,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AASD,gFAAgF;AAChF,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC,CAgDzC;AAED;;;;GAIG;AACH,wBAAsB,iCAAiC,CACrD,MAAM,EAAE,kCAAkC,GACzC,OAAO,CAAC,4BAA4B,CAAC,CA4EvC"}
|
package/dist/worker.js
CHANGED
|
@@ -504,11 +504,14 @@ function indexedDbOpenFailureReason(err) {
|
|
|
504
504
|
class DeliveryReadinessError extends Error {
|
|
505
505
|
code;
|
|
506
506
|
status;
|
|
507
|
+
/** Structured operator diagnostics. Never render this object in candidate UI. */
|
|
508
|
+
details;
|
|
507
509
|
constructor(code, message, options) {
|
|
508
510
|
super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
509
511
|
this.name = "DeliveryReadinessError";
|
|
510
512
|
this.code = code;
|
|
511
513
|
this.status = options?.status;
|
|
514
|
+
this.details = options?.details;
|
|
512
515
|
}
|
|
513
516
|
}
|
|
514
517
|
function newId(prefix) {
|
|
@@ -873,8 +876,8 @@ function sleep(ms, signal) {
|
|
|
873
876
|
);
|
|
874
877
|
});
|
|
875
878
|
}
|
|
876
|
-
const SDK_VERSION = "0.
|
|
877
|
-
const SDK_BUILD_SHA = "
|
|
879
|
+
const SDK_VERSION = "0.2.0";
|
|
880
|
+
const SDK_BUILD_SHA = "0e08674057f5d4f2e5c5fac34cd5feeaa40d97f4";
|
|
878
881
|
const SDK_BUILD_INFO = Object.freeze({
|
|
879
882
|
sdkVersion: SDK_VERSION,
|
|
880
883
|
buildSha: SDK_BUILD_SHA
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a4anthony/proctorkit-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Framework-agnostic browser SDK for the proctoring platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"idb": "^8.0.3",
|
|
29
29
|
"rrweb": "2.1.1",
|
|
30
|
-
"@a4anthony/proctorkit-types": "0.
|
|
30
|
+
"@a4anthony/proctorkit-types": "0.3.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@tailwindcss/vite": "^4.3.0",
|
|
@@ -10,15 +10,22 @@ export type { DeliveryReadinessErrorCode } from "@a4anthony/proctorkit-types";
|
|
|
10
10
|
export class DeliveryReadinessError extends Error {
|
|
11
11
|
readonly code: DeliveryReadinessErrorCode;
|
|
12
12
|
readonly status: number | undefined;
|
|
13
|
+
/** Structured operator diagnostics. Never render this object in candidate UI. */
|
|
14
|
+
readonly details: Readonly<Record<string, string | number>> | undefined;
|
|
13
15
|
|
|
14
16
|
constructor(
|
|
15
17
|
code: DeliveryReadinessErrorCode,
|
|
16
18
|
message: string,
|
|
17
|
-
options?: {
|
|
19
|
+
options?: {
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
status?: number;
|
|
22
|
+
details?: Readonly<Record<string, string | number>>;
|
|
23
|
+
},
|
|
18
24
|
) {
|
|
19
25
|
super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);
|
|
20
26
|
this.name = "DeliveryReadinessError";
|
|
21
27
|
this.code = code;
|
|
22
28
|
this.status = options?.status;
|
|
29
|
+
this.details = options?.details;
|
|
23
30
|
}
|
|
24
31
|
}
|
package/src/index.ts
CHANGED
|
@@ -101,6 +101,12 @@ export type {
|
|
|
101
101
|
WorkerToMainMessage,
|
|
102
102
|
} from "@a4anthony/proctorkit-types";
|
|
103
103
|
export { DeliveryReadinessError } from "./delivery-readiness.js";
|
|
104
|
+
export { measureRecordingDeliveryReadiness } from "./recording-delivery-readiness.js";
|
|
105
|
+
export type {
|
|
106
|
+
RecordingDeliveryMeasurement,
|
|
107
|
+
RecordingDeliveryMeasurementConfig,
|
|
108
|
+
RecordingReadinessConfig,
|
|
109
|
+
} from "./recording-delivery-readiness.js";
|
|
104
110
|
export type { DomObserversConfig } from "./observers/dom-observers.js";
|
|
105
111
|
export type {
|
|
106
112
|
RequestScreenShareOptions,
|
|
@@ -3759,6 +3765,7 @@ export type {
|
|
|
3759
3765
|
CheckOverrides,
|
|
3760
3766
|
CheckRow,
|
|
3761
3767
|
CheckState,
|
|
3768
|
+
ConnectionReadinessCheckResult,
|
|
3762
3769
|
FailCode,
|
|
3763
3770
|
MediaChecksConfig,
|
|
3764
3771
|
PreflightReport,
|
package/src/preflight/checks.ts
CHANGED
|
@@ -66,8 +66,7 @@ export const RETRYABLE_FAIL_CODES: ReadonlySet<FailCode> = new Set([
|
|
|
66
66
|
]);
|
|
67
67
|
|
|
68
68
|
/** True when a "Retry" could plausibly clear this failure without a reload. */
|
|
69
|
-
export const isRetryableFailCode = (code: FailCode): boolean =>
|
|
70
|
-
RETRYABLE_FAIL_CODES.has(code);
|
|
69
|
+
export const isRetryableFailCode = (code: FailCode): boolean => RETRYABLE_FAIL_CODES.has(code);
|
|
71
70
|
|
|
72
71
|
/** Per-check state. Each row stores its own state plus any kind-specific payload. */
|
|
73
72
|
export type CheckState =
|
|
@@ -107,6 +106,16 @@ export interface CheckRow {
|
|
|
107
106
|
latencyMs?: number;
|
|
108
107
|
/** Median variation between latency samples in milliseconds. Connection row only. */
|
|
109
108
|
jitterMs?: number;
|
|
109
|
+
/** Server-selected recording upload mode verified by the readiness canary. */
|
|
110
|
+
recordingUploadMode?: "direct" | "segments" | "post";
|
|
111
|
+
/** Representative readiness payload size. Internal telemetry only. */
|
|
112
|
+
readinessPayloadBytes?: number;
|
|
113
|
+
/** Median representative upload completion time. Internal telemetry only. */
|
|
114
|
+
readinessMedianMs?: number;
|
|
115
|
+
/** Workload-derived completion window. Internal telemetry only. */
|
|
116
|
+
readinessWindowMs?: number;
|
|
117
|
+
/** Number of successful representative readiness samples. Internal telemetry only. */
|
|
118
|
+
readinessSampleCount?: number;
|
|
110
119
|
/**
|
|
111
120
|
* Plain-language quality bucket for a PASSING connection row (Fair / Good /
|
|
112
121
|
* Excellent), relative to the configured floor. Absent on fail rows and on
|
|
@@ -133,13 +142,13 @@ export interface CheckOverrides {
|
|
|
133
142
|
screenShareSupported?: boolean;
|
|
134
143
|
}
|
|
135
144
|
|
|
136
|
-
/**
|
|
145
|
+
/** Default reference floor for downlink quality telemetry in megabits/sec. */
|
|
137
146
|
export const MIN_SPEED_MBPS = 2;
|
|
138
147
|
|
|
139
148
|
/**
|
|
140
|
-
* Plain-language quality bucket
|
|
141
|
-
*
|
|
142
|
-
*
|
|
149
|
+
* Plain-language quality bucket for measured connection telemetry. A value
|
|
150
|
+
* below the reference floor has no band; measured Mbps never decides candidate
|
|
151
|
+
* eligibility.
|
|
143
152
|
*/
|
|
144
153
|
export type ConnectionBand = "fair" | "good" | "excellent";
|
|
145
154
|
|
|
@@ -154,21 +163,11 @@ const BAND_GOOD_MULTIPLE = 1.5;
|
|
|
154
163
|
const BAND_EXCELLENT_MULTIPLE = 3;
|
|
155
164
|
|
|
156
165
|
/**
|
|
157
|
-
* Bucket a measured speed into a {@link ConnectionBand} relative to the
|
|
158
|
-
* Returns null below the floor
|
|
159
|
-
* non-positive input or floor. Pure — the single source of truth for both the
|
|
160
|
-
* application-path and legacy download-only rows.
|
|
166
|
+
* Bucket a measured speed into a {@link ConnectionBand} relative to the
|
|
167
|
+
* reference floor. Returns null below the floor or for invalid inputs.
|
|
161
168
|
*/
|
|
162
|
-
export const connectionQualityBand = (
|
|
163
|
-
mbps
|
|
164
|
-
floorMbps: number,
|
|
165
|
-
): ConnectionBand | null => {
|
|
166
|
-
if (
|
|
167
|
-
!Number.isFinite(mbps) ||
|
|
168
|
-
!Number.isFinite(floorMbps) ||
|
|
169
|
-
floorMbps <= 0 ||
|
|
170
|
-
mbps < floorMbps
|
|
171
|
-
) {
|
|
169
|
+
export const connectionQualityBand = (mbps: number, floorMbps: number): ConnectionBand | null => {
|
|
170
|
+
if (!Number.isFinite(mbps) || !Number.isFinite(floorMbps) || floorMbps <= 0 || mbps < floorMbps) {
|
|
172
171
|
return null;
|
|
173
172
|
}
|
|
174
173
|
if (mbps >= floorMbps * BAND_EXCELLENT_MULTIPLE) return "excellent";
|
|
@@ -228,9 +227,7 @@ export const checkBrowser = (overrides?: CheckOverrides): CheckRow => {
|
|
|
228
227
|
// Safari is a separate beast (no getDisplayMedia, OS interruptions
|
|
229
228
|
// mid-session) and stays as `unsupported-browser`.
|
|
230
229
|
const code: FailCode =
|
|
231
|
-
browser.name === "Chrome" ||
|
|
232
|
-
browser.name === "Edge" ||
|
|
233
|
-
browser.name === "Safari"
|
|
230
|
+
browser.name === "Chrome" || browser.name === "Edge" || browser.name === "Safari"
|
|
234
231
|
? "outdated-browser"
|
|
235
232
|
: "unsupported-browser";
|
|
236
233
|
|
|
@@ -271,14 +268,12 @@ export const checkDevice = (
|
|
|
271
268
|
const lowMem = overrides?.isLowMemory ?? isLowMemoryDevice();
|
|
272
269
|
// Non-iOS mobile gate. iOS is already covered by `iosBlocked`
|
|
273
270
|
// above, so we only check the remaining mobile case here.
|
|
274
|
-
const mobileBlocked =
|
|
275
|
-
!allowMobile && !iosBlocked && isMobileOrTablet() && !isIOS();
|
|
271
|
+
const mobileBlocked = !allowMobile && !iosBlocked && isMobileOrTablet() && !isIOS();
|
|
276
272
|
const screenShareSupported =
|
|
277
273
|
overrides?.screenShareSupported ??
|
|
278
274
|
(typeof navigator !== "undefined" &&
|
|
279
275
|
typeof navigator.mediaDevices?.getDisplayMedia === "function");
|
|
280
|
-
const requiredScreenShareUnavailable =
|
|
281
|
-
requireScreenShareSupport && !screenShareSupported;
|
|
276
|
+
const requiredScreenShareUnavailable = requireScreenShareSupport && !screenShareSupported;
|
|
282
277
|
|
|
283
278
|
if (iosBlocked) {
|
|
284
279
|
return {
|
|
@@ -464,10 +459,7 @@ const measureLatency = (): Promise<number | null> =>
|
|
|
464
459
|
ping();
|
|
465
460
|
});
|
|
466
461
|
|
|
467
|
-
const downloadTest = (
|
|
468
|
-
bytes: number,
|
|
469
|
-
timeoutMs: number,
|
|
470
|
-
): Promise<number | null> =>
|
|
462
|
+
const downloadTest = (bytes: number, timeoutMs: number): Promise<number | null> =>
|
|
471
463
|
new Promise((resolve) => {
|
|
472
464
|
const xhr = new XMLHttpRequest();
|
|
473
465
|
const url = `${CLOUDFLARE_SPEED_URL}?bytes=${bytes}&_=${Date.now()}${Math.random()}`;
|
|
@@ -551,11 +543,9 @@ export const checkConnection = (overrides?: CheckOverrides): CheckRow | null =>
|
|
|
551
543
|
};
|
|
552
544
|
|
|
553
545
|
/**
|
|
554
|
-
* Build the connection row from a measured speed (or null on
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
* unchanged; the engine threads the policy override through when
|
|
558
|
-
* present.
|
|
546
|
+
* Build the connection row from a measured speed (or null on measurement
|
|
547
|
+
* failure). Numeric speed is retained as telemetry, but never gates the
|
|
548
|
+
* candidate because short browser probes are dominated by request latency.
|
|
559
549
|
*/
|
|
560
550
|
export const applySpeedToResult = (
|
|
561
551
|
speed: number | null,
|
|
@@ -571,17 +561,6 @@ export const applySpeedToResult = (
|
|
|
571
561
|
},
|
|
572
562
|
};
|
|
573
563
|
}
|
|
574
|
-
if (speed < minMbps) {
|
|
575
|
-
return {
|
|
576
|
-
kind: "connection",
|
|
577
|
-
state: {
|
|
578
|
-
kind: "fail",
|
|
579
|
-
code: "slow-connection",
|
|
580
|
-
detail: `${speed} Mbps`,
|
|
581
|
-
},
|
|
582
|
-
mbps: speed,
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
564
|
const band = connectionQualityBand(speed, minMbps);
|
|
586
565
|
return {
|
|
587
566
|
kind: "connection",
|
|
@@ -592,10 +571,9 @@ export const applySpeedToResult = (
|
|
|
592
571
|
};
|
|
593
572
|
|
|
594
573
|
/**
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
* public contract; direction-specific values are carried on the row.
|
|
574
|
+
* Preserve application-path measurements as internal diagnostics. A completed
|
|
575
|
+
* measurement passes regardless of the estimated Mbps; candidate eligibility
|
|
576
|
+
* is decided separately by the workload-aware recording-storage canary.
|
|
599
577
|
*/
|
|
600
578
|
export const applyConnectionQualityToResult = (
|
|
601
579
|
result: ConnectionQualityResult,
|
|
@@ -622,42 +600,16 @@ export const applyConnectionQualityToResult = (
|
|
|
622
600
|
...fields,
|
|
623
601
|
};
|
|
624
602
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
code: "slow-connection",
|
|
631
|
-
detail: `Upload ${result.uploadMbps ?? upload} Mbps`,
|
|
632
|
-
},
|
|
633
|
-
...fields,
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
if (download < minDownloadMbps) {
|
|
637
|
-
return {
|
|
638
|
-
kind: "connection",
|
|
639
|
-
state: {
|
|
640
|
-
kind: "fail",
|
|
641
|
-
code: "slow-connection",
|
|
642
|
-
detail: `Download ${result.downloadMbps ?? download} Mbps`,
|
|
643
|
-
},
|
|
644
|
-
...fields,
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
// Band the WORST of the two directions off the conservative p25 (the same
|
|
648
|
-
// value the gate above used), so the label reflects the binding constraint
|
|
649
|
-
// and can never sit above the pass/fail line.
|
|
650
|
-
const band = worseBand(
|
|
651
|
-
connectionQualityBand(upload, minUploadMbps),
|
|
652
|
-
connectionQualityBand(download, minDownloadMbps),
|
|
653
|
-
);
|
|
603
|
+
// Band the worst direction only when both values meet the configured
|
|
604
|
+
// reference floors. The band is display telemetry, never a gate.
|
|
605
|
+
const uploadBand = connectionQualityBand(upload, minUploadMbps);
|
|
606
|
+
const downloadBand = connectionQualityBand(download, minDownloadMbps);
|
|
607
|
+
const band = uploadBand && downloadBand ? worseBand(uploadBand, downloadBand) : null;
|
|
654
608
|
return {
|
|
655
609
|
kind: "connection",
|
|
656
610
|
state: {
|
|
657
611
|
kind: "pass",
|
|
658
|
-
detail: band
|
|
659
|
-
? BAND_LABELS[band]
|
|
660
|
-
: `Up ${result.uploadMbps} · Down ${result.downloadMbps} Mbps`,
|
|
612
|
+
detail: band ? BAND_LABELS[band] : "Connection available",
|
|
661
613
|
},
|
|
662
614
|
...fields,
|
|
663
615
|
...(band ? { band } : {}),
|
|
@@ -676,10 +628,7 @@ const BAND_RANK: Record<ConnectionBand, number> = {
|
|
|
676
628
|
* this branch — the gate already failed the row — so treat null as "no
|
|
677
629
|
* opinion" and fall back to the other direction.
|
|
678
630
|
*/
|
|
679
|
-
function worseBand(
|
|
680
|
-
a: ConnectionBand | null,
|
|
681
|
-
b: ConnectionBand | null,
|
|
682
|
-
): ConnectionBand | null {
|
|
631
|
+
function worseBand(a: ConnectionBand | null, b: ConnectionBand | null): ConnectionBand | null {
|
|
683
632
|
if (a === null) return b;
|
|
684
633
|
if (b === null) return a;
|
|
685
634
|
return BAND_RANK[a] <= BAND_RANK[b] ? a : b;
|