@lensmcp/vite-plugin 1.23.12 → 1.24.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/README.md +3 -0
- package/lib/event-mappers.js +2 -2
- package/lib/plugin.d.ts +1 -1
- package/lib/plugin.js +4 -4
- package/lib/runtime-guard.d.ts +37 -0
- package/lib/runtime-guard.js +1 -0
- package/lib/types.d.ts +11 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -64,12 +64,15 @@ Incoming browser envelopes and Vite/Rollup events are normalised into LensMCP's
|
|
|
64
64
|
| `applyOnBuild` | `boolean` | `false` | Also attach during `vite build`. Off by default — everything here is a development tool. `LENSMCP_VITE_APPLY_ON_BUILD=1` sets it too. |
|
|
65
65
|
| `wsPort` | `number` | ephemeral | WebSocket port the client runtime posts events to. An unused port is chosen automatically when omitted. |
|
|
66
66
|
| `wsHost` | `string` | `'127.0.0.1'` | Bind address for the WebSocket. |
|
|
67
|
+
| `wsAllowedOrigins` | `string[]` | — | Browser origins the WebSocket admits beyond loopback and the origins this dev server serves the runtime to (gateway hosts and LAN addresses are learned automatically). Entries: `https://app.test:8443`, `app.test`, `.app.test`, `*`. `LENSMCP_BRIDGE_ALLOWED_ORIGINS` adds to it. |
|
|
67
68
|
| `injectClient` | `boolean` | `true` | Inject the client runtime via a `<script>` tag. |
|
|
68
69
|
| `aliasValtio` | `boolean` | `true` | Alias `valtio` to the instrumented drop-in in dev. |
|
|
69
70
|
| `transformSource` | `boolean` | `true` | Run the LensMCP source transform (root-wrap + hooks + JSX ids). |
|
|
70
71
|
|
|
71
72
|
The returned plugin also exposes a `wsPort(): number \| undefined` accessor (`LensmcpViteRuntime`), available after Vite's `configureServer` hook fires.
|
|
72
73
|
|
|
74
|
+
The WebSocket is not open to whoever finds the port: each dev-server start mints a token that is baked into the served runtime and required on every upgrade, the runtime itself is only served same-origin or to an origin the WebSocket admits anyway, and a page from any other origin is refused.
|
|
75
|
+
|
|
73
76
|
## How it fits
|
|
74
77
|
|
|
75
78
|
`@lensmcp/vite-plugin` is the front door for the browser-side of LensMCP. It pulls in [`@lensmcp/react-instrumentation`](https://www.npmjs.com/package/@lensmcp/react-instrumentation) (the Babel transform) and [`@lensmcp/valtio-instrumentation`](https://www.npmjs.com/package/@lensmcp/valtio-instrumentation) (the Valtio drop-in), then injects the bridge that those instrumentation layers publish through. The bridge ships the resulting events to the LensMCP runtime, where the gateway / MCP server collect and reduce them into resources (`react://`, `valtio://`, `runtime://browser/*`, `browser://tabs`) that a coding agent can query through the lens.
|
package/lib/event-mappers.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`).slice(0,2);for(const i of n){const a=i.match(/at\s+(\S+)/);if(a&&a[1])return a[1]}return"unknown"}
|
|
1
|
+
"use strict";var ce=Object.defineProperty;var c=(e,n)=>ce(e,"name",{value:n,configurable:!0});var ve=Object.defineProperty,v=c((e,n)=>ve(e,"name",{value:n,configurable:!0}),"c"),se=Object.defineProperty,s=v((e,n)=>se(e,"name",{value:n,configurable:!0}),"u"),fe=Object.defineProperty,f=s((e,n)=>fe(e,"name",{value:n,configurable:!0}),"u"),pe=Object.defineProperty,p=f((e,n)=>pe(e,"name",{value:n,configurable:!0}),"u"),de=Object.defineProperty,d=p((e,n)=>de(e,"name",{value:n,configurable:!0}),"c"),be=Object.defineProperty,b=d((e,n)=>be(e,"name",{value:n,configurable:!0}),"c"),me=Object.defineProperty,m=b((e,n)=>me(e,"name",{value:n,configurable:!0}),"c"),ge=Object.defineProperty,g=m((e,n)=>ge(e,"name",{value:n,configurable:!0}),"u"),ye=Object.defineProperty,y=g((e,n)=>ye(e,"name",{value:n,configurable:!0}),"u"),je=Object.defineProperty,j=y((e,n)=>je(e,"name",{value:n,configurable:!0}),"u"),Te=Object.defineProperty,T=j((e,n)=>Te(e,"name",{value:n,configurable:!0}),"c"),Ee=Object.defineProperty,E=T((e,n)=>Ee(e,"name",{value:n,configurable:!0}),"c"),Oe=Object.defineProperty,O=E((e,n)=>Oe(e,"name",{value:n,configurable:!0}),"c"),Pe=Object.defineProperty,P=O((e,n)=>Pe(e,"name",{value:n,configurable:!0}),"u"),Ie=Object.defineProperty,I=P((e,n)=>Ie(e,"name",{value:n,configurable:!0}),"u"),we=Object.defineProperty,w=I((e,n)=>we(e,"name",{value:n,configurable:!0}),"u"),ke=Object.defineProperty,k=w((e,n)=>ke(e,"name",{value:n,configurable:!0}),"u"),$e=Object.defineProperty,$=k((e,n)=>$e(e,"name",{value:n,configurable:!0}),"u"),he=Object.defineProperty,h=$((e,n)=>he(e,"name",{value:n,configurable:!0}),"u"),xe=Object.defineProperty,x=h((e,n)=>xe(e,"name",{value:n,configurable:!0}),"u"),Se=Object.defineProperty,S=x((e,n)=>Se(e,"name",{value:n,configurable:!0}),"u"),Ue=Object.defineProperty,U=S((e,n)=>Ue(e,"name",{value:n,configurable:!0}),"u"),Re=Object.defineProperty,R=U((e,n)=>Re(e,"name",{value:n,configurable:!0}),"s"),Ae=Object.defineProperty,A=R((e,n)=>Ae(e,"name",{value:n,configurable:!0}),"s"),Ce=Object.defineProperty,C=A((e,n)=>Ce(e,"name",{value:n,configurable:!0}),"s"),Fe=Object.defineProperty,F=C((e,n)=>Fe(e,"name",{value:n,configurable:!0}),"c"),Je=Object.defineProperty,J=F((e,n)=>Je(e,"name",{value:n,configurable:!0}),"c"),Le=Object.defineProperty,L=J((e,n)=>Le(e,"name",{value:n,configurable:!0}),"c"),Ne=Object.defineProperty,N=L((e,n)=>Ne(e,"name",{value:n,configurable:!0}),"c"),qe=Object.defineProperty,q=N((e,n)=>qe(e,"name",{value:n,configurable:!0}),"c"),ze=Object.defineProperty,z=q((e,n)=>ze(e,"name",{value:n,configurable:!0}),"c"),Be=Object.defineProperty,B=z((e,n)=>Be(e,"name",{value:n,configurable:!0}),"l"),De=Object.defineProperty,D=B((e,n)=>De(e,"name",{value:n,configurable:!0}),"l"),Ge=Object.defineProperty,G=D((e,n)=>Ge(e,"name",{value:n,configurable:!0}),"c"),He=Object.defineProperty,H=G((e,n)=>He(e,"name",{value:n,configurable:!0}),"c"),Ke=Object.defineProperty,K=H((e,n)=>Ke(e,"name",{value:n,configurable:!0}),"c"),Me=Object.defineProperty,M=K((e,n)=>Me(e,"name",{value:n,configurable:!0}),"u"),Qe=Object.defineProperty,Q=M((e,n)=>Qe(e,"name",{value:n,configurable:!0}),"u"),Ve=Object.defineProperty,V=Q((e,n)=>Ve(e,"name",{value:n,configurable:!0}),"u"),We=Object.defineProperty,W=V((e,n)=>We(e,"name",{value:n,configurable:!0}),"c"),Xe=Object.defineProperty,X=W((e,n)=>Xe(e,"name",{value:n,configurable:!0}),"c"),Ye=Object.defineProperty,Y=X((e,n)=>Ye(e,"name",{value:n,configurable:!0}),"c"),Ze=Object.defineProperty,Z=Y((e,n)=>Ze(e,"name",{value:n,configurable:!0}),"u"),_e=Object.defineProperty,_=Z((e,n)=>_e(e,"name",{value:n,configurable:!0}),"u"),en=Object.defineProperty,ee=_((e,n)=>en(e,"name",{value:n,configurable:!0}),"u"),nn=Object.defineProperty,ne=ee((e,n)=>nn(e,"name",{value:n,configurable:!0}),"u"),tn=Object.defineProperty,te=ne((e,n)=>tn(e,"name",{value:n,configurable:!0}),"o"),rn=Object.defineProperty,re=te((e,n)=>rn(e,"name",{value:n,configurable:!0}),"o"),on=Object.defineProperty,oe=re((e,n)=>on(e,"name",{value:n,configurable:!0}),"o"),an=Object.defineProperty,ie=oe((e,n)=>an(e,"name",{value:n,configurable:!0}),"o"),ln=Object.defineProperty,ae=ie((e,n)=>ln(e,"name",{value:n,configurable:!0}),"o"),un=Object.defineProperty,le=ae((e,n)=>un(e,"name",{value:n,configurable:!0}),"o");import{fingerprint as l,ulid as u}from"@lensmcp/core";const cn="pending";export function envelopeToEvent(e,n=cn){const i=e.tabId?{tabId:e.tabId}:{};switch(e.kind){case"error":return{id:u(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"error",context:{sessionId:n,...i},fingerprint:l({kind:"runtime",identity:`error:${t(e.stack)}`,file:e.source,detail:e.message}),title:e.message,message:e.stack??e.message,location:e.source?{file:e.source,line:e.lineno,column:e.colno}:void 0,raw:e};case"unhandledrejection":return{id:u(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"error",context:{sessionId:n,...i},fingerprint:l({kind:"runtime",identity:`unhandledrejection:${e.reason.slice(0,80)}`}),title:"Unhandled promise rejection",message:e.reason,raw:e};case"console":{const a=e.level==="error"?"error":e.level==="warn"?"warning":"info",ue=e.args.map(o).join(" ");return{id:u(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:a,context:{sessionId:n,...i},fingerprint:l({kind:"runtime",identity:`console:${e.level}:${ue.slice(0,80)}`}),title:`console.${e.level}`,message:ue,raw:e}}case"fetch":{const a=e.error!==void 0||e.status!==void 0&&e.status>=400;return{id:u(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"network",severity:a?"error":"info",context:{sessionId:n,...i},fingerprint:l({kind:"network",identity:`${e.method}:${r(e.url)}:${e.status??"err"}`}),title:`${e.method} ${e.url}${e.status!==void 0?` \u2192 ${e.status}`:""}`,message:e.error,relatedUrls:[e.url],raw:e}}case"route":return{id:u(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"info",context:{sessionId:n,url:e.url,...i},fingerprint:l({kind:"runtime",identity:`route:${e.url}`}),title:`Route \u2192 ${e.url}`,raw:e};case"ready":return{id:u(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"info",context:{sessionId:n,url:e.url,...i},fingerprint:l({kind:"runtime",identity:"client-ready"}),title:"Client runtime ready",message:e.userAgent,raw:e};case"publish":return{id:u(),sessionId:n,timestamp:e.at,source:e.source,category:e.category,severity:e.severity??"info",context:{sessionId:n,...i,...e.context??{}},fingerprint:e.fingerprint??l({kind:"publish",identity:e.title}),title:e.title,message:e.message,raw:e.raw}}}c(envelopeToEvent,"envelopeToEvent"),v(envelopeToEvent,"envelopeToEvent"),s(envelopeToEvent,"envelopeToEvent"),f(envelopeToEvent,"envelopeToEvent"),p(envelopeToEvent,"envelopeToEvent"),d(envelopeToEvent,"envelopeToEvent"),b(envelopeToEvent,"envelopeToEvent"),m(envelopeToEvent,"envelopeToEvent"),g(envelopeToEvent,"envelopeToEvent"),y(envelopeToEvent,"envelopeToEvent"),j(envelopeToEvent,"envelopeToEvent"),T(envelopeToEvent,"envelopeToEvent"),E(envelopeToEvent,"envelopeToEvent"),O(envelopeToEvent,"envelopeToEvent"),P(envelopeToEvent,"envelopeToEvent"),I(envelopeToEvent,"envelopeToEvent"),w(envelopeToEvent,"envelopeToEvent"),k(envelopeToEvent,"envelopeToEvent"),$(envelopeToEvent,"envelopeToEvent"),h(envelopeToEvent,"envelopeToEvent"),x(envelopeToEvent,"envelopeToEvent"),S(envelopeToEvent,"envelopeToEvent"),U(envelopeToEvent,"envelopeToEvent"),R(envelopeToEvent,"envelopeToEvent"),A(envelopeToEvent,"envelopeToEvent"),C(envelopeToEvent,"envelopeToEvent"),F(envelopeToEvent,"envelopeToEvent"),J(envelopeToEvent,"envelopeToEvent"),L(envelopeToEvent,"envelopeToEvent"),N(envelopeToEvent,"envelopeToEvent"),q(envelopeToEvent,"envelopeToEvent"),z(envelopeToEvent,"envelopeToEvent"),B(envelopeToEvent,"envelopeToEvent"),D(envelopeToEvent,"envelopeToEvent"),G(envelopeToEvent,"envelopeToEvent"),H(envelopeToEvent,"envelopeToEvent"),K(envelopeToEvent,"envelopeToEvent"),M(envelopeToEvent,"envelopeToEvent"),Q(envelopeToEvent,"envelopeToEvent"),V(envelopeToEvent,"envelopeToEvent"),W(envelopeToEvent,"envelopeToEvent"),X(envelopeToEvent,"envelopeToEvent"),Y(envelopeToEvent,"envelopeToEvent"),Z(envelopeToEvent,"envelopeToEvent"),_(envelopeToEvent,"envelopeToEvent"),ee(envelopeToEvent,"envelopeToEvent"),ne(envelopeToEvent,"envelopeToEvent"),te(envelopeToEvent,"envelopeToEvent"),re(envelopeToEvent,"envelopeToEvent"),oe(envelopeToEvent,"envelopeToEvent"),ie(envelopeToEvent,"envelopeToEvent"),ae(envelopeToEvent,"envelopeToEvent"),le(envelopeToEvent,"envelopeToEvent");function t(e){if(!e)return"unknown";const n=e.split(`
|
|
2
|
+
`).slice(0,2);for(const i of n){const a=i.match(/at\s+(\S+)/);if(a&&a[1])return a[1]}return"unknown"}c(t,"t"),v(t,"t"),s(t,"t"),f(t,"t"),p(t,"t"),d(t,"t"),b(t,"t"),m(t,"t"),g(t,"t"),y(t,"t"),j(t,"t"),T(t,"t"),E(t,"t"),O(t,"t"),P(t,"t"),I(t,"t"),w(t,"t"),k(t,"t"),$(t,"t"),h(t,"t"),x(t,"t"),S(t,"t"),U(t,"t"),R(t,"t"),A(t,"t"),C(t,"t"),F(t,"t"),J(t,"t"),L(t,"t"),N(t,"n"),q(t,"t"),z(t,"n"),B(t,"t"),D(t,"n"),G(t,"t"),H(t,"n"),K(t,"t"),M(t,"r"),Q(t,"t"),V(t,"r"),W(t,"t"),X(t,"n"),Y(t,"n"),Z(t,"r"),_(t,"n"),ee(t,"o"),ne(t,"a"),te(t,"l"),re(t,"l"),oe(t,"c"),ie(t,"c"),ae(t,"s"),le(t,"normaliseStackTopFrame");function r(e){try{return`${new URL(e,"http://x").pathname}`}catch{return e}}c(r,"r"),v(r,"r"),s(r,"r"),f(r,"r"),p(r,"r"),d(r,"r"),b(r,"r"),m(r,"r"),g(r,"r"),y(r,"r"),j(r,"r"),T(r,"r"),E(r,"r"),O(r,"r"),P(r,"r"),I(r,"r"),w(r,"r"),k(r,"r"),$(r,"r"),h(r,"r"),x(r,"r"),S(r,"r"),U(r,"r"),R(r,"r"),A(r,"r"),C(r,"r"),F(r,"r"),J(r,"r"),L(r,"r"),N(r,"r"),q(r,"r"),z(r,"r"),B(r,"r"),D(r,"r"),G(r,"r"),H(r,"r"),K(r,"r"),M(r,"n"),Q(r,"r"),V(r,"n"),W(r,"r"),X(r,"r"),Y(r,"r"),Z(r,"i"),_(r,"i"),ee(r,"a"),ne(r,"c"),te(r,"d"),re(r,"d"),oe(r,"l"),ie(r,"l"),ae(r,"m"),le(r,"normaliseUrl");function o(e){if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}c(o,"o"),v(o,"o"),s(o,"o"),f(o,"o"),p(o,"o"),d(o,"o"),b(o,"o"),m(o,"o"),g(o,"o"),y(o,"o"),j(o,"o"),T(o,"o"),E(o,"o"),O(o,"o"),P(o,"o"),I(o,"o"),w(o,"o"),k(o,"o"),$(o,"o"),h(o,"i"),x(o,"o"),S(o,"i"),U(o,"i"),R(o,"i"),A(o,"i"),C(o,"i"),F(o,"i"),J(o,"i"),L(o,"i"),N(o,"i"),q(o,"i"),z(o,"i"),B(o,"i"),D(o,"i"),G(o,"i"),H(o,"i"),K(o,"i"),M(o,"i"),Q(o,"i"),V(o,"i"),W(o,"i"),X(o,"i"),Y(o,"i"),Z(o,"o"),_(o,"o"),ee(o,"c"),ne(o,"u"),te(o,"m"),re(o,"m"),oe(o,"d"),ie(o,"d"),ae(o,"d"),le(o,"stringify");
|
package/lib/plugin.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { LensmcpViteOptions, LensmcpViteRuntime } from './types.js';
|
|
|
9
9
|
* as it is imported. So a NEW file in `shared/` or `web/libs/` never produced an `add` event, the testimony
|
|
10
10
|
* below never stamped it, and the gateway's stale-source-set sweeper read every out-of-root add as "this
|
|
11
11
|
* vite missed it" and rolled the app — 16 of 16 rolling handoffs in one foodguard day were exactly that
|
|
12
|
-
* (issues/dev-gateway
|
|
12
|
+
* (issues/closed/dev-gateway-02). Watching the scope makes the add audible, which both spares the roll AND lets
|
|
13
13
|
* Vite's own create-handling (the resolve-failed retry) work for those files.
|
|
14
14
|
*
|
|
15
15
|
* Shape rules: a dir INSIDE the root is already watched → skipped. A dir that CONTAINS the root (the app's own
|
package/lib/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var V=Object.defineProperty;var g=(e,t)=>V(e,"name",{value:t,configurable:!0});var $=Object.defineProperty,d=g((e,t)=>$(e,"name",{value:t,configurable:!0}),"c");import{randomBytes as J}from"node:crypto";import{existsSync as G,readdirSync as K,statSync as q}from"node:fs";import{delimiter as Y,join as B,relative as R,resolve as T,sep as O}from"node:path";import{fingerprint as I,ulid as F}from"@lensmcp/core";import{BRIDGE_TOKEN_PARAM as z,createBridgeServer as Z,makePublisher as L,handleClientEnvelope as Q}from"@lensmcp/bridge";import{CLIENT_VIRTUAL_ID as D,createRuntimeModuleGuard as ee,RESOLVED_CLIENT_VIRTUAL_ID as N}from"./runtime-guard.js";const te="/__lensmcp/watcher-health",x=256,ne=64,se=new Set(["node_modules",".git","dist","tmp","coverage","build","out","target",".nx",".lensmcp"]);export function resolveExtraWatchRoots(e,t){if(!e)return[];const s=T(t),p=d((u,r)=>{const a=R(r,u);return a!==""&&!a.startsWith("..")&&!a.startsWith(O)&&!/^[A-Za-z]:/.test(a)},"inside"),i=[],f=d(u=>{i.length<ne&&!i.includes(u)&&i.push(u)},"push");for(const u of e.split(Y)){const r=u.trim();if(!r)continue;const a=T(r);let y;try{y=G(a)&&q(a).isDirectory()}catch{y=!1}if(y&&!(a===s||p(a,s))){if(p(s,a)){const _=R(a,s).split(O).filter(Boolean);let w=a;for(const n of _){let l;try{l=K(w,{withFileTypes:!0})}catch{l=[]}for(const o of l)!o.isDirectory()||o.name===n||o.name.startsWith(".")||se.has(o.name)||f(B(w,o.name));w=B(w,n)}continue}f(a)}}return i}g(resolveExtraWatchRoots,"resolveExtraWatchRoots"),d(resolveExtraWatchRoots,"resolveExtraWatchRoots");export function lensmcpVitePlugin(e={},t={}){const s=e.enabled??!0,p=e.wsHost??"127.0.0.1",i=e.injectClient??!0;let f,u,r;const a=L(t),y=e.aliasValtio??!0,_=e.transformSource??!0,w=e.applyOnBuild??process.env.LENSMCP_VITE_APPLY_ON_BUILD==="1";return{name:"@lensmcp/vite-plugin",apply:d((n,l)=>s&&(l.command==="serve"||w),"apply"),enforce:"pre",wsPort:d(()=>f,"wsPort"),config(){if(!(!s||!y))return{resolve:{alias:[{find:/^valtio$/,replacement:"@lensmcp/valtio-instrumentation"}]}}},async configureServer(n){if(!s)return;r=J(24).toString("base64url"),u=await Z({host:p,port:e.wsPort??0,bus:t.bus,sessionId:t.sessionId,token:r,...e.wsAllowedOrigins?{allowedOrigins:e.wsAllowedOrigins}:{}}),f=u.port,n.middlewares.use(ee(u)),n.ws.on("vite:error",c=>{a(U(c,t.sessionId))});const l=resolveExtraWatchRoots(process.env.LENSMCP_WATCH_DIRS,n.config?.root??process.cwd());if(l.length>0&&typeof n.watcher?.add=="function")try{n.watcher.add(l),console.log(`[lensmcp] watching ${l.length} extra source root(s) for set changes: ${l.map(c=>R(n.config?.root??process.cwd(),c)||c).join(", ")}`)}catch(c){console.warn(`[lensmcp] could not widen the watcher: ${c?.message??String(c)}`)}let o=0,h=0,E=0;const S=[],m=d((c,v)=>{o=Date.now(),h+=1,E+=1,S.push({sequence:E,kind:c,path:T(v),at:o}),S.length>x&&S.splice(0,S.length-x)},"stampFsSetEvent");n.watcher.on("add",c=>m("add",c)),n.watcher.on("addDir",c=>m("addDir",c)),n.watcher.on("unlink",c=>m("unlink",c)),n.watcher.on("unlinkDir",c=>m("unlinkDir",c)),n.middlewares.use(te,(c,v)=>{v.setHeader("content-type","application/json"),v.setHeader("cache-control","no-store"),v.end(JSON.stringify({schemaVersion:2,ready:!0,sequence:E,lastFsSetEventAt:o,fsSetEvents:h,recentEvents:S,extraWatchRoots:l,now:Date.now()}))})},resolveId(n){return n===D?N:null},load(n){if(n!==N)return null;const l=f??e.wsPort??0;return X({host:p,port:l,token:r})},async transform(n,l){if(!s||!_)return null;const o=l.split("?")[0]??l;if(o.includes("/node_modules/")||!/\.[jt]sx$/.test(o))return null;const h=await j(n,o);return h?{code:h.code,map:h.map}:null},transformIndexHtml(){if(!(!i||!s))return[{tag:"script",attrs:{type:"module",src:D},injectTo:"head-prepend"}]},handleHotUpdate(n){if(!s)return;const l=n.modules.map(h=>h.file??h.id??null).filter(h=>!!h),o=hmrReloadCause(n.modules);a({id:F(),sessionId:t.sessionId??"pending",timestamp:Date.now(),source:"vite",category:"build",severity:o.willFullReload?"warning":"info",context:{sessionId:t.sessionId??"pending"},fingerprint:I({kind:"hmr",identity:n.file}),title:o.willFullReload?`HMR ${n.file} \u2014 FULL RELOAD (no boundary above ${o.deadEnd??"it"})`:`HMR ${n.file}`,location:{file:n.file},relatedFiles:l,raw:{kind:"hmr",file:n.file,willFullReload:o.willFullReload,deadEnd:o.deadEnd,chain:o.chain,acceptedBy:o.boundaries}})},generateBundle(n,l){if(!s)return;const o=[];let h=0;for(const S of Object.values(l)){const m=S,c=m.fileName;let v;if(m.type==="chunk")v=(m.code??"").length;else{const b=m.source;v=typeof b=="string"?b.length:b instanceof Uint8Array?b.byteLength:0}h+=v;const k=[];if(m.type==="chunk"&&m.modules){for(const[b,A]of Object.entries(m.modules)){const W=A.renderedLength??A.originalLength??0;k.push({id:b,sizeBytes:W})}k.sort((b,A)=>A.sizeBytes-b.sizeBytes)}o.push({name:c,sizeBytes:v,modules:k})}const E={timestamp:Date.now(),totalBytes:h,chunks:o};a({id:F(),sessionId:t.sessionId??"pending",timestamp:Date.now(),source:"rollup",category:"build",severity:"info",context:{sessionId:t.sessionId??"pending"},fingerprint:I({kind:"bundle",identity:"report"}),title:`Bundle report: ${o.length} chunks, ${h} bytes`,raw:{kind:"bundle-report",report:E}})},async closeBundle(){u&&(await u.close(),u=void 0,r=void 0)}}}g(lensmcpVitePlugin,"lensmcpVitePlugin"),d(lensmcpVitePlugin,"lensmcpVitePlugin");const M=12;export function hmrReloadCause(e){const t=[],s=[],p=new Set;let i=null;const f=d(r=>r.file??r.id??"(anonymous module)","nameOf"),u=d((r,a)=>{if(p.has(r))return!1;if(p.add(r),r.isSelfAccepting===!0)return s.length<M&&s.push(f(r)),!1;const y=[...r.importers??[]];if(y.length===0)return i===null&&(i=f(r),t.push(...a.slice(-M),f(r))),!0;let _=!1;for(const w of y){if(w.acceptedHmrDeps?.has(r)){s.length<M&&s.push(f(w));continue}u(w,[...a,f(r)])&&(_=!0)}return _},"walk");return{willFullReload:e.map(r=>u(r,[])).some(Boolean),deadEnd:i,chain:t,boundaries:s}}g(hmrReloadCause,"hmrReloadCause"),d(hmrReloadCause,"hmrReloadCause");async function j(e,t){try{const[{transformSync:s},{lensmcpBabelPlugin:p}]=await Promise.all([import("@babel/core"),import("@lensmcp/react-instrumentation")]),i=s(e,{filename:t,babelrc:!1,configFile:!1,sourceMaps:!0,parserOpts:{plugins:["jsx","typescript"]},plugins:[[p,{rootDir:process.cwd()}]],code:!0});return i?.code?{code:i.code,map:i.map}:null}catch(s){return P(t,s),null}}g(j,"z"),d(j,"runLensmcpBabel");const C=new Set;function P(e,t){C.has(e)||(C.add(e),console.warn(`[lensmcp] could not instrument ${e}: ${t?.message??t}. The file still builds, but its renders, effects and hooks will be MISSING from react://renders/*, react://effects/recent and react://hooks/recent.`))}g(P,"Z"),d(P,"warnTransformFailed");export function resetTransformFailuresForTests(){C.clear()}g(resetTransformFailuresForTests,"resetTransformFailuresForTests"),d(resetTransformFailuresForTests,"resetTransformFailuresForTests");export function handleClientEnvelope(e,t){Q(e,L(t),t.sessionId)}g(handleClientEnvelope,"handleClientEnvelope"),d(handleClientEnvelope,"handleClientEnvelope");function H(e){const t=e?.loc?.file??e?.id;return typeof t!="string"||t.length===0?void 0:{file:t.split("?")[0]||t,...typeof e?.loc?.line=="number"?{line:e.loc.line}:{},...typeof e?.loc?.column=="number"?{column:e.loc.column}:{}}}g(H,"Q"),d(H,"errorLocation");function U(e,t){const s=e.err?.message??"Vite error",p=e.err?.stack,i=H(e.err);return{id:F(),sessionId:t??"pending",timestamp:Date.now(),source:"vite",category:"build",severity:"error",context:{sessionId:t??"pending"},fingerprint:I({kind:"vite",identity:e.type??s,...i?{file:i.file}:{}}),title:s,message:p,...i?{location:i}:{},raw:{kind:"build-diagnostic",...i?{file:i.file}:{},...e.err?.plugin?{plugin:e.err.plugin}:{}}}}g(U,"ee"),d(U,"viteErrorToEvent");function X(e){const t=`ws://${e.host}:${e.port}${e.token?`/?${z}=${encodeURIComponent(e.token)}`:""}`;return`// @lensmcp/client-runtime (inlined by @lensmcp/vite-plugin)
|
|
2
2
|
(() => {
|
|
3
3
|
if (typeof window === 'undefined') return;
|
|
4
4
|
if (window.__LENSMCP_CLIENT__) return;
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
} catch (e) { /* strictly passive \u2014 never break the host page */ }
|
|
43
43
|
})();
|
|
44
44
|
|
|
45
|
-
const WS_URL =
|
|
45
|
+
const WS_URL = ${JSON.stringify(t)};
|
|
46
46
|
let ws = null;
|
|
47
47
|
let queue = [];
|
|
48
48
|
let openOnce = false;
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
const FRAME_EVENTS = 200;
|
|
83
83
|
const MAX_FRAMES_PER_FLUSH = 8;
|
|
84
84
|
// A frame is bounded in BYTES as well as events: 200 events of large bodies still passed the
|
|
85
|
-
// bridge's 1 MiB cap and its 1009 close killed the dev server (issues/foodguard
|
|
85
|
+
// bridge's 1 MiB cap and its 1009 close killed the dev server (issues/closed/foodguard-18). Measured in
|
|
86
86
|
// UTF-16 units and sized so even 3 bytes per unit stays under 1 MiB on the wire.
|
|
87
87
|
const FRAME_CHARS = 300000;
|
|
88
88
|
// What the frame budget may NOT drop: the events that say where a flow, or a document, begins. A flow
|
|
@@ -370,4 +370,4 @@
|
|
|
370
370
|
try { return safeWalk(v, 0, { left: SAFE_MAX_BYTES }); } catch (e) { return String(v); }
|
|
371
371
|
}
|
|
372
372
|
})();
|
|
373
|
-
`}g(
|
|
373
|
+
`}g(X,"te"),d(X,"buildInlineClientRuntime");
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
/** The URL the injected `<script>` loads the client runtime from. */
|
|
3
|
+
export declare const CLIENT_VIRTUAL_ID = "/@lensmcp/client-runtime";
|
|
4
|
+
/** The id `resolveId` maps it to; Vite serves it under `/@id/__x00__lensmcp-client-runtime` as well. */
|
|
5
|
+
export declare const RESOLVED_CLIENT_VIRTUAL_ID = "\0lensmcp-client-runtime";
|
|
6
|
+
/** What the guard needs from the bridge: its origin policy. */
|
|
7
|
+
export interface RuntimeGuardBridge {
|
|
8
|
+
admitsOrigin(origin: string): boolean;
|
|
9
|
+
learnOrigin(origin: string): void;
|
|
10
|
+
}
|
|
11
|
+
/** A Connect-style middleware, structural so this module needs no `connect` or `vite` types. */
|
|
12
|
+
export type RuntimeGuardMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Guards the one response that carries the bridge token: the served client runtime
|
|
15
|
+
* (issues/closed/codex-security-68).
|
|
16
|
+
*
|
|
17
|
+
* The token closes "the port is discoverable" only while a page on another origin cannot READ it, and
|
|
18
|
+
* whether it can is otherwise Vite's `server.cors` — localhost-only by default since Vite 5.4.12/6.0.9,
|
|
19
|
+
* reflect-anything before that and under `cors: true`. So the runtime is handed out on this plugin's
|
|
20
|
+
* terms, whatever the CORS config says:
|
|
21
|
+
*
|
|
22
|
+
* - a request that is provably SAME-ORIGIN (`Sec-Fetch-Site: same-origin`, which page script cannot
|
|
23
|
+
* forge; or, from a client that sends no fetch metadata, no `Origin` or one equal to the origin it
|
|
24
|
+
* addressed) is served, and once the dev server has answered it 200/304 the page's origin is LEARNED
|
|
25
|
+
* by the bridge. That is how a page behind the LensMCP gateway (`https://app.local` — the gateway
|
|
26
|
+
* rewrites `Host` but stamps `X-Forwarded-Host`), on a LAN address, or under a custom /etc/hosts
|
|
27
|
+
* name is admitted with no configuration: the origin the dev server itself served the runtime to.
|
|
28
|
+
* Learning waits for the status so a request Vite's own host check refuses (DNS rebinding) teaches
|
|
29
|
+
* the bridge nothing.
|
|
30
|
+
* - a CROSS-ORIGIN read (`Origin` present) is served only to an origin the bridge would admit anyway
|
|
31
|
+
* — loopback (a backend on :3000 loading the runtime from Vite on :5173) or configured.
|
|
32
|
+
* - a cross-site include with no `Origin` (`<script src>` from another site, `no-cors`) is refused:
|
|
33
|
+
* it cannot read the response, and nothing legitimate needs it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createRuntimeModuleGuard(bridge: RuntimeGuardBridge): RuntimeGuardMiddleware;
|
|
36
|
+
/** Is this a request for the token-bearing runtime module, under any URL Vite would serve it at? */
|
|
37
|
+
export declare function isRuntimeModuleRequest(url: string | undefined): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var m=Object.defineProperty;var r=(e,t)=>m(e,"name",{value:t,configurable:!0});var v=Object.defineProperty,o=r((e,t)=>v(e,"name",{value:t,configurable:!0}),"i");export const CLIENT_VIRTUAL_ID="/@lensmcp/client-runtime",RESOLVED_CLIENT_VIRTUAL_ID="\0lensmcp-client-runtime";export function createRuntimeModuleGuard(e){return(t,i,n)=>{if(!isRuntimeModuleRequest(t.url)){n();return}const c=s(t,"origin"),d=c===void 0?void 0:u(c)??c,l=s(t,"sec-fetch-site"),f=p(t,d,l);if(f===void 0){if(d===void 0?l==="cross-site":!e.admitsOrigin(d)){i.statusCode=403,i.setHeader("content-type","text/plain"),i.end("lensmcp: the client runtime is not served cross-origin to an origin the bridge does not admit");return}n();return}i.once("finish",()=>{(i.statusCode===200||i.statusCode===304)&&e.learnOrigin(f)}),n()}}r(createRuntimeModuleGuard,"createRuntimeModuleGuard"),o(createRuntimeModuleGuard,"createRuntimeModuleGuard");export function isRuntimeModuleRequest(e){if(!e)return!1;let t=e.split("?")[0]?.split("#")[0]??"";try{t=decodeURI(t)}catch{return!1}return t.endsWith(CLIENT_VIRTUAL_ID)||t.endsWith(RESOLVED_CLIENT_VIRTUAL_ID.slice(1))}r(isRuntimeModuleRequest,"isRuntimeModuleRequest"),o(isRuntimeModuleRequest,"isRuntimeModuleRequest");function p(e,t,i){const n=h(e);if(i==="same-origin")return t===void 0?n:u(t);if(i===void 0)return t===void 0||t===n?n:void 0}r(p,"l"),o(p,"sameOriginPage");function h(e){const t=a(s(e,"x-forwarded-proto"))??(e.socket?.encrypted?"https":"http"),i=a(s(e,"x-forwarded-host"))??s(e,"host")??s(e,":authority");return i?u(`${t}://${i}`):void 0}r(h,"h"),o(h,"servedOrigin");function s(e,t){const i=e.headers[t];return typeof i=="string"?i:Array.isArray(i)?i[0]:void 0}r(s,"o"),o(s,"headerOf");function a(e){return e?.split(",")[0]?.trim()||void 0}r(a,"a"),o(a,"firstValue");function u(e){if(e)try{const t=new URL(e);return t.protocol==="http:"||t.protocol==="https:"?t.origin:void 0}catch{return}}r(u,"u"),o(u,"normalizeOrigin");
|
package/lib/types.d.ts
CHANGED
|
@@ -12,6 +12,16 @@ export interface LensmcpViteOptions {
|
|
|
12
12
|
wsPort?: number;
|
|
13
13
|
/** Bind address for the WS. Localhost by default. */
|
|
14
14
|
wsHost?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Browser origins the WS admits beyond the defaults (issues/closed/codex-security-68). Rarely needed:
|
|
17
|
+
* loopback origins are admitted, and so is every origin this dev server itself serves the runtime
|
|
18
|
+
* to — the LensMCP gateway's hosts and LAN addresses included. Name the page's origin here only when
|
|
19
|
+
* it loads the runtime cross-origin from a host that is neither (`https://app.test:8443`, `app.test`,
|
|
20
|
+
* `.app.test` for subdomains, `*` for any). `LENSMCP_BRIDGE_ALLOWED_ORIGINS` adds to this list.
|
|
21
|
+
*
|
|
22
|
+
* Every connection must also carry the per-start token baked into the served runtime.
|
|
23
|
+
*/
|
|
24
|
+
wsAllowedOrigins?: string[];
|
|
15
25
|
/** Inject the client runtime via <script>. Default true. */
|
|
16
26
|
injectClient?: boolean;
|
|
17
27
|
/** Alias `valtio` → the instrumented drop-in in dev. Default true. */
|
|
@@ -22,7 +32,7 @@ export interface LensmcpViteOptions {
|
|
|
22
32
|
* Also attach during `vite build`. Default **false** — everything this plugin does (the WS
|
|
23
33
|
* bridge, the injected client runtime, `data-agent-source` on every element, source paths in
|
|
24
34
|
* `originNodeId`) is a development tool, and `enabled` defaulting to `true` meant a plain
|
|
25
|
-
* `lensmcpVitePlugin()` shipped all of it in a production bundle (issues/codex-security
|
|
35
|
+
* `lensmcpVitePlugin()` shipped all of it in a production bundle (issues/closed/codex-security-39).
|
|
26
36
|
*
|
|
27
37
|
* Set it, or `LENSMCP_VITE_APPLY_ON_BUILD=1`, when you deliberately want an instrumented build —
|
|
28
38
|
* a staging deploy you intend to point the lens at.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/vite-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@babel/core": "^7.0.0",
|
|
18
|
-
"@lensmcp/bridge": "1.
|
|
19
|
-
"@lensmcp/core": "1.
|
|
20
|
-
"@lensmcp/protocol-types": "1.
|
|
21
|
-
"@lensmcp/react-instrumentation": "1.
|
|
22
|
-
"@lensmcp/valtio-instrumentation": "1.
|
|
18
|
+
"@lensmcp/bridge": "1.24.0",
|
|
19
|
+
"@lensmcp/core": "1.24.0",
|
|
20
|
+
"@lensmcp/protocol-types": "1.24.0",
|
|
21
|
+
"@lensmcp/react-instrumentation": "1.24.0",
|
|
22
|
+
"@lensmcp/valtio-instrumentation": "1.24.0",
|
|
23
23
|
"tslib": "^2.3.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|