@push.rocks/smartbrowser 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/classes.livebrowsercanvasrenderer.d.ts +59 -5
- package/dist_ts_web/classes.livebrowsercanvasrenderer.js +443 -126
- package/dist_ts_web/interfaces.livebrowsercanvas.d.ts +20 -0
- package/package.json +3 -3
- package/readme.hints.md +13 -9
- package/readme.md +9 -5
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/classes.livebrowsercanvasrenderer.ts +525 -137
- package/ts_web/interfaces.livebrowsercanvas.ts +21 -0
|
@@ -39,3 +39,23 @@ export interface ILiveBrowserCanvasRendererOptions {
|
|
|
39
39
|
onError?: (errorArg: ILiveBrowserCanvasError) => void;
|
|
40
40
|
onFrameRendered?: (frameArg: ILiveBrowserFrame) => void;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Cumulative renderer counters. Counts accumulate across runs of one instance;
|
|
44
|
+
* inputCommandsInFlight is the live value at the time of the call.
|
|
45
|
+
*/
|
|
46
|
+
export interface ILiveBrowserCanvasRendererStatistics {
|
|
47
|
+
/** Frame events delivered to an active run, including ones rejected by validation. */
|
|
48
|
+
framesReceived: number;
|
|
49
|
+
/** Frames whose bitmap decoded with matching dimensions. */
|
|
50
|
+
framesDecoded: number;
|
|
51
|
+
/** Frames retired without being drawn: superseded, stale, or replaced before presentation. */
|
|
52
|
+
framesSkipped: number;
|
|
53
|
+
/** Input submissions accepted by the renderer, including ones later merged. */
|
|
54
|
+
inputCommandsEnqueued: number;
|
|
55
|
+
/** Submissions merged into another command or dropped under queue pressure. */
|
|
56
|
+
inputCommandsCoalesced: number;
|
|
57
|
+
/** Client input operations currently awaiting a result. */
|
|
58
|
+
inputCommandsInFlight: number;
|
|
59
|
+
/** Duration of the most recently completed input dispatch in milliseconds; 0 before the first. */
|
|
60
|
+
lastInputRoundTripMs: number;
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartbrowser",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "A simplified Puppeteer wrapper for easy automation and testing tasks.",
|
|
5
5
|
"main": "dist_ts/index.js",
|
|
6
6
|
"typings": "dist_ts/index.d.ts",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@push.rocks/smartdelay": "^3.1.0",
|
|
33
33
|
"@push.rocks/smartpdf": "^5.0.1",
|
|
34
|
-
"@push.rocks/smartpuppeteer": "^2.
|
|
34
|
+
"@push.rocks/smartpuppeteer": "^2.7.0",
|
|
35
35
|
"@push.rocks/smartunique": "^3.0.10"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@git.zone/tsbuild": "^4.4.2",
|
|
39
39
|
"@git.zone/tsrun": "^2.0.6",
|
|
40
40
|
"@git.zone/tstest": "^4.0.0",
|
|
41
|
-
"@types/node": "^26.
|
|
41
|
+
"@types/node": "^26.4.1"
|
|
42
42
|
},
|
|
43
43
|
"private": false,
|
|
44
44
|
"files": [
|
package/readme.hints.md
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
# SmartBrowser Hints
|
|
2
2
|
|
|
3
|
-
## Dependencies (as of 2026-08-
|
|
3
|
+
## Dependencies (as of 2026-08-24)
|
|
4
4
|
- `@push.rocks/smartpdf` v5.x uses SmartPuppeteer 2/Puppeteer 25 browser types. `SmartPdf` remains lazy and starts only for PDF methods.
|
|
5
|
-
- `@push.rocks/smartpuppeteer` v2.
|
|
5
|
+
- `@push.rocks/smartpuppeteer` v2.6 owns `LiveBrowserSession`, the canonical transport-neutral live browser contracts, bounded CDP frame acknowledgement flow, and authenticated-proxy service-worker coverage.
|
|
6
6
|
- Tests import `@git.zone/tstest/tapbundle`; browser tests use the `.chromium.ts` suffix.
|
|
7
|
-
- The Node.js entry requires Node.js 22.12 or newer. This repository uses pnpm 11.
|
|
7
|
+
- The Node.js entry requires Node.js 22.12 or newer. This repository uses pnpm 11.21 for development and release tooling.
|
|
8
8
|
- Puppeteer `page.screenshot({ encoding: 'binary' })` returns `Uint8Array`, not `Buffer` - wrap with `Buffer.from()`
|
|
9
9
|
|
|
10
10
|
## Build
|
|
11
11
|
- `pnpm run build` uses `tsbuild tsfolders` and emits both `dist_ts` and `dist_ts_web`.
|
|
12
12
|
- `@push.rocks/smartbrowser/web` must remain runtime-isolated from Puppeteer, SmartPDF, Buffer, and Node.js built-ins. Only type imports from SmartPuppeteer are allowed.
|
|
13
|
-
- `pnpm test` runs Node and Chromium files through tstest 4.
|
|
13
|
+
- `pnpm test` runs Node and Chromium files through tstest 4. The Chromium renderer tests claim a 2D context in `installCanvas()` so pixels can be read back; the renderer then keeps its 2D path. One test uses a fresh canvas to cover the `bitmaprenderer` path.
|
|
14
14
|
|
|
15
15
|
## Canvas Renderer Invariants
|
|
16
16
|
- The client facade is transport-neutral. Authentication, authorization, binary wire encoding, session ownership, and egress policy belong to the consuming adapter.
|
|
17
17
|
- Input always carries the identity of the actually displayed frame, never merely the newest state event.
|
|
18
|
+
- Input and frame rendering require the active tab state to report `streaming: true`; a stopped stream clears the displayed frame even when its identity fields have not advanced.
|
|
18
19
|
- Cached state may lag a new frame generation. A state identity ahead of the displayed frame invalidates it; same-identity metadata updates do not.
|
|
19
|
-
- One frame may decode while only the newest subsequent frame waits. Active-run frames must pass full static validation and then have a sequence strictly above the per-run high-water value. Duplicate and out-of-order frames suspend without acknowledgement; resume resets the high-water value.
|
|
20
|
-
-
|
|
20
|
+
- One frame may decode while only the newest subsequent frame waits; a superseded queued frame is never decoded. Active-run frames must pass full static validation and then have a sequence strictly above the per-run high-water value. Duplicate and out-of-order frames suspend without acknowledgement; resume resets the high-water value.
|
|
21
|
+
- Validated frames are acknowledged at receipt, before decoding. The newest decoded bitmap is presented once per animation frame (16 ms timer while hidden), the canvas backing store is reallocated only when frame dimensions change, and a fresh canvas gets an `ImageBitmapRenderingContext`; a canvas that already owns a 2D context keeps the 2D path. Decoded-dimension integrity is still checked before presentation.
|
|
22
|
+
- The renderer makes exactly one acknowledgement request for each valid frame while its run remains active. Invalid-protocol frames do not start acknowledgement work. An acknowledgement that throws, rejects, or times out suspends the run; `{ accepted: false }` settles the local attempt and does not prove upstream retirement.
|
|
21
23
|
- Every asynchronous client method receives `ILiveBrowserCanvasOperationOptions` with a renderer-owned `AbortSignal`. Operation deadline, run suspension, and stop all abort the signal; adapters must propagate it through the underlying transport.
|
|
22
24
|
- Renderer starts, suspensions, resumes, and stops are serialized by run epoch. Suspension aborts owned work and drops state, frames, queued input, pressed state, and old-generation recovery. Resume creates a fresh epoch and requires newly read state plus a newly displayed frame before input.
|
|
23
25
|
- Native `createImageBitmap()` jobs are tracked outside per-run cleanup. A new start or resume waits for every prior raw decode to settle, and late bitmaps are closed after timeout or interruption. Start or resume can remain pending indefinitely if the browser never settles native decode work.
|
|
24
|
-
- Acknowledgement failures, input-release failures, capacity exhaustion, and client/decode timeouts suspend only the current run. Malformed, oversized, or inconsistent frame protocol and deterministic decode-integrity failures are also terminal to that run. Recovery is explicit through `resume()`; none of these paths retries automatically.
|
|
25
|
-
- Internal renderer ceilings are 16 pending acknowledgements; 12,288 per frame dimension and 8,294,400 frame pixels; 34,226,176 encoded bytes; and a 4,096 x 4,096 viewport, device scale factor 3, and 8,294,400 physical pixels.
|
|
26
|
+
- Acknowledgement failures, input-release failures, acknowledgement-capacity exhaustion, and client/decode timeouts suspend only the current run. Discrete input-queue capacity exhaustion is a non-fatal `input_queue_capacity_exceeded` rejection of the new command; coalescable wheel and move commands never raise it. Malformed, oversized, or inconsistent frame protocol and deterministic decode-integrity failures are also terminal to that run. Recovery is explicit through `resume()`; none of these paths retries automatically.
|
|
27
|
+
- Internal renderer ceilings are 16 pending acknowledgements; 4 in-flight input commands, 128 queued discrete input commands, and 32 queued coalescable input commands; 12,288 per frame dimension and 8,294,400 frame pixels; 34,226,176 encoded bytes; and a 4,096 x 4,096 viewport, device scale factor 3, and 8,294,400 physical pixels.
|
|
26
28
|
- Repeated blur, visibility, and pointer-loss resets coalesce into one bounded reset operation.
|
|
29
|
+
- Input dispatch pipelines up to 4 commands in queue order; input idle means an empty queue and nothing in flight. Wheel deltas accumulate outside the queue and flush at most once per animation frame and only while no wheel dispatch is in flight; a discrete command forces the flush first so order is kept. Consecutive moves coalesce to the newest position. `getStatistics()` exposes cumulative frame and input counters.
|
|
27
30
|
- During an active run, pressed input is retained until release succeeds, with one retry for transient release failures. Input remains blocked while a release is unresolved. Suspension discards pressed state without dispatching releases into a later run.
|
|
28
|
-
-
|
|
31
|
+
- A rejected non-timeout input dispatch clears the ambiguous displayed frame and requires a newly rendered matching frame. A timed-out dispatch suspends the run and requires explicit resume plus a fresh frame.
|
|
32
|
+
- Resize uses a caller-supplied stable CSS element. `ResizeObserver` observations are debounced 100 ms trailing, `syncViewport()` is immediate. Requests are latest-only and serialized; input is released before resizing and remains blocked until a frame meeting the target revision fence is drawn.
|
|
29
33
|
- Intrinsic canvas dimensions come from encoded frame dimensions. Pointer coordinates map to the frame's logical CSS viewport.
|
|
30
34
|
- Pointer and wheel listeners remain on the canvas. Keyboard and composition listeners use `focusTarget`, which defaults to the canvas. Call `insertText()` for text committed by an external IME or dedicated input control.
|
package/readme.md
CHANGED
|
@@ -15,7 +15,7 @@ Install the package with pnpm:
|
|
|
15
15
|
pnpm add @push.rocks/smartbrowser
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
The Node.js entry requires Node.js 22.12 or newer and a Chromium-compatible browser. It uses SmartPuppeteer 2.
|
|
18
|
+
The Node.js entry requires Node.js 22.12 or newer and a Chromium-compatible browser. It uses SmartPuppeteer 2.6, Puppeteer 25, and SmartPDF 5. SmartPuppeteer detects common local, CI, and container environments and configures Chromium accordingly.
|
|
19
19
|
|
|
20
20
|
## Usage
|
|
21
21
|
|
|
@@ -153,19 +153,23 @@ Every asynchronous client method receives a required second `ILiveBrowserCanvasO
|
|
|
153
153
|
|
|
154
154
|
The renderer keeps at most one frame decoding and one newest frame queued. For an active run it validates the complete static frame protocol before requiring `frame.sequence` to be strictly greater than that run's high-water sequence. Duplicate or out-of-order frames are terminal `frame_render_failed` protocol errors and are not acknowledged. A resumed run creates a new subscription and resets the high-water sequence by design.
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
Every frame that passes validation is acknowledged immediately at receipt, before it is decoded, so upstream flow control no longer waits for local decode and presentation. Only the newest queued frame is decoded; a frame superseded while another frame decodes is skipped without decoding. The newest decoded bitmap is presented once per animation frame (a 16 ms timer stands in while the document is hidden), and the canvas backing store is reallocated only when the frame dimensions change. On a canvas without an existing rendering context the renderer claims an `ImageBitmapRenderingContext` and presents frames with `transferFromImageBitmap()`; a canvas that already owns a 2D context keeps the 2D drawing path, which is also what applications need when they read pixels back from the canvas.
|
|
157
|
+
|
|
158
|
+
The renderer makes exactly one acknowledgement attempt for each valid frame, at receipt, while its run remains active; a frame that is later dropped, found stale, superseded, or fails decoding is not acknowledged again. SmartPuppeteer may fulfill an acknowledgement with `{ accepted: false }` for stale, retired, duplicate, or identity-mismatched frames. That result settles the renderer's local attempt without suspending or retrying, and it does not prove upstream retirement. Only an acknowledgement operation that throws, rejects, or times out suspends the run; reaching acknowledgement capacity does the same. Input identity always comes from the frame actually displayed, so tab, generation, viewport, and active-stream changes block stale clicks and keystrokes. A rejected non-timeout input dispatch clears the ambiguous frame and requires a newly rendered matching frame; a timeout suspends the run and requires explicit resume plus a fresh frame. Encoded `frame.width` and `frame.height` set the canvas backing bitmap, while pointer coordinates map through the displayed canvas rectangle into the frame's logical CSS viewport.
|
|
157
159
|
|
|
158
160
|
Call `suspend()` when the adapter observes a transport interruption, then call `resume()` after the adapter has established a fresh transport. Suspension aborts active work, removes listeners, drops queued input and pressed-input recovery, and clears state and the displayed frame. `resume()` creates a new renderer run generation, re-subscribes to events, reads state again, and keeps input blocked until a new matching frame is displayed. Old input and release transitions are never replayed into the new generation. Operation timeouts request this same suspended lifecycle, so one transient acknowledgement timeout does not permanently poison the renderer instance.
|
|
159
161
|
|
|
162
|
+
Input commands are dispatched in queue order with up to four commands in flight at once; the remote session processes operations sequentially, so ordering is preserved while round trips overlap. Wheel events accumulate their `deltaX` and `deltaY` together with the latest coordinates and modifiers, and the merged input is flushed as one `dispatchWheel()` call at most once per animation frame and only after the previous wheel dispatch has settled. A discrete command (mouse down or up, key, or text) flushes accumulated wheel input first so the relative order of events is kept. Consecutive pointer moves coalesce to the newest position. Coalescable commands (wheel and move) never exhaust the input queue: under pressure they merge, or the oldest queued coalescable command is dropped and a dropped wheel's deltas fold into the next queued wheel. Only discrete commands count toward the 128-command queue capacity. When that capacity is reached the renderer reports `input_queue_capacity_exceeded`, rejects only the new command, and keeps running without blocking input or resetting pressed state.
|
|
163
|
+
|
|
160
164
|
Malformed frame identities, metadata, viewport values, formats, MIME pairings, dimensions, pixel areas, byte lengths, and deterministic image decode or dimension-integrity failures are terminal to the current run. Decoded dimensions are checked before supersession and currentness, so a mismatched decoded bitmap remains terminal even when a newer frame arrived during decoding. These failures are reported as `frame_render_failed` and require an explicit `resume()`; the renderer does not retry them automatically. The renderer only schedules rendering, acknowledgements, viewport synchronization, and direct input. Navigation scheduling remains the responsibility of the server runtime and application adapter.
|
|
161
165
|
|
|
162
|
-
The renderer's fixed internal protocol ceilings are 16 pending frame acknowledgement operations; frame dimensions of at most 12,288 on either axis and 8,294,400 pixels; encoded frame data of at most 34,226,176 bytes; and viewports of at most 4,096 x 4,096 CSS pixels, device scale factor 3, and 8,294,400 physical pixels (`ceil(width * deviceScaleFactor) * ceil(height * deviceScaleFactor)`).
|
|
166
|
+
The renderer's fixed internal protocol ceilings are 16 pending frame acknowledgement operations; 4 in-flight input commands, 128 queued discrete input commands, and 32 queued coalescable input commands; frame dimensions of at most 12,288 on either axis and 8,294,400 pixels; encoded frame data of at most 34,226,176 bytes; and viewports of at most 4,096 x 4,096 CSS pixels, device scale factor 3, and 8,294,400 physical pixels (`ceil(width * deviceScaleFactor) * ceil(height * deviceScaleFactor)`).
|
|
163
167
|
|
|
164
168
|
Browser-native `createImageBitmap()` work is not abortable. After a wrapper timeout or run interruption, the renderer closes a bitmap that resolves late and waits for all prior raw decode jobs to settle before `start()` or `resume()` starts another run. Consequently, `start()` or `resume()` can remain pending indefinitely if the browser platform never settles a prior `createImageBitmap()` call.
|
|
165
169
|
|
|
166
170
|
Pointer and wheel input are captured from the canvas. Keyboard and best-effort `compositionend` input are captured from `focusTarget`, which defaults to the canvas and receives focus on pointer down. The renderer temporarily makes an unfocusable focus target focusable and restores its prior `tabindex` on stop. Applications with a dedicated text or IME control can call `renderer.insertText(text)` explicitly.
|
|
167
171
|
|
|
168
|
-
When `resizeTarget` is supplied, resize updates are deduplicated, serialized, and fenced by viewport revision. Positive fractional dimensions are rounded to at least one CSS pixel, oversized dimensions are reduced proportionally to the 4,096 x 4,096 ceiling, and the device scale factor is clamped to 0.25 through 3 before being reduced further when necessary to stay within the physical-pixel ceiling. The target must have stable CSS dimensions that do not depend on `canvas.width` or `canvas.height`; this prevents intrinsic canvas updates from causing resize feedback.
|
|
172
|
+
When `resizeTarget` is supplied, resize updates are deduplicated, serialized, and fenced by viewport revision. Sizes observed through `ResizeObserver` are debounced for about 100 ms (trailing), so a drag resize produces one `setViewport()` call for the final size; `syncViewport()` and window resize events measure immediately. Positive fractional dimensions are rounded to at least one CSS pixel, oversized dimensions are reduced proportionally to the 4,096 x 4,096 ceiling, and the device scale factor is clamped to 0.25 through 3 before being reduced further when necessary to stay within the physical-pixel ceiling. The target must have stable CSS dimensions that do not depend on `canvas.width` or `canvas.height`; this prevents intrinsic canvas updates from causing resize feedback.
|
|
169
173
|
|
|
170
174
|
`ILiveBrowserCanvasRendererOptions` supports:
|
|
171
175
|
|
|
@@ -178,7 +182,7 @@ When `resizeTarget` is supplied, resize updates are deduplicated, serialized, an
|
|
|
178
182
|
- `onError`: receives typed `ILiveBrowserCanvasError` values without interrupting renderer cleanup.
|
|
179
183
|
- `onFrameRendered`: called after a current frame has been drawn.
|
|
180
184
|
|
|
181
|
-
The renderer exposes `start()`, `suspend()`, `resume()`, `stop()`, `insertText()`, `syncViewport()`, and the `isRunning` and `isSuspended` getters. `start()` begins a stopped renderer, while `resume()` is required for a suspended renderer. `syncViewport()` requests a fresh measurement of the configured `resizeTarget`; it is a no-op when no target is configured or the renderer is not running. An explicitly stopped renderer can be started again. The `/web` entry also exports `ILiveBrowserCanvasClient`, `ILiveBrowserCanvasOperationOptions`, `ILiveBrowserCanvasRendererOptions`, `ILiveBrowserCanvasError`, `TLiveBrowserCanvasErrorCode`, and the canonical SmartPuppeteer live-browser contract types.
|
|
185
|
+
The renderer exposes `start()`, `suspend()`, `resume()`, `stop()`, `insertText()`, `syncViewport()`, `getStatistics()`, and the `isRunning` and `isSuspended` getters. `getStatistics()` returns an `ILiveBrowserCanvasRendererStatistics` snapshot with cumulative `framesReceived`, `framesDecoded`, `framesSkipped`, `inputCommandsEnqueued` (every accepted input submission, including ones later merged), `inputCommandsCoalesced` (submissions merged into another command or dropped under pressure), the live `inputCommandsInFlight`, and `lastInputRoundTripMs` for the most recently completed input dispatch. `start()` begins a stopped renderer, while `resume()` is required for a suspended renderer. `syncViewport()` requests a fresh measurement of the configured `resizeTarget`; it is a no-op when no target is configured or the renderer is not running. An explicitly stopped renderer can be started again. The `/web` entry also exports `ILiveBrowserCanvasClient`, `ILiveBrowserCanvasOperationOptions`, `ILiveBrowserCanvasRendererOptions`, `ILiveBrowserCanvasRendererStatistics`, `ILiveBrowserCanvasError`, `TLiveBrowserCanvasErrorCode`, and the canonical SmartPuppeteer live-browser contract types.
|
|
182
186
|
|
|
183
187
|
`LiveBrowserCanvasRenderer` is not a security boundary. The application adapter must authenticate viewers, authorize control, restrict navigation, enforce browser-session ownership, and apply network/egress policy before forwarding commands. The renderer displays webpage viewport pixels only. It does not provide native Chrome UI, audio, extensions, file transfer, clipboard, camera, microphone, or touch emulation.
|
|
184
188
|
|
package/ts/00_commitinfo_data.ts
CHANGED