1 | import http = require("http");
|
2 | import type { Server as HTTPSServer } from "https";
|
3 | import type { Http2SecureServer, Http2Server } from "http2";
|
4 | import { Server as Engine } from "engine.io";
|
5 | import type { ServerOptions as EngineOptions, AttachOptions } from "engine.io";
|
6 | import { ExtendedError, Namespace, ServerReservedEventsMap } from "./namespace";
|
7 | import { Adapter, Room, SocketId } from "socket.io-adapter";
|
8 | import * as parser from "socket.io-parser";
|
9 | import type { Encoder } from "socket.io-parser";
|
10 | import { Socket } from "./socket";
|
11 | import { DisconnectReason } from "./socket-types";
|
12 | import type { BroadcastOperator, RemoteSocket } from "./broadcast-operator";
|
13 | import { EventsMap, DefaultEventsMap, EventParams, StrictEventEmitter, EventNames, DecorateAcknowledgementsWithTimeoutAndMultipleResponses, AllButLast, Last, RemoveAcknowledgements, EventNamesWithAck, FirstNonErrorArg } from "./typed-events";
|
14 | type ParentNspNameMatchFn = (name: string, auth: {
|
15 | [key: string]: any;
|
16 | }, fn: (err: Error | null, success: boolean) => void) => void;
|
17 | type AdapterConstructor = typeof Adapter | ((nsp: Namespace) => Adapter);
|
18 | type TServerInstance = http.Server | HTTPSServer | Http2SecureServer | Http2Server;
|
19 | interface ServerOptions extends EngineOptions, AttachOptions {
|
20 | /**
|
21 | * name of the path to capture
|
22 | * @default "/socket.io"
|
23 | */
|
24 | path: string;
|
25 | /**
|
26 | * whether to serve the client files
|
27 | * @default true
|
28 | */
|
29 | serveClient: boolean;
|
30 | /**
|
31 | * the adapter to use
|
32 | * @default the in-memory adapter (https://github.com/socketio/socket.io-adapter)
|
33 | */
|
34 | adapter: AdapterConstructor;
|
35 | /**
|
36 | * the parser to use
|
37 | * @default the default parser (https://github.com/socketio/socket.io-parser)
|
38 | */
|
39 | parser: any;
|
40 | /**
|
41 | * how many ms before a client without namespace is closed
|
42 | * @default 45000
|
43 | */
|
44 | connectTimeout: number;
|
45 | /**
|
46 | * Whether to enable the recovery of connection state when a client temporarily disconnects.
|
47 | *
|
48 | * The connection state includes the missed packets, the rooms the socket was in and the `data` attribute.
|
49 | */
|
50 | connectionStateRecovery: {
|
51 | /**
|
52 | * The backup duration of the sessions and the packets.
|
53 | *
|
54 | * @default 120000 (2 minutes)
|
55 | */
|
56 | maxDisconnectionDuration?: number;
|
57 | /**
|
58 | * Whether to skip middlewares upon successful connection state recovery.
|
59 | *
|
60 | * @default true
|
61 | */
|
62 | skipMiddlewares?: boolean;
|
63 | };
|
64 | /**
|
65 | * Whether to remove child namespaces that have no sockets connected to them
|
66 | * @default false
|
67 | */
|
68 | cleanupEmptyChildNamespaces: boolean;
|
69 | }
|
70 | /**
|
71 | * Represents a Socket.IO server.
|
72 | *
|
73 | * @example
|
74 | * import { Server } from "socket.io";
|
75 | *
|
76 | * const io = new Server();
|
77 | *
|
78 | * io.on("connection", (socket) => {
|
79 | * console.log(`socket ${socket.id} connected`);
|
80 | *
|
81 | * // send an event to the client
|
82 | * socket.emit("foo", "bar");
|
83 | *
|
84 | * socket.on("foobar", () => {
|
85 | * // an event was received from the client
|
86 | * });
|
87 | *
|
88 | * // upon disconnection
|
89 | * socket.on("disconnect", (reason) => {
|
90 | * console.log(`socket ${socket.id} disconnected due to ${reason}`);
|
91 | * });
|
92 | * });
|
93 | *
|
94 | * io.listen(3000);
|
95 | */
|
96 | export declare class Server<
|
97 | /**
|
98 | * Types for the events received from the clients.
|
99 | *
|
100 | * @example
|
101 | * interface ClientToServerEvents {
|
102 | * hello: (arg: string) => void;
|
103 | * }
|
104 | *
|
105 | * const io = new Server<ClientToServerEvents>();
|
106 | *
|
107 | * io.on("connection", (socket) => {
|
108 | * socket.on("hello", (arg) => {
|
109 | * // `arg` is inferred as string
|
110 | * });
|
111 | * });
|
112 | */
|
113 | ListenEvents extends EventsMap = DefaultEventsMap,
|
114 | /**
|
115 | * Types for the events sent to the clients.
|
116 | *
|
117 | * @example
|
118 | * interface ServerToClientEvents {
|
119 | * hello: (arg: string) => void;
|
120 | * }
|
121 | *
|
122 | * const io = new Server<DefaultEventMap, ServerToClientEvents>();
|
123 | *
|
124 | * io.emit("hello", "world");
|
125 | */
|
126 | EmitEvents extends EventsMap = ListenEvents,
|
127 | /**
|
128 | * Types for the events received from and sent to the other servers.
|
129 | *
|
130 | * @example
|
131 | * interface InterServerEvents {
|
132 | * ping: (arg: number) => void;
|
133 | * }
|
134 | *
|
135 | * const io = new Server<DefaultEventMap, DefaultEventMap, ServerToClientEvents>();
|
136 | *
|
137 | * io.serverSideEmit("ping", 123);
|
138 | *
|
139 | * io.on("ping", (arg) => {
|
140 | * // `arg` is inferred as number
|
141 | * });
|
142 | */
|
143 | ServerSideEvents extends EventsMap = DefaultEventsMap,
|
144 | /**
|
145 | * Additional properties that can be attached to the socket instance.
|
146 | *
|
147 | * Note: any property can be attached directly to the socket instance (`socket.foo = "bar"`), but the `data` object
|
148 | * will be included when calling {@link Server#fetchSockets}.
|
149 | *
|
150 | * @example
|
151 | * io.on("connection", (socket) => {
|
152 | * socket.data.eventsCount = 0;
|
153 | *
|
154 | * socket.onAny(() => {
|
155 | * socket.data.eventsCount++;
|
156 | * });
|
157 | * });
|
158 | */
|
159 | SocketData = any> extends StrictEventEmitter<ServerSideEvents, RemoveAcknowledgements<EmitEvents>, ServerReservedEventsMap<ListenEvents, EmitEvents, ServerSideEvents, SocketData>> {
|
160 | readonly sockets: Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>;
|
161 | /**
|
162 | * A reference to the underlying Engine.IO server.
|
163 | *
|
164 | * @example
|
165 | * const clientsCount = io.engine.clientsCount;
|
166 | *
|
167 | */
|
168 | engine: Engine;
|
169 | /**
|
170 | * The underlying Node.js HTTP server.
|
171 | *
|
172 | * @see https://nodejs.org/api/http.html
|
173 | */
|
174 | httpServer: TServerInstance;
|
175 | /** @private */
|
176 | readonly _parser: typeof parser;
|
177 | /** @private */
|
178 | readonly encoder: Encoder;
|
179 | /**
|
180 | * @private
|
181 | */
|
182 | _nsps: Map<string, Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>>;
|
183 | private parentNsps;
|
184 | /**
|
185 | * A subset of the {@link parentNsps} map, only containing {@link ParentNamespace} which are based on a regular
|
186 | * expression.
|
187 | *
|
188 | * @private
|
189 | */
|
190 | private parentNamespacesFromRegExp;
|
191 | private _adapter?;
|
192 | private _serveClient;
|
193 | private readonly opts;
|
194 | private eio;
|
195 | private _path;
|
196 | private clientPathRegex;
|
197 | /**
|
198 | * @private
|
199 | */
|
200 | _connectTimeout: number;
|
201 | private _corsMiddleware;
|
202 | /**
|
203 | * Server constructor.
|
204 | *
|
205 | * @param srv http server, port, or options
|
206 | * @param [opts]
|
207 | */
|
208 | constructor(opts?: Partial<ServerOptions>);
|
209 | constructor(srv?: TServerInstance | number, opts?: Partial<ServerOptions>);
|
210 | constructor(srv: undefined | Partial<ServerOptions> | TServerInstance | number, opts?: Partial<ServerOptions>);
|
211 | get _opts(): Partial<ServerOptions>;
|
212 | /**
|
213 | * Sets/gets whether client code is being served.
|
214 | *
|
215 | * @param v - whether to serve client code
|
216 | * @return self when setting or value when getting
|
217 | */
|
218 | serveClient(v: boolean): this;
|
219 | serveClient(): boolean;
|
220 | serveClient(v?: boolean): this | boolean;
|
221 | /**
|
222 | * Executes the middleware for an incoming namespace not already created on the server.
|
223 | *
|
224 | * @param name - name of incoming namespace
|
225 | * @param auth - the auth parameters
|
226 | * @param fn - callback
|
227 | *
|
228 | * @private
|
229 | */
|
230 | _checkNamespace(name: string, auth: {
|
231 | [key: string]: any;
|
232 | }, fn: (nsp: Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData> | false) => void): void;
|
233 | /**
|
234 | * Sets the client serving path.
|
235 | *
|
236 | * @param {String} v pathname
|
237 | * @return {Server|String} self when setting or value when getting
|
238 | */
|
239 | path(v: string): this;
|
240 | path(): string;
|
241 | path(v?: string): this | string;
|
242 | /**
|
243 | * Set the delay after which a client without namespace is closed
|
244 | * @param v
|
245 | */
|
246 | connectTimeout(v: number): this;
|
247 | connectTimeout(): number;
|
248 | connectTimeout(v?: number): this | number;
|
249 | /**
|
250 | * Sets the adapter for rooms.
|
251 | *
|
252 | * @param v pathname
|
253 | * @return self when setting or value when getting
|
254 | */
|
255 | adapter(): AdapterConstructor | undefined;
|
256 | adapter(v: AdapterConstructor): this;
|
257 | /**
|
258 | * Attaches socket.io to a server or port.
|
259 | *
|
260 | * @param srv - server or port
|
261 | * @param opts - options passed to engine.io
|
262 | * @return self
|
263 | */
|
264 | listen(srv: TServerInstance | number, opts?: Partial<ServerOptions>): this;
|
265 | /**
|
266 | * Attaches socket.io to a server or port.
|
267 | *
|
268 | * @param srv - server or port
|
269 | * @param opts - options passed to engine.io
|
270 | * @return self
|
271 | */
|
272 | attach(srv: TServerInstance | number, opts?: Partial<ServerOptions>): this;
|
273 | attachApp(app: any, opts?: Partial<ServerOptions>): void;
|
274 | /**
|
275 | * Initialize engine
|
276 | *
|
277 | * @param srv - the server to attach to
|
278 | * @param opts - options passed to engine.io
|
279 | * @private
|
280 | */
|
281 | private initEngine;
|
282 | /**
|
283 | * Attaches the static file serving.
|
284 | *
|
285 | * @param srv http server
|
286 | * @private
|
287 | */
|
288 | private attachServe;
|
289 | /**
|
290 | * Handles a request serving of client source and map
|
291 | *
|
292 | * @param req
|
293 | * @param res
|
294 | * @private
|
295 | */
|
296 | private serve;
|
297 | /**
|
298 | * @param filename
|
299 | * @param req
|
300 | * @param res
|
301 | * @private
|
302 | */
|
303 | private static sendFile;
|
304 | /**
|
305 | * Binds socket.io to an engine.io instance.
|
306 | *
|
307 | * @param engine engine.io (or compatible) server
|
308 | * @return self
|
309 | */
|
310 | bind(engine: any): this;
|
311 | /**
|
312 | * Called with each incoming transport connection.
|
313 | *
|
314 | * @param {engine.Socket} conn
|
315 | * @return self
|
316 | * @private
|
317 | */
|
318 | private onconnection;
|
319 | /**
|
320 | * Looks up a namespace.
|
321 | *
|
322 | * @example
|
323 | * // with a simple string
|
324 | * const myNamespace = io.of("/my-namespace");
|
325 | *
|
326 | * // with a regex
|
327 | * const dynamicNsp = io.of(/^\/dynamic-\d+$/).on("connection", (socket) => {
|
328 | * const namespace = socket.nsp; // newNamespace.name === "/dynamic-101"
|
329 | *
|
330 | * // broadcast to all clients in the given sub-namespace
|
331 | * namespace.emit("hello");
|
332 | * });
|
333 | *
|
334 | * @param name - nsp name
|
335 | * @param fn optional, nsp `connection` ev handler
|
336 | */
|
337 | of(name: string | RegExp | ParentNspNameMatchFn, fn?: (socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>) => void): Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>;
|
338 | /**
|
339 | * Closes server connection
|
340 | *
|
341 | * @param [fn] optional, called as `fn([err])` on error OR all conns closed
|
342 | */
|
343 | close(fn?: (err?: Error) => void): Promise<void>;
|
344 | /**
|
345 | * Registers a middleware, which is a function that gets executed for every incoming {@link Socket}.
|
346 | *
|
347 | * @example
|
348 | * io.use((socket, next) => {
|
349 | * // ...
|
350 | * next();
|
351 | * });
|
352 | *
|
353 | * @param fn - the middleware function
|
354 | */
|
355 | use(fn: (socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>, next: (err?: ExtendedError) => void) => void): this;
|
356 | /**
|
357 | * Targets a room when emitting.
|
358 | *
|
359 | * @example
|
360 | * // the “foo” event will be broadcast to all connected clients in the “room-101” room
|
361 | * io.to("room-101").emit("foo", "bar");
|
362 | *
|
363 | * // with an array of rooms (a client will be notified at most once)
|
364 | * io.to(["room-101", "room-102"]).emit("foo", "bar");
|
365 | *
|
366 | * // with multiple chained calls
|
367 | * io.to("room-101").to("room-102").emit("foo", "bar");
|
368 | *
|
369 | * @param room - a room, or an array of rooms
|
370 | * @return a new {@link BroadcastOperator} instance for chaining
|
371 | */
|
372 | to(room: Room | Room[]): BroadcastOperator<import("./typed-events").DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>;
|
373 | /**
|
374 | * Targets a room when emitting. Similar to `to()`, but might feel clearer in some cases:
|
375 | *
|
376 | * @example
|
377 | * // disconnect all clients in the "room-101" room
|
378 | * io.in("room-101").disconnectSockets();
|
379 | *
|
380 | * @param room - a room, or an array of rooms
|
381 | * @return a new {@link BroadcastOperator} instance for chaining
|
382 | */
|
383 | in(room: Room | Room[]): BroadcastOperator<import("./typed-events").DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>;
|
384 | /**
|
385 | * Excludes a room when emitting.
|
386 | *
|
387 | * @example
|
388 | * // the "foo" event will be broadcast to all connected clients, except the ones that are in the "room-101" room
|
389 | * io.except("room-101").emit("foo", "bar");
|
390 | *
|
391 | * // with an array of rooms
|
392 | * io.except(["room-101", "room-102"]).emit("foo", "bar");
|
393 | *
|
394 | * // with multiple chained calls
|
395 | * io.except("room-101").except("room-102").emit("foo", "bar");
|
396 | *
|
397 | * @param room - a room, or an array of rooms
|
398 | * @return a new {@link BroadcastOperator} instance for chaining
|
399 | */
|
400 | except(room: Room | Room[]): BroadcastOperator<import("./typed-events").DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>;
|
401 | /**
|
402 | * Sends a `message` event to all clients.
|
403 | *
|
404 | * This method mimics the WebSocket.send() method.
|
405 | *
|
406 | * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send
|
407 | *
|
408 | * @example
|
409 | * io.send("hello");
|
410 | *
|
411 | * // this is equivalent to
|
412 | * io.emit("message", "hello");
|
413 | *
|
414 | * @return self
|
415 | */
|
416 | send(...args: EventParams<EmitEvents, "message">): this;
|
417 | /**
|
418 | * Sends a `message` event to all clients. Alias of {@link send}.
|
419 | *
|
420 | * @return self
|
421 | */
|
422 | write(...args: EventParams<EmitEvents, "message">): this;
|
423 | /**
|
424 | * Sends a message to the other Socket.IO servers of the cluster.
|
425 | *
|
426 | * @example
|
427 | * io.serverSideEmit("hello", "world");
|
428 | *
|
429 | * io.on("hello", (arg1) => {
|
430 | * console.log(arg1); // prints "world"
|
431 | * });
|
432 | *
|
433 | * // acknowledgements (without binary content) are supported too:
|
434 | * io.serverSideEmit("ping", (err, responses) => {
|
435 | * if (err) {
|
436 | * // some servers did not acknowledge the event in the given delay
|
437 | * } else {
|
438 | * console.log(responses); // one response per server (except the current one)
|
439 | * }
|
440 | * });
|
441 | *
|
442 | * io.on("ping", (cb) => {
|
443 | * cb("pong");
|
444 | * });
|
445 | *
|
446 | * @param ev - the event name
|
447 | * @param args - an array of arguments, which may include an acknowledgement callback at the end
|
448 | */
|
449 | serverSideEmit<Ev extends EventNames<ServerSideEvents>>(ev: Ev, ...args: EventParams<DecorateAcknowledgementsWithTimeoutAndMultipleResponses<ServerSideEvents>, Ev>): boolean;
|
450 | /**
|
451 | * Sends a message and expect an acknowledgement from the other Socket.IO servers of the cluster.
|
452 | *
|
453 | * @example
|
454 | * try {
|
455 | * const responses = await io.serverSideEmitWithAck("ping");
|
456 | * console.log(responses); // one response per server (except the current one)
|
457 | * } catch (e) {
|
458 | * // some servers did not acknowledge the event in the given delay
|
459 | * }
|
460 | *
|
461 | * @param ev - the event name
|
462 | * @param args - an array of arguments
|
463 | *
|
464 | * @return a Promise that will be fulfilled when all servers have acknowledged the event
|
465 | */
|
466 | serverSideEmitWithAck<Ev extends EventNamesWithAck<ServerSideEvents>>(ev: Ev, ...args: AllButLast<EventParams<ServerSideEvents, Ev>>): Promise<FirstNonErrorArg<Last<EventParams<ServerSideEvents, Ev>>>[]>;
|
467 | /**
|
468 | * Gets a list of socket ids.
|
469 | *
|
470 | * @deprecated this method will be removed in the next major release, please use {@link Server#serverSideEmit} or
|
471 | * {@link Server#fetchSockets} instead.
|
472 | */
|
473 | allSockets(): Promise<Set<SocketId>>;
|
474 | /**
|
475 | * Sets the compress flag.
|
476 | *
|
477 | * @example
|
478 | * io.compress(false).emit("hello");
|
479 | *
|
480 | * @param compress - if `true`, compresses the sending data
|
481 | * @return a new {@link BroadcastOperator} instance for chaining
|
482 | */
|
483 | compress(compress: boolean): BroadcastOperator<import("./typed-events").DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>;
|
484 | /**
|
485 | * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
|
486 | * receive messages (because of network slowness or other issues, or because they’re connected through long polling
|
487 | * and is in the middle of a request-response cycle).
|
488 | *
|
489 | * @example
|
490 | * io.volatile.emit("hello"); // the clients may or may not receive it
|
491 | *
|
492 | * @return a new {@link BroadcastOperator} instance for chaining
|
493 | */
|
494 | get volatile(): BroadcastOperator<import("./typed-events").DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>;
|
495 | /**
|
496 | * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
|
497 | *
|
498 | * @example
|
499 | * // the “foo” event will be broadcast to all connected clients on this node
|
500 | * io.local.emit("foo", "bar");
|
501 | *
|
502 | * @return a new {@link BroadcastOperator} instance for chaining
|
503 | */
|
504 | get local(): BroadcastOperator<import("./typed-events").DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>;
|
505 | /**
|
506 | * Adds a timeout in milliseconds for the next operation.
|
507 | *
|
508 | * @example
|
509 | * io.timeout(1000).emit("some-event", (err, responses) => {
|
510 | * if (err) {
|
511 | * // some clients did not acknowledge the event in the given delay
|
512 | * } else {
|
513 | * console.log(responses); // one response per client
|
514 | * }
|
515 | * });
|
516 | *
|
517 | * @param timeout
|
518 | */
|
519 | timeout(timeout: number): BroadcastOperator<import("./typed-events").DecorateAcknowledgements<import("./typed-events").DecorateAcknowledgementsWithMultipleResponses<EmitEvents>>, SocketData>;
|
520 | /**
|
521 | * Returns the matching socket instances.
|
522 | *
|
523 | * Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
524 | *
|
525 | * @example
|
526 | * // return all Socket instances
|
527 | * const sockets = await io.fetchSockets();
|
528 | *
|
529 | * // return all Socket instances in the "room1" room
|
530 | * const sockets = await io.in("room1").fetchSockets();
|
531 | *
|
532 | * for (const socket of sockets) {
|
533 | * console.log(socket.id);
|
534 | * console.log(socket.handshake);
|
535 | * console.log(socket.rooms);
|
536 | * console.log(socket.data);
|
537 | *
|
538 | * socket.emit("hello");
|
539 | * socket.join("room1");
|
540 | * socket.leave("room2");
|
541 | * socket.disconnect();
|
542 | * }
|
543 | */
|
544 | fetchSockets(): Promise<RemoteSocket<EmitEvents, SocketData>[]>;
|
545 | /**
|
546 | * Makes the matching socket instances join the specified rooms.
|
547 | *
|
548 | * Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
549 | *
|
550 | * @example
|
551 | *
|
552 | * // make all socket instances join the "room1" room
|
553 | * io.socketsJoin("room1");
|
554 | *
|
555 | * // make all socket instances in the "room1" room join the "room2" and "room3" rooms
|
556 | * io.in("room1").socketsJoin(["room2", "room3"]);
|
557 | *
|
558 | * @param room - a room, or an array of rooms
|
559 | */
|
560 | socketsJoin(room: Room | Room[]): void;
|
561 | /**
|
562 | * Makes the matching socket instances leave the specified rooms.
|
563 | *
|
564 | * Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
565 | *
|
566 | * @example
|
567 | * // make all socket instances leave the "room1" room
|
568 | * io.socketsLeave("room1");
|
569 | *
|
570 | * // make all socket instances in the "room1" room leave the "room2" and "room3" rooms
|
571 | * io.in("room1").socketsLeave(["room2", "room3"]);
|
572 | *
|
573 | * @param room - a room, or an array of rooms
|
574 | */
|
575 | socketsLeave(room: Room | Room[]): void;
|
576 | /**
|
577 | * Makes the matching socket instances disconnect.
|
578 | *
|
579 | * Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
580 | *
|
581 | * @example
|
582 | * // make all socket instances disconnect (the connections might be kept alive for other namespaces)
|
583 | * io.disconnectSockets();
|
584 | *
|
585 | * // make all socket instances in the "room1" room disconnect and close the underlying connections
|
586 | * io.in("room1").disconnectSockets(true);
|
587 | *
|
588 | * @param close - whether to close the underlying connection
|
589 | */
|
590 | disconnectSockets(close?: boolean): void;
|
591 | }
|
592 | export { Socket, DisconnectReason, ServerOptions, Namespace, BroadcastOperator, RemoteSocket, DefaultEventsMap, ExtendedError, };
|
593 | export { Event } from "./socket";
|
594 |
|
\ | No newline at end of file |