@nivalos/lithium.js 1.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/LICENSE +661 -0
- package/README.md +317 -0
- package/bin/lithium.js +40 -0
- package/client/backends/scramjet.js +53 -0
- package/client/backends/ultraviolet.js +30 -0
- package/client/index.js +359 -0
- package/client/net.js +114 -0
- package/client/sw.js +33 -0
- package/package.json +69 -0
- package/server/color.js +16 -0
- package/server/doctor.js +132 -0
- package/server/errors.js +21 -0
- package/server/index.js +238 -0
- package/server/meta.js +55 -0
- package/server/pkgs.js +91 -0
- package/server/registry.js +262 -0
- package/server/start.js +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# Lithium.JS
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
A flexible web proxy framework to make your skid dream a reality.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Proxy registry**: Ultraviolet 3.x and Scramjet 2.x built in, `register()` your own
|
|
15
|
+
- **Transport registry**: Epoxy and Libcurl built in, each declaring which interface generation they speak
|
|
16
|
+
- **Compatibility validation at startup**: wrong package version or wrong transport generation throws a clear `LithiumError`, not a proxy that "sort of works" until it doesn't
|
|
17
|
+
- **`lithium doctor`**: one command to check your whole install
|
|
18
|
+
- **Events + history**: `on("navigate", ...)`, `back()`/`forward()`/`reload()`/`search()`, works the same for every proxy
|
|
19
|
+
- **Debug mode + network log**: `debug: true` for verbose tracing, `network_log()`/`on("request", ...)` for a devtools-lite view of the proxied page's `fetch`/XHR traffic
|
|
20
|
+
- **Header policy**: block/allow specific headers, or bypass Lithium's interception entirely with `passthrough` mode
|
|
21
|
+
- **Modular design**: clean separation of client and server code, custom backends are first-class
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install lithium.js
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Scramjet 2.x is published under the `alpha` tag and Lithium pins the exact
|
|
30
|
+
versions it was built against, so you don't need to install Scramjet yourself.
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### Server Setup
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
import { create_lithium_server } from "lithium.js";
|
|
38
|
+
|
|
39
|
+
const { server, port } = create_lithium_server({
|
|
40
|
+
staticDir: 'public',
|
|
41
|
+
port: 8080,
|
|
42
|
+
proxy: 'ultraviolet',
|
|
43
|
+
transport: 'epoxy'
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
server.listen(port, () => {
|
|
47
|
+
console.log(`Lithium server running on http://localhost:${port}`)
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
** Ensure that you import it relative to your type in package.json.
|
|
52
|
+
|
|
53
|
+
`create_lithium_server` doesn't call `listen` for you, so you can attach your
|
|
54
|
+
own routes to the returned `app` first. Want to just try it? `npm start` runs
|
|
55
|
+
a ready-made server (`PROXY`, `TRANSPORT`, `PORT` and `STATIC_DIR` env vars).
|
|
56
|
+
|
|
57
|
+
### Client Setup
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
import { init_lithium, navigate } from "/client/index.js";
|
|
61
|
+
|
|
62
|
+
await init_lithium({
|
|
63
|
+
searchEngine: 'google',
|
|
64
|
+
onReady: () => {
|
|
65
|
+
console.log('Lithium is ready!')
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
navigate('example.com')
|
|
70
|
+
navigate('search query')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
When you are linking this script, make sure to add type="module".
|
|
74
|
+
|
|
75
|
+
`init_lithium` loads everything the chosen proxy needs (bare-mux, the Ultraviolet
|
|
76
|
+
bundle, the Scramjet controller, ...) by itself, so you no longer need extra
|
|
77
|
+
`<script>` tags. It rejects if setup fails, so wrap it in try/catch if you want
|
|
78
|
+
to show an error.
|
|
79
|
+
|
|
80
|
+
Pages are shown in an iframe. Give yours the id `proxyFrame` (and optionally put
|
|
81
|
+
it inside an element with id `container`), or Lithium will create one for you.
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
### Server Options
|
|
86
|
+
|
|
87
|
+
| Option | Type | Default | Description |
|
|
88
|
+
|-----------------------|-----------|-----------------|--------------------------------------------------------------------------------|
|
|
89
|
+
| staticDir | string | `'public'` | Directory for static files (relative to `process.cwd()`, or absolute) |
|
|
90
|
+
| port | number | `8080` | Port, returned from `create_lithium_server` for you to `listen` on |
|
|
91
|
+
| proxy | string | `'ultraviolet'` | Proxy: `'ultraviolet'`, `'scramjet'`, or one you registered (see below) |
|
|
92
|
+
| transport | string | `'epoxy'` | Transport: `'epoxy'`, `'libcurl'`, or one you registered |
|
|
93
|
+
| crossOriginIsolation | boolean | `true` | Sends COOP/COEP headers when the proxy wants them (Scramjet does, UV doesn't) |
|
|
94
|
+
| middleware | function[]| `[]` | `(req, res, next) => ...` functions, run before Lithium's own routes |
|
|
95
|
+
| wisp | object | `{}` | Options merged into the shared wisp-js server, e.g. `{ allow_loopback_ips: true }` |
|
|
96
|
+
| strict | boolean | `true` | `false`: warn about a package version mismatch instead of throwing |
|
|
97
|
+
| quiet | boolean | `false` | Suppress the `[lithium] proxy + transport: ...` startup line |
|
|
98
|
+
| color | boolean | *(auto)* | Force-enable/disable colored console output (auto-detects a TTY otherwise) |
|
|
99
|
+
|
|
100
|
+
An unknown `proxy`/`transport`, an incompatible pair, or a wrong package version throws a `LithiumError` (see below) instead of starting halfway broken.
|
|
101
|
+
|
|
102
|
+
### Client Options (`init_lithium(options)`)
|
|
103
|
+
|
|
104
|
+
| Option | Type | Default | Description |
|
|
105
|
+
|--------------|----------|------------|------------------------------------------------------------------------|
|
|
106
|
+
| searchEngine | string | `'google'` | `'google'` or `'duckduckgo'` |
|
|
107
|
+
| onReady | function | `null` | Shorthand for `on("ready", ...)` |
|
|
108
|
+
| onUrlChange | function | `null` | Shorthand for `on("navigate", ({url}) => ...)`, works for every proxy |
|
|
109
|
+
| debug | boolean | `false` | Verbose `[lithium debug]` tracing, see "Debugging" below |
|
|
110
|
+
| headers | object | see below | Header policy for the proxied page's `fetch`/XHR, see "Headers" below |
|
|
111
|
+
|
|
112
|
+
## Usage Examples
|
|
113
|
+
|
|
114
|
+
### Navigate, search, and move around
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
navigate('google.com') // url or domain
|
|
118
|
+
navigate('https://example.com')
|
|
119
|
+
search('minecraft') // always a search, even if it looks like a url
|
|
120
|
+
|
|
121
|
+
current_url() // the real url currently shown
|
|
122
|
+
back(); forward(); reload() // act on the proxied page's own history
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Listen for navigation
|
|
126
|
+
|
|
127
|
+
Works the same way for every proxy, built-in or custom:
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
on("navigate", ({ url, previousUrl }) => console.log("now at", url))
|
|
131
|
+
on("ready", () => console.log("lithium is ready"))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Check current config
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
console.log(config.proxy, config.transport, config.interface, config.ready)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Switch proxy/transport
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
const { server, port } = create_lithium_server({ proxy: 'scramjet', transport: 'libcurl' })
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Registry (`proxies`, `transports`)
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
import { proxies, transports, supports, get_compatibility } from "lithium.js"
|
|
150
|
+
|
|
151
|
+
proxies.list() // ["ultraviolet", "scramjet"]
|
|
152
|
+
transports.list() // ["epoxy", "libcurl"]
|
|
153
|
+
proxies.get("scramjet") // { name, interface, isolation, builtin, transports, packages }
|
|
154
|
+
supports("scramjet", "epoxy") // true
|
|
155
|
+
get_compatibility("ultraviolet", "x") // { supported: false, interface: "bare-mux", reason: "..." }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Registering a custom backend
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
proxies.register("my-proxy", {
|
|
162
|
+
interface: "bare-mux", // or "proxy-transports", or your own transport's interface name
|
|
163
|
+
isolation: false, // send COOP/COEP for this proxy?
|
|
164
|
+
packages: [], // [{ name, major? }] checked at startup
|
|
165
|
+
routes(app) { /* app.get(...) for anything special */ },
|
|
166
|
+
mounts() { return [{ label: "my-proxy", url: "/my-proxy/", dir: someDir, files: ["bundle.js"] }] },
|
|
167
|
+
serviceWorker: `...`, // JS source; Lithium wraps it with skipWaiting/clients.claim
|
|
168
|
+
clientModule: "/my-proxy/client.js", // browser module implementing init/navigate, see client/backends/*.js
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
transports.register("my-transport", {
|
|
172
|
+
interfaces: { "bare-mux": { mount: "/my-transport/", dir: someDir, entry: "index.mjs" } },
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`client/backends/ultraviolet.js` and `client/backends/scramjet.js` are the reference implementations of the client module contract (`init(ctx)`, `navigate(url, ctx)`, optional `current_url(ctx)`).
|
|
177
|
+
|
|
178
|
+
## Errors (`LithiumError`)
|
|
179
|
+
|
|
180
|
+
Every error Lithium throws on purpose has a `.code` you can branch on, plus `.details` with what would actually work:
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
import { LithiumError } from "lithium.js"
|
|
184
|
+
try {
|
|
185
|
+
create_lithium_server({ proxy: "ultraviolet", transport: "wisp" })
|
|
186
|
+
} catch (err) {
|
|
187
|
+
if (err instanceof LithiumError) console.log(err.code, err.details)
|
|
188
|
+
// "INCOMPATIBLE_TRANSPORT" { proxyInterface: "bare-mux", compatibleTransports: ["epoxy", "libcurl"], ... }
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Codes: `UNKNOWN_PROXY`, `UNKNOWN_TRANSPORT`, `INCOMPATIBLE_TRANSPORT`, `PACKAGE_MISSING`, `PACKAGE_VERSION_MISMATCH`, `TRANSPORT_VERSION_MISMATCH`, `BACKEND_EXISTS`, `INVALID_BACKEND`, `INVALID_OPTION`.
|
|
193
|
+
|
|
194
|
+
## `lithium doctor`
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
npx lithium doctor [--proxy ultraviolet] [--transport epoxy] [--json] [--no-color]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Checks Node's version, every package's version (and, for epoxy/libcurl, which *generation* is installed — this is the check that would have caught the original UV startup bug), that every file each backend needs is actually on disk, and whether the proxy/transport pair you're about to run is compatible. Exits `1` if it finds a problem, so it's CI-friendly. Colored automatically in a terminal, plain when piped, or force with `--no-color`/`NO_COLOR=1`/`FORCE_COLOR=1`.
|
|
201
|
+
|
|
202
|
+
## Debugging
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
await init_lithium({ debug: true }) // or: config.debug = true, any time
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Traces every step (script load order, service worker registration, backend init, navigation targets) as `[lithium debug]` lines, and turns on `window.onerror`/`unhandledrejection` logging. Off by default since it's noisy.
|
|
209
|
+
|
|
210
|
+
## Headers
|
|
211
|
+
|
|
212
|
+
Lithium's Node server never sees the proxied site's raw HTTP (that goes browser → service worker → the wisp tunnel → the real site, as bytes, not parsed HTTP), so header control happens client-side, on the proxied page's own `fetch`/`XHR` calls:
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
await init_lithium({
|
|
216
|
+
headers: {
|
|
217
|
+
mode: "filter", // "filter" (default) or "passthrough" (touch nothing)
|
|
218
|
+
block: ["x-frame-options"], // stripped from requests AND responses, case-insensitive
|
|
219
|
+
allow: null, // if an array, ONLY these header names survive
|
|
220
|
+
},
|
|
221
|
+
})
|
|
222
|
+
set_header_policy({ mode: "passthrough" }) // change it any time, no re-init needed
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Scope, honestly:** this rebuilds the `Response` object `fetch()` hands to the page, so it's real for anything the page's own JS reads. `XMLHttpRequest` traffic is logged but the policy isn't enforced on it. `<img>`, `<script src>`, `<link>`, and CSS loads never go through JS at all, so they're invisible to this — there's no hook point for them at this layer.
|
|
226
|
+
|
|
227
|
+
## Network log
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
on("request", (entry) => console.log(entry)) // { method, url, status, duration, requestHeaders, responseHeaders, blockedRequestHeaders, blockedResponseHeaders, ... }
|
|
231
|
+
network_log() // everything captured so far (capped at 300 entries)
|
|
232
|
+
clear_network_log()
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Same scope as Headers above: `fetch`/`XHR` only, reinstalled fresh on every real navigation (a new page is a new `window`), and only requests made after that page finishes loading. The test app (`lithium-test/`) has a "Network" panel built on this.
|
|
236
|
+
|
|
237
|
+
## Colored output
|
|
238
|
+
|
|
239
|
+
`server/color.js` is a tiny zero-dependency ANSI helper (`color.green(...)`, etc.) used for `lithium doctor` and the server's own startup/warning/error lines. It auto-detects a TTY and respects `NO_COLOR`/`FORCE_COLOR`; pass `color: false` to `create_lithium_server` or `--no-color` to the CLI to force it off.
|
|
240
|
+
|
|
241
|
+
## How the transports work (read this if you touch package.json)
|
|
242
|
+
|
|
243
|
+
The transport packages come in two generations that are **not** interchangeable:
|
|
244
|
+
|
|
245
|
+
| Proxy | Interface | epoxy | libcurl |
|
|
246
|
+
|------------------|--------------------|-------|---------|
|
|
247
|
+
| Ultraviolet 3.x | bare-mux | ^2 | ^1 |
|
|
248
|
+
| Scramjet 2.x | proxy-transports | ^3 | ^2 |
|
|
249
|
+
|
|
250
|
+
Mixing them can look like it works and then fail on specific sites. Lithium
|
|
251
|
+
installs both: the normal `@mercuryworkshop/epoxy-transport` and
|
|
252
|
+
`@mercuryworkshop/libcurl-transport` are the Scramjet generation, and the
|
|
253
|
+
`epoxy-transport-bm` / `libcurl-transport-bm` entries in `package.json` are npm
|
|
254
|
+
aliases for the bare-mux generation that Ultraviolet uses. Don't "clean up" the
|
|
255
|
+
aliases or bump their versions to match.
|
|
256
|
+
|
|
257
|
+
Files are served at:
|
|
258
|
+
|
|
259
|
+
| Path | What |
|
|
260
|
+
|------------------------------|----------------------------------------|
|
|
261
|
+
| `/uv/`, `/baremux/` | Ultraviolet + bare-mux (`/uv/uv.config.js` is generated by Lithium; proxied pages live under `/service/`) |
|
|
262
|
+
| `/bm/epoxy/`, `/bm/libcurl/` | transport for Ultraviolet |
|
|
263
|
+
| `/scram/`, `/controller/`, `/utils/` | Scramjet, its controller, its plugins |
|
|
264
|
+
| `/epoxy/`, `/libcurl/` | transport for Scramjet |
|
|
265
|
+
| `/wisp/` | wisp websocket |
|
|
266
|
+
|
|
267
|
+
## Troubleshooting
|
|
268
|
+
|
|
269
|
+
### Start here
|
|
270
|
+
Run `npx lithium doctor` first — it checks Node's version, every package version (including which transport *generation* is installed, the single most common source of "it starts then breaks on random sites"), that every file each backend needs is on disk, and whether your proxy/transport pair is compatible.
|
|
271
|
+
|
|
272
|
+
### Config not loading
|
|
273
|
+
- Check browser console for `[lithium] config:`
|
|
274
|
+
- Ensure your HTML is a `.html` file in `staticDir` (config is injected into `<head>` when the file is served)
|
|
275
|
+
|
|
276
|
+
### Proxy not working
|
|
277
|
+
- Check if service worker registered: `navigator.serviceWorker.controller`
|
|
278
|
+
- Verify proxy files are accessible in Network tab
|
|
279
|
+
- Look for errors in console, and for `[lithium] ...: expected "..." in ...` lines in the server log
|
|
280
|
+
|
|
281
|
+
### Scramjet: some sites break, or a warning about cross-origin isolation
|
|
282
|
+
- `crossOriginIsolated` must be `true` in the browser console (needs `localhost` or https)
|
|
283
|
+
- If your own page loads third-party fonts/images and they got blocked, either serve them with CORS/CORP headers or set `crossOriginIsolation: false` (some proxied sites will then break)
|
|
284
|
+
|
|
285
|
+
### Ultraviolet: "ServiceWorker script evaluation failed"
|
|
286
|
+
- The browser gives no detail, so Lithium's client logs the status of every file the worker imports (look for `[lithium] 404 ...` lines). A 404 next to `<- config.sw` (or `handler`/`bundle`) means the `uv.config.js` being served isn't Lithium's generated one, e.g. a stale copy in your `staticDir` or a caching layer.
|
|
287
|
+
|
|
288
|
+
### Transport issues
|
|
289
|
+
- Ultraviolet: ensure the BareMux worker is accessible at `/baremux/worker.js`
|
|
290
|
+
- Check WISP connection in DevTools (`/wisp/`)
|
|
291
|
+
- Verify transport files are served (see the table above)
|
|
292
|
+
|
|
293
|
+
### A site looks broken and you don't know why
|
|
294
|
+
- Turn on `init_lithium({ debug: true })` and reload — see "Debugging" above
|
|
295
|
+
- Open `network_log()` (or the test app's "Network" panel) and check for unexpected `status: 0` entries (the request threw) or headers you're blocking that the site actually needed
|
|
296
|
+
- If you're blocking headers, try `set_header_policy({ mode: "passthrough" })` to rule that out first
|
|
297
|
+
|
|
298
|
+
## Copyright notice
|
|
299
|
+
```
|
|
300
|
+
sythora/Platinum: A flexible web proxy framework to make your skid dream a reality.
|
|
301
|
+
Copyright (C) 2026 sythora & nivalos
|
|
302
|
+
|
|
303
|
+
This program is free software: you can redistribute it and/or modify
|
|
304
|
+
it under the terms of the GNU Affero General Public License as
|
|
305
|
+
published by the Free Software Foundation, either version 3 of the
|
|
306
|
+
License, or (at your option) any later version.
|
|
307
|
+
|
|
308
|
+
This program is distributed in the hope that it will be useful,
|
|
309
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
310
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
311
|
+
GNU Affero General Public License for more details.
|
|
312
|
+
|
|
313
|
+
You should have received a copy of the GNU Affero General Public License
|
|
314
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Lithium.js is an updated fork of Platinum.js, this fork updates dependencies, and is the new framework for Lithium (a fork of Utopia)
|
package/bin/lithium.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// lithium doctor [--proxy <name>] [--transport <name>] [--json]
|
|
3
|
+
import { run_doctor, format_doctor } from "../server/doctor.js"
|
|
4
|
+
import { set_color } from "../server/color.js"
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2)
|
|
7
|
+
const flag = (name) => {
|
|
8
|
+
const i = args.indexOf(`--${name}`)
|
|
9
|
+
return i === -1 ? undefined : args[i + 1]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const HELP = `usage: lithium doctor [options]
|
|
13
|
+
|
|
14
|
+
Checks your install: package versions (including the two transport
|
|
15
|
+
generations), served files, and whether a proxy/transport pair is compatible.
|
|
16
|
+
|
|
17
|
+
options:
|
|
18
|
+
--proxy <name> proxy to check the configuration for (default: ultraviolet)
|
|
19
|
+
--transport <name> transport to check the configuration for (default: epoxy)
|
|
20
|
+
--json machine-readable output
|
|
21
|
+
--no-color disable colored output (also respects NO_COLOR)
|
|
22
|
+
-h, --help this text
|
|
23
|
+
|
|
24
|
+
exit code is 1 if a problem was found, 0 otherwise.
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
const command = args[0]
|
|
28
|
+
if (!command || command === "-h" || command === "--help" || command === "help") {
|
|
29
|
+
console.log(HELP)
|
|
30
|
+
process.exit(command ? 0 : 1)
|
|
31
|
+
}
|
|
32
|
+
if (command !== "doctor") {
|
|
33
|
+
console.error(`unknown command "${command}"\n\n${HELP}`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (args.includes("--no-color")) set_color(false)
|
|
38
|
+
const result = await run_doctor({ proxy: flag("proxy"), transport: flag("transport") })
|
|
39
|
+
console.log(args.includes("--json") ? JSON.stringify(result, null, 2) : format_doctor(result))
|
|
40
|
+
process.exit(result.ok ? 0 : 1)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Scramjet 2.x backend for the Lithium client (see ultraviolet.js for the
|
|
2
|
+
// backend module contract).
|
|
3
|
+
|
|
4
|
+
export async function init(ctx) {
|
|
5
|
+
if (!window.crossOriginIsolated) {
|
|
6
|
+
console.warn("[lithium] page is not cross-origin isolated, some proxied sites will break (needs https or localhost, and the server's COOP/COEP headers)")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
for (const src of ["/scram/scramjet.js", "/controller/controller.api.js", "/utils/scramjet-utils.js"]) {
|
|
10
|
+
await ctx.load_script(src)
|
|
11
|
+
}
|
|
12
|
+
const api = window.$scramjetController
|
|
13
|
+
if (!api?.Controller) throw new Error("scramjet controller global ($scramjetController) is missing")
|
|
14
|
+
|
|
15
|
+
// the transport is a plain object handed straight to the controller
|
|
16
|
+
const { default: Transport } = await import(ctx.transport_url)
|
|
17
|
+
|
|
18
|
+
const controller = new api.Controller({
|
|
19
|
+
serviceworker: ctx.serviceworker,
|
|
20
|
+
transport: new Transport({ wisp: ctx.wisp_url }),
|
|
21
|
+
config: {
|
|
22
|
+
scramjetPath: "/scram/scramjet.js",
|
|
23
|
+
wasmPath: "/scram/scramjet.wasm",
|
|
24
|
+
injectPath: "/controller/controller.inject.js",
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
// don't create frames before this resolves or the first navigation can 404
|
|
28
|
+
await controller.wait()
|
|
29
|
+
ctx.state.controller = controller
|
|
30
|
+
|
|
31
|
+
// browsers kill idle service workers after ~30s and scramjet's worker
|
|
32
|
+
// forgets its routes when that happens (later navigations 404 until a
|
|
33
|
+
// reload). a ping resets the idle timer.
|
|
34
|
+
setInterval(() => navigator.serviceWorker.controller?.postMessage("keepalive"), 15000)
|
|
35
|
+
|
|
36
|
+
console.log(`[lithium] scramjet ready (${ctx.transport} over ${ctx.wisp_url})`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function navigate(url, ctx) {
|
|
40
|
+
// one frame, reused (creating a new one per navigation leaks iframes)
|
|
41
|
+
if (!ctx.state.frame) {
|
|
42
|
+
const utils = window.$scramjetUtils
|
|
43
|
+
ctx.state.frame = ctx.state.controller.createFrame(ctx.iframe(), {
|
|
44
|
+
plugins: [
|
|
45
|
+
// target="_blank" / window.open would otherwise escape the proxy
|
|
46
|
+
new utils.CatchEscapedLinksPlugin(() => new URL(location.href)),
|
|
47
|
+
// scramjet tells us where the page went, no polling needed
|
|
48
|
+
new utils.UrlWatcherPlugin((u) => ctx.report_url(String(u))),
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
ctx.state.frame.go(url) // synchronous
|
|
53
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Ultraviolet 3.x backend for the Lithium client.
|
|
2
|
+
// A backend module exports:
|
|
3
|
+
// init(ctx) load scripts, set up the transport
|
|
4
|
+
// navigate(url, ctx) show a real URL (already normalised) in ctx.iframe()
|
|
5
|
+
// current_url(ctx) optional: real URL currently shown (polled)
|
|
6
|
+
// ctx.state is yours to keep things in.
|
|
7
|
+
|
|
8
|
+
export async function init(ctx) {
|
|
9
|
+
// bare-mux carries the transport (a SharedWorker), UV bundle does the rewriting
|
|
10
|
+
await ctx.load_script("/baremux/index.js")
|
|
11
|
+
await ctx.load_script("/uv/uv.bundle.js")
|
|
12
|
+
await ctx.load_script("/uv/uv.config.js")
|
|
13
|
+
|
|
14
|
+
const conn = new BareMux.BareMuxConnection("/baremux/worker.js")
|
|
15
|
+
await conn.setTransport(ctx.transport_url, [{ wisp: ctx.wisp_url }])
|
|
16
|
+
console.log(`[lithium] ultraviolet ready (${ctx.transport} over ${ctx.wisp_url})`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function navigate(url, ctx) {
|
|
20
|
+
ctx.iframe().src = __uv$config.prefix + __uv$config.encodeUrl(url)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ultraviolet has no url-change hook, so Lithium polls this
|
|
24
|
+
export function current_url(ctx) {
|
|
25
|
+
const frame = ctx.iframe(false)
|
|
26
|
+
const cfg = window.__uv$config
|
|
27
|
+
if (!frame || !cfg) return null
|
|
28
|
+
const path = frame.contentWindow.location.pathname
|
|
29
|
+
return path.startsWith(cfg.prefix) ? cfg.decodeUrl(path.slice(cfg.prefix.length)) : null
|
|
30
|
+
}
|