@sailfish-ai/recorder 1.12.4 → 1.12.6
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 +8 -0
- package/dist/canvas/adapters.js +382 -0
- package/dist/canvas/adapters.js.br +0 -0
- package/dist/canvas/adapters.js.gz +0 -0
- package/dist/canvas/canvasManager.js +1949 -0
- package/dist/canvas/canvasManager.js.br +0 -0
- package/dist/canvas/canvasManager.js.gz +0 -0
- package/dist/canvas/canvasPrivacy.js +233 -0
- package/dist/canvas/canvasPrivacy.js.br +0 -0
- package/dist/canvas/canvasPrivacy.js.gz +0 -0
- package/dist/canvas/codec.js +314 -0
- package/dist/canvas/codec.js.br +0 -0
- package/dist/canvas/codec.js.gz +0 -0
- package/dist/canvas/deviceClass.js +67 -0
- package/dist/canvas/deviceClass.js.br +0 -0
- package/dist/canvas/deviceClass.js.gz +0 -0
- package/dist/canvas/encodeWorker.js +284 -0
- package/dist/canvas/encodeWorker.js.br +0 -0
- package/dist/canvas/encodeWorker.js.gz +0 -0
- package/dist/canvas/governor.js +79 -0
- package/dist/canvas/governor.js.br +0 -0
- package/dist/canvas/governor.js.gz +0 -0
- package/dist/canvas/keyframes.js +183 -0
- package/dist/canvas/keyframes.js.br +0 -0
- package/dist/canvas/keyframes.js.gz +0 -0
- package/dist/canvas/mediaRecorderCapture.js +257 -0
- package/dist/canvas/mediaRecorderCapture.js.br +0 -0
- package/dist/canvas/mediaRecorderCapture.js.gz +0 -0
- package/dist/canvas/objectLog.js +150 -0
- package/dist/canvas/objectLog.js.br +0 -0
- package/dist/canvas/objectLog.js.gz +0 -0
- package/dist/canvas/patch2d.js +1302 -0
- package/dist/canvas/patch2d.js.br +0 -0
- package/dist/canvas/patch2d.js.gz +0 -0
- package/dist/canvas/patchGL.js +371 -0
- package/dist/canvas/patchGL.js.br +0 -0
- package/dist/canvas/patchGL.js.gz +0 -0
- package/dist/canvas/patchWebGPU.js +361 -0
- package/dist/canvas/patchWebGPU.js.br +0 -0
- package/dist/canvas/patchWebGPU.js.gz +0 -0
- package/dist/canvas/piiDetect.js +155 -0
- package/dist/canvas/piiDetect.js.br +0 -0
- package/dist/canvas/piiDetect.js.gz +0 -0
- package/dist/canvas/protocol.js +122 -0
- package/dist/canvas/protocol.js.br +0 -0
- package/dist/canvas/protocol.js.gz +0 -0
- package/dist/canvas/videoCapture.js +252 -0
- package/dist/canvas/videoCapture.js.br +0 -0
- package/dist/canvas/videoCapture.js.gz +0 -0
- package/dist/canvas/workerRelay.js +99 -0
- package/dist/canvas/workerRelay.js.br +0 -0
- package/dist/canvas/workerRelay.js.gz +0 -0
- package/dist/canvas/workerShim.js +227 -0
- package/dist/canvas/workerShim.js.br +0 -0
- package/dist/canvas/workerShim.js.gz +0 -0
- package/dist/canvas-worker.cjs +74 -0
- package/dist/canvas-worker.cjs.br +0 -0
- package/dist/canvas-worker.cjs.gz +0 -0
- package/dist/canvas-worker.js +78 -0
- package/dist/canvas-worker.js.br +0 -0
- package/dist/canvas-worker.js.gz +0 -0
- package/dist/canvasFrameStore.js +198 -0
- package/dist/canvasFrameStore.js.br +0 -0
- package/dist/canvasFrameStore.js.gz +0 -0
- package/dist/chunks/canvasManager-BYn3_oyo.js +1440 -0
- package/dist/chunks/canvasManager-BYn3_oyo.js.br +0 -0
- package/dist/chunks/canvasManager-BYn3_oyo.js.gz +0 -0
- package/dist/chunks/canvasManager-CoX5072G.js +1456 -0
- package/dist/chunks/canvasManager-CoX5072G.js.br +0 -0
- package/dist/chunks/canvasManager-CoX5072G.js.gz +0 -0
- package/dist/chunks/{chunkSerializer-FQtY90Av.js → chunkSerializer-C_K0Wyk0.js} +1 -1
- package/dist/chunks/chunkSerializer-C_K0Wyk0.js.br +0 -0
- package/dist/chunks/chunkSerializer-C_K0Wyk0.js.gz +0 -0
- package/dist/chunks/{chunkSerializer-DDukZpgl.js → chunkSerializer-CghPitF4.js} +1 -1
- package/dist/chunks/chunkSerializer-CghPitF4.js.br +0 -0
- package/dist/chunks/chunkSerializer-CghPitF4.js.gz +0 -0
- package/dist/chunks/{index-C-qbsfKe.js → index-BIj4l3CV.js} +1028 -650
- package/dist/chunks/index-BIj4l3CV.js.br +0 -0
- package/dist/chunks/index-BIj4l3CV.js.gz +0 -0
- package/dist/chunks/{index-D6axlCRu.js → index-DIYomvdD.js} +1119 -720
- package/dist/chunks/index-DIYomvdD.js.br +0 -0
- package/dist/chunks/index-DIYomvdD.js.gz +0 -0
- package/dist/chunks/patch2d-9voowhJ_.js +816 -0
- package/dist/chunks/patch2d-9voowhJ_.js.br +0 -0
- package/dist/chunks/patch2d-9voowhJ_.js.gz +0 -0
- package/dist/chunks/patch2d-PQxcaE6r.js +886 -0
- package/dist/chunks/patch2d-PQxcaE6r.js.br +0 -0
- package/dist/chunks/patch2d-PQxcaE6r.js.gz +0 -0
- package/dist/graphql.js +12 -1
- package/dist/graphql.js.br +0 -0
- package/dist/graphql.js.gz +0 -0
- package/dist/inAppReportIssueModal/index.js +48 -0
- package/dist/inAppReportIssueModal/index.js.br +0 -0
- package/dist/inAppReportIssueModal/index.js.gz +0 -0
- package/dist/index.js +144 -2
- package/dist/index.js.br +0 -0
- package/dist/index.js.gz +0 -0
- package/dist/recorder.cjs +2 -2
- package/dist/recorder.cjs.br +0 -0
- package/dist/recorder.cjs.gz +0 -0
- package/dist/recorder.js +52 -41
- package/dist/recorder.js.br +0 -0
- package/dist/recorder.js.gz +0 -0
- package/dist/recorder.umd.cjs +4847 -2212
- package/dist/recorder.umd.cjs.br +0 -0
- package/dist/recorder.umd.cjs.gz +0 -0
- package/dist/recording.js +119 -10
- package/dist/recording.js.br +0 -0
- package/dist/recording.js.gz +0 -0
- package/dist/types/canvas/adapters.d.ts +71 -0
- package/dist/types/canvas/canvasManager.d.ts +273 -0
- package/dist/types/canvas/canvasPrivacy.d.ts +184 -0
- package/dist/types/canvas/codec.d.ts +27 -0
- package/dist/types/canvas/deviceClass.d.ts +40 -0
- package/dist/types/canvas/encodeWorker.d.ts +54 -0
- package/dist/types/canvas/governor.d.ts +44 -0
- package/dist/types/canvas/keyframes.d.ts +81 -0
- package/dist/types/canvas/mediaRecorderCapture.d.ts +91 -0
- package/dist/types/canvas/objectLog.d.ts +75 -0
- package/dist/types/canvas/patch2d.d.ts +339 -0
- package/dist/types/canvas/patchGL.d.ts +74 -0
- package/dist/types/canvas/patchWebGPU.d.ts +37 -0
- package/dist/types/canvas/piiDetect.d.ts +60 -0
- package/dist/types/canvas/protocol.d.ts +176 -0
- package/dist/types/canvas/videoCapture.d.ts +67 -0
- package/dist/types/canvas/workerRelay.d.ts +29 -0
- package/dist/types/canvas/workerShim.d.ts +10 -0
- package/dist/types/canvasFrameStore.d.ts +23 -0
- package/dist/types/graphql.d.ts +1 -1
- package/dist/types/index.d.ts +99 -1
- package/dist/types/types.d.ts +40 -0
- package/dist/types/websocket.d.ts +19 -0
- package/dist/websocket.js +210 -0
- package/dist/websocket.js.br +0 -0
- package/dist/websocket.js.gz +0 -0
- package/package.json +7 -1
- package/dist/chunks/chunkSerializer-DDukZpgl.js.br +0 -0
- package/dist/chunks/chunkSerializer-DDukZpgl.js.gz +0 -0
- package/dist/chunks/chunkSerializer-FQtY90Av.js.br +0 -0
- package/dist/chunks/chunkSerializer-FQtY90Av.js.gz +0 -0
- package/dist/chunks/index-C-qbsfKe.js.br +0 -0
- package/dist/chunks/index-C-qbsfKe.js.gz +0 -0
- package/dist/chunks/index-D6axlCRu.js.br +0 -0
- package/dist/chunks/index-D6axlCRu.js.gz +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared canvas-recording protocol types + the SFCV binary frame format.
|
|
3
|
+
*
|
|
4
|
+
* Types are ported from the validated bench core
|
|
5
|
+
* (test-applications/canvas-recorder-bench/src/capture/patch2d.ts); the SFCV
|
|
6
|
+
* frame builder/parser implements the canvas-recording-pipeline contract §1:
|
|
7
|
+
*
|
|
8
|
+
* bytes 0..3 magic "SFC1" (0x53 0x46 0x43 0x31)
|
|
9
|
+
* bytes 4..7 u32 LE: headerLen
|
|
10
|
+
* bytes 8.. header JSON (utf8, headerLen bytes)
|
|
11
|
+
* rest payload bytes
|
|
12
|
+
*
|
|
13
|
+
* SSR rule: nothing in this module touches window/document/navigator at
|
|
14
|
+
* module scope.
|
|
15
|
+
*/
|
|
16
|
+
export type SerializedArg = number | string | boolean | null | {
|
|
17
|
+
$: "asset";
|
|
18
|
+
id: number;
|
|
19
|
+
} | {
|
|
20
|
+
$: "obj";
|
|
21
|
+
id: number;
|
|
22
|
+
} | {
|
|
23
|
+
$: "arr";
|
|
24
|
+
v: number[];
|
|
25
|
+
} | {
|
|
26
|
+
$: "imagedata";
|
|
27
|
+
id: number;
|
|
28
|
+
}
|
|
29
|
+
/** typed-array payload: k = index into TYPED_ARRAY_KINDS, b = raw bytes (copied at call time) */
|
|
30
|
+
| {
|
|
31
|
+
$: "ta";
|
|
32
|
+
k: number;
|
|
33
|
+
b: Uint8Array;
|
|
34
|
+
} | {
|
|
35
|
+
$: "unsupported";
|
|
36
|
+
kind: string;
|
|
37
|
+
};
|
|
38
|
+
export declare const TYPED_ARRAY_KINDS: readonly [Int8ArrayConstructor, Uint8ArrayConstructor, Uint8ClampedArrayConstructor, Int16ArrayConstructor, Uint16ArrayConstructor, Int32ArrayConstructor, Uint32ArrayConstructor, Float32ArrayConstructor, Float64ArrayConstructor];
|
|
39
|
+
/** Copy a typed array / ArrayBuffer into a serialized arg AT CALL TIME. */
|
|
40
|
+
export declare function serializeTypedArray(v: ArrayBufferView | ArrayBuffer): SerializedArg;
|
|
41
|
+
export declare function deserializeTypedArray(a: {
|
|
42
|
+
$: "ta";
|
|
43
|
+
k: number;
|
|
44
|
+
b: Uint8Array;
|
|
45
|
+
}): ArrayBufferView;
|
|
46
|
+
export interface RecordedOp {
|
|
47
|
+
/** EPOCH MILLISECONDS (contract §1 — rrweb-aligned clock). The codec's
|
|
48
|
+
* µs-delta encoding absorbs the large first delta. */
|
|
49
|
+
t: number;
|
|
50
|
+
op: string;
|
|
51
|
+
args: SerializedArg[];
|
|
52
|
+
/** property assignment rather than method call */
|
|
53
|
+
setter?: boolean;
|
|
54
|
+
/** interned-object creation: args = [factoryName, objId, ...factoryArgs] */
|
|
55
|
+
create?: boolean;
|
|
56
|
+
/** method call on interned object: args = [objId, method, ...callArgs] */
|
|
57
|
+
objcall?: boolean;
|
|
58
|
+
/** draw-site stamp id (Phase 3b) — set by __sfDrawSite, carried via the codec __site marker */
|
|
59
|
+
sfId?: string;
|
|
60
|
+
}
|
|
61
|
+
/** Asset payload captured for transmission + size accounting. */
|
|
62
|
+
export interface CapturedAsset {
|
|
63
|
+
id: number;
|
|
64
|
+
kind: "image" | "imagedata";
|
|
65
|
+
/** time first referenced (epoch ms) */
|
|
66
|
+
t: number;
|
|
67
|
+
/** encoded size in bytes (webp for images, deflated raw for imagedata); resolves async */
|
|
68
|
+
bytes: number;
|
|
69
|
+
/** source dimensions (filled when the encode resolves) */
|
|
70
|
+
w?: number;
|
|
71
|
+
h?: number;
|
|
72
|
+
/** retained only when the recorder runs with retainAssets (worker relay path) */
|
|
73
|
+
bitmap?: ImageBitmap;
|
|
74
|
+
imageData?: ImageData;
|
|
75
|
+
}
|
|
76
|
+
/** Encoded-image result from the webp encoder (size always; bytes on demand). */
|
|
77
|
+
export interface EncodedImage {
|
|
78
|
+
size: number;
|
|
79
|
+
bytes?: ArrayBuffer;
|
|
80
|
+
}
|
|
81
|
+
export type EncodeImageFn = (bitmap: ImageBitmap) => Promise<EncodedImage>;
|
|
82
|
+
export interface AssetDataInfo {
|
|
83
|
+
id: number;
|
|
84
|
+
format: "webp" | "imagedata";
|
|
85
|
+
w: number;
|
|
86
|
+
h: number;
|
|
87
|
+
t: number;
|
|
88
|
+
}
|
|
89
|
+
export interface RecorderSink {
|
|
90
|
+
onOp(op: RecordedOp): void;
|
|
91
|
+
/** called when an asset finished encoding (size known) */
|
|
92
|
+
onAssetBytes(id: number, bytes: number, t: number): void;
|
|
93
|
+
note(msg: string): void;
|
|
94
|
+
/** SDK extension: encoded asset payload ready for an SFCV `asset` frame. */
|
|
95
|
+
onAssetData?(info: AssetDataInfo, payload: Uint8Array): void;
|
|
96
|
+
/** SDK extension: a dynamic VIDEO source was captured for a draw (governor signal). */
|
|
97
|
+
onVideoSource?(t: number): void;
|
|
98
|
+
/** SDK extension: a cross-origin/tainted image source could not be read back
|
|
99
|
+
* (canvas is tainted). `t` is the capture-time timestamp (same clock as ops).
|
|
100
|
+
* The manager emits an `op:"taint"` index event so the player can show a
|
|
101
|
+
* placeholder instead of a silent blank. Deduped manager-side per canvas. */
|
|
102
|
+
onTaint?(t: number): void;
|
|
103
|
+
}
|
|
104
|
+
export type AssetMode = "dataurl" | "cas";
|
|
105
|
+
export interface StateFrame {
|
|
106
|
+
props: Record<string, SerializedArg>;
|
|
107
|
+
/** [a,b,c,d,e,f] */
|
|
108
|
+
transform: number[];
|
|
109
|
+
}
|
|
110
|
+
export interface StateSnapshot {
|
|
111
|
+
stack: StateFrame[];
|
|
112
|
+
current: StateFrame;
|
|
113
|
+
}
|
|
114
|
+
export declare class BufferSink implements RecorderSink {
|
|
115
|
+
ops: RecordedOp[];
|
|
116
|
+
assetEvents: {
|
|
117
|
+
id: number;
|
|
118
|
+
bytes: number;
|
|
119
|
+
t: number;
|
|
120
|
+
}[];
|
|
121
|
+
assetData: {
|
|
122
|
+
info: AssetDataInfo;
|
|
123
|
+
payload: Uint8Array;
|
|
124
|
+
}[];
|
|
125
|
+
notes: string[];
|
|
126
|
+
onOp(op: RecordedOp): void;
|
|
127
|
+
onAssetBytes(id: number, bytes: number, t: number): void;
|
|
128
|
+
onAssetData(info: AssetDataInfo, payload: Uint8Array): void;
|
|
129
|
+
note(m: string): void;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Video-mode dirtiness sink: keeps the patch recorder attached purely to
|
|
133
|
+
* drive the per-canvas dirty flag — every op is discarded with ZERO
|
|
134
|
+
* serialize/encode cost (recorders short-circuit on `instanceof DiscardSink`
|
|
135
|
+
* before serializing arguments).
|
|
136
|
+
*/
|
|
137
|
+
export declare class DiscardSink implements RecorderSink {
|
|
138
|
+
private onDirty;
|
|
139
|
+
constructor(onDirty: () => void);
|
|
140
|
+
onOp(_op: RecordedOp): void;
|
|
141
|
+
onAssetBytes(_id: number, _bytes: number, _t: number): void;
|
|
142
|
+
note(_m: string): void;
|
|
143
|
+
}
|
|
144
|
+
/** Shared zero-cost op handed to DiscardSink (its contents are never read). */
|
|
145
|
+
export declare const DIRTY_OP: RecordedOp;
|
|
146
|
+
export type SfcvKind = "commands" | "keyframe" | "asset" | "video-init" | "video-chunk";
|
|
147
|
+
export interface SfcvHeader {
|
|
148
|
+
v: 1;
|
|
149
|
+
kind: SfcvKind;
|
|
150
|
+
sessionId: string;
|
|
151
|
+
pageVisitUuid: string;
|
|
152
|
+
/** rrweb mirror node id at start, as a string */
|
|
153
|
+
canvasId: string;
|
|
154
|
+
/** monotonically increasing PER CANVAS across all kinds */
|
|
155
|
+
seq: number;
|
|
156
|
+
/** epoch ms — same clock as rrweb event timestamps */
|
|
157
|
+
tMs: number;
|
|
158
|
+
/**
|
|
159
|
+
* payload byte length. SDK extension to contract §1: each stored .sfcv
|
|
160
|
+
* object is self-delimiting, so a CONCATENATION of frames (the GraphQL
|
|
161
|
+
* dataBase64 retrieval shape, contract §3) can be split without scanning
|
|
162
|
+
* payload bytes for magic. Absent => payload runs to end of buffer
|
|
163
|
+
* (single-frame compatibility).
|
|
164
|
+
*/
|
|
165
|
+
pLen?: number;
|
|
166
|
+
meta?: Record<string, unknown>;
|
|
167
|
+
}
|
|
168
|
+
export declare function buildFrame(header: Omit<SfcvHeader, "pLen">, payload?: Uint8Array | ArrayBuffer | null): ArrayBuffer;
|
|
169
|
+
export interface ParsedFrame {
|
|
170
|
+
header: SfcvHeader;
|
|
171
|
+
payload: Uint8Array;
|
|
172
|
+
}
|
|
173
|
+
/** Parse one frame or a concatenation of frames. Throws on corrupt input. */
|
|
174
|
+
export declare function parseFrames(bytes: ArrayBuffer | Uint8Array): ParsedFrame[];
|
|
175
|
+
/** Chunked base64 for ArrayBuffers (video-init decoder descriptions). */
|
|
176
|
+
export declare function bytesToBase64(bytes: ArrayBuffer | Uint8Array): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export interface VideoCaptureOut {
|
|
2
|
+
/** decoder config known — emit the SFCV video-init frame */
|
|
3
|
+
onInit(codec: string, description: ArrayBuffer | null): void;
|
|
4
|
+
/** encoded chunk — emit an SFCV video-chunk frame */
|
|
5
|
+
onChunk(key: boolean, timestampUs: number, data: ArrayBuffer): void;
|
|
6
|
+
note(msg: string): void;
|
|
7
|
+
/** A11 backpressure: true when the transport's send buffer is saturated, so
|
|
8
|
+
* afterRender SKIPS producing a new VideoFrame until it drains. Optional — when
|
|
9
|
+
* absent, frame production is never throttled. */
|
|
10
|
+
isTransportSaturated?(): boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class VideoCapture {
|
|
13
|
+
private canvas;
|
|
14
|
+
private now;
|
|
15
|
+
private out;
|
|
16
|
+
private opts?;
|
|
17
|
+
private client;
|
|
18
|
+
private configured;
|
|
19
|
+
private failed;
|
|
20
|
+
private stopped;
|
|
21
|
+
private lastFrame;
|
|
22
|
+
private lastKey;
|
|
23
|
+
private boostUntil;
|
|
24
|
+
private configuredW;
|
|
25
|
+
private configuredH;
|
|
26
|
+
private intervalId;
|
|
27
|
+
/** A11: dedupe the "throttled" note to once per saturation episode */
|
|
28
|
+
private throttledNoted;
|
|
29
|
+
/** A24: an encoder restart at the new canvas size is in flight */
|
|
30
|
+
private restarting;
|
|
31
|
+
/** A24: force the next captured frame to a keyframe (after a restart) */
|
|
32
|
+
private forceKeyNext;
|
|
33
|
+
static supported(): boolean;
|
|
34
|
+
constructor(canvas: HTMLCanvasElement, now: () => number, out: VideoCaptureOut, opts?: {
|
|
35
|
+
intervalPaced?: boolean;
|
|
36
|
+
});
|
|
37
|
+
/** Resolves true when the encoder configured; false -> keyframes-only fallback. */
|
|
38
|
+
start(): Promise<boolean>;
|
|
39
|
+
onInteraction(tMs: number): void;
|
|
40
|
+
/** Same-task capture: invoked by the manager's microtask pump right after
|
|
41
|
+
* the scene rendered. Dirty-by-construction — only called when a frame was
|
|
42
|
+
* drawn (or on the interval for placeholder canvases). */
|
|
43
|
+
afterRender(t: number): void;
|
|
44
|
+
/**
|
|
45
|
+
* A24: restart the encoder at the canvas's current dimensions after a
|
|
46
|
+
* mid-stream resize. The old encoder is torn down (its locked-size frames are
|
|
47
|
+
* no longer valid) and a fresh one is configured for the new size; the new
|
|
48
|
+
* encoder's first chunk re-emits the decoder config (onInit) so the player
|
|
49
|
+
* re-initializes, and the next captured frame is forced to a keyframe. Skips
|
|
50
|
+
* if already restarting, stopped, or the new size is zero.
|
|
51
|
+
*/
|
|
52
|
+
private requestRestart;
|
|
53
|
+
/** Non-terminal drain (visibilitychange:hidden on mobile, where the OS may
|
|
54
|
+
* kill the tab without a reliable pagehide): push the encoder's queued frames
|
|
55
|
+
* out as chunks WITHOUT tearing down, so capture resumes if the tab returns.
|
|
56
|
+
* No-op once stopped. */
|
|
57
|
+
flush(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* A22 / A6 / A10: immediate teardown WITHOUT flushing — used when the canvas
|
|
60
|
+
* was removed from the DOM, masked mid-session, or its GPU context was lost.
|
|
61
|
+
* Flushing would emit chunks for content that is gone/garbage, so we just
|
|
62
|
+
* terminate the encoder worker and stop. Idempotent.
|
|
63
|
+
*/
|
|
64
|
+
abort(): void;
|
|
65
|
+
/** Flush the encoder (remaining chunks stream through onChunk) and tear down. */
|
|
66
|
+
stop(): Promise<void>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main-thread registry for canvases whose control was transferred to a
|
|
3
|
+
* worker, plus the AUTO-ASSOCIATION patches the bench left to the build
|
|
4
|
+
* plugin (contract §5):
|
|
5
|
+
*
|
|
6
|
+
* - HTMLCanvasElement.prototype.transferControlToOffscreen is patched to
|
|
7
|
+
* remember which OffscreenCanvas came from which canvas element.
|
|
8
|
+
* - Worker.prototype.postMessage is patched to map canvas -> worker when an
|
|
9
|
+
* OffscreenCanvas from a tracked canvas appears in the transfer list.
|
|
10
|
+
*
|
|
11
|
+
* Worker-side recording additionally requires the customer to import
|
|
12
|
+
* `@sailfish-ai/recorder/canvas-worker` in the worker (the shim). The
|
|
13
|
+
* manager pings the worker (sf-record-ping/pong) to detect the shim; absent
|
|
14
|
+
* a pong, the canvas falls back to video capture of the placeholder.
|
|
15
|
+
*
|
|
16
|
+
* SSR rule: nothing here touches window/document at module scope — patches
|
|
17
|
+
* install inside installWorkerRelay().
|
|
18
|
+
*/
|
|
19
|
+
/** Manager subscription: fires when a canvas->worker link is discovered. */
|
|
20
|
+
export declare function setWorkerAssociationNotifier(fn: ((canvas: HTMLCanvasElement, worker: Worker) => void) | null): void;
|
|
21
|
+
export declare function installWorkerRelay(): void;
|
|
22
|
+
export declare function registerWorkerCanvas(canvas: HTMLCanvasElement, worker: Worker): void;
|
|
23
|
+
export declare function getWorkerForCanvas(canvas: HTMLCanvasElement): Worker | undefined;
|
|
24
|
+
export declare function unregisterWorkerCanvas(canvas: HTMLCanvasElement): void;
|
|
25
|
+
/**
|
|
26
|
+
* Detect the worker-side shim: resolves true iff the worker answers
|
|
27
|
+
* sf-record-ping with sf-record-pong within `timeoutMs`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function pingWorkerShim(worker: Worker, timeoutMs?: number): Promise<boolean>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Optional: explicitly register the OffscreenCanvas 2D context to record
|
|
2
|
+
* (the getContext patch normally does this automatically). */
|
|
3
|
+
export declare function registerWorkerCanvasContext(ctx: {
|
|
4
|
+
getTransform(): DOMMatrix;
|
|
5
|
+
}): void;
|
|
6
|
+
/** Optional: keep the recorder clock in sync with a custom render time. */
|
|
7
|
+
export declare function setWorkerNow(now: () => number): void;
|
|
8
|
+
/** Install the relay message listener (call once at worker startup —
|
|
9
|
+
* importing this module inside a worker does it automatically). */
|
|
10
|
+
export declare function installWorkerRecorder(): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface PersistedCanvasFrame {
|
|
2
|
+
id?: number;
|
|
3
|
+
timestamp: number;
|
|
4
|
+
bytes: number;
|
|
5
|
+
buf: ArrayBuffer;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Persist one binary canvas frame. Returns true if the frame was durably
|
|
9
|
+
* written, false if IDB is unavailable (caller should keep the memory-ring
|
|
10
|
+
* fallback). After a successful add, prunes oldest frames so the store stays
|
|
11
|
+
* under FRAME_STORE_MAX_BYTES.
|
|
12
|
+
*/
|
|
13
|
+
export declare function persistCanvasFrame(buf: ArrayBuffer): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Read all persisted frames in insertion order (ascending id), dropping any
|
|
16
|
+
* past the TTL and scheduling their background deletion. The returned `buf`s
|
|
17
|
+
* are ready to hand straight to the WS binary send path.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readAllCanvasFrames(): Promise<PersistedCanvasFrame[]>;
|
|
20
|
+
export declare function deleteCanvasFramesByIds(ids: number[]): Promise<void>;
|
|
21
|
+
export declare function clearCanvasFrames(): Promise<void>;
|
|
22
|
+
/** Diagnostics: total bytes currently persisted (0 when IDB unavailable). */
|
|
23
|
+
export declare function countPersistedCanvasFrameBytes(): Promise<number>;
|
package/dist/types/graphql.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ initialBackoff?: number, // Initial backoff in milliseconds
|
|
|
4
4
|
backoffFactor?: number): Promise<GraphQLResponse<T>>;
|
|
5
5
|
export declare function fetchCaptureSettings(apiKey: string, backendApi: string): Promise<GraphQLResponse<CaptureSettingsResponse>>;
|
|
6
6
|
export declare function fetchFunctionSpanTrackingEnabled(apiKey: string, backendApi: string): Promise<GraphQLResponse<FunctionSpanTrackingEnabledResponse>>;
|
|
7
|
-
export declare function startRecordingSession(apiKey: string, recordingId: string, backendApi: string, serviceIdentifier: string, serviceVersion?: string, mapUuid?: string, gitSha?: string, library?: string, serviceAdditionalMetadata?: Record<string, any
|
|
7
|
+
export declare function startRecordingSession(apiKey: string, recordingId: string, backendApi: string, serviceIdentifier: string, serviceVersion?: string, mapUuid?: string, gitSha?: string, library?: string, serviceAdditionalMetadata?: Record<string, any>, gitOrg?: string, gitRepo?: string, gitProvider?: string): Promise<GraphQLResponse<StartSessionResponse>>;
|
|
8
8
|
export declare function sendDomainsToNotPropagateHeaderTo(apiKey: string, domains: string[], backendApi: string): Promise<GraphQLResponse<void>>;
|
|
9
9
|
export declare function createTriageFromRecorder(apiKey: string, backendApi: string, recordingSessionId: string, timestampStart: string, timestampEnd: string, description?: string, triageSource?: string): Promise<GraphQLResponse<CreateTriageResponse>>;
|
|
10
10
|
export declare function fetchEngineeringTicketPlatformIntegrations(apiKey: string, backendApi: string): Promise<GraphQLResponse<GetEngineeringTicketPlatformIntegrationsFromApiKeyResponse>>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { LogRecordOptions } from "@sailfish-rrweb/rrweb-plugin-console-record";
|
|
2
2
|
import type { ShortcutsConfig } from "./inAppReportIssueModal";
|
|
3
3
|
import { CaptureSettings, MaskInputOptions } from "./types";
|
|
4
|
+
import { type ShapeRedactStyle, type TextRedactStyle } from "./canvas/canvasPrivacy";
|
|
4
5
|
export declare const STORAGE_VERSION = 1;
|
|
5
6
|
export declare const DEFAULT_CAPTURE_SETTINGS: CaptureSettings;
|
|
6
7
|
export declare const DEFAULT_CONSOLE_RECORDING_SETTINGS: LogRecordOptions;
|
|
7
8
|
export declare function toAbsoluteUrl(url: string): string;
|
|
8
9
|
export declare function matchUrlWithWildcard(input: unknown, patterns: string[]): boolean;
|
|
9
10
|
export declare function createSkipHeadersPropagationChecker(domainsToNotPropagateHeaderTo?: string[], domainsToPropagateHeaderTo?: string[]): (url: string) => boolean;
|
|
10
|
-
export declare function startRecording({ apiKey, backendApi, domainsToPropagateHeaderTo, domainsToNotPropagateHeaderTo, serviceVersion, serviceIdentifier, gitSha, serviceAdditionalMetadata, enableIpTracking, captureStreamingResponseBody, captureResponseBodyMaxMb, captureStreamPrefixKb, captureStreamTimeoutMs, enableFiberTracking, deferRecording, deferRecordingStart, chunkSnapshot, useWsWorker, capturePerformanceMetrics, maskTextClass, maskInputSelector, maskTextSelector, blockSelector, unmaskSelector, maskInputOptions, library, headlessRecording, }: {
|
|
11
|
+
export declare function startRecording({ apiKey, backendApi, domainsToPropagateHeaderTo, domainsToNotPropagateHeaderTo, serviceVersion, serviceIdentifier, gitSha, serviceAdditionalMetadata, enableIpTracking, captureStreamingResponseBody, captureResponseBodyMaxMb, captureStreamPrefixKb, captureStreamTimeoutMs, enableFiberTracking, deferRecording, deferRecordingStart, chunkSnapshot, useWsWorker, capturePerformanceMetrics, maskTextClass, maskInputSelector, maskTextSelector, blockSelector, unmaskSelector, maskInputOptions, recordCanvas, maskCanvasClass, gitOrg, gitRepo, gitProvider, library, headlessRecording, }: {
|
|
11
12
|
apiKey: string;
|
|
12
13
|
backendApi?: string;
|
|
13
14
|
domainsToPropagateHeaderTo?: string[];
|
|
@@ -35,6 +36,11 @@ export declare function startRecording({ apiKey, backendApi, domainsToPropagateH
|
|
|
35
36
|
blockSelector?: string;
|
|
36
37
|
unmaskSelector?: string;
|
|
37
38
|
maskInputOptions?: MaskInputOptions;
|
|
39
|
+
recordCanvas?: boolean;
|
|
40
|
+
maskCanvasClass?: string;
|
|
41
|
+
gitOrg?: string;
|
|
42
|
+
gitRepo?: string;
|
|
43
|
+
gitProvider?: string;
|
|
38
44
|
headlessRecording?: boolean;
|
|
39
45
|
}): Promise<void>;
|
|
40
46
|
export declare const initRecorder: (options: {
|
|
@@ -97,6 +103,32 @@ export declare const initRecorder: (options: {
|
|
|
97
103
|
capturePerformanceMetrics?: boolean;
|
|
98
104
|
showEngTicketFieldsInReportIssueModalDefault?: boolean;
|
|
99
105
|
maskTextClass?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Enable the Sailfish pixel-perfect **canvas** recorder (2D command stream +
|
|
108
|
+
* WebGL/WebGPU video + keyframes). A caller value wins over the dashboard
|
|
109
|
+
* Capture Setting. Off by default — see the Canvas Recording docs.
|
|
110
|
+
* Distinct from rrweb's legacy `recordCanvas` webp snapshotting, which this
|
|
111
|
+
* SDK force-disables; this option drives the Sailfish pipeline.
|
|
112
|
+
* @default false
|
|
113
|
+
*/
|
|
114
|
+
recordCanvas?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* CSS class that EXCLUDES a `<canvas>` (or any ancestor) from canvas recording
|
|
117
|
+
* entirely — no commands, no keyframes, no video. Whole-canvas masking; for
|
|
118
|
+
* partial masking use `maskCanvasDraws()`.
|
|
119
|
+
* @default "sailfishSanitize"
|
|
120
|
+
*/
|
|
121
|
+
maskCanvasClass?: string;
|
|
122
|
+
/**
|
|
123
|
+
* Click-to-code: optional git repo for this build, as a FALLBACK when the
|
|
124
|
+
* `@sailfish-ai/sf-map-utils` build plugin isn't used (the plugin auto-detects
|
|
125
|
+
* the git remote and is the preferred source). Sets the build's org/repo on the
|
|
126
|
+
* ServiceIdentifier so the replay user-journey shows org/repo + file:line
|
|
127
|
+
* deep-links per click. `gitProvider` is GITHUB | GITLAB | BITBUCKET.
|
|
128
|
+
*/
|
|
129
|
+
gitOrg?: string;
|
|
130
|
+
gitRepo?: string;
|
|
131
|
+
gitProvider?: string;
|
|
100
132
|
/**
|
|
101
133
|
* When true, recording proceeds even inside headless / automated browser
|
|
102
134
|
* environments (Puppeteer, Playwright, Cypress, etc.), bypassing the
|
|
@@ -117,6 +149,72 @@ export declare const initRecorder: (options: {
|
|
|
117
149
|
export * from "./graphql";
|
|
118
150
|
export { openReportIssueModal } from "./inAppReportIssueModal";
|
|
119
151
|
export * from "./recording";
|
|
152
|
+
/** Per-call redaction options for {@link maskCanvasDraws}. Each field overrides
|
|
153
|
+
* the global style (set via the `canvasRedactStyle` init option) for this block
|
|
154
|
+
* only; omitted fields inherit. */
|
|
155
|
+
export interface MaskCanvasDrawsOptions {
|
|
156
|
+
/** How masked TEXT renders: `"asterisks"` (default — every char → `*`,
|
|
157
|
+
* length-preserving) or `"bar"` (a solid redaction bar over the text). */
|
|
158
|
+
text?: TextRedactStyle;
|
|
159
|
+
/** How masked SHAPES render: `"color"` (default — the SAME shape, recolored to
|
|
160
|
+
* the redaction color), `"block"` (a solid block over the shape's bounding
|
|
161
|
+
* box), or `"remove"` (drop the shape entirely, when the geometry itself is
|
|
162
|
+
* sensitive). */
|
|
163
|
+
shape?: ShapeRedactStyle;
|
|
164
|
+
/** Mask ONLY text inside the block; record shapes/geometry normally. */
|
|
165
|
+
keepShapes?: boolean;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Sanitize SPECIFIC content drawn into a `<canvas>`. Wrap the draw calls that
|
|
169
|
+
* paint sensitive data — geometry, shapes, text, images — and they are **redacted
|
|
170
|
+
* in place**: the recorded command stream draws a redaction *version of the same
|
|
171
|
+
* element* (shapes recolored to a dark redaction color by default, text rendered
|
|
172
|
+
* as length-preserving asterisks), and the masked regions are **blanked on every
|
|
173
|
+
* pixel keyframe** so the real pixels never leave the browser. Layout is
|
|
174
|
+
* preserved; content is hidden. Everything drawn OUTSIDE the wrapper records
|
|
175
|
+
* normally, and the canvas stays pixel-perfect outside the masked regions.
|
|
176
|
+
*
|
|
177
|
+
* This is the only way to mask *part* of a canvas. CSS-selector masking
|
|
178
|
+
* (`data-sf-mask` / `maskCanvasClass` / `maskTextSelector`) is whole-`<canvas>`
|
|
179
|
+
* only — a canvas has no sub-elements for a selector to target, so to redact one
|
|
180
|
+
* shape/region you wrap the draw calls themselves.
|
|
181
|
+
*
|
|
182
|
+
* The redaction style is configurable per call via `opts` (or globally via the
|
|
183
|
+
* `canvasRedactStyle` init option):
|
|
184
|
+
* - `text`: `"asterisks"` (default) | `"bar"`
|
|
185
|
+
* - `shape`: `"color"` (default, same shape recolored) | `"block"` | `"remove"`
|
|
186
|
+
* - `keepShapes`: redact only text, leave shapes visible.
|
|
187
|
+
*
|
|
188
|
+
* Keep the wrapped draws **self-contained** (set the styles/transform you need
|
|
189
|
+
* inside it, or use `ctx.save()`/`ctx.restore()`): masked state setters
|
|
190
|
+
* (fill/stroke styles) are dropped, so a style you change inside but rely on
|
|
191
|
+
* *outside* won't carry over in replay.
|
|
192
|
+
*
|
|
193
|
+
* Safe no-op when canvas recording is off, or when this context isn't being
|
|
194
|
+
* recorded (snippet tier before attach, masked canvas, SSR): it just runs
|
|
195
|
+
* `draw()` and returns its result. 2D contexts only — for WebGL/WebGPU (whose
|
|
196
|
+
* content is pixels, not commands) exclude the whole canvas with `data-sf-mask`.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* import { maskCanvasDraws } from '@sailfish-ai/recorder';
|
|
200
|
+
*
|
|
201
|
+
* function render(ctx) {
|
|
202
|
+
* ctx.fillStyle = '#0a0';
|
|
203
|
+
* ctx.fillRect(0, 0, 800, 600); // recorded
|
|
204
|
+
*
|
|
205
|
+
* maskCanvasDraws(ctx, () => { // redacted in place:
|
|
206
|
+
* ctx.fillStyle = '#007BFF';
|
|
207
|
+
* ctx.fillRect(rectX, rectY, w, h); // ← same rect, recolored to redaction color
|
|
208
|
+
* drawJSTSGeometry(sensitivePolygon, ctx); // ← polygon redacted in place
|
|
209
|
+
* ctx.fillText(patientName, x, y); // ← "***********" (length preserved)
|
|
210
|
+
* });
|
|
211
|
+
*
|
|
212
|
+
* // drop the geometry entirely instead of recoloring it:
|
|
213
|
+
* maskCanvasDraws(ctx, () => drawJSTSGeometry(secretShape, ctx), { shape: 'remove' });
|
|
214
|
+
* }
|
|
215
|
+
*/
|
|
216
|
+
export declare function maskCanvasDraws<T>(target: CanvasRenderingContext2D | HTMLCanvasElement, draw: () => T, opts?: MaskCanvasDrawsOptions): T;
|
|
217
|
+
export { registerKonvaStage, registerFabricCanvas, registerChart, registerThreeScene, registerPixiApp, type SemanticTarget, } from "./canvas/adapters";
|
|
120
218
|
export * from "./sendSailfishMessages";
|
|
121
219
|
export { getOrSetSessionId } from "./session";
|
|
122
220
|
export * from "./types";
|
package/dist/types/types.d.ts
CHANGED
|
@@ -17,7 +17,30 @@ export type MaskInputOptions = Partial<{
|
|
|
17
17
|
password: boolean;
|
|
18
18
|
}>;
|
|
19
19
|
export interface CaptureSettings {
|
|
20
|
+
/**
|
|
21
|
+
* Enable the Sailfish canvas-recording pipeline (SFCV): command/keyframe/video
|
|
22
|
+
* capture of `<canvas>` content shipped as binary frames over the WebSocket.
|
|
23
|
+
* Default FALSE (opt-in). This is the single canvas-recording flag — settable
|
|
24
|
+
* via the dashboard / server payload, or the `initRecorder({ recordCanvas })`
|
|
25
|
+
* option. NB: rrweb's own legacy webp canvas snapshotting is ALWAYS
|
|
26
|
+
* force-disabled at the rrweb `record()` call, so `record()` never receives
|
|
27
|
+
* `recordCanvas: true`; this flag drives ONLY the Sailfish pipeline.
|
|
28
|
+
*/
|
|
20
29
|
recordCanvas: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Backend wire field only (GraphQL `recordCanvasSf` / DB column
|
|
32
|
+
* `record_canvas_sf`). Read ONCE during settings assembly and mapped onto the
|
|
33
|
+
* unified `recordCanvas` above — never read elsewhere in the SDK. Kept so the
|
|
34
|
+
* server `captureSettingsFromApiKey` payload type-checks without renaming the
|
|
35
|
+
* backend field.
|
|
36
|
+
*/
|
|
37
|
+
recordCanvasSf?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Canvases matching `.${maskCanvasClass}`, `[data-sf-mask]`, or inside a
|
|
40
|
+
* masked subtree record NOTHING (an op:"masked" index event is emitted).
|
|
41
|
+
* Default "sailfishSanitize".
|
|
42
|
+
*/
|
|
43
|
+
maskCanvasClass?: string;
|
|
21
44
|
recordCrossOriginIframes: boolean;
|
|
22
45
|
collectFonts: boolean;
|
|
23
46
|
inlineImages: boolean;
|
|
@@ -35,6 +58,23 @@ export interface CaptureSettings {
|
|
|
35
58
|
blockSelector?: string;
|
|
36
59
|
unmaskSelector?: string;
|
|
37
60
|
maskInputOptions?: MaskInputOptions;
|
|
61
|
+
/**
|
|
62
|
+
* Canvas text masking. `true` redacts drawn text on every recorded canvas;
|
|
63
|
+
* `false` opts out of the mirror-DOM default; unset = mirror-DOM (if DOM text
|
|
64
|
+
* masking — `maskTextSelector`/`maskTextClass` — is configured, canvas text is
|
|
65
|
+
* masked too). Independent of `maskCanvasClass` (which suppresses whole
|
|
66
|
+
* canvases).
|
|
67
|
+
*/
|
|
68
|
+
maskCanvasText?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Global canvas redaction style. Text → `"asterisks"` (default) | `"bar"`;
|
|
71
|
+
* shapes (maskCanvasDraws) → `"color"` (default, same shape recolored) |
|
|
72
|
+
* `"block"` | `"remove"`. Per-call `maskCanvasDraws` options override it.
|
|
73
|
+
*/
|
|
74
|
+
canvasRedactStyle?: {
|
|
75
|
+
text?: "asterisks" | "bar";
|
|
76
|
+
shape?: "color" | "block" | "remove";
|
|
77
|
+
};
|
|
38
78
|
}
|
|
39
79
|
export interface ConsoleRecordSettings {
|
|
40
80
|
level: string[];
|
|
@@ -3,6 +3,25 @@ export interface WsHandle {
|
|
|
3
3
|
readyState: number;
|
|
4
4
|
close: () => void;
|
|
5
5
|
}
|
|
6
|
+
/** Count of binary frames dropped from the ring (diagnostics). */
|
|
7
|
+
export declare function getBinaryDropCount(): number;
|
|
8
|
+
/**
|
|
9
|
+
* Current socket send-buffer depth in bytes (best effort). Worker path: the
|
|
10
|
+
* worker posts `bufferedAmount` on a light interval and we cache it. Direct
|
|
11
|
+
* path: read live. 0 when the socket is closed/unavailable.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getBufferedAmount(): number;
|
|
14
|
+
/** True when the socket's send buffer is over the saturation threshold —
|
|
15
|
+
* callers (canvas video capture) should pause producing new frames. */
|
|
16
|
+
export declare function isTransportSaturated(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Send a binary frame (canvas SFCV) over the recording WebSocket.
|
|
19
|
+
* Direct path: webSocket.send(ArrayBuffer); worker path: transferred
|
|
20
|
+
* postMessage. When the socket is not open, the frame is buffered durably in
|
|
21
|
+
* IndexedDB (surviving reload) with a memory-ring fallback, and flushed on
|
|
22
|
+
* reconnect.
|
|
23
|
+
*/
|
|
24
|
+
export declare function wsSendBinary(buffer: ArrayBuffer): boolean;
|
|
6
25
|
export declare function requestTimeSync(): void;
|
|
7
26
|
type NavigationCallback = () => void;
|
|
8
27
|
/** Register a callback to be invoked immediately on pushState/replaceState/popstate/hashchange. */
|