verani 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actor-runtime-CbquUZdU.d.mts +345 -0
- package/dist/actor-runtime-PyNnwtah.mjs +1 -0
- package/dist/actor-runtime-crxBqW3H.d.cts +345 -0
- package/dist/actor-runtime-v4YHMnw9.cjs +1 -0
- package/dist/client-8ubwq_k4.d.mts +204 -0
- package/dist/client-BNppil3J.d.cts +204 -0
- package/dist/client-DaUp7q-P.mjs +1 -0
- package/dist/client-ofEyep6Z.cjs +1 -0
- package/dist/client.cjs +1 -1
- package/dist/client.d.cts +3 -199
- package/dist/client.d.mts +3 -199
- package/dist/client.mjs +1 -1
- package/dist/{decode-9DerwlQ1.d.mts → decode-Cetz5jkZ.d.cts} +4 -46
- package/dist/{decode-DMA_rBWB.d.cts → decode-Cs8CEYAY.d.mts} +4 -46
- package/dist/{types-CJLnZrA8.mjs → encode-BEipaz10.mjs} +1 -1
- package/dist/encode-BYFW_6TI.cjs +1 -0
- package/dist/typed-client.cjs +1 -0
- package/dist/typed-client.d.cts +165 -0
- package/dist/typed-client.d.mts +165 -0
- package/dist/typed-client.mjs +1 -0
- package/dist/typed.cjs +1 -0
- package/dist/typed.d.cts +186 -0
- package/dist/typed.d.mts +186 -0
- package/dist/typed.mjs +1 -0
- package/dist/types-B5NT7bya.mjs +1 -0
- package/dist/types-Bs7hBgfQ.d.mts +46 -0
- package/dist/types-DUO6RVw1.cjs +1 -0
- package/dist/types-cugB3Mcz.d.cts +46 -0
- package/dist/validation-CSmvG882.d.cts +383 -0
- package/dist/validation-D1xWZI_t.d.mts +383 -0
- package/dist/validation-DRz-ayMa.mjs +1 -0
- package/dist/validation-DvWPGkXK.cjs +1 -0
- package/dist/verani.cjs +1 -1
- package/dist/verani.d.cts +3 -343
- package/dist/verani.d.mts +3 -343
- package/dist/verani.mjs +1 -1
- package/package.json +22 -1
- package/dist/types-083oWz55.cjs +0 -1
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { n as ConnectionMeta, r as MessageFrame } from "./types-Bs7hBgfQ.mjs";
|
|
2
|
+
import { Actor, ActorConfiguration } from "@cloudflare/actors";
|
|
3
|
+
|
|
4
|
+
//#region src/actor/types.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for broadcasting messages to connections
|
|
8
|
+
*/
|
|
9
|
+
interface BroadcastOptions {
|
|
10
|
+
/** Exclude specific WebSocket from receiving the broadcast */
|
|
11
|
+
except?: WebSocket;
|
|
12
|
+
/** Only send to specific user IDs */
|
|
13
|
+
userIds?: string[];
|
|
14
|
+
/** Only send to specific client IDs */
|
|
15
|
+
clientIds?: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* RPC-safe version of BroadcastOptions for use over RPC calls.
|
|
19
|
+
* Excludes the `except` field since WebSocket cannot be serialized over RPC.
|
|
20
|
+
*/
|
|
21
|
+
interface RpcBroadcastOptions {
|
|
22
|
+
/** Only send to specific user IDs */
|
|
23
|
+
userIds?: string[];
|
|
24
|
+
/** Only send to specific client IDs */
|
|
25
|
+
clientIds?: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Actor stub interface returned by .get() method.
|
|
29
|
+
* Provides RPC access to actor methods that can be called remotely.
|
|
30
|
+
*
|
|
31
|
+
* Note: RPC methods return Promises even if the underlying method is synchronous.
|
|
32
|
+
* Methods that return non-serializable types (like WebSocket[] or DurableObjectStorage)
|
|
33
|
+
* are excluded from this interface.
|
|
34
|
+
*/
|
|
35
|
+
interface ActorStub {
|
|
36
|
+
/**
|
|
37
|
+
* Standard fetch method for handling HTTP requests and WebSocket upgrades
|
|
38
|
+
*/
|
|
39
|
+
fetch(request: Request): Promise<Response>;
|
|
40
|
+
/**
|
|
41
|
+
* Socket.IO-like emit API: Emit an event to a specific channel via RPC.
|
|
42
|
+
* @param channel - Channel name to emit to
|
|
43
|
+
* @param event - Event name
|
|
44
|
+
* @param data - Event data
|
|
45
|
+
* @returns Promise resolving to the number of connections that received the message
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* await stub.emitToChannel("default", "announcement", { text: "Hello!" });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
emitToChannel(channel: string, event: string, data?: any): Promise<number>;
|
|
52
|
+
/**
|
|
53
|
+
* Socket.IO-like emit API: Emit an event to a specific user (all their sessions) via RPC.
|
|
54
|
+
* @param userId - User ID to emit to
|
|
55
|
+
* @param event - Event name
|
|
56
|
+
* @param data - Event data
|
|
57
|
+
* @returns Promise resolving to the number of sessions that received the message
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* await stub.emitToUser("alice", "notification", { message: "Hello!" });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
emitToUser(userId: string, event: string, data?: any): Promise<number>;
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Use `emitToUser()` instead for Socket.IO-like API.
|
|
66
|
+
* Sends a message to a specific user (all their sessions) via RPC.
|
|
67
|
+
* @param userId - The user ID to send to
|
|
68
|
+
* @param channel - The channel to send to
|
|
69
|
+
* @param data - Message data
|
|
70
|
+
* @returns Promise resolving to the number of sessions that received the message
|
|
71
|
+
*/
|
|
72
|
+
sendToUser(userId: string, channel: string, data?: any): Promise<number>;
|
|
73
|
+
/**
|
|
74
|
+
* @deprecated Use `emitToChannel()` instead for Socket.IO-like API.
|
|
75
|
+
* Broadcasts a message to all connections in a channel via RPC.
|
|
76
|
+
* Note: The `except` option from BroadcastOptions is not available over RPC
|
|
77
|
+
* since WebSocket cannot be serialized.
|
|
78
|
+
* @param channel - The channel to broadcast to
|
|
79
|
+
* @param data - The data to send
|
|
80
|
+
* @param opts - Broadcast options (filtering by userIds or clientIds)
|
|
81
|
+
* @returns Promise resolving to the number of connections that received the message
|
|
82
|
+
*/
|
|
83
|
+
broadcast(channel: string, data: any, opts?: RpcBroadcastOptions): Promise<number>;
|
|
84
|
+
/**
|
|
85
|
+
* Gets the total number of active sessions via RPC.
|
|
86
|
+
* @returns Promise resolving to the number of connected WebSockets
|
|
87
|
+
*/
|
|
88
|
+
getSessionCount(): Promise<number>;
|
|
89
|
+
/**
|
|
90
|
+
* Gets all unique user IDs currently connected via RPC.
|
|
91
|
+
* @returns Promise resolving to an array of unique user IDs
|
|
92
|
+
*/
|
|
93
|
+
getConnectedUserIds(): Promise<string[]>;
|
|
94
|
+
/**
|
|
95
|
+
* Removes all WebSocket sessions that are not in OPEN state via RPC.
|
|
96
|
+
* This prevents stale connections from accumulating in memory.
|
|
97
|
+
* @returns Promise resolving to the number of sessions cleaned up
|
|
98
|
+
*/
|
|
99
|
+
cleanupStaleSessions(): Promise<number>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Extended Actor interface with Verani-specific methods
|
|
103
|
+
*/
|
|
104
|
+
interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends Actor<E> {
|
|
105
|
+
/**
|
|
106
|
+
* Map of active WebSocket sessions keyed by their WebSocket instance.
|
|
107
|
+
* Each entry contains the WebSocket and its associated metadata.
|
|
108
|
+
* See: @src/actor/actor-runtime.ts usage for session management.
|
|
109
|
+
*/
|
|
110
|
+
sessions: Map<WebSocket, {
|
|
111
|
+
ws: WebSocket;
|
|
112
|
+
meta: TMeta;
|
|
113
|
+
}>;
|
|
114
|
+
/**
|
|
115
|
+
* Broadcast a message to all connections in a channel.
|
|
116
|
+
* Performs channel, userId, clientId, and exclusion filtering.
|
|
117
|
+
* Returns the number of connections the message was sent to.
|
|
118
|
+
* @see @src/actor/actor-runtime.ts broadcast()
|
|
119
|
+
*/
|
|
120
|
+
broadcast(channel: string, data: any, opts?: BroadcastOptions): number;
|
|
121
|
+
/**
|
|
122
|
+
* Returns the number of currently connected WebSocket sessions.
|
|
123
|
+
* @see @src/actor/actor-runtime.ts getSessionCount()
|
|
124
|
+
*/
|
|
125
|
+
getSessionCount(): number;
|
|
126
|
+
/**
|
|
127
|
+
* Get all unique user IDs currently connected to this actor.
|
|
128
|
+
* @see @src/actor/actor-runtime.ts getConnectedUserIds()
|
|
129
|
+
*/
|
|
130
|
+
getConnectedUserIds(): string[];
|
|
131
|
+
/**
|
|
132
|
+
* Get all WebSocket sessions for a given user ID.
|
|
133
|
+
* @see @src/actor/actor-runtime.ts getUserSessions()
|
|
134
|
+
*/
|
|
135
|
+
getUserSessions(userId: string): WebSocket[];
|
|
136
|
+
/**
|
|
137
|
+
* Send a message to all sessions belonging to a user ID in a given channel.
|
|
138
|
+
* Message will only be sent to sessions where the user's channels include the given channel.
|
|
139
|
+
* Returns the number of sessions the message was sent to.
|
|
140
|
+
* The message "type" is always "event" (see src/actor/actor-runtime.ts).
|
|
141
|
+
* @see @src/actor/actor-runtime.ts sendToUser()
|
|
142
|
+
*/
|
|
143
|
+
sendToUser(userId: string, channel: string, data?: any): number;
|
|
144
|
+
/**
|
|
145
|
+
* Validates and removes stale WebSocket sessions.
|
|
146
|
+
* Called automatically during broadcast/send operations, but can be called manually.
|
|
147
|
+
* Returns the number of stale sessions removed.
|
|
148
|
+
* @see @src/actor/actor-runtime.ts cleanupStaleSessions()
|
|
149
|
+
*/
|
|
150
|
+
cleanupStaleSessions(): number;
|
|
151
|
+
/**
|
|
152
|
+
* Access the Durable Object storage API for this actor instance.
|
|
153
|
+
* @see @src/actor/actor-runtime.ts getStorage()
|
|
154
|
+
*/
|
|
155
|
+
getStorage(): DurableObjectStorage;
|
|
156
|
+
/**
|
|
157
|
+
* Socket.io-like emit API for actor-level broadcasting
|
|
158
|
+
*/
|
|
159
|
+
emit: ActorEmit<TMeta, E>;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Event handler function type for socket.io-like event handling
|
|
163
|
+
*/
|
|
164
|
+
type EventHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> = (ctx: MessageContext<TMeta, E>, data: any) => void | Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* Event emitter interface for room-level event handling
|
|
167
|
+
*/
|
|
168
|
+
interface RoomEventEmitter<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
|
|
169
|
+
/**
|
|
170
|
+
* Register an event handler
|
|
171
|
+
* @param event - Event name (supports wildcard "*")
|
|
172
|
+
* @param handler - Handler function
|
|
173
|
+
*/
|
|
174
|
+
on(event: string, handler: EventHandler<TMeta, E>): void;
|
|
175
|
+
/**
|
|
176
|
+
* Remove an event handler
|
|
177
|
+
* @param event - Event name
|
|
178
|
+
* @param handler - Optional specific handler to remove, or remove all handlers for event
|
|
179
|
+
*/
|
|
180
|
+
off(event: string, handler?: EventHandler<TMeta, E>): void;
|
|
181
|
+
/**
|
|
182
|
+
* Emit an event to registered handlers
|
|
183
|
+
* @param event - Event name
|
|
184
|
+
* @param ctx - Message context
|
|
185
|
+
* @param data - Event data
|
|
186
|
+
*/
|
|
187
|
+
emit(event: string, ctx: MessageContext<TMeta, E>, data: any): Promise<void>;
|
|
188
|
+
/**
|
|
189
|
+
* Rebuild handlers from static storage.
|
|
190
|
+
* Called after hibernation to restore handlers from the room definition.
|
|
191
|
+
* This method MUST be implemented by all event emitter implementations.
|
|
192
|
+
* @param staticHandlers - Map of event names to handler sets from static storage
|
|
193
|
+
*/
|
|
194
|
+
rebuildHandlers(staticHandlers: Map<string, Set<EventHandler<TMeta, E>>>): void;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Builder interface for targeting specific scopes when emitting
|
|
198
|
+
*/
|
|
199
|
+
interface EmitBuilder<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
|
|
200
|
+
/**
|
|
201
|
+
* Emit to the targeted scope
|
|
202
|
+
* @param event - Event name
|
|
203
|
+
* @param data - Event data
|
|
204
|
+
* @returns Number of connections that received the message
|
|
205
|
+
*/
|
|
206
|
+
emit(event: string, data?: any): number;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Socket-level emit API (available on context)
|
|
210
|
+
* Allows emitting to current socket, user, or channel
|
|
211
|
+
*/
|
|
212
|
+
interface SocketEmit<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
|
|
213
|
+
/**
|
|
214
|
+
* Emit to the current socket
|
|
215
|
+
* @param event - Event name
|
|
216
|
+
* @param data - Event data
|
|
217
|
+
*/
|
|
218
|
+
emit(event: string, data?: any): void;
|
|
219
|
+
/**
|
|
220
|
+
* Target a specific user or channel for emitting
|
|
221
|
+
* @param target - User ID or channel name
|
|
222
|
+
* @returns Builder for emitting to the target
|
|
223
|
+
*/
|
|
224
|
+
to(target: string): EmitBuilder<TMeta, E>;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Actor-level emit API (available on actor)
|
|
228
|
+
* Allows broadcasting to channels
|
|
229
|
+
*/
|
|
230
|
+
interface ActorEmit<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
|
|
231
|
+
/**
|
|
232
|
+
* Broadcast to default channel
|
|
233
|
+
* @param event - Event name
|
|
234
|
+
* @param data - Event data
|
|
235
|
+
* @returns Number of connections that received the message
|
|
236
|
+
*/
|
|
237
|
+
emit(event: string, data?: any): number;
|
|
238
|
+
/**
|
|
239
|
+
* Target a specific channel for broadcasting
|
|
240
|
+
* @param channel - Channel name
|
|
241
|
+
* @returns Builder for emitting to the channel
|
|
242
|
+
*/
|
|
243
|
+
to(channel: string): EmitBuilder<TMeta, E>;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Context provided to room lifecycle hooks
|
|
247
|
+
*/
|
|
248
|
+
interface RoomContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
|
|
249
|
+
/** The actor instance handling this connection */
|
|
250
|
+
actor: VeraniActor<TMeta, E>;
|
|
251
|
+
/** The WebSocket connection */
|
|
252
|
+
ws: WebSocket;
|
|
253
|
+
/** Connection metadata */
|
|
254
|
+
meta: TMeta;
|
|
255
|
+
/** Socket.io-like emit API for this connection */
|
|
256
|
+
emit: SocketEmit<TMeta, E>;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Context for onMessage hook with frame included
|
|
260
|
+
*/
|
|
261
|
+
interface MessageContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends RoomContext<TMeta, E> {
|
|
262
|
+
/** The received message frame */
|
|
263
|
+
frame: MessageFrame;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Room definition with lifecycle hooks
|
|
267
|
+
*
|
|
268
|
+
* **Important:** All lifecycle hooks are properly awaited if they return a Promise.
|
|
269
|
+
* This ensures async operations complete before the actor proceeds to the next step
|
|
270
|
+
* or potentially enters hibernation.
|
|
271
|
+
*/
|
|
272
|
+
interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
|
|
273
|
+
/** Optional room name for debugging */
|
|
274
|
+
name?: string;
|
|
275
|
+
/** WebSocket upgrade path (default: "/ws") */
|
|
276
|
+
websocketPath: string;
|
|
277
|
+
/**
|
|
278
|
+
* Extract metadata from the connection request.
|
|
279
|
+
* This function is awaited if it returns a Promise.
|
|
280
|
+
*/
|
|
281
|
+
extractMeta?(req: Request): TMeta | Promise<TMeta>;
|
|
282
|
+
/**
|
|
283
|
+
* Called when a new WebSocket connection is established.
|
|
284
|
+
* This hook is awaited if it returns a Promise. The session is only added to the
|
|
285
|
+
* sessions map after this hook completes successfully. If this hook throws, the
|
|
286
|
+
* connection is closed and no orphaned session is created.
|
|
287
|
+
*/
|
|
288
|
+
onConnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
|
|
289
|
+
/**
|
|
290
|
+
* Called when a WebSocket connection is closed.
|
|
291
|
+
* This hook is awaited if it returns a Promise. The session is removed from the
|
|
292
|
+
* sessions map before this hook is called.
|
|
293
|
+
*/
|
|
294
|
+
onDisconnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
|
|
295
|
+
/**
|
|
296
|
+
* Called when a message is received from a connection.
|
|
297
|
+
* This hook is awaited if it returns a Promise. The actor will not process
|
|
298
|
+
* other messages from this connection until this hook completes.
|
|
299
|
+
*
|
|
300
|
+
* **Note:** If event handlers are registered via `eventEmitter`, they take priority.
|
|
301
|
+
* This hook is used as a fallback when no matching event handler is found.
|
|
302
|
+
*/
|
|
303
|
+
onMessage?(ctx: MessageContext<TMeta, E>, frame: MessageFrame): void | Promise<void>;
|
|
304
|
+
/**
|
|
305
|
+
* Called when an error occurs in a lifecycle hook.
|
|
306
|
+
* This hook is also awaited if it returns a Promise.
|
|
307
|
+
*/
|
|
308
|
+
onError?(error: Error, ctx: RoomContext<TMeta, E>): void | Promise<void>;
|
|
309
|
+
/**
|
|
310
|
+
* Called after actor wakes from hibernation and sessions are restored.
|
|
311
|
+
* This hook is awaited if it returns a Promise. It is called even if some
|
|
312
|
+
* sessions failed to restore, allowing you to handle partial restoration scenarios.
|
|
313
|
+
*/
|
|
314
|
+
onHibernationRestore?(actor: VeraniActor<TMeta, E>): void | Promise<void>;
|
|
315
|
+
/**
|
|
316
|
+
* Event emitter for socket.io-like event handling.
|
|
317
|
+
* If provided, event handlers registered here will be called for matching message types.
|
|
318
|
+
* If not provided, a default event emitter will be created.
|
|
319
|
+
*/
|
|
320
|
+
eventEmitter?: RoomEventEmitter<TMeta, E>;
|
|
321
|
+
/**
|
|
322
|
+
* Static handler storage that persists across hibernation.
|
|
323
|
+
* Handlers registered via room.on() are stored here and rebuilt in onInit.
|
|
324
|
+
* @internal
|
|
325
|
+
*/
|
|
326
|
+
_staticHandlers?: Map<string, Set<EventHandler<TMeta, E>>>;
|
|
327
|
+
}
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/actor/actor-runtime.d.ts
|
|
330
|
+
/**
|
|
331
|
+
* Return type for createActorHandler - represents an Actor class constructor
|
|
332
|
+
*/
|
|
333
|
+
type ActorHandlerClass<E = unknown> = {
|
|
334
|
+
new (state: any, env: E): Actor<E>;
|
|
335
|
+
get(id: string): ActorStub;
|
|
336
|
+
configuration(request?: Request): ActorConfiguration;
|
|
337
|
+
};
|
|
338
|
+
/**
|
|
339
|
+
* Creates an Actor handler from a room definition
|
|
340
|
+
* @param room - The room definition with lifecycle hooks
|
|
341
|
+
* @returns Actor class for Cloudflare Workers (extends DurableObject)
|
|
342
|
+
*/
|
|
343
|
+
declare function createActorHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown>(room: RoomDefinition<TMeta, E>): ActorHandlerClass<E>;
|
|
344
|
+
//#endregion
|
|
345
|
+
export { EventHandler as a, RoomDefinition as c, BroadcastOptions as i, RpcBroadcastOptions as l, createActorHandler as n, MessageContext as o, ActorStub as r, RoomContext as s, ActorHandlerClass as t, VeraniActor as u };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as decodeFrame$1,n as encodeFrame$1}from"./encode-BEipaz10.mjs";import{Actor}from"@cloudflare/actors";var RoomEventEmitterImpl=class{constructor(){this.handlers=new Map}on(e,w){this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e).add(w)}off(e,w){let T=this.handlers.get(e);T&&(w?(T.delete(w),T.size===0&&this.handlers.delete(e)):this.handlers.delete(e))}async emit(e,w,T){let E=this.handlers.get(e);if(E&&E.size>0){let e=[];for(let D of E)try{let E=D(w,T);E instanceof Promise&&e.push(E)}catch{}await Promise.all(e)}let D=this.handlers.get(`*`);if(D&&D.size>0){let e=[];for(let E of D)try{let D=E(w,T);D instanceof Promise&&e.push(D)}catch{}await Promise.all(e)}}hasHandlers(e){return this.handlers.has(e)&&this.handlers.get(e).size>0||this.handlers.has(`*`)&&this.handlers.get(`*`).size>0}getEventNames(){return Array.from(this.handlers.keys())}rebuildHandlers(e){this.handlers.clear();for(let[w,T]of e.entries())this.handlers.set(w,new Set(T))}};function createRoomEventEmitter(){return new RoomEventEmitterImpl}function defaultExtractMeta(e){let w=crypto.randomUUID(),T=crypto.randomUUID(),E=new URL(e.url).searchParams.get(`channels`);return{userId:w,clientId:T,channels:E?E.split(`,`).map(e=>e.trim()).filter(Boolean):[`default`]}}function defineRoom(e){let w=e.eventEmitter||createRoomEventEmitter(),T=e._staticHandlers||new Map;return{name:e.name,websocketPath:e.websocketPath,extractMeta:e.extractMeta||(e=>defaultExtractMeta(e)),onConnect:e.onConnect,onDisconnect:e.onDisconnect,onMessage:e.onMessage,onError:e.onError,onHibernationRestore:e.onHibernationRestore,eventEmitter:w,_staticHandlers:T,on(e,E){w.on(e,E),T.has(e)||T.set(e,new Set),T.get(e).add(E)},off(e,E){w.off(e,E);let D=T.get(e);D&&(E?(D.delete(E),D.size===0&&T.delete(e)):T.delete(e))}}}function cleanupStaleSessions(e){let w=0,T=[];for(let[w,E]of e.entries())w.readyState!==WebSocket.OPEN&&T.push(w);for(let E of T)e.delete(E),w++;return w}function decodeFrame(w){return decodeFrame$1(w)??{type:`invalid`}}function encodeFrame(e){return encodeFrame$1(e)}function broadcast(e,w,T,E){let D=0,O=encodeFrame({type:`event`,channel:w,data:T}),k=[];for(let{ws:T,meta:A}of e.values())if(A.channels.includes(w)&&!(E?.except&&T===E.except)&&!(E?.userIds&&!E.userIds.includes(A.userId))&&!(E?.clientIds&&!E.clientIds.includes(A.clientId))){if(T.readyState!==WebSocket.OPEN){k.push(T);continue}try{T.send(O),D++}catch{k.push(T)}}for(let w of k)e.delete(w);return k.length,D}function sendToUser(e,w,T,E){let D=0,O=encodeFrame({type:`event`,channel:T,data:E}),k=[];for(let{ws:E,meta:A}of e.values())if(A.userId===w&&A.channels.includes(T)){if(E.readyState!==WebSocket.OPEN){k.push(E);continue}try{E.send(O),D++}catch{k.push(E)}}for(let w of k)e.delete(w);return k.length,D}function getSessionCount(e){return e.size}function getConnectedUserIds(e){let w=new Set;for(let{meta:T}of e.values())w.add(T.userId);return Array.from(w)}function getUserSessions(e,w){let T=[];for(let{ws:E,meta:D}of e.values())D.userId===w&&T.push(E);return T}function getStorage(e){return e.storage}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 createConfiguration(e){return function(w){return{locationHint:`me`,sockets:{upgradePath:e.websocketPath}}}}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,w){e.serializeAttachment(w)}function restoreSessions(e){let w=0,T=0;for(let E of e.ctx.getWebSockets()){if(E.readyState!==WebSocket.OPEN){T++;continue}let D=E.deserializeAttachment();if(!D){T++;continue}if(!isValidConnectionMeta(D)){T++;continue}e.sessions.set(E,{ws:E,meta:D}),w++}}async function onInit(e,w){if(w.eventEmitter&&w._staticHandlers)try{w.eventEmitter.rebuildHandlers(w._staticHandlers)}catch{}try{restoreSessions(e)}catch{}if(w.onHibernationRestore&&e.sessions.size>0)try{await w.onHibernationRestore(e)}catch{}else w.onHibernationRestore&&e.sessions.size}function createUserEmitBuilder(e,w,T){return{emit(E,D){return sendToUser(w,e,T,{type:E,...D})}}}function createChannelEmitBuilder(e,w,T){return{emit(E,D){return broadcast(w,e,{type:E,...D},T)}}}function createSocketEmit(e){let w=e.meta.channels[0]||`default`;return{emit(T,E){if(e.ws.readyState===WebSocket.OPEN)try{let D={type:`event`,channel:w,data:{type:T,...E}};e.ws.send(encodeFrame(D))}catch{}},to(T){return e.meta.channels.includes(T)?createChannelEmitBuilder(T,e.actor.sessions,{except:e.ws}):createUserEmitBuilder(T,e.actor.sessions,w)}}}function createActorEmit(e){return{emit(w,T){let E={type:w,...T};return broadcast(e.sessions,`default`,E)},to(w){return createChannelEmitBuilder(w,e.sessions)}}}async function onWebSocketConnect(e,w,T,E){let D;try{D=w.extractMeta?await w.extractMeta(E):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(T,D);let O={actor:e,ws:T,meta:D,frame:{type:`connect`}};if(w.onConnect){let E={actor:e,ws:T,meta:D,emit:createSocketEmit(O)};await w.onConnect(E)}e.sessions.set(T,{ws:T,meta:D})}catch(E){if(w.onError&&D)try{let O={actor:e,ws:T,meta:D,frame:{type:`error`}};await w.onError(E,{actor:e,ws:T,meta:D,emit:createSocketEmit(O)})}catch{}T.close(1011,`Internal server error`)}}async function onWebSocketMessage(e,w,T,E){let D;try{let O=decodeFrame(E);if(O&&O.type===`ping`){if(T.readyState===WebSocket.OPEN)try{T.send(encodeFrame({type:`pong`}))}catch{}return}if(!O||O.type===`invalid`||(D=e.sessions.get(T),!D))return;let k={actor:e,ws:T,meta:D.meta,frame:O,emit:createSocketEmit({actor:e,ws:T,meta:D.meta,frame:O})},A=w.eventEmitter;A&&A.hasHandlers&&A.hasHandlers(O.type)?await A.emit(O.type,k,O.data||{}):w.onMessage&&await w.onMessage(k,O)}catch(E){if(w.onError&&D)try{await w.onError(E,{actor:e,ws:T,meta:D.meta,emit:createSocketEmit({actor:e,ws:T,meta:D.meta,frame:{type:`error`}})})}catch{}}}async function onWebSocketDisconnect(e,w,T){try{let E=e.sessions.get(T);if(e.sessions.delete(T),E&&w.onDisconnect){let D={actor:e,ws:T,meta:E.meta,frame:{type:`disconnect`}},O={actor:e,ws:T,meta:E.meta,emit:createSocketEmit(D)};await w.onDisconnect(O)}}catch{}}function createFetch(e,w){return async function(T){let E=new URL(T.url),D=T.headers.get(`Upgrade`);return E.pathname===e.websocketPath&&D===`websocket`&&await w.shouldUpgradeWebSocket(T)?w.onWebSocketUpgrade(T):w.onRequest(T)}}function createActorHandler(e){let w=sanitizeToClassName(e.name||e.websocketPath||`VeraniActor`);class E extends Actor{constructor(...w){super(...w),this.sessions=new Map,this.emit=createActorEmit(this),this.fetch=createFetch(e,this)}static{this.configuration=createConfiguration(e)}async shouldUpgradeWebSocket(e){return!0}async onInit(){await onInit(this,e)}async onWebSocketConnect(w,T){await onWebSocketConnect(this,e,w,T)}async onWebSocketMessage(w,T){await onWebSocketMessage(this,e,w,T)}async onWebSocketDisconnect(w){await onWebSocketDisconnect(this,e,w)}cleanupStaleSessions(){return cleanupStaleSessions(this.sessions)}broadcast(e,w,T){return broadcast(this.sessions,e,w,T)}getSessionCount(){return getSessionCount(this.sessions)}getConnectedUserIds(){return getConnectedUserIds(this.sessions)}getUserSessions(e){return getUserSessions(this.sessions,e)}sendToUser(e,w,T){return sendToUser(this.sessions,e,w,T)}emitToChannel(e,w,T){let E={type:w,...T};return broadcast(this.sessions,e,E)}emitToUser(e,w,T){let E=encodeFrame({type:`event`,channel:`default`,data:{type:w,...T}}),D=0,O=[];for(let{ws:w,meta:T}of this.sessions.values())if(T.userId===e){if(w.readyState!==WebSocket.OPEN){O.push(w);continue}try{w.send(E),D++}catch{O.push(w)}}for(let e of O)this.sessions.delete(e);return D}getStorage(){return getStorage(this.ctx)}}return Object.defineProperty(E,`name`,{value:w,writable:!1,configurable:!0}),E}export{defineRoom as i,restoreSessions as n,storeAttachment as r,createActorHandler as t};
|