@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.
Files changed (144) hide show
  1. package/README.md +8 -0
  2. package/dist/canvas/adapters.js +382 -0
  3. package/dist/canvas/adapters.js.br +0 -0
  4. package/dist/canvas/adapters.js.gz +0 -0
  5. package/dist/canvas/canvasManager.js +1949 -0
  6. package/dist/canvas/canvasManager.js.br +0 -0
  7. package/dist/canvas/canvasManager.js.gz +0 -0
  8. package/dist/canvas/canvasPrivacy.js +233 -0
  9. package/dist/canvas/canvasPrivacy.js.br +0 -0
  10. package/dist/canvas/canvasPrivacy.js.gz +0 -0
  11. package/dist/canvas/codec.js +314 -0
  12. package/dist/canvas/codec.js.br +0 -0
  13. package/dist/canvas/codec.js.gz +0 -0
  14. package/dist/canvas/deviceClass.js +67 -0
  15. package/dist/canvas/deviceClass.js.br +0 -0
  16. package/dist/canvas/deviceClass.js.gz +0 -0
  17. package/dist/canvas/encodeWorker.js +284 -0
  18. package/dist/canvas/encodeWorker.js.br +0 -0
  19. package/dist/canvas/encodeWorker.js.gz +0 -0
  20. package/dist/canvas/governor.js +79 -0
  21. package/dist/canvas/governor.js.br +0 -0
  22. package/dist/canvas/governor.js.gz +0 -0
  23. package/dist/canvas/keyframes.js +183 -0
  24. package/dist/canvas/keyframes.js.br +0 -0
  25. package/dist/canvas/keyframes.js.gz +0 -0
  26. package/dist/canvas/mediaRecorderCapture.js +257 -0
  27. package/dist/canvas/mediaRecorderCapture.js.br +0 -0
  28. package/dist/canvas/mediaRecorderCapture.js.gz +0 -0
  29. package/dist/canvas/objectLog.js +150 -0
  30. package/dist/canvas/objectLog.js.br +0 -0
  31. package/dist/canvas/objectLog.js.gz +0 -0
  32. package/dist/canvas/patch2d.js +1302 -0
  33. package/dist/canvas/patch2d.js.br +0 -0
  34. package/dist/canvas/patch2d.js.gz +0 -0
  35. package/dist/canvas/patchGL.js +371 -0
  36. package/dist/canvas/patchGL.js.br +0 -0
  37. package/dist/canvas/patchGL.js.gz +0 -0
  38. package/dist/canvas/patchWebGPU.js +361 -0
  39. package/dist/canvas/patchWebGPU.js.br +0 -0
  40. package/dist/canvas/patchWebGPU.js.gz +0 -0
  41. package/dist/canvas/piiDetect.js +155 -0
  42. package/dist/canvas/piiDetect.js.br +0 -0
  43. package/dist/canvas/piiDetect.js.gz +0 -0
  44. package/dist/canvas/protocol.js +122 -0
  45. package/dist/canvas/protocol.js.br +0 -0
  46. package/dist/canvas/protocol.js.gz +0 -0
  47. package/dist/canvas/videoCapture.js +252 -0
  48. package/dist/canvas/videoCapture.js.br +0 -0
  49. package/dist/canvas/videoCapture.js.gz +0 -0
  50. package/dist/canvas/workerRelay.js +99 -0
  51. package/dist/canvas/workerRelay.js.br +0 -0
  52. package/dist/canvas/workerRelay.js.gz +0 -0
  53. package/dist/canvas/workerShim.js +227 -0
  54. package/dist/canvas/workerShim.js.br +0 -0
  55. package/dist/canvas/workerShim.js.gz +0 -0
  56. package/dist/canvas-worker.cjs +74 -0
  57. package/dist/canvas-worker.cjs.br +0 -0
  58. package/dist/canvas-worker.cjs.gz +0 -0
  59. package/dist/canvas-worker.js +78 -0
  60. package/dist/canvas-worker.js.br +0 -0
  61. package/dist/canvas-worker.js.gz +0 -0
  62. package/dist/canvasFrameStore.js +198 -0
  63. package/dist/canvasFrameStore.js.br +0 -0
  64. package/dist/canvasFrameStore.js.gz +0 -0
  65. package/dist/chunks/canvasManager-BYn3_oyo.js +1440 -0
  66. package/dist/chunks/canvasManager-BYn3_oyo.js.br +0 -0
  67. package/dist/chunks/canvasManager-BYn3_oyo.js.gz +0 -0
  68. package/dist/chunks/canvasManager-CoX5072G.js +1456 -0
  69. package/dist/chunks/canvasManager-CoX5072G.js.br +0 -0
  70. package/dist/chunks/canvasManager-CoX5072G.js.gz +0 -0
  71. package/dist/chunks/{chunkSerializer-FQtY90Av.js → chunkSerializer-C_K0Wyk0.js} +1 -1
  72. package/dist/chunks/chunkSerializer-C_K0Wyk0.js.br +0 -0
  73. package/dist/chunks/chunkSerializer-C_K0Wyk0.js.gz +0 -0
  74. package/dist/chunks/{chunkSerializer-DDukZpgl.js → chunkSerializer-CghPitF4.js} +1 -1
  75. package/dist/chunks/chunkSerializer-CghPitF4.js.br +0 -0
  76. package/dist/chunks/chunkSerializer-CghPitF4.js.gz +0 -0
  77. package/dist/chunks/{index-C-qbsfKe.js → index-BIj4l3CV.js} +1028 -650
  78. package/dist/chunks/index-BIj4l3CV.js.br +0 -0
  79. package/dist/chunks/index-BIj4l3CV.js.gz +0 -0
  80. package/dist/chunks/{index-D6axlCRu.js → index-DIYomvdD.js} +1119 -720
  81. package/dist/chunks/index-DIYomvdD.js.br +0 -0
  82. package/dist/chunks/index-DIYomvdD.js.gz +0 -0
  83. package/dist/chunks/patch2d-9voowhJ_.js +816 -0
  84. package/dist/chunks/patch2d-9voowhJ_.js.br +0 -0
  85. package/dist/chunks/patch2d-9voowhJ_.js.gz +0 -0
  86. package/dist/chunks/patch2d-PQxcaE6r.js +886 -0
  87. package/dist/chunks/patch2d-PQxcaE6r.js.br +0 -0
  88. package/dist/chunks/patch2d-PQxcaE6r.js.gz +0 -0
  89. package/dist/graphql.js +12 -1
  90. package/dist/graphql.js.br +0 -0
  91. package/dist/graphql.js.gz +0 -0
  92. package/dist/inAppReportIssueModal/index.js +48 -0
  93. package/dist/inAppReportIssueModal/index.js.br +0 -0
  94. package/dist/inAppReportIssueModal/index.js.gz +0 -0
  95. package/dist/index.js +144 -2
  96. package/dist/index.js.br +0 -0
  97. package/dist/index.js.gz +0 -0
  98. package/dist/recorder.cjs +2 -2
  99. package/dist/recorder.cjs.br +0 -0
  100. package/dist/recorder.cjs.gz +0 -0
  101. package/dist/recorder.js +52 -41
  102. package/dist/recorder.js.br +0 -0
  103. package/dist/recorder.js.gz +0 -0
  104. package/dist/recorder.umd.cjs +4847 -2212
  105. package/dist/recorder.umd.cjs.br +0 -0
  106. package/dist/recorder.umd.cjs.gz +0 -0
  107. package/dist/recording.js +119 -10
  108. package/dist/recording.js.br +0 -0
  109. package/dist/recording.js.gz +0 -0
  110. package/dist/types/canvas/adapters.d.ts +71 -0
  111. package/dist/types/canvas/canvasManager.d.ts +273 -0
  112. package/dist/types/canvas/canvasPrivacy.d.ts +184 -0
  113. package/dist/types/canvas/codec.d.ts +27 -0
  114. package/dist/types/canvas/deviceClass.d.ts +40 -0
  115. package/dist/types/canvas/encodeWorker.d.ts +54 -0
  116. package/dist/types/canvas/governor.d.ts +44 -0
  117. package/dist/types/canvas/keyframes.d.ts +81 -0
  118. package/dist/types/canvas/mediaRecorderCapture.d.ts +91 -0
  119. package/dist/types/canvas/objectLog.d.ts +75 -0
  120. package/dist/types/canvas/patch2d.d.ts +339 -0
  121. package/dist/types/canvas/patchGL.d.ts +74 -0
  122. package/dist/types/canvas/patchWebGPU.d.ts +37 -0
  123. package/dist/types/canvas/piiDetect.d.ts +60 -0
  124. package/dist/types/canvas/protocol.d.ts +176 -0
  125. package/dist/types/canvas/videoCapture.d.ts +67 -0
  126. package/dist/types/canvas/workerRelay.d.ts +29 -0
  127. package/dist/types/canvas/workerShim.d.ts +10 -0
  128. package/dist/types/canvasFrameStore.d.ts +23 -0
  129. package/dist/types/graphql.d.ts +1 -1
  130. package/dist/types/index.d.ts +99 -1
  131. package/dist/types/types.d.ts +40 -0
  132. package/dist/types/websocket.d.ts +19 -0
  133. package/dist/websocket.js +210 -0
  134. package/dist/websocket.js.br +0 -0
  135. package/dist/websocket.js.gz +0 -0
  136. package/package.json +7 -1
  137. package/dist/chunks/chunkSerializer-DDukZpgl.js.br +0 -0
  138. package/dist/chunks/chunkSerializer-DDukZpgl.js.gz +0 -0
  139. package/dist/chunks/chunkSerializer-FQtY90Av.js.br +0 -0
  140. package/dist/chunks/chunkSerializer-FQtY90Av.js.gz +0 -0
  141. package/dist/chunks/index-C-qbsfKe.js.br +0 -0
  142. package/dist/chunks/index-C-qbsfKe.js.gz +0 -0
  143. package/dist/chunks/index-D6axlCRu.js.br +0 -0
  144. package/dist/chunks/index-D6axlCRu.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;