YmerNode.Scripts.BrowserService (Ymer Node v0.5.0)

Copy Markdown View Source

The node's half of the browser service: the Node program in browser-service/ that a user starts on their own machine, outside the node, which runs Playwright code a script sends it — each browser call in a fresh browser context — and keeps the storage states.

The browser, Playwright and Node.js stay out of the node, its image and its release: a browser is hundreds of megabytes that most nodes never use, and Playwright's own release cadence would become the node's. So the node owns four things and no more — where the service is, the token it sends there, how long a call may wait, and how a failure reads — and a script reaches the service only through YmerNode.Script.Context.playwright/3, never by URL.

Where the service is

url/0. A release reads BROWSER_SERVICE_URL; without it, a node inside its own image — where the bind marker is — reaches the host's loopback-bound service at http://host.docker.internal:8013, and a node on the host at http://127.0.0.1:8013. That default reaches on macOS Docker and on a host node; on Linux Docker it does not, and the variable is the way there. A VM that sets nothing — a script's own repository testing it under runtime: false — answers the host default, and its calls meet the test's stub rather than the network.

The token

Every request carries the service's token as Authorization: Bearer, and the service refuses one without it: a browser call's code runs in the service's own process with the privileges of whoever started it, and a loopback bind does not keep other programs out — every container on a Docker host reaches the host through host.docker.internal, as the node's image does. The token is the contents of a file only its owner can read, ~/.ymer-node/browser-service-token unless BROWSER_SERVICE_TOKEN_FILE moves it, for the service and any node on the host alike, and whichever of them needs it first writes it: the file 0600, and any directory it makes 0700. A node inside its own image cannot read a file on the host, so a release there reads no file and sends the value of BROWSER_SERVICE_TOKEN, which wins wherever it is set. A node that cannot read the file sends no token, and neither does one that finds the file readable by others — that one logs a warning naming the chmod — and the service's refusal says where the token is. A request a plug answers — a test's stub — reaches no service, so the node never reads or writes the default file for one; a token or a file the configuration names is still sent.

How long a call may wait

What remains of the run's deadline, and nothing the script picks: the service is told that remainder less a margin, which leaves it time to stop the code, take the failure screenshot and answer before the node stops listening at the deadline itself. Connecting takes at most a second of the remainder, so a URL where nothing refuses and nothing answers reads as :timeout rather than :unreachable. A run with no time left answers :timeout and sends nothing. A longer browser flow raises its action's timeout key, up to the run's cap.

The wire

Every request goes through YmerNode.Script.Context.request_options/0 — the node's Req policy, and in tests the one plug every script call is stubbed at — with the receive bound set per call. POST /call carries the code, its arguments, the storage state and whether to save it, the options handed to Playwright's browser.newContext(), whether to take the failure screenshot, and bound_ms; the service answers {"ok": true, …} or {"ok": false, "error": {…}}. Anything else is :protocol: when it carries the service's own {"error": text} — a request refused before any code ran — the text is the message, and otherwise the answer is taken for another program's. GET /status is what status/0 reports.

Summary

Functions

One browser call, bounded by deadline — the run's, as the System.monotonic_time(:millisecond) instant it falls at. YmerNode.Script.Context.playwright/3 is the door a script uses, and its doc is where the options and the answer are described. save_storage: true without a storage: name raises ArgumentError: there is no name to save under, and no call is sent.

What scripts browser reports: the URL, whether anything answered there, and — when the browser service did — its Playwright version, the Chromium its headless browser runs (or why none launched), the storage state names and the names an interactive window holds. When nothing answered, something that is not the service did, or the service refused the request — one without its token, say — message says what to do. Facts the node measured at the call, never a verdict.

The browser service's URL (config :ymer_node, YmerNode.Scripts.BrowserService, :url), http://127.0.0.1:8013 when nothing sets it — § Where the service is. A trailing slash is dropped, since the service answers only its own paths.

Functions

call(deadline, code, options)

One browser call, bounded by deadline — the run's, as the System.monotonic_time(:millisecond) instant it falls at. YmerNode.Script.Context.playwright/3 is the door a script uses, and its doc is where the options and the answer are described. save_storage: true without a storage: name raises ArgumentError: there is no name to save under, and no call is sent.

status()

What scripts browser reports: the URL, whether anything answered there, and — when the browser service did — its Playwright version, the Chromium its headless browser runs (or why none launched), the storage state names and the names an interactive window holds. When nothing answered, something that is not the service did, or the service refused the request — one without its token, say — message says what to do. Facts the node measured at the call, never a verdict.

url()

The browser service's URL (config :ymer_node, YmerNode.Scripts.BrowserService, :url), http://127.0.0.1:8013 when nothing sets it — § Where the service is. A trailing slash is dropped, since the service answers only its own paths.