verani 0.1.7 → 0.1.9
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.cjs +1 -1
- package/dist/client.d.cts +36 -0
- package/dist/client.d.mts +36 -0
- package/dist/client.mjs +1 -1
- package/dist/verani.cjs +1 -1
- package/dist/verani.d.cts +40 -6
- package/dist/verani.d.mts +40 -6
- package/dist/verani.mjs +1 -1
- package/package.json +1 -1
package/dist/client.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const require_types=require(`./types-083oWz55.cjs`);function encodeClientMessage$1(t){return require_types.n(t)}function decodeServerMessage$1(t){return require_types.s(t)}const DEFAULT_RECONNECTION_CONFIG={enabled:!0,maxAttempts:10,initialDelay:1e3,maxDelay:3e4,backoffMultiplier:1.5};var ConnectionManager=class{constructor(e=DEFAULT_RECONNECTION_CONFIG,t){this.config=e,this.onStateChange=t,this.state=`disconnected`,this.reconnectAttempts=0,this.currentDelay=e.initialDelay}getState(){return this.state}setState(e){this.state!==e&&(this.state=e,this.onStateChange?.(e))}resetReconnection(){this.reconnectAttempts=0,this.currentDelay=this.config.initialDelay,this.clearReconnectTimer()}scheduleReconnect(e){return this.config.enabled?this.config.maxAttempts>0&&this.reconnectAttempts>=this.config.maxAttempts?(this.setState(`error`),!1):(this.clearReconnectTimer(),this.setState(`reconnecting`),this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{e(),this.currentDelay=Math.min(this.currentDelay*this.config.backoffMultiplier,this.config.maxDelay)},this.currentDelay),!0):!1}cancelReconnect(){this.clearReconnectTimer(),this.state===`reconnecting`&&this.setState(`disconnected`)}clearReconnectTimer(){this.reconnectTimer!==void 0&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0)}getReconnectAttempts(){return this.reconnectAttempts}getNextDelay(){return this.currentDelay}destroy(){this.clearReconnectTimer()}},VeraniClient=class{constructor(e,t={}){this.url=e,this.listeners=new Map,this.messageQueue=[],this.options={reconnection:{enabled:t.reconnection?.enabled??DEFAULT_RECONNECTION_CONFIG.enabled,maxAttempts:t.reconnection?.maxAttempts??DEFAULT_RECONNECTION_CONFIG.maxAttempts,initialDelay:t.reconnection?.initialDelay??DEFAULT_RECONNECTION_CONFIG.initialDelay,maxDelay:t.reconnection?.maxDelay??DEFAULT_RECONNECTION_CONFIG.maxDelay,backoffMultiplier:t.reconnection?.backoffMultiplier??DEFAULT_RECONNECTION_CONFIG.backoffMultiplier},maxQueueSize:t.maxQueueSize??100,connectionTimeout:t.connectionTimeout??1e4},this.connectionManager=new ConnectionManager(this.options.reconnection,e=>{this.onStateChangeCallback?.(e)}),this.connect()}connect(){try{this.connectionManager.setState(`connecting`),this.emitLifecycleEvent(`connecting`),this.ws=new WebSocket(this.url)
|
|
1
|
+
const require_types=require(`./types-083oWz55.cjs`);function encodeClientMessage$1(t){return require_types.n(t)}function decodeServerMessage$1(t){return require_types.s(t)}const DEFAULT_RECONNECTION_CONFIG={enabled:!0,maxAttempts:10,initialDelay:1e3,maxDelay:3e4,backoffMultiplier:1.5};var ConnectionManager=class{constructor(e=DEFAULT_RECONNECTION_CONFIG,t){this.config=e,this.onStateChange=t,this.state=`disconnected`,this.reconnectAttempts=0,this.currentDelay=e.initialDelay}getState(){return this.state}isValidStateTransition(e,t){return{disconnected:[`connecting`,`reconnecting`],connecting:[`connected`,`disconnected`,`error`,`reconnecting`],connected:[`disconnected`,`reconnecting`],reconnecting:[`connecting`,`disconnected`,`error`],error:[`reconnecting`,`disconnected`,`connecting`]}[e]?.includes(t)??!1}setState(e){this.state!==e&&(this.isValidStateTransition(this.state,e),this.state=e,this.onStateChange?.(e))}resetReconnection(){this.reconnectAttempts=0,this.currentDelay=this.config.initialDelay,this.clearReconnectTimer()}scheduleReconnect(e){return this.config.enabled?this.config.maxAttempts>0&&this.reconnectAttempts>=this.config.maxAttempts?(this.setState(`error`),!1):(this.clearReconnectTimer(),this.setState(`reconnecting`),this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{e(),this.currentDelay=Math.min(this.currentDelay*this.config.backoffMultiplier,this.config.maxDelay)},this.currentDelay),!0):!1}cancelReconnect(){this.clearReconnectTimer(),this.state===`reconnecting`&&this.setState(`disconnected`)}clearReconnectTimer(){this.reconnectTimer!==void 0&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0)}getReconnectAttempts(){return this.reconnectAttempts}getNextDelay(){return this.currentDelay}destroy(){this.clearReconnectTimer()}},VeraniClient=class{constructor(e,t={}){this.url=e,this.listeners=new Map,this.messageQueue=[],this.isConnecting=!1,this.connectionId=0,this.lastPongReceived=0,this.options={reconnection:{enabled:t.reconnection?.enabled??DEFAULT_RECONNECTION_CONFIG.enabled,maxAttempts:t.reconnection?.maxAttempts??DEFAULT_RECONNECTION_CONFIG.maxAttempts,initialDelay:t.reconnection?.initialDelay??DEFAULT_RECONNECTION_CONFIG.initialDelay,maxDelay:t.reconnection?.maxDelay??DEFAULT_RECONNECTION_CONFIG.maxDelay,backoffMultiplier:t.reconnection?.backoffMultiplier??DEFAULT_RECONNECTION_CONFIG.backoffMultiplier},maxQueueSize:t.maxQueueSize??100,connectionTimeout:t.connectionTimeout??1e4,pingInterval:t.pingInterval??3e4,pongTimeout:t.pongTimeout??1e4},this.connectionManager=new ConnectionManager(this.options.reconnection,e=>{this.onStateChangeCallback?.(e)}),this.connect()}startPingInterval(){this.options.pingInterval===0||this.pingInterval!==void 0||(this.lastPongReceived=Date.now(),this.pingInterval=setInterval(()=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN){this.stopPingInterval();return}if(Date.now()-this.lastPongReceived>this.options.pongTimeout+this.options.pingInterval){this.stopPingInterval(),this.ws.close(1006,`Pong timeout`);return}try{this.emit(`ping`,{timestamp:Date.now()})}catch{}},this.options.pingInterval))}stopPingInterval(){this.pingInterval!==void 0&&(clearInterval(this.pingInterval),this.pingInterval=void 0),this.pongTimeout!==void 0&&(clearTimeout(this.pongTimeout),this.pongTimeout=void 0)}cleanupWebSocket(){if(this.stopPingInterval(),this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.ws){let e=this.ws;if(this.ws=void 0,e.readyState===WebSocket.OPEN||e.readyState===WebSocket.CONNECTING)try{e.close(1e3,`Cleanup`)}catch{}}}connect(){if(!this.isConnecting&&!this.isConnected()){this.cleanupWebSocket();try{this.isConnecting=!0,this.connectionId++;let e=this.connectionId;this.connectionManager.setState(`connecting`),this.emitLifecycleEvent(`connecting`),this.ws=new WebSocket(this.url),this.connectionTimeout=setTimeout(()=>{this.isConnecting&&this.connectionId===e&&(this.ws?.close(),this.handleConnectionError(Error(`Connection timeout`)))},this.options.connectionTimeout),this.ws.addEventListener(`open`,()=>{this.connectionId===e&&this.handleOpen()}),this.ws.addEventListener(`message`,t=>{this.connectionId===e&&this.handleMessage(t)}),this.ws.addEventListener(`close`,t=>{this.connectionId===e&&this.handleClose(t)}),this.ws.addEventListener(`error`,t=>{this.connectionId===e&&this.handleError(t)})}catch(e){this.isConnecting=!1,this.handleConnectionError(e)}}}handleOpen(){this.isConnecting=!1,this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.connectionManager.setState(`connected`),this.connectionManager.resetReconnection(),this.startPingInterval(),this.flushMessageQueue(),this.connectionResolve&&(this.connectionResolve(),this.connectionPromise=void 0,this.connectionResolve=void 0,this.connectionReject=void 0),this.emitLifecycleEvent(`open`),this.emitLifecycleEvent(`connected`),this.onOpenCallback?.()}handleMessage(e){let t=decodeServerMessage$1(e.data);if(!t)return;if(t.type===`event`&&t.channel===`pong`){this.lastPongReceived=Date.now();return}let r=t.type,i=t.data;t.type===`event`&&t.data&&typeof t.data==`object`&&`type`in t.data&&(r=t.data.type,i=t.data);let a=this.listeners.get(r);if(a)for(let e of a)try{e(i)}catch{}}handleClose(e){this.isConnecting=!1,this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.connectionManager.setState(`disconnected`),this.connectionReject&&=(this.connectionReject(Error(`Connection closed: ${e.reason||`Unknown reason`}`)),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.emitLifecycleEvent(`close`,e),this.emitLifecycleEvent(`disconnected`,e),this.onCloseCallback?.(e),e.code!==1e3&&e.code!==1001&&this.connectionManager.scheduleReconnect(()=>this.connect())&&this.emitLifecycleEvent(`reconnecting`)}handleError(e){this.isConnecting=!1,this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.emitLifecycleEvent(`error`,e),this.onErrorCallback?.(e),this.handleConnectionError(Error(`WebSocket error`))}handleConnectionError(e){this.isConnecting=!1,this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.connectionReject&&=(this.connectionReject(e),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.emitLifecycleEvent(`error`,e),this.connectionManager.scheduleReconnect(()=>this.connect())&&this.emitLifecycleEvent(`reconnecting`)}emitLifecycleEvent(e,t){let n=this.listeners.get(e);if(n)for(let e of n)try{e(t)}catch{}}flushMessageQueue(){if(!(!this.ws||this.ws.readyState!==WebSocket.OPEN))for(;this.messageQueue.length>0;){let e=this.messageQueue.shift();try{this.ws.send(encodeClientMessage$1(e))}catch{}}}getState(){return this.connectionManager.getState()}isConnected(){return this.ws?.readyState===WebSocket.OPEN&&this.connectionManager.getState()===`connected`}getConnectionState(){return{state:this.connectionManager.getState(),isConnected:this.isConnected(),isConnecting:this.isConnecting,reconnectAttempts:this.connectionManager.getReconnectAttempts(),connectionId:this.connectionId}}waitForConnection(){return this.isConnected()?Promise.resolve():(this.connectionPromise||=new Promise((e,t)=>{this.connectionResolve=e,this.connectionReject=t;let n=setTimeout(()=>{this.connectionReject&&=(this.connectionReject(Error(`Connection wait timeout`)),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0)},this.options.connectionTimeout*2);this.connectionPromise&&this.connectionPromise.finally(()=>{clearTimeout(n)})}),this.connectionPromise)}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){let n=this.listeners.get(e);n&&(n.delete(t),n.size===0&&this.listeners.delete(e))}once(e,t){let n=r=>{this.off(e,n),t(r)};this.on(e,n)}emit(e,n){let r={type:e,data:n};if(this.isConnected())try{this.ws.send(encodeClientMessage$1(r))}catch{this.queueMessage(r)}else this.queueMessage(r)}queueMessage(e){this.messageQueue.length>=this.options.maxQueueSize&&this.messageQueue.shift(),this.messageQueue.push(e)}onOpen(e){this.onOpenCallback=e}onClose(e){this.onCloseCallback=e}onError(e){this.onErrorCallback=e}onStateChange(e){this.onStateChangeCallback=e}reconnect(){this.connectionManager.resetReconnection(),this.connectionManager.cancelReconnect(),this.cleanupWebSocket(),this.isConnecting=!1,this.connectionManager.setState(`disconnected`),this.connect()}disconnect(){this.connectionManager.cancelReconnect(),this.isConnecting=!1,this.connectionReject&&=(this.connectionReject(Error(`Connection disconnected`)),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.cleanupWebSocket(),this.connectionManager.setState(`disconnected`)}close(){this.connectionReject&&=(this.connectionReject(Error(`Client closed`)),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.disconnect(),this.listeners.clear(),this.messageQueue=[],this.connectionManager.destroy()}};exports.ConnectionManager=ConnectionManager,exports.DEFAULT_RECONNECTION_CONFIG=DEFAULT_RECONNECTION_CONFIG,exports.PROTOCOL_VERSION=require_types.t,exports.VeraniClient=VeraniClient,exports.decodeClientMessage=require_types.a,exports.decodeFrame=require_types.o,exports.decodeServerMessage=require_types.s,exports.encodeClientMessage=require_types.n,exports.encodeFrame=require_types.r,exports.encodeServerMessage=require_types.i;
|
package/dist/client.d.cts
CHANGED
|
@@ -33,6 +33,10 @@ declare class ConnectionManager {
|
|
|
33
33
|
* Gets the current connection state
|
|
34
34
|
*/
|
|
35
35
|
getState(): ConnectionState;
|
|
36
|
+
/**
|
|
37
|
+
* Validates if a state transition is valid
|
|
38
|
+
*/
|
|
39
|
+
private isValidStateTransition;
|
|
36
40
|
/**
|
|
37
41
|
* Updates the connection state and notifies listeners
|
|
38
42
|
*/
|
|
@@ -78,6 +82,10 @@ interface VeraniClientOptions {
|
|
|
78
82
|
maxQueueSize?: number;
|
|
79
83
|
/** Connection timeout in milliseconds */
|
|
80
84
|
connectionTimeout?: number;
|
|
85
|
+
/** Ping interval in milliseconds (0 = disabled, default: 30000) */
|
|
86
|
+
pingInterval?: number;
|
|
87
|
+
/** Pong timeout in milliseconds (default: 10000) */
|
|
88
|
+
pongTimeout?: number;
|
|
81
89
|
}
|
|
82
90
|
/**
|
|
83
91
|
* Verani WebSocket client with automatic reconnection and lifecycle management
|
|
@@ -96,12 +104,30 @@ declare class VeraniClient {
|
|
|
96
104
|
private connectionPromise?;
|
|
97
105
|
private connectionResolve?;
|
|
98
106
|
private connectionReject?;
|
|
107
|
+
private connectionTimeout?;
|
|
108
|
+
private isConnecting;
|
|
109
|
+
private connectionId;
|
|
110
|
+
private pingInterval?;
|
|
111
|
+
private pongTimeout?;
|
|
112
|
+
private lastPongReceived;
|
|
99
113
|
/**
|
|
100
114
|
* Creates a new Verani client
|
|
101
115
|
* @param url - WebSocket URL to connect to
|
|
102
116
|
* @param options - Client configuration options
|
|
103
117
|
*/
|
|
104
118
|
constructor(url: string, options?: VeraniClientOptions);
|
|
119
|
+
/**
|
|
120
|
+
* Starts the ping interval to keep the connection alive
|
|
121
|
+
*/
|
|
122
|
+
private startPingInterval;
|
|
123
|
+
/**
|
|
124
|
+
* Stops the ping interval
|
|
125
|
+
*/
|
|
126
|
+
private stopPingInterval;
|
|
127
|
+
/**
|
|
128
|
+
* Cleans up existing WebSocket connection and resources
|
|
129
|
+
*/
|
|
130
|
+
private cleanupWebSocket;
|
|
105
131
|
/**
|
|
106
132
|
* Establishes WebSocket connection
|
|
107
133
|
*/
|
|
@@ -142,6 +168,16 @@ declare class VeraniClient {
|
|
|
142
168
|
* Checks if the client is currently connected
|
|
143
169
|
*/
|
|
144
170
|
isConnected(): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Gets detailed connection information
|
|
173
|
+
*/
|
|
174
|
+
getConnectionState(): {
|
|
175
|
+
state: ConnectionState;
|
|
176
|
+
isConnected: boolean;
|
|
177
|
+
isConnecting: boolean;
|
|
178
|
+
reconnectAttempts: number;
|
|
179
|
+
connectionId: number;
|
|
180
|
+
};
|
|
145
181
|
/**
|
|
146
182
|
* Waits for the connection to be established
|
|
147
183
|
* @returns Promise that resolves when connected
|
package/dist/client.d.mts
CHANGED
|
@@ -33,6 +33,10 @@ declare class ConnectionManager {
|
|
|
33
33
|
* Gets the current connection state
|
|
34
34
|
*/
|
|
35
35
|
getState(): ConnectionState;
|
|
36
|
+
/**
|
|
37
|
+
* Validates if a state transition is valid
|
|
38
|
+
*/
|
|
39
|
+
private isValidStateTransition;
|
|
36
40
|
/**
|
|
37
41
|
* Updates the connection state and notifies listeners
|
|
38
42
|
*/
|
|
@@ -78,6 +82,10 @@ interface VeraniClientOptions {
|
|
|
78
82
|
maxQueueSize?: number;
|
|
79
83
|
/** Connection timeout in milliseconds */
|
|
80
84
|
connectionTimeout?: number;
|
|
85
|
+
/** Ping interval in milliseconds (0 = disabled, default: 30000) */
|
|
86
|
+
pingInterval?: number;
|
|
87
|
+
/** Pong timeout in milliseconds (default: 10000) */
|
|
88
|
+
pongTimeout?: number;
|
|
81
89
|
}
|
|
82
90
|
/**
|
|
83
91
|
* Verani WebSocket client with automatic reconnection and lifecycle management
|
|
@@ -96,12 +104,30 @@ declare class VeraniClient {
|
|
|
96
104
|
private connectionPromise?;
|
|
97
105
|
private connectionResolve?;
|
|
98
106
|
private connectionReject?;
|
|
107
|
+
private connectionTimeout?;
|
|
108
|
+
private isConnecting;
|
|
109
|
+
private connectionId;
|
|
110
|
+
private pingInterval?;
|
|
111
|
+
private pongTimeout?;
|
|
112
|
+
private lastPongReceived;
|
|
99
113
|
/**
|
|
100
114
|
* Creates a new Verani client
|
|
101
115
|
* @param url - WebSocket URL to connect to
|
|
102
116
|
* @param options - Client configuration options
|
|
103
117
|
*/
|
|
104
118
|
constructor(url: string, options?: VeraniClientOptions);
|
|
119
|
+
/**
|
|
120
|
+
* Starts the ping interval to keep the connection alive
|
|
121
|
+
*/
|
|
122
|
+
private startPingInterval;
|
|
123
|
+
/**
|
|
124
|
+
* Stops the ping interval
|
|
125
|
+
*/
|
|
126
|
+
private stopPingInterval;
|
|
127
|
+
/**
|
|
128
|
+
* Cleans up existing WebSocket connection and resources
|
|
129
|
+
*/
|
|
130
|
+
private cleanupWebSocket;
|
|
105
131
|
/**
|
|
106
132
|
* Establishes WebSocket connection
|
|
107
133
|
*/
|
|
@@ -142,6 +168,16 @@ declare class VeraniClient {
|
|
|
142
168
|
* Checks if the client is currently connected
|
|
143
169
|
*/
|
|
144
170
|
isConnected(): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Gets detailed connection information
|
|
173
|
+
*/
|
|
174
|
+
getConnectionState(): {
|
|
175
|
+
state: ConnectionState;
|
|
176
|
+
isConnected: boolean;
|
|
177
|
+
isConnecting: boolean;
|
|
178
|
+
reconnectAttempts: number;
|
|
179
|
+
connectionId: number;
|
|
180
|
+
};
|
|
145
181
|
/**
|
|
146
182
|
* Waits for the connection to be established
|
|
147
183
|
* @returns Promise that resolves when connected
|
package/dist/client.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as decodeClientMessage,i as encodeServerMessage,n as encodeClientMessage,o as decodeFrame,r as encodeFrame,s as decodeServerMessage,t as PROTOCOL_VERSION}from"./types-CJLnZrA8.mjs";function encodeClientMessage$1(e){return encodeClientMessage(e)}function decodeServerMessage$1(e){return decodeServerMessage(e)}const DEFAULT_RECONNECTION_CONFIG={enabled:!0,maxAttempts:10,initialDelay:1e3,maxDelay:3e4,backoffMultiplier:1.5};var ConnectionManager=class{constructor(e=DEFAULT_RECONNECTION_CONFIG,c){this.config=e,this.onStateChange=c,this.state=`disconnected`,this.reconnectAttempts=0,this.currentDelay=e.initialDelay}getState(){return this.state}setState(e){this.state!==e&&(this.state=e,this.onStateChange?.(e))}resetReconnection(){this.reconnectAttempts=0,this.currentDelay=this.config.initialDelay,this.clearReconnectTimer()}scheduleReconnect(e){return this.config.enabled?this.config.maxAttempts>0&&this.reconnectAttempts>=this.config.maxAttempts?(this.setState(`error`),!1):(this.clearReconnectTimer(),this.setState(`reconnecting`),this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{e(),this.currentDelay=Math.min(this.currentDelay*this.config.backoffMultiplier,this.config.maxDelay)},this.currentDelay),!0):!1}cancelReconnect(){this.clearReconnectTimer(),this.state===`reconnecting`&&this.setState(`disconnected`)}clearReconnectTimer(){this.reconnectTimer!==void 0&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0)}getReconnectAttempts(){return this.reconnectAttempts}getNextDelay(){return this.currentDelay}destroy(){this.clearReconnectTimer()}},VeraniClient=class{constructor(e,c={}){this.url=e,this.listeners=new Map,this.messageQueue=[],this.options={reconnection:{enabled:c.reconnection?.enabled??DEFAULT_RECONNECTION_CONFIG.enabled,maxAttempts:c.reconnection?.maxAttempts??DEFAULT_RECONNECTION_CONFIG.maxAttempts,initialDelay:c.reconnection?.initialDelay??DEFAULT_RECONNECTION_CONFIG.initialDelay,maxDelay:c.reconnection?.maxDelay??DEFAULT_RECONNECTION_CONFIG.maxDelay,backoffMultiplier:c.reconnection?.backoffMultiplier??DEFAULT_RECONNECTION_CONFIG.backoffMultiplier},maxQueueSize:c.maxQueueSize??100,connectionTimeout:c.connectionTimeout??1e4},this.connectionManager=new ConnectionManager(this.options.reconnection,e=>{this.onStateChangeCallback?.(e)}),this.connect()}connect(){try{this.connectionManager.setState(`connecting`),this.emitLifecycleEvent(`connecting`),this.ws=new WebSocket(this.url)
|
|
1
|
+
import{a as decodeClientMessage,i as encodeServerMessage,n as encodeClientMessage,o as decodeFrame,r as encodeFrame,s as decodeServerMessage,t as PROTOCOL_VERSION}from"./types-CJLnZrA8.mjs";function encodeClientMessage$1(e){return encodeClientMessage(e)}function decodeServerMessage$1(e){return decodeServerMessage(e)}const DEFAULT_RECONNECTION_CONFIG={enabled:!0,maxAttempts:10,initialDelay:1e3,maxDelay:3e4,backoffMultiplier:1.5};var ConnectionManager=class{constructor(e=DEFAULT_RECONNECTION_CONFIG,c){this.config=e,this.onStateChange=c,this.state=`disconnected`,this.reconnectAttempts=0,this.currentDelay=e.initialDelay}getState(){return this.state}isValidStateTransition(e,c){return{disconnected:[`connecting`,`reconnecting`],connecting:[`connected`,`disconnected`,`error`,`reconnecting`],connected:[`disconnected`,`reconnecting`],reconnecting:[`connecting`,`disconnected`,`error`],error:[`reconnecting`,`disconnected`,`connecting`]}[e]?.includes(c)??!1}setState(e){this.state!==e&&(this.isValidStateTransition(this.state,e),this.state=e,this.onStateChange?.(e))}resetReconnection(){this.reconnectAttempts=0,this.currentDelay=this.config.initialDelay,this.clearReconnectTimer()}scheduleReconnect(e){return this.config.enabled?this.config.maxAttempts>0&&this.reconnectAttempts>=this.config.maxAttempts?(this.setState(`error`),!1):(this.clearReconnectTimer(),this.setState(`reconnecting`),this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{e(),this.currentDelay=Math.min(this.currentDelay*this.config.backoffMultiplier,this.config.maxDelay)},this.currentDelay),!0):!1}cancelReconnect(){this.clearReconnectTimer(),this.state===`reconnecting`&&this.setState(`disconnected`)}clearReconnectTimer(){this.reconnectTimer!==void 0&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0)}getReconnectAttempts(){return this.reconnectAttempts}getNextDelay(){return this.currentDelay}destroy(){this.clearReconnectTimer()}},VeraniClient=class{constructor(e,c={}){this.url=e,this.listeners=new Map,this.messageQueue=[],this.isConnecting=!1,this.connectionId=0,this.lastPongReceived=0,this.options={reconnection:{enabled:c.reconnection?.enabled??DEFAULT_RECONNECTION_CONFIG.enabled,maxAttempts:c.reconnection?.maxAttempts??DEFAULT_RECONNECTION_CONFIG.maxAttempts,initialDelay:c.reconnection?.initialDelay??DEFAULT_RECONNECTION_CONFIG.initialDelay,maxDelay:c.reconnection?.maxDelay??DEFAULT_RECONNECTION_CONFIG.maxDelay,backoffMultiplier:c.reconnection?.backoffMultiplier??DEFAULT_RECONNECTION_CONFIG.backoffMultiplier},maxQueueSize:c.maxQueueSize??100,connectionTimeout:c.connectionTimeout??1e4,pingInterval:c.pingInterval??3e4,pongTimeout:c.pongTimeout??1e4},this.connectionManager=new ConnectionManager(this.options.reconnection,e=>{this.onStateChangeCallback?.(e)}),this.connect()}startPingInterval(){this.options.pingInterval===0||this.pingInterval!==void 0||(this.lastPongReceived=Date.now(),this.pingInterval=setInterval(()=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN){this.stopPingInterval();return}if(Date.now()-this.lastPongReceived>this.options.pongTimeout+this.options.pingInterval){this.stopPingInterval(),this.ws.close(1006,`Pong timeout`);return}try{this.emit(`ping`,{timestamp:Date.now()})}catch{}},this.options.pingInterval))}stopPingInterval(){this.pingInterval!==void 0&&(clearInterval(this.pingInterval),this.pingInterval=void 0),this.pongTimeout!==void 0&&(clearTimeout(this.pongTimeout),this.pongTimeout=void 0)}cleanupWebSocket(){if(this.stopPingInterval(),this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.ws){let e=this.ws;if(this.ws=void 0,e.readyState===WebSocket.OPEN||e.readyState===WebSocket.CONNECTING)try{e.close(1e3,`Cleanup`)}catch{}}}connect(){if(!this.isConnecting&&!this.isConnected()){this.cleanupWebSocket();try{this.isConnecting=!0,this.connectionId++;let e=this.connectionId;this.connectionManager.setState(`connecting`),this.emitLifecycleEvent(`connecting`),this.ws=new WebSocket(this.url),this.connectionTimeout=setTimeout(()=>{this.isConnecting&&this.connectionId===e&&(this.ws?.close(),this.handleConnectionError(Error(`Connection timeout`)))},this.options.connectionTimeout),this.ws.addEventListener(`open`,()=>{this.connectionId===e&&this.handleOpen()}),this.ws.addEventListener(`message`,c=>{this.connectionId===e&&this.handleMessage(c)}),this.ws.addEventListener(`close`,c=>{this.connectionId===e&&this.handleClose(c)}),this.ws.addEventListener(`error`,c=>{this.connectionId===e&&this.handleError(c)})}catch(e){this.isConnecting=!1,this.handleConnectionError(e)}}}handleOpen(){this.isConnecting=!1,this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.connectionManager.setState(`connected`),this.connectionManager.resetReconnection(),this.startPingInterval(),this.flushMessageQueue(),this.connectionResolve&&(this.connectionResolve(),this.connectionPromise=void 0,this.connectionResolve=void 0,this.connectionReject=void 0),this.emitLifecycleEvent(`open`),this.emitLifecycleEvent(`connected`),this.onOpenCallback?.()}handleMessage(e){let c=decodeServerMessage$1(e.data);if(!c)return;if(c.type===`event`&&c.channel===`pong`){this.lastPongReceived=Date.now();return}let l=c.type,u=c.data;c.type===`event`&&c.data&&typeof c.data==`object`&&`type`in c.data&&(l=c.data.type,u=c.data);let d=this.listeners.get(l);if(d)for(let e of d)try{e(u)}catch{}}handleClose(e){this.isConnecting=!1,this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.connectionManager.setState(`disconnected`),this.connectionReject&&=(this.connectionReject(Error(`Connection closed: ${e.reason||`Unknown reason`}`)),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.emitLifecycleEvent(`close`,e),this.emitLifecycleEvent(`disconnected`,e),this.onCloseCallback?.(e),e.code!==1e3&&e.code!==1001&&this.connectionManager.scheduleReconnect(()=>this.connect())&&this.emitLifecycleEvent(`reconnecting`)}handleError(e){this.isConnecting=!1,this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.emitLifecycleEvent(`error`,e),this.onErrorCallback?.(e),this.handleConnectionError(Error(`WebSocket error`))}handleConnectionError(e){this.isConnecting=!1,this.connectionTimeout!==void 0&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.connectionReject&&=(this.connectionReject(e),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.emitLifecycleEvent(`error`,e),this.connectionManager.scheduleReconnect(()=>this.connect())&&this.emitLifecycleEvent(`reconnecting`)}emitLifecycleEvent(e,c){let l=this.listeners.get(e);if(l)for(let e of l)try{e(c)}catch{}}flushMessageQueue(){if(!(!this.ws||this.ws.readyState!==WebSocket.OPEN))for(;this.messageQueue.length>0;){let e=this.messageQueue.shift();try{this.ws.send(encodeClientMessage$1(e))}catch{}}}getState(){return this.connectionManager.getState()}isConnected(){return this.ws?.readyState===WebSocket.OPEN&&this.connectionManager.getState()===`connected`}getConnectionState(){return{state:this.connectionManager.getState(),isConnected:this.isConnected(),isConnecting:this.isConnecting,reconnectAttempts:this.connectionManager.getReconnectAttempts(),connectionId:this.connectionId}}waitForConnection(){return this.isConnected()?Promise.resolve():(this.connectionPromise||=new Promise((e,c)=>{this.connectionResolve=e,this.connectionReject=c;let l=setTimeout(()=>{this.connectionReject&&=(this.connectionReject(Error(`Connection wait timeout`)),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0)},this.options.connectionTimeout*2);this.connectionPromise&&this.connectionPromise.finally(()=>{clearTimeout(l)})}),this.connectionPromise)}on(e,c){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(c)}off(e,c){let l=this.listeners.get(e);l&&(l.delete(c),l.size===0&&this.listeners.delete(e))}once(e,c){let l=u=>{this.off(e,l),c(u)};this.on(e,l)}emit(e,c){let l={type:e,data:c};if(this.isConnected())try{this.ws.send(encodeClientMessage$1(l))}catch{this.queueMessage(l)}else this.queueMessage(l)}queueMessage(e){this.messageQueue.length>=this.options.maxQueueSize&&this.messageQueue.shift(),this.messageQueue.push(e)}onOpen(e){this.onOpenCallback=e}onClose(e){this.onCloseCallback=e}onError(e){this.onErrorCallback=e}onStateChange(e){this.onStateChangeCallback=e}reconnect(){this.connectionManager.resetReconnection(),this.connectionManager.cancelReconnect(),this.cleanupWebSocket(),this.isConnecting=!1,this.connectionManager.setState(`disconnected`),this.connect()}disconnect(){this.connectionManager.cancelReconnect(),this.isConnecting=!1,this.connectionReject&&=(this.connectionReject(Error(`Connection disconnected`)),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.cleanupWebSocket(),this.connectionManager.setState(`disconnected`)}close(){this.connectionReject&&=(this.connectionReject(Error(`Client closed`)),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.disconnect(),this.listeners.clear(),this.messageQueue=[],this.connectionManager.destroy()}};export{ConnectionManager,DEFAULT_RECONNECTION_CONFIG,PROTOCOL_VERSION,VeraniClient,decodeClientMessage,decodeFrame,decodeServerMessage,encodeClientMessage,encodeFrame,encodeServerMessage};
|
package/dist/verani.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const require_types=require(`./types-083oWz55.cjs`);let __cloudflare_actors=require(`@cloudflare/actors`);function defaultExtractMeta(e){let a=crypto.randomUUID(),o=crypto.randomUUID(),s=new URL(e.url).searchParams.get(`channels`);return{userId:a,clientId:o,channels:s?s.split(`,`).map(e=>e.trim()).filter(Boolean):[`default`]}}function defineRoom(e){return{name:e.name,websocketPath:e.websocketPath,extractMeta:e.extractMeta||defaultExtractMeta,onConnect:e.onConnect,onDisconnect:e.onDisconnect,onMessage:e.onMessage,onError:e.onError}}function storeAttachment(e,a){e.serializeAttachment(a)}function restoreSessions(e){let a=0;for(let
|
|
1
|
+
const require_types=require(`./types-083oWz55.cjs`);let __cloudflare_actors=require(`@cloudflare/actors`);function defaultExtractMeta(e){let a=crypto.randomUUID(),o=crypto.randomUUID(),s=new URL(e.url).searchParams.get(`channels`);return{userId:a,clientId:o,channels:s?s.split(`,`).map(e=>e.trim()).filter(Boolean):[`default`]}}function defineRoom(e){return{name:e.name,websocketPath:e.websocketPath,extractMeta:e.extractMeta||defaultExtractMeta,onConnect:e.onConnect,onDisconnect:e.onDisconnect,onMessage:e.onMessage,onError:e.onError}}function isValidConnectionMeta(e){return!(!e||typeof e!=`object`||typeof e.userId!=`string`||!e.userId||typeof e.clientId!=`string`||!e.clientId||!Array.isArray(e.channels)||!e.channels.every(e=>typeof e==`string`))}function storeAttachment(e,a){e.serializeAttachment(a)}function restoreSessions(e){let a=0,o=0;for(let s of e.ctx.getWebSockets()){if(s.readyState!==WebSocket.OPEN){o++;continue}let l=s.deserializeAttachment();if(!l){o++;continue}if(!isValidConnectionMeta(l)){o++;continue}e.sessions.set(s,{ws:s,meta:l}),a++}}function decodeFrame$1(a){return require_types.o(a)??{type:`invalid`}}function encodeFrame$1(a){return require_types.r(a)}function sanitizeToClassName(e){return e.replace(/^\/+/,``).split(/[-_\/\s]+/).map(e=>e.replace(/[^a-zA-Z0-9]/g,``)).filter(e=>e.length>0).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(``)||`VeraniActor`}function createActorHandler(e){let o=sanitizeToClassName(e.name||e.websocketPath||`VeraniActor`);class s extends __cloudflare_actors.Actor{constructor(...e){super(...e),this.sessions=new Map}static configuration(a){return{locationHint:`me`,sockets:{upgradePath:e.websocketPath,autoResponse:{ping:`ping`,pong:`pong`}}}}async shouldUpgradeWebSocket(e){return!0}async fetch(a){let o=new URL(a.url),s=a.headers.get(`Upgrade`);return o.pathname===e.websocketPath&&s===`websocket`&&await this.shouldUpgradeWebSocket(a)?this.onWebSocketUpgrade(a):this.onRequest(a)}async onInit(){try{restoreSessions(this)}catch{}if(e.onHibernationRestore&&this.sessions.size>0)try{await e.onHibernationRestore(this)}catch{}else e.onHibernationRestore&&this.sessions.size}async onWebSocketConnect(a,o){let s;try{if(s=e.extractMeta?await e.extractMeta(o):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(a,s),e.onConnect){let o={actor:this,ws:a,meta:s};await e.onConnect(o)}this.sessions.set(a,{ws:a,meta:s})}catch(o){if(e.onError&&s)try{await e.onError(o,{actor:this,ws:a,meta:s})}catch{}a.close(1011,`Internal server error`)}}async onWebSocketMessage(a,o){let s;try{let c=decodeFrame$1(o);if(s=this.sessions.get(a),!s)return;if(e.onMessage){let o={actor:this,ws:a,meta:s.meta,frame:c};await e.onMessage(o,c)}}catch(o){if(e.onError&&s)try{await e.onError(o,{actor:this,ws:a,meta:s.meta})}catch{}}}async onWebSocketDisconnect(a){try{let o=this.sessions.get(a);if(this.sessions.delete(a),o&&e.onDisconnect){let s={actor:this,ws:a,meta:o.meta};await e.onDisconnect(s)}}catch{}}cleanupStaleSessions(){let e=0,a=[];for(let[e,o]of this.sessions.entries())e.readyState!==WebSocket.OPEN&&a.push(e);for(let o of a)this.sessions.delete(o),e++;return e}broadcast(e,a,o){let s=0,c=encodeFrame$1({type:`event`,channel:e,data:a}),l=[];for(let{ws:a,meta:u}of this.sessions.values())if(u.channels.includes(e)&&!(o?.except&&a===o.except)&&!(o?.userIds&&!o.userIds.includes(u.userId))&&!(o?.clientIds&&!o.clientIds.includes(u.clientId))){if(a.readyState!==WebSocket.OPEN){l.push(a);continue}try{a.send(c),s++}catch{l.push(a)}}for(let e of l)this.sessions.delete(e);return l.length,s}getSessionCount(){return this.sessions.size}getConnectedUserIds(){let e=new Set;for(let{meta:a}of this.sessions.values())e.add(a.userId);return Array.from(e)}getUserSessions(e){let a=[];for(let{ws:o,meta:s}of this.sessions.values())s.userId===e&&a.push(o);return a}sendToUser(e,a,o){let s=0,c=encodeFrame$1({type:`event`,channel:a,data:o}),l=[];for(let{ws:o,meta:u}of this.sessions.values())if(u.userId===e&&u.channels.includes(a)){if(o.readyState!==WebSocket.OPEN){l.push(o);continue}try{o.send(c),s++}catch{l.push(o)}}for(let e of l)this.sessions.delete(e);return l.length,s}getStorage(){return this.ctx.storage}}return Object.defineProperty(s,`name`,{value:o,writable:!1,configurable:!0}),s}exports.PROTOCOL_VERSION=require_types.t,exports.createActorHandler=createActorHandler,exports.decodeClientMessage=require_types.a,exports.decodeFrame=require_types.o,exports.decodeServerMessage=require_types.s,exports.defineRoom=defineRoom,exports.encodeClientMessage=require_types.n,exports.encodeFrame=require_types.r,exports.encodeServerMessage=require_types.i,exports.restoreSessions=restoreSessions,exports.storeAttachment=storeAttachment;
|
package/dist/verani.d.cts
CHANGED
|
@@ -57,6 +57,13 @@ interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown
|
|
|
57
57
|
* @see @src/actor/actor-runtime.ts sendToUser()
|
|
58
58
|
*/
|
|
59
59
|
sendToUser(userId: string, channel: string, data?: any): number;
|
|
60
|
+
/**
|
|
61
|
+
* Validates and removes stale WebSocket sessions.
|
|
62
|
+
* Called automatically during broadcast/send operations, but can be called manually.
|
|
63
|
+
* Returns the number of stale sessions removed.
|
|
64
|
+
* @see @src/actor/actor-runtime.ts cleanupStaleSessions()
|
|
65
|
+
*/
|
|
66
|
+
cleanupStaleSessions(): number;
|
|
60
67
|
/**
|
|
61
68
|
* Access the Durable Object storage API for this actor instance.
|
|
62
69
|
* @see @src/actor/actor-runtime.ts getStorage()
|
|
@@ -83,23 +90,50 @@ interface MessageContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unkn
|
|
|
83
90
|
}
|
|
84
91
|
/**
|
|
85
92
|
* Room definition with lifecycle hooks
|
|
93
|
+
*
|
|
94
|
+
* **Important:** All lifecycle hooks are properly awaited if they return a Promise.
|
|
95
|
+
* This ensures async operations complete before the actor proceeds to the next step
|
|
96
|
+
* or potentially enters hibernation.
|
|
86
97
|
*/
|
|
87
98
|
interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
|
|
88
99
|
/** Optional room name for debugging */
|
|
89
100
|
name?: string;
|
|
90
101
|
/** WebSocket upgrade path (default: "/ws") */
|
|
91
102
|
websocketPath: string;
|
|
92
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Extract metadata from the connection request.
|
|
105
|
+
* This function is awaited if it returns a Promise.
|
|
106
|
+
*/
|
|
93
107
|
extractMeta?(req: Request): TMeta | Promise<TMeta>;
|
|
94
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Called when a new WebSocket connection is established.
|
|
110
|
+
* This hook is awaited if it returns a Promise. The session is only added to the
|
|
111
|
+
* sessions map after this hook completes successfully. If this hook throws, the
|
|
112
|
+
* connection is closed and no orphaned session is created.
|
|
113
|
+
*/
|
|
95
114
|
onConnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
|
|
96
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Called when a WebSocket connection is closed.
|
|
117
|
+
* This hook is awaited if it returns a Promise. The session is removed from the
|
|
118
|
+
* sessions map before this hook is called.
|
|
119
|
+
*/
|
|
97
120
|
onDisconnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
|
|
98
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* Called when a message is received from a connection.
|
|
123
|
+
* This hook is awaited if it returns a Promise. The actor will not process
|
|
124
|
+
* other messages from this connection until this hook completes.
|
|
125
|
+
*/
|
|
99
126
|
onMessage?(ctx: MessageContext<TMeta, E>, frame: MessageFrame): void | Promise<void>;
|
|
100
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Called when an error occurs in a lifecycle hook.
|
|
129
|
+
* This hook is also awaited if it returns a Promise.
|
|
130
|
+
*/
|
|
101
131
|
onError?(error: Error, ctx: RoomContext<TMeta, E>): void | Promise<void>;
|
|
102
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Called after actor wakes from hibernation and sessions are restored.
|
|
134
|
+
* This hook is awaited if it returns a Promise. It is called even if some
|
|
135
|
+
* sessions failed to restore, allowing you to handle partial restoration scenarios.
|
|
136
|
+
*/
|
|
103
137
|
onHibernationRestore?(actor: VeraniActor<TMeta, E>): void | Promise<void>;
|
|
104
138
|
}
|
|
105
139
|
//#endregion
|
package/dist/verani.d.mts
CHANGED
|
@@ -57,6 +57,13 @@ interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown
|
|
|
57
57
|
* @see @src/actor/actor-runtime.ts sendToUser()
|
|
58
58
|
*/
|
|
59
59
|
sendToUser(userId: string, channel: string, data?: any): number;
|
|
60
|
+
/**
|
|
61
|
+
* Validates and removes stale WebSocket sessions.
|
|
62
|
+
* Called automatically during broadcast/send operations, but can be called manually.
|
|
63
|
+
* Returns the number of stale sessions removed.
|
|
64
|
+
* @see @src/actor/actor-runtime.ts cleanupStaleSessions()
|
|
65
|
+
*/
|
|
66
|
+
cleanupStaleSessions(): number;
|
|
60
67
|
/**
|
|
61
68
|
* Access the Durable Object storage API for this actor instance.
|
|
62
69
|
* @see @src/actor/actor-runtime.ts getStorage()
|
|
@@ -83,23 +90,50 @@ interface MessageContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unkn
|
|
|
83
90
|
}
|
|
84
91
|
/**
|
|
85
92
|
* Room definition with lifecycle hooks
|
|
93
|
+
*
|
|
94
|
+
* **Important:** All lifecycle hooks are properly awaited if they return a Promise.
|
|
95
|
+
* This ensures async operations complete before the actor proceeds to the next step
|
|
96
|
+
* or potentially enters hibernation.
|
|
86
97
|
*/
|
|
87
98
|
interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
|
|
88
99
|
/** Optional room name for debugging */
|
|
89
100
|
name?: string;
|
|
90
101
|
/** WebSocket upgrade path (default: "/ws") */
|
|
91
102
|
websocketPath: string;
|
|
92
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Extract metadata from the connection request.
|
|
105
|
+
* This function is awaited if it returns a Promise.
|
|
106
|
+
*/
|
|
93
107
|
extractMeta?(req: Request): TMeta | Promise<TMeta>;
|
|
94
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Called when a new WebSocket connection is established.
|
|
110
|
+
* This hook is awaited if it returns a Promise. The session is only added to the
|
|
111
|
+
* sessions map after this hook completes successfully. If this hook throws, the
|
|
112
|
+
* connection is closed and no orphaned session is created.
|
|
113
|
+
*/
|
|
95
114
|
onConnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
|
|
96
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Called when a WebSocket connection is closed.
|
|
117
|
+
* This hook is awaited if it returns a Promise. The session is removed from the
|
|
118
|
+
* sessions map before this hook is called.
|
|
119
|
+
*/
|
|
97
120
|
onDisconnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
|
|
98
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* Called when a message is received from a connection.
|
|
123
|
+
* This hook is awaited if it returns a Promise. The actor will not process
|
|
124
|
+
* other messages from this connection until this hook completes.
|
|
125
|
+
*/
|
|
99
126
|
onMessage?(ctx: MessageContext<TMeta, E>, frame: MessageFrame): void | Promise<void>;
|
|
100
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Called when an error occurs in a lifecycle hook.
|
|
129
|
+
* This hook is also awaited if it returns a Promise.
|
|
130
|
+
*/
|
|
101
131
|
onError?(error: Error, ctx: RoomContext<TMeta, E>): void | Promise<void>;
|
|
102
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Called after actor wakes from hibernation and sessions are restored.
|
|
134
|
+
* This hook is awaited if it returns a Promise. It is called even if some
|
|
135
|
+
* sessions failed to restore, allowing you to handle partial restoration scenarios.
|
|
136
|
+
*/
|
|
103
137
|
onHibernationRestore?(actor: VeraniActor<TMeta, E>): void | Promise<void>;
|
|
104
138
|
}
|
|
105
139
|
//#endregion
|
package/dist/verani.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as decodeClientMessage,i as encodeServerMessage,n as encodeClientMessage,o as decodeFrame,r as encodeFrame,s as decodeServerMessage,t as PROTOCOL_VERSION}from"./types-CJLnZrA8.mjs";import{Actor}from"@cloudflare/actors";function defaultExtractMeta(e){let d=crypto.randomUUID(),f=crypto.randomUUID(),p=new URL(e.url).searchParams.get(`channels`);return{userId:d,clientId:f,channels:p?p.split(`,`).map(e=>e.trim()).filter(Boolean):[`default`]}}function defineRoom(e){return{name:e.name,websocketPath:e.websocketPath,extractMeta:e.extractMeta||defaultExtractMeta,onConnect:e.onConnect,onDisconnect:e.onDisconnect,onMessage:e.onMessage,onError:e.onError}}function storeAttachment(e,d){e.serializeAttachment(d)}function restoreSessions(e){let d=0;for(let
|
|
1
|
+
import{a as decodeClientMessage,i as encodeServerMessage,n as encodeClientMessage,o as decodeFrame,r as encodeFrame,s as decodeServerMessage,t as PROTOCOL_VERSION}from"./types-CJLnZrA8.mjs";import{Actor}from"@cloudflare/actors";function defaultExtractMeta(e){let d=crypto.randomUUID(),f=crypto.randomUUID(),p=new URL(e.url).searchParams.get(`channels`);return{userId:d,clientId:f,channels:p?p.split(`,`).map(e=>e.trim()).filter(Boolean):[`default`]}}function defineRoom(e){return{name:e.name,websocketPath:e.websocketPath,extractMeta:e.extractMeta||defaultExtractMeta,onConnect:e.onConnect,onDisconnect:e.onDisconnect,onMessage:e.onMessage,onError:e.onError}}function isValidConnectionMeta(e){return!(!e||typeof e!=`object`||typeof e.userId!=`string`||!e.userId||typeof e.clientId!=`string`||!e.clientId||!Array.isArray(e.channels)||!e.channels.every(e=>typeof e==`string`))}function storeAttachment(e,d){e.serializeAttachment(d)}function restoreSessions(e){let d=0,f=0;for(let p of e.ctx.getWebSockets()){if(p.readyState!==WebSocket.OPEN){f++;continue}let m=p.deserializeAttachment();if(!m){f++;continue}if(!isValidConnectionMeta(m)){f++;continue}e.sessions.set(p,{ws:p,meta:m}),d++}}function decodeFrame$1(e){return decodeFrame(e)??{type:`invalid`}}function encodeFrame$1(e){return encodeFrame(e)}function sanitizeToClassName(e){return e.replace(/^\/+/,``).split(/[-_\/\s]+/).map(e=>e.replace(/[^a-zA-Z0-9]/g,``)).filter(e=>e.length>0).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(``)||`VeraniActor`}function createActorHandler(e){let d=sanitizeToClassName(e.name||e.websocketPath||`VeraniActor`);class f extends Actor{constructor(...e){super(...e),this.sessions=new Map}static configuration(d){return{locationHint:`me`,sockets:{upgradePath:e.websocketPath,autoResponse:{ping:`ping`,pong:`pong`}}}}async shouldUpgradeWebSocket(e){return!0}async fetch(d){let f=new URL(d.url),p=d.headers.get(`Upgrade`);return f.pathname===e.websocketPath&&p===`websocket`&&await this.shouldUpgradeWebSocket(d)?this.onWebSocketUpgrade(d):this.onRequest(d)}async onInit(){try{restoreSessions(this)}catch{}if(e.onHibernationRestore&&this.sessions.size>0)try{await e.onHibernationRestore(this)}catch{}else e.onHibernationRestore&&this.sessions.size}async onWebSocketConnect(d,f){let p;try{if(p=e.extractMeta?await e.extractMeta(f):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(d,p),e.onConnect){let f={actor:this,ws:d,meta:p};await e.onConnect(f)}this.sessions.set(d,{ws:d,meta:p})}catch(f){if(e.onError&&p)try{await e.onError(f,{actor:this,ws:d,meta:p})}catch{}d.close(1011,`Internal server error`)}}async onWebSocketMessage(d,f){let p;try{let m=decodeFrame$1(f);if(p=this.sessions.get(d),!p)return;if(e.onMessage){let f={actor:this,ws:d,meta:p.meta,frame:m};await e.onMessage(f,m)}}catch(f){if(e.onError&&p)try{await e.onError(f,{actor:this,ws:d,meta:p.meta})}catch{}}}async onWebSocketDisconnect(d){try{let f=this.sessions.get(d);if(this.sessions.delete(d),f&&e.onDisconnect){let p={actor:this,ws:d,meta:f.meta};await e.onDisconnect(p)}}catch{}}cleanupStaleSessions(){let e=0,d=[];for(let[e,f]of this.sessions.entries())e.readyState!==WebSocket.OPEN&&d.push(e);for(let f of d)this.sessions.delete(f),e++;return e}broadcast(e,d,f){let p=0,m=encodeFrame$1({type:`event`,channel:e,data:d}),h=[];for(let{ws:d,meta:g}of this.sessions.values())if(g.channels.includes(e)&&!(f?.except&&d===f.except)&&!(f?.userIds&&!f.userIds.includes(g.userId))&&!(f?.clientIds&&!f.clientIds.includes(g.clientId))){if(d.readyState!==WebSocket.OPEN){h.push(d);continue}try{d.send(m),p++}catch{h.push(d)}}for(let e of h)this.sessions.delete(e);return h.length,p}getSessionCount(){return this.sessions.size}getConnectedUserIds(){let e=new Set;for(let{meta:d}of this.sessions.values())e.add(d.userId);return Array.from(e)}getUserSessions(e){let d=[];for(let{ws:f,meta:p}of this.sessions.values())p.userId===e&&d.push(f);return d}sendToUser(e,d,f){let p=0,m=encodeFrame$1({type:`event`,channel:d,data:f}),h=[];for(let{ws:f,meta:g}of this.sessions.values())if(g.userId===e&&g.channels.includes(d)){if(f.readyState!==WebSocket.OPEN){h.push(f);continue}try{f.send(m),p++}catch{h.push(f)}}for(let e of h)this.sessions.delete(e);return h.length,p}getStorage(){return this.ctx.storage}}return Object.defineProperty(f,`name`,{value:d,writable:!1,configurable:!0}),f}export{PROTOCOL_VERSION,createActorHandler,decodeClientMessage,decodeFrame,decodeServerMessage,defineRoom,encodeClientMessage,encodeFrame,encodeServerMessage,restoreSessions,storeAttachment};
|