verani 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/verani.cjs +1 -1
- package/dist/verani.d.cts +35 -1
- package/dist/verani.d.mts +35 -1
- package/dist/verani.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ export const ChatRoom = createActorHandler(chatRoom);
|
|
|
86
86
|
The three-way relationship:
|
|
87
87
|
1. **Export** in `src/index.ts`: `export { ChatRoom }`
|
|
88
88
|
2. **Class name** in `wrangler.jsonc`: `"class_name": "ChatRoom"`
|
|
89
|
-
3. **Env binding**: Access via `env.
|
|
89
|
+
3. **Env binding**: Access via `env.ChatRoom` in your fetch handler
|
|
90
90
|
|
|
91
91
|
### Client Side
|
|
92
92
|
|
package/dist/verani.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let __cloudflare_actors=require(`@cloudflare/actors`);function defaultExtractMeta(e){let _=crypto.randomUUID(),v=crypto.randomUUID(),y=new URL(e.url).searchParams.get(`channels`);return{userId:_,clientId:v,channels:y?y.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,_){e.serializeAttachment(_)}function restoreSessions(e){let _=0;for(let v of e.ctx.getWebSockets()){let y=v.deserializeAttachment();y&&(e.sessions.set(v,{ws:v,meta:y}),_++)}}function isValidFrame(e){return e&&typeof e==`object`&&typeof e.type==`string`&&(e.channel===void 0||typeof e.channel==`string`)}function decodeFrame(e){try{let _=typeof e==`string`?e:e.toString(),v=JSON.parse(_);return isValidFrame(v)?v:null}catch{return null}}function decodeClientMessage(e){return decodeFrame(e)}function decodeServerMessage(e){return decodeFrame(e)}function encodeFrame(e){try{return JSON.stringify(e)}catch(e){throw Error(`Failed to encode frame: ${e instanceof Error?e.message:`unknown error`}`)}}function encodeClientMessage(e){return encodeFrame(e)}function encodeServerMessage(e){return encodeFrame(e)}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(_){let v=sanitizeToClassName(_.name||_.websocketPath||`VeraniActor`);class x extends __cloudflare_actors.Actor{constructor(...e){super(...e),this.sessions=new Map}static configuration(e){return{locationHint:`me`,sockets:{upgradePath:_.websocketPath,autoResponse:{ping:`ping`,pong:`pong`}}}}async shouldUpgradeWebSocket(e){return!0}async fetch(e){let v=new URL(e.url),y=e.headers.get(`Upgrade`);return v.pathname===_.websocketPath&&y===`websocket`&&await this.shouldUpgradeWebSocket(e)?this.onWebSocketUpgrade(e):this.onRequest(e)}async onInit(){try{restoreSessions(this),_.onHibernationRestore&&this.sessions.size>0&&await _.onHibernationRestore(this)}catch{}}onWebSocketConnect(e,v){try{let b;if(b=_.extractMeta?_.extractMeta(v):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(e,b),this.sessions.set(e,{ws:e,meta:b}),_.onConnect){let v={actor:this,ws:e,meta:b};_.onConnect(v)}}catch(v){if(_.onError)try{_.onError(v,{actor:this,ws:e,meta:{userId:`unknown`,clientId:`unknown`,channels:[]}})}catch{}e.close(1011,`Internal server error`)}}onWebSocketMessage(e,v){let y;try{let b=decodeFrame$1(v);if(y=this.sessions.get(e),!y)return;if(_.onMessage){let v={actor:this,ws:e,meta:y.meta,frame:b};_.onMessage(v,b)}}catch(v){if(_.onError&&y)try{_.onError(v,{actor:this,ws:e,meta:y.meta})}catch{}}}onWebSocketDisconnect(e){try{let v=this.sessions.get(e);if(this.sessions.delete(e),v&&_.onDisconnect){let y={actor:this,ws:e,meta:v.meta};_.onDisconnect(y)}}catch{}}broadcast(e,_,v){let y=0,b=encodeFrame$1({type:`event`,channel:e,data:_});for(let{ws:_,meta:x}of this.sessions.values())if(x.channels.includes(e)&&!(v?.except&&_===v.except)&&!(v?.userIds&&!v.userIds.includes(x.userId))&&!(v?.clientIds&&!v.clientIds.includes(x.clientId)))try{_.send(b),y++}catch{}return y}getSessionCount(){return this.sessions.size}getConnectedUserIds(){let e=new Set;for(let{meta:_}of this.sessions.values())e.add(_.userId);return Array.from(e)}getUserSessions(e){let _=[];for(let{ws:v,meta:y}of this.sessions.values())y.userId===e&&_.push(v);return _}sendToUser(e,_,v){let y=0,b=encodeFrame$1({type:_,data:v});for(let{ws:
|
|
1
|
+
let __cloudflare_actors=require(`@cloudflare/actors`);function defaultExtractMeta(e){let _=crypto.randomUUID(),v=crypto.randomUUID(),y=new URL(e.url).searchParams.get(`channels`);return{userId:_,clientId:v,channels:y?y.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,_){e.serializeAttachment(_)}function restoreSessions(e){let _=0;for(let v of e.ctx.getWebSockets()){let y=v.deserializeAttachment();y&&(e.sessions.set(v,{ws:v,meta:y}),_++)}}function isValidFrame(e){return e&&typeof e==`object`&&typeof e.type==`string`&&(e.channel===void 0||typeof e.channel==`string`)}function decodeFrame(e){try{let _=typeof e==`string`?e:e.toString(),v=JSON.parse(_);return isValidFrame(v)?v:null}catch{return null}}function decodeClientMessage(e){return decodeFrame(e)}function decodeServerMessage(e){return decodeFrame(e)}function encodeFrame(e){try{return JSON.stringify(e)}catch(e){throw Error(`Failed to encode frame: ${e instanceof Error?e.message:`unknown error`}`)}}function encodeClientMessage(e){return encodeFrame(e)}function encodeServerMessage(e){return encodeFrame(e)}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(_){let v=sanitizeToClassName(_.name||_.websocketPath||`VeraniActor`);class x extends __cloudflare_actors.Actor{constructor(...e){super(...e),this.sessions=new Map}static configuration(e){return{locationHint:`me`,sockets:{upgradePath:_.websocketPath,autoResponse:{ping:`ping`,pong:`pong`}}}}async shouldUpgradeWebSocket(e){return!0}async fetch(e){let v=new URL(e.url),y=e.headers.get(`Upgrade`);return v.pathname===_.websocketPath&&y===`websocket`&&await this.shouldUpgradeWebSocket(e)?this.onWebSocketUpgrade(e):this.onRequest(e)}async onInit(){try{restoreSessions(this),_.onHibernationRestore&&this.sessions.size>0&&await _.onHibernationRestore(this)}catch{}}onWebSocketConnect(e,v){try{let b;if(b=_.extractMeta?_.extractMeta(v):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(e,b),this.sessions.set(e,{ws:e,meta:b}),_.onConnect){let v={actor:this,ws:e,meta:b};_.onConnect(v)}}catch(v){if(_.onError)try{_.onError(v,{actor:this,ws:e,meta:{userId:`unknown`,clientId:`unknown`,channels:[]}})}catch{}e.close(1011,`Internal server error`)}}onWebSocketMessage(e,v){let y;try{let b=decodeFrame$1(v);if(y=this.sessions.get(e),!y)return;if(_.onMessage){let v={actor:this,ws:e,meta:y.meta,frame:b};_.onMessage(v,b)}}catch(v){if(_.onError&&y)try{_.onError(v,{actor:this,ws:e,meta:y.meta})}catch{}}}onWebSocketDisconnect(e){try{let v=this.sessions.get(e);if(this.sessions.delete(e),v&&_.onDisconnect){let y={actor:this,ws:e,meta:v.meta};_.onDisconnect(y)}}catch{}}broadcast(e,_,v){let y=0,b=encodeFrame$1({type:`event`,channel:e,data:_});for(let{ws:_,meta:x}of this.sessions.values())if(x.channels.includes(e)&&!(v?.except&&_===v.except)&&!(v?.userIds&&!v.userIds.includes(x.userId))&&!(v?.clientIds&&!v.clientIds.includes(x.clientId)))try{_.send(b),y++}catch{}return y}getSessionCount(){return this.sessions.size}getConnectedUserIds(){let e=new Set;for(let{meta:_}of this.sessions.values())e.add(_.userId);return Array.from(e)}getUserSessions(e){let _=[];for(let{ws:v,meta:y}of this.sessions.values())y.userId===e&&_.push(v);return _}sendToUser(e,_,v){let y=0,b=encodeFrame$1({type:`event`,channel:_,data:v});for(let{ws:v,meta:x}of this.sessions.values())if(x.userId===e&&x.channels.includes(_))try{v.send(b),y++}catch{}return y}getStorage(){return this.ctx.storage}}return Object.defineProperty(x,`name`,{value:v,writable:!1,configurable:!0}),x}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,_){this.config=e,this.onStateChange=_,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,_={}){this.url=e,this.listeners=new Map,this.messageQueue=[],this.options={reconnection:{enabled:_.reconnection?.enabled??DEFAULT_RECONNECTION_CONFIG.enabled,maxAttempts:_.reconnection?.maxAttempts??DEFAULT_RECONNECTION_CONFIG.maxAttempts,initialDelay:_.reconnection?.initialDelay??DEFAULT_RECONNECTION_CONFIG.initialDelay,maxDelay:_.reconnection?.maxDelay??DEFAULT_RECONNECTION_CONFIG.maxDelay,backoffMultiplier:_.reconnection?.backoffMultiplier??DEFAULT_RECONNECTION_CONFIG.backoffMultiplier},maxQueueSize:_.maxQueueSize??100,connectionTimeout:_.connectionTimeout??1e4},this.connectionManager=new ConnectionManager(this.options.reconnection,e=>{this.onStateChangeCallback?.(e)}),this.connect()}connect(){try{this.connectionManager.setState(`connecting`),this.ws=new WebSocket(this.url);let e=setTimeout(()=>{this.connectionManager.getState()===`connecting`&&(this.ws?.close(),this.handleConnectionError(Error(`Connection timeout`)))},this.options.connectionTimeout);this.ws.addEventListener(`open`,()=>{clearTimeout(e),this.handleOpen()}),this.ws.addEventListener(`message`,e=>{this.handleMessage(e)}),this.ws.addEventListener(`close`,_=>{clearTimeout(e),this.handleClose(_)}),this.ws.addEventListener(`error`,_=>{clearTimeout(e),this.handleError(_)})}catch(e){this.handleConnectionError(e)}}handleOpen(){this.connectionManager.setState(`connected`),this.connectionManager.resetReconnection(),this.flushMessageQueue(),this.connectionResolve&&(this.connectionResolve(),this.connectionPromise=void 0,this.connectionResolve=void 0,this.connectionReject=void 0),this.onOpenCallback?.()}handleMessage(e){let _=decodeServerMessage$1(e.data);if(!_)return;let v=_.type,y=_.data;_.type===`event`&&_.data&&typeof _.data==`object`&&`type`in _.data&&(v=_.data.type,y=_.data);let b=this.listeners.get(v);if(b)for(let e of b)try{e(y)}catch{}}handleClose(e){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.onCloseCallback?.(e),e.code!==1e3&&e.code!==1001&&this.connectionManager.scheduleReconnect(()=>this.connect())}handleError(e){this.onErrorCallback?.(e)}handleConnectionError(e){this.connectionReject&&=(this.connectionReject(e),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.connectionManager.scheduleReconnect(()=>this.connect())}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}waitForConnection(){return this.isConnected()?Promise.resolve():(this.connectionPromise||=new Promise((e,_)=>{this.connectionResolve=e,this.connectionReject=_}),this.connectionPromise)}on(e,_){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(_)}off(e,_){let v=this.listeners.get(e);v&&(v.delete(_),v.size===0&&this.listeners.delete(e))}once(e,_){let v=y=>{this.off(e,v),_(y)};this.on(e,v)}emit(e,_){let v={type:e,data:_};if(this.isConnected())try{this.ws.send(encodeClientMessage$1(v))}catch{this.queueMessage(v)}else this.queueMessage(v)}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.disconnect(),this.connect()}disconnect(){this.connectionManager.cancelReconnect(),this.ws&&=(this.ws.close(1e3,`Client disconnect`),void 0)}close(){this.disconnect(),this.listeners.clear(),this.messageQueue=[],this.connectionManager.destroy()}};const PROTOCOL_VERSION=`1.0.0`;exports.ConnectionManager=ConnectionManager,exports.DEFAULT_RECONNECTION_CONFIG=DEFAULT_RECONNECTION_CONFIG,exports.PROTOCOL_VERSION=`1.0.0`,exports.VeraniClient=VeraniClient,exports.createActorHandler=createActorHandler,exports.decodeClientMessage=decodeClientMessage,exports.decodeFrame=decodeFrame,exports.decodeServerMessage=decodeServerMessage,exports.defineRoom=defineRoom,exports.encodeClientMessage=encodeClientMessage,exports.encodeFrame=encodeFrame,exports.encodeServerMessage=encodeServerMessage,exports.restoreSessions=restoreSessions,exports.storeAttachment=storeAttachment;
|
package/dist/verani.d.cts
CHANGED
|
@@ -62,15 +62,49 @@ interface BroadcastOptions {
|
|
|
62
62
|
* Extended Actor interface with Verani-specific methods
|
|
63
63
|
*/
|
|
64
64
|
interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends Actor<E> {
|
|
65
|
+
/**
|
|
66
|
+
* Map of active WebSocket sessions keyed by their WebSocket instance.
|
|
67
|
+
* Each entry contains the WebSocket and its associated metadata.
|
|
68
|
+
* See: @src/actor/actor-runtime.ts usage for session management.
|
|
69
|
+
*/
|
|
65
70
|
sessions: Map<WebSocket, {
|
|
66
71
|
ws: WebSocket;
|
|
67
72
|
meta: TMeta;
|
|
68
73
|
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Broadcast a message to all connections in a channel.
|
|
76
|
+
* Performs channel, userId, clientId, and exclusion filtering.
|
|
77
|
+
* Returns the number of connections the message was sent to.
|
|
78
|
+
* @see @src/actor/actor-runtime.ts broadcast()
|
|
79
|
+
*/
|
|
69
80
|
broadcast(channel: string, data: any, opts?: BroadcastOptions): number;
|
|
81
|
+
/**
|
|
82
|
+
* Returns the number of currently connected WebSocket sessions.
|
|
83
|
+
* @see @src/actor/actor-runtime.ts getSessionCount()
|
|
84
|
+
*/
|
|
70
85
|
getSessionCount(): number;
|
|
86
|
+
/**
|
|
87
|
+
* Get all unique user IDs currently connected to this actor.
|
|
88
|
+
* @see @src/actor/actor-runtime.ts getConnectedUserIds()
|
|
89
|
+
*/
|
|
71
90
|
getConnectedUserIds(): string[];
|
|
91
|
+
/**
|
|
92
|
+
* Get all WebSocket sessions for a given user ID.
|
|
93
|
+
* @see @src/actor/actor-runtime.ts getUserSessions()
|
|
94
|
+
*/
|
|
72
95
|
getUserSessions(userId: string): WebSocket[];
|
|
73
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Send a message to all sessions belonging to a user ID in a given channel.
|
|
98
|
+
* Message will only be sent to sessions where the user's channels include the given channel.
|
|
99
|
+
* Returns the number of sessions the message was sent to.
|
|
100
|
+
* The message "type" is always "event" (see src/actor/actor-runtime.ts).
|
|
101
|
+
* @see @src/actor/actor-runtime.ts sendToUser()
|
|
102
|
+
*/
|
|
103
|
+
sendToUser(userId: string, channel: string, data?: any): number;
|
|
104
|
+
/**
|
|
105
|
+
* Access the Durable Object storage API for this actor instance.
|
|
106
|
+
* @see @src/actor/actor-runtime.ts getStorage()
|
|
107
|
+
*/
|
|
74
108
|
getStorage(): DurableObjectStorage;
|
|
75
109
|
}
|
|
76
110
|
/**
|
package/dist/verani.d.mts
CHANGED
|
@@ -62,15 +62,49 @@ interface BroadcastOptions {
|
|
|
62
62
|
* Extended Actor interface with Verani-specific methods
|
|
63
63
|
*/
|
|
64
64
|
interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends Actor<E> {
|
|
65
|
+
/**
|
|
66
|
+
* Map of active WebSocket sessions keyed by their WebSocket instance.
|
|
67
|
+
* Each entry contains the WebSocket and its associated metadata.
|
|
68
|
+
* See: @src/actor/actor-runtime.ts usage for session management.
|
|
69
|
+
*/
|
|
65
70
|
sessions: Map<WebSocket, {
|
|
66
71
|
ws: WebSocket;
|
|
67
72
|
meta: TMeta;
|
|
68
73
|
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Broadcast a message to all connections in a channel.
|
|
76
|
+
* Performs channel, userId, clientId, and exclusion filtering.
|
|
77
|
+
* Returns the number of connections the message was sent to.
|
|
78
|
+
* @see @src/actor/actor-runtime.ts broadcast()
|
|
79
|
+
*/
|
|
69
80
|
broadcast(channel: string, data: any, opts?: BroadcastOptions): number;
|
|
81
|
+
/**
|
|
82
|
+
* Returns the number of currently connected WebSocket sessions.
|
|
83
|
+
* @see @src/actor/actor-runtime.ts getSessionCount()
|
|
84
|
+
*/
|
|
70
85
|
getSessionCount(): number;
|
|
86
|
+
/**
|
|
87
|
+
* Get all unique user IDs currently connected to this actor.
|
|
88
|
+
* @see @src/actor/actor-runtime.ts getConnectedUserIds()
|
|
89
|
+
*/
|
|
71
90
|
getConnectedUserIds(): string[];
|
|
91
|
+
/**
|
|
92
|
+
* Get all WebSocket sessions for a given user ID.
|
|
93
|
+
* @see @src/actor/actor-runtime.ts getUserSessions()
|
|
94
|
+
*/
|
|
72
95
|
getUserSessions(userId: string): WebSocket[];
|
|
73
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Send a message to all sessions belonging to a user ID in a given channel.
|
|
98
|
+
* Message will only be sent to sessions where the user's channels include the given channel.
|
|
99
|
+
* Returns the number of sessions the message was sent to.
|
|
100
|
+
* The message "type" is always "event" (see src/actor/actor-runtime.ts).
|
|
101
|
+
* @see @src/actor/actor-runtime.ts sendToUser()
|
|
102
|
+
*/
|
|
103
|
+
sendToUser(userId: string, channel: string, data?: any): number;
|
|
104
|
+
/**
|
|
105
|
+
* Access the Durable Object storage API for this actor instance.
|
|
106
|
+
* @see @src/actor/actor-runtime.ts getStorage()
|
|
107
|
+
*/
|
|
74
108
|
getStorage(): DurableObjectStorage;
|
|
75
109
|
}
|
|
76
110
|
/**
|
package/dist/verani.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Actor}from"@cloudflare/actors";function defaultExtractMeta(e){let _=crypto.randomUUID(),v=crypto.randomUUID(),y=new URL(e.url).searchParams.get(`channels`);return{userId:_,clientId:v,channels:y?y.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,_){e.serializeAttachment(_)}function restoreSessions(e){let _=0;for(let v of e.ctx.getWebSockets()){let y=v.deserializeAttachment();y&&(e.sessions.set(v,{ws:v,meta:y}),_++)}}function isValidFrame(e){return e&&typeof e==`object`&&typeof e.type==`string`&&(e.channel===void 0||typeof e.channel==`string`)}function decodeFrame(e){try{let _=typeof e==`string`?e:e.toString(),v=JSON.parse(_);return isValidFrame(v)?v:null}catch{return null}}function decodeClientMessage(e){return decodeFrame(e)}function decodeServerMessage(e){return decodeFrame(e)}function encodeFrame(e){try{return JSON.stringify(e)}catch(e){throw Error(`Failed to encode frame: ${e instanceof Error?e.message:`unknown error`}`)}}function encodeClientMessage(e){return encodeFrame(e)}function encodeServerMessage(e){return encodeFrame(e)}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(_){let v=sanitizeToClassName(_.name||_.websocketPath||`VeraniActor`);class x extends Actor{constructor(...e){super(...e),this.sessions=new Map}static configuration(e){return{locationHint:`me`,sockets:{upgradePath:_.websocketPath,autoResponse:{ping:`ping`,pong:`pong`}}}}async shouldUpgradeWebSocket(e){return!0}async fetch(e){let v=new URL(e.url),y=e.headers.get(`Upgrade`);return v.pathname===_.websocketPath&&y===`websocket`&&await this.shouldUpgradeWebSocket(e)?this.onWebSocketUpgrade(e):this.onRequest(e)}async onInit(){try{restoreSessions(this),_.onHibernationRestore&&this.sessions.size>0&&await _.onHibernationRestore(this)}catch{}}onWebSocketConnect(e,v){try{let b;if(b=_.extractMeta?_.extractMeta(v):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(e,b),this.sessions.set(e,{ws:e,meta:b}),_.onConnect){let v={actor:this,ws:e,meta:b};_.onConnect(v)}}catch(v){if(_.onError)try{_.onError(v,{actor:this,ws:e,meta:{userId:`unknown`,clientId:`unknown`,channels:[]}})}catch{}e.close(1011,`Internal server error`)}}onWebSocketMessage(e,v){let y;try{let b=decodeFrame$1(v);if(y=this.sessions.get(e),!y)return;if(_.onMessage){let v={actor:this,ws:e,meta:y.meta,frame:b};_.onMessage(v,b)}}catch(v){if(_.onError&&y)try{_.onError(v,{actor:this,ws:e,meta:y.meta})}catch{}}}onWebSocketDisconnect(e){try{let v=this.sessions.get(e);if(this.sessions.delete(e),v&&_.onDisconnect){let y={actor:this,ws:e,meta:v.meta};_.onDisconnect(y)}}catch{}}broadcast(e,_,v){let y=0,b=encodeFrame$1({type:`event`,channel:e,data:_});for(let{ws:_,meta:x}of this.sessions.values())if(x.channels.includes(e)&&!(v?.except&&_===v.except)&&!(v?.userIds&&!v.userIds.includes(x.userId))&&!(v?.clientIds&&!v.clientIds.includes(x.clientId)))try{_.send(b),y++}catch{}return y}getSessionCount(){return this.sessions.size}getConnectedUserIds(){let e=new Set;for(let{meta:_}of this.sessions.values())e.add(_.userId);return Array.from(e)}getUserSessions(e){let _=[];for(let{ws:v,meta:y}of this.sessions.values())y.userId===e&&_.push(v);return _}sendToUser(e,_,v){let y=0,b=encodeFrame$1({type:_,data:v});for(let{ws:
|
|
1
|
+
import{Actor}from"@cloudflare/actors";function defaultExtractMeta(e){let _=crypto.randomUUID(),v=crypto.randomUUID(),y=new URL(e.url).searchParams.get(`channels`);return{userId:_,clientId:v,channels:y?y.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,_){e.serializeAttachment(_)}function restoreSessions(e){let _=0;for(let v of e.ctx.getWebSockets()){let y=v.deserializeAttachment();y&&(e.sessions.set(v,{ws:v,meta:y}),_++)}}function isValidFrame(e){return e&&typeof e==`object`&&typeof e.type==`string`&&(e.channel===void 0||typeof e.channel==`string`)}function decodeFrame(e){try{let _=typeof e==`string`?e:e.toString(),v=JSON.parse(_);return isValidFrame(v)?v:null}catch{return null}}function decodeClientMessage(e){return decodeFrame(e)}function decodeServerMessage(e){return decodeFrame(e)}function encodeFrame(e){try{return JSON.stringify(e)}catch(e){throw Error(`Failed to encode frame: ${e instanceof Error?e.message:`unknown error`}`)}}function encodeClientMessage(e){return encodeFrame(e)}function encodeServerMessage(e){return encodeFrame(e)}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(_){let v=sanitizeToClassName(_.name||_.websocketPath||`VeraniActor`);class x extends Actor{constructor(...e){super(...e),this.sessions=new Map}static configuration(e){return{locationHint:`me`,sockets:{upgradePath:_.websocketPath,autoResponse:{ping:`ping`,pong:`pong`}}}}async shouldUpgradeWebSocket(e){return!0}async fetch(e){let v=new URL(e.url),y=e.headers.get(`Upgrade`);return v.pathname===_.websocketPath&&y===`websocket`&&await this.shouldUpgradeWebSocket(e)?this.onWebSocketUpgrade(e):this.onRequest(e)}async onInit(){try{restoreSessions(this),_.onHibernationRestore&&this.sessions.size>0&&await _.onHibernationRestore(this)}catch{}}onWebSocketConnect(e,v){try{let b;if(b=_.extractMeta?_.extractMeta(v):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(e,b),this.sessions.set(e,{ws:e,meta:b}),_.onConnect){let v={actor:this,ws:e,meta:b};_.onConnect(v)}}catch(v){if(_.onError)try{_.onError(v,{actor:this,ws:e,meta:{userId:`unknown`,clientId:`unknown`,channels:[]}})}catch{}e.close(1011,`Internal server error`)}}onWebSocketMessage(e,v){let y;try{let b=decodeFrame$1(v);if(y=this.sessions.get(e),!y)return;if(_.onMessage){let v={actor:this,ws:e,meta:y.meta,frame:b};_.onMessage(v,b)}}catch(v){if(_.onError&&y)try{_.onError(v,{actor:this,ws:e,meta:y.meta})}catch{}}}onWebSocketDisconnect(e){try{let v=this.sessions.get(e);if(this.sessions.delete(e),v&&_.onDisconnect){let y={actor:this,ws:e,meta:v.meta};_.onDisconnect(y)}}catch{}}broadcast(e,_,v){let y=0,b=encodeFrame$1({type:`event`,channel:e,data:_});for(let{ws:_,meta:x}of this.sessions.values())if(x.channels.includes(e)&&!(v?.except&&_===v.except)&&!(v?.userIds&&!v.userIds.includes(x.userId))&&!(v?.clientIds&&!v.clientIds.includes(x.clientId)))try{_.send(b),y++}catch{}return y}getSessionCount(){return this.sessions.size}getConnectedUserIds(){let e=new Set;for(let{meta:_}of this.sessions.values())e.add(_.userId);return Array.from(e)}getUserSessions(e){let _=[];for(let{ws:v,meta:y}of this.sessions.values())y.userId===e&&_.push(v);return _}sendToUser(e,_,v){let y=0,b=encodeFrame$1({type:`event`,channel:_,data:v});for(let{ws:v,meta:x}of this.sessions.values())if(x.userId===e&&x.channels.includes(_))try{v.send(b),y++}catch{}return y}getStorage(){return this.ctx.storage}}return Object.defineProperty(x,`name`,{value:v,writable:!1,configurable:!0}),x}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,_){this.config=e,this.onStateChange=_,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,_={}){this.url=e,this.listeners=new Map,this.messageQueue=[],this.options={reconnection:{enabled:_.reconnection?.enabled??DEFAULT_RECONNECTION_CONFIG.enabled,maxAttempts:_.reconnection?.maxAttempts??DEFAULT_RECONNECTION_CONFIG.maxAttempts,initialDelay:_.reconnection?.initialDelay??DEFAULT_RECONNECTION_CONFIG.initialDelay,maxDelay:_.reconnection?.maxDelay??DEFAULT_RECONNECTION_CONFIG.maxDelay,backoffMultiplier:_.reconnection?.backoffMultiplier??DEFAULT_RECONNECTION_CONFIG.backoffMultiplier},maxQueueSize:_.maxQueueSize??100,connectionTimeout:_.connectionTimeout??1e4},this.connectionManager=new ConnectionManager(this.options.reconnection,e=>{this.onStateChangeCallback?.(e)}),this.connect()}connect(){try{this.connectionManager.setState(`connecting`),this.ws=new WebSocket(this.url);let e=setTimeout(()=>{this.connectionManager.getState()===`connecting`&&(this.ws?.close(),this.handleConnectionError(Error(`Connection timeout`)))},this.options.connectionTimeout);this.ws.addEventListener(`open`,()=>{clearTimeout(e),this.handleOpen()}),this.ws.addEventListener(`message`,e=>{this.handleMessage(e)}),this.ws.addEventListener(`close`,_=>{clearTimeout(e),this.handleClose(_)}),this.ws.addEventListener(`error`,_=>{clearTimeout(e),this.handleError(_)})}catch(e){this.handleConnectionError(e)}}handleOpen(){this.connectionManager.setState(`connected`),this.connectionManager.resetReconnection(),this.flushMessageQueue(),this.connectionResolve&&(this.connectionResolve(),this.connectionPromise=void 0,this.connectionResolve=void 0,this.connectionReject=void 0),this.onOpenCallback?.()}handleMessage(e){let _=decodeServerMessage$1(e.data);if(!_)return;let v=_.type,y=_.data;_.type===`event`&&_.data&&typeof _.data==`object`&&`type`in _.data&&(v=_.data.type,y=_.data);let b=this.listeners.get(v);if(b)for(let e of b)try{e(y)}catch{}}handleClose(e){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.onCloseCallback?.(e),e.code!==1e3&&e.code!==1001&&this.connectionManager.scheduleReconnect(()=>this.connect())}handleError(e){this.onErrorCallback?.(e)}handleConnectionError(e){this.connectionReject&&=(this.connectionReject(e),this.connectionPromise=void 0,this.connectionResolve=void 0,void 0),this.connectionManager.scheduleReconnect(()=>this.connect())}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}waitForConnection(){return this.isConnected()?Promise.resolve():(this.connectionPromise||=new Promise((e,_)=>{this.connectionResolve=e,this.connectionReject=_}),this.connectionPromise)}on(e,_){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(_)}off(e,_){let v=this.listeners.get(e);v&&(v.delete(_),v.size===0&&this.listeners.delete(e))}once(e,_){let v=y=>{this.off(e,v),_(y)};this.on(e,v)}emit(e,_){let v={type:e,data:_};if(this.isConnected())try{this.ws.send(encodeClientMessage$1(v))}catch{this.queueMessage(v)}else this.queueMessage(v)}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.disconnect(),this.connect()}disconnect(){this.connectionManager.cancelReconnect(),this.ws&&=(this.ws.close(1e3,`Client disconnect`),void 0)}close(){this.disconnect(),this.listeners.clear(),this.messageQueue=[],this.connectionManager.destroy()}};const PROTOCOL_VERSION=`1.0.0`;export{ConnectionManager,DEFAULT_RECONNECTION_CONFIG,PROTOCOL_VERSION,VeraniClient,createActorHandler,decodeClientMessage,decodeFrame,decodeServerMessage,defineRoom,encodeClientMessage,encodeFrame,encodeServerMessage,restoreSessions,storeAttachment};
|