@vcp-dev/site-edit-bridge 0.4.9 → 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/dist/auto-bootstrap.d.ts +2 -1
- package/dist/auto-bootstrap.js +1 -1
- package/dist/client.d.ts +77 -99
- package/dist/client.js +1 -1
- package/dist/dev-demo-auth-fixture.d.ts +36 -0
- package/dist/dev-demo-auth-fixture.js +1 -0
- package/dist/document-codec.d.ts +13 -14
- package/dist/document-codec.js +1 -1
- package/dist/index.js +4 -3
- package/dist/next-dev-server.d.ts +13 -0
- package/dist/next-dev-server.js +4 -3
- package/dist/protocol.d.ts +61 -17
- package/dist/server.d.ts +43 -2
- package/dist/server.js +2 -2
- package/dist/start-next-dev-server.js +1 -0
- package/dist/window-message.d.ts +34 -5
- package/package.json +9 -4
package/dist/next-dev-server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import je from"node:http";import{existsSync as y,readFileSync as m}from"node:fs";import{createRequire as V}from"node:module";import k from"node:path";import{basename as z,dirname as S,join as b}from"node:path";import{fileURLToPath as G}from"node:url";import ie from"node:http";import{randomBytes as oe,timingSafeEqual as ae}from"node:crypto";import ue from"node:path";import{WebSocket as q,WebSocketServer as ce}from"ws";import{createPreviewSiteEditEngineRuntime as de}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as he,scanNextAppRouterRoutes as le}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as fe}from"@vcp-dev/site-edit-engine/source-watcher";function O(t){let e=new Map,n=[],r=new Map,s=[],i=a=>{let u=e.get(a);if(typeof u=="number")return u;let c=n.length;return n.push(a),e.set(a,c),c},o=a=>{let u=null;for(let c of ne(a)){let d=`${u??"root"}\0${c}`,f=r.get(d);if(typeof f=="number"){u=f;continue}let h=s.length;s.push({parent:u,token:i(c)}),r.set(d,h),u=h}if(u===null)throw new Error("Document key must not be empty");return u};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,routeId:t.routeId,version:t.version,tokens:n,nodes:s,rootKeyNodeIds:t.rootKeys.map(o),entries:t.entries.map(a=>({keyNodeId:o(a.key),tag:a.tag,label:a.label,parentKeyNodeId:a.parentKey?o(a.parentKey):null,childKeyNodeIds:a.childKeys.map(o),operationSummary:a.operationSummary}))}}function ne(t){if(!t)return[];let e=[],n=0,r=N(t,n);if(r<0)return e.push(t),e;for(e.push(t.slice(0,r)),n=r;n<t.length;){if(r=N(t,n+(t.startsWith("::",n)?2:1)),r<0){e.push(t.slice(n));break}e.push(t.slice(n,r)),n=r}return e}function N(t,e){let n=t.indexOf("/",e),r=t.indexOf("::",e);return n<0?r:r<0?n:Math.min(n,r)}var p=class t{constructor(){}static parseMessage(e,n={}){let r=JSON.parse(e);if(!t.isMessage(r,n))throw new Error("Invalid site edit bridge message");return r}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(typeof e=="object"&&e!==null&&"payload"in e&&typeof e.payload=="object"&&e.payload!==null){let n=e.payload,r=typeof n.id=="string"?n.id:"unknown",s=t.isRequestKind(n.kind)?n.kind:"get-document";return{id:r,kind:s}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="get-document"||e==="get-object-summary"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="undo"||e==="redo"}static isMessage(e,n={}){return!l(e)||typeof e.type!="string"?!1:e.type==="request"?t.isRequestPayload(e.payload,n):e.type==="response"||e.type==="error"?l(e.payload)&&typeof e.payload.id=="string":e.type==="event"?l(e.payload)&&l(e.payload.event):e.type==="auth"?l(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?l(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,n={}){if(!l(e)||typeof e.id!="string"||!t.isRequestKind(e.kind))return!1;switch(e.kind){case"get-document":return typeof e.routeId=="string";case"get-object-summary":case"get-object-edit-context":case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&(e.routeId===void 0||typeof e.routeId=="string");case"get-source-tree":return!0;case"get-source-file":return typeof e.path=="string";case"execute":return re(e.request)&&(n.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return!0}}},R=class{constructor(e){this.options=e,this.messageQueue=new _,this.eventDispatcher=new E(n=>{this.sendJson(n)}),this.unsubscribe=e.engine.subscribe(n=>{this.eventDispatcher.push(n)}),e.socket.onmessage=n=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(n.data))}catch(r){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:r instanceof Error?r.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe()}}async handleMessage(e){try{let n=e;if(n.type!=="request")return;let r=n.payload,{id:s}=r;switch(r.kind){case"get-document":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:{ok:!0,data:O((await this.options.engine.getDocument(r.routeId)).data)}}});return;case"get-object-summary":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getObjectSummary(r.key,r.routeId)}});return;case"get-object-edit-context":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getObjectEditContext(r.key,r.routeId)}});return;case"get-class-name-source":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getClassNameSource(r.key,r.routeId)}});return;case"get-source-tree":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getSourceTree()}});return;case"get-source-file":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getSourceFile({path:r.path})}});return;case"resolve-source-location":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.resolveSourceLocation({key:r.key,routeId:r.routeId})}});return;case"execute":await this.handleBufferedOperation(s,r.kind,()=>this.options.engine.execute(r.request));return;case"undo":await this.handleBufferedOperation(s,r.kind,()=>this.options.engine.undo());return;case"redo":await this.handleBufferedOperation(s,r.kind,()=>this.options.engine.redo());return}}catch(n){let r=p.extractRequestInfo(e);this.sendJson({type:"error",payload:{...r,message:n instanceof Error?n.message:"Unknown websocket operation failure"}})}}async handleRawMessage(e){try{await this.handleMessage(p.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let r=n instanceof Error?n.message:"Invalid websocket message",s=se(e);this.sendJson({type:"error",payload:{...s,message:r}})}}sendJson(e){this.options.socket.send(p.stringifyMessage(e))}async handleBufferedOperation(e,n,r){this.eventDispatcher.beginBuffering();try{let s=await r();this.sendJson({type:"response",payload:{id:e,kind:n,result:s}}),this.eventDispatcher.flush()}catch(s){this.sendJson({type:"error",payload:{id:e,kind:n,message:s instanceof Error?s.message:"Unknown websocket operation failure"}}),this.eventDispatcher.flush()}}},E=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let n={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(n);return}this.send(n)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(n=>{this.send(n)})}},_=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let n=async()=>{this.queued-=1,await e()},r=this.queue.then(n,n);return this.queue=r.then(()=>{},()=>{}),r}};function l(t){return typeof t=="object"&&t!==null}function re(t){return l(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&(t.routeId===void 0||typeof t.routeId=="string")&&typeof t.kind=="string"&&l(t.target)&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&l(t.params)}function se(t){try{return p.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}import{SiteEditOperationRequestSchema as pe}from"@vcp-dev/contracts";var ge="127.0.0.1",me=30218,ye="/__vcp/site-edit/bridge",Se=6*1024*1024,be=3e3,K=50,M="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function F(t,e){let n=new P(we(e),t,!1);return n.start({listen:!1}),n}var P=class{constructor(e,n=ie.createServer((s,i)=>{i.writeHead(404),i.end("VCP site edit bridge server")}),r=!0){this.options=e,this.server=n,this.closeHttpServerOnStop=r,this.runtime=null,this.routes=[],this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1}start(e={}){if(this.isStarted)return;this.refreshRoutes(),this.runtime=de({siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,routes:this.routes});let n=Ie({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=n.stop,this.stopWatching=new fe({projectRoot:this.options.projectRoot,watch_dirs:this.options.runtime.watch_dirs,debounce_ms:this.options.runtime.watch_debounce_ms??K,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:r=>this.handleFileChanged(r),on_batch_change:r=>this.handleFilesChanged(r)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host),this.isStarted=!0}getHealth(){let e=!!this.runtime;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.closeHttpServerOnStop&&this.server.close();let e=Re(),n=xe(this.options);e.get(n)===this&&e.delete(n),this.isStarted=!1}createBridgeEngine(){return{getDocument:async e=>{let{runtime:n,resolvedRouteId:r}=await this.activateRoute(e);return n.getDocument(r)},getObjectSummary:async(e,n)=>this.getRuntime().getObjectSummary(e,n?this.resolveRouteId(n):void 0),getObjectEditContext:async(e,n)=>this.getRuntime().getObjectEditContext(e,n?this.resolveRouteId(n):void 0),getClassNameSource:async(e,n)=>{let r=this.getRuntime(),s=n?this.resolveRouteId(n):void 0;return await r.refreshSourceForKey(e,s),r.getClassNameSource(e,s)},getSourceTree:async()=>this.getRuntime().getSourceTree(),getSourceFile:async e=>this.getRuntime().getSourceFile(e),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,routeId:e.routeId?this.resolveRouteId(e.routeId):void 0}),execute:async e=>this.getRuntime().executeOperation(this.normalizeRequest(e)),undo:async()=>this.getRuntime().undo(),redo:async()=>this.getRuntime().redo(),subscribe:e=>this.getRuntime().subscribe(e)}}async activateRoute(e){let n=this.getRuntime(),r=this.resolveRouteId(e);return await n.setActiveRoute(r),{runtime:n,resolvedRouteId:r}}normalizeRequest(e){return e.target.kind!=="route"?e.routeId?{...e,routeId:this.resolveRouteId(e.routeId)}:e:{...e,target:{...e.target,routeId:this.resolveRouteId(e.target.routeId)}}}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let r of e)this.pendingChangedFiles.add(r);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??K;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},n),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((n,r)=>n.localeCompare(r));this.pendingChangedFiles.clear(),this.refreshRoutes();for(let n of e)this.runtime?.notifyFileChanged(n)}refreshRoutes(){let e=this.options.runtime.routes?.length?this.options.runtime.routes:le(this.options.projectRoot),n=new Map(this.routes.map(s=>[s.routeId,s.entryFile])),r=new Set(e.map(s=>s.routeId));this.routes=e;for(let s of e)n.get(s.routeId)!==s.entryFile&&this.runtime?.registerRoute(s.routeId,s.entryFile);for(let s of n.keys())r.has(s)||this.runtime?.unregisterRoute(s)}resolveRouteId(e){let n=he(e,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${e}`);return n}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function we(t){return{siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:ue.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??ge,port:t.port??me,path:t.path??ye,sessionKey:t.sessionKey??Te(),allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{}}}function Re(){let t=globalThis;return t[M]??=new Map,t[M]}function xe(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}`}function Ie(t){let e=new ce({maxPayload:Se,noServer:!0}),n=new WeakSet,r=i=>{new R({socket:new T(i),engine:t.engine,validateOperationRequest:o=>pe.safeParse(o).success})},s=(i,o,a)=>{if(!ke(i,t))return;let u=ve(i,t);if(!u){o.write(`HTTP/1.1 401 Unauthorized\r
|
|
1
|
+
import Ge from"node:http";import{existsSync as I,readFileSync as S}from"node:fs";import{createRequire as ue}from"node:module";import x from"node:path";import{basename as ce,dirname as R,join as E}from"node:path";import{fileURLToPath as de}from"node:url";import{createEd25519MutationContextVerifier as Qe}from"@vcp-dev/site-edit-engine";import Re from"node:http";import{createHash as X,randomBytes as Ee,timingSafeEqual as Ce}from"node:crypto";import O from"node:path";import{WebSocket as ee,WebSocketServer as be}from"ws";import{createPreviewSiteEditEngineRuntime as ve,openFileHistoryStore as ke}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as _e,scanNextAppRouterRoutes as Pe}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as Te}from"@vcp-dev/site-edit-engine/source-watcher";function V(t){let e=new Map,n=[],i=new Map,s=[],r=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],o=new Map(r.map((a,c)=>[a,c])),u=t.pageComposition.status==="supported"?{status:"supported",containerKey:t.pageComposition.containerKey,hasBusinessMembers:t.pageComposition.hasBusinessMembers,placementUnitKeys:t.pageComposition.placementUnitKeys,placements:t.pageComposition.placements.map(a=>({placementKey:a.placementKey,placementUnitKey:a.placementUnitKey,relation:a.relation}))}:{status:"unsupported",reason:t.pageComposition.reason,diagnostics:t.pageComposition.diagnostics},d=a=>{let c=e.get(a);if(typeof c=="number")return c;let f=n.length;return n.push(a),e.set(a,f),f},h=a=>{let c=null;for(let f of Se(a)){let g=`${c??"root"}\0${f}`,C=i.get(g);if(typeof C=="number"){c=C;continue}let b=s.length;s.push([c,d(f)]),i.set(g,b),c=b}if(c===null)throw new Error("Document key must not be empty");return c};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,routeId:t.routeId,version:t.version,pageComposition:u,tokens:n,nodes:s,entries:t.entries.map(a=>{let c=a.pageCompositionUnitKey?o.get(a.pageCompositionUnitKey)??-1:-1;if(a.pageCompositionUnitKey&&c<0)throw new Error(`Render entry ${a.key} references unknown page composition unit ${a.pageCompositionUnitKey}`);return[h(a.key),ye(a.operationSummary),...c>=0?[c]:[]]})}}function ye(t){return(t.canUpdateText?1:0)|(t.canInsertChild?2:0)|(t.canMove?4:0)|(t.canRemove?8:0)}function Se(t){if(!t)return[];let e=[],n=0,i=$(t,n);if(i<0)return e.push(t),e;for(e.push(t.slice(0,i)),n=i;n<t.length;){if(i=$(t,n+(t.startsWith("::",n)?2:1)),i<0){e.push(t.slice(n));break}e.push(t.slice(n,i)),n=i}return e}function $(t,e){let n=t.indexOf("/",e),i=t.indexOf("::",e);return n<0?i:i<0?n:Math.min(n,i)}var p=class t{constructor(){}static parseMessage(e,n={}){let i=JSON.parse(e);if(!t.isMessage(i,n))throw new Error("Invalid site edit bridge message");return i}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(typeof e=="object"&&e!==null&&"payload"in e&&typeof e.payload=="object"&&e.payload!==null){let n=e.payload,i=typeof n.id=="string"?n.id:"unknown",s=t.isRequestKind(n.kind)?n.kind:"get-document";return{id:i,kind:s}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="initialize-session"||e==="get-document"||e==="get-history-status"||e==="get-style-context"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="undo"||e==="redo"}static isMessage(e,n={}){return!l(e)||typeof e.type!="string"?!1:e.type==="request"?t.isRequestPayload(e.payload,n):e.type==="response"||e.type==="error"?l(e.payload)&&typeof e.payload.id=="string":e.type==="event"?l(e.payload)&&l(e.payload.event):e.type==="auth"?l(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?l(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,n={}){if(!l(e)||typeof e.id!="string"||!t.isRequestKind(e.kind))return!1;switch(e.kind){case"initialize-session":return typeof e.routeId=="string"&&(e.mutationContext===void 0||m(e.mutationContext));case"get-document":case"get-style-context":return typeof e.routeId=="string";case"get-object-edit-context":case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&(e.routeId===void 0||typeof e.routeId=="string");case"get-history-status":return e.mutationContext===void 0;case"get-source-tree":return!0;case"get-source-file":return typeof e.path=="string";case"execute":return Ie(e.request)&&m(e.request.mutationContext)&&(n.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return m(e.mutationContext)}}},k=class{constructor(e){this.options=e,this.messageQueue=new M,this.eventDispatcher=new D(n=>{this.sendJson(n)}),this.unsubscribe=e.engine.subscribe(n=>{this.eventDispatcher.push(n)}),e.socket.onmessage=n=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(n.data))}catch(i){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:i instanceof Error?i.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe()}}async handleMessage(e){try{let n=e;if(n.type!=="request")return;let i=n.payload,{id:s}=i;switch(i.kind){case"initialize-session":{let r=await this.options.engine.initializeSession({routeId:i.routeId,...i.mutationContext?{mutationContext:i.mutationContext}:{}});this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:{ok:!0,data:{document:V(r.data.document),historyStatus:r.data.historyStatus}}}});return}case"get-document":this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:{ok:!0,data:V((await this.options.engine.getDocument(i.routeId)).data)}}});return;case"get-history-status":this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:await this.options.engine.getHistoryStatus()}});return;case"get-style-context":this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:await this.options.engine.getStyleContext(i.routeId)}});return;case"get-object-edit-context":this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:await this.options.engine.getObjectEditContext(i.key,i.routeId)}});return;case"get-class-name-source":this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:await this.options.engine.getClassNameSource(i.key,i.routeId)}});return;case"get-source-tree":this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:await this.options.engine.getSourceTree()}});return;case"get-source-file":this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:await this.options.engine.getSourceFile({path:i.path})}});return;case"resolve-source-location":this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:await this.options.engine.resolveSourceLocation({key:i.key,routeId:i.routeId})}});return;case"execute":{if(!m(i.request.mutationContext))throw new Error("Invalid site edit bridge message");let r=i.request.mutationContext;await this.handleBufferedOperation(s,i.kind,()=>this.options.engine.execute(i.request,r));return}case"undo":if(!m(i.mutationContext))throw new Error("Invalid site edit bridge message");await this.handleBufferedOperation(s,i.kind,()=>this.options.engine.undo(i.mutationContext));return;case"redo":if(!m(i.mutationContext))throw new Error("Invalid site edit bridge message");await this.handleBufferedOperation(s,i.kind,()=>this.options.engine.redo(i.mutationContext));return}}catch(n){let i=p.extractRequestInfo(e);this.sendJson({type:"error",payload:{...i,message:W(n),code:J(n)}})}}async handleRawMessage(e){try{await this.handleMessage(p.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let i=n instanceof Error?n.message:"Invalid websocket message",s=xe(e);this.sendJson({type:"error",payload:{...s,message:i}})}}sendJson(e){this.options.socket.send(p.stringifyMessage(e))}async handleBufferedOperation(e,n,i){this.eventDispatcher.beginBuffering();try{let s=await i();this.sendJson({type:"response",payload:{id:e,kind:n,result:s}}),this.eventDispatcher.flush()}catch(s){this.sendJson({type:"error",payload:{id:e,kind:n,message:W(s),code:J(s)}}),this.eventDispatcher.flush()}}},D=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let n={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(n);return}this.send(n)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(n=>{this.send(n)})}},M=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let n=async()=>{this.queued-=1,await e()},i=this.queue.then(n,n);return this.queue=i.then(()=>{},()=>{}),i}};function l(t){return typeof t=="object"&&t!==null}function Ie(t){return l(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&(t.routeId===void 0||typeof t.routeId=="string")&&typeof t.kind=="string"&&l(t.target)&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&l(t.params)}function m(t){return we(t)}function J(t){if(!t||typeof t!="object")return;let e=t.code;return typeof e=="string"&&e.length>0?e:void 0}function W(t){if(t instanceof Error)return t.message;if(!t||typeof t!="object")return"Unknown websocket operation failure";let e=t.message;return typeof e=="string"&&e.length>0?e:"Unknown websocket operation failure"}function we(t){return l(t)?typeof t.editingSessionId=="string"&&t.editingSessionId.length>0&&typeof t.lockId=="string"&&t.lockId.length>0&&typeof t.ownerId=="string"&&t.ownerId.length>0&&typeof t.leaseAssertion=="string"&&t.leaseAssertion.length>0:!1}function xe(t){try{return p.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}var _=class{constructor(){this.tail=Promise.resolve()}run(e){let n=this.tail.then(e,e);return this.tail=n.then(()=>{},()=>{}),n}};import{SiteEditOperationRequestSchema as He}from"@vcp-dev/contracts";var Be="127.0.0.1",Ve=30218,De="/__vcp/site-edit/bridge",te=6*1024*1024,Me=te*2,Ne=3e3,z=50,G="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function ne(t,e,n={}){let i=new j(Oe(e),t,!1,n);return i.start({listen:!1}),i}var j=class{constructor(e,n=Re.createServer((r,o)=>{o.writeHead(404),o.end("VCP site edit bridge server")}),i=!0,s={}){this.options=e,this.server=n,this.closeHttpServerOnStop=i,this.dependencies=s,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new _,this.eventListeners=new Set,this.routes=[],this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.runtimeVersionFloor=0,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve()}start(e={}){if(this.isStarted)return;this.refreshRoutes(),this.isStarted=!0,this.createRuntime();let n=Ke({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=n.stop,this.stopWatching=new Te({projectRoot:this.options.projectRoot,watch_dirs:this.options.runtime.watch_dirs,debounce_ms:this.options.runtime.watch_debounce_ms??z,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:i=>this.handleFileChanged(i),on_batch_change:i=>this.handleFilesChanged(i)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host)}getHealth(){let e=!!this.runtime;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.closeHttpServerOnStop&&this.server.close();let e=Ue(),n=Ae(this.options);e.get(n)===this&&e.delete(n),this.isStarted=!1}createRuntime(e,n=null){let i=this.runtime;this.activeHistoryStore=e??null;let s=(this.dependencies.createRuntime??ve)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,routes:this.routes,onEvent:r=>{"version"in r&&typeof r.version=="number"&&this.recordRuntimeVersion(r.version);for(let o of this.eventListeners)o(r)},...e?{historyStore:e}:{},initialDocumentVersion:this.runtimeVersionFloor,...this.options.mutationContextVerifier?{mutationContextVerifier:this.options.mutationContextVerifier}:{}});this.runtime=s,this.activeHistorySessionId=n,i?.dispose()}createBridgeEngine(){return{initializeSession:e=>this.mutationQueue.run(async()=>{if(e.mutationContext){let o=await this.verifyMutationContext(e.mutationContext);if(!o.ok)throw new Error(`${o.code}: ${o.message??"Mutation session verification failed"}`);let u=await this.bindHistorySession(e.mutationContext.editingSessionId);if(!u.ok)throw new Error(`${u.code}: ${u.message}`)}let{runtime:n,resolvedRouteId:i}=await this.activateRoute(e.routeId),s=n.getDocument(i).data;await this.recordRuntimeVersion(s.version);let r=(await n.getHistoryStatus()).data;return{ok:!0,data:{document:s,historyStatus:r}}}),getDocument:async e=>{let{runtime:n,resolvedRouteId:i}=await this.activateRoute(e),s=n.getDocument(i);return await this.recordRuntimeVersion(s.data.version),s},getHistoryStatus:async()=>this.getRuntime().getHistoryStatus(),getStyleContext:async e=>{let{runtime:n,resolvedRouteId:i}=await this.activateRoute(e);return n.getStyleContext(i)},getObjectEditContext:async(e,n)=>this.getRuntime().getObjectEditContext(e,n?this.resolveRouteId(n):void 0),getClassNameSource:async(e,n)=>{let i=this.getRuntime(),s=n?this.resolveRouteId(n):void 0;return i.getClassNameSource(e,s)},getSourceTree:async()=>this.getRuntime().getSourceTree(),getSourceFile:async e=>this.getRuntime().getSourceFile(e),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,routeId:e.routeId?this.resolveRouteId(e.routeId):void 0}),execute:(e,n)=>this.mutationQueue.run(async()=>{let i=n??e.mutationContext,s=await this.verifyMutationContext(i);if(!s.ok)return y(s,e);let r=await this.bindHistorySession(i.editingSessionId);if(!r.ok)return y(r,e);let o=await this.getRuntime().executeOperation(this.normalizeRequest(e),i);return o.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(o.data.resultVersion),o}),undo:e=>this.mutationQueue.run(async()=>{let n=await this.verifyMutationContext(e);if(!n.ok)return y(n);let i=await this.bindHistorySession(e.editingSessionId);if(!i.ok)return y(i);let s=await this.getRuntime().undo(e);return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion),s}),redo:e=>this.mutationQueue.run(async()=>{let n=await this.verifyMutationContext(e);if(!n.ok)return y(n);let i=await this.bindHistorySession(e.editingSessionId);if(!i.ok)return y(i);let s=await this.getRuntime().redo(e);return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion),s}),subscribe:e=>(this.eventListeners.add(e),()=>this.eventListeners.delete(e))}}async bindHistorySession(e){if(!this.options.historyRoot)return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history is not configured."};if(this.activeHistorySessionId===e)return{ok:!0};let n=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===e)return;let i=await(this.dependencies.openHistoryStore??ke)(je({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e}));if(!this.isStarted)return;let s=i.readDocumentVersion?await i.readDocumentVersion():0;this.runtimeVersionFloor=Math.max(this.runtimeVersionFloor,s),this.createRuntime(i,e)});this.historySessionTransition=n.catch(()=>{});try{return await n,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(e){this.runtimeVersionFloor=Math.max(this.runtimeVersionFloor,e);let n=this.activeHistoryStore;return n?.writeDocumentVersion?(this.versionPersistence=this.versionPersistence.then(()=>n.writeDocumentVersion(this.runtimeVersionFloor)).catch(()=>{}),this.versionPersistence):Promise.resolve()}async activateRoute(e){let n=this.getRuntime(),i=this.resolveRouteId(e);return await n.setActiveRoute(i),{runtime:n,resolvedRouteId:i}}normalizeRequest(e){return e.target.kind!=="route"?e.routeId?{...e,routeId:this.resolveRouteId(e.routeId)}:e:{...e,target:{...e.target,routeId:this.resolveRouteId(e.target.routeId)}}}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let i of e)this.pendingChangedFiles.add(i);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??z;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},n),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((n,i)=>n.localeCompare(i));this.pendingChangedFiles.clear(),this.refreshRoutes();for(let n of e)this.runtime?.notifyFileChanged(n)}refreshRoutes(){let e=this.options.runtime.routes?.length?this.options.runtime.routes:Pe(this.options.projectRoot),n=new Map(this.routes.map(s=>[s.routeId,s.entryFile])),i=new Set(e.map(s=>s.routeId));this.routes=e;for(let s of e)n.get(s.routeId)!==s.entryFile&&this.runtime?.registerRoute(s.routeId,s.entryFile);for(let s of n.keys())i.has(s)||this.runtime?.unregisterRoute(s)}resolveRouteId(e){let n=_e(e,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${e}`);return n}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}async verifyMutationContext(e){if(!e)return{ok:!1,code:"MUTATION_SESSION_REQUIRED",message:"A mutation session is required for write operations."};if(!this.options.mutationContextVerifier||!this.options.tenantId)return{ok:!1,code:"LEASE_ASSERTION_INVALID",message:"Lease assertion verification is unavailable."};try{return await this.options.mutationContextVerifier.verify(e,{tenantId:this.options.tenantId,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e.editingSessionId,lockId:e.lockId,ownerId:e.ownerId})}catch{return{ok:!1,code:"LEASE_ASSERTION_INVALID",message:"Lease assertion verification failed."}}}};function Oe(t){return{tenantId:t.tenantId,siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:O.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??Be,port:t.port??Ve,path:t.path??De,sessionKey:t.sessionKey??ze(),historyRoot:t.historyRoot,mutationContextVerifier:t.mutationContextVerifier,allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{}}}function je(t){return O.join(O.resolve(t.root),Q(t.siteId),Q(t.snapshotId),X("sha256").update(t.editingSessionId).digest("hex").slice(0,32))}function Q(t){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(t)&&t!=="."&&t!==".."?t:X("sha256").update(t).digest("hex").slice(0,32)}function Ue(){let t=globalThis;return t[G]??=new Map,t[G]}function Ae(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}:${t.historyRoot??""}`}function y(t,e){return{ok:!1,error:{code:t.code,message:t.message},data:{requestId:e?.id??"mutation-context",kind:e?.kind??"update-text",target:e?.target??{kind:"node",key:""},resultVersion:e?.documentVersion??0}}}function Ke(t){let e=new be({maxPayload:te,noServer:!0}),n=new WeakSet,i=r=>{new k({socket:new U(r),engine:t.engine,validateOperationRequest:o=>He.safeParse(o).success})},s=(r,o,u)=>{if(!Fe(r,t))return;let d=Le(r,t);if(!d){o.write(`HTTP/1.1 401 Unauthorized\r
|
|
2
2
|
Connection: close\r
|
|
3
3
|
\r
|
|
4
|
-
`),o.destroy();return}
|
|
4
|
+
`),o.destroy();return}d.authenticatedByHeader&&n.add(r),e.handleUpgrade(r,o,u,h=>{e.emit("connection",h,r)})};return t.server.on("upgrade",s),e.on("connection",(r,o)=>{if(n.has(o)){i(r);return}qe(r,t.sessionKey,()=>{i(r)})}),e.stop=()=>{t.server.off("upgrade",s);for(let r of e.clients)r.close(1001,"bridge stopped");e.close()},e}var U=class{constructor(e){this.socket=e,this.onmessage=null,this.onclose=null,e.on("error",()=>{}),e.on("message",n=>{this.onmessage?.({data:n.toString()})}),e.on("close",()=>{this.onclose?.()})}send(e){if(this.socket.readyState===ee.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(e,"utf8")>Me){this.socket.close(1013,"bridge backpressure");return}this.socket.send(e)}}close(e,n){this.socket.close(e,n)}};function Fe(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function Le(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let i=N(t.headers["x-vcp-site-edit-key"]),s=ie(i,e.sessionKey),r=N(t.headers.host),o=N(t.headers.origin);return r&&!$e(r,e.host,o)?null:o?We(o,r,e.allowedOrigins)?{authenticatedByHeader:s}:null:s?{authenticatedByHeader:!0}:null}function qe(t,e,n){let i=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},Ne),s,r=()=>{clearTimeout(i),t.off("message",s),t.off("close",r)},o=()=>{r(),t.close(1008,"bridge authentication failed")};s=u=>{try{let d=p.parseMessage(u.toString());if(d.type!=="auth"||!ie(d.payload.key,e)){o();return}r(),t.readyState===ee.OPEN&&t.send(p.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),n()}catch{o()}},t.on("message",s),t.on("close",r)}function ie(t,e){if(!t)return!1;let n=Buffer.from(t),i=Buffer.from(e);return n.length===i.length&&Ce(n,i)}function $e(t,e,n){let i=Z(t),s=Z(e);return i===s||Y(s)&&Y(i)&&(!n||A(n,t))||Je(s)&&(!n||A(n,t))}function Je(t){return t==="0.0.0.0"||t==="::"}function Y(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function We(t,e,n){return n.includes(t)||(e?A(t,e):!1)}function A(t,e){try{return new URL(t).host===se(e)}catch{return!1}}function Z(t){let e=se(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function se(t){return t.trim().toLowerCase()}function N(t){return Array.isArray(t)?t[0]:t}function ze(){return Ee(32).toString("base64url")}var Ye="next",Ze="127.0.0.1",Xe=3e3,P="/__vcp/site-edit/bridge",he="/__vcp/site-edit/health",T="/__vcp/site-edit/client/",et=`${T}auto-bootstrap.js`,fe='data-vcp-site-edit-bootstrap="true"',tt=["next.config.ts","next.config.mts","next.config.cts","next.config.js","next.config.mjs","next.config.cjs"],nt="@vcp-dev/site-edit-engine/webpack-loader",it=/@vcp\/site-edit-engine\/(?:webpack-loader|site-edit-instrumentation-loader)/,st=[/\bconfig\.webpack\s*=/,/\bwebpack\s*\([^)]*\)\s*\{/,/\bwebpack\s*:\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>/,/\bwebpack\s*:\s*(?:async\s+)?function\b/],rt=new Set(["auto-bootstrap.js","client.js","designer-trace.js","document-codec.js","protocol.js","window-message.js"]);async function fn(t={}){let e=x.resolve(t.projectRoot??process.cwd()),n=t.host??Ze,i=t.port??At(process.env.PORT)??Xe,s=t.bridgePath??P,r=gt(s),o=Ht(),u=t.basePath??process.env.VCP_SITE_PREVIEW_BASE_PATH??"/",{tenantId:d,mutationContextVerifier:h}=ot(t,process.env),a=await Nt(e),c=ct(e),f=a({dev:!0,dir:e,hostname:n,port:i,...dt(c)}),g=f.getRequestHandler(),C=t.siteId??process.env.VCP_SITE_ID??"site_preview",b=t.snapshotId??process.env.VCP_SNAPSHOT_ID??Ut(e),L=t.historyRoot??process.env.VCP_SITE_EDIT_HISTORY_ROOT,q=L?.trim()?x.resolve(L):void 0;await f.prepare();let H=null,w=Ge.createServer((ge,v)=>{Rt({basePath:u,bridgePath:s,bridgeHealthPath:r,bridgeSessionKey:o,getBridgeHealth:()=>H?.getHealth()??Ct(s),handle:g,request:ge,response:v}).catch(me=>{console.error(me),v.headersSent||(v.statusCode=500),v.end("site edit preview request failed")})});H=ne(w,{...d?{tenantId:d}:{},siteId:C,snapshotId:b,projectRoot:e,basePath:u,host:n,port:i,path:s,sessionKey:o,...q?{historyRoot:q}:{},...h?{mutationContextVerifier:h}:{}}),lt({server:w,bridgePath:s,handleUpgrade:f.getUpgradeHandler()});let B=()=>{H?.stop(),f.close().catch(()=>{}),w.close()};return process.on("SIGINT",B),process.on("SIGTERM",B),await Kt(w,i,n),{bundler:c,close:B,server:w}}function ot(t,e){let n=(t.tenantId??e.VCP_TENANT_ID)?.trim()||void 0,i=t.requireMutationContextVerifier??ut(e.VCP_DESIGNER_SESSION_REQUIRE_VERIFIER),s=at(t,e);if(i&&!n)throw new Error("tenantId is required when mutation context verification is enabled.");if(i&&!s)throw new Error("A mutation context verifier is required when mutation context verification is enabled.");return{...n?{tenantId:n}:{},...s?{mutationContextVerifier:s}:{},requireMutationContextVerifier:i}}function at(t,e){if(t.mutationContextVerifier)return t.mutationContextVerifier;let n=(t.mutationContextPublicKeyPem??e.VCP_DESIGNER_SESSION_PUBLIC_KEY_PEM)?.trim();if(n)try{let i=e.VCP_DESIGNER_SESSION_KEY_ID?.trim();if(!i)throw new Error("VCP_DESIGNER_SESSION_KEY_ID is required.");return Qe({publicKey:n.replace(/\\n/g,`
|
|
5
|
+
`),issuer:e.VCP_DESIGNER_SESSION_ISSUER??"vcp-api",audience:e.VCP_DESIGNER_SESSION_AUDIENCE??"site-edit-engine",keyId:i})}catch(i){throw new Error("VCP_DESIGNER_SESSION_PUBLIC_KEY_PEM is invalid.",{cause:i})}}function ut(t){return t==="1"||t==="true"}function ct(t){let e=ht(t);return e&&ft(e)?"webpack":"turbopack"}function dt(t){return t==="webpack"?{webpack:!0}:{turbopack:!0}}function ht(t){for(let e of tt){let n=E(t,e);if(I(n))return S(n,"utf8")}return null}function ft(t){return t.includes(nt)?!0:it.test(t)&&st.some(e=>e.test(t))}function lt(t){let e=(n,i,s)=>{pt(n.url,t.bridgePath)||t.handleUpgrade(n,i,s).catch(r=>{console.error(r),i.destroyed||i.destroy()})};return t.server.on("upgrade",e),()=>t.server.off("upgrade",e)}function pt(t,e){return new URL(t??"/","http://localhost").pathname===e}function gt(t=P){if(t===P)return he;let e=(t.startsWith("/")?t:`/${t}`).replace(/\/+$/,"");return e.endsWith("/bridge")?`${e.slice(0,-7)}/health`:e?`${e}/health`:"/health"}function mt(t){let e=JSON.stringify({basePath:t.basePath,wsPath:t.bridgePath,bridge_session_key:t.bridgeSessionKey,defer_initial_sync:!0,runtime_versions:t.runtimeVersions??yt()});return`<script type="module" ${fe}>import { startSiteEditAutoBootstrap } from ${JSON.stringify(et)};void startSiteEditAutoBootstrap(window, ${e}).catch(() => undefined);</script>`}function yt(){return{bridge:K("@vcp-dev/site-edit-bridge"),contracts:K("@vcp-dev/contracts"),engine:K("@vcp-dev/site-edit-engine")}}function K(t){let e=ue(import.meta.url);try{let n=e.resolve(t),i=St(n,t);if(!i)return;let s=JSON.parse(S(i,"utf8"));return typeof s.version=="string"?s.version:void 0}catch{return}}function St(t,e){let n=x.parse(t).root,i=R(t);for(;i&&i!==n;){let s=E(i,"package.json");if(I(s))try{if(JSON.parse(S(s,"utf8")).name===e)return s}catch{return}i=R(i)}}function It(t,e){if(t.includes(fe))return t;let n=mt(e),i=/<\/body\s*>/i.exec(t);return i?`${t.slice(0,i.index)}${n}${t.slice(i.index)}`:`${t}${n}`}function ln(t,e=R(de(import.meta.url)),n=I){return le(t,e,n)?.path??null}function le(t,e=R(de(import.meta.url)),n=I){let i=new URL(t??"/","http://localhost").pathname;if(!i.startsWith(T))return null;let s=i.slice(T.length);if(!rt.has(s))return null;let r=F(Ot(e),s);if(ce(e)!=="src")return{path:r,requiresTranspile:!1};let o=F(e,s.replace(/\.js$/,".ts"));return n(o)?{path:o,requiresTranspile:!0}:{path:r,requiresTranspile:!1}}async function wt(t,e){let n=le(t.url);if(!n)return!1;if(!I(n.path))return e.writeHead(404),e.end("site edit client asset not found"),!0;let i=n.requiresTranspile?await xt(S(n.path,"utf8")):S(n.path);return e.writeHead(200,{"cache-control":"no-store","content-type":"text/javascript; charset=utf-8"}),e.end(i),!0}async function xt(t){let e=await import("typescript");return e.transpileModule(t,{compilerOptions:{module:e.ModuleKind.ES2022,target:e.ScriptTarget.ES2022}}).outputText}async function Rt({basePath:t,bridgePath:e,bridgeHealthPath:n,bridgeSessionKey:i,getBridgeHealth:s,handle:r,request:o,response:u}){if(Et(o,u,n,s)||await wt(o,u))return;let d=bt(o),h=vt(o),a=d||h?Pt(u,{basePath:t,bridgePath:e,bridgeSessionKey:i,patchNextDevChunk:h}):()=>{};try{await r(o,u)}catch(c){throw a(),c}}function Et(t,e,n,i){if(t.method!=="GET"||new URL(t.url??"/","http://localhost").pathname!==n)return!1;let r=i();return e.writeHead(r.ok?200:503,{"cache-control":"no-store","content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(r)),!0}function Ct(t){return{ok:!1,bridgePath:t,runtimeReady:!1,routesIndexed:0,sessionRequired:!0}}function bt(t){return kt(t)?(t.headers["accept-encoding"]="identity",!0):!1}function vt(t){return _t(t)?(t.headers["accept-encoding"]="identity",!0):!1}function kt(t){if(t.method!=="GET")return!1;let e=new URL(t.url??"/","http://localhost").pathname;if(e.startsWith("/_next/")||e.startsWith(T)||e===P||e===he)return!1;let n=t.headers.accept??"";return n===""||n.includes("text/html")}function _t(t){if(t.method!=="GET"||!String(t.headers["user-agent"]??"").includes("Firefox"))return!1;let n=new URL(t.url??"/","http://localhost").pathname;return n.startsWith("/_next/static/chunks/")&&n.endsWith(".js")}function Pt(t,e){let n=[],i=t,s=i.flushHeaders.bind(t),r=i.write.bind(t),o=i.end.bind(t);return i.flushHeaders=()=>{},i.write=(u,d,h)=>(u!==void 0&&n.push(re(u,oe(d))),ae(d),ae(h),!0),i.end=(u,d,h)=>{let a=typeof u=="function"?void 0:u;a!==void 0&&n.push(re(a,oe(d))),i.write=r,i.end=o,i.flushHeaders=s;let c=Buffer.concat(n),f=Tt(t,c,e);f!==c&&!t.headersSent&&t.removeHeader("content-length");let g=typeof u=="function"?u:typeof d=="function"?d:typeof h=="function"?h:void 0;return g?o(f,g):o(f)},()=>{i.write=r,i.end=o,i.flushHeaders=s}}function Tt(t,e,n){if(Bt(t,e))return Buffer.from(It(e.toString("utf8"),n));if(n.patchNextDevChunk&&Vt(t)){let i=e.toString("utf8"),s=Dt(i);if(s!==i)return Buffer.from(s)}return e}function Ht(){let t=new Uint8Array(32);return globalThis.crypto.getRandomValues(t),Buffer.from(t).toString("base64url")}function Bt(t,e){if(t.statusCode<200||t.statusCode>=300)return!1;let n=t.getHeader("content-type"),i=Array.isArray(n)?n.join(";"):String(n??"");return e.toString("utf8",0,256).includes("<html")&&(i===""||i.includes("text/html"))}function Vt(t){if(t.statusCode<200||t.statusCode>=300)return!1;let e=t.getHeader("content-type"),n=Array.isArray(e)?e.join(";"):String(e??"");return n===""||n.includes("javascript")||n.includes("ecmascript")}function Dt(t){return[["debugChannel = createDebugChannel(undefined);","debugChannel = undefined;"],["if (isStreamErrorOrUnfinished(ctr)) {","if (false) {"],["return ctr.desiredSize === null || ctr.desiredSize < 0;","return false;"],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join(`
|
|
5
6
|
`),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join(`
|
|
6
|
-
`)],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join("\\n"),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join("\\n")],["debugChannel: createDebugChannel && createDebugChannel(requestHeaders)","debugChannel: undefined"],["debugChannel: createDebugChannel && createDebugChannel(headers)","debugChannel: undefined"]].reduce((n,[
|
|
7
|
+
`)],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join("\\n"),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join("\\n")],["debugChannel: createDebugChannel && createDebugChannel(requestHeaders)","debugChannel: undefined"],["debugChannel: createDebugChannel && createDebugChannel(headers)","debugChannel: undefined"]].reduce((n,[i,s])=>Mt(n,i,s),t)}function Mt(t,e,n){if(n.length>e.length)throw new Error("Replacement must not be longer than the search string.");return t.replaceAll(e,n.padEnd(e.length," "))}function re(t,e){return Buffer.isBuffer(t)?t:t instanceof Uint8Array?Buffer.from(t):Buffer.from(String(t),e)}function oe(t){return typeof t=="string"?t:void 0}function ae(t){typeof t=="function"&&t()}async function Nt(t){let n=ue(E(t,"package.json"))(Ye);return n.default??n}function Ot(t){return ce(t)!=="src"?t:F(jt(t),"dist")}function jt(t){return pe(t)?x.posix.dirname(t):R(t)}function F(t,e){return pe(t)?x.posix.join(t,e):E(t,e)}function pe(t){return t.includes("/")&&!t.includes("\\")}function Ut(t){let e=E(t,".vcp/current-snapshot.json");if(!I(e))return"sandbox_worktree";try{let n=JSON.parse(S(e,"utf8"));return typeof n.snapshotId=="string"?n.snapshotId:"sandbox_worktree"}catch{return"sandbox_worktree"}}function At(t){if(!t)return null;let e=Number.parseInt(t,10);return Number.isInteger(e)&&e>0&&e<=65535?e:null}function Kt(t,e,n){return new Promise((i,s)=>{t.once("error",s),t.listen(e,n,()=>{t.off("error",s),i()})})}export{lt as attachNextDevUpgradeHandler,mt as createSiteEditAutoBootstrapHtmlSnippet,It as injectSiteEditAutoBootstrap,Dt as patchNextFirefoxDevChunk,vt as prepareFirefoxNextDevChunkPatchRequest,bt as prepareSiteEditHtmlInjectionRequest,gt as resolveSiteEditBridgeHealthPath,le as resolveSiteEditClientAsset,ln as resolveSiteEditClientAssetPath,ct as resolveSiteEditNextDevBundler,ot as resolveSiteEditNextDevServerMutationConfig,fn as startSiteEditNextDevServer,xt as transpileSiteEditClientSource};
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import type { SiteEditCommandSuccess, SiteEditEvent, SiteEditGetClassNameSourceCommandResult, SiteEditObjectEditContext,
|
|
1
|
+
import type { SiteEditCommandSuccess, SiteEditClassNameSourceStyleContext, SiteEditEvent, SiteEditGetClassNameSourceCommandResult, SiteEditObjectEditContext, SiteEditOperationCommandResult, SiteEditMutationContext, SiteEditInitializeSessionInput, SiteEditAuthenticatedOperationRequest, SiteEditOperationRequest, SiteEditRenderDocument, SiteEditResolveSourceLocationInput, SiteEditSourceFileResponse, SiteEditSourceLocation, SiteEditSourceTreeResponse } from "@vcp-dev/contracts";
|
|
2
2
|
import { type CompactRenderDocument } from "./document-codec.js";
|
|
3
3
|
type Awaitable<T> = T | Promise<T>;
|
|
4
|
-
export type
|
|
4
|
+
export type SiteEditHistoryStatus = {
|
|
5
|
+
canUndo: boolean;
|
|
6
|
+
canRedo: boolean;
|
|
7
|
+
undoDepth: number;
|
|
8
|
+
redoDepth: number;
|
|
9
|
+
recoveryRequired: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type SiteEditBridgeRequestKind = "initialize-session" | "get-document" | "get-history-status" | "get-style-context" | "get-object-edit-context" | "get-class-name-source" | "get-source-tree" | "get-source-file" | "resolve-source-location" | "execute" | "undo" | "redo";
|
|
5
12
|
export type SiteEditBridgeRequest = {
|
|
13
|
+
type: "request";
|
|
14
|
+
payload: {
|
|
15
|
+
id: string;
|
|
16
|
+
kind: "initialize-session";
|
|
17
|
+
routeId: string;
|
|
18
|
+
mutationContext?: SiteEditMutationContext;
|
|
19
|
+
};
|
|
20
|
+
} | {
|
|
6
21
|
type: "request";
|
|
7
22
|
payload: {
|
|
8
23
|
id: string;
|
|
@@ -13,9 +28,14 @@ export type SiteEditBridgeRequest = {
|
|
|
13
28
|
type: "request";
|
|
14
29
|
payload: {
|
|
15
30
|
id: string;
|
|
16
|
-
kind: "get-
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
kind: "get-history-status";
|
|
32
|
+
};
|
|
33
|
+
} | {
|
|
34
|
+
type: "request";
|
|
35
|
+
payload: {
|
|
36
|
+
id: string;
|
|
37
|
+
kind: "get-style-context";
|
|
38
|
+
routeId: string;
|
|
19
39
|
};
|
|
20
40
|
} | {
|
|
21
41
|
type: "request";
|
|
@@ -59,22 +79,47 @@ export type SiteEditBridgeRequest = {
|
|
|
59
79
|
payload: {
|
|
60
80
|
id: string;
|
|
61
81
|
kind: "execute";
|
|
62
|
-
request:
|
|
82
|
+
request: SiteEditAuthenticatedOperationRequest;
|
|
63
83
|
};
|
|
64
84
|
} | {
|
|
65
85
|
type: "request";
|
|
66
86
|
payload: {
|
|
67
87
|
id: string;
|
|
68
88
|
kind: "undo" | "redo";
|
|
89
|
+
mutationContext: SiteEditMutationContext;
|
|
69
90
|
};
|
|
70
91
|
};
|
|
71
92
|
export type SiteEditBridgeResponse = {
|
|
93
|
+
type: "response";
|
|
94
|
+
payload: {
|
|
95
|
+
id: string;
|
|
96
|
+
kind: "initialize-session";
|
|
97
|
+
result: SiteEditCommandSuccess<{
|
|
98
|
+
document: SiteEditRenderDocument | CompactRenderDocument;
|
|
99
|
+
historyStatus: SiteEditHistoryStatus;
|
|
100
|
+
}>;
|
|
101
|
+
};
|
|
102
|
+
} | {
|
|
72
103
|
type: "response";
|
|
73
104
|
payload: {
|
|
74
105
|
id: string;
|
|
75
106
|
kind: "get-document";
|
|
76
107
|
result: SiteEditCommandSuccess<SiteEditRenderDocument> | SiteEditCommandSuccess<CompactRenderDocument>;
|
|
77
108
|
};
|
|
109
|
+
} | {
|
|
110
|
+
type: "response";
|
|
111
|
+
payload: {
|
|
112
|
+
id: string;
|
|
113
|
+
kind: "get-history-status";
|
|
114
|
+
result: SiteEditCommandSuccess<SiteEditHistoryStatus>;
|
|
115
|
+
};
|
|
116
|
+
} | {
|
|
117
|
+
type: "response";
|
|
118
|
+
payload: {
|
|
119
|
+
id: string;
|
|
120
|
+
kind: "get-style-context";
|
|
121
|
+
result: SiteEditCommandSuccess<SiteEditClassNameSourceStyleContext | null>;
|
|
122
|
+
};
|
|
78
123
|
} | {
|
|
79
124
|
type: "response";
|
|
80
125
|
payload: {
|
|
@@ -96,13 +141,6 @@ export type SiteEditBridgeResponse = {
|
|
|
96
141
|
kind: "resolve-source-location";
|
|
97
142
|
result: SiteEditCommandSuccess<SiteEditSourceLocation>;
|
|
98
143
|
};
|
|
99
|
-
} | {
|
|
100
|
-
type: "response";
|
|
101
|
-
payload: {
|
|
102
|
-
id: string;
|
|
103
|
-
kind: "get-object-summary";
|
|
104
|
-
result: SiteEditCommandSuccess<SiteEditObjectSummary>;
|
|
105
|
-
};
|
|
106
144
|
} | {
|
|
107
145
|
type: "response";
|
|
108
146
|
payload: {
|
|
@@ -131,6 +169,7 @@ export type SiteEditBridgeError = {
|
|
|
131
169
|
id: string;
|
|
132
170
|
kind: SiteEditBridgeRequestKind;
|
|
133
171
|
message: string;
|
|
172
|
+
code?: string;
|
|
134
173
|
};
|
|
135
174
|
};
|
|
136
175
|
export type SiteEditBridgeEventMessage = {
|
|
@@ -153,8 +192,13 @@ export type SiteEditBridgeAuthOkMessage = {
|
|
|
153
192
|
};
|
|
154
193
|
export type SiteEditBridgeMessage = SiteEditBridgeAuthMessage | SiteEditBridgeAuthOkMessage | SiteEditBridgeRequest | SiteEditBridgeResponse | SiteEditBridgeError | SiteEditBridgeEventMessage;
|
|
155
194
|
export type SiteEditBridgeEngine = {
|
|
195
|
+
initializeSession(input: SiteEditInitializeSessionInput): Awaitable<SiteEditCommandSuccess<{
|
|
196
|
+
document: SiteEditRenderDocument;
|
|
197
|
+
historyStatus: SiteEditHistoryStatus;
|
|
198
|
+
}>>;
|
|
156
199
|
getDocument(routeId: string): Awaitable<SiteEditCommandSuccess<SiteEditRenderDocument>>;
|
|
157
|
-
|
|
200
|
+
getHistoryStatus(): Awaitable<SiteEditCommandSuccess<SiteEditHistoryStatus>>;
|
|
201
|
+
getStyleContext(routeId: string): Awaitable<SiteEditCommandSuccess<SiteEditClassNameSourceStyleContext | null>>;
|
|
158
202
|
getObjectEditContext(key: string, routeId?: string): Awaitable<SiteEditCommandSuccess<SiteEditObjectEditContext>>;
|
|
159
203
|
getClassNameSource(key: string, routeId?: string): Awaitable<SiteEditGetClassNameSourceCommandResult>;
|
|
160
204
|
getSourceTree(): Awaitable<SiteEditCommandSuccess<SiteEditSourceTreeResponse>>;
|
|
@@ -162,9 +206,9 @@ export type SiteEditBridgeEngine = {
|
|
|
162
206
|
path: string;
|
|
163
207
|
}): Awaitable<SiteEditCommandSuccess<SiteEditSourceFileResponse>>;
|
|
164
208
|
resolveSourceLocation(input: SiteEditResolveSourceLocationInput): Awaitable<SiteEditCommandSuccess<SiteEditSourceLocation>>;
|
|
165
|
-
execute(request:
|
|
166
|
-
undo(): Awaitable<SiteEditOperationCommandResult>;
|
|
167
|
-
redo(): Awaitable<SiteEditOperationCommandResult>;
|
|
209
|
+
execute(request: SiteEditAuthenticatedOperationRequest, mutationContext: SiteEditMutationContext): Awaitable<SiteEditOperationCommandResult>;
|
|
210
|
+
undo(mutationContext: SiteEditMutationContext): Awaitable<SiteEditOperationCommandResult>;
|
|
211
|
+
redo(mutationContext: SiteEditMutationContext): Awaitable<SiteEditOperationCommandResult>;
|
|
168
212
|
subscribe(listener: (event: SiteEditEvent) => void): () => void;
|
|
169
213
|
};
|
|
170
214
|
export type SiteEditMessageEventLike = {
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Server as HttpServer } from "node:http";
|
|
2
2
|
import { WebSocketServer } from "ws";
|
|
3
|
+
import { type PreviewSiteEditEngineRuntime, type PreviewSiteEditEngineRuntimeOptions } from "@vcp-dev/site-edit-engine";
|
|
4
|
+
import type { FileHistoryStore } from "@vcp-dev/site-edit-engine";
|
|
3
5
|
import { type SiteEditNextRouteConfig } from "@vcp-dev/site-edit-engine/next-app-router";
|
|
4
6
|
import { type SiteEditBridgeEngine } from "./protocol.js";
|
|
7
|
+
import type { SiteEditMutationContextVerifier } from "@vcp-dev/site-edit-engine";
|
|
5
8
|
export type SiteEditBridgeRuntimeOptions = {
|
|
6
9
|
routes?: SiteEditNextRouteConfig[];
|
|
7
10
|
watch_dirs?: string[];
|
|
@@ -12,6 +15,7 @@ export type SiteEditBridgeRuntimeOptions = {
|
|
|
12
15
|
watch_max_startup_ms?: number;
|
|
13
16
|
};
|
|
14
17
|
export type SiteEditBridgeServerOptions = {
|
|
18
|
+
tenantId?: string;
|
|
15
19
|
siteId: string;
|
|
16
20
|
snapshotId: string;
|
|
17
21
|
projectRoot?: string;
|
|
@@ -20,9 +24,17 @@ export type SiteEditBridgeServerOptions = {
|
|
|
20
24
|
port?: number;
|
|
21
25
|
path?: string;
|
|
22
26
|
sessionKey?: string;
|
|
27
|
+
/** Private runtime directory for the durable, server-side undo/redo store. */
|
|
28
|
+
historyRoot?: string;
|
|
29
|
+
mutationContextVerifier?: SiteEditMutationContextVerifier;
|
|
23
30
|
allowedOrigins?: string[];
|
|
24
31
|
runtime?: SiteEditBridgeRuntimeOptions;
|
|
25
32
|
};
|
|
33
|
+
/** @internal Dependency seam for deterministic runtime and durable-store tests. */
|
|
34
|
+
export type SiteEditBridgeServerDependencies = {
|
|
35
|
+
createRuntime?: (options: PreviewSiteEditEngineRuntimeOptions) => PreviewSiteEditEngineRuntime;
|
|
36
|
+
openHistoryStore?: (root: string) => Promise<FileHistoryStore>;
|
|
37
|
+
};
|
|
26
38
|
export type SiteEditBridgeHealth = {
|
|
27
39
|
ok: boolean;
|
|
28
40
|
bridgePath: string;
|
|
@@ -31,26 +43,38 @@ export type SiteEditBridgeHealth = {
|
|
|
31
43
|
sessionRequired: boolean;
|
|
32
44
|
};
|
|
33
45
|
export declare const SITE_EDIT_BRIDGE_MAX_PAYLOAD_BYTES: number;
|
|
46
|
+
export declare const SITE_EDIT_BRIDGE_MAX_BUFFERED_BYTES: number;
|
|
34
47
|
export declare function ensureSiteEditBridgeServerStarted(options: SiteEditBridgeServerOptions): SiteEditBridgeServer;
|
|
35
|
-
export declare function attachSiteEditBridgeServer(server: HttpServer, options: SiteEditBridgeServerOptions): SiteEditBridgeServer;
|
|
48
|
+
export declare function attachSiteEditBridgeServer(server: HttpServer, options: SiteEditBridgeServerOptions, dependencies?: SiteEditBridgeServerDependencies): SiteEditBridgeServer;
|
|
36
49
|
export declare class SiteEditBridgeServer {
|
|
37
50
|
private readonly options;
|
|
38
51
|
private readonly server;
|
|
39
52
|
private readonly closeHttpServerOnStop;
|
|
53
|
+
private readonly dependencies;
|
|
40
54
|
private runtime;
|
|
55
|
+
private activeHistorySessionId;
|
|
56
|
+
private historySessionTransition;
|
|
57
|
+
private readonly mutationQueue;
|
|
58
|
+
private readonly eventListeners;
|
|
41
59
|
private routes;
|
|
42
60
|
private stopWatching;
|
|
43
61
|
private stopWebSocketServer;
|
|
44
62
|
private readonly pendingChangedFiles;
|
|
45
63
|
private fileChangeTimer;
|
|
46
64
|
private isStarted;
|
|
47
|
-
|
|
65
|
+
private runtimeVersionFloor;
|
|
66
|
+
private activeHistoryStore;
|
|
67
|
+
private versionPersistence;
|
|
68
|
+
constructor(options: ReturnType<typeof normalizeOptions>, server?: HttpServer, closeHttpServerOnStop?: boolean, dependencies?: SiteEditBridgeServerDependencies);
|
|
48
69
|
start(options?: {
|
|
49
70
|
listen?: boolean;
|
|
50
71
|
}): void;
|
|
51
72
|
getHealth(): SiteEditBridgeHealth;
|
|
52
73
|
stop(): void;
|
|
74
|
+
private createRuntime;
|
|
53
75
|
private createBridgeEngine;
|
|
76
|
+
private bindHistorySession;
|
|
77
|
+
private recordRuntimeVersion;
|
|
54
78
|
private activateRoute;
|
|
55
79
|
private normalizeRequest;
|
|
56
80
|
private handleFileChanged;
|
|
@@ -59,7 +83,23 @@ export declare class SiteEditBridgeServer {
|
|
|
59
83
|
private refreshRoutes;
|
|
60
84
|
private resolveRouteId;
|
|
61
85
|
private getRuntime;
|
|
86
|
+
private verifyMutationContext;
|
|
62
87
|
}
|
|
88
|
+
declare function normalizeOptions(options: SiteEditBridgeServerOptions): {
|
|
89
|
+
tenantId: string | undefined;
|
|
90
|
+
siteId: string;
|
|
91
|
+
snapshotId: string;
|
|
92
|
+
projectRoot: string;
|
|
93
|
+
basePath: string;
|
|
94
|
+
host: string;
|
|
95
|
+
port: number;
|
|
96
|
+
path: string;
|
|
97
|
+
sessionKey: string;
|
|
98
|
+
historyRoot: string | undefined;
|
|
99
|
+
mutationContextVerifier: SiteEditMutationContextVerifier | undefined;
|
|
100
|
+
allowedOrigins: string[];
|
|
101
|
+
runtime: SiteEditBridgeRuntimeOptions;
|
|
102
|
+
};
|
|
63
103
|
export declare function attachSiteEditWebSocketServer(options: {
|
|
64
104
|
server: HttpServer;
|
|
65
105
|
path: string;
|
|
@@ -70,3 +110,4 @@ export declare function attachSiteEditWebSocketServer(options: {
|
|
|
70
110
|
}): WebSocketServer & {
|
|
71
111
|
stop(): void;
|
|
72
112
|
};
|
|
113
|
+
export {};
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import D from"node:http";import{randomBytes as N,timingSafeEqual as H}from"node:crypto";import P from"node:path";import{WebSocket as C,WebSocketServer as J}from"ws";import{createPreviewSiteEditEngineRuntime as W}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as A,scanNextAppRouterRoutes as U}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as L}from"@vcp-dev/site-edit-engine/source-watcher";function k(t){let e=new Map,s=[],n=new Map,r=[],o=a=>{let u=e.get(a);if(typeof u=="number")return u;let h=s.length;return s.push(a),e.set(a,h),h},i=a=>{let u=null;for(let h of j(a)){let R=`${u??"root"}\0${h}`,w=n.get(R);if(typeof w=="number"){u=w;continue}let S=r.length;r.push({parent:u,token:o(h)}),n.set(R,S),u=S}if(u===null)throw new Error("Document key must not be empty");return u};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,routeId:t.routeId,version:t.version,tokens:s,nodes:r,rootKeyNodeIds:t.rootKeys.map(i),entries:t.entries.map(a=>({keyNodeId:i(a.key),tag:a.tag,label:a.label,parentKeyNodeId:a.parentKey?i(a.parentKey):null,childKeyNodeIds:a.childKeys.map(i),operationSummary:a.operationSummary}))}}function j(t){if(!t)return[];let e=[],s=0,n=b(t,s);if(n<0)return e.push(t),e;for(e.push(t.slice(0,n)),s=n;s<t.length;){if(n=b(t,s+(t.startsWith("::",s)?2:1)),n<0){e.push(t.slice(s));break}e.push(t.slice(s,n)),s=n}return e}function b(t,e){let s=t.indexOf("/",e),n=t.indexOf("::",e);return s<0?n:n<0?s:Math.min(s,n)}var d=class t{constructor(){}static parseMessage(e,s={}){let n=JSON.parse(e);if(!t.isMessage(n,s))throw new Error("Invalid site edit bridge message");return n}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(typeof e=="object"&&e!==null&&"payload"in e&&typeof e.payload=="object"&&e.payload!==null){let s=e.payload,n=typeof s.id=="string"?s.id:"unknown",r=t.isRequestKind(s.kind)?s.kind:"get-document";return{id:n,kind:r}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="get-document"||e==="get-object-summary"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="undo"||e==="redo"}static isMessage(e,s={}){return!c(e)||typeof e.type!="string"?!1:e.type==="request"?t.isRequestPayload(e.payload,s):e.type==="response"||e.type==="error"?c(e.payload)&&typeof e.payload.id=="string":e.type==="event"?c(e.payload)&&c(e.payload.event):e.type==="auth"?c(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?c(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,s={}){if(!c(e)||typeof e.id!="string"||!t.isRequestKind(e.kind))return!1;switch(e.kind){case"get-document":return typeof e.routeId=="string";case"get-object-summary":case"get-object-edit-context":case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&(e.routeId===void 0||typeof e.routeId=="string");case"get-source-tree":return!0;case"get-source-file":return typeof e.path=="string";case"execute":return M(e.request)&&(s.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return!0}}},l=class{constructor(e){this.options=e,this.messageQueue=new g,this.eventDispatcher=new f(s=>{this.sendJson(s)}),this.unsubscribe=e.engine.subscribe(s=>{this.eventDispatcher.push(s)}),e.socket.onmessage=s=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(s.data))}catch(n){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:n instanceof Error?n.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe()}}async handleMessage(e){try{let s=e;if(s.type!=="request")return;let n=s.payload,{id:r}=n;switch(n.kind){case"get-document":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:{ok:!0,data:k((await this.options.engine.getDocument(n.routeId)).data)}}});return;case"get-object-summary":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getObjectSummary(n.key,n.routeId)}});return;case"get-object-edit-context":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getObjectEditContext(n.key,n.routeId)}});return;case"get-class-name-source":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getClassNameSource(n.key,n.routeId)}});return;case"get-source-tree":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getSourceTree()}});return;case"get-source-file":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getSourceFile({path:n.path})}});return;case"resolve-source-location":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.resolveSourceLocation({key:n.key,routeId:n.routeId})}});return;case"execute":await this.handleBufferedOperation(r,n.kind,()=>this.options.engine.execute(n.request));return;case"undo":await this.handleBufferedOperation(r,n.kind,()=>this.options.engine.undo());return;case"redo":await this.handleBufferedOperation(r,n.kind,()=>this.options.engine.redo());return}}catch(s){let n=d.extractRequestInfo(e);this.sendJson({type:"error",payload:{...n,message:s instanceof Error?s.message:"Unknown websocket operation failure"}})}}async handleRawMessage(e){try{await this.handleMessage(d.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(s){let n=s instanceof Error?s.message:"Invalid websocket message",r=F(e);this.sendJson({type:"error",payload:{...r,message:n}})}}sendJson(e){this.options.socket.send(d.stringifyMessage(e))}async handleBufferedOperation(e,s,n){this.eventDispatcher.beginBuffering();try{let r=await n();this.sendJson({type:"response",payload:{id:e,kind:s,result:r}}),this.eventDispatcher.flush()}catch(r){this.sendJson({type:"error",payload:{id:e,kind:s,message:r instanceof Error?r.message:"Unknown websocket operation failure"}}),this.eventDispatcher.flush()}}},f=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let s={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(s);return}this.send(s)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(s=>{this.send(s)})}},g=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let s=async()=>{this.queued-=1,await e()},n=this.queue.then(s,s);return this.queue=n.then(()=>{},()=>{}),n}};function c(t){return typeof t=="object"&&t!==null}function M(t){return c(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&(t.routeId===void 0||typeof t.routeId=="string")&&typeof t.kind=="string"&&c(t.target)&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&c(t.params)}function F(t){try{return d.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}import{SiteEditOperationRequestSchema as $}from"@vcp-dev/contracts";var z="127.0.0.1",V=30218,G="/__vcp/site-edit/bridge",Q=6*1024*1024,Y=3e3,x=50,_="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function Ie(t){let e=O(t),s=K(e),n=T(),r=n.get(s);if(r)return r;let o=new p(e);n.set(s,o);try{o.start()}catch(i){throw n.delete(s),o.stop(),i}return o}function Re(t,e){let s=new p(O(e),t,!1);return s.start({listen:!1}),s}var p=class{constructor(e,s=D.createServer((r,o)=>{o.writeHead(404),o.end("VCP site edit bridge server")}),n=!0){this.options=e,this.server=s,this.closeHttpServerOnStop=n,this.runtime=null,this.routes=[],this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1}start(e={}){if(this.isStarted)return;this.refreshRoutes(),this.runtime=W({siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,routes:this.routes});let s=X({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=s.stop,this.stopWatching=new L({projectRoot:this.options.projectRoot,watch_dirs:this.options.runtime.watch_dirs,debounce_ms:this.options.runtime.watch_debounce_ms??x,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:n=>this.handleFileChanged(n),on_batch_change:n=>this.handleFilesChanged(n)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host),this.isStarted=!0}getHealth(){let e=!!this.runtime;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.closeHttpServerOnStop&&this.server.close();let e=T(),s=K(this.options);e.get(s)===this&&e.delete(s),this.isStarted=!1}createBridgeEngine(){return{getDocument:async e=>{let{runtime:s,resolvedRouteId:n}=await this.activateRoute(e);return s.getDocument(n)},getObjectSummary:async(e,s)=>this.getRuntime().getObjectSummary(e,s?this.resolveRouteId(s):void 0),getObjectEditContext:async(e,s)=>this.getRuntime().getObjectEditContext(e,s?this.resolveRouteId(s):void 0),getClassNameSource:async(e,s)=>{let n=this.getRuntime(),r=s?this.resolveRouteId(s):void 0;return await n.refreshSourceForKey(e,r),n.getClassNameSource(e,r)},getSourceTree:async()=>this.getRuntime().getSourceTree(),getSourceFile:async e=>this.getRuntime().getSourceFile(e),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,routeId:e.routeId?this.resolveRouteId(e.routeId):void 0}),execute:async e=>this.getRuntime().executeOperation(this.normalizeRequest(e)),undo:async()=>this.getRuntime().undo(),redo:async()=>this.getRuntime().redo(),subscribe:e=>this.getRuntime().subscribe(e)}}async activateRoute(e){let s=this.getRuntime(),n=this.resolveRouteId(e);return await s.setActiveRoute(n),{runtime:s,resolvedRouteId:n}}normalizeRequest(e){return e.target.kind!=="route"?e.routeId?{...e,routeId:this.resolveRouteId(e.routeId)}:e:{...e,target:{...e.target,routeId:this.resolveRouteId(e.target.routeId)}}}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let n of e)this.pendingChangedFiles.add(n);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let s=this.options.runtime.watch_debounce_ms??x;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},s),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((s,n)=>s.localeCompare(n));this.pendingChangedFiles.clear(),this.refreshRoutes();for(let s of e)this.runtime?.notifyFileChanged(s)}refreshRoutes(){let e=this.options.runtime.routes?.length?this.options.runtime.routes:U(this.options.projectRoot),s=new Map(this.routes.map(r=>[r.routeId,r.entryFile])),n=new Set(e.map(r=>r.routeId));this.routes=e;for(let r of e)s.get(r.routeId)!==r.entryFile&&this.runtime?.registerRoute(r.routeId,r.entryFile);for(let r of s.keys())n.has(r)||this.runtime?.unregisterRoute(r)}resolveRouteId(e){let s=A(e,this.routes,this.options.basePath);if(!s)throw new Error(`Route is not indexed by the preview bridge: ${e}`);return s}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function O(t){return{siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:P.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??z,port:t.port??V,path:t.path??G,sessionKey:t.sessionKey??oe(),allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{}}}function T(){let t=globalThis;return t[_]??=new Map,t[_]}function K(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}`}function X(t){let e=new J({maxPayload:Q,noServer:!0}),s=new WeakSet,n=o=>{new l({socket:new y(o),engine:t.engine,validateOperationRequest:i=>$.safeParse(i).success})},r=(o,i,a)=>{if(!Z(o,t))return;let u=ee(o,t);if(!u){i.write(`HTTP/1.1 401 Unauthorized\r
|
|
1
|
+
import Y from"node:http";import{createHash as U,randomBytes as X,timingSafeEqual as Z}from"node:crypto";import C from"node:path";import{WebSocket as P,WebSocketServer as ee}from"ws";import{createPreviewSiteEditEngineRuntime as te,openFileHistoryStore as se}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as ne,scanNextAppRouterRoutes as ie}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as oe}from"@vcp-dev/site-edit-engine/source-watcher";function S(t){let e=new Map,s=[],n=new Map,i=[],o=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],r=new Map(o.map((a,u)=>[a,u])),d=t.pageComposition.status==="supported"?{status:"supported",containerKey:t.pageComposition.containerKey,hasBusinessMembers:t.pageComposition.hasBusinessMembers,placementUnitKeys:t.pageComposition.placementUnitKeys,placements:t.pageComposition.placements.map(a=>({placementKey:a.placementKey,placementUnitKey:a.placementUnitKey,relation:a.relation}))}:{status:"unsupported",reason:t.pageComposition.reason,diagnostics:t.pageComposition.diagnostics},p=a=>{let u=e.get(a);if(typeof u=="number")return u;let g=s.length;return s.push(a),e.set(a,g),g},w=a=>{let u=null;for(let g of W(a)){let E=`${u??"root"}\0${g}`,_=n.get(E);if(typeof _=="number"){u=_;continue}let T=i.length;i.push([u,p(g)]),n.set(E,T),u=T}if(u===null)throw new Error("Document key must not be empty");return u};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,routeId:t.routeId,version:t.version,pageComposition:d,tokens:s,nodes:i,entries:t.entries.map(a=>{let u=a.pageCompositionUnitKey?r.get(a.pageCompositionUnitKey)??-1:-1;if(a.pageCompositionUnitKey&&u<0)throw new Error(`Render entry ${a.key} references unknown page composition unit ${a.pageCompositionUnitKey}`);return[w(a.key),J(a.operationSummary),...u>=0?[u]:[]]})}}function J(t){return(t.canUpdateText?1:0)|(t.canInsertChild?2:0)|(t.canMove?4:0)|(t.canRemove?8:0)}function W(t){if(!t)return[];let e=[],s=0,n=V(t,s);if(n<0)return e.push(t),e;for(e.push(t.slice(0,n)),s=n;s<t.length;){if(n=V(t,s+(t.startsWith("::",s)?2:1)),n<0){e.push(t.slice(s));break}e.push(t.slice(s,n)),s=n}return e}function V(t,e){let s=t.indexOf("/",e),n=t.indexOf("::",e);return s<0?n:n<0?s:Math.min(s,n)}var h=class t{constructor(){}static parseMessage(e,s={}){let n=JSON.parse(e);if(!t.isMessage(n,s))throw new Error("Invalid site edit bridge message");return n}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(typeof e=="object"&&e!==null&&"payload"in e&&typeof e.payload=="object"&&e.payload!==null){let s=e.payload,n=typeof s.id=="string"?s.id:"unknown",i=t.isRequestKind(s.kind)?s.kind:"get-document";return{id:n,kind:i}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="initialize-session"||e==="get-document"||e==="get-history-status"||e==="get-style-context"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="undo"||e==="redo"}static isMessage(e,s={}){return!c(e)||typeof e.type!="string"?!1:e.type==="request"?t.isRequestPayload(e.payload,s):e.type==="response"||e.type==="error"?c(e.payload)&&typeof e.payload.id=="string":e.type==="event"?c(e.payload)&&c(e.payload.event):e.type==="auth"?c(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?c(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,s={}){if(!c(e)||typeof e.id!="string"||!t.isRequestKind(e.kind))return!1;switch(e.kind){case"initialize-session":return typeof e.routeId=="string"&&(e.mutationContext===void 0||l(e.mutationContext));case"get-document":case"get-style-context":return typeof e.routeId=="string";case"get-object-edit-context":case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&(e.routeId===void 0||typeof e.routeId=="string");case"get-history-status":return e.mutationContext===void 0;case"get-source-tree":return!0;case"get-source-file":return typeof e.path=="string";case"execute":return z(e.request)&&l(e.request.mutationContext)&&(s.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return l(e.mutationContext)}}},m=class{constructor(e){this.options=e,this.messageQueue=new x,this.eventDispatcher=new R(s=>{this.sendJson(s)}),this.unsubscribe=e.engine.subscribe(s=>{this.eventDispatcher.push(s)}),e.socket.onmessage=s=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(s.data))}catch(n){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:n instanceof Error?n.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe()}}async handleMessage(e){try{let s=e;if(s.type!=="request")return;let n=s.payload,{id:i}=n;switch(n.kind){case"initialize-session":{let o=await this.options.engine.initializeSession({routeId:n.routeId,...n.mutationContext?{mutationContext:n.mutationContext}:{}});this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:{ok:!0,data:{document:S(o.data.document),historyStatus:o.data.historyStatus}}}});return}case"get-document":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:{ok:!0,data:S((await this.options.engine.getDocument(n.routeId)).data)}}});return;case"get-history-status":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getHistoryStatus()}});return;case"get-style-context":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getStyleContext(n.routeId)}});return;case"get-object-edit-context":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getObjectEditContext(n.key,n.routeId)}});return;case"get-class-name-source":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getClassNameSource(n.key,n.routeId)}});return;case"get-source-tree":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getSourceTree()}});return;case"get-source-file":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getSourceFile({path:n.path})}});return;case"resolve-source-location":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.resolveSourceLocation({key:n.key,routeId:n.routeId})}});return;case"execute":{if(!l(n.request.mutationContext))throw new Error("Invalid site edit bridge message");let o=n.request.mutationContext;await this.handleBufferedOperation(i,n.kind,()=>this.options.engine.execute(n.request,o));return}case"undo":if(!l(n.mutationContext))throw new Error("Invalid site edit bridge message");await this.handleBufferedOperation(i,n.kind,()=>this.options.engine.undo(n.mutationContext));return;case"redo":if(!l(n.mutationContext))throw new Error("Invalid site edit bridge message");await this.handleBufferedOperation(i,n.kind,()=>this.options.engine.redo(n.mutationContext));return}}catch(s){let n=h.extractRequestInfo(e);this.sendJson({type:"error",payload:{...n,message:M(s),code:B(s)}})}}async handleRawMessage(e){try{await this.handleMessage(h.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(s){let n=s instanceof Error?s.message:"Invalid websocket message",i=G(e);this.sendJson({type:"error",payload:{...i,message:n}})}}sendJson(e){this.options.socket.send(h.stringifyMessage(e))}async handleBufferedOperation(e,s,n){this.eventDispatcher.beginBuffering();try{let i=await n();this.sendJson({type:"response",payload:{id:e,kind:s,result:i}}),this.eventDispatcher.flush()}catch(i){this.sendJson({type:"error",payload:{id:e,kind:s,message:M(i),code:B(i)}}),this.eventDispatcher.flush()}}},R=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let s={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(s);return}this.send(s)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(s=>{this.send(s)})}},x=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let s=async()=>{this.queued-=1,await e()},n=this.queue.then(s,s);return this.queue=n.then(()=>{},()=>{}),n}};function c(t){return typeof t=="object"&&t!==null}function z(t){return c(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&(t.routeId===void 0||typeof t.routeId=="string")&&typeof t.kind=="string"&&c(t.target)&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&c(t.params)}function l(t){return Q(t)}function B(t){if(!t||typeof t!="object")return;let e=t.code;return typeof e=="string"&&e.length>0?e:void 0}function M(t){if(t instanceof Error)return t.message;if(!t||typeof t!="object")return"Unknown websocket operation failure";let e=t.message;return typeof e=="string"&&e.length>0?e:"Unknown websocket operation failure"}function Q(t){return c(t)?typeof t.editingSessionId=="string"&&t.editingSessionId.length>0&&typeof t.lockId=="string"&&t.lockId.length>0&&typeof t.ownerId=="string"&&t.ownerId.length>0&&typeof t.leaseAssertion=="string"&&t.leaseAssertion.length>0:!1}function G(t){try{return h.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}var y=class{constructor(){this.tail=Promise.resolve()}run(e){let s=this.tail.then(e,e);return this.tail=s.then(()=>{},()=>{}),s}};import{SiteEditOperationRequestSchema as re}from"@vcp-dev/contracts";var ae="127.0.0.1",ue=30218,ce="/__vcp/site-edit/bridge",q=6*1024*1024,de=q*2,he=3e3,O=50,H="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function De(t){let e=A(t),s=j(e),n=L(),i=n.get(s);if(i)return i;let o=new I(e);n.set(s,o);try{o.start()}catch(r){throw n.delete(s),o.stop(),r}return o}function Ke(t,e,s={}){let n=new I(A(e),t,!1,s);return n.start({listen:!1}),n}var I=class{constructor(e,s=Y.createServer((o,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),n=!0,i={}){this.options=e,this.server=s,this.closeHttpServerOnStop=n,this.dependencies=i,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new y,this.eventListeners=new Set,this.routes=[],this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.runtimeVersionFloor=0,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve()}start(e={}){if(this.isStarted)return;this.refreshRoutes(),this.isStarted=!0,this.createRuntime();let s=le({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=s.stop,this.stopWatching=new oe({projectRoot:this.options.projectRoot,watch_dirs:this.options.runtime.watch_dirs,debounce_ms:this.options.runtime.watch_debounce_ms??O,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:n=>this.handleFileChanged(n),on_batch_change:n=>this.handleFilesChanged(n)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host)}getHealth(){let e=!!this.runtime;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.closeHttpServerOnStop&&this.server.close();let e=L(),s=j(this.options);e.get(s)===this&&e.delete(s),this.isStarted=!1}createRuntime(e,s=null){let n=this.runtime;this.activeHistoryStore=e??null;let i=(this.dependencies.createRuntime??te)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,routes:this.routes,onEvent:o=>{"version"in o&&typeof o.version=="number"&&this.recordRuntimeVersion(o.version);for(let r of this.eventListeners)r(o)},...e?{historyStore:e}:{},initialDocumentVersion:this.runtimeVersionFloor,...this.options.mutationContextVerifier?{mutationContextVerifier:this.options.mutationContextVerifier}:{}});this.runtime=i,this.activeHistorySessionId=s,n?.dispose()}createBridgeEngine(){return{initializeSession:e=>this.mutationQueue.run(async()=>{if(e.mutationContext){let r=await this.verifyMutationContext(e.mutationContext);if(!r.ok)throw new Error(`${r.code}: ${r.message??"Mutation session verification failed"}`);let d=await this.bindHistorySession(e.mutationContext.editingSessionId);if(!d.ok)throw new Error(`${d.code}: ${d.message}`)}let{runtime:s,resolvedRouteId:n}=await this.activateRoute(e.routeId),i=s.getDocument(n).data;await this.recordRuntimeVersion(i.version);let o=(await s.getHistoryStatus()).data;return{ok:!0,data:{document:i,historyStatus:o}}}),getDocument:async e=>{let{runtime:s,resolvedRouteId:n}=await this.activateRoute(e),i=s.getDocument(n);return await this.recordRuntimeVersion(i.data.version),i},getHistoryStatus:async()=>this.getRuntime().getHistoryStatus(),getStyleContext:async e=>{let{runtime:s,resolvedRouteId:n}=await this.activateRoute(e);return s.getStyleContext(n)},getObjectEditContext:async(e,s)=>this.getRuntime().getObjectEditContext(e,s?this.resolveRouteId(s):void 0),getClassNameSource:async(e,s)=>{let n=this.getRuntime(),i=s?this.resolveRouteId(s):void 0;return n.getClassNameSource(e,i)},getSourceTree:async()=>this.getRuntime().getSourceTree(),getSourceFile:async e=>this.getRuntime().getSourceFile(e),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,routeId:e.routeId?this.resolveRouteId(e.routeId):void 0}),execute:(e,s)=>this.mutationQueue.run(async()=>{let n=s??e.mutationContext,i=await this.verifyMutationContext(n);if(!i.ok)return f(i,e);let o=await this.bindHistorySession(n.editingSessionId);if(!o.ok)return f(o,e);let r=await this.getRuntime().executeOperation(this.normalizeRequest(e),n);return r.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(r.data.resultVersion),r}),undo:e=>this.mutationQueue.run(async()=>{let s=await this.verifyMutationContext(e);if(!s.ok)return f(s);let n=await this.bindHistorySession(e.editingSessionId);if(!n.ok)return f(n);let i=await this.getRuntime().undo(e);return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion),i}),redo:e=>this.mutationQueue.run(async()=>{let s=await this.verifyMutationContext(e);if(!s.ok)return f(s);let n=await this.bindHistorySession(e.editingSessionId);if(!n.ok)return f(n);let i=await this.getRuntime().redo(e);return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion),i}),subscribe:e=>(this.eventListeners.add(e),()=>this.eventListeners.delete(e))}}async bindHistorySession(e){if(!this.options.historyRoot)return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history is not configured."};if(this.activeHistorySessionId===e)return{ok:!0};let s=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===e)return;let n=await(this.dependencies.openHistoryStore??se)(pe({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e}));if(!this.isStarted)return;let i=n.readDocumentVersion?await n.readDocumentVersion():0;this.runtimeVersionFloor=Math.max(this.runtimeVersionFloor,i),this.createRuntime(n,e)});this.historySessionTransition=s.catch(()=>{});try{return await s,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(e){this.runtimeVersionFloor=Math.max(this.runtimeVersionFloor,e);let s=this.activeHistoryStore;return s?.writeDocumentVersion?(this.versionPersistence=this.versionPersistence.then(()=>s.writeDocumentVersion(this.runtimeVersionFloor)).catch(()=>{}),this.versionPersistence):Promise.resolve()}async activateRoute(e){let s=this.getRuntime(),n=this.resolveRouteId(e);return await s.setActiveRoute(n),{runtime:s,resolvedRouteId:n}}normalizeRequest(e){return e.target.kind!=="route"?e.routeId?{...e,routeId:this.resolveRouteId(e.routeId)}:e:{...e,target:{...e.target,routeId:this.resolveRouteId(e.target.routeId)}}}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let n of e)this.pendingChangedFiles.add(n);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let s=this.options.runtime.watch_debounce_ms??O;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},s),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((s,n)=>s.localeCompare(n));this.pendingChangedFiles.clear(),this.refreshRoutes();for(let s of e)this.runtime?.notifyFileChanged(s)}refreshRoutes(){let e=this.options.runtime.routes?.length?this.options.runtime.routes:ie(this.options.projectRoot),s=new Map(this.routes.map(i=>[i.routeId,i.entryFile])),n=new Set(e.map(i=>i.routeId));this.routes=e;for(let i of e)s.get(i.routeId)!==i.entryFile&&this.runtime?.registerRoute(i.routeId,i.entryFile);for(let i of s.keys())n.has(i)||this.runtime?.unregisterRoute(i)}resolveRouteId(e){let s=ne(e,this.routes,this.options.basePath);if(!s)throw new Error(`Route is not indexed by the preview bridge: ${e}`);return s}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}async verifyMutationContext(e){if(!e)return{ok:!1,code:"MUTATION_SESSION_REQUIRED",message:"A mutation session is required for write operations."};if(!this.options.mutationContextVerifier||!this.options.tenantId)return{ok:!1,code:"LEASE_ASSERTION_INVALID",message:"Lease assertion verification is unavailable."};try{return await this.options.mutationContextVerifier.verify(e,{tenantId:this.options.tenantId,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e.editingSessionId,lockId:e.lockId,ownerId:e.ownerId})}catch{return{ok:!1,code:"LEASE_ASSERTION_INVALID",message:"Lease assertion verification failed."}}}};function A(t){return{tenantId:t.tenantId,siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:C.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??ae,port:t.port??ue,path:t.path??ce,sessionKey:t.sessionKey??Se(),historyRoot:t.historyRoot,mutationContextVerifier:t.mutationContextVerifier,allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{}}}function pe(t){return C.join(C.resolve(t.root),D(t.siteId),D(t.snapshotId),U("sha256").update(t.editingSessionId).digest("hex").slice(0,32))}function D(t){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(t)&&t!=="."&&t!==".."?t:U("sha256").update(t).digest("hex").slice(0,32)}function L(){let t=globalThis;return t[H]??=new Map,t[H]}function j(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}:${t.historyRoot??""}`}function f(t,e){return{ok:!1,error:{code:t.code,message:t.message},data:{requestId:e?.id??"mutation-context",kind:e?.kind??"update-text",target:e?.target??{kind:"node",key:""},resultVersion:e?.documentVersion??0}}}function le(t){let e=new ee({maxPayload:q,noServer:!0}),s=new WeakSet,n=o=>{new m({socket:new b(o),engine:t.engine,validateOperationRequest:r=>re.safeParse(r).success})},i=(o,r,d)=>{if(!fe(o,t))return;let p=ge(o,t);if(!p){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
2
2
|
Connection: close\r
|
|
3
3
|
\r
|
|
4
|
-
`),
|
|
4
|
+
`),r.destroy();return}p.authenticatedByHeader&&s.add(o),e.handleUpgrade(o,r,d,w=>{e.emit("connection",w,o)})};return t.server.on("upgrade",i),e.on("connection",(o,r)=>{if(s.has(r)){n(o);return}me(o,t.sessionKey,()=>{n(o)})}),e.stop=()=>{t.server.off("upgrade",i);for(let o of e.clients)o.close(1001,"bridge stopped");e.close()},e}var b=class{constructor(e){this.socket=e,this.onmessage=null,this.onclose=null,e.on("error",()=>{}),e.on("message",s=>{this.onmessage?.({data:s.toString()})}),e.on("close",()=>{this.onclose?.()})}send(e){if(this.socket.readyState===P.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(e,"utf8")>de){this.socket.close(1013,"bridge backpressure");return}this.socket.send(e)}}close(e,s){this.socket.close(e,s)}};function fe(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function ge(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let n=k(t.headers["x-vcp-site-edit-key"]),i=N(n,e.sessionKey),o=k(t.headers.host),r=k(t.headers.origin);return o&&!ye(o,e.host,r)?null:r?we(r,o,e.allowedOrigins)?{authenticatedByHeader:i}:null:i?{authenticatedByHeader:!0}:null}function me(t,e,s){let n=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},he),i,o=()=>{clearTimeout(n),t.off("message",i),t.off("close",o)},r=()=>{o(),t.close(1008,"bridge authentication failed")};i=d=>{try{let p=h.parseMessage(d.toString());if(p.type!=="auth"||!N(p.payload.key,e)){r();return}o(),t.readyState===P.OPEN&&t.send(h.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),s()}catch{r()}},t.on("message",i),t.on("close",o)}function N(t,e){if(!t)return!1;let s=Buffer.from(t),n=Buffer.from(e);return s.length===n.length&&Z(s,n)}function ye(t,e,s){let n=F(t),i=F(e);return n===i||K(i)&&K(n)&&(!s||v(s,t))||Ie(i)&&(!s||v(s,t))}function Ie(t){return t==="0.0.0.0"||t==="::"}function K(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function we(t,e,s){return s.includes(t)||(e?v(t,e):!1)}function v(t,e){try{return new URL(t).host===$(e)}catch{return!1}}function F(t){let e=$(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function $(t){return t.trim().toLowerCase()}function k(t){return Array.isArray(t)?t[0]:t}function Se(){return X(32).toString("base64url")}export{de as SITE_EDIT_BRIDGE_MAX_BUFFERED_BYTES,q as SITE_EDIT_BRIDGE_MAX_PAYLOAD_BYTES,I as SiteEditBridgeServer,Ke as attachSiteEditBridgeServer,le as attachSiteEditWebSocketServer,De as ensureSiteEditBridgeServerStarted};
|
|
@@ -10,6 +10,7 @@ const preview = await startSiteEditNextDevServer({
|
|
|
10
10
|
projectRoot,
|
|
11
11
|
siteId: process.env.VCP_SITE_ID ?? "site_preview",
|
|
12
12
|
snapshotId: process.env.VCP_SNAPSHOT_ID,
|
|
13
|
+
requireMutationContextVerifier: true,
|
|
13
14
|
});
|
|
14
15
|
console.log(JSON.stringify({
|
|
15
16
|
status: "ready",
|
package/dist/window-message.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { SiteEditCommandSuccess, SiteEditEvent, SiteEditObjectEditContext,
|
|
1
|
+
import type { SiteEditCommandSuccess, SiteEditClassNameSourceStyleContext, SiteEditEvent, SiteEditObjectEditContext, SiteEditGetClassNameSourceCommandResult, SiteEditMutationContext, SiteEditOperationCommandResult, SiteEditOperationRequest, SiteEditRenderDocument, SiteEditSourceFileResponse, SiteEditSourceLocation, SiteEditSourceTreeResponse } from "@vcp-dev/contracts";
|
|
2
|
+
import type { SiteEditHistoryStatus } from "./protocol.js";
|
|
2
3
|
export declare const VCP_WINDOW_MESSAGE_SOURCE = "vcp-window-message";
|
|
3
4
|
export declare const VCP_WINDOW_MESSAGE_VERSION = 1;
|
|
4
5
|
export type VcpWindowMessageEnvelope<TNamespace extends string = string, TChannel extends string = string, TBody = unknown> = {
|
|
@@ -210,15 +211,36 @@ export type SiteEditFrameRuntimeRequestBody = {
|
|
|
210
211
|
} | {
|
|
211
212
|
type: "request";
|
|
212
213
|
id: string;
|
|
213
|
-
kind: "bridge-
|
|
214
|
+
kind: "bridge-initialize-session";
|
|
215
|
+
payload: {
|
|
216
|
+
routeId: string;
|
|
217
|
+
mutationContext?: SiteEditMutationContext;
|
|
218
|
+
};
|
|
219
|
+
} | {
|
|
220
|
+
type: "request";
|
|
221
|
+
id: string;
|
|
222
|
+
kind: "bridge-get-document" | "bridge-ensure-document-version" | "bridge-acknowledge-document-version";
|
|
214
223
|
payload: {
|
|
215
224
|
routeId: string;
|
|
216
225
|
minVersion?: number;
|
|
226
|
+
version?: number;
|
|
227
|
+
};
|
|
228
|
+
} | {
|
|
229
|
+
type: "request";
|
|
230
|
+
id: string;
|
|
231
|
+
kind: "bridge-get-history-status";
|
|
232
|
+
payload: Record<string, never>;
|
|
233
|
+
} | {
|
|
234
|
+
type: "request";
|
|
235
|
+
id: string;
|
|
236
|
+
kind: "bridge-get-style-context";
|
|
237
|
+
payload: {
|
|
238
|
+
routeId: string;
|
|
217
239
|
};
|
|
218
240
|
} | {
|
|
219
241
|
type: "request";
|
|
220
242
|
id: string;
|
|
221
|
-
kind: "bridge-get-object-
|
|
243
|
+
kind: "bridge-get-object-edit-context" | "bridge-get-class-name-source";
|
|
222
244
|
payload: {
|
|
223
245
|
key: string;
|
|
224
246
|
routeId?: string;
|
|
@@ -229,12 +251,15 @@ export type SiteEditFrameRuntimeRequestBody = {
|
|
|
229
251
|
kind: "bridge-execute";
|
|
230
252
|
payload: {
|
|
231
253
|
request: SiteEditOperationRequest;
|
|
254
|
+
mutationContext: SiteEditMutationContext;
|
|
232
255
|
};
|
|
233
256
|
} | {
|
|
234
257
|
type: "request";
|
|
235
258
|
id: string;
|
|
236
259
|
kind: "bridge-undo" | "bridge-redo";
|
|
237
|
-
payload:
|
|
260
|
+
payload: {
|
|
261
|
+
mutationContext: SiteEditMutationContext;
|
|
262
|
+
};
|
|
238
263
|
} | {
|
|
239
264
|
type: "request";
|
|
240
265
|
id: string;
|
|
@@ -274,6 +299,7 @@ export type SiteEditFrameRuntimeHit = {
|
|
|
274
299
|
componentName?: string;
|
|
275
300
|
sourceFile?: string;
|
|
276
301
|
parentKey?: string;
|
|
302
|
+
pageCompositionUnitKey?: string;
|
|
277
303
|
textPreview?: string;
|
|
278
304
|
dataset?: Record<string, string>;
|
|
279
305
|
attributes?: Record<string, string>;
|
|
@@ -298,7 +324,10 @@ export type SiteEditFrameRuntimeResponseBody = {
|
|
|
298
324
|
type: "response";
|
|
299
325
|
id: string;
|
|
300
326
|
ok: boolean;
|
|
301
|
-
result?: SiteEditFrameRuntimeStarted | SiteEditFrameRuntimeHit | SiteEditFrameRuntimeNodePatchAck | SiteEditFrameRuntimeHit[] | SiteEditFrameRuntimeDropTarget | SiteEditFrameRuntimeScrollMetrics | SiteEditCommandSuccess<SiteEditRenderDocument> | SiteEditCommandSuccess<
|
|
327
|
+
result?: SiteEditFrameRuntimeStarted | SiteEditFrameRuntimeHit | SiteEditFrameRuntimeNodePatchAck | SiteEditFrameRuntimeHit[] | SiteEditFrameRuntimeDropTarget | SiteEditFrameRuntimeScrollMetrics | SiteEditCommandSuccess<SiteEditRenderDocument> | SiteEditCommandSuccess<{
|
|
328
|
+
document: SiteEditRenderDocument;
|
|
329
|
+
historyStatus: SiteEditHistoryStatus;
|
|
330
|
+
}> | SiteEditCommandSuccess<SiteEditHistoryStatus> | SiteEditCommandSuccess<SiteEditClassNameSourceStyleContext | null> | SiteEditCommandSuccess<SiteEditObjectEditContext> | SiteEditGetClassNameSourceCommandResult | SiteEditCommandSuccess<SiteEditSourceTreeResponse> | SiteEditCommandSuccess<SiteEditSourceFileResponse> | SiteEditCommandSuccess<SiteEditSourceLocation> | SiteEditOperationCommandResult | (SiteEditFrameRuntimeScrollMetrics & {
|
|
302
331
|
scrolled: true;
|
|
303
332
|
}) | {
|
|
304
333
|
interactionEnabled: boolean;
|