@eleven-am/pondsocket 0.1.215 → 0.1.217

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.
@@ -19,12 +19,18 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
19
19
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
20
20
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
21
21
  };
22
- var _RedisDistributedBackend_instances, _RedisDistributedBackend_publishClient, _RedisDistributedBackend_subscribeClient, _RedisDistributedBackend_keyPrefix, _RedisDistributedBackend_heartbeatSubject, _RedisDistributedBackend_nodeId, _RedisDistributedBackend_heartbeatIntervalMs, _RedisDistributedBackend_heartbeatTimeoutMs, _RedisDistributedBackend_onError, _RedisDistributedBackend_isConnected, _RedisDistributedBackend_heartbeatTimer, _RedisDistributedBackend_attachEventHandlers, _RedisDistributedBackend_startHeartbeat, _RedisDistributedBackend_publishHeartbeat, _RedisDistributedBackend_handleHeartbeatMessage, _RedisDistributedBackend_isValidMessage, _RedisDistributedBackend_buildKey;
22
+ var _RedisDistributedBackend_instances, _RedisDistributedBackend_publishClient, _RedisDistributedBackend_subscribeClient, _RedisDistributedBackend_keyPrefix, _RedisDistributedBackend_namespace, _RedisDistributedBackend_heartbeatSubject, _RedisDistributedBackend_nodeId, _RedisDistributedBackend_heartbeatIntervalMs, _RedisDistributedBackend_heartbeatTimeoutMs, _RedisDistributedBackend_onError, _RedisDistributedBackend_isConnected, _RedisDistributedBackend_heartbeatTimer, _RedisDistributedBackend_attachEventHandlers, _RedisDistributedBackend_startHeartbeat, _RedisDistributedBackend_publishHeartbeat, _RedisDistributedBackend_handleHeartbeatMessage, _RedisDistributedBackend_isValidMessage, _RedisDistributedBackend_buildKey, _RedisDistributedBackend_normalizeTopicPart;
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.RedisDistributedBackend = void 0;
25
25
  const pondsocket_common_1 = require("@eleven-am/pondsocket-common");
26
26
  const redis_1 = require("redis");
27
27
  const types_1 = require("../types");
28
+ const DISTRIBUTED_PROTOCOL = 'pondsocket.distributed';
29
+ const DISTRIBUTED_VERSION = 1;
30
+ function distributedId() {
31
+ return `${Date.now().toString(36)}-${Math.random().toString(36)
32
+ .slice(2)}`;
33
+ }
28
34
  class RedisDistributedBackend {
29
35
  get nodeId() {
30
36
  return __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f");
@@ -37,6 +43,7 @@ class RedisDistributedBackend {
37
43
  _RedisDistributedBackend_publishClient.set(this, void 0);
38
44
  _RedisDistributedBackend_subscribeClient.set(this, void 0);
39
45
  _RedisDistributedBackend_keyPrefix.set(this, void 0);
46
+ _RedisDistributedBackend_namespace.set(this, void 0);
40
47
  _RedisDistributedBackend_heartbeatSubject.set(this, void 0);
41
48
  _RedisDistributedBackend_nodeId.set(this, void 0);
42
49
  _RedisDistributedBackend_heartbeatIntervalMs.set(this, void 0);
@@ -44,8 +51,9 @@ class RedisDistributedBackend {
44
51
  _RedisDistributedBackend_onError.set(this, void 0);
45
52
  _RedisDistributedBackend_isConnected.set(this, false);
46
53
  _RedisDistributedBackend_heartbeatTimer.set(this, null);
47
- const { host = 'localhost', port = 6379, password, database = 0, url, keyPrefix = 'pondsocket', heartbeatIntervalMs = 30000, heartbeatTimeoutMs = 90000, onError = null, } = options;
54
+ const { host = 'localhost', port = 6379, password, database = 0, url, keyPrefix = 'pondsocket', namespace = 'default', heartbeatIntervalMs = 30000, heartbeatTimeoutMs = 90000, onError = null, } = options;
48
55
  __classPrivateFieldSet(this, _RedisDistributedBackend_keyPrefix, keyPrefix, "f");
56
+ __classPrivateFieldSet(this, _RedisDistributedBackend_namespace, namespace, "f");
49
57
  __classPrivateFieldSet(this, _RedisDistributedBackend_heartbeatIntervalMs, heartbeatIntervalMs, "f");
50
58
  __classPrivateFieldSet(this, _RedisDistributedBackend_heartbeatTimeoutMs, heartbeatTimeoutMs, "f");
51
59
  __classPrivateFieldSet(this, _RedisDistributedBackend_onError, onError, "f");
@@ -80,7 +88,7 @@ class RedisDistributedBackend {
80
88
  if (!__classPrivateFieldGet(this, _RedisDistributedBackend_instances, "m", _RedisDistributedBackend_isValidMessage).call(this, parsedMessage)) {
81
89
  return;
82
90
  }
83
- if (parsedMessage.nodeId !== __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f")) {
91
+ if (parsedMessage.sourceNodeId !== __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f")) {
84
92
  handler(parsedMessage);
85
93
  }
86
94
  }
@@ -99,11 +107,13 @@ class RedisDistributedBackend {
99
107
  }
100
108
  broadcast(endpointName, channelName, message) {
101
109
  return __awaiter(this, void 0, void 0, function* () {
110
+ var _a, _b;
102
111
  if (!__classPrivateFieldGet(this, _RedisDistributedBackend_isConnected, "f")) {
103
112
  throw new Error('Redis backend is not connected');
104
113
  }
105
114
  const key = __classPrivateFieldGet(this, _RedisDistributedBackend_instances, "m", _RedisDistributedBackend_buildKey).call(this, endpointName, channelName);
106
- const distributedMessage = Object.assign(Object.assign({}, message), { nodeId: __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f"), sourceNodeId: __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f") });
115
+ const normalizedEndpointName = __classPrivateFieldGet(this, _RedisDistributedBackend_instances, "m", _RedisDistributedBackend_normalizeTopicPart).call(this, endpointName);
116
+ const distributedMessage = Object.assign(Object.assign({}, message), { protocol: DISTRIBUTED_PROTOCOL, version: DISTRIBUTED_VERSION, messageId: (_a = message.messageId) !== null && _a !== void 0 ? _a : distributedId(), timestamp: (_b = message.timestamp) !== null && _b !== void 0 ? _b : Date.now(), sourceNodeId: __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f"), endpointName: normalizedEndpointName });
107
117
  const serializedMessage = JSON.stringify(distributedMessage);
108
118
  yield __classPrivateFieldGet(this, _RedisDistributedBackend_publishClient, "f").publish(key, serializedMessage);
109
119
  });
@@ -140,7 +150,7 @@ class RedisDistributedBackend {
140
150
  }
141
151
  }
142
152
  exports.RedisDistributedBackend = RedisDistributedBackend;
143
- _RedisDistributedBackend_publishClient = new WeakMap(), _RedisDistributedBackend_subscribeClient = new WeakMap(), _RedisDistributedBackend_keyPrefix = new WeakMap(), _RedisDistributedBackend_heartbeatSubject = new WeakMap(), _RedisDistributedBackend_nodeId = new WeakMap(), _RedisDistributedBackend_heartbeatIntervalMs = new WeakMap(), _RedisDistributedBackend_heartbeatTimeoutMs = new WeakMap(), _RedisDistributedBackend_onError = new WeakMap(), _RedisDistributedBackend_isConnected = new WeakMap(), _RedisDistributedBackend_heartbeatTimer = new WeakMap(), _RedisDistributedBackend_instances = new WeakSet(), _RedisDistributedBackend_attachEventHandlers = function _RedisDistributedBackend_attachEventHandlers(client, label) {
153
+ _RedisDistributedBackend_publishClient = new WeakMap(), _RedisDistributedBackend_subscribeClient = new WeakMap(), _RedisDistributedBackend_keyPrefix = new WeakMap(), _RedisDistributedBackend_namespace = new WeakMap(), _RedisDistributedBackend_heartbeatSubject = new WeakMap(), _RedisDistributedBackend_nodeId = new WeakMap(), _RedisDistributedBackend_heartbeatIntervalMs = new WeakMap(), _RedisDistributedBackend_heartbeatTimeoutMs = new WeakMap(), _RedisDistributedBackend_onError = new WeakMap(), _RedisDistributedBackend_isConnected = new WeakMap(), _RedisDistributedBackend_heartbeatTimer = new WeakMap(), _RedisDistributedBackend_instances = new WeakSet(), _RedisDistributedBackend_attachEventHandlers = function _RedisDistributedBackend_attachEventHandlers(client, label) {
144
154
  client.on('error', (err) => {
145
155
  __classPrivateFieldSet(this, _RedisDistributedBackend_isConnected, false, "f");
146
156
  if (__classPrivateFieldGet(this, _RedisDistributedBackend_onError, "f")) {
@@ -165,9 +175,14 @@ _RedisDistributedBackend_publishClient = new WeakMap(), _RedisDistributedBackend
165
175
  return;
166
176
  }
167
177
  const heartbeatMessage = {
178
+ protocol: DISTRIBUTED_PROTOCOL,
179
+ version: DISTRIBUTED_VERSION,
168
180
  type: types_1.DistributedMessageType.NODE_HEARTBEAT,
181
+ messageId: distributedId(),
169
182
  endpointName: '__heartbeat__',
170
183
  channelName: '__heartbeat__',
184
+ sourceNodeId: __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f"),
185
+ timestamp: Date.now(),
171
186
  nodeId: __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f"),
172
187
  };
173
188
  const key = __classPrivateFieldGet(this, _RedisDistributedBackend_instances, "m", _RedisDistributedBackend_buildKey).call(this, '__heartbeat__', '__heartbeat__');
@@ -179,8 +194,8 @@ _RedisDistributedBackend_publishClient = new WeakMap(), _RedisDistributedBackend
179
194
  }, _RedisDistributedBackend_handleHeartbeatMessage = function _RedisDistributedBackend_handleHeartbeatMessage(message) {
180
195
  try {
181
196
  const parsedMessage = JSON.parse(message);
182
- if (parsedMessage.type === types_1.DistributedMessageType.NODE_HEARTBEAT && parsedMessage.nodeId !== __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f")) {
183
- __classPrivateFieldGet(this, _RedisDistributedBackend_heartbeatSubject, "f").publish(parsedMessage.nodeId);
197
+ if (__classPrivateFieldGet(this, _RedisDistributedBackend_instances, "m", _RedisDistributedBackend_isValidMessage).call(this, parsedMessage) && parsedMessage.type === types_1.DistributedMessageType.NODE_HEARTBEAT && parsedMessage.sourceNodeId && parsedMessage.sourceNodeId !== __classPrivateFieldGet(this, _RedisDistributedBackend_nodeId, "f")) {
198
+ __classPrivateFieldGet(this, _RedisDistributedBackend_heartbeatSubject, "f").publish(parsedMessage.sourceNodeId);
184
199
  }
185
200
  }
186
201
  catch (_) {
@@ -188,10 +203,20 @@ _RedisDistributedBackend_publishClient = new WeakMap(), _RedisDistributedBackend
188
203
  }
189
204
  }, _RedisDistributedBackend_isValidMessage = function _RedisDistributedBackend_isValidMessage(message) {
190
205
  return (typeof message === 'object' &&
206
+ message.protocol === DISTRIBUTED_PROTOCOL &&
207
+ message.version === DISTRIBUTED_VERSION &&
191
208
  typeof message.type === 'string' &&
192
209
  Object.values(types_1.DistributedMessageType).includes(message.type) &&
210
+ typeof message.messageId === 'string' &&
193
211
  typeof message.endpointName === 'string' &&
194
- typeof message.channelName === 'string');
212
+ typeof message.channelName === 'string' &&
213
+ typeof message.sourceNodeId === 'string' &&
214
+ typeof message.timestamp === 'number');
195
215
  }, _RedisDistributedBackend_buildKey = function _RedisDistributedBackend_buildKey(endpointName, channelName) {
196
- return `${__classPrivateFieldGet(this, _RedisDistributedBackend_keyPrefix, "f")}:${endpointName}:${channelName}`;
216
+ if (endpointName === '__heartbeat__' && channelName === '__heartbeat__') {
217
+ return `${__classPrivateFieldGet(this, _RedisDistributedBackend_keyPrefix, "f")}:v1:${__classPrivateFieldGet(this, _RedisDistributedBackend_namespace, "f")}:__heartbeat__`;
218
+ }
219
+ return `${__classPrivateFieldGet(this, _RedisDistributedBackend_keyPrefix, "f")}:v1:${__classPrivateFieldGet(this, _RedisDistributedBackend_namespace, "f")}:${__classPrivateFieldGet(this, _RedisDistributedBackend_instances, "m", _RedisDistributedBackend_normalizeTopicPart).call(this, endpointName)}:${channelName}`;
220
+ }, _RedisDistributedBackend_normalizeTopicPart = function _RedisDistributedBackend_normalizeTopicPart(value) {
221
+ return value.replace(/^\/+/, '');
197
222
  };
@@ -1,7 +1,7 @@
1
1
  import { IncomingHttpHeaders } from 'http';
2
2
  import { IncomingMessage } from 'node:http';
3
3
  import internal from 'node:stream';
4
- import { ChannelEvent, EventParams, JoinParams, PondAssigns, PondMessage, ServerActions, SystemSender, Unsubscribe, UserData } from '@eleven-am/pondsocket-common';
4
+ import { ChannelEvent, AnyPondSchema, AssignsOf, EventParams, EventsOf, JoinParamsOf, PondAssigns, PondEventMap, PondMessage, PondPresence, ServerActions, SystemSender, Unsubscribe, UserData } from '@eleven-am/pondsocket-common';
5
5
  import { WebSocket, WebSocketServer } from 'ws';
6
6
  import { ConnectionContext } from '../contexts/connectionContext';
7
7
  import { EventContext } from '../contexts/eventContext';
@@ -17,10 +17,13 @@ export interface SocketRequest {
17
17
  headers: IncomingHttpHeaders;
18
18
  address: string;
19
19
  }
20
- export type ConnectionHandler<Path extends string> = (ctx: ConnectionContext<Path>, next: NextFunction) => void | Promise<void>;
21
- export type AuthorizationHandler<Path extends string> = (ctx: JoinContext<Path>, next: NextFunction) => void | Promise<void>;
22
- export type EventHandler<Path extends string> = (ctx: EventContext<Path>, next: NextFunction) => void | Promise<void>;
23
- export type OutgoingEventHandler<Event extends string> = (event: OutgoingContext<Event>, next: NextFunction) => PondMessage | Promise<PondMessage> | void | Promise<void>;
20
+ export type EventPayload<EventMap extends PondEventMap, Event extends keyof EventMap> = EventMap[Event] extends [PondMessage, PondMessage] ? EventMap[Event][0] : EventMap[Event];
21
+ export type EventResponse<EventMap extends PondEventMap, Event extends keyof EventMap> = EventMap[Event] extends [PondMessage, PondMessage] ? EventMap[Event][1] : PondMessage;
22
+ export type EventKey<Schema extends AnyPondSchema> = Extract<keyof EventsOf<Schema>, string>;
23
+ export type ConnectionHandler<Path extends string> = (ctx: ConnectionContext<Path>, next: NextFunction) => unknown | Promise<unknown>;
24
+ export type AuthorizationHandler<Path extends string, Schema extends AnyPondSchema = AnyPondSchema> = (ctx: JoinContext<Path, Schema>, next: NextFunction) => unknown | Promise<unknown>;
25
+ export type EventHandler<Path extends string, Schema extends AnyPondSchema = AnyPondSchema, Event extends EventKey<Schema> = EventKey<Schema>> = (ctx: EventContext<Path, Schema, Event>, next: NextFunction) => unknown | Promise<unknown>;
26
+ export type OutgoingEventHandler<Path extends string, Schema extends AnyPondSchema = AnyPondSchema, Event extends EventKey<Schema> = EventKey<Schema>> = (event: OutgoingContext<Path, Schema, Event>, next: NextFunction) => EventPayload<EventsOf<Schema>, Event> | Promise<EventPayload<EventsOf<Schema>, Event>> | void | Promise<void>;
24
27
  export interface ConnectionParams {
25
28
  head: Buffer;
26
29
  socket: internal.Duplex;
@@ -32,16 +35,17 @@ export interface SocketCache {
32
35
  socket: WebSocket;
33
36
  assigns: PondAssigns;
34
37
  subscriptions: Set<Unsubscribe>;
38
+ channelSubscriptions?: Map<string, Unsubscribe>;
35
39
  }
36
40
  export interface ConnectionResponseOptions {
37
41
  engine: EndpointEngine;
38
42
  params: ConnectionParams;
39
43
  webSocketServer: WebSocketServer;
40
44
  }
41
- export interface JoinRequestOptions<Path extends string> {
45
+ export interface JoinRequestOptions<Path extends string, Schema extends AnyPondSchema = AnyPondSchema> {
42
46
  clientId: string;
43
- assigns: PondAssigns;
44
- joinParams: JoinParams;
47
+ assigns: AssignsOf<Schema>;
48
+ joinParams: JoinParamsOf<Schema>;
45
49
  params: EventParams<Path>;
46
50
  }
47
51
  export interface RequestCache extends SocketCache {
@@ -58,7 +62,7 @@ export type BroadcastEvent = Omit<InternalChannelEvent, 'action' | 'payload' | '
58
62
  payload: PondMessage;
59
63
  };
60
64
  export interface LeaveEvent {
61
- user: UserData;
65
+ user: UserData<PondPresence, PondAssigns>;
62
66
  channel: Channel;
63
67
  }
64
68
  export type LeaveCallback = (event: LeaveEvent) => void;
@@ -1,18 +1,23 @@
1
- import { ChannelReceivers, EventParams, PondEvent, PondMessage, PondObject, UserAssigns, UserPresences } from '@eleven-am/pondsocket-common';
1
+ import { ChannelReceivers, AnyPondSchema, AssignsOf, EventParams, EventPayload, EventsOf, PondEvent, PondMessage, PondObject, PresenceOf, UserAssigns, UserPresences } from '@eleven-am/pondsocket-common';
2
2
  import { ChannelEngine } from '../engines/channelEngine';
3
3
  import { Channel } from '../wrappers/channel';
4
- export declare abstract class BaseContext<Path extends string> {
4
+ export declare abstract class BaseContext<Path extends string, Schema extends AnyPondSchema = AnyPondSchema, EventName extends Extract<keyof EventsOf<Schema>, string> = Extract<keyof EventsOf<Schema>, string>, Payload extends PondMessage = EventPayload<EventsOf<Schema>, EventName>> {
5
5
  #private;
6
- constructor(engine: ChannelEngine, params: EventParams<Path>, event: string, payload: PondMessage, sender: string);
7
- get event(): PondEvent<Path>;
6
+ protected constructor(engine: ChannelEngine, params: EventParams<Path>, event: string, payload: Payload, sender: string);
7
+ get event(): PondEvent<Path> & {
8
+ event: EventName;
9
+ payload: Payload;
10
+ };
11
+ get query(): EventParams<Path>['query'];
12
+ get params(): EventParams<Path>['params'];
8
13
  get channelName(): string;
9
- get channel(): Channel;
10
- get presences(): UserPresences;
11
- get assigns(): UserAssigns;
12
- get user(): import("@eleven-am/pondsocket-common").UserData<PondObject, PondObject>;
13
- broadcast(event: string, payload: PondMessage): this;
14
- broadcastFrom(event: string, payload: PondMessage): this;
15
- broadcastTo(event: string, payload: PondMessage, userIds: string | string[]): this;
14
+ get channel(): Channel<Schema>;
15
+ get presences(): UserPresences<PresenceOf<Schema>>;
16
+ get assigns(): UserAssigns<AssignsOf<Schema>>;
17
+ get user(): import("@eleven-am/pondsocket-common").UserData<PresenceOf<Schema>, AssignsOf<Schema>> | null;
18
+ broadcast<Event extends Extract<keyof EventsOf<Schema>, string>>(event: Event, payload: EventPayload<EventsOf<Schema>, Event>): this;
19
+ broadcastFrom<Event extends Extract<keyof EventsOf<Schema>, string>>(event: Event, payload: EventPayload<EventsOf<Schema>, Event>): this;
20
+ broadcastTo<Event extends Extract<keyof EventsOf<Schema>, string>>(event: Event, payload: EventPayload<EventsOf<Schema>, Event>, userIds: string | string[]): this;
16
21
  protected abstract _sendMessageToRecipients(recipient: ChannelReceivers, event: string, payload: PondObject): void;
17
22
  protected get engine(): ChannelEngine;
18
23
  protected get sender(): string;
@@ -36,6 +36,12 @@ class BaseContext {
36
36
  payload: __classPrivateFieldGet(this, _BaseContext_payload, "f"),
37
37
  };
38
38
  }
39
+ get query() {
40
+ return __classPrivateFieldGet(this, _BaseContext_params, "f").query;
41
+ }
42
+ get params() {
43
+ return __classPrivateFieldGet(this, _BaseContext_params, "f").params;
44
+ }
39
45
  get channelName() {
40
46
  return __classPrivateFieldGet(this, _BaseContext_engine, "f").name;
41
47
  }
@@ -54,7 +54,7 @@ export declare class ConnectionContext<Path extends string> {
54
54
  /**
55
55
  * Accepts the connection request
56
56
  */
57
- accept(): ConnectionContext<Path>;
57
+ accept(assigns?: PondAssigns): ConnectionContext<Path>;
58
58
  /**
59
59
  * Declines the connection request
60
60
  */
@@ -115,12 +115,16 @@ class ConnectionContext {
115
115
  /**
116
116
  * Accepts the connection request
117
117
  */
118
- accept() {
118
+ accept(assigns) {
119
+ if (assigns) {
120
+ this.assign(assigns);
121
+ }
119
122
  __classPrivateFieldGet(this, _ConnectionContext_instances, "m", _ConnectionContext_performChecks).call(this);
120
123
  __classPrivateFieldGet(this, _ConnectionContext_webSocketServer, "f").handleUpgrade(__classPrivateFieldGet(this, _ConnectionContext_params, "f").request, __classPrivateFieldGet(this, _ConnectionContext_params, "f").socket, __classPrivateFieldGet(this, _ConnectionContext_params, "f").head, (ws) => {
121
124
  const cache = {
122
125
  clientId: __classPrivateFieldGet(this, _ConnectionContext_clientId, "f"),
123
126
  subscriptions: new Set(),
127
+ channelSubscriptions: new Map(),
124
128
  assigns: __classPrivateFieldGet(this, _ConnectionContext_assigns, "f"),
125
129
  socket: ws,
126
130
  };
@@ -1,15 +1,16 @@
1
- import { ChannelReceivers, EventParams, PondAssigns, PondMessage, PondObject, PondPresence } from '@eleven-am/pondsocket-common';
1
+ import { ChannelReceivers, AnyPondSchema, AssignsOf, EventParams, EventPayload, EventsOf, PondObject, PresenceOf } from '@eleven-am/pondsocket-common';
2
2
  import { BaseContext } from './baseContext';
3
3
  import { BroadcastEvent } from '../abstracts/types';
4
4
  import { ChannelEngine } from '../engines/channelEngine';
5
- export declare class EventContext<Path extends string> extends BaseContext<Path> {
5
+ export declare class EventContext<Path extends string, Schema extends AnyPondSchema = AnyPondSchema, EventName extends Extract<keyof EventsOf<Schema>, string> = Extract<keyof EventsOf<Schema>, string>> extends BaseContext<Path, Schema, EventName, EventPayload<EventsOf<Schema>, EventName>> {
6
6
  #private;
7
7
  constructor(event: BroadcastEvent, params: EventParams<Path>, engine: ChannelEngine);
8
- assign(assigns: PondAssigns): EventContext<Path>;
9
- reply(event: string, payload: PondMessage): this;
10
- trackPresence(presence: PondPresence, userId?: string): EventContext<Path>;
11
- updatePresence(presence: PondPresence, userId?: string): EventContext<Path>;
12
- evictUser(reason: string, userId?: string): EventContext<Path>;
13
- removePresence(userId?: string): EventContext<Path>;
8
+ get payload(): EventPayload<EventsOf<Schema>, EventName>;
9
+ assign(assigns: Partial<AssignsOf<Schema>>): EventContext<Path, Schema, EventName>;
10
+ reply<Event extends Extract<keyof EventsOf<Schema>, string>>(event: Event, payload: EventPayload<EventsOf<Schema>, Event>): this;
11
+ trackPresence(presence: PresenceOf<Schema>, userId?: string): EventContext<Path, Schema, EventName>;
12
+ updatePresence(presence: Partial<PresenceOf<Schema>>, userId?: string): EventContext<Path, Schema, EventName>;
13
+ evictUser(reason: string, userId?: string): EventContext<Path, Schema, EventName>;
14
+ removePresence(userId?: string): EventContext<Path, Schema, EventName>;
14
15
  protected _sendMessageToRecipients(recipient: ChannelReceivers, event: string, payload: PondObject): void;
15
16
  }
@@ -23,6 +23,9 @@ class EventContext extends baseContext_1.BaseContext {
23
23
  __classPrivateFieldSet(this, _EventContext_event, event, "f");
24
24
  __classPrivateFieldSet(this, _EventContext_requestId, event.requestId, "f");
25
25
  }
26
+ get payload() {
27
+ return __classPrivateFieldGet(this, _EventContext_event, "f").payload;
28
+ }
26
29
  assign(assigns) {
27
30
  this.channel.updateAssigns(__classPrivateFieldGet(this, _EventContext_event, "f").sender, assigns);
28
31
  return this;
@@ -1,17 +1,17 @@
1
- import { ChannelReceivers, JoinParams, PondAssigns, PondMessage, PondObject, PondPresence, UserData } from '@eleven-am/pondsocket-common';
1
+ import { ChannelReceivers, AnyPondSchema, AssignsOf, EventsOf, JoinParamsOf, PondMessage, PondObject, PresenceOf, UserData } from '@eleven-am/pondsocket-common';
2
2
  import { BaseContext } from './baseContext';
3
3
  import { JoinRequestOptions, RequestCache } from '../abstracts/types';
4
4
  import { ChannelEngine } from '../engines/channelEngine';
5
- export declare class JoinContext<Path extends string> extends BaseContext<Path> {
5
+ export declare class JoinContext<Path extends string, Schema extends AnyPondSchema = AnyPondSchema> extends BaseContext<Path, Schema, Extract<keyof EventsOf<Schema>, string>, JoinParamsOf<Schema>> {
6
6
  #private;
7
- constructor(options: JoinRequestOptions<Path>, engine: ChannelEngine, user: RequestCache);
8
- get user(): UserData;
9
- get joinParams(): JoinParams;
7
+ constructor(options: JoinRequestOptions<Path, Schema>, engine: ChannelEngine, user: RequestCache);
8
+ get user(): UserData<PresenceOf<Schema>, AssignsOf<Schema>>;
9
+ get joinParams(): JoinParamsOf<Schema>;
10
10
  get hasResponded(): boolean;
11
- accept(): JoinContext<Path>;
12
- decline(message?: string, errorCode?: number): JoinContext<Path>;
13
- assign(assigns: PondAssigns): JoinContext<Path>;
14
- reply(event: string, payload: PondMessage): JoinContext<Path>;
15
- trackPresence(presence: PondPresence): JoinContext<Path>;
11
+ accept(assigns?: Partial<AssignsOf<Schema>>): JoinContext<Path, Schema>;
12
+ decline(message?: string, errorCode?: number): JoinContext<Path, Schema>;
13
+ assign(assigns: Partial<AssignsOf<Schema>>): JoinContext<Path, Schema>;
14
+ reply(event: string, payload: PondMessage): JoinContext<Path, Schema>;
15
+ trackPresence(presence: PresenceOf<Schema>): JoinContext<Path, Schema>;
16
16
  protected _sendMessageToRecipients(recipient: ChannelReceivers, event: string, payload: PondObject): void;
17
17
  }
@@ -44,11 +44,22 @@ class JoinContext extends baseContext_1.BaseContext {
44
44
  get hasResponded() {
45
45
  return __classPrivateFieldGet(this, _JoinContext_executed, "f");
46
46
  }
47
- accept() {
47
+ accept(assigns) {
48
+ var _a;
49
+ if (assigns) {
50
+ this.assign(assigns);
51
+ }
48
52
  __classPrivateFieldGet(this, _JoinContext_instances, "m", _JoinContext_performChecks).call(this);
49
53
  const onMessage = this.engine.parent.parent.sendMessage.bind(this.engine.parent.parent, __classPrivateFieldGet(this, _JoinContext_user, "f").socket);
50
54
  const subscription = this.engine.addUser(__classPrivateFieldGet(this, _JoinContext_user, "f").clientId, __classPrivateFieldGet(this, _JoinContext_newAssigns, "f"), onMessage);
51
- __classPrivateFieldGet(this, _JoinContext_user, "f").subscriptions.add(subscription);
55
+ const wrappedSubscription = () => {
56
+ var _a;
57
+ subscription();
58
+ __classPrivateFieldGet(this, _JoinContext_user, "f").subscriptions.delete(wrappedSubscription);
59
+ (_a = __classPrivateFieldGet(this, _JoinContext_user, "f").channelSubscriptions) === null || _a === void 0 ? void 0 : _a.delete(this.engine.name);
60
+ };
61
+ __classPrivateFieldGet(this, _JoinContext_user, "f").subscriptions.add(wrappedSubscription);
62
+ (_a = __classPrivateFieldGet(this, _JoinContext_user, "f").channelSubscriptions) === null || _a === void 0 ? void 0 : _a.set(this.engine.name, wrappedSubscription);
52
63
  __classPrivateFieldSet(this, _JoinContext_accepted, true, "f");
53
64
  return this;
54
65
  }
@@ -1,10 +1,10 @@
1
- import { ChannelReceivers, Event, EventParams, PondMessage, PondObject } from '@eleven-am/pondsocket-common';
1
+ import { ChannelReceivers, AnyPondSchema, Event, EventParams, EventPayload, EventsOf, PondObject } from '@eleven-am/pondsocket-common';
2
2
  import { BaseContext } from './baseContext';
3
3
  import { ChannelEngine } from '../engines/channelEngine';
4
- export declare class OutgoingContext<Path extends string> extends BaseContext<Path> {
4
+ export declare class OutgoingContext<Path extends string, Schema extends AnyPondSchema = AnyPondSchema, EventName extends Extract<keyof EventsOf<Schema>, string> = Extract<keyof EventsOf<Schema>, string>> extends BaseContext<Path, Schema, EventName, EventPayload<EventsOf<Schema>, EventName>> {
5
5
  #private;
6
6
  constructor(event: Event, params: EventParams<Path>, engine: ChannelEngine, userid: string);
7
- get payload(): PondMessage;
7
+ get payload(): EventPayload<EventsOf<Schema>, EventName>;
8
8
  /**
9
9
  * Blocks the outgoing context, preventing further processing of the event.
10
10
  */
@@ -18,7 +18,7 @@ export declare class OutgoingContext<Path extends string> extends BaseContext<Pa
18
18
  * Transforms the outgoing context with a new payload.
19
19
  * @param payload - The new payload to set for the context.
20
20
  */
21
- transform(payload: PondMessage): this;
21
+ transform(payload: EventPayload<EventsOf<Schema>, EventName>): this;
22
22
  /**
23
23
  * Updates the parameters of the outgoing context.
24
24
  * @param params - The new parameters to set for the context.
@@ -30,7 +30,7 @@ var __rest = (this && this.__rest) || function (s, e) {
30
30
  }
31
31
  return t;
32
32
  };
33
- var _ChannelEngine_instances, _ChannelEngine_endpointId, _ChannelEngine_backend, _ChannelEngine_nodeId, _ChannelEngine_presenceEngine, _ChannelEngine_assignsCache, _ChannelEngine_userSubscriptions, _ChannelEngine_publisher, _ChannelEngine_distributedSubscription, _ChannelEngine_heartbeatSubscription, _ChannelEngine_nodeLastSeen, _ChannelEngine_nodeUsers, _ChannelEngine_staleNodeTimer, _ChannelEngine_name, _ChannelEngine_safeRemovePresence, _ChannelEngine_buildSubscriber, _ChannelEngine_getOrCreatePresenceEngine, _ChannelEngine_getUsersFromRecipients, _ChannelEngine_setupDistributedSubscription, _ChannelEngine_handleDistributedMessage, _ChannelEngine_requestChannelState, _ChannelEngine_handleStateRequest, _ChannelEngine_handleStateResponse, _ChannelEngine_handleRemoteUserJoined, _ChannelEngine_handleRemoteUserLeft, _ChannelEngine_handleRemoteMessage, _ChannelEngine_handleRemotePresenceUpdate, _ChannelEngine_handleRemotePresenceRemoved, _ChannelEngine_handleRemoteAssignsUpdate, _ChannelEngine_handleRemoteEvictUser, _ChannelEngine_setupHeartbeatTracking, _ChannelEngine_cleanupStaleNodes, _ChannelEngine_trackNodeUser, _ChannelEngine_untrackNodeUser, _ChannelEngine_broadcastToNodes;
33
+ var _ChannelEngine_instances, _ChannelEngine_endpointId, _ChannelEngine_backend, _ChannelEngine_nodeId, _ChannelEngine_presenceEngine, _ChannelEngine_assignsCache, _ChannelEngine_userSubscriptions, _ChannelEngine_publisher, _ChannelEngine_distributedSubscription, _ChannelEngine_heartbeatSubscription, _ChannelEngine_nodeLastSeen, _ChannelEngine_nodeUsers, _ChannelEngine_staleNodeTimer, _ChannelEngine_name, _ChannelEngine_closed, _ChannelEngine_distributedReady, _ChannelEngine_safeRemovePresence, _ChannelEngine_buildSubscriber, _ChannelEngine_getOrCreatePresenceEngine, _ChannelEngine_getUsersFromRecipients, _ChannelEngine_setupDistributedSubscription, _ChannelEngine_afterDistributedReady, _ChannelEngine_handleDistributedMessage, _ChannelEngine_requestChannelState, _ChannelEngine_handleStateRequest, _ChannelEngine_handleStateResponse, _ChannelEngine_handleRemoteUserJoined, _ChannelEngine_handleRemoteUserLeft, _ChannelEngine_handleRemoteMessage, _ChannelEngine_handleRemotePresenceUpdate, _ChannelEngine_handleRemotePresenceRemoved, _ChannelEngine_handleRemoteAssignsUpdate, _ChannelEngine_handleRemoteEvictUser, _ChannelEngine_setupHeartbeatTracking, _ChannelEngine_cleanupStaleNodes, _ChannelEngine_trackNodeUser, _ChannelEngine_untrackNodeUser, _ChannelEngine_broadcastToNodes;
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
35
  exports.ChannelEngine = void 0;
36
36
  const pondsocket_common_1 = require("@eleven-am/pondsocket-common");
@@ -61,12 +61,14 @@ class ChannelEngine {
61
61
  _ChannelEngine_nodeUsers.set(this, new Map());
62
62
  _ChannelEngine_staleNodeTimer.set(this, null);
63
63
  _ChannelEngine_name.set(this, void 0);
64
+ _ChannelEngine_closed.set(this, false);
65
+ _ChannelEngine_distributedReady.set(this, null);
64
66
  __classPrivateFieldSet(this, _ChannelEngine_name, name, "f");
65
67
  __classPrivateFieldSet(this, _ChannelEngine_backend, backend, "f");
66
68
  __classPrivateFieldSet(this, _ChannelEngine_nodeId, (0, pondsocket_common_1.uuid)(), "f");
67
69
  __classPrivateFieldSet(this, _ChannelEngine_endpointId, parent.parent.path, "f");
68
70
  if (__classPrivateFieldGet(this, _ChannelEngine_backend, "f")) {
69
- __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_setupDistributedSubscription).call(this).catch(() => { });
71
+ __classPrivateFieldSet(this, _ChannelEngine_distributedReady, __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_setupDistributedSubscription).call(this).catch(() => { }), "f");
70
72
  __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_setupHeartbeatTracking).call(this);
71
73
  }
72
74
  }
@@ -92,7 +94,7 @@ class ChannelEngine {
92
94
  __classPrivateFieldGet(this, _ChannelEngine_assignsCache, "f").set(userId, assigns);
93
95
  __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_buildSubscriber).call(this, userId, onMessage);
94
96
  if (isFirstUser && __classPrivateFieldGet(this, _ChannelEngine_backend, "f")) {
95
- __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_requestChannelState).call(this);
97
+ __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_afterDistributedReady).call(this, () => __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_requestChannelState).call(this));
96
98
  }
97
99
  __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_broadcastToNodes).call(this, {
98
100
  type: types_1.DistributedMessageType.USER_JOINED,
@@ -282,6 +284,7 @@ class ChannelEngine {
282
284
  };
283
285
  }
284
286
  close() {
287
+ __classPrivateFieldSet(this, _ChannelEngine_closed, true, "f");
285
288
  __classPrivateFieldGet(this, _ChannelEngine_userSubscriptions, "f").forEach((unsubscribe) => unsubscribe());
286
289
  __classPrivateFieldGet(this, _ChannelEngine_userSubscriptions, "f").clear();
287
290
  __classPrivateFieldGet(this, _ChannelEngine_assignsCache, "f").clear();
@@ -308,7 +311,7 @@ class ChannelEngine {
308
311
  }
309
312
  }
310
313
  exports.ChannelEngine = ChannelEngine;
311
- _ChannelEngine_endpointId = new WeakMap(), _ChannelEngine_backend = new WeakMap(), _ChannelEngine_nodeId = new WeakMap(), _ChannelEngine_presenceEngine = new WeakMap(), _ChannelEngine_assignsCache = new WeakMap(), _ChannelEngine_userSubscriptions = new WeakMap(), _ChannelEngine_publisher = new WeakMap(), _ChannelEngine_distributedSubscription = new WeakMap(), _ChannelEngine_heartbeatSubscription = new WeakMap(), _ChannelEngine_nodeLastSeen = new WeakMap(), _ChannelEngine_nodeUsers = new WeakMap(), _ChannelEngine_staleNodeTimer = new WeakMap(), _ChannelEngine_name = new WeakMap(), _ChannelEngine_instances = new WeakSet(), _ChannelEngine_safeRemovePresence = function _ChannelEngine_safeRemovePresence(userId) {
314
+ _ChannelEngine_endpointId = new WeakMap(), _ChannelEngine_backend = new WeakMap(), _ChannelEngine_nodeId = new WeakMap(), _ChannelEngine_presenceEngine = new WeakMap(), _ChannelEngine_assignsCache = new WeakMap(), _ChannelEngine_userSubscriptions = new WeakMap(), _ChannelEngine_publisher = new WeakMap(), _ChannelEngine_distributedSubscription = new WeakMap(), _ChannelEngine_heartbeatSubscription = new WeakMap(), _ChannelEngine_nodeLastSeen = new WeakMap(), _ChannelEngine_nodeUsers = new WeakMap(), _ChannelEngine_staleNodeTimer = new WeakMap(), _ChannelEngine_name = new WeakMap(), _ChannelEngine_closed = new WeakMap(), _ChannelEngine_distributedReady = new WeakMap(), _ChannelEngine_instances = new WeakSet(), _ChannelEngine_safeRemovePresence = function _ChannelEngine_safeRemovePresence(userId) {
312
315
  if (__classPrivateFieldGet(this, _ChannelEngine_presenceEngine, "f")) {
313
316
  try {
314
317
  __classPrivateFieldGet(this, _ChannelEngine_presenceEngine, "f").removePresence(userId);
@@ -370,10 +373,25 @@ _ChannelEngine_endpointId = new WeakMap(), _ChannelEngine_backend = new WeakMap(
370
373
  if (!__classPrivateFieldGet(this, _ChannelEngine_backend, "f")) {
371
374
  return;
372
375
  }
373
- __classPrivateFieldSet(this, _ChannelEngine_distributedSubscription, yield __classPrivateFieldGet(this, _ChannelEngine_backend, "f").subscribeToChannel(__classPrivateFieldGet(this, _ChannelEngine_endpointId, "f"), __classPrivateFieldGet(this, _ChannelEngine_name, "f"), (message) => {
376
+ const unsubscribe = yield __classPrivateFieldGet(this, _ChannelEngine_backend, "f").subscribeToChannel(__classPrivateFieldGet(this, _ChannelEngine_endpointId, "f"), __classPrivateFieldGet(this, _ChannelEngine_name, "f"), (message) => {
374
377
  __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_handleDistributedMessage).call(this, message);
375
- }), "f");
378
+ });
379
+ if (__classPrivateFieldGet(this, _ChannelEngine_closed, "f")) {
380
+ unsubscribe();
381
+ return;
382
+ }
383
+ __classPrivateFieldSet(this, _ChannelEngine_distributedSubscription, unsubscribe, "f");
376
384
  });
385
+ }, _ChannelEngine_afterDistributedReady = function _ChannelEngine_afterDistributedReady(callback) {
386
+ if (!__classPrivateFieldGet(this, _ChannelEngine_distributedReady, "f")) {
387
+ callback();
388
+ return;
389
+ }
390
+ __classPrivateFieldGet(this, _ChannelEngine_distributedReady, "f").then(() => {
391
+ if (!__classPrivateFieldGet(this, _ChannelEngine_closed, "f")) {
392
+ return callback();
393
+ }
394
+ }).catch(() => { });
377
395
  }, _ChannelEngine_handleDistributedMessage = function _ChannelEngine_handleDistributedMessage(message) {
378
396
  switch (message.type) {
379
397
  case types_1.DistributedMessageType.STATE_REQUEST:
@@ -494,7 +512,17 @@ _ChannelEngine_endpointId = new WeakMap(), _ChannelEngine_backend = new WeakMap(
494
512
  }, _ChannelEngine_handleRemotePresenceRemoved = function _ChannelEngine_handleRemotePresenceRemoved(message) {
495
513
  __classPrivateFieldGet(this, _ChannelEngine_instances, "m", _ChannelEngine_safeRemovePresence).call(this, message.userId);
496
514
  }, _ChannelEngine_handleRemoteAssignsUpdate = function _ChannelEngine_handleRemoteAssignsUpdate(message) {
497
- __classPrivateFieldGet(this, _ChannelEngine_assignsCache, "f").set(message.userId, message.assigns);
515
+ const current = __classPrivateFieldGet(this, _ChannelEngine_assignsCache, "f").get(message.userId);
516
+ if (!current) {
517
+ return;
518
+ }
519
+ if (message.assigns) {
520
+ __classPrivateFieldGet(this, _ChannelEngine_assignsCache, "f").set(message.userId, message.assigns);
521
+ return;
522
+ }
523
+ if (message.key) {
524
+ __classPrivateFieldGet(this, _ChannelEngine_assignsCache, "f").set(message.userId, Object.assign(Object.assign({}, current), { [message.key]: message.value }));
525
+ }
498
526
  }, _ChannelEngine_handleRemoteEvictUser = function _ChannelEngine_handleRemoteEvictUser(message) {
499
527
  if (__classPrivateFieldGet(this, _ChannelEngine_userSubscriptions, "f").has(message.userId)) {
500
528
  const kickedOutEvent = {
@@ -576,6 +604,9 @@ _ChannelEngine_endpointId = new WeakMap(), _ChannelEngine_backend = new WeakMap(
576
604
  return;
577
605
  }
578
606
  try {
607
+ if (__classPrivateFieldGet(this, _ChannelEngine_closed, "f")) {
608
+ return;
609
+ }
579
610
  yield __classPrivateFieldGet(this, _ChannelEngine_backend, "f").broadcast(__classPrivateFieldGet(this, _ChannelEngine_endpointId, "f"), __classPrivateFieldGet(this, _ChannelEngine_name, "f"), message);
580
611
  }
581
612
  catch (_) {
@@ -1,4 +1,4 @@
1
- import { ChannelEvent, PondPath } from '@eleven-am/pondsocket-common';
1
+ import { ChannelEvent, AnyPondSchema, PondPath } from '@eleven-am/pondsocket-common';
2
2
  import { WebSocket } from 'ws';
3
3
  import { AuthorizationHandler, SocketCache } from '../abstracts/types';
4
4
  import { IDistributedBackend } from '../types';
@@ -10,7 +10,7 @@ export declare class EndpointEngine {
10
10
  /**
11
11
  * Creates a new channel on a specified path
12
12
  */
13
- createChannel<Path extends string>(path: PondPath<Path>, handler: AuthorizationHandler<Path>): PondChannel;
13
+ createChannel<Path extends string, Schema extends AnyPondSchema = AnyPondSchema>(path: PondPath<Path>, handler: AuthorizationHandler<Path, Schema>): PondChannel<Schema>;
14
14
  /**
15
15
  * Gets all connected clients
16
16
  */
@@ -52,7 +52,7 @@ class EndpointEngine {
52
52
  };
53
53
  const channel = lobbyEngine.getOrCreateChannel(user.channelName);
54
54
  const context = new joinContext_1.JoinContext(options, channel, user);
55
- return handler(context, next);
55
+ return Promise.resolve(handler(context, next)).then(() => undefined);
56
56
  }
57
57
  next();
58
58
  });
@@ -115,11 +115,13 @@ class EndpointEngine {
115
115
  }
116
116
  exports.EndpointEngine = EndpointEngine;
117
117
  _EndpointEngine_sockets = new WeakMap(), _EndpointEngine_backend = new WeakMap(), _EndpointEngine_middleware = new WeakMap(), _EndpointEngine_lobbyEngines = new WeakMap(), _EndpointEngine_maxMessageSize = new WeakMap(), _EndpointEngine_instances = new WeakSet(), _EndpointEngine_handleSocketClose = function _EndpointEngine_handleSocketClose(cache) {
118
+ var _a;
118
119
  try {
119
120
  __classPrivateFieldGet(this, _EndpointEngine_sockets, "f").delete(cache.clientId);
120
121
  cache.subscriptions.forEach((unsubscribe) => unsubscribe());
122
+ (_a = cache.channelSubscriptions) === null || _a === void 0 ? void 0 : _a.clear();
121
123
  }
122
- catch (_a) {
124
+ catch (_b) {
123
125
  void 0;
124
126
  }
125
127
  }, _EndpointEngine_handleMessage = function _EndpointEngine_handleMessage(cache, message) {
@@ -143,6 +145,12 @@ _EndpointEngine_sockets = new WeakMap(), _EndpointEngine_backend = new WeakMap()
143
145
  throw error;
144
146
  });
145
147
  }, _EndpointEngine_leaveChannel = function _EndpointEngine_leaveChannel(channel, socket) {
148
+ var _a;
149
+ const subscription = (_a = socket.channelSubscriptions) === null || _a === void 0 ? void 0 : _a.get(channel);
150
+ if (subscription) {
151
+ subscription();
152
+ return;
153
+ }
146
154
  const engine = __classPrivateFieldGet(this, _EndpointEngine_instances, "m", _EndpointEngine_retrieveChannel).call(this, channel);
147
155
  engine.removeUser(socket.clientId);
148
156
  }, _EndpointEngine_retrieveChannel = function _EndpointEngine_retrieveChannel(channel) {
@@ -1,4 +1,4 @@
1
- import { Event, PondPath } from '@eleven-am/pondsocket-common';
1
+ import { AnyPondSchema, Event, EventsOf, PondPath } from '@eleven-am/pondsocket-common';
2
2
  import { ChannelEngine } from './channelEngine';
3
3
  import { EndpointEngine } from './endpointEngine';
4
4
  import { Middleware } from '../abstracts/middleware';
@@ -20,11 +20,11 @@ export declare class LobbyEngine {
20
20
  /**
21
21
  * Attaches a handler for a specific event pattern
22
22
  */
23
- onEvent<Event extends string>(event: PondPath<Event>, handler: EventHandler<Event>): void;
23
+ onEvent<Schema extends AnyPondSchema, Path extends Extract<keyof EventsOf<Schema>, string>>(event: PondPath<Path>, handler: EventHandler<Path, Schema, Path>): void;
24
24
  /**
25
25
  * Attaches a handler for outgoing events
26
26
  */
27
- handleOutgoingEvent<Event extends string>(event: PondPath<Event>, handler: OutgoingEventHandler<Event>): void;
27
+ handleOutgoingEvent<Schema extends AnyPondSchema, Path extends Extract<keyof EventsOf<Schema>, string>>(event: PondPath<Path>, handler: OutgoingEventHandler<Path, Schema, Path>): void;
28
28
  /**
29
29
  * Processes an outgoing event, applying middleware and returning a new event
30
30
  * @param event - The channel event to process
@@ -56,7 +56,7 @@ class LobbyEngine {
56
56
  return next();
57
57
  }
58
58
  const context = new eventContext_1.EventContext(requestEvent, params, channel);
59
- return handler(context, next);
59
+ return Promise.resolve(handler(context, next)).then(() => undefined);
60
60
  });
61
61
  }
62
62
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eleven-am/pondsocket",
3
- "version": "0.1.215",
3
+ "version": "0.1.217",
4
4
  "description": "PondSocket is a fast simple socket server",
5
5
  "keywords": [
6
6
  "socket",
@@ -43,21 +43,23 @@
43
43
  "url": "git+https://github.com/Eleven-am/pondSocket.git"
44
44
  },
45
45
  "dependencies": {
46
- "@eleven-am/pondsocket-common": "^0.0.37",
47
- "redis": "^5.11.0",
48
- "ws": "^8.19.0"
46
+ "@eleven-am/pondsocket-common": "^0.0.39",
47
+ "@types/node": "^25.3.3",
48
+ "@types/ws": "^8.18.1",
49
+ "redis": "^5.12.1",
50
+ "ws": "^8.20.1"
49
51
  },
50
52
  "devDependencies": {
51
53
  "@eslint/compat": "^1.4.0",
52
54
  "@eslint/eslintrc": "^3.3.4",
53
- "@eslint/js": "^9.38.0",
55
+ "@eslint/js": "^9.39.4",
54
56
  "@stylistic/eslint-plugin-ts": "^4.4.1",
55
57
  "@types/jest": "^30.0.0",
56
58
  "@types/node": "^25.3.3",
57
59
  "@types/ws": "^8.18.1",
58
- "@typescript-eslint/eslint-plugin": "^8.56.1",
59
- "@typescript-eslint/parser": "^8.56.1",
60
- "eslint": "^9.38.0",
60
+ "@typescript-eslint/eslint-plugin": "^8.59.4",
61
+ "@typescript-eslint/parser": "^8.59.4",
62
+ "eslint": "^9.39.4",
61
63
  "eslint-config-prettier": "^10.1.8",
62
64
  "eslint-import-resolver-node": "^0.3.9",
63
65
  "eslint-plugin-file-progress": "^3.0.2",
@@ -65,11 +67,11 @@
65
67
  "eslint-plugin-prettier": "^5.5.5",
66
68
  "globals": "^16.4.0",
67
69
  "jest": "^30.2.0",
68
- "prettier": "^3.8.1",
70
+ "prettier": "^3.8.3",
69
71
  "source-map-support": "^0.5.21",
70
72
  "supertest": "^7.2.2",
71
- "ts-jest": "^29.4.6",
72
- "ts-loader": "^9.5.4",
73
+ "ts-jest": "^29.4.11",
74
+ "ts-loader": "^9.5.7",
73
75
  "ts-node": "^10.9.2",
74
76
  "tsconfig-paths": "^4.2.0",
75
77
  "typescript": "^5.9.3"
package/server/server.js CHANGED
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _PondSocket_instances, _PondSocket_server, _PondSocket_exclusiveServer, _PondSocket_socketServer, _PondSocket_backend, _PondSocket_middleware, _PondSocket_maxMessageSize, _PondSocket_heartbeatMs, _PondSocket_heartbeatTimer, _PondSocket_handleUpgrade, _PondSocket_manageHeartbeat, _PondSocket_init, _PondSocket_getCookies;
13
+ var _PondSocket_instances, _PondSocket_server, _PondSocket_exclusiveServer, _PondSocket_socketServer, _PondSocket_backend, _PondSocket_middleware, _PondSocket_maxMessageSize, _PondSocket_heartbeatMs, _PondSocket_heartbeatTimer, _PondSocket_backendReady, _PondSocket_handleUpgrade, _PondSocket_manageHeartbeat, _PondSocket_init, _PondSocket_getCookies;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.PondSocket = void 0;
16
16
  const http_1 = require("http");
@@ -32,6 +32,7 @@ class PondSocket {
32
32
  _PondSocket_maxMessageSize.set(this, void 0);
33
33
  _PondSocket_heartbeatMs.set(this, void 0);
34
34
  _PondSocket_heartbeatTimer.set(this, null);
35
+ _PondSocket_backendReady.set(this, null);
35
36
  __classPrivateFieldSet(this, _PondSocket_middleware, new middleware_1.Middleware(), "f");
36
37
  __classPrivateFieldSet(this, _PondSocket_exclusiveServer, exclusiveServer, "f");
37
38
  __classPrivateFieldSet(this, _PondSocket_server, server !== null && server !== void 0 ? server : new http_1.Server(), "f");
@@ -51,6 +52,7 @@ class PondSocket {
51
52
  * Close the server
52
53
  */
53
54
  close(callback, timeout = 5000) {
55
+ var _a, _b;
54
56
  if (__classPrivateFieldGet(this, _PondSocket_heartbeatTimer, "f")) {
55
57
  clearInterval(__classPrivateFieldGet(this, _PondSocket_heartbeatTimer, "f"));
56
58
  __classPrivateFieldSet(this, _PondSocket_heartbeatTimer, null, "f");
@@ -71,6 +73,7 @@ class PondSocket {
71
73
  __classPrivateFieldGet(this, _PondSocket_socketServer, "f").close(() => {
72
74
  clearTimeout(forceClose);
73
75
  });
76
+ __classPrivateFieldSet(this, _PondSocket_backendReady, (_b = (_a = __classPrivateFieldGet(this, _PondSocket_backendReady, "f")) === null || _a === void 0 ? void 0 : _a.catch(() => { }).then(() => { var _a; return (_a = __classPrivateFieldGet(this, _PondSocket_backend, "f")) === null || _a === void 0 ? void 0 : _a.cleanup(); }).catch(() => { })) !== null && _b !== void 0 ? _b : null, "f");
74
77
  return __classPrivateFieldGet(this, _PondSocket_server, "f").close(callback);
75
78
  }
76
79
  /**
@@ -90,13 +93,13 @@ class PondSocket {
90
93
  webSocketServer: __classPrivateFieldGet(this, _PondSocket_socketServer, "f"),
91
94
  };
92
95
  const context = new connectionContext_1.ConnectionContext(request, newParams);
93
- return handler(context, next);
96
+ return Promise.resolve(handler(context, next)).then(() => undefined);
94
97
  });
95
98
  return new endpoint_1.Endpoint(endpoint);
96
99
  }
97
100
  }
98
101
  exports.PondSocket = PondSocket;
99
- _PondSocket_server = new WeakMap(), _PondSocket_exclusiveServer = new WeakMap(), _PondSocket_socketServer = new WeakMap(), _PondSocket_backend = new WeakMap(), _PondSocket_middleware = new WeakMap(), _PondSocket_maxMessageSize = new WeakMap(), _PondSocket_heartbeatMs = new WeakMap(), _PondSocket_heartbeatTimer = new WeakMap(), _PondSocket_instances = new WeakSet(), _PondSocket_handleUpgrade = function _PondSocket_handleUpgrade(req, socket, head) {
102
+ _PondSocket_server = new WeakMap(), _PondSocket_exclusiveServer = new WeakMap(), _PondSocket_socketServer = new WeakMap(), _PondSocket_backend = new WeakMap(), _PondSocket_middleware = new WeakMap(), _PondSocket_maxMessageSize = new WeakMap(), _PondSocket_heartbeatMs = new WeakMap(), _PondSocket_heartbeatTimer = new WeakMap(), _PondSocket_backendReady = new WeakMap(), _PondSocket_instances = new WeakSet(), _PondSocket_handleUpgrade = function _PondSocket_handleUpgrade(req, socket, head) {
100
103
  const clientId = (0, pondsocket_common_1.uuid)();
101
104
  const request = {
102
105
  id: clientId,
@@ -137,7 +140,11 @@ _PondSocket_server = new WeakMap(), _PondSocket_exclusiveServer = new WeakMap(),
137
140
  });
138
141
  }, __classPrivateFieldGet(this, _PondSocket_heartbeatMs, "f"));
139
142
  }, _PondSocket_init = function _PondSocket_init() {
143
+ var _a, _b;
140
144
  __classPrivateFieldSet(this, _PondSocket_heartbeatTimer, __classPrivateFieldGet(this, _PondSocket_instances, "m", _PondSocket_manageHeartbeat).call(this), "f");
145
+ __classPrivateFieldSet(this, _PondSocket_backendReady, (_b = (_a = __classPrivateFieldGet(this, _PondSocket_backend, "f")) === null || _a === void 0 ? void 0 : _a.initialize().catch((error) => {
146
+ __classPrivateFieldGet(this, _PondSocket_server, "f").emit('error', error);
147
+ })) !== null && _b !== void 0 ? _b : null, "f");
141
148
  __classPrivateFieldGet(this, _PondSocket_server, "f").on('error', (error) => {
142
149
  if (__classPrivateFieldGet(this, _PondSocket_heartbeatTimer, "f")) {
143
150
  clearInterval(__classPrivateFieldGet(this, _PondSocket_heartbeatTimer, "f"));
package/types.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface RedisDistributedBackendOptions {
8
8
  database?: number;
9
9
  url?: string;
10
10
  keyPrefix?: string;
11
+ namespace?: string;
11
12
  heartbeatIntervalMs?: number;
12
13
  heartbeatTimeoutMs?: number;
13
14
  onError?: (error: Error) => void;
@@ -30,10 +31,16 @@ export declare enum DistributedMessageType {
30
31
  PRESENCE_REMOVED = "PRESENCE_REMOVED",
31
32
  ASSIGNS_UPDATE = "ASSIGNS_UPDATE",
32
33
  EVICT_USER = "EVICT_USER",
34
+ USER_REMOVE = "USER_REMOVE",
35
+ USER_GET_REQUEST = "USER_GET_REQUEST",
36
+ USER_GET_RESPONSE = "USER_GET_RESPONSE",
33
37
  NODE_HEARTBEAT = "NODE_HEARTBEAT"
34
38
  }
35
39
  export interface DistributedMessage {
40
+ protocol?: 'pondsocket.distributed';
41
+ version?: 1;
36
42
  type: DistributedMessageType;
43
+ messageId?: string;
37
44
  endpointName: string;
38
45
  channelName: string;
39
46
  timestamp?: number;
@@ -81,19 +88,38 @@ export interface PresenceRemoved extends DistributedMessage {
81
88
  export interface AssignsUpdate extends DistributedMessage {
82
89
  type: DistributedMessageType.ASSIGNS_UPDATE;
83
90
  userId: string;
84
- assigns: PondAssigns;
91
+ assigns?: PondAssigns;
92
+ key?: string;
93
+ value?: unknown;
85
94
  }
86
95
  export interface EvictUser extends DistributedMessage {
87
96
  type: DistributedMessageType.EVICT_USER;
88
97
  userId: string;
89
98
  reason: string;
90
- fromNode?: string;
99
+ }
100
+ export interface UserRemove extends DistributedMessage {
101
+ type: DistributedMessageType.USER_REMOVE;
102
+ userId: string;
103
+ reason: string;
104
+ }
105
+ export interface UserGetRequest extends DistributedMessage {
106
+ type: DistributedMessageType.USER_GET_REQUEST;
107
+ userId: string;
108
+ lookupRequestId: string;
109
+ }
110
+ export interface UserGetResponse extends DistributedMessage {
111
+ type: DistributedMessageType.USER_GET_RESPONSE;
112
+ lookupRequestId: string;
113
+ userId: string;
114
+ found: boolean;
115
+ assigns?: PondAssigns;
116
+ presence?: PondPresence;
91
117
  }
92
118
  export interface NodeHeartbeat extends DistributedMessage {
93
119
  type: DistributedMessageType.NODE_HEARTBEAT;
94
120
  nodeId: string;
95
121
  }
96
- export type DistributedChannelMessage = StateRequest | StateResponse | UserJoined | UserLeft | UserMessage | PresenceUpdate | PresenceRemoved | AssignsUpdate | EvictUser | NodeHeartbeat;
122
+ export type DistributedChannelMessage = StateRequest | StateResponse | UserJoined | UserLeft | UserMessage | PresenceUpdate | PresenceRemoved | AssignsUpdate | EvictUser | UserRemove | UserGetRequest | UserGetResponse | NodeHeartbeat;
97
123
  export interface IDistributedBackend {
98
124
  readonly nodeId: string;
99
125
  readonly heartbeatTimeoutMs: number;
package/types.js CHANGED
@@ -12,5 +12,8 @@ var DistributedMessageType;
12
12
  DistributedMessageType["PRESENCE_REMOVED"] = "PRESENCE_REMOVED";
13
13
  DistributedMessageType["ASSIGNS_UPDATE"] = "ASSIGNS_UPDATE";
14
14
  DistributedMessageType["EVICT_USER"] = "EVICT_USER";
15
+ DistributedMessageType["USER_REMOVE"] = "USER_REMOVE";
16
+ DistributedMessageType["USER_GET_REQUEST"] = "USER_GET_REQUEST";
17
+ DistributedMessageType["USER_GET_RESPONSE"] = "USER_GET_RESPONSE";
15
18
  DistributedMessageType["NODE_HEARTBEAT"] = "NODE_HEARTBEAT";
16
19
  })(DistributedMessageType || (exports.DistributedMessageType = DistributedMessageType = {}));
@@ -1,32 +1,32 @@
1
- import { PondMessage, PondAssigns, PondPresence } from '@eleven-am/pondsocket-common';
1
+ import { AnyPondSchema, AssignsOf, EventsOf, EventPayload, PresenceOf, UserData, UserAssigns, UserPresences } from '@eleven-am/pondsocket-common';
2
2
  import { ChannelEngine } from '../engines/channelEngine';
3
- export declare class Channel {
3
+ export declare class Channel<Schema extends AnyPondSchema = AnyPondSchema> {
4
4
  #private;
5
5
  constructor(engine: ChannelEngine);
6
6
  /**
7
7
  * Gets a user's data
8
8
  */
9
- getUserData(userId: string): import("@eleven-am/pondsocket-common").UserData<import("@eleven-am/pondsocket-common").PondObject, import("@eleven-am/pondsocket-common").PondObject>;
9
+ getUserData(userId: string): UserData<PresenceOf<Schema>, AssignsOf<Schema>> | null;
10
10
  /**
11
11
  * Gets all presence data
12
12
  */
13
- getPresences(): import("@eleven-am/pondsocket-common").UserPresences;
13
+ getPresences(): UserPresences<PresenceOf<Schema>>;
14
14
  /**
15
15
  * Gets all assigns data
16
16
  */
17
- getAssigns(): import("@eleven-am/pondsocket-common").UserAssigns;
17
+ getAssigns(): UserAssigns<AssignsOf<Schema>>;
18
18
  /**
19
19
  * Broadcasts a message to all users
20
20
  */
21
- broadcast(event: string, payload: PondMessage): this;
21
+ broadcast<Event extends Extract<keyof EventsOf<Schema>, string>>(event: Event, payload: EventPayload<EventsOf<Schema>, Event>): this;
22
22
  /**
23
23
  * Broadcasts a message from a specific user to all others
24
24
  */
25
- broadcastFrom(userId: string, event: string, payload: PondMessage): this;
25
+ broadcastFrom<Event extends Extract<keyof EventsOf<Schema>, string>>(userId: string, event: Event, payload: EventPayload<EventsOf<Schema>, Event>): this;
26
26
  /**
27
27
  * Broadcasts a message to specific users
28
28
  */
29
- broadcastTo(userIds: string | string[], event: string, payload: PondMessage): this;
29
+ broadcastTo<Event extends Extract<keyof EventsOf<Schema>, string>>(userIds: string | string[], event: Event, payload: EventPayload<EventsOf<Schema>, Event>): this;
30
30
  /**
31
31
  * Kicks a user from the channel
32
32
  */
@@ -34,11 +34,11 @@ export declare class Channel {
34
34
  /**
35
35
  * Tracks a user's presence
36
36
  */
37
- trackPresence(userId: string, presence: PondPresence): this;
37
+ trackPresence(userId: string, presence: PresenceOf<Schema>): this;
38
38
  /**
39
39
  * Updates a user's presence
40
40
  */
41
- updatePresence(userId: string, presence: PondPresence): this;
41
+ updatePresence(userId: string, presence: Partial<PresenceOf<Schema>>): this;
42
42
  /**
43
43
  * Removes a user's presence
44
44
  */
@@ -46,9 +46,9 @@ export declare class Channel {
46
46
  /**
47
47
  * Adds or updates a user's presence
48
48
  */
49
- upsertPresence(userId: string, presence: PondPresence): this;
49
+ upsertPresence(userId: string, presence: PresenceOf<Schema>): this;
50
50
  /**
51
51
  * Updates a user's assigns
52
52
  */
53
- updateAssigns(userId: string, assigns: PondAssigns): this;
53
+ updateAssigns(userId: string, assigns: Partial<AssignsOf<Schema>>): this;
54
54
  }
@@ -1,10 +1,10 @@
1
- import { PondPath } from '@eleven-am/pondsocket-common';
1
+ import { AnyPondSchema, PondPath } from '@eleven-am/pondsocket-common';
2
2
  import { AuthorizationHandler } from '../abstracts/types';
3
3
  import { EndpointEngine } from '../engines/endpointEngine';
4
4
  export declare class Endpoint {
5
5
  #private;
6
6
  constructor(engine: EndpointEngine);
7
- createChannel<Path extends string>(path: PondPath<Path>, handler: AuthorizationHandler<Path>): import("./pondChannel").PondChannel;
7
+ createChannel<Path extends string, Schema extends AnyPondSchema = AnyPondSchema>(path: PondPath<Path>, handler: AuthorizationHandler<Path, Schema>): import("./pondChannel").PondChannel<Schema>;
8
8
  closeConnection(clientIds: string | string[]): void;
9
9
  getClients(): import("ws").WebSocket[];
10
10
  }
@@ -1,15 +1,15 @@
1
- import { PondMessage, PondPath } from '@eleven-am/pondsocket-common';
1
+ import { AnyPondSchema, EventPayload, EventsOf, PondPath } from '@eleven-am/pondsocket-common';
2
2
  import { Channel } from './channel';
3
3
  import { EventHandler, LeaveCallback, OutgoingEventHandler } from '../abstracts/types';
4
4
  import { LobbyEngine } from '../engines/lobbyEngine';
5
- export declare class PondChannel {
5
+ export declare class PondChannel<Schema extends AnyPondSchema = AnyPondSchema> {
6
6
  #private;
7
7
  constructor(lobby: LobbyEngine);
8
- onEvent<Event extends string>(event: PondPath<Event>, handler: EventHandler<Event>): PondChannel;
9
- onLeave(callback: LeaveCallback): PondChannel;
10
- handleOutgoingEvent<Event extends string>(event: PondPath<Event>, handler: OutgoingEventHandler<Event>): this;
11
- getChannel(channelName: string): Channel | null;
12
- broadcast(channelName: string, event: string, payload: PondMessage): PondChannel;
13
- broadcastFrom(channelName: string, userId: string, event: string, payload: PondMessage): PondChannel;
14
- broadcastTo(channelName: string, userIds: string | string[], event: string, payload: PondMessage): PondChannel;
8
+ onEvent<Event extends Extract<keyof EventsOf<Schema>, string>>(event: PondPath<Event>, handler: EventHandler<Event, Schema, Event>): PondChannel<Schema>;
9
+ onLeave(callback: LeaveCallback): PondChannel<Schema>;
10
+ handleOutgoingEvent<Event extends Extract<keyof EventsOf<Schema>, string>>(event: PondPath<Event>, handler: OutgoingEventHandler<Event, Schema, Event>): this;
11
+ getChannel(channelName: string): Channel<Schema> | null;
12
+ broadcast<Event extends Extract<keyof EventsOf<Schema>, string>>(channelName: string, event: Event, payload: EventPayload<EventsOf<Schema>, Event>): PondChannel<Schema>;
13
+ broadcastFrom<Event extends Extract<keyof EventsOf<Schema>, string>>(channelName: string, userId: string, event: Event, payload: EventPayload<EventsOf<Schema>, Event>): PondChannel<Schema>;
14
+ broadcastTo<Event extends Extract<keyof EventsOf<Schema>, string>>(channelName: string, userIds: string | string[], event: Event, payload: EventPayload<EventsOf<Schema>, Event>): PondChannel<Schema>;
15
15
  }