@verdant-web/server 4.1.1 → 4.2.1
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/esm/adapters/node/websocketHandler.js +2 -2
- package/dist/esm/adapters/node/websocketHandler.js.map +1 -1
- package/dist/esm/connections/ClientConnection.d.ts +18 -6
- package/dist/esm/connections/ClientConnection.js +62 -17
- package/dist/esm/connections/ClientConnection.js.map +1 -1
- package/dist/esm/connections/Presence.d.ts +29 -12
- package/dist/esm/connections/Presence.js +54 -44
- package/dist/esm/connections/Presence.js.map +1 -1
- package/dist/esm/internals.d.ts +1 -0
- package/dist/esm/internals.js.map +1 -1
- package/dist/esm/libraries/Library.d.ts +6 -7
- package/dist/esm/libraries/Library.js +22 -13
- package/dist/esm/libraries/Library.js.map +1 -1
- package/dist/esm/libraries/Library.test.js +50 -37
- package/dist/esm/libraries/Library.test.js.map +1 -1
- package/dist/esm/libraries/singleNode.js +1 -2
- package/dist/esm/libraries/singleNode.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/node/websocketHandler.ts +2 -2
- package/src/connections/ClientConnection.ts +75 -18
- package/src/connections/Presence.ts +86 -50
- package/src/internals.ts +1 -0
- package/src/libraries/Library.test.ts +79 -68
- package/src/libraries/Library.ts +26 -29
- package/src/libraries/singleNode.ts +1 -2
|
@@ -11,8 +11,8 @@ export function createNodeWebsocketHandler(core) {
|
|
|
11
11
|
const data = JSON.parse(message.toString());
|
|
12
12
|
await microserver.handleMessage(key, info, data);
|
|
13
13
|
});
|
|
14
|
-
ws.on('close', () => {
|
|
15
|
-
microserver.clientConnections.remove(key);
|
|
14
|
+
ws.on('close', async () => {
|
|
15
|
+
await microserver.clientConnections.remove(key);
|
|
16
16
|
core.log('debug', 'Connection closed', { key, userId: info.userId });
|
|
17
17
|
});
|
|
18
18
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocketHandler.js","sourceRoot":"","sources":["../../../../src/adapters/node/websocketHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,YAAY,EACZ,MAAM,EACN,UAAU,GACV,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC1B,OAAO,EAAa,eAAe,EAAE,MAAM,IAAI,CAAC;AAIhD,MAAM,UAAU,0BAA0B,CAAC,IAA8B;IACxE,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,GAAG,CAAC,EAAE,CACL,YAAY,EACZ,KAAK,EAAE,EAAa,EAAE,GAAoB,EAAE,IAAe,EAAE,EAAE;QAC9D,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;QAEzB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEnD,WAAW,CAAC,iBAAiB,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAEvD,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,OAAY,EAAE,EAAE;YACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAkB,CAAC;YAC7D,MAAM,WAAW,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"websocketHandler.js","sourceRoot":"","sources":["../../../../src/adapters/node/websocketHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,26 +1,38 @@
|
|
|
1
|
-
import { ServerMessage } from '@verdant-web/common';
|
|
1
|
+
import { EventSubscriber, ServerMessage } from '@verdant-web/common';
|
|
2
2
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
3
3
|
import { WebSocket as WSWebSocket } from 'ws';
|
|
4
4
|
import { Logger } from '../logger.js';
|
|
5
5
|
import { UserProfileLoader } from '../Profiles.js';
|
|
6
6
|
import { TokenInfo } from '../TokenVerifier.js';
|
|
7
7
|
import { MessageSender } from './MessageSender.js';
|
|
8
|
-
import { Presence } from './Presence.js';
|
|
9
|
-
export
|
|
8
|
+
import { Presence, PresenceStorage } from './Presence.js';
|
|
9
|
+
export type ClientConnectionsManagerEvents = {
|
|
10
|
+
replicaLost: (replicaId: string, connectionKey: string) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare class ClientConnectionManager extends EventSubscriber<ClientConnectionsManagerEvents> implements MessageSender {
|
|
10
13
|
private readonly connections;
|
|
14
|
+
private connectionToUser;
|
|
15
|
+
private userToConnections;
|
|
11
16
|
readonly presence: Presence;
|
|
12
17
|
private readonly log?;
|
|
13
|
-
constructor({ profiles, log, }: {
|
|
18
|
+
constructor({ profiles, presenceStorage, log, }: {
|
|
14
19
|
profiles: UserProfileLoader<any>;
|
|
20
|
+
presenceStorage?: PresenceStorage;
|
|
15
21
|
log?: Logger;
|
|
16
22
|
});
|
|
23
|
+
private addMappings;
|
|
24
|
+
/**
|
|
25
|
+
* Removes back-mappings and, if all are deleted,
|
|
26
|
+
* also removes user presence.
|
|
27
|
+
*/
|
|
28
|
+
private removeMappings;
|
|
17
29
|
addSocket: (key: string, socket: WebSocket | WSWebSocket, info: TokenInfo, options?: {
|
|
18
30
|
disableAutoRemoveOnClose?: boolean;
|
|
19
31
|
}) => void;
|
|
20
32
|
addRequest: (key: string, request: IncomingMessage, response: ServerResponse, info: TokenInfo) => () => void;
|
|
21
33
|
addFetch: (key: string, req: Request, info: TokenInfo) => (() => Response);
|
|
22
|
-
remove: (key: string) => void
|
|
23
|
-
respond: (key: string, message: ServerMessage) =>
|
|
34
|
+
remove: (key: string) => Promise<void>;
|
|
35
|
+
respond: (key: string, message: ServerMessage) => boolean;
|
|
24
36
|
/**
|
|
25
37
|
* Broadcasts a message to all live-connected clients in a library.
|
|
26
38
|
* This does not reach push/pull clients.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { authz } from '@verdant-web/common';
|
|
1
|
+
import { authz, EventSubscriber } from '@verdant-web/common';
|
|
2
2
|
import { Presence } from './Presence.js';
|
|
3
3
|
class ClientConnection {
|
|
4
4
|
constructor(key, info) {
|
|
@@ -96,31 +96,45 @@ class WebSocketClientConnection extends ClientConnection {
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
export class ClientConnectionManager {
|
|
100
|
-
constructor({ profiles, log, }) {
|
|
99
|
+
export class ClientConnectionManager extends EventSubscriber {
|
|
100
|
+
constructor({ profiles, presenceStorage, log, }) {
|
|
101
|
+
super();
|
|
101
102
|
this.connections = new Map();
|
|
103
|
+
this.connectionToUser = new Map();
|
|
104
|
+
this.userToConnections = new Map();
|
|
102
105
|
this.addSocket = (key, socket, info, options) => {
|
|
103
106
|
const connection = new WebSocketClientConnection(key, socket, info);
|
|
104
107
|
this.connections.set(key, connection);
|
|
108
|
+
this.addMappings(info.userId, key);
|
|
105
109
|
const unsubscribes = [];
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Listen for websocket close and remove connection and mappings.
|
|
112
|
+
* If not disabled, also broadcast presence offline for the final
|
|
113
|
+
* connection per user.
|
|
114
|
+
*
|
|
115
|
+
* This may be disabled for environments where a closed connection
|
|
116
|
+
* doesn't necessarily indicate offline - Cloudflare is like this
|
|
117
|
+
* since it "hibernates" sockets but keeps them connected to the
|
|
118
|
+
* client side.
|
|
119
|
+
*/
|
|
120
|
+
unsubscribes.push(subscribeToSocketEvent(socket, 'close', async () => {
|
|
121
|
+
var _a;
|
|
122
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.call(this, 'info', `Socket closed: ${key}`);
|
|
123
|
+
if (!(options === null || options === void 0 ? void 0 : options.disableAutoRemoveOnClose)) {
|
|
124
|
+
await this.remove(key);
|
|
125
|
+
}
|
|
126
|
+
unsubscribes.forEach((u) => u());
|
|
127
|
+
}));
|
|
115
128
|
unsubscribes.push(subscribeToSocketEvent(socket, 'messsage', () => {
|
|
116
129
|
// sockets get a long keepalive for message activity
|
|
117
130
|
// since they have a reliable close event
|
|
118
|
-
this.presence.keepAlive(
|
|
131
|
+
this.presence.keepAlive(info.userId, 60 * 1000);
|
|
119
132
|
}));
|
|
120
133
|
};
|
|
121
134
|
this.addRequest = (key, request, response, info) => {
|
|
122
135
|
const connection = new RequestClientConnection(key, request, response, info);
|
|
123
136
|
this.connections.set(key, connection);
|
|
137
|
+
this.addMappings(info.userId, key);
|
|
124
138
|
return () => {
|
|
125
139
|
connection.end();
|
|
126
140
|
this.connections.delete(key);
|
|
@@ -129,23 +143,27 @@ export class ClientConnectionManager {
|
|
|
129
143
|
this.addFetch = (key, req, info) => {
|
|
130
144
|
const connection = new FetchClientConnection(key, req, info);
|
|
131
145
|
this.connections.set(key, connection);
|
|
146
|
+
this.addMappings(info.userId, key);
|
|
132
147
|
return () => {
|
|
133
148
|
connection.end();
|
|
134
149
|
this.connections.delete(key);
|
|
135
150
|
return connection.res;
|
|
136
151
|
};
|
|
137
152
|
};
|
|
138
|
-
this.remove = (key) => {
|
|
153
|
+
this.remove = async (key) => {
|
|
139
154
|
this.connections.delete(key);
|
|
140
|
-
this.
|
|
155
|
+
await this.removeMappings(key);
|
|
141
156
|
};
|
|
142
157
|
this.respond = (key, message) => {
|
|
158
|
+
var _a;
|
|
143
159
|
const connection = this.connections.get(key);
|
|
144
160
|
if (connection) {
|
|
145
161
|
connection.respond(message);
|
|
162
|
+
return true;
|
|
146
163
|
}
|
|
147
164
|
else {
|
|
148
|
-
|
|
165
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.call(this, 'warn', `Could not respond: no connection found for key: ${key}`);
|
|
166
|
+
return false;
|
|
149
167
|
}
|
|
150
168
|
};
|
|
151
169
|
/**
|
|
@@ -167,8 +185,35 @@ export class ClientConnectionManager {
|
|
|
167
185
|
}
|
|
168
186
|
this.connections.clear();
|
|
169
187
|
};
|
|
170
|
-
this.presence = new Presence(profiles);
|
|
188
|
+
this.presence = new Presence(profiles, presenceStorage);
|
|
171
189
|
this.log = log;
|
|
172
190
|
}
|
|
191
|
+
addMappings(userId, connectionKey) {
|
|
192
|
+
this.connectionToUser.set(connectionKey, userId);
|
|
193
|
+
if (!this.userToConnections.has(userId)) {
|
|
194
|
+
this.userToConnections.set(userId, new Set());
|
|
195
|
+
}
|
|
196
|
+
this.userToConnections.get(userId).add(connectionKey);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Removes back-mappings and, if all are deleted,
|
|
200
|
+
* also removes user presence.
|
|
201
|
+
*/
|
|
202
|
+
removeMappings(connectionKey, autoRemoveOnEmpty = true) {
|
|
203
|
+
const userId = this.connectionToUser.get(connectionKey);
|
|
204
|
+
if (userId) {
|
|
205
|
+
this.connectionToUser.delete(connectionKey);
|
|
206
|
+
const connections = this.userToConnections.get(userId);
|
|
207
|
+
if (connections) {
|
|
208
|
+
connections.delete(connectionKey);
|
|
209
|
+
if (connections.size === 0) {
|
|
210
|
+
this.userToConnections.delete(userId);
|
|
211
|
+
if (autoRemoveOnEmpty) {
|
|
212
|
+
return this.presence.remove(userId);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
173
218
|
}
|
|
174
219
|
//# sourceMappingURL=ClientConnection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientConnection.js","sourceRoot":"","sources":["../../../src/connections/ClientConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAiB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"ClientConnection.js","sourceRoot":"","sources":["../../../src/connections/ClientConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,eAAe,EAAiB,MAAM,qBAAqB,CAAC;AAO5E,OAAO,EAAE,QAAQ,EAAmB,MAAM,eAAe,CAAC;AAE1D,MAAe,gBAAgB;IAC9B,YACU,GAAW,EACD,IAAe;QADzB,QAAG,GAAH,GAAG,CAAQ;QACD,SAAI,GAAJ,IAAI,CAAW;QAInC,YAAO,GAAG,CAAC,OAAsB,EAAE,EAAE;YACpC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC;QAEM,mBAAc,GAAG,CAAC,OAAsB,EAAiB,EAAE;;YAClE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;gBACtB,KAAK,OAAO,CAAC,CAAC,CAAC;oBACd,uCACI,OAAO,KACV,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAChE,SAAS,EAAE,MAAA,OAAO,CAAC,SAAS,0CAAE,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAC9D;gBACH,CAAC;gBACD,KAAK,WAAW,CAAC,CAAC,CAAC;oBAClB,uCACI,OAAO,KACV,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAChE,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAC7D;gBACH,CAAC;gBACD;oBACC,OAAO,OAAO,CAAC;YACjB,CAAC;QACF,CAAC,CAAC;QAEM,yBAAoB,GAAG,CAAC,SAA6B,EAAE,EAAE;YAChE,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;gBACrB,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;gBAC9C,gDAAgD;gBAChD,OAAO,OAAO,CAAC,OAAO,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YAC7C,CAAC;YACD,OAAO,IAAI,CAAC;QACb,CAAC,CAAC;IAnCC,CAAC;CAoCJ;AAED,MAAM,uBAAwB,SAAQ,gBAAgB;IAKrD,YACC,GAAW,EACM,GAAoB,EACpB,GAAmB,EACpC,IAAe;QAEf,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAJA,QAAG,GAAH,GAAG,CAAiB;QACpB,QAAG,GAAH,GAAG,CAAgB;QAP5B,SAAI,GAAG,SAAS,CAAC;QAElB,cAAS,GAAoB,EAAE,CAAC;QAWxC,kBAAa,GAAG,CAAC,OAAsB,EAAE,EAAE;YAC1C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC,CAAC;QAEF,QAAG,GAAG,GAAG,EAAE;YACV,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;gBACvB,cAAc,EAAE,kBAAkB;aAClC,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,KAAK,CACb,IAAI,CAAC,SAAS,CAAC;gBACd,QAAQ,EAAE,IAAI,CAAC,SAAS;aACxB,CAAC,CACF,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;QAChB,CAAC,CAAC;IAhBF,CAAC;CAiBD;AAED,MAAM,qBAAsB,SAAQ,gBAAgB;IAMnD,YACC,GAAW,EACM,GAAY,EAC7B,IAAe;QAEf,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAHA,QAAG,GAAH,GAAG,CAAS;QAPrB,SAAI,GAAG,OAAO,CAAC;QAEhB,cAAS,GAAoB,EAAE,CAAC;QACxC,QAAG,GAAG,IAAI,QAAQ,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAUxC,kBAAa,GAAG,CAAC,OAAsB,EAAE,EAAE;YAC1C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC,CAAC;QAEF,QAAG,GAAG,GAAG,EAAE;YACV,IAAI,CAAC,GAAG,GAAG,IAAI,QAAQ,CACtB,IAAI,CAAC,SAAS,CAAC;gBACd,QAAQ,EAAE,IAAI,CAAC,SAAS;aACxB,CAAC,EACF;gBACC,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE;oBACR,cAAc,EAAE,kBAAkB;iBAClC;aACD,CACD,CAAC;QACH,CAAC,CAAC;IAlBF,CAAC;CAmBD;AAED,SAAS,sBAAsB,CAC9B,MAA+B,EAC/B,KAAa,EACb,OAA4B;IAE5B,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;QACnB,MAAsB,CAAC,EAAE,CAAC,KAAY,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,GAAG,EAAE,CAAE,MAAsB,CAAC,GAAG,CAAC,KAAY,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;SAAM,CAAC;QACN,MAAoB,CAAC,gBAAgB,CAAC,KAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAC5D,OAAO,CAAE,GAAW,CAAC,IAAI,CAAC,CAC1B,CAAC;QACF,OAAO,GAAG,EAAE,CACV,MAAoB,CAAC,mBAAmB,CAAC,KAAY,EAAE,OAAO,CAAC,CAAC;IACnE,CAAC;AACF,CAAC;AAED,MAAM,yBAA0B,SAAQ,gBAAgB;IAGvD,YACC,GAAW;IACX,+DAA+D;IAC/D,qCAAqC;IAC5B,EAA2B,EACpC,IAAe;QAEf,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAHR,OAAE,GAAF,EAAE,CAAyB;QAN5B,SAAI,GAAG,WAAW,CAAC;QAY5B,kBAAa,GAAG,CAAC,OAAsB,EAAE,EAAE;YAC1C,OAAO,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9C,CAAC,CAAC;IAJF,CAAC;CAKD;AAMD,MAAM,OAAO,uBACZ,SAAQ,eAA+C;IASvD,YAAY,EACX,QAAQ,EACR,eAAe,EACf,GAAG,GAKH;QACA,KAAK,EAAE,CAAC;QAfQ,gBAAW,GAAkC,IAAI,GAAG,EAAE,CAAC;QAChE,qBAAgB,GAAwB,IAAI,GAAG,EAAE,CAAC;QAClD,sBAAiB,GAA6B,IAAI,GAAG,EAAE,CAAC;QA8ChE,cAAS,GAAG,CACX,GAAW,EACX,MAA+B,EAC/B,IAAe,EACf,OAEC,EACA,EAAE;YACH,MAAM,UAAU,GAAG,IAAI,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;YAEpE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAEnC,MAAM,YAAY,GAAmB,EAAE,CAAC;YACxC;;;;;;;;;eASG;YACH,YAAY,CAAC,IAAI,CAChB,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE;;gBAClD,MAAA,IAAI,CAAC,GAAG,qDAAG,MAAM,EAAE,kBAAkB,GAAG,EAAE,CAAC,CAAC;gBAC5C,IAAI,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,wBAAwB,CAAA,EAAE,CAAC;oBACxC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACxB,CAAC;gBACD,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAClC,CAAC,CAAC,CACF,CAAC;YACF,YAAY,CAAC,IAAI,CAChB,sBAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE;gBAC/C,oDAAoD;gBACpD,yCAAyC;gBACzC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;YACjD,CAAC,CAAC,CACF,CAAC;QACH,CAAC,CAAC;QAEF,eAAU,GAAG,CACZ,GAAW,EACX,OAAwB,EACxB,QAAwB,EACxB,IAAe,EACd,EAAE;YACH,MAAM,UAAU,GAAG,IAAI,uBAAuB,CAC7C,GAAG,EACH,OAAO,EACP,QAAQ,EACR,IAAI,CACJ,CAAC;YAEF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAEnC,OAAO,GAAG,EAAE;gBACX,UAAU,CAAC,GAAG,EAAE,CAAC;gBACjB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC9B,CAAC,CAAC;QACH,CAAC,CAAC;QAEF,aAAQ,GAAG,CAAC,GAAW,EAAE,GAAY,EAAE,IAAe,EAAoB,EAAE;YAC3E,MAAM,UAAU,GAAG,IAAI,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAE7D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAEnC,OAAO,GAAG,EAAE;gBACX,UAAU,CAAC,GAAG,EAAE,CAAC;gBACjB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC7B,OAAO,UAAU,CAAC,GAAG,CAAC;YACvB,CAAC,CAAC;QACH,CAAC,CAAC;QAEF,WAAM,GAAG,KAAK,EAAE,GAAW,EAAE,EAAE;YAC9B,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC,CAAC;QAEF,YAAO,GAAG,CAAC,GAAW,EAAE,OAAsB,EAAE,EAAE;;YACjD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC7C,IAAI,UAAU,EAAE,CAAC;gBAChB,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC5B,OAAO,IAAI,CAAC;YACb,CAAC;iBAAM,CAAC;gBACP,MAAA,IAAI,CAAC,GAAG,qDACP,MAAM,EACN,mDAAmD,GAAG,EAAE,CACxD,CAAC;gBACF,OAAO,KAAK,CAAC;YACd,CAAC;QACF,CAAC,CAAC;QAEF;;;WAGG;QACH,cAAS,GAAG,CAAC,OAAsB,EAAE,WAAqB,EAAE,EAAE,EAAE;YAC/D,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBAClD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC7B,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC7B,CAAC;YACF,CAAC;QACF,CAAC,CAAC;QAEF,kBAAa,GAAG,GAAG,EAAE;YACpB,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;gBACpD,IAAI,UAAU,YAAY,yBAAyB,EAAE,CAAC;oBACrD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;gBACvB,CAAC;YACF,CAAC;YACD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC,CAAC;QAnJD,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;QACxD,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IAChB,CAAC;IAEO,WAAW,CAAC,MAAc,EAAE,aAAqB;QACxD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACxD,CAAC;IACD;;;OAGG;IACK,cAAc,CAAC,aAAqB,EAAE,iBAAiB,GAAG,IAAI;QACrE,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACxD,IAAI,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;YAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACvD,IAAI,WAAW,EAAE,CAAC;gBACjB,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;gBAClC,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBAC5B,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBACtC,IAAI,iBAAiB,EAAE,CAAC;wBACvB,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBACrC,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;CAsHD"}
|
|
@@ -1,23 +1,40 @@
|
|
|
1
1
|
import { EventSubscriber, UserInfo, UserInfoUpdate } from '@verdant-web/common';
|
|
2
2
|
import { UserProfiles } from '../Profiles.js';
|
|
3
3
|
export type PresenceEvents = {
|
|
4
|
-
lost: (
|
|
4
|
+
lost: (userId: string) => void;
|
|
5
5
|
};
|
|
6
|
+
export type PresenceStorageItem = UserInfo<any, any> & {
|
|
7
|
+
expiresAt: number;
|
|
8
|
+
};
|
|
9
|
+
export interface PresenceStorage {
|
|
10
|
+
set: (userId: string, userInfo: UserInfoUpdate, profile: any, expiresAt: number) => Promise<UserInfo<any, any>>;
|
|
11
|
+
setExpiresAt: (userId: string, expiresAt: number) => Promise<void>;
|
|
12
|
+
get: (userId: string, time: number) => Promise<UserInfo<any, any> | undefined>;
|
|
13
|
+
remove: (userId: string) => Promise<void>;
|
|
14
|
+
all: () => Promise<Record<string, UserInfo<any, any>>>;
|
|
15
|
+
clear: () => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare class PresenceMemoryStorage implements PresenceStorage {
|
|
18
|
+
private presences;
|
|
19
|
+
set: (userId: string, userInfo: UserInfoUpdate, profile: any, expiresAt: number) => Promise<PresenceStorageItem>;
|
|
20
|
+
setExpiresAt: (userId: string, expiresAt: number) => Promise<void>;
|
|
21
|
+
get: (userId: string, time: number) => Promise<PresenceStorageItem | undefined>;
|
|
22
|
+
remove: (userId: string) => Promise<void>;
|
|
23
|
+
all: () => Promise<Record<string, PresenceStorageItem>>;
|
|
24
|
+
clear: () => Promise<void>;
|
|
25
|
+
}
|
|
6
26
|
/**
|
|
7
|
-
* Stores client presence
|
|
27
|
+
* Stores client presence for connected
|
|
8
28
|
* clients
|
|
9
29
|
*/
|
|
10
30
|
export declare class Presence extends EventSubscriber<PresenceEvents> {
|
|
11
31
|
readonly profiles: UserProfiles<any>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
get: (userId: string) => UserInfo<any, any>;
|
|
20
|
-
removeConnection: (connectionKey: string) => void;
|
|
21
|
-
all: () => Record<string, UserInfo<any, any>>;
|
|
32
|
+
readonly storage: PresenceStorage;
|
|
33
|
+
constructor(profiles: UserProfiles<any>, storage?: PresenceStorage);
|
|
34
|
+
set: (userId: string, userInfo: UserInfoUpdate) => Promise<UserInfo<any, any>>;
|
|
35
|
+
keepAlive: (userId: string, duration?: number) => void;
|
|
36
|
+
get: (userId: string) => Promise<UserInfo<any, any> | undefined>;
|
|
37
|
+
remove: (userId: string) => Promise<void>;
|
|
38
|
+
all: () => Promise<Record<string, UserInfo<any, any>>>;
|
|
22
39
|
clear: () => void;
|
|
23
40
|
}
|
|
@@ -1,64 +1,74 @@
|
|
|
1
1
|
import { EventSubscriber, initialInternalPresence, } from '@verdant-web/common';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
export class Presence extends EventSubscriber {
|
|
7
|
-
constructor(profiles) {
|
|
8
|
-
super();
|
|
9
|
-
this.profiles = profiles;
|
|
2
|
+
const DEFAULT_EXPIRATION_TIME = 60 * 1000;
|
|
3
|
+
export class PresenceMemoryStorage {
|
|
4
|
+
constructor() {
|
|
10
5
|
this.presences = {};
|
|
11
|
-
this.
|
|
12
|
-
this.userToConnection = {};
|
|
13
|
-
this.keepalives = new Map();
|
|
14
|
-
this.set = async (connectionKey, userId, userInfo) => {
|
|
6
|
+
this.set = async (userId, userInfo, profile, expiresAt) => {
|
|
15
7
|
if (!this.presences[userId]) {
|
|
16
|
-
this.presences[userId] = Object.assign(Object.assign({}, userInfo), {
|
|
8
|
+
this.presences[userId] = Object.assign(Object.assign({}, userInfo), { expiresAt,
|
|
9
|
+
profile, internal: userInfo.internal || initialInternalPresence });
|
|
17
10
|
}
|
|
18
11
|
else {
|
|
19
12
|
Object.assign(this.presences[userId], userInfo);
|
|
20
13
|
}
|
|
21
|
-
this.connectionToUser[connectionKey] = userId;
|
|
22
|
-
this.userToConnection[userId] =
|
|
23
|
-
this.userToConnection[userId] || new Set();
|
|
24
|
-
this.userToConnection[userId].add(connectionKey);
|
|
25
|
-
this.keepAlive(connectionKey);
|
|
26
14
|
return this.presences[userId];
|
|
27
15
|
};
|
|
28
|
-
this.
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
|
|
16
|
+
this.setExpiresAt = async (userId, expiresAt) => {
|
|
17
|
+
const presence = this.presences[userId];
|
|
18
|
+
if (presence) {
|
|
19
|
+
presence.expiresAt = expiresAt;
|
|
32
20
|
}
|
|
33
|
-
this.keepalives.set(connectionKey, setTimeout(() => {
|
|
34
|
-
this.removeConnection(connectionKey);
|
|
35
|
-
this.keepalives.delete(connectionKey);
|
|
36
|
-
}, duration));
|
|
37
21
|
};
|
|
38
|
-
this.get = (userId) => {
|
|
39
|
-
|
|
22
|
+
this.get = async (userId, time) => {
|
|
23
|
+
const value = this.presences[userId];
|
|
24
|
+
if (value && value.expiresAt > time) {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
40
28
|
};
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
this.userToConnection[userId].delete(connectionKey);
|
|
46
|
-
if (this.userToConnection[userId].size === 0) {
|
|
47
|
-
delete this.presences[userId];
|
|
48
|
-
this.emit('lost', connectionKey, userId);
|
|
49
|
-
// memory cleanup
|
|
50
|
-
if (Object.keys(this.presences).length === 0) {
|
|
51
|
-
this.clear();
|
|
52
|
-
}
|
|
29
|
+
this.remove = async (userId) => {
|
|
30
|
+
delete this.presences[userId];
|
|
31
|
+
if (Object.keys(this.presences).length === 0) {
|
|
32
|
+
this.clear();
|
|
53
33
|
}
|
|
54
34
|
};
|
|
55
|
-
this.all = () => {
|
|
35
|
+
this.all = async () => {
|
|
56
36
|
return this.presences;
|
|
57
37
|
};
|
|
58
|
-
this.clear = () => {
|
|
38
|
+
this.clear = async () => {
|
|
59
39
|
this.presences = {};
|
|
60
|
-
|
|
61
|
-
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Stores client presence for connected
|
|
45
|
+
* clients
|
|
46
|
+
*/
|
|
47
|
+
export class Presence extends EventSubscriber {
|
|
48
|
+
constructor(profiles, storage = new PresenceMemoryStorage()) {
|
|
49
|
+
super();
|
|
50
|
+
this.profiles = profiles;
|
|
51
|
+
this.storage = storage;
|
|
52
|
+
this.set = async (userId, userInfo) => {
|
|
53
|
+
const value = await this.storage.set(userId, userInfo, await this.profiles.get(userId), Date.now() + DEFAULT_EXPIRATION_TIME);
|
|
54
|
+
return value;
|
|
55
|
+
};
|
|
56
|
+
this.keepAlive = (userId, duration = DEFAULT_EXPIRATION_TIME) => {
|
|
57
|
+
const time = Date.now() + duration;
|
|
58
|
+
this.storage.setExpiresAt(userId, time);
|
|
59
|
+
};
|
|
60
|
+
this.get = (userId) => {
|
|
61
|
+
return this.storage.get(userId, Date.now());
|
|
62
|
+
};
|
|
63
|
+
this.remove = async (userId) => {
|
|
64
|
+
await this.storage.remove(userId);
|
|
65
|
+
this.emit('lost', userId);
|
|
66
|
+
};
|
|
67
|
+
this.all = () => {
|
|
68
|
+
return this.storage.all();
|
|
69
|
+
};
|
|
70
|
+
this.clear = () => {
|
|
71
|
+
this.storage.clear();
|
|
62
72
|
};
|
|
63
73
|
}
|
|
64
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Presence.js","sourceRoot":"","sources":["../../../src/connections/Presence.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,eAAe,EACf,uBAAuB,GAGvB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"Presence.js","sourceRoot":"","sources":["../../../src/connections/Presence.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/internals.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { errorHandler } from './adapters/node/errorHandler.js';
|
|
|
2
2
|
export { createHttpRouter as createHonoRouter } from './adapters/node/httpRouter.js';
|
|
3
3
|
export { tokenMiddleware } from './adapters/node/tokenMiddleware.js';
|
|
4
4
|
export { ClientConnectionManager } from './connections/ClientConnection.js';
|
|
5
|
+
export type * from './connections/Presence.js';
|
|
5
6
|
export { FileStorageLibraryDelegate } from './files/FileStorage.js';
|
|
6
7
|
export type * from './libraries/generic.js';
|
|
7
8
|
export { Library, type LibraryEvents } from './libraries/Library.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internals.js","sourceRoot":"","sources":["../../src/internals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,OAAO,EAAE,gBAAgB,IAAI,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"internals.js","sourceRoot":"","sources":["../../src/internals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,OAAO,EAAE,gBAAgB,IAAI,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAE5E,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAEpE,OAAO,EAAE,OAAO,EAAsB,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAExE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ClientMessage, DocumentBaseline, EventSubscriber, Operation } from '@verdant-web/common';
|
|
2
|
-
import { MessageSender } from '../connections/MessageSender.js';
|
|
3
|
-
import { Presence } from '../connections/Presence.js';
|
|
4
2
|
import { FileInfo, FileStorageLibraryDelegate } from '../files/FileStorage.js';
|
|
3
|
+
import { ClientConnectionManager } from '../internals.js';
|
|
5
4
|
import { Logger } from '../logger.js';
|
|
6
5
|
import { Storage } from '../storage/Storage.js';
|
|
7
6
|
import { TokenInfo } from '../TokenVerifier.js';
|
|
@@ -18,22 +17,22 @@ export interface LibraryFileInfo {
|
|
|
18
17
|
}
|
|
19
18
|
export declare class Library {
|
|
20
19
|
private storage;
|
|
21
|
-
private sender;
|
|
22
20
|
private disableRebasing;
|
|
23
21
|
private fileStorage;
|
|
24
22
|
private events;
|
|
25
23
|
readonly id: string;
|
|
26
|
-
private
|
|
24
|
+
private clientConnections;
|
|
25
|
+
private get presence();
|
|
26
|
+
private get sender();
|
|
27
27
|
private log;
|
|
28
|
-
constructor({ storage,
|
|
28
|
+
constructor({ storage, log, disableRebasing, fileStorage, events, id, clientConnections, }: {
|
|
29
29
|
storage: Storage;
|
|
30
|
-
sender: MessageSender;
|
|
31
30
|
log?: Logger;
|
|
32
31
|
disableRebasing?: boolean;
|
|
33
32
|
fileStorage?: FileStorageLibraryDelegate;
|
|
34
33
|
events?: EventSubscriber<LibraryEvents>;
|
|
35
34
|
id: string;
|
|
36
|
-
|
|
35
|
+
clientConnections: ClientConnectionManager;
|
|
37
36
|
});
|
|
38
37
|
private validateReplicaAccess;
|
|
39
38
|
private hasWriteAccess;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { applyOperations, isFileRef, isObjectRef, ReplicaType, rewriteAuthzOriginator, } from '@verdant-web/common';
|
|
2
2
|
export class Library {
|
|
3
|
-
|
|
3
|
+
get presence() {
|
|
4
|
+
return this.clientConnections.presence;
|
|
5
|
+
}
|
|
6
|
+
get sender() {
|
|
7
|
+
return this.clientConnections;
|
|
8
|
+
}
|
|
9
|
+
constructor({ storage, log = () => { }, disableRebasing, fileStorage, events, id, clientConnections, }) {
|
|
4
10
|
this.validateReplicaAccess = async (replicaId, clientKey, info) => {
|
|
5
11
|
const replica = await this.storage.replicas.get(replicaId);
|
|
6
12
|
if (replica && replica.clientId !== info.userId) {
|
|
@@ -229,7 +235,12 @@ export class Library {
|
|
|
229
235
|
? this.createAckNonce(serverOrderToAck)
|
|
230
236
|
: undefined;
|
|
231
237
|
// respond to client
|
|
232
|
-
|
|
238
|
+
const peerPresence = await this.presence.all();
|
|
239
|
+
this.log('info', 'Sending sync response with', {
|
|
240
|
+
ops: ops.length,
|
|
241
|
+
baselines: baselines.length,
|
|
242
|
+
peers: Object.keys(peerPresence),
|
|
243
|
+
}, 'to client key', clientKey);
|
|
233
244
|
// mutating baselines/operations to remove extra data -
|
|
234
245
|
// mutating just to save on allocations here.
|
|
235
246
|
try {
|
|
@@ -238,7 +249,7 @@ export class Library {
|
|
|
238
249
|
operations: this.removeOperationExtras(ops),
|
|
239
250
|
baselines: this.removeBaselineExtras(baselines),
|
|
240
251
|
globalAckTimestamp: (_b = (await this.storage.replicas.getGlobalAck())) !== null && _b !== void 0 ? _b : undefined,
|
|
241
|
-
peerPresence
|
|
252
|
+
peerPresence,
|
|
242
253
|
// only request the client to overwrite local data if a reset is requested
|
|
243
254
|
// and there is data to overwrite it. otherwise the client may still
|
|
244
255
|
// send its own history to us.
|
|
@@ -278,7 +289,7 @@ export class Library {
|
|
|
278
289
|
}
|
|
279
290
|
};
|
|
280
291
|
this.handleDisconnecting = async (_message, clientKey, _info) => {
|
|
281
|
-
this.
|
|
292
|
+
await this.clientConnections.remove(clientKey);
|
|
282
293
|
};
|
|
283
294
|
this.updateHighwater = async (replicaId, timestamp, info, ignoreClientKeys = []) => {
|
|
284
295
|
await this.storage.replicas.updateAcknowledgedLogicalTime(replicaId, timestamp);
|
|
@@ -317,7 +328,7 @@ export class Library {
|
|
|
317
328
|
// for global ack, to determine consensus, also allow
|
|
318
329
|
// for all actively connected replicas to ack regardless of their
|
|
319
330
|
// type.
|
|
320
|
-
const activeReplicaIds = Object.values(this.presence.all()).map((p) => p.replicaId);
|
|
331
|
+
const activeReplicaIds = Object.values(await this.presence.all()).map((p) => p.replicaId);
|
|
321
332
|
const globalAck = await this.storage.replicas.getGlobalAck(activeReplicaIds);
|
|
322
333
|
const globalServerOrder = await this.storage.replicas.getEarliestAckedServerOrder();
|
|
323
334
|
if (!globalAck) {
|
|
@@ -367,7 +378,7 @@ export class Library {
|
|
|
367
378
|
});
|
|
368
379
|
};
|
|
369
380
|
this.handlePresenceUpdate = async (message, clientKey, info) => {
|
|
370
|
-
const updated = await this.presence.set(
|
|
381
|
+
const updated = await this.presence.set(info.userId, {
|
|
371
382
|
presence: message.presence,
|
|
372
383
|
internal: message.internal,
|
|
373
384
|
replicaId: message.replicaId,
|
|
@@ -381,14 +392,13 @@ export class Library {
|
|
|
381
392
|
// mirror back to the replica which sent it so it has profile
|
|
382
393
|
[]);
|
|
383
394
|
};
|
|
384
|
-
this.onPresenceLost = async (
|
|
385
|
-
this.log('info', 'User disconnected from all replicas:', userId
|
|
395
|
+
this.onPresenceLost = async (userId) => {
|
|
396
|
+
this.log('info', 'User disconnected from all replicas:', userId);
|
|
386
397
|
this.sender.broadcast({
|
|
387
398
|
type: 'presence-offline',
|
|
388
|
-
replicaId,
|
|
389
399
|
userId,
|
|
390
400
|
});
|
|
391
|
-
if (Object.keys(this.presence.all()).length === 0) {
|
|
401
|
+
if (Object.keys(await this.presence.all()).length === 0) {
|
|
392
402
|
this.log('info', `All users have disconnected`);
|
|
393
403
|
// could happen - if the server is shutting down manually
|
|
394
404
|
if (!this.storage.open) {
|
|
@@ -441,7 +451,7 @@ export class Library {
|
|
|
441
451
|
}
|
|
442
452
|
};
|
|
443
453
|
this.getPresence = () => {
|
|
444
|
-
return
|
|
454
|
+
return this.presence.all();
|
|
445
455
|
};
|
|
446
456
|
this.getInfo = async () => {
|
|
447
457
|
var _a;
|
|
@@ -576,12 +586,11 @@ export class Library {
|
|
|
576
586
|
};
|
|
577
587
|
this.id = id;
|
|
578
588
|
this.storage = storage;
|
|
579
|
-
this.sender = sender;
|
|
580
589
|
this.log = log;
|
|
581
590
|
this.disableRebasing = !!disableRebasing;
|
|
582
591
|
this.fileStorage = fileStorage;
|
|
583
592
|
this.events = events;
|
|
584
|
-
this.
|
|
593
|
+
this.clientConnections = clientConnections;
|
|
585
594
|
this.presence.subscribe('lost', this.onPresenceLost);
|
|
586
595
|
}
|
|
587
596
|
}
|