@veltdev/sdk-staging 6.0.13 → 6.0.14-beta.2
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/app/models/element/activity-element.model.d.ts +9 -0
- package/app/models/element/autocomplete-element.model.d.ts +10 -0
- package/app/models/element/comment-element.model.d.ts +72 -0
- package/app/models/element/notification-element.model.d.ts +17 -0
- package/app/utils/constants.d.ts +1 -0
- package/{chunk-DZYVUUMS.js → chunk-2HGCWC7B.js} +1 -1
- package/chunk-2KTYC2AG.js +1 -0
- package/{chunk-5XJDGVGO.js → chunk-3AL5S762.js} +1 -1
- package/{chunk-TE4S24VF.js → chunk-45X5JRZ6.js} +1 -1
- package/chunk-6NWFM6PC.js +1 -0
- package/{chunk-IKXUU4LK.js → chunk-77FADRSO.js} +1 -1
- package/chunk-7TFVNMHJ.js +2 -0
- package/{chunk-NFQ33NZA.js → chunk-C5JOX4MX.js} +1 -1
- package/{chunk-XXU7YXMF.js → chunk-DFEL6HM7.js} +1 -1
- package/{chunk-Q2XHYKCH.js → chunk-DZYXTTOX.js} +1 -1
- package/{chunk-GHWFNVK2.js → chunk-FZSZ6AV3.js} +1 -1
- package/{chunk-MUMFBRNH.js → chunk-GULBDPIM.js} +1 -1
- package/{chunk-EMCGVIKG.js → chunk-HRCWTVRH.js} +1 -1
- package/chunk-I63T4QWP.js +6 -0
- package/{chunk-ZUQTUSHK.js → chunk-IMH5OESK.js} +1 -1
- package/chunk-IQ22I2I3.js +1 -0
- package/{chunk-NYRQTKJA.js → chunk-KNQJBBQA.js} +1 -1
- package/chunk-KQPNTB77.js +1 -0
- package/chunk-KWLMOL5X.js +193 -0
- package/chunk-L4QMAEKA.js +1 -0
- package/chunk-MRY4A7N6.js +1 -0
- package/{chunk-4VEJIKFU.js → chunk-NMPXJFDX.js} +1 -1
- package/chunk-O3FUWWWQ.js +1 -0
- package/{chunk-LOLURD74.js → chunk-PWTBCAPJ.js} +1 -1
- package/{chunk-V32SISMC.js → chunk-QFOWEUDB.js} +1 -1
- package/{chunk-4BAHMWIE.js → chunk-TQNLXTQA.js} +1 -1
- package/{chunk-STNBRDN3.js → chunk-UI2CRSTN.js} +1 -1
- package/{chunk-YTVKKNRL.js → chunk-UVKIGEOH.js} +1 -1
- package/{chunk-4YEJYNMX.js → chunk-VJLHHQ2Z.js} +1 -1
- package/chunk-VW5ND7U4.js +93 -0
- package/{chunk-2LJHR77K.js → chunk-WMDZMDJS.js} +1 -1
- package/{chunk-SYWOQU23.js → chunk-YFBAHLK6.js} +26 -26
- package/chunk-YO4HNOAN.js +1 -0
- package/{chunk-IQIWMWKJ.js → chunk-ZHXLO4CC.js} +1 -1
- package/package.json +1 -1
- package/velt-activity-log.js +1 -1
- package/velt-area.js +1 -1
- package/velt-arrow.js +2 -2
- package/velt-comment.js +7 -7
- package/velt-crdt.js +1 -1
- package/velt-cursor.js +2 -2
- package/velt-huddle.js +2 -2
- package/velt-live-state-sync.js +2 -2
- package/velt-notification.js +1 -1
- package/velt-portable.js +1 -1
- package/velt-presence.js +1 -1
- package/velt-reaction.js +1 -1
- package/velt-recorder.js +1 -1
- package/velt-rewriter.js +2 -2
- package/velt-selection.js +1 -1
- package/velt-tag.js +1 -1
- package/velt-user-invite.js +2 -2
- package/velt-user-request.js +1 -1
- package/velt-video-player.js +1 -1
- package/velt-views.js +2 -2
- package/velt.js +6 -6
- package/chunk-6YGRELTV.js +0 -2
- package/chunk-7TNGPXR5.js +0 -1
- package/chunk-A4HLP4VU.js +0 -1
- package/chunk-A6I23DJF.js +0 -1
- package/chunk-DAUJEOMM.js +0 -193
- package/chunk-IEOSSENF.js +0 -1
- package/chunk-KJN6PDWN.js +0 -93
- package/chunk-KKKGHIG4.js +0 -1
- package/chunk-RSCWN2VG.js +0 -1
- package/chunk-SZUD6M7J.js +0 -6
- package/chunk-TG2UOHTU.js +0 -1
- package/chunk-UY5BYX47.js +0 -1
package/velt-portable.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$i as T,Vi as W,Yd as o,Zi as O,_i as p,a as b,aj as M,c as _,g as m,l as C,qj as D,sj as L}from"./chunk-SYWOQU23.js";var H=["hello","subscribe","unsubscribe","publish","presence","onDisconnect","onDisconnectCancel","ping"],V=["welcome","snapshot","delta","result","error","pong"],X=[...H,...V],y=3e4,q=2,k=2e4,I=1e3,N=3e4,x=1;function v(i){if(typeof i!="string")return NaN;let e=Number(i.split(".")[0]);return Number.isInteger(e)?e:NaN}function g(i){try{let e=i.apiKey??"",s=i.instanceId?`${i.feature}/${i.instanceId}`:i.feature;switch(i.scope){case"doc":return`${e}:${i.organizationId??""}:doc:${i.documentId??""}:${s}`;case"user":return i.organizationId?`${e}:${i.organizationId}:user:${i.userIdHash??""}:${s}`:`${e}:user:${i.userIdHash??""}:${s}`;case"org":return`${e}:${i.organizationId??""}:${s}`;default:return`${e}:${s}`}}catch(e){return o.catch("Error in buildPortableWireKey: ",e),`${i?.apiKey??""}:${i?.feature??""}`}}var U={presence:"updatePresence",liveState:"publishLiveState",cursor:"updateCursor",selection:"updateSelection",huddle:"updateHuddle",syncVideoPlayer:"updateSyncVideoPlayer",flockSession:"updateFlock"};function K(i,e){if(i==="crdt"){let s=e?.uptoSeq;return Number.isInteger(s)?"writeCrdtSnapshot":"writeCrdtUpdate"}return U[i]}var P=class{constructor(e){this.session=e}whenReady(){return new Promise(e=>{try{let s=!1,t=null,n=null,r=()=>{s||(s=!0,n!==null&&(clearTimeout(n),n=null),setTimeout(()=>{try{t?.unsubscribe()}catch{}},0),e())};n=setTimeout(r,k),t=this.session.getAuthState$().subscribe(a=>{a?.signedIn&&r()})}catch(s){o.catch("Error in PortableRealtimeAdapter whenReady: ",s),e()}})}subscribe(e,s,t,n){try{let r=this.session.getTransport();if(!r)return{unsubscribe:()=>{}};let a=g(e),l=!1,c=r.subscribeChannel(a,s,d=>{try{if(d.kind==="denied"){o.catch(`Velt [portable backend]: subscribe denied on channel ${a}: ${d.code??"unknown"}${L(d)}`,b({ok:!1,code:d.code},d.reasonCode?{reasonCode:d.reasonCode}:{})),n?.onError&&!l&&(l=!0,n.onError(d.code??"permission-denied"));return}if(d.kind==="delta"){t({type:d.change??"modified",id:d.id??"",data:d.data,seq:d.seq});return}if(d.kind==="snapshot-end"){n?.onSnapshotComplete?.();return}t({type:"added",id:d.id??"",data:d.data})}catch(h){o.catch("Error in PortableRealtimeAdapter subscribe callback: ",h)}},n?.since);return{unsubscribe:()=>c.unsubscribe()}}catch(r){return o.catch("Error in PortableRealtimeAdapter subscribe: ",r),{unsubscribe:()=>{}}}}publish(e,s,t){try{let n=K(e.feature,t);if(!n)return Promise.resolve({ok:!1,code:"unimplemented",message:`no realtime publish operation for feature '${e.feature}'`});let r=this.session.getTransport();return r?r.request("publish",g(e),n,t):Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"})}catch(n){return o.catch("Error in PortableRealtimeAdapter publish: ",n),Promise.resolve({ok:!1,code:"failed-precondition"})}}presence(e,s){try{let t=this.session.getTransport();return t?t.request("presence",g(e),"updatePresence",s):Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"})}catch(t){return o.catch("Error in PortableRealtimeAdapter presence: ",t),Promise.resolve({ok:!1,code:"failed-precondition"})}}onDisconnect(e,s){return m(this,null,function*(){try{let t=this.session.getTransport();return t?yield t.registerOnDisconnect(g(e),s):()=>{}}catch(t){return o.catch("Error in PortableRealtimeAdapter onDisconnect: ",t),()=>{}}})}getServerTimeOffset(){try{return Promise.resolve(this.session.getTransport()?.getServerTimeOffset()??0)}catch(e){return o.catch("Error in PortableRealtimeAdapter getServerTimeOffset: ",e),Promise.resolve(0)}}onServerTimeOffset(e){try{return this.session.getTransport()?.onServerTimeOffset(e)??(()=>{})}catch(s){return o.catch("Error in PortableRealtimeAdapter onServerTimeOffset: ",s),()=>{}}}};var S=class{constructor(e,s){this.rest=e,this.session=s}read(e){return m(this,null,function*(){let s=yield this.rest.call("read",e);return s.ok?{data:s.data}:{data:void 0,errorCode:s.code}})}runTransaction(e){return e()}op(e,s){if(s&&typeof s=="object"&&"channel"in s){let t=s,{channel:n}=t,r=_(t,["channel"]);return this.rest.call(e,r)}return this.rest.call(e,s)}publishOp(e,s,t){if(!s.channel)return Promise.resolve({ok:!1,code:"failed-precondition",message:`portable publish op '${e}' requires a channel on the OperationRequest`});let n=this.session?.getTransport();return n?n.request("publish",g(s.channel),e,t):Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"})}notificationWirePayload(e,s){let t=e.resourceId??(typeof e.data?.notificationId=="string"?e.data.notificationId:void 0),n={};if(t!=null&&String(t).length>0&&(n.notificationId=String(t)),s&&e.data!==void 0){let r=e.data;r&&typeof r=="object"&&r.data!==void 0&&r.notificationId!==void 0?n.data=r.data:n.data=e.data}return n}annotationWirePayload(e,s,t=!1){let n=e.resourceId??(typeof e.data?.annotationId=="string"?e.data.annotationId:void 0),r={};return n!=null&&String(n).length>0&&(r.annotationId=String(n)),s&&e.data!==void 0&&(r.data=e.data),t&&e.notificationData!==void 0&&(r.notificationData=e.notificationData),r}addComment(e){return this.publishOp("addComment",e,this.annotationWirePayload(e,!0,!0))}editComment(e){return this.publishOp("editComment",e,this.annotationWirePayload(e,!0))}deleteComment(e){return this.publishOp("deleteComment",e,this.annotationWirePayload(e,!1))}addReaction(e){return this.publishOp("addReaction",e,this.annotationWirePayload(e,!0))}editReaction(e){return this.publishOp("editReaction",e,this.annotationWirePayload(e,!0))}deleteReaction(e){return this.publishOp("deleteReaction",e,this.annotationWirePayload(e,!1))}recordDocumentView(e){return this.publishOp("recordDocumentView",e,{})}getActivitiesForDocuments(e){return this.rest.call("getActivitiesForDocuments",e.data)}getActivitiesForEntity(e){return this.rest.call("getActivitiesForEntity",e.data)}setCommentResolved(e){return this.op("setCommentResolved",e)}setCommentMetadata(e){return this.op("setCommentMetadata",e)}bumpCommentViews(e){return this.op("bumpCommentViews",e)}markCommentThreadRead(e){return this.publishOp("markCommentThreadRead",e,this.notificationWirePayload(e,!1))}markNotificationRead(e){return this.publishOp("markNotificationRead",e,this.notificationWirePayload(e,!1))}addNotification(e){return this.op("addNotification",e)}deleteNotification(e){return this.publishOp("deleteNotification",e,this.notificationWirePayload(e,!1))}updateNotification(e){return this.publishOp("updateNotification",e,this.notificationWirePayload(e,!0))}updatePresence(e){return this.op("updatePresence",e)}grantAccess(e){return this.op("grantAccess",e)}revokeAccess(e){return this.rest.call("revokeAccess",{data:e.data})}getAccess(e){return this.rest.call("getAccess",{data:e.data})}upsertOrgUserProfile(e){return this.rest.call("addUserInfoToOrganization",e.data)}upsertDocMetadata(e){return this.op("upsertDocMetadata",e)}upsertOrgMetadata(e){return this.op("upsertOrgMetadata",e)}createActivity(e){return this.rest.call("createActivity",e.data)}};var j={fetch:(i,e)=>fetch(i,e)},F={400:"invalid-argument",401:"unauthenticated",403:"permission-denied",404:"not-found",412:"failed-precondition",429:"resource-exhausted"};function z(i){return!!i&&typeof i=="object"&&typeof i.ok=="boolean"}var B=!1,w=class{constructor(e){this.session=e}call(e,s){return m(this,null,function*(){try{let t=this.session.getApiEndpoint(),n=this.session.getToken();if(!t||!n)return{ok:!1,code:"failed-precondition",message:"portable session not established"};if(!W(t))return B||(B=!0,o.catch("Velt [portable backend]: refusing to call a non-https:// gateway API endpoint ("+t+"). The session token would travel in cleartext; portable operations are disabled. (Plain http:// is permitted for localhost only.)",void 0)),{ok:!1,code:"failed-precondition",message:"insecure portable api endpoint"};let r=yield j.fetch(`${t}/operations/${e}`,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify(s??{})}),a;try{a=yield r.json()}catch{a=void 0}if(z(a)){if(a.ok)return r.ok?a:{ok:!1,code:F[r.status]??"permission-denied",message:`HTTP ${r.status} on operation '${e}' contradicted an ok:true envelope`};let c=a.code;return c&&M.includes(c)?a:{ok:!1,code:"permission-denied",message:`unrecognized denial code '${c}'`}}return r.ok?{ok:!0,data:a}:{ok:!1,code:F[r.status]??"permission-denied",message:`HTTP ${r.status} on operation '${e}'`}}catch(t){return o.catch(`Error in PortableRestClient call '${e}': `,t),{ok:!1,code:"permission-denied",message:"portable operation request failed"}}})}};function Y(i){if(!i)return!1;try{let e=new URL(i);return e.protocol==="wss:"?!0:e.protocol==="ws:"&&["localhost","127.0.0.1","[::1]"].includes(e.hostname)}catch{return!1}}var E=class{constructor(e){this.transportFactory=e,this.token=null,this.wsEndpoint=null,this.apiEndpoint=null,this.transport=null,this.authState$=new C(null),this.nullTokenWarned=!1,this.insecureEndpointWarned=!1,this.offOffset=null,this.offTerminal=null}establish(e){try{if(!e)return;if(this.wsEndpoint=e.wsEndpoint??null,this.apiEndpoint=e.apiEndpoint??null,this.token=typeof e.token=="string"&&e.token.length>0?e.token:null,!this.token){this.transport&&this.teardown(),this.nullTokenWarned||(this.nullTokenWarned=!0,console.warn("Velt [portable backend]: identify() returned no session token (token: null). Portable transport will not connect on this page load; portable features are disabled."));return}if(!Y(this.wsEndpoint)){this.transport&&this.teardown(),this.insecureEndpointWarned||(this.insecureEndpointWarned=!0,console.warn("Velt [portable backend]: refusing to connect to a non-wss:// gateway endpoint ("+this.wsEndpoint+"). The session token would travel in cleartext; portable features are disabled. (Plain ws:// is permitted for localhost only.)"));return}this.transport||(this.transport=this.transportFactory(this),this.offOffset=this.transport.onServerTimeOffset(()=>{this.authState$.value?.signedIn||this.authState$.next({signedIn:!0})}),this.offTerminal=this.transport.onTerminal(()=>{this.authState$.value!==null&&this.authState$.next(null)})),this.transport.connect()}catch(s){o.catch("Error in PortableSession establish: ",s)}}refreshToken(e){try{if(typeof e!="string"||!e.length)return;this.token=e,this.transport?.refreshAuth()}catch(s){o.catch("Error in PortableSession refreshToken: ",s)}}teardown(){try{this.offOffset?.(),this.offOffset=null,this.offTerminal?.(),this.offTerminal=null,this.transport?.close(),this.transport=null,this.authState$.value!==null&&this.authState$.next(null)}catch(e){o.catch("Error in PortableSession teardown: ",e)}}getToken(){return this.token}getWsEndpoint(){return this.wsEndpoint}getApiEndpoint(){return this.apiEndpoint}getTransport(){return this.transport}getAuthState$(){return this.authState$.asObservable()}};var f=1,R=class{constructor(e){this.deps=e,this.state="idle",this.socket=null,this.channels=new Map,this.pendingRequests=new Map,this.nextRequestId=1,this.serverTimeOffsetMs=0,this.offsetListeners=new Set,this.lastInboundAtMs=0,this.heartbeatTimer=null,this.pongDeadlineTimer=null,this.reconnectTimer=null,this.reconnectAttempts=0,this.authRecoveryAttempts=0,this.terminalListeners=new Set}onTerminal(e){return this.terminalListeners.add(e),()=>{this.terminalListeners.delete(e)}}notifyTerminal(e){this.terminalListeners.forEach(s=>{try{s(e)}catch(t){o.catch("Error in PortableWsTransport terminal listener: ",t)}})}getState(){return this.state}connect(){try{if(this.state==="connecting"||this.state==="open"||this.state==="reauthing"||this.state==="unsupported")return;let e=this.deps.getUrl(),s=this.deps.getToken();if(!e||!s)return;this.state="connecting";let t=this.deps.wsFactory(e);this.socket=t,t.onopen=()=>this.onSocketOpen(t),t.onmessage=n=>this.onFrame(t,n.data),t.onclose=()=>this.onSocketClosed(t),t.onerror=()=>{}}catch(e){o.catch("Error in PortableWsTransport connect: ",e)}}close(){try{this.state="closed",this.notifyTerminal("closed"),this.clearTimers();let e=this.socket;this.socket=null,e&&e.readyState<=f&&e.close(1e3,"client close"),this.failPendingRequests()}catch(e){o.catch("Error in PortableWsTransport close: ",e)}}checkLivenessOnResume(e=y*2){try{if(this.state!=="open"&&this.state!=="reauthing")return;let s=this.lastInboundAtMs?this.now()-this.lastInboundAtMs:Number.POSITIVE_INFINITY;if(s<=e)return;p("liveness.stale-on-resume",`silentFor=${Math.round(s/1e3)}s state=${this.state}`),T("the realtime connection was silent while the tab was in the background","Nothing had been received for longer than two heartbeats when this tab came back, so the connection is treated as dead and re-established. Data shown before this point may have been stale \u2014 the UI keeps what it already had, so this is invisible without this message."),this.handleDeadSocket()}catch(s){o.catch("Error in PortableWsTransport checkLivenessOnResume: ",s)}}refreshAuth(){try{if(this.state==="open"&&this.socket&&this.socket.readyState===f){this.state="reauthing",this.sendHello();return}(this.state==="closed"||this.state==="idle")&&(this.state="idle",this.connect())}catch(e){o.catch("Error in PortableWsTransport refreshAuth: ",e)}}subscribeChannel(e,s,t,n){let r=this.ensureChannel(e,s),a={onEvent:t,active:!0};if(r.subscribers.add(a),r.lastSeq===void 0&&typeof n=="number"&&(r.lastSeq=n),r.hasState){for(let[l,c]of r.lastKnown)try{a.onEvent({kind:"snapshot-item",id:l,data:c})}catch(d){o.catch("Error in PortableWsTransport cached-state replay: ",d)}try{a.onEvent({kind:"snapshot-end"})}catch(l){o.catch("Error in PortableWsTransport cached-state snapshot-end: ",l)}}return this.wireSubscribeIfNeeded(e,r),{unsubscribe:()=>{try{if(!a.active)return;a.active=!1,r.subscribers.delete(a),r.subscribers.size===0&&(this.clearSnapshotWatchdog(r),this.channels.delete(e),r.wired&&this.socket&&this.socket.readyState===f&&this.sendFrame({type:"unsubscribe",channel:e,requestId:`r${this.nextRequestId++}`}))}catch(l){o.catch("Error in PortableWsTransport unsubscribe: ",l)}}}}request(e,s,t,n){try{if(this.state!=="open"&&this.state!=="reauthing")return Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"});let r=`r${this.nextRequestId++}`,a=new Promise(d=>{this.pendingRequests.set(r,d)}),l={type:e,channel:s,op:t,requestId:r};e==="presence"?l.state=n:l.payload=n;let c=!1;try{c=this.sendFrame(l)}catch(d){throw this.pendingRequests.delete(r),d}return c?a:(this.pendingRequests.delete(r),Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"}))}catch(r){return o.catch("Error in PortableWsTransport request: ",r),Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport send failed"})}}registerOnDisconnect(e,s){return m(this,null,function*(){if(this.state!=="open"&&this.state!=="reauthing")return()=>{};let t=`r${this.nextRequestId++}`,n=new Promise(c=>{this.pendingRequests.set(t,c)}),r=!1;try{r=this.sendFrame({type:"onDisconnect",channel:e,cleanup:s,requestId:t})}catch(c){return this.pendingRequests.delete(t),o.catch("Error in PortableWsTransport registerOnDisconnect send: ",c),()=>{}}if(!r)return this.pendingRequests.delete(t),()=>{};let a=yield n,l=a.ok?a.id:void 0;return()=>{try{l&&this.socket&&this.socket.readyState===f&&this.sendFrame({type:"onDisconnectCancel",intentId:l,requestId:`r${this.nextRequestId++}`})}catch(c){o.catch("Error in PortableWsTransport onDisconnect cancel: ",c)}}})}getServerTimeOffset(){return this.serverTimeOffsetMs}onServerTimeOffset(e){return this.offsetListeners.add(e),()=>this.offsetListeners.delete(e)}now(){return this.deps.now?this.deps.now():Date.now()}ensureChannel(e,s){let t=this.channels.get(e);return t||(t={mode:s,subscribers:new Set,lastSeq:void 0,denied:!1,wired:!1,snapshotWatchdog:null,lastKnown:new Map,hasState:!1,resumeRequested:!1},this.channels.set(e,t)),t}wireSubscribeIfNeeded(e,s){if(s.wired||s.denied||this.state!=="open"&&this.state!=="reauthing"||!this.socket||this.socket.readyState!==f)return;let t={type:"subscribe",channel:e,mode:s.mode,requestId:`r${this.nextRequestId++}`};s.resumeRequested=!1,s.mode==="persistent"&&s.lastSeq!==void 0&&(t.since=s.lastSeq,s.resumeRequested=!0);try{s.wired=this.sendFrame(t),s.wired&&(p("channel.subscribe",`${e}${t.since!==void 0?" since="+t.since:" (full)"}`),this.armSnapshotWatchdog(e,s))}catch(n){o.catch("Error in PortableWsTransport wireSubscribeIfNeeded: ",n)}}sendHello(){this.sendFrame({type:"hello",protocolVersion:"1.0",token:this.deps.getToken(),capabilities:[]})}sendFrame(e){return!this.socket||this.socket.readyState!==f?!1:(this.socket.send(JSON.stringify(e)),!0)}onSocketOpen(e){e===this.socket&&(p("socket.open"),this.sendHello())}onWelcome(e){let s=v(e.protocolVersion),t=v("1.0");if(s!==t){this.state!=="unsupported"&&console.error(`Velt [portable backend]: gateway speaks WS protocol '${e.protocolVersion}' but this SDK speaks '${"1.0"}' (incompatible MAJOR). Portable features are disabled. Align the deployed gateway and SDK versions (see docs/self-hosting-portable-protocol.md).`),this.state="unsupported",this.notifyTerminal("unsupported"),this.clearTimers();try{this.socket?.close(1e3,"protocol-version")}catch{}this.socket=null;return}let n=this.state==="reauthing";if(p("handshake.welcome",`reauth=${n}`),this.state="open",this.reconnectAttempts=0,typeof e.serverTimeMs=="number"){this.serverTimeOffsetMs=e.serverTimeMs-this.now();for(let r of this.offsetListeners)try{r(this.serverTimeOffsetMs)}catch(a){o.catch("Error in PortableWsTransport offset listener: ",a)}}if(this.startHeartbeat(),!n)for(let[r,a]of this.channels)a.wired=!1,this.wireSubscribeIfNeeded(r,a)}onFrame(e,s){if(e!==this.socket)return;this.lastInboundAtMs=this.now();let t;try{t=JSON.parse(s)}catch{o.log("Velt [portable backend]: dropped unparseable WS frame");return}try{switch(t.type){case"welcome":this.onWelcome(t);return;case"pong":this.clearPongDeadline(),O("pong"),this.authRecoveryAttempts=0;return;case"delta":{let n=this.channels.get(t.channel);if(!n)return;typeof t.seq=="number"&&(n.lastSeq=t.seq),n.hasState=!0,t.change==="removed"?n.lastKnown.delete(t.id):t.id!==void 0&&n.lastKnown.set(t.id,t.data),this.emit(n,{kind:"delta",change:t.change,id:t.id,data:t.data,seq:t.seq});return}case"snapshot":{let n=this.channels.get(t.channel);if(!n)return;this.clearSnapshotWatchdog(n),p("channel.snapshot",`${t.channel} items=${Array.isArray(t.items)?t.items.length:"?"}${t.full?" full":""}${t.truncated?" truncated":""}`),typeof t.seq=="number"&&(n.lastSeq=t.seq);let r=Array.isArray(t.items)?t.items:[],a=t.truncated===!0,l=!a&&(t.full===!0||!n.resumeRequested);if(n.hasState=!0,a){for(let c of r)c?.id!==void 0&&n.lastKnown.set(c.id,c?.data),this.emit(n,{kind:"snapshot-item",id:c?.id,data:c?.data});this.emit(n,{kind:"snapshot-end"})}else if(l){let c=new Set;for(let h of r)h?.id!==void 0&&c.add(h.id);let d=[];for(let h of n.lastKnown.keys())c.has(h)||d.push(h);n.lastKnown.clear();for(let h of d)this.emit(n,{kind:"delta",change:"removed",id:h,data:void 0});for(let h of r)h?.id!==void 0&&n.lastKnown.set(h.id,h?.data),this.emit(n,{kind:"snapshot-item",id:h?.id,data:h?.data});this.emit(n,{kind:"snapshot-end"})}else{for(let c of r)c?.id!==void 0&&(c.data===void 0?(n.lastKnown.delete(c.id),this.emit(n,{kind:"delta",change:"removed",id:c.id,data:void 0})):(n.lastKnown.set(c.id,c.data),this.emit(n,{kind:"snapshot-item",id:c.id,data:c.data})));this.emit(n,{kind:"snapshot-end"})}return}case"result":{let n=this.pendingRequests.get(t.requestId);if(!n){o.log(`Velt [portable backend]: uncorrelated result frame (requestId ${t.requestId}, ok=${!!t.ok}${t.code?`, code=${t.code}`:""}) \u2014 no pending request. Expected for unsubscribe / onDisconnectCancel, which are fire-and-forget.`);return}this.pendingRequests.delete(t.requestId),t.ok?n(b({ok:!0,id:t.id,data:t.data},typeof t.seq=="number"?{seq:t.seq}:{})):n(b({ok:!1,code:t.code??"permission-denied",message:t.message},typeof t.reasonCode=="string"?{reasonCode:t.reasonCode}:{}));return}case"error":{let n=t.channel;if(n&&this.channels.has(n)){let r=this.channels.get(n);r.denied=!0,r.wired=!1,this.emit(r,{kind:"denied",code:t.code,reasonCode:t.reasonCode});return}if(t.requestId&&this.pendingRequests.has(t.requestId)){let r=this.pendingRequests.get(t.requestId);this.pendingRequests.delete(t.requestId),r(b({ok:!1,code:t.code??"permission-denied"},typeof t.reasonCode=="string"?{reasonCode:t.reasonCode}:{}));return}if(t.code==="unauthenticated"){if(this.state==="open"&&this.authRecoveryAttempts<x){this.authRecoveryAttempts++,console.warn("Velt [portable backend]: the gateway dropped an authenticated session (unauthenticated on a live connection). Reconnecting once to re-establish it."),this.clearHeartbeat();let a=this.socket;this.socket=null;try{a&&a.readyState<=f&&a.close(4401,"session-lost")}catch{}this.failPendingRequests(),this.markAllChannelsUnwired(),this.scheduleReconnect();return}console.warn("Velt [portable backend]: the gateway rejected authentication. Portable transport is fail-closed for this session; a fresh identify()/token is required to reconnect."),this.state="closed",this.notifyTerminal("closed"),this.clearTimers();let r=this.socket;this.socket=null;try{r&&r.readyState<=f&&r.close(4401,"unauthenticated")}catch{}this.failPendingRequests();return}return}default:return}}catch(n){o.catch("Error in PortableWsTransport onFrame: ",n)}}emit(e,s){for(let t of e.subscribers)if(t.active)try{t.onEvent(s)}catch(n){o.catch("Error in PortableWsTransport subscriber callback: ",n)}}onSocketClosed(e){p("socket.close",`stale=${e!==this.socket} state=${this.state}`),e===this.socket&&(this.socket=null,this.clearTimers(),this.failPendingRequests(),!(this.state==="closed"||this.state==="unsupported")&&(this.markAllChannelsUnwired(),this.scheduleReconnect()))}markAllChannelsUnwired(){for(let e of this.channels.values())e.wired=!1}failPendingRequests(){for(let[,e]of this.pendingRequests)e({ok:!1,code:"failed-precondition",message:"portable transport disconnected"});this.pendingRequests.clear()}scheduleReconnect(){if(this.reconnectTimer)return;this.state="reconnecting";let e=Math.min(N,I*Math.pow(2,this.reconnectAttempts)),s=Math.max(I,e*(.5+Math.random()*.5));this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.state==="reconnecting"&&(this.state="idle",this.connect())},s)}startHeartbeat(){this.clearHeartbeat(),this.heartbeatTimer=setInterval(()=>{try{if(this.state!=="open"&&this.state!=="reauthing")return;if(!this.socket||this.socket.readyState!==f){p("socket.dead-without-close",`state=${this.state} readyState=${this.socket?this.socket.readyState:"no-socket"}`),T("the realtime socket stopped without reporting it (recovering)","The connection was closed by the browser or the network without delivering a close event \u2014 most often a backgrounded/frozen tab. The SDK is reconnecting; if realtime features stay stuck after this, the reconnect is the thing to look at."),this.handleDeadSocket();return}O("ping"),this.sendFrame({type:"ping"}),this.armPongDeadline()}catch(e){o.catch("Error in PortableWsTransport heartbeat: ",e)}},y)}handleDeadSocket(){try{let e=this.socket;this.socket=null,this.clearHeartbeat(),this.clearPongDeadline();try{e?.close(4e3,"socket dead without close event")}catch{}this.failPendingRequests(),this.markAllChannelsUnwired(),this.state!=="closed"&&this.state!=="unsupported"&&this.scheduleReconnect()}catch(e){o.catch("Error in PortableWsTransport handleDeadSocket: ",e)}}armSnapshotWatchdog(e,s){this.clearSnapshotWatchdog(s),s.snapshotWatchdog=setTimeout(()=>{s.snapshotWatchdog=null,p("channel.snapshot-missing",`${e} after ${k}ms`),T("a realtime channel was subscribed but never delivered its first snapshot","Anything waiting on that first emission \u2014 a sidebar skeleton, an empty comment list \u2014 will stay in its loading state. The trail below shows whether the socket handshook, whether the credential was refreshed, and how long the tab was away.",`snapshot-missing:${e}`)},k)}clearSnapshotWatchdog(e){e?.snapshotWatchdog&&(clearTimeout(e.snapshotWatchdog),e.snapshotWatchdog=null)}armPongDeadline(){this.pongDeadlineTimer||(this.pongDeadlineTimer=setTimeout(()=>{this.pongDeadlineTimer=null;let e=this.socket;this.socket=null,this.clearHeartbeat();try{e?.close(4e3,"pong timeout")}catch{}this.failPendingRequests();for(let s of this.channels.values())s.wired=!1;this.state!=="closed"&&this.state!=="unsupported"&&this.scheduleReconnect()},y*q))}clearPongDeadline(){this.pongDeadlineTimer&&(clearTimeout(this.pongDeadlineTimer),this.pongDeadlineTimer=null)}clearHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.clearPongDeadline()}clearTimers(){this.clearHeartbeat();for(let e of this.channels.values())this.clearSnapshotWatchdog(e);this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}};var u=null,A=null;function G(){try{u?.teardown()}catch{}}function Oe(){try{u?.getTransport()?.checkLivenessOnResume()}catch(i){o.catch("Error in checkPortableBackendLiveness: ",i)}}function Ie(){G(),u=null,A=null}function J(i){let e=!u;if(u||(u=new E(s=>new R({wsFactory:t=>new WebSocket(t),getUrl:()=>s.getWsEndpoint(),getToken:()=>s.getToken()}))),i?.token&&u.getToken()&&u.getTransport()?u.refreshToken(i.token):u.establish(i),e){let s=new w(u);A={db:new S(s,u),realtime:new P(u)}}return A&&D(A),u}export{Oe as checkPortableBackendLiveness,J as default,Ie as resetPortableBackendEntryForTest,G as teardownPortableBackend};
|
|
1
|
+
import{$i as T,Vi as W,Yd as o,Zi as O,_i as p,a as b,aj as M,c as _,g as m,l as C,qj as q,sj as L}from"./chunk-YFBAHLK6.js";var B=["hello","subscribe","unsubscribe","publish","presence","onDisconnect","onDisconnectCancel","ping"],H=["welcome","snapshot","delta","result","error","pong"],J=[...B,...H],y=3e4,D=2,k=2e4,I=1e3,N=3e4,x=1;function v(i){if(typeof i!="string")return NaN;let e=Number(i.split(".")[0]);return Number.isInteger(e)?e:NaN}function g(i){try{let e=i.apiKey??"",s=i.instanceId?`${i.feature}/${i.instanceId}`:i.feature;switch(i.scope){case"doc":return`${e}:${i.organizationId??""}:doc:${i.documentId??""}:${s}`;case"user":return i.organizationId?`${e}:${i.organizationId}:user:${i.userIdHash??""}:${s}`:`${e}:user:${i.userIdHash??""}:${s}`;case"org":return`${e}:${i.organizationId??""}:${s}`;default:return`${e}:${s}`}}catch(e){return o.catch("Error in buildPortableWireKey: ",e),`${i?.apiKey??""}:${i?.feature??""}`}}var V={presence:"updatePresence",liveState:"publishLiveState",cursor:"updateCursor",selection:"updateSelection",huddle:"updateHuddle",syncVideoPlayer:"updateSyncVideoPlayer",flockSession:"updateFlock"};function U(i,e){if(i==="crdt"){let s=e?.uptoSeq;return Number.isInteger(s)?"writeCrdtSnapshot":"writeCrdtUpdate"}return V[i]}var P=class{constructor(e){this.session=e}whenReady(){return new Promise(e=>{try{let s=!1,t=null,n=null,r=()=>{s||(s=!0,n!==null&&(clearTimeout(n),n=null),setTimeout(()=>{try{t?.unsubscribe()}catch{}},0),e())};n=setTimeout(r,k),t=this.session.getAuthState$().subscribe(a=>{a?.signedIn&&r()})}catch(s){o.catch("Error in PortableRealtimeAdapter whenReady: ",s),e()}})}subscribe(e,s,t,n){try{let r=this.session.getTransport();if(!r)return{unsubscribe:()=>{}};let a=g(e),l=!1,c=r.subscribeChannel(a,s,d=>{try{if(d.kind==="denied"){o.catch(`Velt [portable backend]: subscribe denied on channel ${a}: ${d.code??"unknown"}${L(d)}`,b({ok:!1,code:d.code},d.reasonCode?{reasonCode:d.reasonCode}:{})),n?.onError&&!l&&(l=!0,n.onError(d.code??"permission-denied"));return}if(d.kind==="delta"){t({type:d.change??"modified",id:d.id??"",data:d.data,seq:d.seq});return}if(d.kind==="snapshot-end"){n?.onSnapshotComplete?.();return}t({type:"added",id:d.id??"",data:d.data})}catch(h){o.catch("Error in PortableRealtimeAdapter subscribe callback: ",h)}},n?.since);return{unsubscribe:()=>c.unsubscribe()}}catch(r){return o.catch("Error in PortableRealtimeAdapter subscribe: ",r),{unsubscribe:()=>{}}}}publish(e,s,t){try{let n=U(e.feature,t);if(!n)return Promise.resolve({ok:!1,code:"unimplemented",message:`no realtime publish operation for feature '${e.feature}'`});let r=this.session.getTransport();return r?r.request("publish",g(e),n,t):Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"})}catch(n){return o.catch("Error in PortableRealtimeAdapter publish: ",n),Promise.resolve({ok:!1,code:"failed-precondition"})}}presence(e,s){try{let t=this.session.getTransport();return t?t.request("presence",g(e),"updatePresence",s):Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"})}catch(t){return o.catch("Error in PortableRealtimeAdapter presence: ",t),Promise.resolve({ok:!1,code:"failed-precondition"})}}onDisconnect(e,s){return m(this,null,function*(){try{let t=this.session.getTransport();return t?yield t.registerOnDisconnect(g(e),s):()=>{}}catch(t){return o.catch("Error in PortableRealtimeAdapter onDisconnect: ",t),()=>{}}})}getServerTimeOffset(){try{return Promise.resolve(this.session.getTransport()?.getServerTimeOffset()??0)}catch(e){return o.catch("Error in PortableRealtimeAdapter getServerTimeOffset: ",e),Promise.resolve(0)}}onServerTimeOffset(e){try{return this.session.getTransport()?.onServerTimeOffset(e)??(()=>{})}catch(s){return o.catch("Error in PortableRealtimeAdapter onServerTimeOffset: ",s),()=>{}}}};var S=class{constructor(e,s){this.rest=e,this.session=s}read(e){return m(this,null,function*(){let s=yield this.rest.call("read",e);return s.ok?{data:s.data}:{data:void 0,errorCode:s.code}})}runTransaction(e){return e()}op(e,s){if(s&&typeof s=="object"&&"channel"in s){let t=s,{channel:n}=t,r=_(t,["channel"]);return this.rest.call(e,r)}return this.rest.call(e,s)}publishOp(e,s,t){if(!s.channel)return Promise.resolve({ok:!1,code:"failed-precondition",message:`portable publish op '${e}' requires a channel on the OperationRequest`});let n=this.session?.getTransport();return n?n.request("publish",g(s.channel),e,t):Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"})}notificationWirePayload(e,s){let t=e.resourceId??(typeof e.data?.notificationId=="string"?e.data.notificationId:void 0),n={};if(t!=null&&String(t).length>0&&(n.notificationId=String(t)),s&&e.data!==void 0){let r=e.data;r&&typeof r=="object"&&r.data!==void 0&&r.notificationId!==void 0?n.data=r.data:n.data=e.data}return n}annotationWirePayload(e,s,t=!1){let n=e.resourceId??(typeof e.data?.annotationId=="string"?e.data.annotationId:void 0),r={};return n!=null&&String(n).length>0&&(r.annotationId=String(n)),s&&e.data!==void 0&&(r.data=e.data),t&&e.notificationData!==void 0&&(r.notificationData=e.notificationData),r}addComment(e){return this.publishOp("addComment",e,this.annotationWirePayload(e,!0,!0))}editComment(e){return this.publishOp("editComment",e,this.annotationWirePayload(e,!0))}deleteComment(e){return this.publishOp("deleteComment",e,this.annotationWirePayload(e,!1))}addReaction(e){return this.publishOp("addReaction",e,this.annotationWirePayload(e,!0))}editReaction(e){return this.publishOp("editReaction",e,this.annotationWirePayload(e,!0))}deleteReaction(e){return this.publishOp("deleteReaction",e,this.annotationWirePayload(e,!1))}recordDocumentView(e){return this.publishOp("recordDocumentView",e,{})}getActivitiesForDocuments(e){return this.rest.call("getActivitiesForDocuments",e.data)}getActivitiesForEntity(e){return this.rest.call("getActivitiesForEntity",e.data)}setCommentResolved(e){return this.op("setCommentResolved",e)}setCommentMetadata(e){return this.op("setCommentMetadata",e)}bumpCommentViews(e){return this.op("bumpCommentViews",e)}markCommentThreadRead(e){return this.publishOp("markCommentThreadRead",e,this.notificationWirePayload(e,!1))}markNotificationRead(e){return this.publishOp("markNotificationRead",e,this.notificationWirePayload(e,!1))}addNotification(e){return this.op("addNotification",e)}deleteNotification(e){return this.publishOp("deleteNotification",e,this.notificationWirePayload(e,!1))}updateNotification(e){return this.publishOp("updateNotification",e,this.notificationWirePayload(e,!0))}updatePresence(e){return this.op("updatePresence",e)}grantAccess(e){return this.op("grantAccess",e)}revokeAccess(e){return this.rest.call("revokeAccess",{data:e.data})}getAccess(e){return this.rest.call("getAccess",{data:e.data})}upsertOrgUserProfile(e){return this.rest.call("addUserInfoToOrganization",e.data)}upsertDocMetadata(e){return this.op("upsertDocMetadata",e)}upsertOrgMetadata(e){return this.op("upsertOrgMetadata",e)}createActivity(e){return this.rest.call("createActivity",e.data)}};var K={fetch:(i,e)=>fetch(i,e)},F={400:"invalid-argument",401:"unauthenticated",403:"permission-denied",404:"not-found",412:"failed-precondition",429:"resource-exhausted"};function j(i){return!!i&&typeof i=="object"&&typeof i.ok=="boolean"}var w=class{constructor(e){this.session=e,this.insecureEndpointReported=new Set}call(e,s){return m(this,null,function*(){try{let t=this.session.getApiEndpoint(),n=this.session.getToken();if(!t||!n)return{ok:!1,code:"failed-precondition",message:"portable session not established"};if(!W(t))return this.insecureEndpointReported.has(t)||(this.insecureEndpointReported.add(t),console.error(`Velt [portable backend]: refusing to send the session token to insecure apiEndpoint '${t}' (https required; http is loopback-only). Portable REST operations are disabled until identify returns a secure endpoint.`)),{ok:!1,code:"failed-precondition",message:"portable apiEndpoint is not a secure URL; request not sent"};let r=yield K.fetch(`${t}/operations/${e}`,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify(s??{})}),a;try{a=yield r.json()}catch{a=void 0}if(j(a)){if(a.ok)return r.ok?a:{ok:!1,code:F[r.status]??"permission-denied",message:`HTTP ${r.status} on operation '${e}' contradicted an ok:true envelope`};let c=a.code;return c&&M.includes(c)?a:{ok:!1,code:"permission-denied",message:`unrecognized denial code '${c}'`}}return r.ok?{ok:!0,data:a}:{ok:!1,code:F[r.status]??"permission-denied",message:`HTTP ${r.status} on operation '${e}'`}}catch(t){return o.catch(`Error in PortableRestClient call '${e}': `,t),{ok:!1,code:"permission-denied",message:"portable operation request failed"}}})}};function z(i){if(!i)return!1;try{let e=new URL(i);return e.protocol==="wss:"?!0:e.protocol==="ws:"&&["localhost","127.0.0.1","[::1]"].includes(e.hostname)}catch{return!1}}var E=class{constructor(e){this.transportFactory=e,this.token=null,this.wsEndpoint=null,this.apiEndpoint=null,this.transport=null,this.authState$=new C(null),this.nullTokenWarned=!1,this.insecureEndpointWarned=!1,this.offOffset=null,this.offTerminal=null}establish(e){try{if(!e)return;if(this.wsEndpoint=e.wsEndpoint??null,this.apiEndpoint=e.apiEndpoint??null,this.token=typeof e.token=="string"&&e.token.length>0?e.token:null,!this.token){this.transport&&this.teardown(),this.nullTokenWarned||(this.nullTokenWarned=!0,console.warn("Velt [portable backend]: identify() returned no session token (token: null). Portable transport will not connect on this page load; portable features are disabled."));return}if(!z(this.wsEndpoint)){this.transport&&this.teardown(),this.insecureEndpointWarned||(this.insecureEndpointWarned=!0,console.warn("Velt [portable backend]: refusing to connect to a non-wss:// gateway endpoint ("+this.wsEndpoint+"). The session token would travel in cleartext; portable features are disabled. (Plain ws:// is permitted for localhost only.)"));return}this.transport||(this.transport=this.transportFactory(this),this.offOffset=this.transport.onServerTimeOffset(()=>{this.authState$.value?.signedIn||this.authState$.next({signedIn:!0})}),this.offTerminal=this.transport.onTerminal(()=>{this.authState$.value!==null&&this.authState$.next(null)})),this.transport.connect()}catch(s){o.catch("Error in PortableSession establish: ",s)}}refreshToken(e){try{if(typeof e!="string"||!e.length)return;this.token=e,this.transport?.refreshAuth()}catch(s){o.catch("Error in PortableSession refreshToken: ",s)}}teardown(){try{this.offOffset?.(),this.offOffset=null,this.offTerminal?.(),this.offTerminal=null,this.transport?.close(),this.transport=null,this.authState$.value!==null&&this.authState$.next(null)}catch(e){o.catch("Error in PortableSession teardown: ",e)}}getToken(){return this.token}getWsEndpoint(){return this.wsEndpoint}getApiEndpoint(){return this.apiEndpoint}getTransport(){return this.transport}getAuthState$(){return this.authState$.asObservable()}};var f=1,R=class{constructor(e){this.deps=e,this.state="idle",this.socket=null,this.channels=new Map,this.pendingRequests=new Map,this.nextRequestId=1,this.serverTimeOffsetMs=0,this.offsetListeners=new Set,this.lastInboundAtMs=0,this.heartbeatTimer=null,this.pongDeadlineTimer=null,this.reconnectTimer=null,this.reconnectAttempts=0,this.authRecoveryAttempts=0,this.terminalListeners=new Set}onTerminal(e){return this.terminalListeners.add(e),()=>{this.terminalListeners.delete(e)}}notifyTerminal(e){this.terminalListeners.forEach(s=>{try{s(e)}catch(t){o.catch("Error in PortableWsTransport terminal listener: ",t)}})}getState(){return this.state}connect(){try{if(this.state==="connecting"||this.state==="open"||this.state==="reauthing"||this.state==="unsupported")return;let e=this.deps.getUrl(),s=this.deps.getToken();if(!e||!s)return;this.state="connecting";let t=this.deps.wsFactory(e);this.socket=t,t.onopen=()=>this.onSocketOpen(t),t.onmessage=n=>this.onFrame(t,n.data),t.onclose=()=>this.onSocketClosed(t),t.onerror=()=>{}}catch(e){o.catch("Error in PortableWsTransport connect: ",e)}}close(){try{this.state="closed",this.notifyTerminal("closed"),this.clearTimers();let e=this.socket;this.socket=null,e&&e.readyState<=f&&e.close(1e3,"client close"),this.failPendingRequests()}catch(e){o.catch("Error in PortableWsTransport close: ",e)}}checkLivenessOnResume(e=y*2){try{if(this.state!=="open"&&this.state!=="reauthing")return;let s=this.lastInboundAtMs?this.now()-this.lastInboundAtMs:Number.POSITIVE_INFINITY;if(s<=e)return;p("liveness.stale-on-resume",`silentFor=${Math.round(s/1e3)}s state=${this.state}`),T("the realtime connection was silent while the tab was in the background","Nothing had been received for longer than two heartbeats when this tab came back, so the connection is treated as dead and re-established. Data shown before this point may have been stale \u2014 the UI keeps what it already had, so this is invisible without this message."),this.handleDeadSocket()}catch(s){o.catch("Error in PortableWsTransport checkLivenessOnResume: ",s)}}refreshAuth(){try{if(this.state==="open"&&this.socket&&this.socket.readyState===f){this.state="reauthing",this.sendHello();return}(this.state==="closed"||this.state==="idle")&&(this.state="idle",this.connect())}catch(e){o.catch("Error in PortableWsTransport refreshAuth: ",e)}}subscribeChannel(e,s,t,n){let r=this.ensureChannel(e,s),a={onEvent:t,active:!0};if(r.subscribers.add(a),r.lastSeq===void 0&&typeof n=="number"&&(r.lastSeq=n),r.hasState){for(let[l,c]of r.lastKnown)try{a.onEvent({kind:"snapshot-item",id:l,data:c})}catch(d){o.catch("Error in PortableWsTransport cached-state replay: ",d)}try{a.onEvent({kind:"snapshot-end"})}catch(l){o.catch("Error in PortableWsTransport cached-state snapshot-end: ",l)}}return this.wireSubscribeIfNeeded(e,r),{unsubscribe:()=>{try{if(!a.active)return;a.active=!1,r.subscribers.delete(a),r.subscribers.size===0&&(this.clearSnapshotWatchdog(r),this.channels.delete(e),r.wired&&this.socket&&this.socket.readyState===f&&this.sendFrame({type:"unsubscribe",channel:e,requestId:`r${this.nextRequestId++}`}))}catch(l){o.catch("Error in PortableWsTransport unsubscribe: ",l)}}}}request(e,s,t,n){try{if(this.state!=="open"&&this.state!=="reauthing")return Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"});let r=`r${this.nextRequestId++}`,a=new Promise(d=>{this.pendingRequests.set(r,d)}),l={type:e,channel:s,op:t,requestId:r};e==="presence"?l.state=n:l.payload=n;let c=!1;try{c=this.sendFrame(l)}catch(d){throw this.pendingRequests.delete(r),d}return c?a:(this.pendingRequests.delete(r),Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport not connected"}))}catch(r){return o.catch("Error in PortableWsTransport request: ",r),Promise.resolve({ok:!1,code:"failed-precondition",message:"portable transport send failed"})}}registerOnDisconnect(e,s){return m(this,null,function*(){if(this.state!=="open"&&this.state!=="reauthing")return()=>{};let t=`r${this.nextRequestId++}`,n=new Promise(c=>{this.pendingRequests.set(t,c)}),r=!1;try{r=this.sendFrame({type:"onDisconnect",channel:e,cleanup:s,requestId:t})}catch(c){return this.pendingRequests.delete(t),o.catch("Error in PortableWsTransport registerOnDisconnect send: ",c),()=>{}}if(!r)return this.pendingRequests.delete(t),()=>{};let a=yield n,l=a.ok?a.id:void 0;return()=>{try{l&&this.socket&&this.socket.readyState===f&&this.sendFrame({type:"onDisconnectCancel",intentId:l,requestId:`r${this.nextRequestId++}`})}catch(c){o.catch("Error in PortableWsTransport onDisconnect cancel: ",c)}}})}getServerTimeOffset(){return this.serverTimeOffsetMs}onServerTimeOffset(e){return this.offsetListeners.add(e),()=>this.offsetListeners.delete(e)}now(){return this.deps.now?this.deps.now():Date.now()}ensureChannel(e,s){let t=this.channels.get(e);return t||(t={mode:s,subscribers:new Set,lastSeq:void 0,denied:!1,wired:!1,snapshotWatchdog:null,lastKnown:new Map,hasState:!1,resumeRequested:!1},this.channels.set(e,t)),t}wireSubscribeIfNeeded(e,s){if(s.wired||s.denied||this.state!=="open"&&this.state!=="reauthing"||!this.socket||this.socket.readyState!==f)return;let t={type:"subscribe",channel:e,mode:s.mode,requestId:`r${this.nextRequestId++}`};s.resumeRequested=!1,s.mode==="persistent"&&s.lastSeq!==void 0&&(t.since=s.lastSeq,s.resumeRequested=!0);try{s.wired=this.sendFrame(t),s.wired&&(p("channel.subscribe",`${e}${t.since!==void 0?" since="+t.since:" (full)"}`),this.armSnapshotWatchdog(e,s))}catch(n){o.catch("Error in PortableWsTransport wireSubscribeIfNeeded: ",n)}}sendHello(){this.sendFrame({type:"hello",protocolVersion:"1.0",token:this.deps.getToken(),capabilities:[]})}sendFrame(e){return!this.socket||this.socket.readyState!==f?!1:(this.socket.send(JSON.stringify(e)),!0)}onSocketOpen(e){e===this.socket&&(p("socket.open"),this.sendHello())}onWelcome(e){let s=v(e.protocolVersion),t=v("1.0");if(s!==t){this.state!=="unsupported"&&console.error(`Velt [portable backend]: gateway speaks WS protocol '${e.protocolVersion}' but this SDK speaks '${"1.0"}' (incompatible MAJOR). Portable features are disabled. Align the deployed gateway and SDK versions (see docs/self-hosting-portable-protocol.md).`),this.state="unsupported",this.notifyTerminal("unsupported"),this.clearTimers();try{this.socket?.close(1e3,"protocol-version")}catch{}this.socket=null;return}let n=this.state==="reauthing";if(p("handshake.welcome",`reauth=${n}`),this.state="open",this.reconnectAttempts=0,typeof e.serverTimeMs=="number"){this.serverTimeOffsetMs=e.serverTimeMs-this.now();for(let r of this.offsetListeners)try{r(this.serverTimeOffsetMs)}catch(a){o.catch("Error in PortableWsTransport offset listener: ",a)}}if(this.startHeartbeat(),!n)for(let[r,a]of this.channels)a.wired=!1,this.wireSubscribeIfNeeded(r,a)}onFrame(e,s){if(e!==this.socket)return;this.lastInboundAtMs=this.now();let t;try{t=JSON.parse(s)}catch{o.log("Velt [portable backend]: dropped unparseable WS frame");return}try{switch(t.type){case"welcome":this.onWelcome(t);return;case"pong":this.clearPongDeadline(),O("pong"),this.authRecoveryAttempts=0;return;case"delta":{let n=this.channels.get(t.channel);if(!n)return;typeof t.seq=="number"&&(n.lastSeq=t.seq),n.hasState=!0,t.change==="removed"?n.lastKnown.delete(t.id):t.id!==void 0&&n.lastKnown.set(t.id,t.data),this.emit(n,{kind:"delta",change:t.change,id:t.id,data:t.data,seq:t.seq});return}case"snapshot":{let n=this.channels.get(t.channel);if(!n)return;this.clearSnapshotWatchdog(n),p("channel.snapshot",`${t.channel} items=${Array.isArray(t.items)?t.items.length:"?"}${t.full?" full":""}${t.truncated?" truncated":""}`),typeof t.seq=="number"&&(n.lastSeq=t.seq);let r=Array.isArray(t.items)?t.items:[],a=t.truncated===!0,l=!a&&(t.full===!0||!n.resumeRequested);if(n.hasState=!0,a){for(let c of r)c?.id!==void 0&&n.lastKnown.set(c.id,c?.data),this.emit(n,{kind:"snapshot-item",id:c?.id,data:c?.data});this.emit(n,{kind:"snapshot-end"})}else if(l){let c=new Set;for(let h of r)h?.id!==void 0&&c.add(h.id);let d=[];for(let h of n.lastKnown.keys())c.has(h)||d.push(h);n.lastKnown.clear();for(let h of d)this.emit(n,{kind:"delta",change:"removed",id:h,data:void 0});for(let h of r)h?.id!==void 0&&n.lastKnown.set(h.id,h?.data),this.emit(n,{kind:"snapshot-item",id:h?.id,data:h?.data});this.emit(n,{kind:"snapshot-end"})}else{for(let c of r)c?.id!==void 0&&(c.data===void 0?(n.lastKnown.delete(c.id),this.emit(n,{kind:"delta",change:"removed",id:c.id,data:void 0})):(n.lastKnown.set(c.id,c.data),this.emit(n,{kind:"snapshot-item",id:c.id,data:c.data})));this.emit(n,{kind:"snapshot-end"})}return}case"result":{let n=this.pendingRequests.get(t.requestId);if(!n){o.log(`Velt [portable backend]: uncorrelated result frame (requestId ${t.requestId}, ok=${!!t.ok}${t.code?`, code=${t.code}`:""}) \u2014 no pending request. Expected for unsubscribe / onDisconnectCancel, which are fire-and-forget.`);return}this.pendingRequests.delete(t.requestId),t.ok?n(b({ok:!0,id:t.id,data:t.data},typeof t.seq=="number"?{seq:t.seq}:{})):n(b({ok:!1,code:t.code??"permission-denied",message:t.message},typeof t.reasonCode=="string"?{reasonCode:t.reasonCode}:{}));return}case"error":{let n=t.channel;if(n&&this.channels.has(n)){let r=this.channels.get(n);r.denied=!0,r.wired=!1,this.emit(r,{kind:"denied",code:t.code,reasonCode:t.reasonCode});return}if(t.requestId&&this.pendingRequests.has(t.requestId)){let r=this.pendingRequests.get(t.requestId);this.pendingRequests.delete(t.requestId),r(b({ok:!1,code:t.code??"permission-denied"},typeof t.reasonCode=="string"?{reasonCode:t.reasonCode}:{}));return}if(t.code==="unauthenticated"){if(this.state==="open"&&this.authRecoveryAttempts<x){this.authRecoveryAttempts++,console.warn("Velt [portable backend]: the gateway dropped an authenticated session (unauthenticated on a live connection). Reconnecting once to re-establish it."),this.clearHeartbeat();let a=this.socket;this.socket=null;try{a&&a.readyState<=f&&a.close(4401,"session-lost")}catch{}this.failPendingRequests(),this.markAllChannelsUnwired(),this.scheduleReconnect();return}console.warn("Velt [portable backend]: the gateway rejected authentication. Portable transport is fail-closed for this session; a fresh identify()/token is required to reconnect."),this.state="closed",this.notifyTerminal("closed"),this.clearTimers();let r=this.socket;this.socket=null;try{r&&r.readyState<=f&&r.close(4401,"unauthenticated")}catch{}this.failPendingRequests();return}return}default:return}}catch(n){o.catch("Error in PortableWsTransport onFrame: ",n)}}emit(e,s){for(let t of e.subscribers)if(t.active)try{t.onEvent(s)}catch(n){o.catch("Error in PortableWsTransport subscriber callback: ",n)}}onSocketClosed(e){p("socket.close",`stale=${e!==this.socket} state=${this.state}`),e===this.socket&&(this.socket=null,this.clearTimers(),this.failPendingRequests(),!(this.state==="closed"||this.state==="unsupported")&&(this.markAllChannelsUnwired(),this.scheduleReconnect()))}markAllChannelsUnwired(){for(let e of this.channels.values())e.wired=!1}failPendingRequests(){for(let[,e]of this.pendingRequests)e({ok:!1,code:"failed-precondition",message:"portable transport disconnected"});this.pendingRequests.clear()}scheduleReconnect(){if(this.reconnectTimer)return;this.state="reconnecting";let e=Math.min(N,I*Math.pow(2,this.reconnectAttempts)),s=Math.max(I,e*(.5+Math.random()*.5));this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.state==="reconnecting"&&(this.state="idle",this.connect())},s)}startHeartbeat(){this.clearHeartbeat(),this.heartbeatTimer=setInterval(()=>{try{if(this.state!=="open"&&this.state!=="reauthing")return;if(!this.socket||this.socket.readyState!==f){p("socket.dead-without-close",`state=${this.state} readyState=${this.socket?this.socket.readyState:"no-socket"}`),T("the realtime socket stopped without reporting it (recovering)","The connection was closed by the browser or the network without delivering a close event \u2014 most often a backgrounded/frozen tab. The SDK is reconnecting; if realtime features stay stuck after this, the reconnect is the thing to look at."),this.handleDeadSocket();return}O("ping"),this.sendFrame({type:"ping"}),this.armPongDeadline()}catch(e){o.catch("Error in PortableWsTransport heartbeat: ",e)}},y)}handleDeadSocket(){try{let e=this.socket;this.socket=null,this.clearHeartbeat(),this.clearPongDeadline();try{e?.close(4e3,"socket dead without close event")}catch{}this.failPendingRequests(),this.markAllChannelsUnwired(),this.state!=="closed"&&this.state!=="unsupported"&&this.scheduleReconnect()}catch(e){o.catch("Error in PortableWsTransport handleDeadSocket: ",e)}}armSnapshotWatchdog(e,s){this.clearSnapshotWatchdog(s),s.snapshotWatchdog=setTimeout(()=>{s.snapshotWatchdog=null,p("channel.snapshot-missing",`${e} after ${k}ms`),T("a realtime channel was subscribed but never delivered its first snapshot","Anything waiting on that first emission \u2014 a sidebar skeleton, an empty comment list \u2014 will stay in its loading state. The trail below shows whether the socket handshook, whether the credential was refreshed, and how long the tab was away.",`snapshot-missing:${e}`)},k)}clearSnapshotWatchdog(e){e?.snapshotWatchdog&&(clearTimeout(e.snapshotWatchdog),e.snapshotWatchdog=null)}armPongDeadline(){this.pongDeadlineTimer||(this.pongDeadlineTimer=setTimeout(()=>{this.pongDeadlineTimer=null;let e=this.socket;this.socket=null,this.clearHeartbeat();try{e?.close(4e3,"pong timeout")}catch{}this.failPendingRequests();for(let s of this.channels.values())s.wired=!1;this.state!=="closed"&&this.state!=="unsupported"&&this.scheduleReconnect()},y*D))}clearPongDeadline(){this.pongDeadlineTimer&&(clearTimeout(this.pongDeadlineTimer),this.pongDeadlineTimer=null)}clearHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.clearPongDeadline()}clearTimers(){this.clearHeartbeat();for(let e of this.channels.values())this.clearSnapshotWatchdog(e);this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}};var u=null,A=null;function Y(){try{u?.teardown()}catch{}}function Ae(){try{u?.getTransport()?.checkLivenessOnResume()}catch(i){o.catch("Error in checkPortableBackendLiveness: ",i)}}function Oe(){Y(),u=null,A=null}function G(i){let e=!u;if(u||(u=new E(s=>new R({wsFactory:t=>new WebSocket(t),getUrl:()=>s.getWsEndpoint(),getToken:()=>s.getToken()}))),i?.token&&u.getToken()&&u.getTransport()?u.refreshToken(i.token):u.establish(i),e){let s=new w(u);A={db:new S(s,u),realtime:new P(u)}}return A&&q(A),u}export{Ae as checkPortableBackendLiveness,G as default,Oe as resetPortableBackendEntryForTest,Y as teardownPortableBackend};
|