@vcp-dev/site-edit-bridge 0.4.9 → 0.6.1

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/client.js CHANGED
@@ -1 +1 @@
1
- function S(n){if(!E(n))return n;let e=new Map,t=s=>{let r=e.get(s);if(r)return r;let o=n.nodes[s];if(!o)throw new Error(`Missing compact document node ${s}`);let c=n.tokens[o.token];if(typeof c!="string")throw new Error(`Missing compact document token ${o.token}`);let i=`${o.parent===null?"":t(o.parent)}${c}`;return e.set(s,i),i};return{siteId:n.siteId,snapshotId:n.snapshotId,routeId:n.routeId,version:n.version,rootKeys:n.rootKeyNodeIds.map(t),entries:n.entries.map(s=>({key:t(s.keyNodeId),tag:s.tag,label:s.label,parentKey:s.parentKeyNodeId===null?null:t(s.parentKeyNodeId),childKeys:s.childKeyNodeIds.map(t),operationSummary:s.operationSummary}))}}function E(n){return typeof n=="object"&&n!==null&&"encoding"in n&&n.encoding==="segment-trie-v1"}var h=class n{constructor(){}static parseMessage(e,t={}){let s=JSON.parse(e);if(!n.isMessage(s,t))throw new Error("Invalid site edit bridge message");return s}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 t=e.payload,s=typeof t.id=="string"?t.id:"unknown",r=n.isRequestKind(t.kind)?t.kind:"get-document";return{id:s,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,t={}){return!a(e)||typeof e.type!="string"?!1:e.type==="request"?n.isRequestPayload(e.payload,t):e.type==="response"||e.type==="error"?a(e.payload)&&typeof e.payload.id=="string":e.type==="event"?a(e.payload)&&a(e.payload.event):e.type==="auth"?a(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?a(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,t={}){if(!a(e)||typeof e.id!="string"||!n.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 q(e.request)&&(t.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return!0}}};function a(n){return typeof n=="object"&&n!==null}function q(n){return a(n)&&typeof n.id=="string"&&typeof n.siteId=="string"&&typeof n.baseSnapshotId=="string"&&(n.routeId===void 0||typeof n.routeId=="string")&&typeof n.kind=="string"&&a(n.target)&&typeof n.documentVersion=="number"&&Number.isInteger(n.documentVersion)&&n.documentVersion>=0&&a(n.params)}var x="vcp:designer:trace",C="designer_trace",I="__VCP_DESIGNER_TRACE__";var R=/(authorization|cookie|token|secret|providerTrace|headers?|sourceCode|rawSource|fullSource)/i;function T(n=globalThis){let e=n;try{if(e.localStorage?.getItem(x)==="1")return!0}catch{}try{let t=e.location?.search??"";return new URLSearchParams(t).get(C)==="1"}catch{return!1}}function d(n,e={},t=globalThis){if(!T(t))return;let s={type:n,timestamp:Date.now(),payload:D(e)},r=t,o=r[I]??[];o.push(s),o.length>200&&o.splice(0,o.length-200),r[I]=o;let c=t.console?.debug;typeof c=="function"&&c.call(t.console,"[designer-trace]",s)}function D(n){return g(n,0)}function g(n,e){if(e>4)return"[truncated]";if(Array.isArray(n))return n.map(s=>g(s,e+1));if(!n||typeof n!="object")return n;let t={};for(let[s,r]of Object.entries(n)){if(R.test(s)){t[s]="[redacted]";continue}t[s]=g(r,e+1)}return t}var k=class{constructor(e){this.options=e,this.socket=null,this.connectPromise=null,this.connectAttempt=null,this.connectionState="idle",this.intentionalClose=!1,this.sequence=0,this.pending=new Map,this.listeners=new Set,this.connectionStatusListeners=new Set}connect(){return this.socket?.readyState===m&&this.connectionState==="open"?(this.setConnectionState("open"),Promise.resolve()):this.connectPromise?this.connectPromise:(this.intentionalClose=!1,this.setConnectionState("connecting"),this.connectPromise=new Promise((e,t)=>{let s;try{s=this.createSocket(this.options.url)}catch(i){let u=l(i,"WebSocket failed");this.socket=null,this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("failed",u.message),t(u);return}let r={socket:s,settled:!1,reject:t};this.connectAttempt=r,this.socket=s;let o=()=>{this.socket!==s||this.connectAttempt!==r||(r.settled=!0,this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("open"),e())},c=(i,u)=>{this.cleanupSocket(s,i,u)};s.onopen=()=>{if(!(this.socket!==s||this.connectAttempt!==r)){if(this.options.authKey){try{s.send(h.stringifyMessage({type:"auth",payload:{key:this.options.authKey}}))}catch(i){c(l(i,"WebSocket auth failed"),"failed")}return}o()}},s.onerror=i=>{if(this.socket!==s)return;let u=l(i,"WebSocket failed");c(u,"failed")},s.onmessage=i=>{if(this.socket===s){if(this.options.authKey&&this.connectAttempt===r&&!r.settled){this.handleAuthHandshakeMessage(i.data,s,r,()=>{o()});return}this.handleMessage(i.data)}},s.onclose=i=>{if(this.socket!==s)return;let u=this.intentionalClose,f=new Error(j(i,{intentional:u}));c(f,u?"closed":"failed")}}),this.connectPromise)}disconnect(){this.intentionalClose=!0,this.setConnectionState("closing");let e=this.socket;if(!e){this.rejectAll(new Error("WebSocket disconnected")),this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("closed");return}this.cleanupSocket(e,new Error("WebSocket disconnected"),"closed")}getConnectionState(){return this.connectionState}subscribeConnectionStatus(e){return this.connectionStatusListeners.add(e),e({state:this.connectionState}),()=>{this.connectionStatusListeners.delete(e)}}getDocument(e){return this.request("get-document",{routeId:e})}getObjectSummary(e,t){return this.request("get-object-summary",{key:e,routeId:t})}getObjectEditContext(e,t){return this.request("get-object-edit-context",{key:e,routeId:t})}getClassNameSource(e,t){return this.request("get-class-name-source",{key:e,routeId:t})}getSourceTree(){return this.request("get-source-tree",{})}getSourceFile(e){return this.request("get-source-file",e)}resolveSourceLocation(e){return this.request("resolve-source-location",e)}execute(e){return this.request("execute",{request:e})}undo(){return this.request("undo",{})}redo(){return this.request("redo",{})}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}request(e,t){let s=this.socket;if(!s||s.readyState!==m||this.connectionState!=="open")return Promise.reject(new Error(`WebSocket is not open (state: ${this.connectionState}, readyState: ${s?.readyState??"none"})`));let r=`${e}-${this.sequence+=1}`,o=N(r,e,t);return d("bridge.request",{id:r,kind:e,routeId:typeof t.routeId=="string"?t.routeId:null,key:typeof t.key=="string"?t.key:null,operationKind:w(t.request,"kind"),documentVersion:w(t.request,"documentVersion")}),new Promise((c,i)=>{let u=this.resolveRequestTimeoutMs(e),f=u>0?setTimeout(()=>{let p=this.pending.get(r);p&&(this.pending.delete(r),d("bridge.response",{id:r,kind:e,ok:!1,timedOut:!0}),p.reject(new Error(`Bridge request timed out after ${u}ms: ${e}`)))},u):null;f?.unref?.(),this.pending.set(r,{kind:e,timeout:f,resolve:p=>c(p),reject:i});try{s.send(h.stringifyMessage(o))}catch(p){let b=l(p,"WebSocket send failed");this.clearPendingRequest(r),i(b),this.cleanupSocket(s,b,"failed")}})}handleAuthHandshakeMessage(e,t,s,r){try{if(h.parseMessage(e).type==="auth-ok"){r();return}throw new Error("Bridge authentication failed")}catch(o){let c=l(o,"Bridge authentication failed");this.connectAttempt===s&&this.cleanupSocket(t,c,"failed")}}handleMessage(e){let t=h.parseMessage(e);if(t.type==="event"){for(let r of this.listeners)r(t.payload.event);return}if(t.type!=="response"&&t.type!=="error")return;let s=this.pending.get(t.payload.id);if(s){if(this.clearPendingRequest(t.payload.id),t.type==="error"){d("bridge.response",{id:t.payload.id,kind:s.kind,ok:!1}),s.reject(new Error(t.payload.message));return}d("bridge.response",{id:t.payload.id,kind:s.kind,ok:!0,resultVersion:K(t.payload.result)}),s.resolve(s.kind==="get-document"?{...t.payload.result,data:S(t.payload.result.data)}:t.payload.result)}}rejectAll(e){for(let[t,s]of[...this.pending])this.clearPendingRequest(t),s.reject(e)}clearPendingRequest(e){let t=this.pending.get(e);t&&(t.timeout&&clearTimeout(t.timeout),this.pending.delete(e))}resolveRequestTimeoutMs(e){let t=this.options.requestTimeoutMsByKind?.[e];return typeof t=="number"?Math.max(0,t):typeof this.options.requestTimeoutMs=="number"?Math.max(0,this.options.requestTimeoutMs):O[e]??_}cleanupSocket(e,t,s){if(this.socket===e&&(this.socket=null),this.connectAttempt?.socket===e){let r=this.connectAttempt;this.connectAttempt=null,r.settled||(r.settled=!0,r.reject(t))}this.connectPromise=null,this.rejectAll(t),this.setConnectionState(s,t.message),e.onopen=null,e.onmessage=null,e.onerror=null,e.onclose=null;try{(e.readyState===M||e.readyState===m)&&e.close()}catch{}}setConnectionState(e,t){if(this.connectionState===e&&!t)return;this.connectionState=e;let s={state:e,...t?{message:t}:{}};this.options.onConnectionStatus?.(s);for(let r of this.connectionStatusListeners)r(s)}createSocket(e){if(this.options.createSocket)return this.options.createSocket(e);if(typeof WebSocket>"u")throw new Error("Site edit preview bridge requires WebSocket");return new WebSocket(e)}},M=0,m=1,_=15e3,O={"get-source-tree":1e4,"get-source-file":1e4,"resolve-source-location":1e4,execute:6e4,undo:2e4,redo:2e4};function l(n,e){return n instanceof Error?n:new Error(e)}function j(n,e){return e.intentional?"WebSocket disconnected":n?.code===1008?n.reason?`Bridge session authentication failed: ${n.reason}`:"Bridge session authentication failed":n?.reason?`WebSocket closed: ${n.reason}`:typeof n?.code=="number"&&n.code!==1005?`WebSocket closed with code ${n.code}`:"WebSocket closed"}function w(n,e){if(!n||typeof n!="object")return null;let s=n[e];return typeof s=="string"||typeof s=="number"?s:null}function K(n){if(!n||typeof n!="object")return null;let e=n,t=e.data?.resultVersion??e.data?.version??null;return typeof t=="number"?t:null}function V(n){return new y(n)}var y=class{constructor(e){this.params=e,this.refreshInFlight=null,this.currentDocument=e.initialDocument,this.summaryCache=new Map(Object.entries(e.initialDetails??{}).map(([t,s])=>[t,P(s)])),this.editContextCache=new Map(Object.entries(e.initialDetails??{}).map(([t,s])=>[t,F(s)]))}async getDocument(e){if(e!==this.params.routeId)throw new Error(`Site edit bridge is bound to route ${this.params.routeId}, received ${e}`);return A(this.currentDocument)&&(this.currentDocument=await this.refreshSnapshot(void 0,"get-document")),{ok:!0,data:this.currentDocument}}async ensureDocumentVersion(e,t){if(e!==this.params.routeId)throw new Error(`Site edit bridge is bound to route ${this.params.routeId}, received ${e}`);let s=await this.refreshSnapshot(t,"ensure-version");if(s.version<t)throw new Error(`Document refresh returned version ${s.version}, expected at least ${t}`);return{ok:!0,data:s}}async getObjectSummary(e,t){let s=this.summaryCache.get(e);if(s)return{ok:!0,data:s};let r=await this.params.client.getObjectSummary(e,t??this.params.routeId);return this.summaryCache.set(e,r.data),r}async getObjectEditContext(e,t){let s=this.editContextCache.get(e);if(s)return{ok:!0,data:s};let r=await this.params.client.getObjectEditContext(e,t??this.params.routeId);return this.editContextCache.set(e,r.data),r}async getClassNameSource(e,t){return this.params.client.getClassNameSource(e,t??this.params.routeId)}async getSourceTree(){return this.params.client.getSourceTree()}async getSourceFile(e){return this.params.client.getSourceFile(e)}async resolveSourceLocation(e){return this.params.client.resolveSourceLocation({...e,routeId:e.routeId??this.params.routeId})}async execute(e){return this.params.client.execute(e.target.kind==="node"&&!e.routeId?{...e,routeId:this.params.routeId}:e)}async undo(){return this.params.client.undo()}async redo(){return this.params.client.redo()}subscribe(e){return this.params.client.subscribe(t=>{if(t.type==="site-edit.document.changed"&&t.routeId===this.params.routeId){this.refreshSnapshot(t.version,"event").catch(()=>{}).finally(()=>e(t));return}e(t)})}async refreshSnapshot(e,t="ensure-version"){if(typeof e=="number"&&this.currentDocument.version>=e)return d("bridge.document-refresh",{routeId:this.params.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!0,skipped:!0,source:t}),this.currentDocument;if(this.refreshInFlight){d("bridge.document-refresh",{routeId:this.params.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!0,source:t});let r=await this.refreshInFlight;if(typeof e!="number"||r.version>=e||this.currentDocument.version>=e)return this.currentDocument}d("bridge.document-refresh",{routeId:this.params.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!1,source:t});let s=this.params.client.getDocument(this.params.routeId).then(r=>(this.currentDocument=r.data,this.clearObjectCaches(),this.currentDocument));this.refreshInFlight=s;try{return await s}finally{this.refreshInFlight===s&&(this.refreshInFlight=null)}}clearObjectCaches(){this.summaryCache.clear(),this.editContextCache.clear()}};function A(n){return n.version===0&&n.rootKeys.length===0}function N(n,e,t){switch(e){case"get-document":return{type:"request",payload:{id:n,kind:e,routeId:String(t.routeId)}};case"get-object-summary":return{type:"request",payload:{id:n,kind:e,key:String(t.key),routeId:typeof t.routeId=="string"?t.routeId:void 0}};case"get-object-edit-context":return{type:"request",payload:{id:n,kind:e,key:String(t.key),routeId:typeof t.routeId=="string"?t.routeId:void 0}};case"get-class-name-source":return{type:"request",payload:{id:n,kind:e,key:String(t.key),routeId:typeof t.routeId=="string"?t.routeId:void 0}};case"get-source-tree":return{type:"request",payload:{id:n,kind:e}};case"get-source-file":return{type:"request",payload:{id:n,kind:e,path:String(t.path)}};case"resolve-source-location":return{type:"request",payload:{id:n,kind:e,key:String(t.key),routeId:typeof t.routeId=="string"?t.routeId:void 0}};case"execute":return{type:"request",payload:{id:n,kind:e,request:t.request}};case"undo":case"redo":return{type:"request",payload:{id:n,kind:e}}}}function P(n){return{key:n.key,tag:n.tag,label:n.label,capabilities:{constraints:n.capabilities.constraints},isOpaque:n.isOpaque,provenanceChain:n.provenanceChain?{editMode:n.provenanceChain.editMode,diagnostics:n.provenanceChain.diagnostics}:void 0,ancestors:n.ancestors}}function F(n){return{key:n.key,componentName:n.componentName,sourceFile:n.sourceFile,capabilities:n.capabilities,writeTarget:n.writeTarget,effectiveWriteTarget:n.effectiveWriteTarget,provenanceChain:n.provenanceChain,componentSemantic:n.componentSemantic,textSegments:n.textSegments,boundaryKind:n.boundaryKind,children:n.children,siblings:n.siblings}}export{k as SiteEditWebSocketClient,V as createSiteEditBridge};
1
+ function f(n){if(!q(n))return n;let e=new Map,t=n.pageComposition.status==="supported"?n.pageComposition.placementUnitKeys:[],s=r=>{let i=e.get(r);if(i)return i;let a=n.nodes[r];if(!a)throw new Error(`Missing compact document node ${r}`);let[o,c]=a,l=n.tokens[c];if(typeof l!="string")throw new Error(`Missing compact document token ${c}`);let d=`${o===null?"":s(o)}${l}`;return e.set(r,d),d};return{siteId:n.siteId,snapshotId:n.snapshotId,routeId:n.routeId,version:n.version,pageComposition:n.pageComposition,entries:n.entries.map(r=>{let[i,a,o]=r,c=o===void 0?void 0:t[o];if(o!==void 0&&!c)throw new Error(`Missing page composition unit ${o}`);return{key:s(i),...c?{pageCompositionUnitKey:c}:{},operationSummary:E(a)}})}}function E(n){return{canUpdateText:(n&1)!==0,canInsertChild:(n&2)!==0,canMove:(n&4)!==0,canRemove:(n&8)!==0}}function q(n){return typeof n=="object"&&n!==null&&"encoding"in n&&n.encoding==="segment-trie-v1"}var p=class n{constructor(){}static parseMessage(e,t={}){let s=JSON.parse(e);if(!n.isMessage(s,t))throw new Error("Invalid site edit bridge message");return s}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 t=e.payload,s=typeof t.id=="string"?t.id:"unknown",r=n.isRequestKind(t.kind)?t.kind:"get-document";return{id:s,kind:r}}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,t={}){return!u(e)||typeof e.type!="string"?!1:e.type==="request"?n.isRequestPayload(e.payload,t):e.type==="response"||e.type==="error"?u(e.payload)&&typeof e.payload.id=="string":e.type==="event"?u(e.payload)&&u(e.payload.event):e.type==="auth"?u(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?u(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,t={}){if(!u(e)||typeof e.id!="string"||!n.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 D(e.request)&&(e.request.mutationContext===void 0||m(e.request.mutationContext))&&(t.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return e.mutationContext===void 0||m(e.mutationContext)}}};function u(n){return typeof n=="object"&&n!==null}function D(n){return u(n)&&typeof n.id=="string"&&typeof n.siteId=="string"&&typeof n.baseSnapshotId=="string"&&(n.routeId===void 0||typeof n.routeId=="string")&&typeof n.kind=="string"&&u(n.target)&&typeof n.documentVersion=="number"&&Number.isInteger(n.documentVersion)&&n.documentVersion>=0&&u(n.params)}function m(n){return R(n)}function R(n){return u(n)?typeof n.editingSessionId=="string"&&n.editingSessionId.length>0&&typeof n.lockId=="string"&&n.lockId.length>0&&typeof n.ownerId=="string"&&n.ownerId.length>0&&typeof n.leaseAssertion=="string"&&n.leaseAssertion.length>0:!1}var M="vcp:designer:trace",T="designer_trace",b="__VCP_DESIGNER_TRACE__";var _=/(authorization|cookie|token|secret|providerTrace|headers?|sourceCode|rawSource|fullSource)/i;function F(n=globalThis){let e=n;try{if(e.localStorage?.getItem(M)==="1")return!0}catch{}try{let t=e.location?.search??"";return new URLSearchParams(t).get(T)==="1"}catch{return!1}}function h(n,e={},t=globalThis){if(!F(t))return;let s={type:n,timestamp:Date.now(),payload:K(e)},r=t,i=r[b]??[];i.push(s),i.length>200&&i.splice(0,i.length-200),r[b]=i;let a=t.console?.debug;typeof a=="function"&&a.call(t.console,"[designer-trace]",s)}function K(n){return y(n,0)}function y(n,e){if(e>4)return"[truncated]";if(Array.isArray(n))return n.map(s=>y(s,e+1));if(!n||typeof n!="object")return n;let t={};for(let[s,r]of Object.entries(n)){if(_.test(s)){t[s]="[redacted]";continue}t[s]=y(r,e+1)}return t}var x=class{constructor(e){this.options=e,this.socket=null,this.connectPromise=null,this.connectAttempt=null,this.connectionState="idle",this.intentionalClose=!1,this.sequence=0,this.pending=new Map,this.listeners=new Set,this.connectionStatusListeners=new Set}connect(){return this.socket?.readyState===I&&this.connectionState==="open"?(this.setConnectionState("open"),Promise.resolve()):this.connectPromise?this.connectPromise:(this.intentionalClose=!1,this.setConnectionState("connecting"),this.connectPromise=new Promise((e,t)=>{let s;try{s=this.createSocket(this.options.url)}catch(o){let c=g(o,"WebSocket failed");this.socket=null,this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("failed",c.message),t(c);return}let r={socket:s,settled:!1,reject:t};this.connectAttempt=r,this.socket=s;let i=()=>{this.socket!==s||this.connectAttempt!==r||(r.settled=!0,this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("open"),e())},a=(o,c)=>{this.cleanupSocket(s,o,c)};s.onopen=()=>{if(!(this.socket!==s||this.connectAttempt!==r)){if(this.options.authKey){try{s.send(p.stringifyMessage({type:"auth",payload:{key:this.options.authKey}}))}catch(o){a(g(o,"WebSocket auth failed"),"failed")}return}i()}},s.onerror=o=>{if(this.socket!==s)return;let c=g(o,"WebSocket failed");a(c,"failed")},s.onmessage=o=>{if(this.socket===s){if(this.options.authKey&&this.connectAttempt===r&&!r.settled){this.handleAuthHandshakeMessage(o.data,s,r,()=>{i()});return}this.handleMessage(o.data)}},s.onclose=o=>{if(this.socket!==s)return;let c=this.intentionalClose,l=new Error(U(o,{intentional:c}));a(l,c?"closed":"failed")}}),this.connectPromise)}disconnect(){this.intentionalClose=!0,this.setConnectionState("closing");let e=this.socket;if(!e){this.rejectAll(new Error("WebSocket disconnected")),this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("closed");return}this.cleanupSocket(e,new Error("WebSocket disconnected"),"closed")}getConnectionState(){return this.connectionState}subscribeConnectionStatus(e){return this.connectionStatusListeners.add(e),e({state:this.connectionState}),()=>{this.connectionStatusListeners.delete(e)}}getDocument(e){return this.request("get-document",{routeId:e})}initializeSession(e){return this.request("initialize-session",e)}getHistoryStatus(){return this.request("get-history-status",{})}getStyleContext(e){return this.request("get-style-context",{routeId:e})}getObjectEditContext(e,t){return this.request("get-object-edit-context",{key:e,routeId:t})}getClassNameSource(e,t){return this.request("get-class-name-source",{key:e,routeId:t})}getSourceTree(){return this.request("get-source-tree",{})}getSourceFile(e){return this.request("get-source-file",e)}resolveSourceLocation(e){return this.request("resolve-source-location",e)}execute(e,t){return this.request("execute",{request:e})}undo(e){return this.request("undo",{})}redo(e){return this.request("redo",{})}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}request(e,t){let s=this.socket;if(!s||s.readyState!==I||this.connectionState!=="open")return Promise.reject(new Error(`WebSocket is not open (state: ${this.connectionState}, readyState: ${s?.readyState??"none"})`));let r=`${e}-${this.sequence+=1}`,i=P(r,e,t);return h("bridge.request",{id:r,kind:e,routeId:typeof t.routeId=="string"?t.routeId:null,key:typeof t.key=="string"?t.key:null,operationKind:k(t.request,"kind"),documentVersion:k(t.request,"documentVersion")}),new Promise((a,o)=>{let c=this.resolveRequestTimeoutMs(e),l=c>0?setTimeout(()=>{let d=this.pending.get(r);d&&(this.pending.delete(r),h("bridge.response",{id:r,kind:e,ok:!1,timedOut:!0}),d.reject(new Error(`Bridge request timed out after ${c}ms: ${e}`)))},c):null;l?.unref?.(),this.pending.set(r,{kind:e,timeout:l,resolve:d=>a(d),reject:o});try{s.send(p.stringifyMessage(i))}catch(d){let C=g(d,"WebSocket send failed");this.clearPendingRequest(r),o(C),this.cleanupSocket(s,C,"failed")}})}handleAuthHandshakeMessage(e,t,s,r){try{if(p.parseMessage(e).type==="auth-ok"){r();return}throw new Error("Bridge authentication failed")}catch(i){let a=g(i,"Bridge authentication failed");this.connectAttempt===s&&this.cleanupSocket(t,a,"failed")}}handleMessage(e){let t=p.parseMessage(e);if(t.type==="event"){for(let r of this.listeners)r(t.payload.event);return}if(t.type!=="response"&&t.type!=="error")return;let s=this.pending.get(t.payload.id);if(s){if(this.clearPendingRequest(t.payload.id),t.type==="error"){h("bridge.response",{id:t.payload.id,kind:s.kind,ok:!1}),s.reject(new Error(t.payload.message));return}h("bridge.response",{id:t.payload.id,kind:s.kind,ok:!0,resultVersion:$(t.payload.result)}),s.resolve(s.kind==="get-document"?{...t.payload.result,data:f(t.payload.result.data)}:s.kind==="initialize-session"?N(t.payload.result):t.payload.result)}}rejectAll(e){for(let[t,s]of[...this.pending])this.clearPendingRequest(t),s.reject(e)}clearPendingRequest(e){let t=this.pending.get(e);t&&(t.timeout&&clearTimeout(t.timeout),this.pending.delete(e))}resolveRequestTimeoutMs(e){let t=this.options.requestTimeoutMsByKind?.[e];return typeof t=="number"?Math.max(0,t):typeof this.options.requestTimeoutMs=="number"?Math.max(0,this.options.requestTimeoutMs):O[e]??A}cleanupSocket(e,t,s){if(this.socket===e&&(this.socket=null),this.connectAttempt?.socket===e){let r=this.connectAttempt;this.connectAttempt=null,r.settled||(r.settled=!0,r.reject(t))}this.connectPromise=null,this.rejectAll(t),this.setConnectionState(s,t.message),e.onopen=null,e.onmessage=null,e.onerror=null,e.onclose=null;try{(e.readyState===j||e.readyState===I)&&e.close()}catch{}}setConnectionState(e,t){if(this.connectionState===e&&!t)return;this.connectionState=e;let s={state:e,...t?{message:t}:{}};this.options.onConnectionStatus?.(s);for(let r of this.connectionStatusListeners)r(s)}createSocket(e){if(this.options.createSocket)return this.options.createSocket(e);if(typeof WebSocket>"u")throw new Error("Site edit preview bridge requires WebSocket");return new WebSocket(e)}},j=0,I=1,A=15e3,O={"get-source-tree":1e4,"get-source-file":1e4,"resolve-source-location":1e4,execute:6e4,undo:2e4,redo:2e4};function g(n,e){return n instanceof Error?n:new Error(e)}function U(n,e){return e.intentional?"WebSocket disconnected":n?.code===1008?n.reason?`Bridge session authentication failed: ${n.reason}`:"Bridge session authentication failed":n?.reason?`WebSocket closed: ${n.reason}`:typeof n?.code=="number"&&n.code!==1005?`WebSocket closed with code ${n.code}`:"WebSocket closed"}function k(n,e){if(!n||typeof n!="object")return null;let s=n[e];return typeof s=="string"||typeof s=="number"?s:null}function $(n){if(!n||typeof n!="object")return null;let e=n,t=e.data?.resultVersion??e.data?.version??null;return typeof t=="number"?t:null}function H(n){return new S(n)}var S=class{constructor(e){this.params=e,this.refreshInFlight=null,this.objectGeneration=0,this.styleGeneration=0,this.editContextCache=new Map,this.editContextInFlight=new Map,this.classNameSourceCache=new Map,this.classNameSourceInFlight=new Map,this.styleContextCache=null,this.styleContextInFlight=null,this.currentDocument=e.initialDocument,this.observedDocumentVersion=e.initialDocument.version,this.sessionInitialized=!w(e.initialDocument)}async getDocument(e){if(e!==this.params.routeId)throw new Error(`Site edit bridge is bound to route ${this.params.routeId}, received ${e}`);let t=w(this.currentDocument);return(t||this.currentDocument.version<this.observedDocumentVersion)&&(this.currentDocument=await this.refreshSnapshot(t?void 0:this.observedDocumentVersion,"get-document"),this.sessionInitialized=!0),{ok:!0,data:this.currentDocument}}async initializeSession(e){if(e.routeId!==this.params.routeId)throw new Error(`Site edit bridge is bound to route ${this.params.routeId}, received ${e.routeId}`);if(!this.params.client.initializeSession)throw new Error("Site edit bridge client does not support initialize-session");let t=await this.params.client.initializeSession(e);return this.currentDocument=t.data.document,this.observedDocumentVersion=t.data.document.version,this.sessionInitialized=!0,this.clearObjectCaches(),t}async getHistoryStatus(){return this.params.client.getHistoryStatus()}async getStyleContext(e){if(e!==this.params.routeId)throw new Error(`Site edit bridge is bound to route ${this.params.routeId}, received ${e}`);let t=this.styleContextCache;if(t?.generation===this.styleGeneration)return{ok:!0,data:t.value};let s=this.styleContextInFlight;if(s?.generation===this.styleGeneration)return s.request;let r=this.styleGeneration,i=this.params.client.getStyleContext(e).then(a=>(this.styleGeneration===r&&(this.styleContextCache={generation:r,value:a.data}),a)).finally(()=>{this.styleContextInFlight?.request===i&&(this.styleContextInFlight=null)});return this.styleContextInFlight={generation:r,request:i},i}async ensureDocumentVersion(e,t){if(e!==this.params.routeId)throw new Error(`Site edit bridge is bound to route ${this.params.routeId}, received ${e}`);let s=await this.refreshSnapshot(t,"ensure-version");if(s.version<t)throw new Error(`Document refresh returned version ${s.version}, expected at least ${t}`);return{ok:!0,data:s}}async acknowledgeDocumentVersion(e,t){if(e!==this.params.routeId)throw new Error(`Site edit bridge is bound to route ${this.params.routeId}, received ${e}`);return t>this.currentDocument.version&&(this.currentDocument={...this.currentDocument,version:t},this.clearObjectCaches({preserveStyleContext:!0})),this.observedDocumentVersion=Math.max(this.observedDocumentVersion,t),{ok:!0,data:this.currentDocument}}async getObjectEditContext(e,t){let s=this.objectCacheKey(e,t),r=this.editContextCache.get(s);if(r)return{ok:!0,data:r};let i=this.editContextInFlight.get(s);if(i)return i;let a=this.objectGeneration,o=this.params.client.getObjectEditContext(e,t??this.params.routeId).then(c=>(this.objectGeneration===a&&this.editContextCache.set(s,c.data),c)).finally(()=>{this.editContextInFlight.get(s)===o&&this.editContextInFlight.delete(s)});return this.editContextInFlight.set(s,o),o}async getClassNameSource(e,t){let s=this.objectCacheKey(e,t),r=this.classNameSourceCache.get(s);if(r)return r;let i=this.classNameSourceInFlight.get(s);if(i)return i;let a=this.objectGeneration,o=this.params.client.getClassNameSource(e,t??this.params.routeId).then(c=>(this.objectGeneration===a&&this.classNameSourceCache.set(s,c),c)).finally(()=>{this.classNameSourceInFlight.get(s)===o&&this.classNameSourceInFlight.delete(s)});return this.classNameSourceInFlight.set(s,o),o}async getSourceTree(){return this.params.client.getSourceTree()}async getSourceFile(e){return this.params.client.getSourceFile(e)}async resolveSourceLocation(e){return this.params.client.resolveSourceLocation({...e,routeId:e.routeId??this.params.routeId})}async execute(e,t){let s=e.target.kind==="node"&&!e.routeId?{...e,routeId:this.params.routeId}:e;return this.params.client.execute(s)}async undo(e){return this.params.client.undo()}async redo(e){return this.params.client.redo()}subscribe(e){return this.params.client.subscribe(t=>{if(t.type==="site-edit.document.changed"&&t.routeId===this.params.routeId&&this.sessionInitialized){this.observedDocumentVersion=Math.max(this.observedDocumentVersion,t.version),t.version>this.currentDocument.version&&this.clearObjectCaches(),e(t);return}e(t)})}async refreshSnapshot(e,t="ensure-version"){if(typeof e=="number"&&this.currentDocument.version>=e)return h("bridge.document-refresh",{routeId:this.params.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!0,skipped:!0,source:t}),this.currentDocument;if(this.refreshInFlight){h("bridge.document-refresh",{routeId:this.params.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!0,source:t});let r=await this.refreshInFlight;if(typeof e!="number"||r.version>=e||this.currentDocument.version>=e)return this.currentDocument}h("bridge.document-refresh",{routeId:this.params.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!1,source:t});let s=this.params.client.getDocument(this.params.routeId).then(r=>(this.currentDocument=r.data,this.observedDocumentVersion=Math.max(this.observedDocumentVersion,r.data.version),this.clearObjectCaches(),this.currentDocument));this.refreshInFlight=s;try{return await s}finally{this.refreshInFlight===s&&(this.refreshInFlight=null)}}clearObjectCaches(e){this.objectGeneration+=1,this.editContextCache.clear(),this.editContextInFlight.clear(),this.classNameSourceCache.clear(),this.classNameSourceInFlight.clear(),!e?.preserveStyleContext&&(this.styleGeneration+=1,this.styleContextCache=null,this.styleContextInFlight=null)}objectCacheKey(e,t){return`${t??this.params.routeId}\0${e}`}};function w(n){return n.version===0&&n.entries.length===0}function N(n){let e=n;return{...e,data:{...e.data,document:f(e.data.document)}}}function P(n,e,t){switch(e){case"initialize-session":return{type:"request",payload:{id:n,kind:e,routeId:String(t.routeId),...t.mutationContext?{mutationContext:t.mutationContext}:{}}};case"get-document":return{type:"request",payload:{id:n,kind:e,routeId:String(t.routeId)}};case"get-history-status":return{type:"request",payload:{id:n,kind:e}};case"get-style-context":return{type:"request",payload:{id:n,kind:e,routeId:String(t.routeId)}};case"get-object-edit-context":return{type:"request",payload:{id:n,kind:e,key:String(t.key),routeId:typeof t.routeId=="string"?t.routeId:void 0}};case"get-class-name-source":return{type:"request",payload:{id:n,kind:e,key:String(t.key),routeId:typeof t.routeId=="string"?t.routeId:void 0}};case"get-source-tree":return{type:"request",payload:{id:n,kind:e}};case"get-source-file":return{type:"request",payload:{id:n,kind:e,path:String(t.path)}};case"resolve-source-location":return{type:"request",payload:{id:n,kind:e,key:String(t.key),routeId:typeof t.routeId=="string"?t.routeId:void 0}};case"execute":return{type:"request",payload:{id:n,kind:e,request:t.request}};case"undo":case"redo":return{type:"request",payload:{id:n,kind:e,mutationContext:t.mutationContext}}}}export{x as SiteEditWebSocketClient,H as createSiteEditBridge};
@@ -1,27 +1,26 @@
1
- import type { SiteEditRenderDocument, SiteEditRenderEntry } from "@vcp-dev/contracts";
1
+ import type { SiteEditRenderDocument } from "@vcp-dev/contracts";
2
2
  export type CompactRenderDocument = {
3
3
  encoding: "segment-trie-v1";
4
4
  siteId: string;
5
5
  snapshotId: string;
6
6
  routeId: string;
7
7
  version: number;
8
+ pageComposition: SiteEditRenderDocument["pageComposition"];
8
9
  tokens: string[];
9
10
  nodes: CompactRenderDocumentNode[];
10
- rootKeyNodeIds: number[];
11
11
  entries: CompactRenderDocumentEntry[];
12
12
  };
13
- export type CompactRenderDocumentNode = {
14
- parent: number | null;
15
- token: number;
16
- };
17
- export type CompactRenderDocumentEntry = {
18
- keyNodeId: number;
19
- tag: string;
20
- label: string;
21
- parentKeyNodeId: number | null;
22
- childKeyNodeIds: number[];
23
- operationSummary: SiteEditRenderEntry["operationSummary"];
24
- };
13
+ /** [parentNodeId, tokenId] */
14
+ export type CompactRenderDocumentNode = [
15
+ parentNodeId: number | null,
16
+ tokenId: number
17
+ ];
18
+ /** [keyNodeId, four-bit operation flags, optional placement-unit index] */
19
+ export type CompactRenderDocumentEntry = [
20
+ keyNodeId: number,
21
+ operationFlags: number,
22
+ pageCompositionUnitIndex?: number
23
+ ];
25
24
  export declare function encodeRenderDocument(document: SiteEditRenderDocument): CompactRenderDocument;
26
25
  export declare function decodeRenderDocument(document: CompactRenderDocument | SiteEditRenderDocument): SiteEditRenderDocument;
27
26
  export declare function isCompactRenderDocument(value: CompactRenderDocument | SiteEditRenderDocument): value is CompactRenderDocument;
@@ -1 +1 @@
1
- function I(e){let s=new Map,t=[],n=new Map,p=[],i=o=>{let r=s.get(o);if(typeof r=="number")return r;let d=t.length;return t.push(o),s.set(o,d),d},a=o=>{let r=null;for(let d of g(o)){let c=`${r??"root"}\0${d}`,u=n.get(c);if(typeof u=="number"){r=u;continue}let l=p.length;p.push({parent:r,token:i(d)}),n.set(c,l),r=l}if(r===null)throw new Error("Document key must not be empty");return r};return{encoding:"segment-trie-v1",siteId:e.siteId,snapshotId:e.snapshotId,routeId:e.routeId,version:e.version,tokens:t,nodes:p,rootKeyNodeIds:e.rootKeys.map(a),entries:e.entries.map(o=>({keyNodeId:a(o.key),tag:o.tag,label:o.label,parentKeyNodeId:o.parentKey?a(o.parentKey):null,childKeyNodeIds:o.childKeys.map(a),operationSummary:o.operationSummary}))}}function m(e){if(!f(e))return e;let s=new Map,t=n=>{let p=s.get(n);if(p)return p;let i=e.nodes[n];if(!i)throw new Error(`Missing compact document node ${n}`);let a=e.tokens[i.token];if(typeof a!="string")throw new Error(`Missing compact document token ${i.token}`);let o=`${i.parent===null?"":t(i.parent)}${a}`;return s.set(n,o),o};return{siteId:e.siteId,snapshotId:e.snapshotId,routeId:e.routeId,version:e.version,rootKeys:e.rootKeyNodeIds.map(t),entries:e.entries.map(n=>({key:t(n.keyNodeId),tag:n.tag,label:n.label,parentKey:n.parentKeyNodeId===null?null:t(n.parentKeyNodeId),childKeys:n.childKeyNodeIds.map(t),operationSummary:n.operationSummary}))}}function f(e){return typeof e=="object"&&e!==null&&"encoding"in e&&e.encoding==="segment-trie-v1"}function g(e){if(!e)return[];let s=[],t=0,n=h(e,t);if(n<0)return s.push(e),s;for(s.push(e.slice(0,n)),t=n;t<e.length;){if(n=h(e,t+(e.startsWith("::",t)?2:1)),n<0){s.push(e.slice(t));break}s.push(e.slice(t,n)),t=n}return s}function h(e,s){let t=e.indexOf("/",s),n=e.indexOf("::",s);return t<0?n:n<0?t:Math.min(t,n)}export{m as decodeRenderDocument,I as encodeRenderDocument,f as isCompactRenderDocument};
1
+ function x(e){let s=new Map,t=[],o=new Map,r=[],c=e.pageComposition.status==="supported"?e.pageComposition.placementUnitKeys:[],u=new Map(c.map((n,i)=>[n,i])),p=e.pageComposition.status==="supported"?{status:"supported",containerKey:e.pageComposition.containerKey,hasBusinessMembers:e.pageComposition.hasBusinessMembers,placementUnitKeys:e.pageComposition.placementUnitKeys,placements:e.pageComposition.placements.map(n=>({placementKey:n.placementKey,placementUnitKey:n.placementUnitKey,relation:n.relation}))}:{status:"unsupported",reason:e.pageComposition.reason,diagnostics:e.pageComposition.diagnostics},a=n=>{let i=s.get(n);if(typeof i=="number")return i;let g=t.length;return t.push(n),s.set(n,g),g},d=n=>{let i=null;for(let g of K(n)){let m=`${i??"root"}\0${g}`,l=o.get(m);if(typeof l=="number"){i=l;continue}let f=r.length;r.push([i,a(g)]),o.set(m,f),i=f}if(i===null)throw new Error("Document key must not be empty");return i};return{encoding:"segment-trie-v1",siteId:e.siteId,snapshotId:e.snapshotId,routeId:e.routeId,version:e.version,pageComposition:p,tokens:t,nodes:r,entries:e.entries.map(n=>{let i=n.pageCompositionUnitKey?u.get(n.pageCompositionUnitKey)??-1:-1;if(n.pageCompositionUnitKey&&i<0)throw new Error(`Render entry ${n.key} references unknown page composition unit ${n.pageCompositionUnitKey}`);return[d(n.key),C(n.operationSummary),...i>=0?[i]:[]]})}}function U(e){if(!I(e))return e;let s=new Map,t=e.pageComposition.status==="supported"?e.pageComposition.placementUnitKeys:[],o=r=>{let c=s.get(r);if(c)return c;let u=e.nodes[r];if(!u)throw new Error(`Missing compact document node ${r}`);let[p,a]=u,d=e.tokens[a];if(typeof d!="string")throw new Error(`Missing compact document token ${a}`);let n=`${p===null?"":o(p)}${d}`;return s.set(r,n),n};return{siteId:e.siteId,snapshotId:e.snapshotId,routeId:e.routeId,version:e.version,pageComposition:e.pageComposition,entries:e.entries.map(r=>{let[c,u,p]=r,a=p===void 0?void 0:t[p];if(p!==void 0&&!a)throw new Error(`Missing page composition unit ${p}`);return{key:o(c),...a?{pageCompositionUnitKey:a}:{},operationSummary:y(u)}})}}function C(e){return(e.canUpdateText?1:0)|(e.canInsertChild?2:0)|(e.canMove?4:0)|(e.canRemove?8:0)}function y(e){return{canUpdateText:(e&1)!==0,canInsertChild:(e&2)!==0,canMove:(e&4)!==0,canRemove:(e&8)!==0}}function I(e){return typeof e=="object"&&e!==null&&"encoding"in e&&e.encoding==="segment-trie-v1"}function K(e){if(!e)return[];let s=[],t=0,o=h(e,t);if(o<0)return s.push(e),s;for(s.push(e.slice(0,o)),t=o;t<e.length;){if(o=h(e,t+(e.startsWith("::",t)?2:1)),o<0){s.push(e.slice(t));break}s.push(e.slice(t,o)),t=o}return s}function h(e,s){let t=e.indexOf("/",s),o=e.indexOf("::",s);return t<0?o:o<0?t:Math.min(t,o)}export{U as decodeRenderDocument,x as encodeRenderDocument,I as isCompactRenderDocument};