@sailfish-ai/recorder 1.12.5 → 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-Cd8y9tID.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-pkpgT-FH.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-Bvd8oTpV.js → index-BIj4l3CV.js} +1025 -655
- package/dist/chunks/index-BIj4l3CV.js.br +0 -0
- package/dist/chunks/index-BIj4l3CV.js.gz +0 -0
- package/dist/chunks/{index-CXvHbLRT.js → index-DIYomvdD.js} +1115 -724
- 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/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 +4842 -2215
- 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-Cd8y9tID.js.br +0 -0
- package/dist/chunks/chunkSerializer-Cd8y9tID.js.gz +0 -0
- package/dist/chunks/chunkSerializer-pkpgT-FH.js.br +0 -0
- package/dist/chunks/chunkSerializer-pkpgT-FH.js.gz +0 -0
- package/dist/chunks/index-Bvd8oTpV.js.br +0 -0
- package/dist/chunks/index-Bvd8oTpV.js.gz +0 -0
- package/dist/chunks/index-CXvHbLRT.js.br +0 -0
- package/dist/chunks/index-CXvHbLRT.js.gz +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global 2D-context instrumentation: prototype patches that feed per-context
|
|
3
|
+
* recorders. Port of the validated bench core
|
|
4
|
+
* (test-applications/canvas-recorder-bench/src/capture/patch2d.ts).
|
|
5
|
+
*
|
|
6
|
+
* Captures: every CanvasRenderingContext2D method call + property set,
|
|
7
|
+
* gradient/pattern creation (interned objects), image sources (asset CAS),
|
|
8
|
+
* ImageData, and a shadow state stack (props + transform per save()) that
|
|
9
|
+
* makes keyframe seeking possible.
|
|
10
|
+
*
|
|
11
|
+
* SDK adaptations (everything else is a faithful port):
|
|
12
|
+
* - NO window/document/navigator access at module top level (SSR rule);
|
|
13
|
+
* all installation happens inside exported install functions.
|
|
14
|
+
* - `encodeImage` returns `{size, bytes?}` so encoded webp payloads can be
|
|
15
|
+
* shipped as SFCV asset frames (sink.onAssetData), not just size-counted.
|
|
16
|
+
* - `retainAssets` option: the worker-relay path keeps bitmaps/ImageData for
|
|
17
|
+
* postMessage transfer (bench behavior); the main-thread manager passes
|
|
18
|
+
* false so per-draw dynamic-source assets do not accumulate in memory.
|
|
19
|
+
* - Untracked-context + context-created notifiers so the production manager
|
|
20
|
+
* can discover contexts created BEFORE hooks installed (first method call
|
|
21
|
+
* after install) and AFTER (getContext auto-attach path).
|
|
22
|
+
* - DiscardSink short-circuit: in video mode the recorder stays attached
|
|
23
|
+
* purely for dirtiness; ops are dropped BEFORE argument serialization.
|
|
24
|
+
*/
|
|
25
|
+
import { type MaskRegion, type RedactStyle } from "./canvasPrivacy";
|
|
26
|
+
import { type AssetMode, type CapturedAsset, type EncodeImageFn, type RecorderSink, type StateSnapshot } from "./protocol";
|
|
27
|
+
export { BufferSink, serializeTypedArray, deserializeTypedArray, TYPED_ARRAY_KINDS, } from "./protocol";
|
|
28
|
+
export type { AssetMode, CapturedAsset, RecordedOp, RecorderSink, SerializedArg, StateSnapshot, } from "./protocol";
|
|
29
|
+
export declare function notePreAttachMaskUsed(canvas?: HTMLCanvasElement | null): void;
|
|
30
|
+
export declare function wasPreAttachMaskUsed(canvas?: HTMLCanvasElement | null): boolean;
|
|
31
|
+
export declare function ignore2DContext(ctx: object): void;
|
|
32
|
+
export declare function setUntracked2DContextNotifier(fn: ((ctx: CanvasRenderingContext2D) => void) | null): void;
|
|
33
|
+
/**
|
|
34
|
+
* Return the active recorder bound to a 2D context, or undefined when the
|
|
35
|
+
* context isn't being recorded (recording off, not yet attached, or a
|
|
36
|
+
* non-recorded canvas). Used by the `maskCanvasDraws` public API to toggle
|
|
37
|
+
* per-draw masking. Never throws.
|
|
38
|
+
*/
|
|
39
|
+
export declare function recorderForContext(ctx: object | null | undefined): ContextRecorder | undefined;
|
|
40
|
+
export declare function setContextCreatedNotifier(fn: ((canvas: HTMLCanvasElement, type: string) => void) | null): void;
|
|
41
|
+
/**
|
|
42
|
+
* Build-plugin-tier emulation: when auto-attach is armed (BEFORE app/scene
|
|
43
|
+
* code runs), every newly created 2D context gets a recorder with a buffering
|
|
44
|
+
* sink. attach() later swaps in the strategy's real sink and drains the
|
|
45
|
+
* buffer — so creation-time work (gradients, initial render) is captured,
|
|
46
|
+
* exactly like the production pre-app wrapper.
|
|
47
|
+
*/
|
|
48
|
+
export interface AutoAttachConfig {
|
|
49
|
+
now: () => number;
|
|
50
|
+
assetMode: AssetMode;
|
|
51
|
+
encodeImage: EncodeImageFn;
|
|
52
|
+
retainAssets?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** What kind of context each canvas created (set by the getContext patch). */
|
|
55
|
+
export interface CanvasContextInfo {
|
|
56
|
+
type: string;
|
|
57
|
+
attrs?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
export declare function getContextInfo(canvas: HTMLCanvasElement): CanvasContextInfo | undefined;
|
|
60
|
+
export declare function setAutoAttach(config: AutoAttachConfig | null): void;
|
|
61
|
+
/** Drop the auto-created (still-buffering) recorder for one canvas the
|
|
62
|
+
* manager decided not to record — e.g. invisible canvases (Konva hit
|
|
63
|
+
* canvases) whose BufferSink would otherwise grow unbounded while they keep
|
|
64
|
+
* drawing. No-op once a strategy sink is bound. */
|
|
65
|
+
export declare function releaseAutoRecorder(canvas: HTMLCanvasElement): void;
|
|
66
|
+
/** Drop auto-created recorders that no strategy bound (e.g. probe canvases). */
|
|
67
|
+
export declare function pruneUnboundRecorders(): void;
|
|
68
|
+
export declare class ContextRecorder {
|
|
69
|
+
/** main-thread or OffscreenCanvas 2D context */
|
|
70
|
+
private ctx;
|
|
71
|
+
private sink;
|
|
72
|
+
private now;
|
|
73
|
+
private assetMode;
|
|
74
|
+
private encodeImage;
|
|
75
|
+
readonly assets: Map<number, CapturedAsset>;
|
|
76
|
+
/** dedupe static image sources by identity */
|
|
77
|
+
private assetBySource;
|
|
78
|
+
private pendingEncodes;
|
|
79
|
+
private props;
|
|
80
|
+
private stack;
|
|
81
|
+
/** accumulated main-thread ms spent in capture code */
|
|
82
|
+
cpuMs: number;
|
|
83
|
+
/** a strategy sink has been bound (vs still buffering) */
|
|
84
|
+
bound: boolean;
|
|
85
|
+
/** keep bitmaps/ImageData on assets for postMessage transfer (worker path) */
|
|
86
|
+
private retainAssets;
|
|
87
|
+
constructor(
|
|
88
|
+
/** main-thread or OffscreenCanvas 2D context */
|
|
89
|
+
ctx: {
|
|
90
|
+
getTransform(): DOMMatrix;
|
|
91
|
+
}, sink: RecorderSink, now: () => number, assetMode: AssetMode, encodeImage: EncodeImageFn, opts?: {
|
|
92
|
+
retainAssets?: boolean;
|
|
93
|
+
});
|
|
94
|
+
get rawCtx(): object;
|
|
95
|
+
/** Bind the strategy's sink, draining anything buffered since getContext. */
|
|
96
|
+
swapSink(sink: RecorderSink): void;
|
|
97
|
+
/**
|
|
98
|
+
* A26: register a captured asset's metadata, evicting the oldest entries when
|
|
99
|
+
* the per-recorder cap is exceeded (LRU; Map keeps insertion order). Eviction
|
|
100
|
+
* only happens on the non-retaining manager path — when retainAssets is true
|
|
101
|
+
* (worker relay) the full set is transferred at stop, so we never evict there.
|
|
102
|
+
*/
|
|
103
|
+
private addAsset;
|
|
104
|
+
/**
|
|
105
|
+
* Draw-site stamp id for the NEXT recorded op (Phase 3b). Set synchronously
|
|
106
|
+
* by __sfDrawSite (canvasManager) when stamped app code calls
|
|
107
|
+
* `__sfds(ctx, id).method(...)` — the method call runs immediately after, in
|
|
108
|
+
* the same task, so a single pending slot is race-free. Consumed + cleared
|
|
109
|
+
* by the next recordCall/recordSet.
|
|
110
|
+
*/
|
|
111
|
+
pendingSiteId?: string;
|
|
112
|
+
/**
|
|
113
|
+
* A13 — privacy text redactor. When set (canvasManager installs it for
|
|
114
|
+
* "redact" and "normal" canvases that have a privacy rule / content-detection
|
|
115
|
+
* flag off), the FIRST string argument of `fillText` / `strokeText` is passed
|
|
116
|
+
* through this fn BEFORE serialization, so the original drawn text never
|
|
117
|
+
* reaches the command stream. Returns the (possibly masked) replacement
|
|
118
|
+
* string; non-string args are never handed to it. Left undefined on the hot
|
|
119
|
+
* path for canvases with no privacy rule, so normal recording pays nothing.
|
|
120
|
+
*/
|
|
121
|
+
textRedactor?: (text: string) => string;
|
|
122
|
+
/**
|
|
123
|
+
* Runtime per-draw masking (the `maskCanvasDraws` public API). While
|
|
124
|
+
* `maskDepth > 0`, EVERY recorded op is DROPPED — `fillRect`, path
|
|
125
|
+
* construction (`moveTo`/`lineTo`/`arc`/…), `fill`/`stroke`, `fillText`,
|
|
126
|
+
* `drawImage`, state setters — so the sensitive shape/geometry/text the app
|
|
127
|
+
* draws inside the wrapped block never reaches the command stream. This is the
|
|
128
|
+
* only way to sanitize SPECIFIC content within a canvas: DOM/selector masking
|
|
129
|
+
* is whole-`<canvas>` only (there are no sub-canvas elements to target).
|
|
130
|
+
*/
|
|
131
|
+
maskDepth: number;
|
|
132
|
+
/**
|
|
133
|
+
* Set true the first time a draw is masked by `maskDepth`. The canvas manager
|
|
134
|
+
* polls this to keep the canvas in command mode (a masked canvas must never
|
|
135
|
+
* demote to video — video frames can't be cheaply region-blanked) and to wire
|
|
136
|
+
* `maskedRegions` into the keyframe capturer so the real pixels of masked
|
|
137
|
+
* content are blanked on every keyframe. Replay shows the baked redaction
|
|
138
|
+
* (recolored shapes / asterisks / bars) from the command stream.
|
|
139
|
+
*/
|
|
140
|
+
hasMaskedDraws: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Resolved redaction style for masked ops — how masked TEXT (asterisks/bar) and
|
|
143
|
+
* SHAPES (color/block/remove) are baked into the command stream. Set by the
|
|
144
|
+
* manager from the global `maskCanvasText` config, and overridden per-block by
|
|
145
|
+
* `maskCanvasDraws(ctx, fn, opts)`.
|
|
146
|
+
*/
|
|
147
|
+
redactStyle: RedactStyle;
|
|
148
|
+
/**
|
|
149
|
+
* Per-block "text only" flag (maskCanvasDraws `keepShapes:true`). While set
|
|
150
|
+
* inside a mask block, only TEXT ops are redacted; shapes/geometry are recorded
|
|
151
|
+
* normally. Default false (mask everything in the block).
|
|
152
|
+
*/
|
|
153
|
+
textOnlyMask: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Accumulated device-space (canvas-pixel) bounding boxes of masked content
|
|
156
|
+
* since the last full-canvas clear. The keyframe capturer fills each with
|
|
157
|
+
* REDACTION_COLOR before encoding so the real masked pixels never ship. Bounded
|
|
158
|
+
* (oldest evicted past the cap) so an incremental canvas that never clears
|
|
159
|
+
* cannot grow it without limit.
|
|
160
|
+
*/
|
|
161
|
+
readonly maskedRegions: MaskRegion[];
|
|
162
|
+
/**
|
|
163
|
+
* Set when a masked op's keyframe-blank region CANNOT be computed (a degenerate
|
|
164
|
+
* transform, or geometry with no trackable bounds such as `ctx.fill(path2d)`).
|
|
165
|
+
* The redaction is already baked into the command stream, so to avoid leaking
|
|
166
|
+
* the real pixels the manager polls this and SUPPRESSES pixel keyframes for the
|
|
167
|
+
* canvas (command-only replay) — safer than blanking the whole canvas on every
|
|
168
|
+
* keyframe. [audit C-NEW-1 / C2 / M-NEW-2]
|
|
169
|
+
*/
|
|
170
|
+
keyframeUnsafe: boolean;
|
|
171
|
+
/** Running LOCAL-space (pre-transform) path bounds for the in-progress path,
|
|
172
|
+
* used to size the masked-region bbox + the synthetic block fillRect for
|
|
173
|
+
* `fill`/`stroke` inside a mask block. Reset on beginPath. */
|
|
174
|
+
private maskPathBounds;
|
|
175
|
+
private takePendingSiteId;
|
|
176
|
+
recordCall(op: string, args: unknown[]): void;
|
|
177
|
+
/**
|
|
178
|
+
* A34 — hot-path arg serialization. A plain loop instead of
|
|
179
|
+
* `args.map((a) => this.serializeArg(a, t))` to avoid allocating a new arrow
|
|
180
|
+
* closure on every recorded call. Primitives (number / string / boolean /
|
|
181
|
+
* null / undefined) are handled inline so the common all-primitive draw call
|
|
182
|
+
* (fillRect, lineTo, setTransform…) never enters the slower object-dispatch
|
|
183
|
+
* branch of serializeArg. Produces an array byte-identical to the old .map
|
|
184
|
+
* output for the codec — verified by canvasHotPathAlloc.test.ts. The output
|
|
185
|
+
* array itself is freshly allocated (the BufferSink / worker paths retain it,
|
|
186
|
+
* so it cannot be pooled), but the per-call closure garbage is eliminated.
|
|
187
|
+
*/
|
|
188
|
+
private serializeArgs;
|
|
189
|
+
recordSet(prop: string, value: unknown): void;
|
|
190
|
+
/** Redact one op issued inside a mask block. */
|
|
191
|
+
private maskCall;
|
|
192
|
+
/** Record a shape op normally from inside a keepShapes block (text-only mask).
|
|
193
|
+
* A trimmed recordCall tail: maintains the save/restore shadow stack so later
|
|
194
|
+
* keyframe state snapshots stay correct. */
|
|
195
|
+
private recordPassthrough;
|
|
196
|
+
/** Push a non-empty masked region. Past the cap, COALESCE to the union bbox
|
|
197
|
+
* rather than evicting the oldest — dropping a region would leave its real
|
|
198
|
+
* (masked) pixels un-blanked on the kept keyframe (a leak). Coalescing
|
|
199
|
+
* over-blanks within the union but never under-blanks (privacy > fidelity for
|
|
200
|
+
* a pathological canvas that masks >cap draws without ever clearing). */
|
|
201
|
+
private pushRegion;
|
|
202
|
+
/**
|
|
203
|
+
* Bound the region list past the cap WITHOUT dropping coverage (dropping would
|
|
204
|
+
* leak) and WITHOUT a one-way ratchet to whole-canvas (a single growing union
|
|
205
|
+
* could never be cleared by a partial clearRect — audit C-NEW-2). Snap regions
|
|
206
|
+
* to a coarse GRID of tiles: bounded count, full coverage, and a later clear can
|
|
207
|
+
* still drop individual tiles (clipRegionsToClear). Falls back to a single union
|
|
208
|
+
* only when the canvas size is unknown (worker/synthetic ctx).
|
|
209
|
+
*/
|
|
210
|
+
private coalesceRegions;
|
|
211
|
+
/** Push the keyframe-blank region for a MASKED op. A redaction was already
|
|
212
|
+
* baked into the command stream, so the pixels MUST be blanked — if the bbox
|
|
213
|
+
* couldn't be computed (degenerate transform, or unbounded geometry like
|
|
214
|
+
* `ctx.fill(path2d)` / a zero-area paint), flag the canvas keyframe-UNSAFE so
|
|
215
|
+
* the manager suppresses pixel keyframes (command-only replay) rather than
|
|
216
|
+
* blanking the whole canvas on every keyframe. [audit C-NEW-1 / M-NEW-2] */
|
|
217
|
+
private pushMaskRegion;
|
|
218
|
+
/** Clear accumulated masked regions — called by the manager when the canvas is
|
|
219
|
+
* resized (a `canvas.width`/`height` assignment, or a `reset()`, wipes all
|
|
220
|
+
* pixels, so the prior regions are stale and would over-blank fresh content).
|
|
221
|
+
*
|
|
222
|
+
* [audit H1 / Round 2] Also clears `keyframeUnsafe`. A full-canvas clear/resize
|
|
223
|
+
* wipes whatever unboundable masked content tripped the flag, so the canvas is
|
|
224
|
+
* safe to keyframe again. Without this, a canvas that hit ONE degenerate-bbox
|
|
225
|
+
* mask op and was THEN resized still suppressed pixel keyframes for life. NOTE:
|
|
226
|
+
* this is only effective when the resize lands BEFORE the pump reads the flag —
|
|
227
|
+
* once `suppressPixelCapture` latches `ctl.pixelSuppressed` (tearing down the
|
|
228
|
+
* keyframe capturer + any video encoder), there is no cheap resurrection, so an
|
|
229
|
+
* already-suppressed controller stays suppressed by design (a deliberate
|
|
230
|
+
* one-way safety tradeoff — resurrecting mid-session risks shipping the very
|
|
231
|
+
* pixels we suppressed). Resetting the flag here keeps the recorder-side state
|
|
232
|
+
* honest and avoids a needless suppression for the pre-pump resize race. */
|
|
233
|
+
resetMaskedRegions(): void;
|
|
234
|
+
/** A clearRect wiped the pixels in its (device-space) rect — drop every masked
|
|
235
|
+
* region FULLY CONTAINED by it so the next keyframe doesn't blank now-empty
|
|
236
|
+
* areas. Works for a full-canvas clear (all regions inside → all dropped, i.e.
|
|
237
|
+
* a reset), a sub-rect clear, AND a TRANSLATED/SCALED clear (the cleared rect is
|
|
238
|
+
* wherever it actually landed in device space). A region that only PARTIALLY
|
|
239
|
+
* overlaps the cleared rect is KEPT — its still-painted part must stay blanked
|
|
240
|
+
* (over-blank the cleared part; never leak). [audit M1/M2]
|
|
241
|
+
*
|
|
242
|
+
* [audit H5 / Round 2] Containment is only sound when the cleared rect is
|
|
243
|
+
* AXIS-ALIGNED in device space. Under a ROTATED/SKEWED CTM, clearRect clears a
|
|
244
|
+
* PARALLELOGRAM, but `transformedBox` gives its AABB — which is strictly LARGER
|
|
245
|
+
* than the true cleared area. A masked region tucked into an AABB corner that
|
|
246
|
+
* the rotated clear never actually touched would be (wrongly) judged contained
|
|
247
|
+
* and DROPPED → its still-painted pixels leak on the kept keyframe. The clear's
|
|
248
|
+
* AABB over-claiming coverage is the dangerous direction (dropping a region is
|
|
249
|
+
* irreversible), so for a non-axis-aligned CTM we DROP NOTHING — the regions
|
|
250
|
+
* stay and over-blank the (now-cleared) area on the next keyframe, which is
|
|
251
|
+
* privacy-safe. A pure translate/scale/flip (b==0 && c==0, or a==0 && d==0)
|
|
252
|
+
* keeps the exact-rect guarantee and still clips. */
|
|
253
|
+
private clipRegionsToClear;
|
|
254
|
+
/** Emit a synthetic recorded method call (bypasses the maskDepth gate). */
|
|
255
|
+
private emitSynthCall;
|
|
256
|
+
/** Emit a synthetic recorded property set (a color override). */
|
|
257
|
+
private emitSynthSetter;
|
|
258
|
+
/** Emit a redaction block (save → fillStyle=REDACTION_COLOR → fillRect →
|
|
259
|
+
* restore) over a LOCAL-space rect; replay applies the current transform. */
|
|
260
|
+
private emitRedactionBlock;
|
|
261
|
+
/** Update the running LOCAL (user-space) path bounds for a path-build op. */
|
|
262
|
+
private updatePathBounds;
|
|
263
|
+
/** LOCAL (user-space) rect of a paint op — for the block fillRect. */
|
|
264
|
+
private shapeLocalRect;
|
|
265
|
+
/** DEVICE-space region of a paint op (its accumulated keyframe-blank bbox). */
|
|
266
|
+
private shapeRegion;
|
|
267
|
+
/** Dest rect (user space) of a drawImage in any of its 3/5/9-arg forms. */
|
|
268
|
+
private drawImageDest;
|
|
269
|
+
/** Device-space region of a drawn-text arg (the keyframe-blank bbox). The box
|
|
270
|
+
* comes from the glyph ADVANCE width, so it's padded proportionally to the font
|
|
271
|
+
* size AND the transform's scale to cover italic / emoji ink overhang in device
|
|
272
|
+
* space (under-covering would leak a glyph sliver on the kept keyframe). */
|
|
273
|
+
private textRegionFor;
|
|
274
|
+
/** LOCAL-space rect of a drawn-text arg (for a bar / block). */
|
|
275
|
+
private textLocalRect;
|
|
276
|
+
/** Measure a drawn-text arg (width/ascent/descent + align/baseline), falling
|
|
277
|
+
* back to a font-size approximation when the context has no measureText (worker
|
|
278
|
+
* OffscreenCanvas always does; the test fake ctx does not). */
|
|
279
|
+
private textMetrics;
|
|
280
|
+
/** Parse the px size from the recorded `font` prop (fallback 12). */
|
|
281
|
+
private fontSizePx;
|
|
282
|
+
/** The canvas backing the context (null for the test fake / detached ctx). */
|
|
283
|
+
private canvasSize;
|
|
284
|
+
/** Current transform as a plain Mat2D (null when getTransform throws). */
|
|
285
|
+
private getMat;
|
|
286
|
+
/** per-recorder emission state for cross-context objects */
|
|
287
|
+
private objRefs;
|
|
288
|
+
snapshotState(): StateSnapshot;
|
|
289
|
+
settle(): Promise<void>;
|
|
290
|
+
/** [audit B6] In-flight encode count — exported for tests to assert the set
|
|
291
|
+
* stays bounded (auto-removes on completion) rather than growing per frame. */
|
|
292
|
+
pendingEncodeCount(): number;
|
|
293
|
+
/** [audit B6] Register an in-flight encode so settle() awaits it, and AUTO-REMOVE
|
|
294
|
+
* it on completion so `pendingEncodes` never grows unbounded over a long session
|
|
295
|
+
* of per-frame dynamic-source captures. The internal then/finally swallow keeps
|
|
296
|
+
* the tracking from raising an unhandled rejection (settle still observes the
|
|
297
|
+
* original promise's outcome via the snapshot). */
|
|
298
|
+
private trackEncode;
|
|
299
|
+
private readTransform;
|
|
300
|
+
private serializeArg;
|
|
301
|
+
/**
|
|
302
|
+
* Factory objects (gradients/patterns/paths) emit lazily into THIS stream
|
|
303
|
+
* on first use — a __create plus logged method calls; later uses emit only
|
|
304
|
+
* newly appended calls. Cross-context safe: every stream that references
|
|
305
|
+
* the object re-creates it locally under the same global id.
|
|
306
|
+
*/
|
|
307
|
+
private serializeFactoryObject;
|
|
308
|
+
private captureImageData;
|
|
309
|
+
private captureImageSource;
|
|
310
|
+
}
|
|
311
|
+
/** A cross-origin/tainted canvas readback throws a DOMException named
|
|
312
|
+
* "SecurityError" (toDataURL / createImageBitmap on a tainted source). Match by
|
|
313
|
+
* name (and message as a belt-and-braces fallback) so unrelated asset failures
|
|
314
|
+
* — OOM, codec errors — do NOT get reported as taint. */
|
|
315
|
+
export declare function isTaintError(e: unknown): boolean;
|
|
316
|
+
/** Patch one 2D-context prototype (CanvasRenderingContext2D on the main
|
|
317
|
+
* thread, OffscreenCanvasRenderingContext2D inside workers). */
|
|
318
|
+
export declare function patch2DProto(protoObj: object): void;
|
|
319
|
+
/** Main-thread installation: 2D proto + getContext auto-attach + kind registry. */
|
|
320
|
+
export declare function installCommandHooks(): void;
|
|
321
|
+
/** Attach directly to a known 2D context (worker-side OffscreenCanvas path,
|
|
322
|
+
* or main-thread contexts created before the hooks were installed). */
|
|
323
|
+
export declare function attachRecorderToContext(ctx: {
|
|
324
|
+
getTransform(): DOMMatrix;
|
|
325
|
+
}, sink: RecorderSink, now: () => number, assetMode: AssetMode, encodeImage: EncodeImageFn, opts?: {
|
|
326
|
+
retainAssets?: boolean;
|
|
327
|
+
}): ContextRecorder;
|
|
328
|
+
export declare function attachRecorder(canvas: HTMLCanvasElement, sink: RecorderSink, now: () => number, assetMode: AssetMode, encodeImage: EncodeImageFn, opts?: {
|
|
329
|
+
retainAssets?: boolean;
|
|
330
|
+
}): ContextRecorder | null;
|
|
331
|
+
export declare function detachRecorder(canvas: HTMLCanvasElement): void;
|
|
332
|
+
export declare function detachRecorderFromContext(ctx: object): void;
|
|
333
|
+
/**
|
|
334
|
+
* Draw-site stamp routing (Phase 3b): when stamped app code calls
|
|
335
|
+
* `__sfds(ctx, id).fillRect(...)`, canvasManager's __sfDrawSite calls this
|
|
336
|
+
* with the 2D context object; if it has a recorder, the id rides the next op.
|
|
337
|
+
* Returns true when handled (ctx is a recorded 2D context).
|
|
338
|
+
*/
|
|
339
|
+
export declare function noteCtxPendingSiteId(ctx: object, id: string): boolean;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGL1/2 command capture — port of the validated bench core
|
|
3
|
+
* (test-applications/canvas-recorder-bench/src/capture/patchGL.ts;
|
|
4
|
+
* evanw/webgl-recorder lineage, binary-encoded). Creator calls intern their
|
|
5
|
+
* results via the same __create meta-op the 2D path uses, so the shared codec
|
|
6
|
+
* + replayer machinery handles GL streams unchanged.
|
|
7
|
+
*
|
|
8
|
+
* Replay fidelity is "deterministic on the same GPU/driver" — pixel keyframes
|
|
9
|
+
* remain the bit-exact truth, exactly like the 2D path.
|
|
10
|
+
*
|
|
11
|
+
* SDK adaptations: SSR-safe installation (no top-level globals), untracked-
|
|
12
|
+
* context notifier for contexts created before install, DiscardSink
|
|
13
|
+
* short-circuit, attachGLRecorderToContext for mid-stream attach.
|
|
14
|
+
*/
|
|
15
|
+
import { type RecorderSink } from "./protocol";
|
|
16
|
+
type GL = WebGLRenderingContext | WebGL2RenderingContext;
|
|
17
|
+
/**
|
|
18
|
+
* Draw-site stamp routing (Phase 3b): when stamped app code calls
|
|
19
|
+
* `__sfds(glctx, id).drawArrays(...)`, canvasManager's __sfDrawSite calls this
|
|
20
|
+
* with the GL context; if it has a recorder, the id rides the next op.
|
|
21
|
+
* Returns true when handled (ctx is a recorded GL context).
|
|
22
|
+
*/
|
|
23
|
+
export declare function noteGLPendingSiteId(ctx: object, id: string): boolean;
|
|
24
|
+
export declare function setUntrackedGLContextNotifier(fn: ((ctx: GL) => void) | null): void;
|
|
25
|
+
/**
|
|
26
|
+
* Build-plugin-tier auto-attach, mirroring the 2D path: when armed before
|
|
27
|
+
* scene/app code runs, every new WebGL(2) context gets a buffering recorder —
|
|
28
|
+
* shader compiles and geometry uploads happen at app init and MUST be in the
|
|
29
|
+
* stream or replay renders nothing.
|
|
30
|
+
*/
|
|
31
|
+
export interface GLAutoConfig {
|
|
32
|
+
now: () => number;
|
|
33
|
+
}
|
|
34
|
+
export declare function setGLAutoAttach(config: GLAutoConfig | null): void;
|
|
35
|
+
export declare function pruneUnboundGLRecorders(): void;
|
|
36
|
+
export declare class GLRecorder {
|
|
37
|
+
private now;
|
|
38
|
+
cpuMs: number;
|
|
39
|
+
bound: boolean;
|
|
40
|
+
rawCtx: GL | null;
|
|
41
|
+
/** ids for GL objects, scoped to this recorder's stream */
|
|
42
|
+
private refs;
|
|
43
|
+
private nextId;
|
|
44
|
+
private sink;
|
|
45
|
+
private pendingEncodes;
|
|
46
|
+
private assetBySource;
|
|
47
|
+
private nextAssetId;
|
|
48
|
+
private assetCount;
|
|
49
|
+
constructor(sink: RecorderSink, now: () => number);
|
|
50
|
+
/** Await pending texture-asset encodes so they ship before the stream stops
|
|
51
|
+
* (mirrors ContextRecorder.settle). The manager awaits this at teardown. */
|
|
52
|
+
settle(): Promise<void>;
|
|
53
|
+
/** [audit B6] Register an in-flight encode + auto-remove on completion so the
|
|
54
|
+
* set never grows unbounded. (then/finally swallow avoids unhandled rejection.) */
|
|
55
|
+
private trackEncode;
|
|
56
|
+
/** Capture a static GL texture source as an imagedata asset (dedup'd by source
|
|
57
|
+
* identity, bounded). Returns the asset ref to serialize; an unreadable /
|
|
58
|
+
* tainted / over-cap source returns `unsupported` (uncaptured → blank replay,
|
|
59
|
+
* the prior behavior). */
|
|
60
|
+
private captureTexSource;
|
|
61
|
+
/** Bind the strategy's sink, draining anything buffered since getContext. */
|
|
62
|
+
swapSink(sink: RecorderSink): void;
|
|
63
|
+
/** draw-site stamp id for the next op (Phase 3b); see ContextRecorder.pendingSiteId */
|
|
64
|
+
pendingSiteId?: string;
|
|
65
|
+
recordCall(op: string, args: unknown[], result: unknown): void;
|
|
66
|
+
private arg;
|
|
67
|
+
}
|
|
68
|
+
export declare function installGLHooks(): void;
|
|
69
|
+
export declare function attachGLRecorder(canvas: HTMLCanvasElement, contextType: string, sink: RecorderSink, now: () => number): GLRecorder | null;
|
|
70
|
+
/** Mid-stream attach to a known GL context (created before hooks installed). */
|
|
71
|
+
export declare function attachGLRecorderToContext(ctx: GL, sink: RecorderSink, now: () => number): GLRecorder;
|
|
72
|
+
export declare function detachGLRecorder(canvas: HTMLCanvasElement, contextType: string): void;
|
|
73
|
+
export declare function detachGLRecorderFromContext(ctx: object): void;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGPU command capture via object-graph proxying — capture-side port of the
|
|
3
|
+
* validated bench core (test-applications/canvas-recorder-bench/src/capture/
|
|
4
|
+
* patchWebGPU.ts; webgpu_recorder lineage). navigator.gpu and every object
|
|
5
|
+
* reachable from it (adapter, device, queue, encoders, pipelines, textures,
|
|
6
|
+
* views...) are wrapped in recording proxies. Method calls/property hops are
|
|
7
|
+
* logged as meta-ops with interned result ids; descriptors serialize as JSON
|
|
8
|
+
* with {__sfRef} markers for object references; buffer payloads ride the
|
|
9
|
+
* typed-array tag.
|
|
10
|
+
*
|
|
11
|
+
* Replay (player-side, NOT ported here) re-executes the op list against a
|
|
12
|
+
* fresh adapter/device with the recorded canvas context bound to the replay
|
|
13
|
+
* target. Deterministic on the same GPU; pixel keyframes remain the bit-exact
|
|
14
|
+
* truth (hybrid).
|
|
15
|
+
*
|
|
16
|
+
* SDK adaptations: SSR-safe installation, DiscardSink short-circuit (video
|
|
17
|
+
* demotion keeps dirtiness with zero serialize cost), swapGPUSink for the
|
|
18
|
+
* manager, replayGPU dropped (player concern).
|
|
19
|
+
*/
|
|
20
|
+
import { type RecorderSink } from "./protocol";
|
|
21
|
+
interface GPUAutoConfig {
|
|
22
|
+
now: () => number;
|
|
23
|
+
}
|
|
24
|
+
export declare function setGPUDeviceLostNotifier(fn: ((lost: boolean) => void) | null): void;
|
|
25
|
+
export declare function setGPUAutoAttach(config: GPUAutoConfig | null): void;
|
|
26
|
+
export declare function pruneUnboundGPURecorders(): void;
|
|
27
|
+
/** The canvas (if any) that created a webgpu context while armed. */
|
|
28
|
+
export declare function getGPURecordedCanvas(): HTMLCanvasElement | null;
|
|
29
|
+
export declare function installGPUHooks(): void;
|
|
30
|
+
export interface GPURecorderHandle {
|
|
31
|
+
detach(): void;
|
|
32
|
+
cpuMs(): number;
|
|
33
|
+
}
|
|
34
|
+
export declare function attachGPURecorder(canvas: HTMLCanvasElement, strategySink: RecorderSink, now: () => number): GPURecorderHandle | null;
|
|
35
|
+
/** Swap the page-global GPU sink (manager demotion path). */
|
|
36
|
+
export declare function swapGPUSink(s: RecorderSink): void;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A13 — content-level PII detection + redaction shared between the rrweb input
|
|
3
|
+
* masker (`maskInputFn` in recording.tsx) and the canvas text path
|
|
4
|
+
* (`canvasPrivacy.ts`). Extracted so both call sites use ONE implementation of
|
|
5
|
+
* the credit-card / SSN / DOB detectors and their partial-mask formats.
|
|
6
|
+
*
|
|
7
|
+
* The "record*" flags are INVERTED privacy intent: `recordCreditCardInfo:false`
|
|
8
|
+
* means "do NOT record credit-card content" → redact it. A flag that is TRUE
|
|
9
|
+
* (or undefined → treated as record-everything for that type) leaves the
|
|
10
|
+
* matching content untouched. This mirrors the historical maskInputFn behavior,
|
|
11
|
+
* which always partial-masked detected CC/SSN content regardless of flags; here
|
|
12
|
+
* the canvas path gates on the flags so an account that explicitly opts INTO
|
|
13
|
+
* recording a PII type is honored.
|
|
14
|
+
*
|
|
15
|
+
* SSR rule: pure string functions, no window/document/navigator access. Safe to
|
|
16
|
+
* import at module scope anywhere.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Luhn checksum over the digits of a candidate credit-card string. Accepts the
|
|
20
|
+
* raw matched substring (may contain spaces/hyphens) OR a pre-stripped digit
|
|
21
|
+
* string. Returns false for anything without 13–16 digits so non-card numbers
|
|
22
|
+
* (phones, SKUs) that happen to match the candidate regex are rejected.
|
|
23
|
+
*/
|
|
24
|
+
export declare function passesLuhn(digits: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* True when `s` contains a 13–16 digit run that ALSO passes the Luhn check.
|
|
27
|
+
* The Luhn gate removes the vast majority of false positives (phone numbers,
|
|
28
|
+
* order ids) the bare digit-run regex would otherwise flag.
|
|
29
|
+
*/
|
|
30
|
+
export declare function looksLikeCreditCard(s: string): boolean;
|
|
31
|
+
/** True when `s` contains a `NNN-NN-NNNN` SSN-formatted run. */
|
|
32
|
+
export declare function looksLikeSsn(s: string): boolean;
|
|
33
|
+
/** True when `s` contains a `D/M/Y`-style date (any of `/ - .` separators). */
|
|
34
|
+
export declare function looksLikeDob(s: string): boolean;
|
|
35
|
+
/** "**** **** **** "+last4 — the historical maskInputFn credit-card format. */
|
|
36
|
+
export declare function redactCreditCard(s: string): string;
|
|
37
|
+
/** "***-**-"+last4 — the historical maskInputFn SSN format. */
|
|
38
|
+
export declare function redactSsn(s: string): string;
|
|
39
|
+
/** DOB partial mask: two masked groups + "/" + last 4 chars. */
|
|
40
|
+
export declare function redactDob(s: string): string;
|
|
41
|
+
export interface SensitiveContentFlags {
|
|
42
|
+
/** when FALSE, credit-card content is redacted */
|
|
43
|
+
recordCreditCardInfo?: boolean;
|
|
44
|
+
/** when FALSE, SSN content is redacted */
|
|
45
|
+
recordSsn?: boolean;
|
|
46
|
+
/** when FALSE, DOB content is redacted */
|
|
47
|
+
recordDob?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Apply the partial-mask redactions to every PII match in `text`, but ONLY for
|
|
51
|
+
* the flags that are explicitly FALSE (privacy intent: "do not record this PII
|
|
52
|
+
* type"). A flag that is `true` or `undefined` leaves that PII type intact.
|
|
53
|
+
* Returns the original string when nothing matched or every relevant flag is on.
|
|
54
|
+
*
|
|
55
|
+
* Each enabled redactor replaces ALL of its matches in the string (a line like
|
|
56
|
+
* "DOB 01/02/1990 card 4111 1111 1111 1111" redacts both). Order:
|
|
57
|
+
* credit-card → SSN → DOB so a CC's internal digits are masked before the DOB
|
|
58
|
+
* pass scans (the partial-mask output contains no date-shaped runs).
|
|
59
|
+
*/
|
|
60
|
+
export declare function redactSensitiveContent(text: string, flags: SensitiveContentFlags): string;
|