@lensmcp/bridge 1.25.37 → 1.25.39
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/lib/env-sink.js +3 -3
- package/lib/envelope-contract.js +1 -1
- package/lib/event-mappers.js +3 -3
- package/lib/origin-policy.js +1 -1
- package/lib/replay-ingest.js +2 -2
- package/lib/server.js +1 -1
- package/main.js +1 -1
- package/package.json +3 -3
package/lib/env-sink.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var _=Object.defineProperty;var f=(t,n)=>_(t,"name",{value:n,configurable:!0});var
|
|
2
|
-
`;
|
|
3
|
-
`);n.length>1e4;)n.shift();if(e&&e.writable){i();return}if(e){try{e.destroy()}catch{}e=null}
|
|
1
|
+
"use strict";var _=Object.defineProperty;var f=(t,n)=>_(t,"name",{value:n,configurable:!0});var b=Object.defineProperty,r=f((t,n)=>b(t,"name",{value:n,configurable:!0}),"r");import{connect as P}from"node:net";import{createSocket as A}from"node:dgram";import{appendFileSync as M,closeSync as T,openSync as F,renameSync as L,statSync as O,unlinkSync as x}from"node:fs";import{busCapsFromEnv as R,createBusRotator as I}from"@lensmcp/core/bus-rotation";import{ENV_SINK_CLOSE_GRACE_MS as B}from"./constants.js";const D=R(),d=(()=>{const t=process.env.LENSMCP_EVENT_MAX_EPS;if(t==="off")return 1/0;const n=Number(t);return t!==void 0&&t!==""&&Number.isFinite(n)?n<=0?1/0:n:5})(),y=Math.max(30,d===1/0?0:d*4),S=new Map;function k(t){if(d===1/0)return 0;const n=Date.now();let e=S.get(t);if(!e){if(S.size>=2048){for(const[s,o]of S)n-o.lastSeenAt>6e4&&S.delete(s);S.size>=2048&&S.clear()}e={tokens:y,lastRefillAt:n,suppressed:0,lastSeenAt:n},S.set(t,e)}if(e.lastSeenAt=n,e.tokens=Math.min(y,e.tokens+(n-e.lastRefillAt)/1e3*d),e.lastRefillAt=n,e.tokens>=1){e.tokens-=1;const s=e.suppressed;return e.suppressed=0,s}return e.suppressed+=1,-1}f(k,"B"),r(k,"floodAdmit");let u,m;export function resolveEnvSink(){if(u!==void 0)return u;const t=process.env.LENSMCP_EVENT_FILE;if(t){const s=I(t,{...D,fs:{statSync:O,renameSync:L,unlinkSync:x,openSync:F,closeSync:T}});return u=r(o=>{try{const c=k(o.fingerprint);if(c<0)return;const i=JSON.stringify(c>0?{...o,raw:{...o.raw??{},floodSuppressed:c}}:o)+`
|
|
2
|
+
`;s(i.length),M(t,i)}catch{}},"cachedEnvSink"),u}const n=process.env.LENSMCP_UDS;if(n){const s=v(n);return u=s.write,m=s.close,u}const e=process.env.LENSMCP_IPC_SOCKET;if(e){const[s,o]=e.split(":"),c=Number(o);if(s&&Number.isFinite(c)){const i=w(s,c);return u=i.write,m=i.close,u}}return u=null,null}f(resolveEnvSink,"resolveEnvSink"),r(resolveEnvSink,"resolveEnvSink");export function resetEnvSinkCache(){u=void 0,m=void 0}f(resetEnvSinkCache,"resetEnvSinkCache"),r(resetEnvSinkCache,"resetEnvSinkCache");export function closeEnvSink(t=B){const n=m;return resetEnvSinkCache(),n?n(t):Promise.resolve()}f(closeEnvSink,"closeEnvSink"),r(closeEnvSink,"closeEnvSink");export function udsEnvSink(t){return v(t).write}f(udsEnvSink,"udsEnvSink"),r(udsEnvSink,"udsEnvSink");function v(t){const n=[];let e=null,s=!1,o,c=!1;const i=r(()=>{if(!(!e||!e.writable))for(;n.length>0;)e.write(n.shift())},"flush"),a=r(()=>{if(s||e)return;s=!0;const l=P(t);l.on("connect",()=>{s=!1,e=l,i(),o?.(!0)}),l.on("drain",i),l.on("error",()=>{s=!1,e=null,o?.(!1)}),l.on("close",()=>{e=null}),l.unref()},"open");return a(),{write:r(l=>{if(!c){for(n.push(JSON.stringify(l)+`
|
|
3
|
+
`);n.length>1e4;)n.shift();if(e&&e.writable){i();return}if(e){try{e.destroy()}catch{}e=null}a()}},"write"),close:r(l=>new Promise(N=>{c=!0;let E=!1;const p=r(()=>{if(!E){E=!0,clearTimeout(g),o=void 0,n.length=0;try{e?.destroy()}catch{}e=null,N()}},"finish"),g=setTimeout(p,l),h=r(()=>{if(!e)return p();i(),e.end(p)},"endWhenFlushed");e?.writable?h():n.length===0?p():(o=r(C=>C?h():p(),"onConnectSettled"),a())}),"close")}}f(v,"y"),r(v,"openUdsSink");function w(t,n){const e=A("udp4");e.unref();let s=0,o;return{write:r(c=>{try{const i=Buffer.from(JSON.stringify(c));e.send(i,n,t,()=>{s-=1,s===0&&o?.()}),s+=1}catch{}},"write"),close:r(c=>new Promise(i=>{const a=r(()=>{clearTimeout(l),o=void 0;try{e.close()}catch{}i()},"finish"),l=setTimeout(a,c);s===0?a():o=a}),"close")}}f(w,"D"),r(w,"openUdpSink");
|
package/lib/envelope-contract.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var d=Object.defineProperty;var a=(t,e)=>d(t,"name",{value:e,configurable:!0});var g=Object.defineProperty,s=a((t,e)=>g(t,"name",{value:e,configurable:!0}),"r");import{BaseEventSchema as f,TraceContextSchema as l}from"@lensmcp/protocol-types";const r=s(t=>e=>t.safeParse(e).success,"conforms"),n=s(t=>typeof t=="string","isString"),p=s((...t)=>e=>typeof e=="string"&&t.includes(e),"oneOf"),o=f.shape,m={at:r(o.timestamp),tabId:r(l.shape.tabId)},v={error:{},"resource-error":{tagName:n},unhandledrejection:{reason:n},console:{level:p("log","info","warn","error","debug"),args:Array.isArray},fetch:{method:n,url:n},route:{url:n},ready:{userAgent:n,url:n},hmr:{event:p("full-reload","update","invalidate","ws-disconnect","ws-connect","ws-failed")},gone:{},publish:{source:r(o.source),category:r(o.category),severity:r(o.severity.optional()),title:r(o.title),message:r(o.message),fingerprint:r(o.fingerprint.optional()),context:r(l.partial().optional())}},y=new Map(Object.entries(v));export function conformsToEnvelopeContract(t){const e=t,i=e.kind,c=typeof i=="string"?y.get(i):void 0;return c?u(e,m)&&u(e,c):!1}a(conformsToEnvelopeContract,"conformsToEnvelopeContract"),s(conformsToEnvelopeContract,"conformsToEnvelopeContract");export function asBaseEvent(t){const e=f.safeParse(t);return e.success?e.data:void 0}a(asBaseEvent,"asBaseEvent"),s(asBaseEvent,"asBaseEvent");function u(t,e){for(const[i,c]of Object.entries(e))if(!c(t[i]))return!1;return!0}a(u,"l"),s(u,"satisfies");
|
package/lib/event-mappers.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)[0]??
|
|
3
|
-
`).slice(0,2);for(const s of
|
|
1
|
+
"use strict";var I=Object.defineProperty;var c=(e,n)=>I(e,"name",{value:n,configurable:!0});var x=Object.defineProperty,d=c((e,n)=>x(e,"name",{value:n,configurable:!0}),"u");import{fingerprint as o,ulid as a}from"@lensmcp/core";import{redactDeep as y,redactText as m,redactUrl as g,safeText as u,safeTitle as l}from"./redact.js";const b="pending";export function envelopeToEvent(e,n=b){const s=e.tabId?{tabId:e.tabId}:{};switch(e.kind){case"error":{const r=!e.stack&&!e.source&&(!e.message||e.message==="Script error."),i=e.name&&e.name!=="Error"?`${e.name}: `:"",t=r?"Uncaught cross-origin script error (no detail available \u2014 add crossorigin to the script tag)":e.message?`${i}${e.message}`:`Uncaught ${e.name??"error"}`;return{id:a(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"error",context:{sessionId:n,...s},fingerprint:o({kind:"runtime",identity:`error:${h(e.stack)}`,file:e.source,detail:e.message??e.name}),title:l(t),message:u(e.stack??e.message),location:e.source?{file:g(e.source),line:e.lineno,column:e.colno}:void 0,raw:{...e,...e.message?{message:m(e.message)}:{},...e.stack?{stack:u(e.stack)}:{},...e.source?{source:g(e.source)}:{}}}}case"resource-error":{const r=e.url?g(e.url):void 0;return{id:a(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"error",context:{sessionId:n,...s},fingerprint:o({kind:"runtime",identity:`resource-error:${e.tagName}:${v(e.url??"")}`}),title:l(`Failed to load <${e.tagName}>${r?` ${r}`:""}`),...r?{relatedUrls:[r]}:{},raw:{...e,...r?{url:r}:{}}}}case"unhandledrejection":{const r=e.name&&e.name!=="Error"?`${e.name}: `:"",i=e.reason.split(`
|
|
2
|
+
`)[0]??e.reason;return{id:a(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"error",context:{sessionId:n,...s},fingerprint:o({kind:"runtime",identity:`unhandledrejection:${e.reason.slice(0,80)}`}),title:l(`Unhandled promise rejection: ${r}${i}`),message:u(e.stack??e.reason),raw:{...e,reason:m(e.reason),...e.stack?{stack:u(e.stack)}:{}}}}case"console":{const r=e.level==="error"?"error":e.level==="warn"?"warning":"info",i=y(e.args),t=i.map(k).join(" ");return{id:a(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:r,context:{sessionId:n,...s},fingerprint:o({kind:"runtime",identity:`console:${e.level}:${t.slice(0,80)}`}),title:`console.${e.level}`,message:u(t),raw:{...e,args:i}}}case"fetch":{const r=e.error!==void 0||e.status!==void 0&&e.status>=400,i=g(e.url),t=e.status!==void 0?` \u2192 ${e.status}${e.statusText?` ${e.statusText}`:""}`:"";return{id:a(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"network",severity:r?"error":"info",context:{sessionId:n,...s},fingerprint:o({kind:"network",identity:`${e.method}:${v(e.url)}:${e.status??"err"}`}),title:l(`${e.method} ${i}${t}`),message:u(e.error),relatedUrls:[i],raw:{...e,url:i}}}case"route":return{id:a(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"info",context:{sessionId:n,url:e.url,...s},fingerprint:o({kind:"runtime",identity:`route:${e.url}`}),title:`Route \u2192 ${e.url}`,raw:e};case"ready":return{id:a(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"info",context:{sessionId:n,url:e.url,...s},fingerprint:o({kind:"runtime",identity:"client-ready"}),title:"Client runtime ready",message:e.reloadCause?`${e.userAgent} \u2014 after a Vite full reload${e.reloadCause.triggeredBy?` triggered by ${u(m(e.reloadCause.triggeredBy))}`:""}`:e.userAgent,raw:e};case"hmr":{const r=e.triggeredBy?m(e.triggeredBy):void 0,i=e.path?m(e.path):void 0,t=e.event==="full-reload"?`full-reload:${r??i??"?"}`:e.event==="invalidate"?`invalidate:${i??"?"}`:e.event,p=e.event==="full-reload"?`Vite full reload${r?` \u2014 ${l(r)} could not hot-update`:i?` \u2014 ${l(i)}`:""}`:e.event==="update"?`Vite HMR update (${e.count??e.updates?.length??0} module${(e.count??e.updates?.length??0)===1?"":"s"})`:e.event==="invalidate"?`Vite HMR invalidate \u2014 ${l(i??"?")}`:e.event==="ws-disconnect"?"Vite HMR socket lost \u2014 the page will poll and reload":e.event==="ws-failed"?e.recovering?"Vite HMR socket failed to connect \u2014 the page reloads once to get one":"Vite HMR socket failed to connect \u2014 this page gets no hot updates until it is reloaded":"Vite HMR socket connected";return{id:a(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"build",severity:e.event==="full-reload"||e.event==="ws-disconnect"||e.event==="ws-failed"?"warning":"info",context:{sessionId:n,...s},fingerprint:o({kind:"hmr",identity:t}),title:p,...e.message?{message:u(m(e.message))}:{},...r||i?{location:{file:r??i??""}}:{},...e.updates?.length?{relatedFiles:e.updates.map(f=>m(f))}:{},raw:{...e,...r?{triggeredBy:r}:{},...i?{path:i}:{}}}}case"gone":return{id:a(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"info",context:{sessionId:n,...s},fingerprint:o({kind:"runtime",identity:"client-gone"}),title:"Client runtime gone",raw:e};case"publish":{if(!w(e.source))return;const r=$(e.category,e.raw),i=r&&r.status!==void 0&&r.status>=400?"error":e.severity??"info",t=d(f=>r&&r.url!==r.redactedUrl?f.split(r.url).join(r.redactedUrl):f,"scrub"),p=t(e.title);return{id:a(),sessionId:n,timestamp:e.at,source:e.source,category:e.category,severity:i,context:{...s,...e.context??{},sessionId:n},fingerprint:e.fingerprint?t(e.fingerprint):o({kind:"publish",identity:p}),title:l(p),message:u(e.message),...r?{relatedUrls:[r.redactedUrl]}:{},raw:r?{...y(e.raw),url:r.redactedUrl}:y(e.raw)}}}}c(envelopeToEvent,"envelopeToEvent"),d(envelopeToEvent,"envelopeToEvent");function $(e,n){if(e!=="network"||!n||typeof n!="object")return;const s=n;if(!(typeof s.url!="string"||s.url.length===0))return{url:s.url,redactedUrl:g(s.url),...typeof s.status=="number"?{status:s.status}:{}}}c($,"k"),d($,"networkDetails");function h(e){if(!e)return"unknown";const n=e.split(`
|
|
3
|
+
`).slice(0,2);for(const s of n){const r=s.match(/at\s+(\S+)/);if(r&&r[1])return r[1]}return"unknown"}c(h,"w"),d(h,"normaliseStackTopFrame");function v(e){try{return`${new URL(e,"http://x").pathname}`}catch{return e}}c(v,"y"),d(v,"normaliseUrl");function k(e){if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}c(k,"x"),d(k,"stringify");const U=new Set(["react","valtio","memory","client-runtime"]);function w(e){return typeof e=="string"&&U.has(e)}c(w,"b"),d(w,"isBrowserPublishableSource");
|
package/lib/origin-policy.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var f=Object.defineProperty;var i=(t,e)=>f(t,"name",{value:e,configurable:!0});var d=Object.defineProperty,s=i((t,e)=>d(t,"name",{value:e,configurable:!0}),"s");import{capMap as h}from"@lensmcp/protocol-types";export const MAX_LEARNED_ORIGINS=64;export function createOriginPolicy(t=[]){let e=!1,n=!1;const u=[];for(const r of t){const o=r.trim().toLowerCase();if(o)if(o==="*")e=!0;else if(o==="null")n=!0;else{const a=p(o);a&&u.push(a)}}const c=new Map;return{admits(r){if(r===void 0)return!0;if(r==="null")return n;const o=l(r);return o?e||isLoopbackHostname(o.hostname)||c.has(o.origin)||u.some(a=>a(o)):!1},learn(r){const o=l(r);o&&(c.delete(o.origin),c.set(o.origin,!0),h(c,MAX_LEARNED_ORIGINS))}}}i(createOriginPolicy,"createOriginPolicy"),s(createOriginPolicy,"createOriginPolicy");export function parseAllowedOrigins(t){return(t??"").split(/[\s,]+/).filter(e=>e.length>0)}i(parseAllowedOrigins,"parseAllowedOrigins"),s(parseAllowedOrigins,"parseAllowedOrigins");export function isLoopbackHostname(t){const e=t.toLowerCase();return e==="localhost"||e.endsWith(".localhost")||e==="[::1]"||/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(e)}i(isLoopbackHostname,"isLoopbackHostname"),s(isLoopbackHostname,"isLoopbackHostname");function l(t){try{const e=new URL(t);return e.protocol==="http:"||e.protocol==="https:"?e:void 0}catch{return}}i(l,"l"),s(l,"parseOrigin");function p(t){if(t.includes("://")){const e=l(t)?.origin;return e?n=>n.origin===e:void 0}if(t.startsWith(".")){const e=t.slice(1);return e?n=>n.hostname===e||n.hostname.endsWith(t):void 0}return e=>e.hostname===t}i(p,"d"),s(p,"ruleFor");
|
package/lib/replay-ingest.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`,{mode:384}),f.chunks+=1,f.bytes+=N,f.lastAt=Math.max(f.lastAt,r.t1),r.base&&(f.base=r.base);for(const l of r.blobs)f.blobs.includes(l)||f.blobs.push(l);const
|
|
1
|
+
"use strict";var G=Object.defineProperty;var B=(e,o)=>G(e,"name",{value:o,configurable:!0});var U=Object.defineProperty,u=B((e,o)=>U(e,"name",{value:o,configurable:!0}),"d");import{createHash as V}from"node:crypto";import{appendFile as W,mkdir as q,readFile as C,rename as F,rm as K,stat as Q,writeFile as _}from"node:fs/promises";import{promisify as X}from"node:util";import{gunzip as ee}from"node:zlib";import{dirname as te,join as y}from"node:path";import{replayStoreFromEnv as re}from"@lensmcp/core/replay-store";import{ulid as ae}from"@lensmcp/core";const oe=X(ee);export{re as replayStoreFromEnv};export const REPLAY_WIRE_VERSION=1,REPLAY_INGEST_LIMITS={bodyBytes:32*1024*1024,loadBytesPerMinute:64*1024*1024,bridgeBytesPerMinute:256*1024*1024};const E=/^[0-9a-f]{64}$/,ne=/^load-[a-z0-9]{1,16}-[a-z0-9]{1,16}$/,se=/^[A-Za-z0-9_-]{1,64}$/,ie=/^\d{1,15}$/,ce=/^[A-Za-z0-9@/._-]{1,100}$/,de=/^[frv]:[^,\s]{1,200}$/,le=256;function D(e){const o=e.get("v");if(o!==String(REPLAY_WIRE_VERSION))return`unsupported replay wire version ${o??"(none)"}`;const i=e.get("load")??"",z=e.get("tab")??"";if(!ne.test(i))return"bad load";if(!se.test(z))return"bad tab";for(const p of["seq","t0","t1","n"])if(!ie.test(e.get(p)??""))return`bad ${p}`;const m=e.get("enc");if(m!=="gzip"&&m!=="identity")return"bad enc";const b=e.get("full");if(b!=="0"&&b!=="1")return"bad full";const P=e.get("app")??void 0;if(P!==void 0&&!ce.test(P))return"bad app";const w=e.get("base")??void 0;if(w!==void 0&&!E.test(w))return"bad base";const v=(e.get("a")??"").split(",").filter(Boolean);if(v.length>1e3||v.some(p=>!de.test(p)))return"bad anchors";const S=(e.get("b")??"").split(",").filter(Boolean);return S.length>le||S.some(p=>!E.test(p))?"bad blobs":{load:i,tab:z,seq:Number(e.get("seq")),t0:Number(e.get("t0")),t1:Number(e.get("t1")),n:Number(e.get("n")),full:b==="1",enc:m,...P?{app:P}:{},...w?{base:w}:{},anchors:v,more:e.get("am")==="1",blobs:S}}B(D,"it"),u(D,"chunkParams");class L{static{B(this,"L")}static{u(this,"MinuteBudget")}constructor(o){this.max=o,this.start=0,this.used=0}take(o,i){return i-this.start>=6e4&&(this.start=i,this.used=0),this.used+o>this.max?!1:(this.used+=o,!0)}}export function createReplayIngest(e){const o={...REPLAY_INGEST_LIMITS,...e.limits},i=e.now??Date.now,z=new L(o.bridgeBytesPerMinute),m=new Map,b=new Map,P=y(e.root,"blobs"),w=u(t=>y(e.root,e.checkout,t),"loadDir"),v=u((t,n)=>y(P,t.slice(0,2),`${t}${n==="gzip"?".gz":".json"}`),"blobFile"),S=u(async t=>{try{return await Q(t),!0}catch{return!1}},"exists"),p=u(async t=>await S(v(t,"gzip"))||await S(v(t,"identity")),"hasBlob"),A=u((t,n,c)=>new Promise((h,g)=>{const d=[];let a=0,r=!1;t.on("data",s=>{a+=s.length,a>o.bodyBytes?(r=!0,d.length=0):r||d.push(s)}),t.on("error",g),t.on("end",()=>{if(r)return h("too-big");const s=Buffer.concat(d);(c?c(s):Promise.resolve(!0)).then($=>{if(!$)return h("bad-hash");const k=`${n}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;_(k,s,{mode:384}).then(()=>F(k,n)).then(()=>h(a),async I=>{await K(k,{force:!0}).catch(()=>{}),g(I)})},g)})}),"receive"),Y=u(async(t,n)=>{const c=b.get(t.load);if(c)return c;try{const a=JSON.parse(await C(y(w(t.load),"meta.json"),"utf8"));return b.set(t.load,a),a}catch{}const h=n.headers.origin,g=n.headers["user-agent"],d={load:t.load,tab:t.tab,...t.app?{app:t.app}:{},checkout:e.checkout,...typeof h=="string"?{origin:h}:{},...typeof g=="string"?{userAgent:g.slice(0,300)}:{},wire:REPLAY_WIRE_VERSION,started:t.t0,lastAt:t.t1,chunks:0,bytes:0,blobs:[]};return b.size>=256&&b.delete(b.keys().next().value),b.set(t.load,d),e.publish?.(J(d,e.sessionId,i())),d},"loadMeta");return(t,n)=>{let c;try{c=new URL(t.url??"/","http://bridge.invalid")}catch{return!1}if(c.pathname!=="/replay"&&c.pathname!=="/replay/blob")return!1;const h=typeof t.headers.origin=="string"?t.headers.origin:void 0,g=e.admits(h),d=g&&h?{"access-control-allow-origin":h,vary:"origin"}:{},a=u((r,s)=>{const $=s===void 0?"":JSON.stringify(s);n.headersSent||n.writeHead(r,{...d,"content-type":"application/json","cache-control":"no-store","content-length":Buffer.byteLength($)}),n.end($),t.readableEnded||t.resume()},"answer");return t.method==="OPTIONS"?g?(n.writeHead(204,{...d,"access-control-allow-methods":"POST, HEAD","access-control-allow-headers":"content-type","access-control-max-age":"7200"}),n.end(),!0):(a(403,{error:"origin not allowed"}),!0):g?e.tokenOk(t.url)?((async()=>{try{if(c.pathname==="/replay/blob"){const l=c.searchParams.get("sha")??"";if(!E.test(l))return a(400,{error:"bad sha"});if(t.method==="HEAD"){const O=await p(l);return n.headersSent||n.writeHead(O?200:404,d),void n.end()}if(t.method!=="POST")return a(405,{error:"POST or HEAD"});if(await p(l))return a(200,{stored:!1,reason:"held"});const j=c.searchParams.get("enc")==="identity"?"identity":"gzip",T=Number(t.headers["content-length"]??0);if(T>o.bodyBytes||!z.take(T,i()))return a(413,{error:"over the replay byte budget"});const H=v(l,j);await q(te(H),{recursive:!0,mode:448});const R=await A(t,H,async O=>{try{return V("sha256").update(j==="gzip"?await oe(O):O).digest("hex")===l}catch{return!1}});return R==="bad-hash"?a(400,{error:"the blob does not hash to its sha"}):R==="too-big"?a(413,{error:"blob over the body limit"}):a(201,{stored:!0})}if(t.method!=="POST")return a(405,{error:"POST"});const r=D(c.searchParams);if(typeof r=="string")return a(400,{error:r});const s=w(r.load),$=y(s,`${String(r.seq).padStart(6,"0")}${r.enc==="gzip"?".json.gz":".json"}`);if(await S($))return a(200,{stored:!1,reason:"duplicate"});const k=[];for(const l of r.blobs)await p(l)||k.push(l);if(k.length)return a(409,{missing:k});const I=Number(t.headers["content-length"]??0);let x=m.get(r.load);if(x||(m.size>=256&&m.delete(m.keys().next().value),x=new L(o.loadBytesPerMinute),m.set(r.load,x)),I>o.bodyBytes||!x.take(I,i())||!z.take(I,i()))return a(413,{error:"over the replay byte budget"});await q(s,{recursive:!0,mode:448});const f=await Y(r,t),N=await A(t,$);if(N==="too-big"||N==="bad-hash")return a(413,{error:"chunk over the body limit"});const Z={seq:r.seq,t0:r.t0,t1:r.t1,n:r.n,full:r.full,enc:r.enc,bytes:N,...r.base?{base:r.base}:{},...r.anchors.length?{anchors:r.anchors}:{},...r.more?{anchorsInBody:!0}:{},...r.blobs.length?{blobs:r.blobs}:{},at:i()};await W(y(s,"index.jsonl"),`${JSON.stringify(Z)}
|
|
2
|
+
`,{mode:384}),f.chunks+=1,f.bytes+=N,f.lastAt=Math.max(f.lastAt,r.t1),r.base&&(f.base=r.base);for(const l of r.blobs)f.blobs.includes(l)||f.blobs.push(l);const M=y(s,`meta.json.${process.pid}.tmp`);return await _(M,JSON.stringify(f),{mode:384}),await F(M,y(s,"meta.json")),a(201,{stored:!0})}catch{return a(500,{error:"replay store unavailable"})}})(),!0):(a(401,{error:"missing or wrong bridge token"}),!0):(a(403,{error:"origin not allowed"}),!0)}}B(createReplayIngest,"createReplayIngest"),u(createReplayIngest,"createReplayIngest");function J(e,o,i){return{id:ae(),sessionId:o??"pending",timestamp:i,source:"client-runtime",category:"runtime",severity:"info",title:`session replay recording${e.app?` ${e.app}`:""} (${e.tab})`,fingerprint:`replay:replay-started:${e.load}`,context:{sessionId:o??"pending",tabId:e.tab},raw:{kind:"replay-started",loadId:e.load,tabId:e.tab,checkout:e.checkout,...e.app?{app:e.app}:{},...e.origin?{origin:e.origin}:{}}}}B(J,"ct"),u(J,"startedEvent");
|
package/lib/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var B=Object.defineProperty;var d=(e,r)=>B(e,"name",{value:r,configurable:!0});var D=Object.defineProperty,o=d((e,r)=>D(e,"name",{value:r,configurable:!0}),"o");import{createHash as x,timingSafeEqual as $}from"node:crypto";import{createServer as F,STATUS_CODES as U}from"node:http";import{createServer as M}from"node:net";import{WebSocket as W,WebSocketServer as G}from"ws";import{boundedClone as H,createEventFloodGuard as J,DEFAULT_RAW_BYTE_LIMIT as j,exceedsJsonByteBudget as q}from"@lensmcp/protocol-types";import{BRIDGE_TOKEN_PARAM as z,CONNECTION_BURST as V,CONNECTION_PER_SEC as X,DEFAULT_WS_HOST as K,DEFAULT_WS_PORT as Y,ENV_BRIDGE_ALLOWED_ORIGINS as w,MAX_BATCH_EVENTS as Q,MAX_BRIDGE_CONNECTIONS as Z,MAX_FRAME_BYTES as ee}from"./constants.js";import{resolveEnvSink as re}from"./env-sink.js";import{asBaseEvent as te,conformsToEnvelopeContract as ne}from"./envelope-contract.js";import{envelopeToEvent as oe}from"./event-mappers.js";import{createOriginPolicy as se,parseAllowedOrigins as ie}from"./origin-policy.js";import{createReplayIngest as ae,replayStoreFromEnv as ce}from"./replay-ingest.js";export function makePublisher(e){let r;return t=>{if(e.bus){e.bus.publish(t);return}if(e.sink){e.sink(t);return}if(r===void 0&&(r=re()),r){r(t);return}(t.severity==="error"||t.severity==="fatal")&&console.debug(`[lensmcp-bridge] ${t.severity}: ${t.title}`)}}d(makePublisher,"makePublisher"),o(makePublisher,"makePublisher");export function handleClientEnvelope(e,r,t,a={}){const c=a.admit??de;let l;try{l=JSON.parse(e)}catch{c();return}if(!l||typeof l!="object"||!("kind"in l)){c();return}if(l.kind==="batch"){if(!Array.isArray(l.events)){c();return}for(const f of l.events.slice(0,Q))c()&&(!f||typeof f!="object"||!("kind"in f)||f.kind!=="batch"&&E(f,r,t));return}c()&&E(l,r,t)}d(handleClientEnvelope,"handleClientEnvelope"),o(handleClientEnvelope,"handleClientEnvelope");const de=o(()=>!0,"admitAll");function E(e,r,t){if(!ne(e))return;let a;try{const c=oe(e,t);a=c&&te(c)}catch{return}if(a)try{r(C(a))}catch{}}d(E,"h"),o(E,"mapAndPublish");function C(e){const r=e.raw;return r==null||!q(r,j)?e:{...e,raw:H(r).value}}d(C,"oe"),o(C,"boundRaw");export function readTabId(e){try{const r=JSON.parse(e);return typeof r?.tabId=="string"&&r.tabId?r.tabId:void 0}catch{return}}d(readTabId,"readTabId"),o(readTabId,"readTabId");export async function createBridgeServer(e={}){const r=e.host??K,t=e.port??Y,a=makePublisher(e),c=se([...e.allowedOrigins??[],...ie(process.env[w])]),l=e.token?O(e.token):void 0,f=e.maxConnections??Z,h=e.connectionRateLimit??{burst:V,perSec:X},P=e.onRefuse??N(),A=o(n=>{const s=n.headers.origin,i=s===void 0?{}:{origin:s};if(m.clients.size>=f)return{status:503,reason:"bridge connection limit reached",...i};if(!c.admits(s))return{status:403,reason:"origin not allowed",...i};if(l&&!S(n.url,l))return{status:401,reason:"missing or wrong bridge token",...i}},"refuseUpgrade"),y=e.replay===!1?void 0:e.replay??ce(),R=y?ae({...y,admits:o(n=>c.admits(n),"admits"),tokenOk:o(n=>!l||S(n,l),"tokenOk"),publish:a,...e.sessionId?{sessionId:e.sessionId}:{}}):void 0,u=F((n,s)=>{R?.(n,s)||I(n,s)});u.on("error",()=>{});const m=new G({server:u,maxPayload:ee,verifyClient:o((n,s)=>{const i=A(n.req);if(!i){s(!0);return}try{P(i)}catch{}s(!1,i.status,i.reason)},"verifyClient")});m.on("error",()=>{}),m.on("connection",n=>{if(n.on("error",()=>{}),m.clients.size>f){n.close(1013,"bridge connection limit reached");return}let s;const i=J({burst:h.burst,perSec:h.perSec,envOverride:!1}),g=o(p=>{handleClientEnvelope(JSON.stringify({kind:"console",level:"warn",at:Date.now(),...s?{tabId:s}:{},args:[CONNECTION_THROTTLE_NOTICE,{dropped:p}]}),a,e.sessionId)},"announceDropped"),v={admit:o(()=>{const p=i.admit(le);return p.ok&&p.suppressed>0&&g(p.suppressed),p.ok},"admit")};n.on("message",p=>{const T=p.toString();s??=readTabId(T),handleClientEnvelope(T,a,e.sessionId,v)}),n.on("close",()=>{s&&handleClientEnvelope(JSON.stringify({kind:"gone",at:Date.now(),tabId:s}),a,e.sessionId)})}),await new Promise((n,s)=>{const i=o(v=>{u.off("listening",g),s(v)},"onError"),g=o(()=>{u.off("error",i),n()},"onListening");u.once("error",i),u.once("listening",g),u.listen(t,r)});const b=u.address(),L=typeof b=="object"&&b!==null?b.port:t;let _;return{port:L,host:r,learnOrigin:o(n=>c.learn(n),"learnOrigin"),admitsOrigin:o(n=>c.admits(n),"admitsOrigin"),close:o(()=>(_??=(async()=>{const n=[...m.clients],s=[new Promise(i=>m.close(()=>i())),new Promise(i=>u.close(()=>i())),...n.map(k)];for(const i of n)i.terminate();u.closeAllConnections(),await Promise.all(s)})(),_),"close")}}d(createBridgeServer,"createBridgeServer"),o(createBridgeServer,"createBridgeServer");function I(e,r){const t=U[426]??"Upgrade Required";r.writeHead(426,{"Content-Length":Buffer.byteLength(t),"Content-Type":"text/plain"}),r.end(t)}d(I,"ie"),o(I,"answerPlainRequest");function k(e){return e.readyState===W.CLOSED?Promise.resolve():new Promise(r=>e.once("close",()=>r()))}d(k,"se"),o(k,"socketClosed");const le="connection";export const CONNECTION_THROTTLE_NOTICE="[lensmcp-bridge] per-connection rate limit: this tab sent envelopes faster than the bridge admits, and some were dropped";const ue=32;function N(){const e=new Set;return r=>{const t=`${r.status} ${r.origin??""}`;if(e.has(t)||e.size>=ue)return;e.add(t);const a=r.status===403?` \u2014 if this is your app, add its origin to ${w} (or the Vite plugin's \`wsAllowedOrigins\`)`:r.status===401?" \u2014 a tab opened before the dev server restarted needs a reload; a no-build page needs the token as __LENSMCP_WS_TOKEN__":"";console.warn(`[lensmcp-bridge] refused a connection${r.origin?` from ${r.origin}`:""}: ${r.reason} (${r.status})${a}`)}}d(N,"de"),o(N,"warnRefusalOnce");function O(e){return x("sha256").update(e).digest()}d(O,"w"),o(O,"digest");function S(e,r){let t;try{t=new URL(e??"/","http://bridge.invalid").searchParams.get(z)}catch{return!1}return t!==null&&$(O(t),r)}d(S,"T"),o(S,"carriesToken");export function findFreePort(){return new Promise((e,r)=>{const t=M();t.unref(),t.on("error",r),t.listen(0,()=>{const a=t.address();if(!a){t.close(),r(new Error("Could not allocate free port"));return}const c=a.port;t.close(()=>e(c))})})}d(findFreePort,"findFreePort"),o(findFreePort,"findFreePort");
|
package/main.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var S=Object.defineProperty;var i=(s,e)=>S(s,"name",{value:e,configurable:!0});var E=Object.defineProperty,l=i((s,e)=>E(s,"name",{value:e,configurable:!0}),"i");import{DEFAULT_WS_HOST as u,DEFAULT_WS_PORT as v,ENV_BRIDGE_ALLOWED_ORIGINS as t,ENV_BRIDGE_TOKEN as $,ENV_SESSION_ID as N,ENV_WS_HOST as b,ENV_WS_PORT as d,SHUTDOWN_GRACE_MS as m}from"./lib/constants.js";import{closeEnvSink as T}from"./lib/env-sink.js";import{createBridgeServer as f}from"./lib/server.js";async function g(){const s=process.env[b]??u,e=process.env[d],o=e!==void 0&&e!==""?Number(e):v;(!Number.isFinite(o)||o<0)&&(console.error(`[lensmcp-bridge] invalid ${d}=${e}`),process.exit(2));const _=process.env[N],c=process.env[$]||void 0;let r;try{r=await f({host:s,port:o,sessionId:_,token:c})}catch(n){console.error(`[lensmcp-bridge] failed to listen on ws://${s}:${o} \u2014 ${n.message}`),process.exit(1)}console.log(`[lensmcp-bridge] listening on ws://${r.host}:${r.port}`),console.log(`[lensmcp-bridge] admitting pages from loopback origins${process.env[t]?` and ${process.env[t]}`:` (set ${t} to add more)`}; token ${c?"required":"not required"}`);let a=!1;const p=l(n=>{a&&(console.error(`[lensmcp-bridge] ${n} again \u2014 exiting now`),process.exit(1)),a=!0,setTimeout(()=>{console.error(`[lensmcp-bridge] shutdown did not finish within ${m}ms \u2014 exiting anyway`),process.exit(1)},m),r.close().then(()=>T()).then(()=>process.exit(0),()=>process.exit(1))},"shutdown");process.on("SIGINT",p),process.on("SIGTERM",p)}i(g,"u"),l(g,"main"),g();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/bridge",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.39",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@lensmcp/core": "1.25.
|
|
23
|
-
"@lensmcp/protocol-types": "1.25.
|
|
22
|
+
"@lensmcp/core": "1.25.39",
|
|
23
|
+
"@lensmcp/protocol-types": "1.25.39",
|
|
24
24
|
"tslib": "^2.3.0",
|
|
25
25
|
"ws": "^8.21.0"
|
|
26
26
|
},
|