@christianirack/brainllm-runtime 0.6.1 → 0.8.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/brain-data.js +5 -2
- package/brain-env.js +8 -6
- package/brain-form.js +46 -36
- package/brain-inspect.js +19 -6
- package/brain-island.js +2 -1
- package/brain-lazy.js +2 -0
- package/brain-links.js +37 -0
- package/brain-live.js +21 -5
- package/brain-observe.js +120 -0
- package/brain-router.js +224 -178
- package/brain-schema.js +41 -9
- package/brain-trace.js +269 -0
- package/brain-vitals.js +60 -0
- package/package.json +1 -1
- package/server/llm.mjs +199 -0
- package/server/native-sockets.mjs +13 -10
- package/server/server.mjs +663 -105
- package/shim/dom.js +42 -7
- package/sw/brain-sw.js +17 -9
- package/ui/error-state.brain +1 -1
|
@@ -17,7 +17,7 @@ const unique = (headers, names) => {
|
|
|
17
17
|
return true
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export function installNativeSockets(server, {target, base='/', cookie, log}) {
|
|
20
|
+
export function installNativeSockets(server, {target, base='/', cookie, client, log}) {
|
|
21
21
|
if (installed.has(server)) throw Error('The native socket bridge is already installed')
|
|
22
22
|
if (typeof target !== 'string' || target.length>2048) throw Error('Native sockets require a fixed loopback HTTP backend')
|
|
23
23
|
let origin
|
|
@@ -29,15 +29,17 @@ export function installNativeSockets(server, {target, base='/', cookie, log}) {
|
|
|
29
29
|
const path = (base === '/' ? '' : base.slice(0,-1)) + '/_brain/socket'
|
|
30
30
|
const owned = new Set()
|
|
31
31
|
let stopped = false, window = 0, reports = 0
|
|
32
|
-
// Fixed fields only: never log target URLs, credentials, frames
|
|
33
|
-
|
|
32
|
+
// Fixed fields only: never log target URLs, credentials, frames or exception messages. The one request header that
|
|
33
|
+
// is logged is a refused Origin, bounded, because it is the whole evidence of that refusal (B0743).
|
|
34
|
+
const report = (code,rid,origin) => {
|
|
34
35
|
const current = Math.floor(Date.now()/60000)
|
|
35
36
|
if (window !== current) { window=current; reports=0 }
|
|
36
37
|
if (reports++ >= 16) return
|
|
37
|
-
|
|
38
|
+
const refused = code === 'NATIVE_SOCKET_ORIGIN' ? {brainCode:'B0743',seenOrigin:typeof origin === 'string' ? origin.slice(0,256) : null,fix:'This Origin is not one canonical http(s) origin. Open the site at an exact origin listed in brain.json backend.sockets.allowedOrigins (never a wildcard). Run brain explain B0743'} : {fix:'Check native socket activation, the trusted Origin, authentication and backend availability; reconnect after correcting the cause'}
|
|
39
|
+
try { log('warn','native socket unavailable',{code,rid,...refused}) } catch {}
|
|
38
40
|
}
|
|
39
|
-
const reject = (socket,status,rid,code) => {
|
|
40
|
-
report(code,rid)
|
|
41
|
+
const reject = (socket,status,rid,code,origin) => {
|
|
42
|
+
report(code,rid,origin)
|
|
41
43
|
if (socket.destroyed) return
|
|
42
44
|
socket.setTimeout(LIMITS.close,()=>socket.destroy())
|
|
43
45
|
socket.end(`HTTP/1.1 ${status} Socket unavailable\r\nConnection: close\r\nCache-Control: no-store\r\nX-Request-Id: ${rid}\r\nContent-Length: 0\r\n\r\n`,()=>socket.destroy())
|
|
@@ -58,8 +60,8 @@ export function installNativeSockets(server, {target, base='/', cookie, log}) {
|
|
|
58
60
|
if (headerBytes>LIMITS.headers || !unique(incoming.rawHeaders,SINGLE)) return reject(browser,431,rid,'NATIVE_SOCKET_HEADERS')
|
|
59
61
|
if (head.length>LIMITS.head || incoming.headers['content-length'] && incoming.headers['content-length'] !== '0' || incoming.headers['transfer-encoding']) return reject(browser,400,rid,'NATIVE_SOCKET_BODY')
|
|
60
62
|
let site
|
|
61
|
-
try { site=new URL(incoming.headers.origin) } catch { return reject(browser,403,rid,'NATIVE_SOCKET_ORIGIN') }
|
|
62
|
-
if (!['http:','https:'].includes(site.protocol) || site.origin !== incoming.headers.origin || site.origin.length>2048) return reject(browser,403,rid,'NATIVE_SOCKET_ORIGIN')
|
|
63
|
+
try { site=new URL(incoming.headers.origin) } catch { return reject(browser,403,rid,'NATIVE_SOCKET_ORIGIN',incoming.headers.origin) }
|
|
64
|
+
if (!['http:','https:'].includes(site.protocol) || site.origin !== incoming.headers.origin || site.origin.length>2048) return reject(browser,403,rid,'NATIVE_SOCKET_ORIGIN',incoming.headers.origin)
|
|
63
65
|
const key = incoming.headers['sec-websocket-key']
|
|
64
66
|
if (incoming.headers.upgrade?.toLowerCase() !== 'websocket' || !connectionUpgrade(incoming.headers.connection) || incoming.headers['sec-websocket-version'] !== '13' || incoming.headers['sec-websocket-protocol'] !== PROTOCOL || typeof key !== 'string' || !/^[A-Za-z0-9+/]{22}==$/.test(key) || Buffer.from(key,'base64').toString('base64') !== key) return reject(browser,400,rid,'NATIVE_SOCKET_HANDSHAKE')
|
|
65
67
|
let credentials
|
|
@@ -116,7 +118,7 @@ export function installNativeSockets(server, {target, base='/', cookie, log}) {
|
|
|
116
118
|
try {
|
|
117
119
|
outgoing=request(new URL('/_brain/socket',origin),{
|
|
118
120
|
method:'GET',agent:false,maxHeaderSize:8192,
|
|
119
|
-
headers:{host:origin.host,origin:site.origin,connection:'Upgrade',upgrade:'websocket','sec-websocket-version':'13','sec-websocket-key':key,'sec-websocket-protocol':PROTOCOL,'x-request-id':rid,...(credentials ? {cookie:credentials} : {}),...(authorization !== undefined ? {authorization} : {})},
|
|
121
|
+
headers:{host:origin.host,origin:site.origin,connection:'Upgrade',upgrade:'websocket','sec-websocket-version':'13','sec-websocket-key':key,'sec-websocket-protocol':PROTOCOL,'x-request-id':rid,...(credentials ? {cookie:credentials} : {}),...(authorization !== undefined ? {authorization} : {}),...(client?.(incoming) ? {'x-forwarded-for':client(incoming)} : {})},
|
|
120
122
|
})
|
|
121
123
|
outgoing.once('error',()=>finish('NATIVE_SOCKET_UPSTREAM_UNAVAILABLE',502))
|
|
122
124
|
outgoing.once('response',response=>{
|
|
@@ -127,7 +129,8 @@ export function installNativeSockets(server, {target, base='/', cookie, log}) {
|
|
|
127
129
|
if (done) { socket.destroy(); return }
|
|
128
130
|
upstream=socket
|
|
129
131
|
socket.on('error',()=>finish('NATIVE_SOCKET_UPSTREAM_ERROR'))
|
|
130
|
-
|
|
132
|
+
// After a clean EOF the browser may still hold queued bytes: its own finish (or the bounded close timer) ends the tunnel.
|
|
133
|
+
socket.once('close',()=>{if (!socket.readableEnded || browser.writableFinished) finish()})
|
|
131
134
|
const expected=createHash('sha1').update(key+'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64')
|
|
132
135
|
if (response.statusCode !== 101 || !unique(response.rawHeaders,new Set(['upgrade','sec-websocket-accept','sec-websocket-protocol'])) || response.headers.upgrade?.toLowerCase() !== 'websocket' || !connectionUpgrade(response.headers.connection) || response.headers['sec-websocket-accept'] !== expected || response.headers['sec-websocket-protocol'] !== PROTOCOL || response.headers['sec-websocket-extensions'] || buffered.length>LIMITS.upstreamHead) return finish('NATIVE_SOCKET_BAD_UPSTREAM_HANDSHAKE',502)
|
|
133
136
|
clearTimeout(handshake)
|