@lensmcp/bridge 1.18.7 → 1.18.10
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/event-mappers.d.ts +3 -1
- package/lib/event-mappers.js +3 -3
- package/lib/server.js +1 -1
- package/lib/types.d.ts +13 -0
- package/main.js +1 -1
- package/package.json +3 -3
package/lib/env-sink.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var y=Object.defineProperty;var f=(
|
|
2
|
-
`;
|
|
3
|
-
`);
|
|
1
|
+
"use strict";var y=Object.defineProperty;var f=(n,t)=>y(n,"name",{value:t,configurable:!0});var k=Object.defineProperty,c=f((n,t)=>k(n,"name",{value:t,configurable:!0}),"i");import{connect as _}from"node:net";import{createSocket as M}from"node:dgram";import{appendFileSync as b,renameSync as S,statSync as A,unlinkSync as C}from"node:fs";const g=(()=>{const n=Number(process.env.LENSMCP_EVENT_MAX_BYTES);return Number.isFinite(n)&&n>0?n:50*1024*1024})(),E=(()=>{const n=Number(process.env.LENSMCP_EVENT_MAX_FILES);return Number.isFinite(n)&&n>=1&&n<=20?Math.floor(n):5})(),L=1024*1024;let p=0;function d(n,t){if(p+=t,!(p<L)){p=0;try{if(A(n).size<=g)return}catch{return}try{C(`${n}.${E}`)}catch{}for(let e=E-1;e>=1;e--)try{S(`${n}.${e}`,`${n}.${e+1}`)}catch{}try{S(n,`${n}.1`)}catch{}}}f(d,"C"),c(d,"rotateIfLarge");const a=(()=>{const n=process.env.LENSMCP_EVENT_MAX_EPS;if(n==="off")return 1/0;const t=Number(n);return n!==void 0&&n!==""&&Number.isFinite(t)?t<=0?1/0:t:5})(),h=Math.max(30,a===1/0?0:a*4),l=new Map;function v(n){if(a===1/0)return 0;const t=Date.now();let e=l.get(n);if(!e){if(l.size>=2048){for(const[r,s]of l)t-s.lastSeenAt>6e4&&l.delete(r);l.size>=2048&&l.clear()}e={tokens:h,lastRefillAt:t,suppressed:0,lastSeenAt:t},l.set(n,e)}if(e.lastSeenAt=t,e.tokens=Math.min(h,e.tokens+(t-e.lastRefillAt)/1e3*a),e.lastRefillAt=t,e.tokens>=1){e.tokens-=1;const r=e.suppressed;return e.suppressed=0,r}return e.suppressed+=1,-1}f(v,"T"),c(v,"floodAdmit");let i;export function resolveEnvSink(){if(i!==void 0)return i;const n=process.env.LENSMCP_EVENT_FILE;if(n)return i=c(r=>{try{const s=v(r.fingerprint);if(s<0)return;const u=JSON.stringify(s>0?{...r,raw:{...r.raw??{},floodSuppressed:s}}:r)+`
|
|
2
|
+
`;d(n,u.length),b(n,u)}catch{}},"cachedEnvSink"),i;const t=process.env.LENSMCP_UDS;if(t)return i=udsEnvSink(t),i;const e=process.env.LENSMCP_IPC_SOCKET;if(e){const[r,s]=e.split(":"),u=Number(s);if(r&&Number.isFinite(u)){const o=M("udp4");return o.unref(),i=c(N=>{try{const m=Buffer.from(JSON.stringify(N));o.send(m,u,r,()=>{})}catch{}},"cachedEnvSink"),i}}return i=null,null}f(resolveEnvSink,"resolveEnvSink"),c(resolveEnvSink,"resolveEnvSink");export function resetEnvSinkCache(){i=void 0}f(resetEnvSinkCache,"resetEnvSinkCache"),c(resetEnvSinkCache,"resetEnvSinkCache");export function udsEnvSink(n){const t=[];let e=null,r=!1;const s=c(()=>{if(!(!e||!e.writable))for(;t.length>0;)e.write(t.shift())},"flush"),u=c(()=>{if(r||e)return;r=!0;const o=_(n);o.on("connect",()=>{r=!1,e=o,s()}),o.on("drain",s),o.on("error",()=>{r=!1,e=null}),o.on("close",()=>{e=null}),o.unref()},"open");return u(),o=>{for(t.push(JSON.stringify(o)+`
|
|
3
|
+
`);t.length>1e4;)t.shift();if(e&&e.writable){s();return}if(e){try{e.destroy()}catch{}e=null}u()}}f(udsEnvSink,"udsEnvSink"),c(udsEnvSink,"udsEnvSink");
|
package/lib/event-mappers.d.ts
CHANGED
|
@@ -24,4 +24,6 @@ import type { ClientEnvelope } from './types.js';
|
|
|
24
24
|
* (`./redact.js`) — enrichment is exactly what creates the leak risk, so the
|
|
25
25
|
* two land together. See that module for what is and is not scrubbed.
|
|
26
26
|
*/
|
|
27
|
-
export declare function envelopeToEvent(env: ClientEnvelope,
|
|
27
|
+
export declare function envelopeToEvent(env: Exclude<ClientEnvelope, {
|
|
28
|
+
kind: 'batch';
|
|
29
|
+
}>, sessionId?: string): BaseEvent;
|
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 h=Object.defineProperty,l=c((e,n)=>h(e,"name",{value:n,configurable:!0}),"s");import{fingerprint as o,ulid as a}from"@lensmcp/core";import{redactDeep as g,redactText as $,redactUrl as d,safeText as u,safeTitle as m}from"./redact.js";const x="pending";export function envelopeToEvent(e,n=x){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:${v(e.stack)}`,file:e.source,detail:e.message??e.name}),title:m(t),message:u(e.stack??e.message),location:e.source?{file:d(e.source),line:e.lineno,column:e.colno}:void 0,raw:{...e,...e.message?{message:$(e.message)}:{},...e.stack?{stack:u(e.stack)}:{},...e.source?{source:d(e.source)}:{}}}}case"resource-error":{const r=e.url?d(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}:${p(e.url??"")}`}),title:m(`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:m(`Unhandled promise rejection: ${r}${i}`),message:u(e.stack??e.reason),raw:{...e,reason:$(e.reason),...e.stack?{stack:u(e.stack)}:{}}}}case"console":{const r=e.level==="error"?"error":e.level==="warn"?"warning":"info",i=g(e.args),t=i.map(w).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=d(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}:${p(e.url)}:${e.status??"err"}`}),title:m(`${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.userAgent,raw:e};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":{const r=k(e.category,e.raw),i=r&&r.status!==void 0&&r.status>=400?"error":e.severity??"info",t=l(y=>r&&r.url!==r.redactedUrl?y.split(r.url).join(r.redactedUrl):y,"scrub"),f=t(e.title);return{id:a(),sessionId:n,timestamp:e.at,source:e.source,category:e.category,severity:i,context:{sessionId:n,...s,...e.context??{}},fingerprint:e.fingerprint?t(e.fingerprint):o({kind:"publish",identity:f}),title:m(f),message:u(e.message),...r?{relatedUrls:[r.redactedUrl]}:{},raw:r?{...g(e.raw),url:r.redactedUrl}:g(e.raw)}}}}c(envelopeToEvent,"envelopeToEvent"),l(envelopeToEvent,"envelopeToEvent");function k(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:d(s.url),...typeof s.status=="number"?{status:s.status}:{}}}c(k,"w"),l(k,"networkDetails");function v(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(v,"h"),l(v,"normaliseStackTopFrame");function p(e){try{return`${new URL(e,"http://x").pathname}`}catch{return e}}c(p,"y"),l(p,"normaliseUrl");function w(e){if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}c(w,"x"),l(w,"stringify");
|
package/lib/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var b=Object.defineProperty;var s=(r,e)=>b(r,"name",{value:e,configurable:!0});var v=Object.defineProperty,i=s((r,e)=>v(r,"name",{value:e,configurable:!0}),"i");import{createServer as m}from"node:net";import{WebSocketServer as g}from"ws";import{DEFAULT_WS_HOST as y,DEFAULT_WS_PORT as S}from"./constants.js";import{resolveEnvSink as h}from"./env-sink.js";import{envelopeToEvent as u}from"./event-mappers.js";export function makePublisher(r){let e;return t=>{if(r.bus){r.bus.publish(t);return}if(r.sink){r.sink(t);return}if(e===void 0&&(e=h()),e){e(t);return}(t.severity==="error"||t.severity==="fatal")&&console.debug(`[lensmcp-bridge] ${t.severity}: ${t.title}`)}}s(makePublisher,"makePublisher"),i(makePublisher,"makePublisher");export function handleClientEnvelope(r,e,t){let o;try{o=JSON.parse(r)}catch{return}if(!(!o||typeof o!="object"||!("kind"in o))){if(o.kind==="batch"){if(!Array.isArray(o.events))return;for(const n of o.events)!n||typeof n!="object"||!("kind"in n)||n.kind!=="batch"&&e(u(n,t));return}e(u(o,t))}}s(handleClientEnvelope,"handleClientEnvelope"),i(handleClientEnvelope,"handleClientEnvelope");export function readTabId(r){try{const e=JSON.parse(r);return typeof e?.tabId=="string"&&e.tabId?e.tabId:void 0}catch{return}}s(readTabId,"readTabId"),i(readTabId,"readTabId");export async function createBridgeServer(r={}){const e=r.host??y,t=r.port??S,o=t===0?await findFreePort():t,n=makePublisher(r),a=new g({host:e,port:o});return a.on("connection",c=>{let f;c.on("message",d=>{const l=d.toString();f??=readTabId(l),handleClientEnvelope(l,n,r.sessionId)}),c.on("close",()=>{f&&handleClientEnvelope(JSON.stringify({kind:"gone",at:Date.now(),tabId:f}),n,r.sessionId)})}),await new Promise((c,f)=>{const d=i(p=>{a.off("listening",l),f(p)},"onError"),l=i(()=>{a.off("error",d),c()},"onListening");a.once("error",d),a.once("listening",l)}),{port:o,host:e,close:i(()=>new Promise(c=>{a.close(()=>c())}),"close")}}s(createBridgeServer,"createBridgeServer"),i(createBridgeServer,"createBridgeServer");export function findFreePort(){return new Promise((r,e)=>{const t=m();t.unref(),t.on("error",e),t.listen(0,()=>{const o=t.address();if(!o){t.close(),e(new Error("Could not allocate free port"));return}const n=o.port;t.close(()=>r(n))})})}s(findFreePort,"findFreePort"),i(findFreePort,"findFreePort");
|
package/lib/types.d.ts
CHANGED
|
@@ -108,6 +108,19 @@ export type ClientEnvelope = (
|
|
|
108
108
|
fingerprint?: string;
|
|
109
109
|
context?: Record<string, unknown>;
|
|
110
110
|
raw?: unknown;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A COALESCED frame: every envelope the page produced within one flush
|
|
114
|
+
* window (a macrotask), in capture order. The client runtime batches so a
|
|
115
|
+
* render storm costs ONE stringify + ONE socket frame scheduled OFF the
|
|
116
|
+
* interaction task instead of hundreds inline in it — each inner event
|
|
117
|
+
* still carries its own capture-time `at` (and `tabId`), so the recorded
|
|
118
|
+
* timeline is unchanged; only where the transport work happens moves.
|
|
119
|
+
*/
|
|
120
|
+
| {
|
|
121
|
+
kind: 'batch';
|
|
122
|
+
at: number;
|
|
123
|
+
events: ClientEnvelope[];
|
|
111
124
|
}) & {
|
|
112
125
|
/** Per-tab identity stamped by the client runtime (sessionStorage is per-tab) — keys `browser://tabs`. */
|
|
113
126
|
tabId?: string;
|
package/main.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var S=Object.defineProperty;var n=(s,e)=>S(s,"name",{value:e,configurable:!0});var d=Object.defineProperty,i=n((s,e)=>d(s,"name",{value:e,configurable:!0}),"n");import{DEFAULT_WS_HOST as _,DEFAULT_WS_PORT as g,ENV_SESSION_ID as v,ENV_WS_HOST as u,ENV_WS_PORT as a}from"./lib/constants.js";import{createBridgeServer as T}from"./lib/server.js";async function l(){const s=process.env[u]??_,e=process.env[a],o=e!==void 0&&e!==""?Number(e):g;(!Number.isFinite(o)||o<0)&&(console.error(`[lensmcp-bridge] invalid ${a}=${e}`),process.exit(2));const p=process.env[v];let r;try{r=await T({host:s,port:o,sessionId:p})}catch(m){console.error(`[lensmcp-bridge] failed to listen on ws://${s}:${o} \u2014 ${m.message}`),process.exit(1)}console.log(`[lensmcp-bridge] listening on ws://${r.host}:${r.port}`);let t=!1;const c=i(()=>{t||(t=!0,r.close().then(()=>process.exit(0)))},"shutdown");process.on("SIGINT",c),process.on("SIGTERM",c)}n(l,"g"),i(l,"main"),l();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/bridge",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.10",
|
|
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.18.
|
|
23
|
-
"@lensmcp/protocol-types": "1.18.
|
|
22
|
+
"@lensmcp/core": "1.18.10",
|
|
23
|
+
"@lensmcp/protocol-types": "1.18.10",
|
|
24
24
|
"tslib": "^2.3.0",
|
|
25
25
|
"ws": "^8.21.0"
|
|
26
26
|
},
|