@sysce/stomp 2.0.0-beta → 4.2.0-nightly.202611100525.gd963224
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 +222 -19
- package/dist/Controller.js +2 -0
- package/dist/Controller.js.map +7 -0
- package/dist/SearchBuilder.js +2 -0
- package/dist/SearchBuilder.js.map +7 -0
- package/dist/controller.mjs +2 -0
- package/dist/controller.mjs.map +7 -0
- package/dist/controllerFrame.html +35 -0
- package/dist/controllerFrame.js +2 -0
- package/dist/controllerFrame.js.map +7 -0
- package/dist/injectDocument.js +99 -0
- package/dist/injectDocument.js.map +7 -0
- package/dist/injectWorker.js +85 -0
- package/dist/injectWorker.js.map +7 -0
- package/dist/rewriteWorker.js +53 -0
- package/dist/rewriteWorker.js.map +7 -0
- package/dist/serviceWorker.js +125 -0
- package/dist/serviceWorker.js.map +7 -0
- package/dist/transportWorker.js +2 -0
- package/dist/transportWorker.js.map +7 -0
- package/dist/types/Codecs.d.ts +29 -0
- package/dist/types/StompURL.d.ts +12 -0
- package/dist/types/acceptLanguage.d.ts +1 -0
- package/dist/types/bootstrapper/SearchBuilder.d.ts +5 -0
- package/dist/types/config.d.ts +109 -0
- package/dist/types/controller/history.d.ts +26 -0
- package/dist/types/controller/id.d.ts +4 -0
- package/dist/types/controller/index.d.ts +347 -0
- package/dist/types/controller/messages.d.ts +111 -0
- package/dist/types/controller/remote.d.ts +145 -0
- package/dist/types/controller/rewriteWorkers.d.ts +19 -0
- package/dist/types/helperNames.d.ts +53 -0
- package/dist/types/hooks.d.ts +81 -0
- package/dist/types/transport/RemoteTransport.d.ts +21 -0
- package/dist/types/transport/TransportWorker.d.ts +26 -0
- package/dist/types/transport/bodyPort.d.ts +11 -0
- package/dist/types/transport/construct.d.ts +13 -0
- package/dist/types/transport/http3/Http3Transport.d.ts +66 -0
- package/dist/types/transport/http3/altSvc.d.ts +51 -0
- package/dist/types/transport/http3/dnsHttps.d.ts +17 -0
- package/dist/types/transport/messages.d.ts +378 -0
- package/dist/types/transport/rpc.d.ts +25 -0
- package/dist/types/transport/serveTransport.d.ts +13 -0
- package/dist/types/version.d.ts +1 -0
- package/package.json +71 -46
- package/dist/bootstrapper.js +0 -2
- package/dist/bootstrapper.js.map +0 -1
- package/dist/client.js +0 -3
- package/dist/client.js.LICENSE.txt +0 -14
- package/dist/client.js.map +0 -1
- package/dist/worker.js +0 -3
- package/dist/worker.js.LICENSE.txt +0 -14
- package/dist/worker.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,38 +1,241 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sysce/stomp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A rewrite of the Stomp proxy.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- [Frontend](https://github.com/sysce/tomp-demo)
|
|
5
|
+
Original implementation of https://github.com/tomphttp/specifications.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Stomp rewrites HTML, CSS and JavaScript inside a service worker and tunnels the
|
|
8
|
+
actual HTTP and WebSocket traffic through a
|
|
9
|
+
[proxy-transports](https://github.com/MercuryWorkshop/proxy-transports)
|
|
10
|
+
transport (for example [epoxy](https://github.com/MercuryWorkshop/EpoxyTransport)
|
|
11
|
+
or [libcurl](https://github.com/MercuryWorkshop/CurlTransport)) that lives in
|
|
12
|
+
the page that owns the proxy. Proxied content is shown in frames.
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
## Usage
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
Serve `dist/` (its path is exported from `@sysce/stomp/path` as `stompPath`)
|
|
17
|
+
from a directory of your site, register the service worker from that directory
|
|
18
|
+
(`register()` uses it as the scope and resolves once the worker is activated),
|
|
19
|
+
and hand the registration to a controller together with a transport:
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
```js
|
|
22
|
+
import EpoxyTransport from '@mercuryworkshop/epoxy-transport';
|
|
23
|
+
import { StompController } from '@sysce/stomp';
|
|
15
24
|
|
|
16
|
-
|
|
25
|
+
const registration = await StompController.register('/stomp/');
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
const controller = new StompController({
|
|
28
|
+
serviceworker: registration,
|
|
29
|
+
transport: new EpoxyTransport({ wisp: 'wss://example.com/wisp/' }),
|
|
30
|
+
directory: '/stomp/',
|
|
31
|
+
codec: 'xor',
|
|
32
|
+
});
|
|
19
33
|
|
|
20
|
-
|
|
34
|
+
await controller.wait();
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
> Cloning into stomp...
|
|
25
|
-
$ cd stomp
|
|
36
|
+
const frame = controller.createFrame(document.querySelector('iframe'));
|
|
37
|
+
await frame.go('https://example.com');
|
|
26
38
|
```
|
|
27
39
|
|
|
28
|
-
|
|
40
|
+
`dist/Controller.js` and `dist/SearchBuilder.js` expose the same API as classic
|
|
41
|
+
scripts through the `StompController` and `SearchBuilder` globals.
|
|
42
|
+
|
|
43
|
+
Any `ProxyTransport` works. In testing against a `wisp-js` server, epoxy was
|
|
44
|
+
the better choice for HTTP-heavy sites and libcurl for WebSockets; see the
|
|
45
|
+
changelog's known limitations before picking one.
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
`StompController` (also `window.StompController` from `dist/Controller.js`):
|
|
50
|
+
|
|
51
|
+
| Member | Description |
|
|
52
|
+
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| `StompController.register(directory?, { assets, updateViaCache })` | static: registers the service worker served from `directory` (the constructor's default when omitted; `assets.serviceWorker` names the file) with that scope and `updateViaCache: 'none'` (overridable), resolves with the registration once its worker is activated, 30 s at the most; a worker stuck installing never hangs the host |
|
|
54
|
+
| `new StompController(init)` | `init.serviceworker` (a `ServiceWorker`, or a `ServiceWorkerRegistration` whose active, waiting or installing worker is taken and the handshake made once it is activated), `init.transport` (an instance or a descriptor, next row), optional `codec`, `directory` |
|
|
55
|
+
| `init.transport` | a `ProxyTransport` instance (served from this page) or a `TransportDescriptor` `{ module, options, host? }`: the controller imports and constructs it in a dedicated Worker (`transportWorker.js` from `directory`), off the page's thread, so a proxied frame's synchronous XHR that needs the network completes; `host: 'page'` imports it in the page instead. The module is an ES module importable in a worker whose default export is a ProxyTransport class (epoxy and libcurl qualify) |
|
|
56
|
+
| `init.transport.http3` | optional, off by default: HTTP/3 over QUIC for origins that offer it (`Alt-Svc`, an HTTPS DNS record with `dns`, or `force`), carried by the transport's wisp UDP streams; `true` or `{ force, dns, timeout, wait, module, pem }`. Needs epoxy (`client.connect_udp`) and a wisp server that relays UDP; until a handshake has worked, requests go over the transport as before, and a failure falls back with no wait. Works in `RemoteController` descriptors too. See docs/http3.md |
|
|
57
|
+
| `transportHost` | `worker` or `page`: where the transport runs; `page` for an instance, `host: 'page'`, or after the worker fell back |
|
|
58
|
+
| `transporthost` event | `detail: { host: 'page', error }`: the worker could not start or could not import the module, so the page imported it instead (when that fails too, requests fail at once until `setTransport()`) |
|
|
59
|
+
| `init.allowInvalidJs` | a script the rewriter cannot parse runs as it was (default); `false` makes it fail with a `SyntaxError` naming the rewriter error, so a broken rewrite is loud |
|
|
60
|
+
| `init.membrane` | the window membrane, on unless `false`: every window and every object of another window's realm reaches a page through a proxy that checks on each use which site the window shows now, and acts as the browser's cross-origin window when it is another proxied site. `false` turns it off for a site it breaks. The setting reaches every document; the membrane itself is being built, and until it lands both values behave alike |
|
|
61
|
+
| `init.rewriteWorkers` | how many dedicated Workers (`rewriteWorker.js` from `directory`) the controller starts to rewrite scripts for the service worker, whose own thread rewrites one at a time; default half the cores, one to four, `0` for none. A worker is used once it says it is ready; whenever none is (none started, one failed to load or crashed, this page closed) the service worker rewrites the script itself, the same output |
|
|
62
|
+
| `init.rewriteTimeout` | milliseconds the service worker waits for a rewrite worker before it rewrites the script itself and stops using that worker; default 10000 |
|
|
63
|
+
| `init.assets` | optional names of the served files (`serviceWorker`, `injectDocument`, `injectWorker`, `controllerFrame`, `controllerFrameScript`, `transportWorker`, `rewriteWorker`), default the dist names; a host that vendors the bundle under neutral names tells stomp what it called them, and no requested URL then carries the package name |
|
|
64
|
+
| `init.names` | optional global names the rewriter and the inject bundles use, a deployment's own instead of `$s$j` (access API), `$s$L` and `$s$N` (stand-in markers) and the client's, which the controller mints (16 random characters) when unset: `{ access, client, globalProxy, globalName, error }` (`error` is the global the error page reads, default `stompError`; a host whose obfuscator renames it sets this), each an identifier no plain window has; a bad one is a `TypeError` from the constructor. Leave `client` unset: it names the object that holds the real windows, and a fixed name is one every page knows. `DEFAULT_NAMES` exports the defaults |
|
|
65
|
+
| `init.hooks` | optional host code run inside every proxied realm (the page, nested frames, about:blank children, popups, srcdoc frames; workers with `hooks.workers`): `hooks.init.pre` before the inject installs its hooks, `hooks.init.post` after, before the page's first script. Each is `{ source }` or `{ url }` (fetched once at construction; its text is what travels, so a changed file needs a new controller). Raw host code with the realm's real globals, one argument `{ url, origin, isTop, nested, kind, phase, names, frameId, window }`; what it leaves on the window the page can reach. A throw is logged in the realm and reported, never thrown at the page. See `examples/hooks/hide-ads.js` |
|
|
66
|
+
| `build` | the build this controller bundle came from (version plus commit, or the package version), the same string the module exports as `BUILD`; the worker's is `config.build` and `stale` reports a mismatch |
|
|
67
|
+
| `unroute(url)` | the real URL a routed one stands for (the inverse of `route()`, this controller's codec), null for anything that is not a route |
|
|
68
|
+
| `onHookError(info, frame)`, `hookerror` event, `hookErrors` | a hook threw in a realm (`phase` `pre` or `post`, `kind`, the page `url`, `message`, `stack`) or a hook URL could not be fetched (`phase: 'url'`, the hook `url`); the controller serves without that hook |
|
|
69
|
+
| `init.id` | optional, this controller's id, 1 to 32 characters of `[A-Za-z0-9_-]` (a TypeError otherwise), 8 random ones by default. It names the controller's address under `directory` (`/stomp/<id>/`), so two host tabs on one directory run two controllers side by side; the same id passed again replaces the earlier controller. A host that keeps tabs stores one id per tab (sessionStorage) and passes it back, so the tab's history entries, reloads and copied links keep resolving. A routed URL whose controller is gone is redirected to the newest live controller of the same codec; with none, the error page answers with kind `controller` |
|
|
70
|
+
| `id`, `directory` | `id` is the id in use (given or minted). `directory` is the service worker's scope and the directory `dist/` is served from (`/stomp/`); routed URLs are `${directory}${id}/${kind}/${encoded}`, and the inject bundles and the transport worker are fetched from the directory itself (`config.directory` is the address, `config.scope` the directory). The cookie jar, the HTTP cache and the storage partitions are shared by every controller of a directory: a controller is a tab, not a browser |
|
|
71
|
+
| `init.rules` | optional `RequestRule[]`: declarative request rules applied in the service worker to the real URL, first match wins: `match` (URLPattern `url`, `method`, `destination`, `site`), `action` `'block'` or `{ requestHeaders, responseHeaders, redirect, status }` (`null` deletes a header); applied after stomp rebuilds Origin, Referer and cookies, so a host rule wins. URLPattern is Chrome-only; a missing constructor or an invalid pattern skips that rule with one console warning |
|
|
72
|
+
| `init.cache` | `cache` (default `true`): HTTP cache in the service worker, explicit freshness only, revalidated when stale; a script's rewritten output is kept beside it (keyed by the body, URL, script type, config and build; never for `no-store`, `private`, Authorization or a cookie without `public`; 300 entries) and cleared with it by `clearCache()`; `false` turns both off |
|
|
73
|
+
| `init.onRequest` | optional `onRequest(info)`: one report per proxied request: real `url` (a CSS image without the rewriter's `#url(...)` fragment), `method`, `destination` (`fetch` for fetch, XHR and keepalive beacons), `status` (0 with `error` and `kind` on a failure the proxy produced), `ms`, `cached`, `bytes`, for a script `rewriteCache` (`hit`, `miss`, or `skip` when it may not be kept) and `rewriteIn` (`pool` or `service-worker`, when it was rewritten now), and `frame`: the document's own report and head subresources wait up to 1.5 s for the page's client id, so they carry their frame too; also dispatched as a `request` CustomEvent on the controller (`detail` is the report); nothing is sent when unset |
|
|
74
|
+
| `init.onNavigate` | optional `onNavigate(info, frame)`: the page reported where it is (`{ url, title, canGoBack, canGoForward }`); also dispatched as a `navigate` CustomEvent on the frame and on the controller (`detail: { info, frame }`) |
|
|
75
|
+
| `NavigateInfo.icon` | the page's icon as it declared it (first `link rel="icon"` / `shortcut icon`, else `apple-touch-icon`), resolved against the real URL, or null; reported again when the page swaps it |
|
|
76
|
+
| `init.onOpen` | optional `onOpen(url, frame)`: called when a page wants a new tab (`window.open`, a link with `target="_blank"` or a name, a modifier or middle click); the page's `window.open` then returns `null`, like a popup blocker; also dispatched as an `open` CustomEvent on the controller (`detail: { url, frame }`). Without it popups open natively as bare proxied tabs |
|
|
77
|
+
| `init.errorPage` | optional: the host's own page for a document the proxy failed to produce (unreachable origin, timeout, routing error, blocked rule, the refusal to proxy its own origin). A same-origin URL; served under the frame's URL with the failure's status, never cached, with `globalThis.stompError = { url, status, error, kind }` in its head; `location.reload()` retries. In isolation mode a path on the proxy host, where the worker runs. See `examples/error.html` |
|
|
78
|
+
| `wait()` | resolves when the service worker has a server for this controller; a build mismatch is healed first (update, wait for activation, rebind) and otherwise resolves on the worker at hand with `stale` set |
|
|
79
|
+
| `stale`, `stale` / `rebind` events | `{ controller, worker }` builds while an older worker is still in charge; the controller rebinds on its own when the new one activates and dispatches `rebind` (`{ build }`) |
|
|
80
|
+
| `createFrame(iframe?)` | returns a `Frame`; creates an `<iframe>` when none is given |
|
|
81
|
+
| `setTransport(transport)` | hot-swap the transport: a descriptor on a worker host (the worker imports it; in-flight requests finish on the old one), an instance on a page host, a TypeError otherwise; resolves once the new transport is in place |
|
|
82
|
+
| `rebind(worker?)` | talk to another service worker (default: the registration's active one) and shake hands again; a build mismatch does this on its own before `wait()` would reject |
|
|
83
|
+
| `clearCache()` | drop the HTTP cache |
|
|
84
|
+
| `setRules(rules)` | replace the request rules at run time; `onRequest` reports the index of the rule applied as `info.rule` |
|
|
85
|
+
| `cookies.get(url)` / `.set(url, setCookie)` / `.list(origin?)` / `.clear(origin?)` | the jar as the host sees it (host-only: pages never reach this port) |
|
|
86
|
+
| `route(url)` | the proxied URL for `url`, if you need it without a `Frame` |
|
|
87
|
+
| `transport`, `frames` | current transport and the frames created so far |
|
|
88
|
+
|
|
89
|
+
`Frame`:
|
|
90
|
+
|
|
91
|
+
| Member | Description |
|
|
92
|
+
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
+
| `go(url)` | navigate the frame to `url` through the proxy; resolves when the new document has reported (URL, title, history state in), a second after the frame's `load` event for a document that never reports, 30 s at the most. The first report comes at document start, so wait for the frame's `load` event (listened for before `go()`) before reading the page's DOM |
|
|
94
|
+
| `back()` / `forward()` | step the frame's own history; resolves to `false` when there is no entry in that direction (never walks the host page's history) |
|
|
95
|
+
| `canGoBack` / `canGoForward` | whether the frame's own history has an entry in that direction; `null` when the browser has no Navigation API |
|
|
96
|
+
| `reload()` | reload the proxied page; resolves like `go()`, `false` when there was nothing to reload |
|
|
97
|
+
| `url` | the real URL of the page the frame shows, or `null` |
|
|
98
|
+
| `navigate` event, `title`, `lastNavigate` | the page's own reports (load, pushState, hashchange, title changes); no polling needed |
|
|
99
|
+
| `element` | the underlying `<iframe>` |
|
|
100
|
+
|
|
101
|
+
`SearchBuilder(template)` turns free text into a URL: hosts and URLs pass
|
|
102
|
+
through, everything else is substituted into `template` at `%s`.
|
|
103
|
+
|
|
104
|
+
`RemoteController` (also `window.StompRemoteController` from
|
|
105
|
+
`dist/Controller.js`) is the host-side controller for origin isolation; it has
|
|
106
|
+
the same shape as `StompController` but runs the real controller in a hidden
|
|
107
|
+
frame on the proxy origin:
|
|
108
|
+
|
|
109
|
+
| Member | Description |
|
|
110
|
+
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
111
|
+
| `new RemoteController(init)` | `init.proxyOrigin`, `init.transport` (`{ module, options, host? }`, hosted in the controller frame's transport worker unless `host: 'page'`), optional `directory`, `codec`, `sandbox`, `pollInterval` |
|
|
112
|
+
| `init.allowInvalidJs` | as on `StompController` |
|
|
113
|
+
| `init.membrane` | as on `StompController` |
|
|
114
|
+
| `init.rewriteWorkers`, `init.rewriteTimeout` | as on `StompController`; the controller frame starts the workers |
|
|
115
|
+
| `init.assets` | as on `StompController`; the controller frame is loaded as `<directory><controllerFrame>?script=<controllerFrameScript>` and learns its script name from that query |
|
|
116
|
+
| `init.names` | as on `StompController`, passed to the controller frame |
|
|
117
|
+
| `init.hooks` | as on `StompController`; a `{ url }` is fetched by the controller frame from the proxy origin |
|
|
118
|
+
| `build` | the build this controller bundle came from (version plus commit, or the package version), the same string the module exports as `BUILD`; the worker's is `config.build` and `stale` reports a mismatch |
|
|
119
|
+
| `unroute(url)` | the real URL a routed one stands for (the inverse of `route()`, this controller's codec), null for anything that is not a route |
|
|
120
|
+
| `init.id` | as on `StompController`, minted here (`id`) and handed to the controller frame, so the host knows the address before the frame answers; two host tabs run two controller frames and two controllers on one proxy origin |
|
|
121
|
+
| `init.rules` | as on `StompController`, relayed through the controller frame |
|
|
122
|
+
| `init.cache` | as on `StompController` |
|
|
123
|
+
| `init.onRequest` | as on `StompController`, relayed through the controller frame |
|
|
124
|
+
| `init.onNavigate` | as on `StompController`; the reports arrive from the proxy origin and are accepted only from this controller's own frames |
|
|
125
|
+
| `NavigateInfo.icon` | the page's icon as it declared it (first `link rel="icon"` / `shortcut icon`, else `apple-touch-icon`), resolved against the real URL, or null; reported again when the page swaps it |
|
|
126
|
+
| `init.onOpen` | as on `StompController`: the request reaches the host from the proxy origin and is accepted only from this controller's own frames |
|
|
127
|
+
| `wait()` | resolves once the controller frame has registered the service worker and loaded the transport |
|
|
128
|
+
| `createFrame(iframe?)` | returns a `RemoteFrame`; applies the sandbox attribute |
|
|
129
|
+
| `setTransport(descriptor)` | hot-swap the transport (loaded inside the controller frame) |
|
|
130
|
+
| `clearCache()` | drop the HTTP cache (inside the controller frame) |
|
|
131
|
+
| `setRules(rules)` | as on `StompController` |
|
|
132
|
+
| `cookies.*` | as on `StompController`, relayed through the controller frame |
|
|
133
|
+
| `route(url)` | the proxied URL for `url` on the proxy origin |
|
|
134
|
+
| `frames`, `config`, `element` | frames created so far, the controller's config, the hidden controller iframe |
|
|
135
|
+
| `stale`, `stale` / `rebind` events | as on `StompController`, mirrored from the controller frame |
|
|
136
|
+
|
|
137
|
+
`RemoteFrame`: `go(url)`, `back()`, `forward()`, `reload()` (the last three
|
|
138
|
+
are relayed through the controller frame, which is same-origin with the
|
|
139
|
+
proxied page), `url`, `title`, `canGoBack` and `canGoForward` (last known,
|
|
140
|
+
refreshed every `pollInterval` ms), `element`.
|
|
141
|
+
|
|
142
|
+
A transport descriptor is `{ module: '/epoxy/index.mjs', options: [{ wisp }] }`:
|
|
143
|
+
the module URL must be on the proxy origin and its default export is the
|
|
144
|
+
transport class, constructed with `...options`.
|
|
145
|
+
|
|
146
|
+
## Origin isolation
|
|
147
|
+
|
|
148
|
+
`StompController` keeps everything on one origin, so a proxied page is
|
|
149
|
+
same-origin with the page that embeds it. Browsers grant same-origin frames
|
|
150
|
+
full access to each other: `parent.document`, `top.location = ...`,
|
|
151
|
+
`frameElement`, cookies and storage of the host. Stomp's hooks hide most of
|
|
152
|
+
that from the page's own (rewritten) JavaScript, but hooks are not a security
|
|
153
|
+
boundary. **Same-origin mode is for development only.**
|
|
154
|
+
|
|
155
|
+
Origin isolation serves `dist/` and all proxied content from a second origin
|
|
156
|
+
(for example `app.example.com` hosting the UI and `proxy.example.com` serving
|
|
157
|
+
the proxy). The host page creates a `RemoteController`, which embeds
|
|
158
|
+
`dist/controllerFrame.html` from the proxy origin; that frame registers the
|
|
159
|
+
service worker, loads the transport and runs the real `StompController`.
|
|
160
|
+
Proxied pages live in sandboxed, cross-origin iframes (`allow-same-origin`
|
|
161
|
+
so the service worker controls them, no `allow-top-navigation`), so the
|
|
162
|
+
browser itself refuses them any access to the host, and the service worker
|
|
163
|
+
sends `Content-Security-Policy: frame-ancestors <host origin>` so nothing
|
|
164
|
+
else can embed the proxy origin. On top of that the inject bundle makes the
|
|
165
|
+
page believe it is top-level: `top`, `parent`, `frameElement`, `window.name`,
|
|
166
|
+
`outerWidth`/`outerHeight`, `screenX`/`screenY`, `document.referrer` and
|
|
167
|
+
`location.ancestorOrigins` all answer as they would for a real top window,
|
|
168
|
+
and `message` events from outside the proxied world never reach the page.
|
|
169
|
+
|
|
170
|
+
## Embedding
|
|
171
|
+
|
|
172
|
+
Install the package from npm. Nightly builds of master are published as
|
|
173
|
+
`@sysce/stomp@nightly` (versions like `4.2.1-nightly.202611100717.g1a2b3c4`:
|
|
174
|
+
the next patch, the UTC time, the commit), each only after CI passed on that
|
|
175
|
+
commit, with npm provenance:
|
|
29
176
|
|
|
30
177
|
```sh
|
|
31
|
-
|
|
178
|
+
npm install @sysce/stomp@nightly
|
|
32
179
|
```
|
|
33
180
|
|
|
34
|
-
|
|
181
|
+
Pin the exact version a host was tested with. `stompPath` from
|
|
182
|
+
`@sysce/stomp/path` is the directory to serve.
|
|
183
|
+
|
|
184
|
+
[`examples/minimal-host.html`](examples/minimal-host.html) is the smallest
|
|
185
|
+
host application: plain HTML and one module script, with a comment per step.
|
|
186
|
+
The demo server serves it at `/examples/minimal-host.html`
|
|
187
|
+
(`npm run demo`), and `test/suites/host-api.mjs` runs the host API checklist
|
|
188
|
+
against it. The deployment settings sit in one object at the top of the
|
|
189
|
+
script (`directory`, the transport module, the wisp URL, `codec`).
|
|
190
|
+
|
|
191
|
+
The boot sequence it shows: `StompController.register(directory)` for the
|
|
192
|
+
directory `dist/` is served from (that scope, `updateViaCache: 'none'`,
|
|
193
|
+
resolves once the worker is activated); import the transport module and
|
|
194
|
+
construct it with the wisp URL; create the `StompController` with the
|
|
195
|
+
registration, the transport, the directory, the codec and the callbacks; `await controller.wait()`; then `createFrame()` and
|
|
196
|
+
place its `<iframe>`. From there `frame.go(url)`, `back()`, `forward()` and
|
|
197
|
+
`reload()` drive the frame, and `canGoBack` / `canGoForward` tell the host
|
|
198
|
+
what to enable. `controller.stale` and the `stale` / `rebind` events say
|
|
199
|
+
whether an older service worker is still in charge after a deploy.
|
|
200
|
+
|
|
201
|
+
The three callbacks are the host's view of the pages: `onNavigate` reports
|
|
202
|
+
URL, title and history state whenever the page changes them (the example
|
|
203
|
+
updates its address input and title from it), `onOpen` receives the URL a
|
|
204
|
+
page wanted in a new tab (the page's `window.open` returns `null`; the
|
|
205
|
+
example opens it in place, a real host opens a tab), and `onRequest` reports
|
|
206
|
+
every proxied request with its real URL, status and the `Frame` it belongs
|
|
207
|
+
to (the example appends one line per request to a log).
|
|
208
|
+
|
|
209
|
+
The example runs the same-origin `StompController`, which is not a security
|
|
210
|
+
boundary; a deployment that must keep proxied pages away from the host uses
|
|
211
|
+
`RemoteController` on a second origin, see "Origin isolation" above.
|
|
212
|
+
|
|
213
|
+
Transport: use epoxy for HTTP-heavy sites. Pages that rely on WebSockets are
|
|
214
|
+
the exception (incoming frames stop after the first round trip against
|
|
215
|
+
wisp-js 0.5, see the changelog's known limitations and the transports note
|
|
216
|
+
in `AGENTS.md`); the demo's transport selector switches to libcurl for
|
|
217
|
+
those, which in turn stops answering HTTP after some tens of requests.
|
|
218
|
+
|
|
219
|
+
Use the epoxy build this repository pins (`deploy/epoxy/`), not the npm
|
|
220
|
+
release: upstream epoxy loses one of two requests started together on a cold
|
|
221
|
+
transport (two frames of one page navigating at once, the first never loads)
|
|
222
|
+
and hangs on a pooled connection the server has already closed for idling.
|
|
223
|
+
The pinned build patches both (`epoxy-concurrency` suite).
|
|
224
|
+
|
|
225
|
+
## Demo
|
|
226
|
+
|
|
227
|
+
`npm run demo` serves a complete host application on http://localhost:8080,
|
|
228
|
+
including a wisp server and both transports. It is documented in
|
|
229
|
+
[`demo/README.md`](demo/README.md) and is the quickest way to see the API above
|
|
230
|
+
in use. It is live at https://stomp.fracit.al, deployed from `master` by
|
|
231
|
+
`.github/workflows/deploy.yml`.
|
|
232
|
+
|
|
233
|
+
## Development
|
|
35
234
|
|
|
36
235
|
```sh
|
|
37
|
-
|
|
236
|
+
npm install
|
|
237
|
+
npm run build # type-check, bundle with esbuild, emit declarations
|
|
238
|
+
npm run dev # rebuild on change
|
|
239
|
+
npm run demo # http://localhost:8080 with a wisp server, epoxy and libcurl
|
|
240
|
+
npm run lint
|
|
38
241
|
```
|