@factoidal/core 0.19.0 → 0.21.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/CHANGELOG.md +65 -0
- package/README.md +59 -15
- package/authz/server.mjs +23 -2
- package/bin/engine.mjs +22 -1
- package/bin/factoidal.mjs +710 -38
- package/bin/pack-host.mjs +5 -0
- package/bin/serve.mjs +19 -3
- package/browser.d.ts +23 -0
- package/browser.js +26 -0
- package/index.d.ts +463 -24
- package/index.mjs +29 -0
- package/l4-assets/l4factoidal.js +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +6 -6
- package/lib/api.mjs +480 -28
- package/lib/browser-engine.mjs +28 -1
- package/lib/cid-node.mjs +77 -0
- package/lib/cid.mjs +146 -0
- package/lib/cipher-host.mjs +126 -0
- package/lib/dataset.mjs +96 -12
- package/lib/engine-integrity.mjs +56 -0
- package/lib/lean-entry.mjs +30 -12
- package/lib/nodes.mjs +318 -17
- package/lib/sbm11-lws.mjs +4 -2
- package/lib/sbm11.mjs +29 -6
- package/lib/version.mjs +1 -1
- package/lws/README.md +9 -0
- package/lws/client.mjs +19 -2
- package/lws/persist.mjs +169 -0
- package/lws/server.mjs +82 -29
- package/package.json +2 -1
- package/solid/server/index.mjs +79 -13
- package/webdav/server.mjs +154 -25
- package/xmpp/client.mjs +332 -0
- package/xmpp/proxy.mjs +114 -0
package/xmpp/proxy.mjs
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// factoidal/xmpp/proxy.mjs — `factoidal xmpp-http-proxy`: a local HTTP
|
|
2
|
+
// listener whose every request is sent over XMPP (XEP-0332) to ONE target
|
|
3
|
+
// JID, so a file browser, rclone, curl or our LWS client can use
|
|
4
|
+
// http://127.0.0.1:PORT/ to reach `webdav-serve --xmpp` behind NAT.
|
|
5
|
+
// https://github.com/danbri/factoidal/issues/1107 (step 2). Design record:
|
|
6
|
+
// docs/designissues/2026-10-28-http-over-xmpp.md, section "Step 2".
|
|
7
|
+
//
|
|
8
|
+
// WHAT THIS FILE IS ALLOWED TO DO
|
|
9
|
+
// Listen on the address the engine accepted (`listenHost`, 127.0.0.1 only in
|
|
10
|
+
// this step), read each HTTP request (method, request target, raw header
|
|
11
|
+
// pairs, body up to the engine's `maxBodyBytes` plus one byte), give it to
|
|
12
|
+
// `connectHttpRequester().request`, and write the status, header pairs and
|
|
13
|
+
// body it resolves with. No protocol decision: which headers are forwarded,
|
|
14
|
+
// where the stanzas go, the refusals, limits, timeouts and every status are
|
|
15
|
+
// decided in formal/lean4/L4Factoidal/XMPP/HttpRequester.lean and
|
|
16
|
+
// formal/lean4/Wasm/Ops/XmppClient.lean.
|
|
17
|
+
|
|
18
|
+
import http from 'node:http'
|
|
19
|
+
import { connectHttpRequester } from './client.mjs'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Start the proxy. Resolves when the XMPP stream is bound and the listener
|
|
23
|
+
* is open.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} options the options of `connectHttpRequester` (jid,
|
|
26
|
+
* password, target, host, port, tls, tlsOptions, timeoutSeconds, engine,
|
|
27
|
+
* onError), plus:
|
|
28
|
+
* @param {string} [options.listenHost] default 127.0.0.1 (the engine refuses any other)
|
|
29
|
+
* @param {number} [options.listenPort] default 8091; 0 takes an ephemeral port
|
|
30
|
+
* @returns {Promise<{origin: string, jid: string, target: string, closed: Promise<string>, close: Function}>}
|
|
31
|
+
*/
|
|
32
|
+
export async function startHttpProxy (options = {}) {
|
|
33
|
+
const onError = typeof options.onError === 'function' ? options.onError : () => {}
|
|
34
|
+
const conn = await connectHttpRequester({
|
|
35
|
+
...options,
|
|
36
|
+
listenHost: typeof options.listenHost === 'string' ? options.listenHost : '127.0.0.1'
|
|
37
|
+
})
|
|
38
|
+
const limit = conn.maxBodyBytes
|
|
39
|
+
|
|
40
|
+
const server = http.createServer((req, res) => {
|
|
41
|
+
const parts = []
|
|
42
|
+
let size = 0
|
|
43
|
+
req.on('data', (b) => {
|
|
44
|
+
// Keep at most limit + 1 bytes: enough for the engine to answer 413.
|
|
45
|
+
if (size > limit) return
|
|
46
|
+
parts.push(b)
|
|
47
|
+
size += b.length
|
|
48
|
+
})
|
|
49
|
+
req.on('end', async () => {
|
|
50
|
+
const headers = []
|
|
51
|
+
for (let i = 0; i + 1 < req.rawHeaders.length; i += 2) headers.push([req.rawHeaders[i], req.rawHeaders[i + 1]])
|
|
52
|
+
let body = Buffer.concat(parts)
|
|
53
|
+
if (body.length > limit + 1) body = body.subarray(0, limit + 1)
|
|
54
|
+
let answer
|
|
55
|
+
try {
|
|
56
|
+
answer = await conn.request({ method: req.method, target: req.url, headers, body })
|
|
57
|
+
} catch (error) {
|
|
58
|
+
onError(error)
|
|
59
|
+
answer = { status: 502, headers: [['content-type', 'text/plain; charset=utf-8']], body: Buffer.from(`proxy host error: ${error.message}\n`) }
|
|
60
|
+
}
|
|
61
|
+
const flat = []
|
|
62
|
+
for (const [n, v] of answer.headers) flat.push(n, v)
|
|
63
|
+
try {
|
|
64
|
+
res.writeHead(answer.status, flat)
|
|
65
|
+
res.end(Buffer.from(answer.body))
|
|
66
|
+
} catch (error) {
|
|
67
|
+
onError(error)
|
|
68
|
+
if (!res.headersSent) { res.writeHead(502); res.end() } else res.destroy()
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
req.on('error', (error) => onError(error))
|
|
72
|
+
})
|
|
73
|
+
// Node gives CONNECT as its own event, with the raw socket. The request
|
|
74
|
+
// goes to the engine like any other (it answers 405); the answer is written
|
|
75
|
+
// on the socket, which is then closed. No tunnel is ever opened.
|
|
76
|
+
server.on('connect', async (req, socket) => {
|
|
77
|
+
const headers = []
|
|
78
|
+
for (let i = 0; i + 1 < req.rawHeaders.length; i += 2) headers.push([req.rawHeaders[i], req.rawHeaders[i + 1]])
|
|
79
|
+
let answer
|
|
80
|
+
try {
|
|
81
|
+
answer = await conn.request({ method: req.method, target: req.url, headers, body: new Uint8Array(0) })
|
|
82
|
+
} catch (error) {
|
|
83
|
+
onError(error)
|
|
84
|
+
answer = { status: 502, headers: [], body: new Uint8Array(0) }
|
|
85
|
+
}
|
|
86
|
+
const body = Buffer.from(answer.body)
|
|
87
|
+
const lines = [`HTTP/1.1 ${answer.status} ${http.STATUS_CODES[answer.status] || ''}`]
|
|
88
|
+
for (const [n, v] of answer.headers) lines.push(`${n}: ${v}`)
|
|
89
|
+
lines.push(`content-length: ${body.length}`, 'connection: close', '', '')
|
|
90
|
+
socket.end(Buffer.concat([Buffer.from(lines.join('\r\n'), 'latin1'), body]))
|
|
91
|
+
})
|
|
92
|
+
const port = typeof options.listenPort === 'number' ? options.listenPort : 8091
|
|
93
|
+
try {
|
|
94
|
+
await new Promise((resolve, reject) => {
|
|
95
|
+
server.once('error', reject)
|
|
96
|
+
server.listen(port, conn.listenHost, resolve)
|
|
97
|
+
})
|
|
98
|
+
} catch (error) {
|
|
99
|
+
await conn.close()
|
|
100
|
+
throw error
|
|
101
|
+
}
|
|
102
|
+
const addr = server.address()
|
|
103
|
+
conn.closed.then(() => server.close())
|
|
104
|
+
return {
|
|
105
|
+
origin: `http://${addr.address}:${addr.port}/`,
|
|
106
|
+
jid: conn.jid,
|
|
107
|
+
target: conn.target,
|
|
108
|
+
closed: conn.closed,
|
|
109
|
+
async close () {
|
|
110
|
+
server.close()
|
|
111
|
+
await conn.close()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|