@christianirack/brainllm-runtime 0.4.0 → 0.6.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-ai.js +15 -7
- package/brain-analytics.js +401 -0
- package/brain-budget.js +46 -25
- package/brain-ch.js +35 -8
- package/brain-data.js +4 -1
- package/brain-env.js +5 -4
- package/brain-guard.js +6 -6
- package/brain-live.js +367 -0
- package/brain-mcp.js +87 -18
- package/brain-outbound.js +244 -0
- package/brain-router.js +8 -12
- package/brain-update.js +37 -0
- package/brain-url.js +7 -5
- package/brain.js +6 -3
- package/package.json +1 -1
- package/server/native-sockets.mjs +156 -0
- package/server/server.mjs +319 -85
- package/sw/brain-sw.js +51 -2
- package/ui/error-state.brain +26 -13
- package/ui/tabs.brain +4 -3
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Fixed loopback Upgrade transport. Rust owns WebSocket frames, authentication and per-delivery policy.
|
|
2
|
+
import {request} from 'node:http'
|
|
3
|
+
import {createHash, randomUUID} from 'node:crypto'
|
|
4
|
+
|
|
5
|
+
const PROTOCOL = 'brain.v1'
|
|
6
|
+
const LIMITS = Object.freeze({connections:32, headers:16384, credentials:16384, head:4096, upstreamHead:8192, queued:262144, bytes:64*1024*1024, handshake:5000, write:2000, idle:70000, lifetime:15*60*1000, close:1000})
|
|
7
|
+
const SINGLE = new Set(['host','origin','authorization','cookie','upgrade','sec-websocket-key','sec-websocket-version','sec-websocket-protocol'])
|
|
8
|
+
const installed = new WeakSet()
|
|
9
|
+
const connectionUpgrade = value => typeof value === 'string' && value.toLowerCase().split(/\s*,\s*/).includes('upgrade')
|
|
10
|
+
const unique = (headers, names) => {
|
|
11
|
+
const seen = new Set()
|
|
12
|
+
for (let i=0;i<headers.length;i+=2) {
|
|
13
|
+
const name = headers[i].toLowerCase()
|
|
14
|
+
if (names.has(name) && seen.has(name)) return false
|
|
15
|
+
seen.add(name)
|
|
16
|
+
}
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function installNativeSockets(server, {target, base='/', cookie, log}) {
|
|
21
|
+
if (installed.has(server)) throw Error('The native socket bridge is already installed')
|
|
22
|
+
if (typeof target !== 'string' || target.length>2048) throw Error('Native sockets require a fixed loopback HTTP backend')
|
|
23
|
+
let origin
|
|
24
|
+
try { origin = new URL(target) } catch { throw Error('Native sockets require a fixed loopback HTTP backend') }
|
|
25
|
+
if (origin.protocol !== 'http:' || !['127.0.0.1','localhost','[::1]'].includes(origin.hostname) || origin.username || origin.password || origin.pathname !== '/' || origin.search || origin.hash) throw Error('Native sockets require a fixed loopback HTTP backend')
|
|
26
|
+
if (origin.hostname === 'localhost') origin.hostname = '127.0.0.1'
|
|
27
|
+
if (typeof base !== 'string' || base.length>2048 || !base.startsWith('/') || !base.endsWith('/') || /[\\?#\s]/.test(base) || base.includes('//') || new URL(base,'http://brain.invalid').pathname !== base) throw Error('Native sockets require a canonical application base path')
|
|
28
|
+
if (typeof cookie !== 'function' || typeof log !== 'function') throw Error('Native socket bridge callbacks are required')
|
|
29
|
+
const path = (base === '/' ? '' : base.slice(0,-1)) + '/_brain/socket'
|
|
30
|
+
const owned = new Set()
|
|
31
|
+
let stopped = false, window = 0, reports = 0
|
|
32
|
+
// Fixed fields only: never log target URLs, credentials, frames, request headers or exception messages.
|
|
33
|
+
const report = (code,rid) => {
|
|
34
|
+
const current = Math.floor(Date.now()/60000)
|
|
35
|
+
if (window !== current) { window=current; reports=0 }
|
|
36
|
+
if (reports++ >= 16) return
|
|
37
|
+
try { log('warn','native socket unavailable',{code,rid,fix:'Check native socket activation, the trusted Origin, authentication and backend availability; reconnect after correcting the cause'}) } catch {}
|
|
38
|
+
}
|
|
39
|
+
const reject = (socket,status,rid,code) => {
|
|
40
|
+
report(code,rid)
|
|
41
|
+
if (socket.destroyed) return
|
|
42
|
+
socket.setTimeout(LIMITS.close,()=>socket.destroy())
|
|
43
|
+
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())
|
|
44
|
+
}
|
|
45
|
+
const upgrade = (incoming,browser,head) => {
|
|
46
|
+
const ownedPath=typeof incoming.url === 'string' && incoming.url.split(/[?#]/,1)[0] === path
|
|
47
|
+
// Other trusted listeners own their endpoints. If this is the only listener, reject unknown upgrades promptly
|
|
48
|
+
// instead of retaining a raw socket with no owner. Queries on our exact endpoint always remain ours to reject.
|
|
49
|
+
if (!ownedPath && server.listenerCount('upgrade')>1) return
|
|
50
|
+
if (browser.destroyed) return
|
|
51
|
+
const rid = randomUUID()
|
|
52
|
+
// A socket error can precede admission, including while a rejected response is flushing.
|
|
53
|
+
browser.on('error',()=>{})
|
|
54
|
+
if (stopped || server.draining) return reject(browser,503,rid,'NATIVE_SOCKET_DRAINING')
|
|
55
|
+
if (incoming.method !== 'GET') return reject(browser,405,rid,'NATIVE_SOCKET_METHOD')
|
|
56
|
+
if (incoming.url !== path) return reject(browser,400,rid,'NATIVE_SOCKET_PATH')
|
|
57
|
+
const headerBytes = incoming.rawHeaders.reduce((bytes,value)=>bytes+Buffer.byteLength(value)+2,0)
|
|
58
|
+
if (headerBytes>LIMITS.headers || !unique(incoming.rawHeaders,SINGLE)) return reject(browser,431,rid,'NATIVE_SOCKET_HEADERS')
|
|
59
|
+
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
|
+
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
|
+
const key = incoming.headers['sec-websocket-key']
|
|
64
|
+
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
|
+
let credentials
|
|
66
|
+
try { credentials=cookie(incoming) } catch { return reject(browser,400,rid,'NATIVE_SOCKET_CREDENTIALS') }
|
|
67
|
+
const authorization = incoming.headers.authorization
|
|
68
|
+
if (typeof credentials !== 'string' || authorization !== undefined && typeof authorization !== 'string' || Buffer.byteLength(credentials)+Buffer.byteLength(authorization || '')>LIMITS.credentials) return reject(browser,431,rid,'NATIVE_SOCKET_CREDENTIALS')
|
|
69
|
+
if (owned.size>=LIMITS.connections) return reject(browser,503,rid,'NATIVE_SOCKET_CAPACITY')
|
|
70
|
+
browser.pause()
|
|
71
|
+
let outgoing, upstream, handshake, lifetime, closing, bytes=0, done=false, upgraded=false
|
|
72
|
+
const writes=new Set()
|
|
73
|
+
const finish = (code,status) => {
|
|
74
|
+
if (done) return
|
|
75
|
+
done=true
|
|
76
|
+
clearTimeout(handshake); clearTimeout(lifetime); clearTimeout(closing); owned.delete(finish)
|
|
77
|
+
for (const timer of writes) clearTimeout(timer)
|
|
78
|
+
writes.clear()
|
|
79
|
+
browser.setTimeout(0)
|
|
80
|
+
if (upstream) { upstream.setTimeout(0); browser.unpipe(upstream); upstream.unpipe(browser); upstream.destroy() }
|
|
81
|
+
outgoing?.destroy()
|
|
82
|
+
if (status && !upgraded) reject(browser,status,rid,code)
|
|
83
|
+
else { if (code) report(code,rid); browser.destroy() }
|
|
84
|
+
}
|
|
85
|
+
const relay = (source,destination) => {
|
|
86
|
+
let blocked
|
|
87
|
+
destination.on('drain',()=>{if(blocked){clearTimeout(blocked);writes.delete(blocked);blocked=undefined}})
|
|
88
|
+
source.on('data',chunk=>{
|
|
89
|
+
bytes+=chunk.length
|
|
90
|
+
if (bytes>LIMITS.bytes || destination.writableLength+chunk.length>LIMITS.queued) finish('NATIVE_SOCKET_WRITE_BUDGET')
|
|
91
|
+
})
|
|
92
|
+
source.once('end',()=>{
|
|
93
|
+
if (done) return
|
|
94
|
+
closing ??= setTimeout(()=>finish('NATIVE_SOCKET_HALF_CLOSE'),LIMITS.close)
|
|
95
|
+
closing.unref?.()
|
|
96
|
+
destination.once('finish',()=>finish())
|
|
97
|
+
})
|
|
98
|
+
// Native pipe honors write(false)/drain. highWaterMark alone is a threshold, so the explicit queued cap above
|
|
99
|
+
// also stops a future/custom stream chunk that exceeds the admitted write budget.
|
|
100
|
+
source.pipe(destination)
|
|
101
|
+
// Observe after pipe's synchronous write so an unresponsive destination cannot retain its bounded queue
|
|
102
|
+
// until the much longer idle/lifetime limits. There is at most one stalled-write timer per direction.
|
|
103
|
+
source.on('data',()=>{
|
|
104
|
+
if (!done && destination.writableNeedDrain && !blocked) {
|
|
105
|
+
blocked=setTimeout(()=>finish('NATIVE_SOCKET_WRITE_TIMEOUT'),LIMITS.write)
|
|
106
|
+
blocked.unref?.();writes.add(blocked)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
owned.add(finish)
|
|
111
|
+
browser.once('error',()=>finish('NATIVE_SOCKET_CLIENT_ERROR'))
|
|
112
|
+
browser.once('close',()=>finish())
|
|
113
|
+
browser.setTimeout(LIMITS.idle,()=>finish('NATIVE_SOCKET_IDLE'))
|
|
114
|
+
handshake=setTimeout(()=>finish('NATIVE_SOCKET_HANDSHAKE_TIMEOUT',504),LIMITS.handshake)
|
|
115
|
+
handshake.unref?.()
|
|
116
|
+
try {
|
|
117
|
+
outgoing=request(new URL('/_brain/socket',origin),{
|
|
118
|
+
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} : {})},
|
|
120
|
+
})
|
|
121
|
+
outgoing.once('error',()=>finish('NATIVE_SOCKET_UPSTREAM_UNAVAILABLE',502))
|
|
122
|
+
outgoing.once('response',response=>{
|
|
123
|
+
response.destroy()
|
|
124
|
+
finish('NATIVE_SOCKET_UPSTREAM_REJECTED',[400,401,403,404,429,503].includes(response.statusCode) ? response.statusCode : 502)
|
|
125
|
+
})
|
|
126
|
+
outgoing.once('upgrade',(response,socket,buffered)=>{
|
|
127
|
+
if (done) { socket.destroy(); return }
|
|
128
|
+
upstream=socket
|
|
129
|
+
socket.on('error',()=>finish('NATIVE_SOCKET_UPSTREAM_ERROR'))
|
|
130
|
+
socket.once('close',()=>finish())
|
|
131
|
+
const expected=createHash('sha1').update(key+'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64')
|
|
132
|
+
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
|
+
clearTimeout(handshake)
|
|
134
|
+
lifetime=setTimeout(()=>finish('NATIVE_SOCKET_LIFETIME'),LIMITS.lifetime)
|
|
135
|
+
lifetime.unref?.()
|
|
136
|
+
socket.setTimeout(LIMITS.idle,()=>finish('NATIVE_SOCKET_IDLE'))
|
|
137
|
+
upgraded=true
|
|
138
|
+
browser.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${expected}\r\nSec-WebSocket-Protocol: ${PROTOCOL}\r\nCache-Control: no-store\r\nX-Request-Id: ${rid}\r\n\r\n`)
|
|
139
|
+
bytes=buffered.length+head.length
|
|
140
|
+
if (buffered.length) browser.write(buffered)
|
|
141
|
+
if (head.length) socket.write(head)
|
|
142
|
+
relay(browser,socket); relay(socket,browser); browser.resume()
|
|
143
|
+
})
|
|
144
|
+
outgoing.end()
|
|
145
|
+
} catch { finish('NATIVE_SOCKET_UPSTREAM_UNAVAILABLE',502) }
|
|
146
|
+
}
|
|
147
|
+
const closeOwned = () => { for (const finish of [...owned]) finish() }
|
|
148
|
+
const stop = () => { if (stopped) return; stopped=true; closeOwned() }
|
|
149
|
+
installed.add(server)
|
|
150
|
+
server.on('upgrade',upgrade)
|
|
151
|
+
server.once('close',()=>{stop();server.removeListener('upgrade',upgrade)})
|
|
152
|
+
const originalClose=server.close, originalAll=server.closeAllConnections
|
|
153
|
+
server.close=function(...args){stop();return originalClose.apply(this,args)}
|
|
154
|
+
if (originalAll) server.closeAllConnections=function(...args){closeOwned();return originalAll.apply(this,args)}
|
|
155
|
+
return stop
|
|
156
|
+
}
|