@verdant-web/server 3.3.11 → 4.0.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/bin/server.mjs +110 -107
- package/dist/esm/TokenVerifier.d.ts +1 -0
- package/dist/esm/TokenVerifier.js +3 -0
- package/dist/esm/TokenVerifier.js.map +1 -1
- package/dist/esm/adapters/node/errorHandler.d.ts +2 -0
- package/dist/esm/adapters/node/errorHandler.js +12 -0
- package/dist/esm/adapters/node/errorHandler.js.map +1 -0
- package/dist/esm/adapters/node/getRequestToken.d.ts +1 -0
- package/dist/esm/adapters/node/getRequestToken.js +25 -0
- package/dist/esm/adapters/node/getRequestToken.js.map +1 -0
- package/dist/esm/adapters/node/httpRouter.d.ts +47 -0
- package/dist/esm/adapters/node/httpRouter.js +88 -0
- package/dist/esm/adapters/node/httpRouter.js.map +1 -0
- package/dist/esm/adapters/node/index.d.ts +5 -0
- package/dist/esm/adapters/node/index.js +6 -0
- package/dist/esm/adapters/node/index.js.map +1 -0
- package/dist/esm/adapters/node/tokenMiddleware.d.ts +7 -0
- package/dist/esm/adapters/node/tokenMiddleware.js +22 -0
- package/dist/esm/adapters/node/tokenMiddleware.js.map +1 -0
- package/dist/esm/adapters/node/websocketHandler.d.ts +4 -0
- package/dist/esm/adapters/node/websocketHandler.js +54 -0
- package/dist/esm/adapters/node/websocketHandler.js.map +1 -0
- package/dist/esm/connections/ClientConnection.d.ts +25 -0
- package/dist/esm/{ClientConnection.js → connections/ClientConnection.js} +47 -38
- package/dist/esm/connections/ClientConnection.js.map +1 -0
- package/dist/esm/connections/MessageSender.d.ts +5 -0
- package/dist/esm/connections/MessageSender.js.map +1 -0
- package/dist/esm/connections/Presence.d.ts +23 -0
- package/dist/esm/connections/Presence.js +65 -0
- package/dist/esm/connections/Presence.js.map +1 -0
- package/dist/esm/core.d.ts +15 -0
- package/dist/esm/core.js +15 -0
- package/dist/esm/core.js.map +1 -0
- package/dist/esm/defaults.d.ts +2 -0
- package/dist/esm/defaults.js +3 -0
- package/dist/esm/defaults.js.map +1 -0
- package/dist/esm/files/FileStorage.d.ts +12 -3
- package/dist/esm/files/FileStorage.js +20 -0
- package/dist/esm/files/FileStorage.js.map +1 -1
- package/dist/esm/index.d.ts +7 -7
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internals.d.ts +14 -0
- package/dist/esm/internals.js +12 -0
- package/dist/esm/internals.js.map +1 -0
- package/dist/esm/libraries/Library.d.ts +78 -0
- package/dist/esm/{ServerLibrary.js → libraries/Library.js} +165 -151
- package/dist/esm/libraries/Library.js.map +1 -0
- package/dist/esm/{ServerLibrary.test.js → libraries/Library.test.js} +42 -33
- package/dist/esm/libraries/Library.test.js.map +1 -0
- package/dist/esm/libraries/generic.d.ts +8 -0
- package/dist/esm/libraries/generic.js +2 -0
- package/dist/esm/libraries/generic.js.map +1 -0
- package/dist/esm/libraries/singleNode.d.ts +53 -0
- package/dist/esm/libraries/singleNode.js +88 -0
- package/dist/esm/libraries/singleNode.js.map +1 -0
- package/dist/esm/logger.d.ts +1 -0
- package/dist/esm/logger.js +2 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/storage/Storage.d.ts +39 -38
- package/dist/esm/storage/StorageManager.d.ts +11 -0
- package/dist/esm/storage/StorageManager.js +32 -0
- package/dist/esm/storage/StorageManager.js.map +1 -0
- package/dist/esm/storage/index.d.ts +1 -2
- package/dist/esm/storage/index.js +1 -2
- package/dist/esm/storage/index.js.map +1 -1
- package/dist/esm/storage/sqlShard/Databases.d.ts +5 -3
- package/dist/esm/storage/sqlShard/Databases.js +11 -7
- package/dist/esm/storage/sqlShard/Databases.js.map +1 -1
- package/dist/esm/storage/sqlShard/SqlBaselines.d.ts +16 -17
- package/dist/esm/storage/sqlShard/SqlBaselines.js +55 -59
- package/dist/esm/storage/sqlShard/SqlBaselines.js.map +1 -1
- package/dist/esm/storage/sqlShard/SqlFileMetadata.d.ts +13 -13
- package/dist/esm/storage/sqlShard/SqlFileMetadata.js +44 -57
- package/dist/esm/storage/sqlShard/SqlFileMetadata.js.map +1 -1
- package/dist/esm/storage/sqlShard/SqlOperations.d.ts +12 -12
- package/dist/esm/storage/sqlShard/SqlOperations.js +38 -73
- package/dist/esm/storage/sqlShard/SqlOperations.js.map +1 -1
- package/dist/esm/storage/sqlShard/SqlReplicas.d.ts +18 -17
- package/dist/esm/storage/sqlShard/SqlReplicas.js +63 -100
- package/dist/esm/storage/sqlShard/SqlReplicas.js.map +1 -1
- package/dist/esm/storage/sqlShard/database.d.ts +25 -5
- package/dist/esm/storage/sqlShard/database.js +47 -15
- package/dist/esm/storage/sqlShard/database.js.map +1 -1
- package/dist/esm/storage/sqlShard/migrations/v1.d.ts +2 -3
- package/dist/esm/storage/sqlShard/migrations/v1.js +42 -47
- package/dist/esm/storage/sqlShard/migrations/v1.js.map +1 -1
- package/dist/esm/storage/sqlShard/migrations/v2.d.ts +2 -3
- package/dist/esm/storage/sqlShard/migrations/v2.js +5 -15
- package/dist/esm/storage/sqlShard/migrations/v2.js.map +1 -1
- package/dist/esm/storage/sqlShard/migrations.d.ts +5 -7
- package/dist/esm/storage/sqlShard/migrations.js +48 -1
- package/dist/esm/storage/sqlShard/migrations.js.map +1 -1
- package/dist/esm/storage/sqlShard/sqlShardStorage.d.ts +25 -4
- package/dist/esm/storage/sqlShard/sqlShardStorage.js +43 -40
- package/dist/esm/storage/sqlShard/sqlShardStorage.js.map +1 -1
- package/dist/esm/storage/sqlShard/tables.d.ts +9 -6
- package/dist/esm/types.d.ts +2 -2
- package/package.json +24 -13
- package/src/TokenVerifier.ts +4 -0
- package/src/adapters/node/errorHandler.ts +18 -0
- package/src/adapters/node/getRequestToken.ts +34 -0
- package/src/adapters/node/httpRouter.ts +139 -0
- package/src/adapters/node/index.ts +5 -0
- package/src/adapters/node/tokenMiddleware.ts +32 -0
- package/src/adapters/node/websocketHandler.ts +79 -0
- package/src/{ClientConnection.ts → connections/ClientConnection.ts} +64 -57
- package/src/connections/MessageSender.ts +6 -0
- package/src/connections/Presence.ts +95 -0
- package/src/core.ts +36 -0
- package/src/defaults.ts +2 -0
- package/src/files/FileStorage.ts +36 -3
- package/src/index.ts +7 -7
- package/src/internals.ts +14 -0
- package/src/{ServerLibrary.test.ts → libraries/Library.test.ts} +51 -49
- package/src/{ServerLibrary.ts → libraries/Library.ts} +205 -206
- package/src/libraries/generic.ts +9 -0
- package/src/libraries/singleNode.ts +145 -0
- package/src/logger.ts +4 -0
- package/src/storage/Storage.ts +37 -66
- package/src/storage/StorageManager.ts +39 -0
- package/src/storage/index.ts +1 -2
- package/src/storage/sqlShard/Databases.ts +26 -13
- package/src/storage/sqlShard/SqlBaselines.ts +58 -67
- package/src/storage/sqlShard/SqlFileMetadata.ts +51 -72
- package/src/storage/sqlShard/SqlOperations.ts +57 -81
- package/src/storage/sqlShard/SqlReplicas.ts +80 -121
- package/src/storage/sqlShard/database.ts +82 -18
- package/src/storage/sqlShard/migrations/v1.ts +42 -52
- package/src/storage/sqlShard/migrations/v2.ts +5 -19
- package/src/storage/sqlShard/migrations.ts +66 -1
- package/src/storage/sqlShard/sqlShardStorage.ts +56 -55
- package/src/storage/sqlShard/tables.ts +6 -6
- package/src/types.ts +2 -4
- package/dist/esm/ClientConnection.d.ts +0 -19
- package/dist/esm/ClientConnection.js.map +0 -1
- package/dist/esm/MessageSender.d.ts +0 -5
- package/dist/esm/MessageSender.js.map +0 -1
- package/dist/esm/Presence.d.ts +0 -18
- package/dist/esm/Presence.js +0 -65
- package/dist/esm/Presence.js.map +0 -1
- package/dist/esm/ReplicaKeepaliveTimers.d.ts +0 -17
- package/dist/esm/ReplicaKeepaliveTimers.js +0 -27
- package/dist/esm/ReplicaKeepaliveTimers.js.map +0 -1
- package/dist/esm/Server.d.ts +0 -166
- package/dist/esm/Server.js +0 -572
- package/dist/esm/Server.js.map +0 -1
- package/dist/esm/ServerLibrary.d.ts +0 -64
- package/dist/esm/ServerLibrary.js.map +0 -1
- package/dist/esm/ServerLibrary.test.js.map +0 -1
- package/dist/esm/storage/sql/SqlBaselines.d.ts +0 -22
- package/dist/esm/storage/sql/SqlBaselines.js +0 -107
- package/dist/esm/storage/sql/SqlBaselines.js.map +0 -1
- package/dist/esm/storage/sql/SqlFileMetadata.d.ts +0 -18
- package/dist/esm/storage/sql/SqlFileMetadata.js +0 -69
- package/dist/esm/storage/sql/SqlFileMetadata.js.map +0 -1
- package/dist/esm/storage/sql/SqlOperations.d.ts +0 -19
- package/dist/esm/storage/sql/SqlOperations.js +0 -122
- package/dist/esm/storage/sql/SqlOperations.js.map +0 -1
- package/dist/esm/storage/sql/SqlReplicas.d.ts +0 -32
- package/dist/esm/storage/sql/SqlReplicas.js +0 -163
- package/dist/esm/storage/sql/SqlReplicas.js.map +0 -1
- package/dist/esm/storage/sql/database.d.ts +0 -9
- package/dist/esm/storage/sql/database.js +0 -24
- package/dist/esm/storage/sql/database.js.map +0 -1
- package/dist/esm/storage/sql/migrations/v1.d.ts +0 -3
- package/dist/esm/storage/sql/migrations/v1.js +0 -59
- package/dist/esm/storage/sql/migrations/v1.js.map +0 -1
- package/dist/esm/storage/sql/migrations/v2.d.ts +0 -3
- package/dist/esm/storage/sql/migrations/v2.js +0 -16
- package/dist/esm/storage/sql/migrations/v2.js.map +0 -1
- package/dist/esm/storage/sql/migrations.d.ts +0 -7
- package/dist/esm/storage/sql/migrations.js +0 -4
- package/dist/esm/storage/sql/migrations.js.map +0 -1
- package/dist/esm/storage/sql/sqlStorage.d.ts +0 -6
- package/dist/esm/storage/sql/sqlStorage.js +0 -35
- package/dist/esm/storage/sql/sqlStorage.js.map +0 -1
- package/dist/esm/storage/sql/tables.d.ts +0 -44
- package/dist/esm/storage/sql/tables.js +0 -2
- package/dist/esm/storage/sql/tables.js.map +0 -1
- package/dist/esm/storage/sqlShard/transfer.d.ts +0 -7
- package/dist/esm/storage/sqlShard/transfer.js +0 -90
- package/dist/esm/storage/sqlShard/transfer.js.map +0 -1
- package/dist/esm/storage/sqlShard/transfer.test.d.ts +0 -1
- package/dist/esm/storage/sqlShard/transfer.test.js +0 -109
- package/dist/esm/storage/sqlShard/transfer.test.js.map +0 -1
- package/src/MessageSender.ts +0 -10
- package/src/Presence.ts +0 -86
- package/src/ReplicaKeepaliveTimers.ts +0 -34
- package/src/Server.ts +0 -859
- package/src/storage/sql/SqlBaselines.ts +0 -147
- package/src/storage/sql/SqlFileMetadata.ts +0 -94
- package/src/storage/sql/SqlOperations.ts +0 -160
- package/src/storage/sql/SqlReplicas.ts +0 -234
- package/src/storage/sql/database.ts +0 -32
- package/src/storage/sql/migrations/v1.ts +0 -64
- package/src/storage/sql/migrations/v2.ts +0 -20
- package/src/storage/sql/migrations.ts +0 -4
- package/src/storage/sql/sqlStorage.ts +0 -57
- package/src/storage/sql/tables.ts +0 -51
- package/src/storage/sqlShard/transfer.test.ts +0 -149
- package/src/storage/sqlShard/transfer.ts +0 -123
- /package/dist/esm/{MessageSender.js → connections/MessageSender.js} +0 -0
- /package/dist/esm/{ServerLibrary.test.d.ts → libraries/Library.test.d.ts} +0 -0
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AckMessage,
|
|
3
|
+
applyOperations,
|
|
3
4
|
ClientMessage,
|
|
4
5
|
DocumentBaseline,
|
|
5
6
|
EventSubscriber,
|
|
6
7
|
HeartbeatMessage,
|
|
8
|
+
isFileRef,
|
|
9
|
+
isObjectRef,
|
|
7
10
|
Operation,
|
|
8
11
|
OperationMessage,
|
|
9
12
|
PresenceUpdateMessage,
|
|
10
13
|
Ref,
|
|
11
14
|
ReplicaType,
|
|
12
|
-
SyncMessage,
|
|
13
|
-
applyOperations,
|
|
14
|
-
isFileRef,
|
|
15
|
-
isObjectRef,
|
|
16
15
|
rewriteAuthzOriginator,
|
|
16
|
+
SyncMessage,
|
|
17
17
|
} from '@verdant-web/common';
|
|
18
|
-
import { MessageSender } from '
|
|
19
|
-
import { Presence } from '
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
18
|
+
import { MessageSender } from '../connections/MessageSender.js';
|
|
19
|
+
import { Presence } from '../connections/Presence.js';
|
|
20
|
+
import { FileInfo, FileStorageLibraryDelegate } from '../files/FileStorage.js';
|
|
21
|
+
import { Logger } from '../logger.js';
|
|
22
|
+
import { Storage } from '../storage/Storage.js';
|
|
23
|
+
import { TokenInfo } from '../TokenVerifier.js';
|
|
24
24
|
import {
|
|
25
25
|
LibraryInfo,
|
|
26
26
|
StoredDocumentBaseline,
|
|
27
27
|
StoredOperation,
|
|
28
28
|
StoredReplicaInfo,
|
|
29
|
-
} from '
|
|
29
|
+
} from '../types.js';
|
|
30
30
|
|
|
31
|
-
export type
|
|
31
|
+
export type LibraryEvents = {
|
|
32
32
|
changes: (
|
|
33
33
|
info: TokenInfo,
|
|
34
34
|
operations: Operation[],
|
|
@@ -36,55 +36,63 @@ export type ServerLibraryEvents = {
|
|
|
36
36
|
) => void;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
export
|
|
39
|
+
export interface LibraryFileInfo {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
type: string;
|
|
43
|
+
url: string | null;
|
|
44
|
+
libraryId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class Library {
|
|
40
48
|
private storage;
|
|
41
49
|
private sender;
|
|
42
|
-
private profiles;
|
|
43
|
-
private presences;
|
|
44
50
|
private disableRebasing: boolean;
|
|
45
|
-
private fileStorage
|
|
51
|
+
private fileStorage;
|
|
52
|
+
private events;
|
|
53
|
+
private id: string;
|
|
54
|
+
private presence: Presence;
|
|
55
|
+
|
|
56
|
+
private log: Logger;
|
|
46
57
|
|
|
47
|
-
private log: (...args: any[]) => void;
|
|
48
58
|
constructor({
|
|
49
59
|
storage,
|
|
50
60
|
sender,
|
|
51
|
-
profiles,
|
|
52
61
|
log = () => {},
|
|
53
62
|
disableRebasing,
|
|
54
63
|
fileStorage,
|
|
64
|
+
events,
|
|
65
|
+
id,
|
|
66
|
+
presence,
|
|
55
67
|
}: {
|
|
56
68
|
storage: Storage;
|
|
57
69
|
sender: MessageSender;
|
|
58
|
-
|
|
59
|
-
log?: (...args: any[]) => void;
|
|
70
|
+
log?: Logger;
|
|
60
71
|
disableRebasing?: boolean;
|
|
61
|
-
fileStorage?:
|
|
72
|
+
fileStorage?: FileStorageLibraryDelegate;
|
|
73
|
+
events?: EventSubscriber<LibraryEvents>;
|
|
74
|
+
id: string;
|
|
75
|
+
presence: Presence;
|
|
62
76
|
}) {
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
this.id = id;
|
|
78
|
+
this.storage = storage;
|
|
79
|
+
this.sender = sender;
|
|
65
80
|
this.log = log;
|
|
66
81
|
this.disableRebasing = !!disableRebasing;
|
|
67
|
-
this.sender = sender;
|
|
68
|
-
this.profiles = profiles;
|
|
69
|
-
this.presences = new Presence(profiles);
|
|
70
|
-
this.storage = storage;
|
|
71
82
|
this.fileStorage = fileStorage;
|
|
72
|
-
this.
|
|
83
|
+
this.events = events;
|
|
84
|
+
this.presence = presence;
|
|
85
|
+
this.presence.subscribe('lost', this.onPresenceLost);
|
|
73
86
|
}
|
|
74
87
|
|
|
75
|
-
/**
|
|
76
|
-
* Validates a user's access to a replica. If the replica does not
|
|
77
|
-
* exist, a user may create it. But if it does exist, its user (clientId)
|
|
78
|
-
* must match the user making the request.
|
|
79
|
-
*/
|
|
80
88
|
private validateReplicaAccess = async (
|
|
81
89
|
replicaId: string,
|
|
82
90
|
clientKey: string,
|
|
83
91
|
info: TokenInfo,
|
|
84
92
|
) => {
|
|
85
|
-
const replica = await this.storage.replicas.get(
|
|
93
|
+
const replica = await this.storage.replicas.get(replicaId);
|
|
86
94
|
if (replica && replica.clientId !== info.userId) {
|
|
87
|
-
this.sender.
|
|
95
|
+
this.sender.respond(clientKey, {
|
|
88
96
|
type: 'forbidden',
|
|
89
97
|
});
|
|
90
98
|
return false;
|
|
@@ -99,18 +107,22 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
99
107
|
);
|
|
100
108
|
};
|
|
101
109
|
|
|
102
|
-
|
|
110
|
+
handleMessage = async (
|
|
103
111
|
message: ClientMessage,
|
|
104
112
|
clientKey: string,
|
|
105
113
|
info: TokenInfo,
|
|
106
114
|
) => {
|
|
107
|
-
|
|
115
|
+
if (message.type === 'heartbeat') {
|
|
116
|
+
this.handleHeartbeat(message, clientKey, info);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
108
119
|
|
|
109
120
|
const allowed = await this.validateReplicaAccess(
|
|
110
121
|
message.replicaId,
|
|
111
122
|
clientKey,
|
|
112
123
|
info,
|
|
113
124
|
);
|
|
125
|
+
|
|
114
126
|
if (!allowed) return;
|
|
115
127
|
|
|
116
128
|
switch (message.type) {
|
|
@@ -123,14 +135,11 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
123
135
|
case 'ack':
|
|
124
136
|
await this.handleAck(message, clientKey, info);
|
|
125
137
|
break;
|
|
126
|
-
case 'heartbeat':
|
|
127
|
-
await this.handleHeartbeat(message, clientKey, info);
|
|
128
|
-
break;
|
|
129
138
|
case 'presence-update':
|
|
130
139
|
await this.handlePresenceUpdate(message, clientKey, info);
|
|
131
140
|
break;
|
|
132
141
|
default:
|
|
133
|
-
this.log('Unknown message type', (message as any).type);
|
|
142
|
+
this.log('error', 'Unknown message type', (message as any).type);
|
|
134
143
|
break;
|
|
135
144
|
}
|
|
136
145
|
// a bit fragile - skipping presence-update since currently
|
|
@@ -138,15 +147,39 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
138
147
|
// sees a recent last seen for the replica even if it was
|
|
139
148
|
// truant before the presence-update.
|
|
140
149
|
if (message.type !== 'presence-update') {
|
|
141
|
-
await this.storage.replicas.updateLastSeen(
|
|
142
|
-
info.libraryId,
|
|
143
|
-
message.replicaId,
|
|
144
|
-
);
|
|
150
|
+
await this.storage.replicas.updateLastSeen(message.replicaId);
|
|
145
151
|
}
|
|
146
152
|
};
|
|
147
153
|
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
private rebroadcastOperations = async (
|
|
155
|
+
libraryId: string,
|
|
156
|
+
clientKey: string,
|
|
157
|
+
replicaId: string,
|
|
158
|
+
ops: Operation[],
|
|
159
|
+
baselines?: DocumentBaseline[],
|
|
160
|
+
) => {
|
|
161
|
+
if (ops.length === 0 && !baselines?.length) return;
|
|
162
|
+
|
|
163
|
+
this.log(
|
|
164
|
+
'info',
|
|
165
|
+
'Rebroadcasting',
|
|
166
|
+
ops.length,
|
|
167
|
+
'operations',
|
|
168
|
+
baselines?.length ?? 0,
|
|
169
|
+
'baselines',
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
this.sender.broadcast(
|
|
173
|
+
{
|
|
174
|
+
type: 'op-re',
|
|
175
|
+
operations: ops,
|
|
176
|
+
baselines,
|
|
177
|
+
replicaId,
|
|
178
|
+
globalAckTimestamp:
|
|
179
|
+
(await this.storage.replicas.getGlobalAck()) ?? undefined,
|
|
180
|
+
},
|
|
181
|
+
[clientKey],
|
|
182
|
+
);
|
|
150
183
|
};
|
|
151
184
|
|
|
152
185
|
private handleOperation = async (
|
|
@@ -157,7 +190,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
157
190
|
if (!message.operations.length) return;
|
|
158
191
|
|
|
159
192
|
if (!this.hasWriteAccess(info)) {
|
|
160
|
-
this.sender.
|
|
193
|
+
this.sender.respond(clientKey, {
|
|
161
194
|
type: 'forbidden',
|
|
162
195
|
});
|
|
163
196
|
return;
|
|
@@ -175,7 +208,6 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
175
208
|
|
|
176
209
|
// first, record the replica's serverOrder before insertion.
|
|
177
210
|
const replicaInfo = await this.storage.replicas.getOrCreate(
|
|
178
|
-
info.libraryId,
|
|
179
211
|
message.replicaId,
|
|
180
212
|
{
|
|
181
213
|
userId: info.userId,
|
|
@@ -183,7 +215,6 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
183
215
|
},
|
|
184
216
|
);
|
|
185
217
|
const newServerOrder = await this.storage.operations.insertAll(
|
|
186
|
-
info.libraryId,
|
|
187
218
|
message.replicaId,
|
|
188
219
|
message.operations,
|
|
189
220
|
);
|
|
@@ -193,10 +224,10 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
193
224
|
message.operations.length,
|
|
194
225
|
);
|
|
195
226
|
|
|
196
|
-
this.enqueueRebase(
|
|
227
|
+
this.enqueueRebase();
|
|
197
228
|
|
|
198
229
|
// tell sender we got and processed their operation
|
|
199
|
-
this.sender.
|
|
230
|
+
this.sender.respond(clientKey, {
|
|
200
231
|
type: 'server-ack',
|
|
201
232
|
timestamp: message.timestamp,
|
|
202
233
|
});
|
|
@@ -207,7 +238,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
207
238
|
info,
|
|
208
239
|
);
|
|
209
240
|
|
|
210
|
-
this.emit(`changes`, info, message.operations, []);
|
|
241
|
+
this.events?.emit(`changes`, info, message.operations, []);
|
|
211
242
|
};
|
|
212
243
|
|
|
213
244
|
/**
|
|
@@ -225,7 +256,6 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
225
256
|
insertedOperationsCount
|
|
226
257
|
) {
|
|
227
258
|
await this.storage.replicas.updateAckedServerOrder(
|
|
228
|
-
replicaInfo.libraryId,
|
|
229
259
|
replicaInfo.id,
|
|
230
260
|
newServerOrder,
|
|
231
261
|
);
|
|
@@ -239,38 +269,6 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
239
269
|
}
|
|
240
270
|
};
|
|
241
271
|
|
|
242
|
-
private rebroadcastOperations = async (
|
|
243
|
-
libraryId: string,
|
|
244
|
-
clientKey: string,
|
|
245
|
-
replicaId: string,
|
|
246
|
-
ops: Operation[],
|
|
247
|
-
baselines?: DocumentBaseline[],
|
|
248
|
-
) => {
|
|
249
|
-
if (ops.length === 0 && !baselines?.length) return;
|
|
250
|
-
|
|
251
|
-
this.log(
|
|
252
|
-
'info',
|
|
253
|
-
'Rebroadcasting',
|
|
254
|
-
ops.length,
|
|
255
|
-
'operations',
|
|
256
|
-
baselines?.length ?? 0,
|
|
257
|
-
'baselines',
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
this.sender.broadcast(
|
|
261
|
-
libraryId,
|
|
262
|
-
{
|
|
263
|
-
type: 'op-re',
|
|
264
|
-
operations: ops,
|
|
265
|
-
baselines,
|
|
266
|
-
replicaId,
|
|
267
|
-
globalAckTimestamp:
|
|
268
|
-
(await this.storage.replicas.getGlobalAck(libraryId)) ?? undefined,
|
|
269
|
-
},
|
|
270
|
-
[clientKey],
|
|
271
|
-
);
|
|
272
|
-
};
|
|
273
|
-
|
|
274
272
|
private handleSync = async (
|
|
275
273
|
message: SyncMessage,
|
|
276
274
|
clientKey: string,
|
|
@@ -284,7 +282,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
284
282
|
!this.hasWriteAccess(info) &&
|
|
285
283
|
(message.operations.length > 0 || message.baselines.length > 0)
|
|
286
284
|
) {
|
|
287
|
-
this.sender.
|
|
285
|
+
this.sender.respond(clientKey, {
|
|
288
286
|
type: 'forbidden',
|
|
289
287
|
});
|
|
290
288
|
this.log('warn', 'sync with changes from read-only replica', replicaId);
|
|
@@ -293,11 +291,11 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
293
291
|
|
|
294
292
|
if (message.resyncAll) {
|
|
295
293
|
// forget our local understanding of the replica and reset it
|
|
296
|
-
await this.storage.replicas.delete(
|
|
294
|
+
await this.storage.replicas.delete(replicaId);
|
|
297
295
|
}
|
|
298
296
|
|
|
299
297
|
const { status, replicaInfo: clientReplicaInfo } =
|
|
300
|
-
await this.storage.replicas.getOrCreate(
|
|
298
|
+
await this.storage.replicas.getOrCreate(replicaId, info);
|
|
301
299
|
|
|
302
300
|
this.log(
|
|
303
301
|
'info',
|
|
@@ -309,7 +307,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
309
307
|
let overrideTruant = false;
|
|
310
308
|
if (status === 'truant') {
|
|
311
309
|
const latestServerOrder =
|
|
312
|
-
await this.storage.operations.getLatestServerOrder(
|
|
310
|
+
await this.storage.operations.getLatestServerOrder();
|
|
313
311
|
if (clientReplicaInfo.ackedServerOrder >= latestServerOrder) {
|
|
314
312
|
overrideTruant = true;
|
|
315
313
|
this.log(
|
|
@@ -321,7 +319,6 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
321
319
|
|
|
322
320
|
// lookup operations after the last ack the client gave us
|
|
323
321
|
const ops = await this.storage.operations.getAfterServerOrder(
|
|
324
|
-
info.libraryId,
|
|
325
322
|
clientReplicaInfo.ackedServerOrder,
|
|
326
323
|
);
|
|
327
324
|
|
|
@@ -336,7 +333,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
336
333
|
// replica will generate the same baseline locally, so it
|
|
337
334
|
// doesn't need to receive it from the server.
|
|
338
335
|
const baselines = replicaShouldReset
|
|
339
|
-
? await this.storage.baselines.getAll(
|
|
336
|
+
? await this.storage.baselines.getAll()
|
|
340
337
|
: [];
|
|
341
338
|
|
|
342
339
|
// We detect that a library is new by checking if it has any history.
|
|
@@ -377,7 +374,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
377
374
|
'Detected local data is incomplete, requesting full history from replica',
|
|
378
375
|
replicaId,
|
|
379
376
|
);
|
|
380
|
-
this.sender.
|
|
377
|
+
this.sender.respond(clientKey, {
|
|
381
378
|
type: 'need-since',
|
|
382
379
|
since: null,
|
|
383
380
|
});
|
|
@@ -412,7 +409,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
412
409
|
// we are not overwriting the replica's data
|
|
413
410
|
if (!overwriteReplicaData) {
|
|
414
411
|
// store all incoming operations and baselines
|
|
415
|
-
await this.storage.baselines.insertAll(
|
|
412
|
+
await this.storage.baselines.insertAll(message.baselines);
|
|
416
413
|
|
|
417
414
|
this.log(
|
|
418
415
|
'debug',
|
|
@@ -423,7 +420,6 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
423
420
|
'operations',
|
|
424
421
|
);
|
|
425
422
|
const newServerOrder = await this.storage.operations.insertAll(
|
|
426
|
-
info.libraryId,
|
|
427
423
|
replicaId,
|
|
428
424
|
message.operations,
|
|
429
425
|
);
|
|
@@ -433,7 +429,6 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
433
429
|
message.operations.length,
|
|
434
430
|
);
|
|
435
431
|
await this.storage.replicas.updateAcknowledgedLogicalTime(
|
|
436
|
-
info.libraryId,
|
|
437
432
|
replicaId,
|
|
438
433
|
message.timestamp,
|
|
439
434
|
);
|
|
@@ -450,7 +445,12 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
450
445
|
isEmptyLibrary ? message.baselines : undefined,
|
|
451
446
|
);
|
|
452
447
|
if (message.operations.length || message.baselines.length) {
|
|
453
|
-
this.emit(
|
|
448
|
+
this.events?.emit(
|
|
449
|
+
`changes`,
|
|
450
|
+
info,
|
|
451
|
+
message.operations,
|
|
452
|
+
message.baselines,
|
|
453
|
+
);
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
456
|
|
|
@@ -476,25 +476,32 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
476
476
|
'operations and',
|
|
477
477
|
baselines.length,
|
|
478
478
|
'baselines',
|
|
479
|
+
'to client key',
|
|
480
|
+
clientKey,
|
|
479
481
|
);
|
|
480
482
|
|
|
481
483
|
// mutating baselines/operations to remove extra data -
|
|
482
484
|
// mutating just to save on allocations here.
|
|
483
485
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
486
|
+
try {
|
|
487
|
+
this.sender.respond(clientKey, {
|
|
488
|
+
type: 'sync-resp',
|
|
489
|
+
operations: this.removeOperationExtras(ops),
|
|
490
|
+
baselines: this.removeBaselineExtras(baselines),
|
|
491
|
+
globalAckTimestamp:
|
|
492
|
+
(await this.storage.replicas.getGlobalAck()) ?? undefined,
|
|
493
|
+
peerPresence: this.presence.all(),
|
|
494
|
+
// only request the client to overwrite local data if a reset is requested
|
|
495
|
+
// and there is data to overwrite it. otherwise the client may still
|
|
496
|
+
// send its own history to us.
|
|
497
|
+
overwriteLocalData: overwriteReplicaData,
|
|
498
|
+
ackedTimestamp: message.timestamp,
|
|
499
|
+
ackThisNonce,
|
|
500
|
+
});
|
|
501
|
+
} catch (err) {
|
|
502
|
+
this.log('error', err);
|
|
503
|
+
throw err;
|
|
504
|
+
}
|
|
498
505
|
};
|
|
499
506
|
|
|
500
507
|
private createAckNonce = (serverOrder: number): string | undefined => {
|
|
@@ -511,6 +518,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
511
518
|
Buffer.from(message.nonce, 'base64').toString('utf8'),
|
|
512
519
|
);
|
|
513
520
|
this.log(
|
|
521
|
+
'debug',
|
|
514
522
|
'Ack from',
|
|
515
523
|
message.replicaId,
|
|
516
524
|
'(user',
|
|
@@ -526,21 +534,17 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
526
534
|
|
|
527
535
|
const replicaId = message.replicaId;
|
|
528
536
|
await this.storage.replicas.updateAckedServerOrder(
|
|
529
|
-
info.libraryId,
|
|
530
537
|
replicaId,
|
|
531
538
|
decodedNonce,
|
|
532
539
|
);
|
|
533
540
|
} else if (message.timestamp) {
|
|
534
541
|
await this.storage.replicas.acknowledgeOperation(
|
|
535
|
-
info.libraryId,
|
|
536
542
|
message.replicaId,
|
|
537
543
|
message.timestamp,
|
|
538
544
|
);
|
|
539
|
-
const globalAck = await this.storage.replicas.getGlobalAck(
|
|
540
|
-
info.libraryId,
|
|
541
|
-
);
|
|
545
|
+
const globalAck = await this.storage.replicas.getGlobalAck();
|
|
542
546
|
if (globalAck) {
|
|
543
|
-
this.sender.broadcast(
|
|
547
|
+
this.sender.broadcast({
|
|
544
548
|
type: 'global-ack',
|
|
545
549
|
timestamp: globalAck,
|
|
546
550
|
});
|
|
@@ -555,16 +559,12 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
555
559
|
ignoreClientKeys: string[] = [],
|
|
556
560
|
) => {
|
|
557
561
|
await this.storage.replicas.updateAcknowledgedLogicalTime(
|
|
558
|
-
info.libraryId,
|
|
559
562
|
replicaId,
|
|
560
563
|
timestamp,
|
|
561
564
|
);
|
|
562
|
-
const newGlobalAck = await this.storage.replicas.getGlobalAck(
|
|
563
|
-
info.libraryId,
|
|
564
|
-
);
|
|
565
|
+
const newGlobalAck = await this.storage.replicas.getGlobalAck();
|
|
565
566
|
if (newGlobalAck) {
|
|
566
567
|
this.sender.broadcast(
|
|
567
|
-
info.libraryId,
|
|
568
568
|
{
|
|
569
569
|
type: 'global-ack',
|
|
570
570
|
timestamp: newGlobalAck,
|
|
@@ -575,15 +575,15 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
575
575
|
};
|
|
576
576
|
|
|
577
577
|
private pendingRebaseTimeout: NodeJS.Timeout | null = null;
|
|
578
|
-
private enqueueRebase = (
|
|
578
|
+
private enqueueRebase = () => {
|
|
579
579
|
if (this.disableRebasing) return;
|
|
580
580
|
|
|
581
581
|
if (!this.pendingRebaseTimeout) {
|
|
582
|
-
setTimeout(() => this.rebase(
|
|
582
|
+
setTimeout(() => this.rebase(), 0);
|
|
583
583
|
}
|
|
584
584
|
};
|
|
585
585
|
|
|
586
|
-
private rebase = async (
|
|
586
|
+
private rebase = async () => {
|
|
587
587
|
if (this.disableRebasing) return;
|
|
588
588
|
|
|
589
589
|
this.log('debug', 'Performing rebase check');
|
|
@@ -604,15 +604,13 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
604
604
|
// for global ack, to determine consensus, also allow
|
|
605
605
|
// for all actively connected replicas to ack regardless of their
|
|
606
606
|
// type.
|
|
607
|
-
const activeReplicaIds = Object.values(this.
|
|
607
|
+
const activeReplicaIds = Object.values(this.presence.all()).map(
|
|
608
608
|
(p) => p.replicaId,
|
|
609
609
|
);
|
|
610
|
-
const globalAck =
|
|
611
|
-
|
|
612
|
-
activeReplicaIds,
|
|
613
|
-
);
|
|
610
|
+
const globalAck =
|
|
611
|
+
await this.storage.replicas.getGlobalAck(activeReplicaIds);
|
|
614
612
|
const globalServerOrder =
|
|
615
|
-
await this.storage.replicas.getEarliestAckedServerOrder(
|
|
613
|
+
await this.storage.replicas.getEarliestAckedServerOrder();
|
|
616
614
|
|
|
617
615
|
if (!globalAck) {
|
|
618
616
|
this.log('debug', 'No global ack, skipping rebase');
|
|
@@ -625,10 +623,8 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
625
623
|
}
|
|
626
624
|
|
|
627
625
|
// these are in forward chronological order
|
|
628
|
-
const ops =
|
|
629
|
-
|
|
630
|
-
globalServerOrder,
|
|
631
|
-
);
|
|
626
|
+
const ops =
|
|
627
|
+
await this.storage.operations.getBeforeServerOrder(globalServerOrder);
|
|
632
628
|
|
|
633
629
|
const opsToApply: Record<string, StoredOperation[]> = {};
|
|
634
630
|
// if we encounter a sequential operation in history which does
|
|
@@ -648,18 +644,17 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
648
644
|
|
|
649
645
|
const deletedRefs = new Array<Ref>();
|
|
650
646
|
for (const [documentId, ops] of Object.entries(opsToApply)) {
|
|
651
|
-
this.log('Rebasing', documentId);
|
|
647
|
+
this.log('debug', 'Rebasing', documentId);
|
|
652
648
|
await this.storage.baselines.applyOperations(
|
|
653
|
-
libraryId,
|
|
654
649
|
documentId,
|
|
655
650
|
ops,
|
|
656
651
|
deletedRefs,
|
|
657
652
|
);
|
|
658
|
-
await this.storage.operations.delete(
|
|
653
|
+
await this.storage.operations.delete(ops);
|
|
659
654
|
}
|
|
660
655
|
|
|
661
656
|
// hint to clients they can rebase too
|
|
662
|
-
this.sender.broadcast(
|
|
657
|
+
this.sender.broadcast({
|
|
663
658
|
type: 'global-ack',
|
|
664
659
|
timestamp: globalAck,
|
|
665
660
|
});
|
|
@@ -668,9 +663,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
668
663
|
await Promise.all(
|
|
669
664
|
deletedRefs
|
|
670
665
|
.filter(isFileRef)
|
|
671
|
-
.map((ref) =>
|
|
672
|
-
this.storage.fileMetadata.markPendingDelete(libraryId, ref.id),
|
|
673
|
-
),
|
|
666
|
+
.map((ref) => this.storage.fileMetadata.markPendingDelete(ref.id)),
|
|
674
667
|
);
|
|
675
668
|
};
|
|
676
669
|
|
|
@@ -679,7 +672,7 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
679
672
|
clientKey: string,
|
|
680
673
|
info: TokenInfo,
|
|
681
674
|
) => {
|
|
682
|
-
this.sender.
|
|
675
|
+
this.sender.respond(clientKey, {
|
|
683
676
|
type: 'heartbeat-response',
|
|
684
677
|
});
|
|
685
678
|
};
|
|
@@ -689,14 +682,13 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
689
682
|
clientKey: string,
|
|
690
683
|
info: TokenInfo,
|
|
691
684
|
) => {
|
|
692
|
-
const updated = await this.
|
|
685
|
+
const updated = await this.presence.set(clientKey, info.userId, {
|
|
693
686
|
presence: message.presence,
|
|
694
687
|
internal: message.internal,
|
|
695
688
|
replicaId: message.replicaId,
|
|
696
689
|
id: info.userId,
|
|
697
690
|
});
|
|
698
691
|
this.sender.broadcast(
|
|
699
|
-
info.libraryId,
|
|
700
692
|
{
|
|
701
693
|
type: 'presence-changed',
|
|
702
694
|
replicaId: message.replicaId,
|
|
@@ -707,27 +699,21 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
707
699
|
);
|
|
708
700
|
};
|
|
709
701
|
|
|
710
|
-
private onPresenceLost = async (
|
|
711
|
-
libraryId: string,
|
|
712
|
-
replicaId: string,
|
|
713
|
-
userId: string,
|
|
714
|
-
) => {
|
|
702
|
+
private onPresenceLost = async (replicaId: string, userId: string) => {
|
|
715
703
|
this.log('info', 'User disconnected from all replicas:', userId);
|
|
716
|
-
this.sender.broadcast(
|
|
704
|
+
this.sender.broadcast({
|
|
717
705
|
type: 'presence-offline',
|
|
718
706
|
replicaId,
|
|
719
707
|
userId,
|
|
720
708
|
});
|
|
721
|
-
if (Object.keys(this.
|
|
722
|
-
this.log('info', `All users have disconnected
|
|
709
|
+
if (Object.keys(this.presence.all()).length === 0) {
|
|
710
|
+
this.log('info', `All users have disconnected`);
|
|
723
711
|
// could happen - if the server is shutting down manually
|
|
724
712
|
if (!this.storage.open) {
|
|
725
713
|
this.log('debug', 'Database not open, skipping cleanup');
|
|
726
714
|
return;
|
|
727
715
|
}
|
|
728
|
-
const pendingDelete = await this.storage.fileMetadata.getPendingDelete(
|
|
729
|
-
libraryId,
|
|
730
|
-
);
|
|
716
|
+
const pendingDelete = await this.storage.fileMetadata.getPendingDelete();
|
|
731
717
|
if (pendingDelete.length > 0) {
|
|
732
718
|
if (!this.fileStorage) {
|
|
733
719
|
throw new Error('File storage not configured, cannot delete files');
|
|
@@ -741,12 +727,11 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
741
727
|
pendingDelete.map(async (fileInfo): Promise<void> => {
|
|
742
728
|
try {
|
|
743
729
|
await this.fileStorage?.delete({
|
|
744
|
-
libraryId: fileInfo.libraryId,
|
|
745
730
|
fileName: fileInfo.name,
|
|
746
731
|
id: fileInfo.fileId,
|
|
747
732
|
type: fileInfo.type,
|
|
748
733
|
});
|
|
749
|
-
this.storage.fileMetadata.delete(
|
|
734
|
+
await this.storage.fileMetadata.delete(fileInfo.fileId);
|
|
750
735
|
} catch (e) {
|
|
751
736
|
this.log(
|
|
752
737
|
'error',
|
|
@@ -762,62 +747,57 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
762
747
|
}
|
|
763
748
|
};
|
|
764
749
|
|
|
765
|
-
destroy = async (
|
|
766
|
-
this.log('info', 'Destroying library'
|
|
767
|
-
this.
|
|
750
|
+
destroy = async () => {
|
|
751
|
+
this.log('info', 'Destroying library');
|
|
752
|
+
this.presence.clear();
|
|
768
753
|
await Promise.all([
|
|
769
|
-
this.storage.replicas.deleteAll(
|
|
770
|
-
this.storage.operations.deleteAll(
|
|
771
|
-
this.storage.baselines.deleteAll(
|
|
754
|
+
this.storage.replicas.deleteAll(),
|
|
755
|
+
this.storage.operations.deleteAll(),
|
|
756
|
+
this.storage.baselines.deleteAll(),
|
|
772
757
|
]);
|
|
773
|
-
const allFiles = await this.storage.fileMetadata.getAll(
|
|
758
|
+
const allFiles = await this.storage.fileMetadata.getAll();
|
|
774
759
|
if (allFiles.length > 0) {
|
|
775
760
|
this.log(
|
|
776
761
|
'info',
|
|
777
|
-
`Deleting ${allFiles.length} files
|
|
762
|
+
`Deleting ${allFiles.length} files`,
|
|
778
763
|
allFiles.map((f) => f.fileId).join(', '),
|
|
779
764
|
);
|
|
780
765
|
for (const fileInfo of allFiles) {
|
|
781
766
|
await this.fileStorage?.delete({
|
|
782
|
-
libraryId,
|
|
783
767
|
fileName: fileInfo.name,
|
|
784
768
|
id: fileInfo.fileId,
|
|
785
769
|
type: fileInfo.type,
|
|
786
770
|
});
|
|
787
771
|
}
|
|
788
|
-
await this.storage.fileMetadata.deleteAll(
|
|
772
|
+
await this.storage.fileMetadata.deleteAll();
|
|
789
773
|
}
|
|
790
774
|
};
|
|
791
775
|
|
|
792
|
-
getPresence = (
|
|
793
|
-
return this.
|
|
776
|
+
getPresence = () => {
|
|
777
|
+
return Promise.resolve(this.presence.all());
|
|
794
778
|
};
|
|
795
779
|
|
|
796
|
-
getInfo = async (
|
|
797
|
-
const rawReplicas = await this.storage.replicas.getAll(
|
|
798
|
-
const
|
|
799
|
-
rawReplicas.map((r) => this.profiles.get(r.clientId)),
|
|
800
|
-
);
|
|
801
|
-
const replicas = rawReplicas.map((r, index) => ({
|
|
780
|
+
getInfo = async (): Promise<LibraryInfo | null> => {
|
|
781
|
+
const rawReplicas = await this.storage.replicas.getAll();
|
|
782
|
+
const replicas = rawReplicas.map((r) => ({
|
|
802
783
|
id: r.id,
|
|
784
|
+
userId: r.clientId,
|
|
803
785
|
ackedLogicalTime: r.ackedLogicalTime,
|
|
804
786
|
ackedServerOrder: r.ackedServerOrder,
|
|
805
787
|
type: r.type,
|
|
806
788
|
truant:
|
|
807
789
|
!!r.lastSeenWallClockTime &&
|
|
808
790
|
r.lastSeenWallClockTime < this.storage.replicas.truantCutoff,
|
|
809
|
-
profile:
|
|
791
|
+
profile: this.presence.get(r.clientId)?.profile ?? null,
|
|
810
792
|
}));
|
|
811
793
|
|
|
812
794
|
const data = {
|
|
813
|
-
id:
|
|
795
|
+
id: this.id,
|
|
814
796
|
replicas,
|
|
815
|
-
latestServerOrder: await this.storage.operations.getLatestServerOrder(
|
|
816
|
-
|
|
817
|
-
),
|
|
818
|
-
|
|
819
|
-
baselinesCount: await this.storage.baselines.getCount(libraryId),
|
|
820
|
-
globalAck: (await this.storage.replicas.getGlobalAck(libraryId)) ?? null,
|
|
797
|
+
latestServerOrder: await this.storage.operations.getLatestServerOrder(),
|
|
798
|
+
operationsCount: await this.storage.operations.getCount(),
|
|
799
|
+
baselinesCount: await this.storage.baselines.getCount(),
|
|
800
|
+
globalAck: (await this.storage.replicas.getGlobalAck()) ?? null,
|
|
821
801
|
};
|
|
822
802
|
|
|
823
803
|
if (
|
|
@@ -831,35 +811,53 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
831
811
|
return data;
|
|
832
812
|
};
|
|
833
813
|
|
|
834
|
-
evictUser = async (
|
|
835
|
-
await this.storage.replicas.deleteAllForUser(
|
|
814
|
+
evictUser = async (userId: string) => {
|
|
815
|
+
await this.storage.replicas.deleteAllForUser(userId);
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
forceTruant = async (replicaId: string) => {
|
|
819
|
+
await this.storage.replicas.forceTruant(replicaId);
|
|
836
820
|
};
|
|
837
821
|
|
|
838
|
-
getDocumentSnapshot = (
|
|
839
|
-
return this.hydrateObject(
|
|
822
|
+
getDocumentSnapshot = (oid: string) => {
|
|
823
|
+
return this.hydrateObject(oid);
|
|
840
824
|
};
|
|
841
825
|
|
|
842
|
-
|
|
826
|
+
getFileInfo = async (fileId: string) => {
|
|
827
|
+
const rawFile = await this.hydrateFile(fileId);
|
|
828
|
+
if (rawFile) {
|
|
829
|
+
return {
|
|
830
|
+
...rawFile,
|
|
831
|
+
libraryId: this.id,
|
|
832
|
+
};
|
|
833
|
+
} else {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
putFileInfo: (info: FileInfo) => Promise<void> = async (info) => {
|
|
839
|
+
this.log('debug', 'Storing file info', info.id, info.fileName);
|
|
840
|
+
await this.storage.fileMetadata.put(info);
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
private getObjectSnapshot = async (oid: string) => {
|
|
843
844
|
const [baseline, ops] = await Promise.all([
|
|
844
|
-
this.storage.baselines.get(
|
|
845
|
-
this.storage.operations.getAll(
|
|
845
|
+
this.storage.baselines.get(oid),
|
|
846
|
+
this.storage.operations.getAll(oid),
|
|
846
847
|
]);
|
|
847
848
|
const snapshot = applyOperations(baseline?.snapshot ?? undefined, ops);
|
|
848
849
|
return snapshot;
|
|
849
850
|
};
|
|
850
851
|
|
|
851
|
-
private hydrateObject = async (
|
|
852
|
-
|
|
853
|
-
oid: string,
|
|
854
|
-
): Promise<any> => {
|
|
855
|
-
const snapshot = await this.getObjectSnapshot(libraryId, oid);
|
|
852
|
+
private hydrateObject = async (oid: string): Promise<any> => {
|
|
853
|
+
const snapshot = await this.getObjectSnapshot(oid);
|
|
856
854
|
if (Array.isArray(snapshot)) {
|
|
857
855
|
return Promise.all(
|
|
858
856
|
snapshot.map(async (item: any) => {
|
|
859
857
|
if (isObjectRef(item)) {
|
|
860
|
-
return this.hydrateObject(
|
|
858
|
+
return this.hydrateObject(item.id);
|
|
861
859
|
} else if (isFileRef(item)) {
|
|
862
|
-
return this.hydrateFile(
|
|
860
|
+
return this.hydrateFile(item.id);
|
|
863
861
|
} else {
|
|
864
862
|
return item;
|
|
865
863
|
}
|
|
@@ -870,9 +868,9 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
870
868
|
await Promise.all(
|
|
871
869
|
Object.entries(snapshot).map(async ([key, value]) => {
|
|
872
870
|
if (isObjectRef(value)) {
|
|
873
|
-
hydrated[key] = await this.hydrateObject(
|
|
871
|
+
hydrated[key] = await this.hydrateObject(value.id);
|
|
874
872
|
} else if (isFileRef(value)) {
|
|
875
|
-
hydrated[key] = await this.hydrateFile(
|
|
873
|
+
hydrated[key] = await this.hydrateFile(value.id);
|
|
876
874
|
}
|
|
877
875
|
}),
|
|
878
876
|
);
|
|
@@ -882,22 +880,23 @@ export class ServerLibrary extends EventSubscriber<ServerLibraryEvents> {
|
|
|
882
880
|
}
|
|
883
881
|
};
|
|
884
882
|
|
|
885
|
-
private hydrateFile = async (
|
|
886
|
-
|
|
883
|
+
private hydrateFile = async (
|
|
884
|
+
fileId: string,
|
|
885
|
+
): Promise<Omit<LibraryFileInfo, 'libraryId'> | null> => {
|
|
886
|
+
const data = await this.storage.fileMetadata.get(fileId);
|
|
887
887
|
if (data) {
|
|
888
888
|
const url =
|
|
889
|
-
this.fileStorage?.getUrl({
|
|
889
|
+
(await this.fileStorage?.getUrl({
|
|
890
890
|
fileName: data.name,
|
|
891
891
|
id: data.fileId,
|
|
892
|
-
libraryId,
|
|
893
892
|
type: data.type,
|
|
894
|
-
}) ?? null;
|
|
893
|
+
})) ?? null;
|
|
895
894
|
|
|
896
895
|
return {
|
|
897
|
-
url,
|
|
898
896
|
id: data.fileId,
|
|
899
897
|
name: data.name,
|
|
900
898
|
type: data.type,
|
|
899
|
+
url,
|
|
901
900
|
};
|
|
902
901
|
} else {
|
|
903
902
|
return null;
|