@nymphjs/pubsub 1.0.0-beta.13 → 1.0.0-beta.130
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/CHANGELOG.md +534 -0
- package/README.md +12 -10
- package/dist/PubSub.d.ts +77 -14
- package/dist/PubSub.js +548 -213
- package/dist/PubSub.js.map +1 -1
- package/dist/PubSub.test.js +265 -81
- package/dist/PubSub.test.js.map +1 -1
- package/dist/PubSub.types.d.ts +11 -2
- package/dist/PubSub.types.js +1 -2
- package/dist/conf/defaults.d.ts +1 -1
- package/dist/conf/defaults.js +4 -4
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +31 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/createServer.d.ts +2 -2
- package/dist/createServer.js +14 -15
- package/dist/createServer.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +7 -27
- package/dist/index.js.map +1 -1
- package/jest.config.js +31 -5
- package/package.json +37 -28
- package/src/PubSub.test.ts +343 -137
- package/src/PubSub.ts +796 -481
- package/src/PubSub.types.ts +11 -6
- package/src/conf/defaults.ts +2 -2
- package/src/conf/index.ts +31 -2
- package/src/createServer.ts +8 -8
- package/src/index.ts +4 -4
- package/tsconfig.json +6 -3
- package/typedoc.json +4 -0
- package/dist/conf/d.d.ts +0 -7
- package/dist/conf/d.js +0 -3
- package/dist/conf/d.js.map +0 -1
- package/src/conf/d.ts +0 -30
package/src/PubSub.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Nymph,
|
|
3
|
+
Entity,
|
|
3
4
|
EntityConstructor,
|
|
4
5
|
EntityInterface,
|
|
5
6
|
Options,
|
|
6
7
|
Selector,
|
|
7
8
|
SerializedEntityData,
|
|
8
9
|
classNamesToEntityConstructors,
|
|
10
|
+
referencesToEntities,
|
|
11
|
+
entityConstructorsToClassNames,
|
|
12
|
+
entitiesToReferences,
|
|
9
13
|
} from '@nymphjs/nymph';
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
connection,
|
|
15
|
-
Message,
|
|
16
|
-
} from 'websocket';
|
|
17
|
-
import { difference } from 'lodash';
|
|
18
|
-
|
|
19
|
-
import { Config, ConfigDefaults as defaults } from './conf';
|
|
14
|
+
import ws from 'websocket';
|
|
15
|
+
import { difference } from 'lodash-es';
|
|
16
|
+
|
|
17
|
+
import { Config, ConfigDefaults as defaults } from './conf/index.js';
|
|
20
18
|
import type {
|
|
21
19
|
QuerySubscriptionData,
|
|
22
20
|
AuthenticateMessageData,
|
|
@@ -28,14 +26,14 @@ import type {
|
|
|
28
26
|
PublishMessageData,
|
|
29
27
|
MessageData,
|
|
30
28
|
MessageOptions,
|
|
31
|
-
} from './PubSub.types';
|
|
29
|
+
} from './PubSub.types.js';
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
32
|
* A publish/subscribe server for Nymph.
|
|
35
33
|
*
|
|
36
34
|
* Written by Hunter Perrin for SciActive.
|
|
37
35
|
*
|
|
38
|
-
* @author Hunter Perrin <hperrin@
|
|
36
|
+
* @author Hunter Perrin <hperrin@port87.com>
|
|
39
37
|
* @copyright SciActive Inc
|
|
40
38
|
* @see http://nymph.io/
|
|
41
39
|
*/
|
|
@@ -53,17 +51,25 @@ export default class PubSub {
|
|
|
53
51
|
/**
|
|
54
52
|
* The WebSocket server.
|
|
55
53
|
*/
|
|
56
|
-
public server:
|
|
54
|
+
public server: ws.server;
|
|
57
55
|
|
|
58
|
-
private sessions = new Map<
|
|
56
|
+
private sessions = new Map<
|
|
57
|
+
ws.connection,
|
|
58
|
+
{ authToken: string; switchToken?: string }
|
|
59
|
+
>();
|
|
59
60
|
protected querySubs: {
|
|
60
61
|
[etype: string]: {
|
|
61
|
-
[query: string]: Map<connection, QuerySubscriptionData>;
|
|
62
|
+
[query: string]: Map<ws.connection, QuerySubscriptionData>;
|
|
62
63
|
};
|
|
63
64
|
} = {};
|
|
64
65
|
protected uidSubs: {
|
|
65
|
-
[uidName: string]: Map<connection, { count: boolean }>;
|
|
66
|
+
[uidName: string]: Map<ws.connection, { count: boolean }>;
|
|
66
67
|
} = {};
|
|
68
|
+
protected outgoingClientMessages = new Map<
|
|
69
|
+
ws.connection,
|
|
70
|
+
{ subscription: QuerySubscriptionData; publish: PublishEntityMessageData }[]
|
|
71
|
+
>();
|
|
72
|
+
protected outgoingClientMessagesTimeout: NodeJS.Immediate | null = null;
|
|
67
73
|
protected static transactionPublishes: {
|
|
68
74
|
nymph: Nymph;
|
|
69
75
|
payload: string;
|
|
@@ -72,226 +78,267 @@ export default class PubSub {
|
|
|
72
78
|
|
|
73
79
|
public static initPublisher(config: Partial<Config>, nymph: Nymph) {
|
|
74
80
|
const configWithDefaults: Config = { ...defaults, ...config };
|
|
81
|
+
const unsubscribers: (() => boolean)[] = [];
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
unsubscribers.push(
|
|
84
|
+
nymph.on('beforeSaveEntity', async (enymph, entity) => {
|
|
85
|
+
const guid = entity.guid;
|
|
86
|
+
const EntityClass = entity.constructor as EntityConstructor;
|
|
87
|
+
const etype = EntityClass.ETYPE;
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
off();
|
|
82
|
-
off2();
|
|
83
|
-
if (!(await result)) {
|
|
89
|
+
if (!EntityClass.pubSubEnabled) {
|
|
84
90
|
return;
|
|
85
91
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
|
|
93
|
+
const off = enymph.on('afterSaveEntity', async (curNymph, result) => {
|
|
94
|
+
off();
|
|
95
|
+
off2();
|
|
96
|
+
if (!(await result)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const payload = JSON.stringify({
|
|
100
|
+
action: 'publish',
|
|
101
|
+
event: guid == null ? 'create' : 'update',
|
|
102
|
+
guid: entity.guid,
|
|
103
|
+
entity: entity.toJSON(),
|
|
104
|
+
etype: etype,
|
|
105
|
+
});
|
|
106
|
+
this.transactionPublishes.push({
|
|
107
|
+
nymph: curNymph,
|
|
108
|
+
payload,
|
|
109
|
+
config: configWithDefaults,
|
|
110
|
+
});
|
|
111
|
+
await this.publishTransactionPublishes(curNymph);
|
|
92
112
|
});
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
config: configWithDefaults,
|
|
113
|
+
const off2 = enymph.on('failedSaveEntity', async () => {
|
|
114
|
+
off();
|
|
115
|
+
off2();
|
|
97
116
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const off2 = nymph.on('failedSaveEntity', async () => {
|
|
101
|
-
off();
|
|
102
|
-
off2();
|
|
103
|
-
});
|
|
104
|
-
});
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
105
119
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
unsubscribers.push(
|
|
121
|
+
nymph.on('beforeDeleteEntity', async (enymph, entity) => {
|
|
122
|
+
const guid = entity.guid;
|
|
123
|
+
const EntityClass = entity.constructor as EntityConstructor;
|
|
124
|
+
const etype = EntityClass.ETYPE;
|
|
109
125
|
|
|
110
|
-
|
|
111
|
-
off();
|
|
112
|
-
off2();
|
|
113
|
-
if (!(await result)) {
|
|
126
|
+
if (!EntityClass.pubSubEnabled) {
|
|
114
127
|
return;
|
|
115
128
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
|
|
130
|
+
const off = enymph.on('afterDeleteEntity', async (curNymph, result) => {
|
|
131
|
+
off();
|
|
132
|
+
off2();
|
|
133
|
+
if (!(await result)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const payload = JSON.stringify({
|
|
137
|
+
action: 'publish',
|
|
138
|
+
event: 'delete',
|
|
139
|
+
guid: guid,
|
|
140
|
+
etype: etype,
|
|
141
|
+
});
|
|
142
|
+
this.transactionPublishes.push({
|
|
143
|
+
nymph: curNymph,
|
|
144
|
+
payload,
|
|
145
|
+
config: configWithDefaults,
|
|
146
|
+
});
|
|
147
|
+
await this.publishTransactionPublishes(curNymph);
|
|
121
148
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
config: configWithDefaults,
|
|
149
|
+
const off2 = enymph.on('failedDeleteEntity', async () => {
|
|
150
|
+
off();
|
|
151
|
+
off2();
|
|
126
152
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const off2 = nymph.on('failedDeleteEntity', async () => {
|
|
130
|
-
off();
|
|
131
|
-
off2();
|
|
132
|
-
});
|
|
133
|
-
});
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
134
155
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
156
|
+
unsubscribers.push(
|
|
157
|
+
nymph.on('beforeDeleteEntityByID', async (enymph, guid, className) => {
|
|
158
|
+
try {
|
|
159
|
+
const EntityClass = enymph.getEntityClass(className ?? 'Entity');
|
|
160
|
+
const etype = EntityClass.ETYPE;
|
|
138
161
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
162
|
+
if (!EntityClass.pubSubEnabled) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const off = enymph.on(
|
|
167
|
+
'afterDeleteEntityByID',
|
|
168
|
+
async (curNymph, result) => {
|
|
169
|
+
off();
|
|
170
|
+
off2();
|
|
171
|
+
if (!(await result)) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const payload = JSON.stringify({
|
|
175
|
+
action: 'publish',
|
|
176
|
+
event: 'delete',
|
|
177
|
+
guid: guid,
|
|
178
|
+
etype: etype,
|
|
179
|
+
});
|
|
180
|
+
this.transactionPublishes.push({
|
|
181
|
+
nymph: curNymph,
|
|
182
|
+
payload,
|
|
183
|
+
config: configWithDefaults,
|
|
184
|
+
});
|
|
185
|
+
await this.publishTransactionPublishes(curNymph);
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
const off2 = enymph.on('failedDeleteEntityByID', async () => {
|
|
142
189
|
off();
|
|
143
190
|
off2();
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
191
|
+
});
|
|
192
|
+
} catch (e: any) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
unsubscribers.push(
|
|
199
|
+
nymph.on('beforeNewUID', async (enymph, name) => {
|
|
200
|
+
const off = enymph.on('afterNewUID', async (curNymph, result) => {
|
|
201
|
+
off();
|
|
202
|
+
off2();
|
|
203
|
+
const value = await result;
|
|
204
|
+
if (value == null) {
|
|
205
|
+
return;
|
|
159
206
|
}
|
|
160
|
-
|
|
161
|
-
|
|
207
|
+
const payload = JSON.stringify({
|
|
208
|
+
action: 'publish',
|
|
209
|
+
event: 'newUID',
|
|
210
|
+
name: name,
|
|
211
|
+
value: value,
|
|
212
|
+
});
|
|
213
|
+
this.transactionPublishes.push({
|
|
214
|
+
nymph: curNymph,
|
|
215
|
+
payload,
|
|
216
|
+
config: configWithDefaults,
|
|
217
|
+
});
|
|
218
|
+
await this.publishTransactionPublishes(curNymph);
|
|
219
|
+
});
|
|
220
|
+
const off2 = enymph.on('failedNewUID', async () => {
|
|
162
221
|
off();
|
|
163
222
|
off2();
|
|
164
223
|
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
});
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
169
226
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
off()
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
227
|
+
unsubscribers.push(
|
|
228
|
+
nymph.on('beforeSetUID', async (enymph, name, value) => {
|
|
229
|
+
const off = enymph.on('afterSetUID', async (curNymph, result) => {
|
|
230
|
+
off();
|
|
231
|
+
off2();
|
|
232
|
+
if (!(await result)) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const payload = JSON.stringify({
|
|
236
|
+
action: 'publish',
|
|
237
|
+
event: 'setUID',
|
|
238
|
+
name: name,
|
|
239
|
+
value: value,
|
|
240
|
+
});
|
|
241
|
+
this.transactionPublishes.push({
|
|
242
|
+
nymph: curNymph,
|
|
243
|
+
payload,
|
|
244
|
+
config: configWithDefaults,
|
|
245
|
+
});
|
|
246
|
+
await this.publishTransactionPublishes(curNymph);
|
|
183
247
|
});
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
config: configWithDefaults,
|
|
248
|
+
const off2 = enymph.on('failedSetUID', async () => {
|
|
249
|
+
off();
|
|
250
|
+
off2();
|
|
188
251
|
});
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const off2 = nymph.on('failedNewUID', async () => {
|
|
192
|
-
off();
|
|
193
|
-
off2();
|
|
194
|
-
});
|
|
195
|
-
});
|
|
252
|
+
}),
|
|
253
|
+
);
|
|
196
254
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
off()
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
255
|
+
unsubscribers.push(
|
|
256
|
+
nymph.on('beforeRenameUID', async (enymph, oldName, newName) => {
|
|
257
|
+
const off = enymph.on('afterRenameUID', async (curNymph, result) => {
|
|
258
|
+
off();
|
|
259
|
+
off2();
|
|
260
|
+
if (!(await result)) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const payload = JSON.stringify({
|
|
264
|
+
action: 'publish',
|
|
265
|
+
event: 'renameUID',
|
|
266
|
+
oldName: oldName,
|
|
267
|
+
newName: newName,
|
|
268
|
+
});
|
|
269
|
+
this.transactionPublishes.push({
|
|
270
|
+
nymph: curNymph,
|
|
271
|
+
payload,
|
|
272
|
+
config: configWithDefaults,
|
|
273
|
+
});
|
|
274
|
+
await this.publishTransactionPublishes(curNymph);
|
|
209
275
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
config: configWithDefaults,
|
|
276
|
+
const off2 = enymph.on('failedRenameUID', async () => {
|
|
277
|
+
off();
|
|
278
|
+
off2();
|
|
214
279
|
});
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const off2 = nymph.on('failedSetUID', async () => {
|
|
218
|
-
off();
|
|
219
|
-
off2();
|
|
220
|
-
});
|
|
221
|
-
});
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
222
282
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
off()
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
283
|
+
unsubscribers.push(
|
|
284
|
+
nymph.on('beforeDeleteUID', async (enymph, name) => {
|
|
285
|
+
const off = enymph.on('afterDeleteUID', async (curNymph, result) => {
|
|
286
|
+
off();
|
|
287
|
+
off2();
|
|
288
|
+
if (!(await result)) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const payload = JSON.stringify({
|
|
292
|
+
action: 'publish',
|
|
293
|
+
event: 'deleteUID',
|
|
294
|
+
name: name,
|
|
295
|
+
});
|
|
296
|
+
this.transactionPublishes.push({
|
|
297
|
+
nymph: curNymph,
|
|
298
|
+
payload,
|
|
299
|
+
config: configWithDefaults,
|
|
300
|
+
});
|
|
301
|
+
await this.publishTransactionPublishes(curNymph);
|
|
235
302
|
});
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
config: configWithDefaults,
|
|
303
|
+
const off2 = enymph.on('failedDeleteUID', async () => {
|
|
304
|
+
off();
|
|
305
|
+
off2();
|
|
240
306
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const off2 = nymph.on('failedRenameUID', async () => {
|
|
244
|
-
off();
|
|
245
|
-
off2();
|
|
246
|
-
});
|
|
247
|
-
});
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
248
309
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (!(await result)) {
|
|
254
|
-
return;
|
|
310
|
+
unsubscribers.push(
|
|
311
|
+
nymph.on('afterCommitTransaction', async (enymph, _name, result) => {
|
|
312
|
+
if (result) {
|
|
313
|
+
await this.publishTransactionPublishes(enymph);
|
|
255
314
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
event: 'deleteUID',
|
|
259
|
-
name: name,
|
|
260
|
-
});
|
|
261
|
-
this.transactionPublishes.push({
|
|
262
|
-
nymph: curNymph,
|
|
263
|
-
payload,
|
|
264
|
-
config: configWithDefaults,
|
|
265
|
-
});
|
|
266
|
-
await this.publishTransactionPublishes(curNymph);
|
|
267
|
-
});
|
|
268
|
-
const off2 = nymph.on('failedDeleteUID', async () => {
|
|
269
|
-
off();
|
|
270
|
-
off2();
|
|
271
|
-
});
|
|
272
|
-
});
|
|
315
|
+
}),
|
|
316
|
+
);
|
|
273
317
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
318
|
+
unsubscribers.push(
|
|
319
|
+
nymph.on('afterRollbackTransaction', async (enymph) => {
|
|
320
|
+
this.removeTransactionPublishes(enymph);
|
|
321
|
+
}),
|
|
322
|
+
);
|
|
279
323
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
324
|
+
return () => {
|
|
325
|
+
for (let unsubscribe of unsubscribers) {
|
|
326
|
+
unsubscribe();
|
|
327
|
+
}
|
|
328
|
+
this.transactionPublishes = [];
|
|
329
|
+
};
|
|
283
330
|
}
|
|
284
331
|
|
|
285
332
|
private static publish(message: string, config: Config) {
|
|
286
333
|
for (let host of config.entries ?? []) {
|
|
287
|
-
const client = new
|
|
334
|
+
const client = new ws.client();
|
|
288
335
|
|
|
289
336
|
client.on('connectFailed', (error) => {
|
|
290
337
|
if (config.logger) {
|
|
291
338
|
config.logger(
|
|
292
339
|
'error',
|
|
293
340
|
new Date().toISOString(),
|
|
294
|
-
`Publish connection failed. (${error.toString()}, ${host})
|
|
341
|
+
`Publish connection failed. (${error.toString()}, ${host})`,
|
|
295
342
|
);
|
|
296
343
|
}
|
|
297
344
|
});
|
|
@@ -302,7 +349,7 @@ export default class PubSub {
|
|
|
302
349
|
config.logger(
|
|
303
350
|
'error',
|
|
304
351
|
new Date().toISOString(),
|
|
305
|
-
`Publish connect error. (${error.toString()}, ${host})
|
|
352
|
+
`Publish connect error. (${error.toString()}, ${host})`,
|
|
306
353
|
);
|
|
307
354
|
}
|
|
308
355
|
});
|
|
@@ -318,7 +365,7 @@ export default class PubSub {
|
|
|
318
365
|
}
|
|
319
366
|
}
|
|
320
367
|
|
|
321
|
-
private static
|
|
368
|
+
private static isOrIsDescendant(parent: Nymph, child: Nymph) {
|
|
322
369
|
let check: Nymph | null = child;
|
|
323
370
|
while (check) {
|
|
324
371
|
if (check === parent) {
|
|
@@ -335,31 +382,52 @@ export default class PubSub {
|
|
|
335
382
|
return;
|
|
336
383
|
}
|
|
337
384
|
|
|
338
|
-
this.transactionPublishes =
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
385
|
+
let notReadyTransactionPublishes: typeof this.transactionPublishes = [];
|
|
386
|
+
let readyTransactionPublishes: typeof this.transactionPublishes = [];
|
|
387
|
+
|
|
388
|
+
for (let publish of this.transactionPublishes) {
|
|
389
|
+
if (
|
|
390
|
+
!this.isOrIsDescendant(nymph, publish.nymph) ||
|
|
391
|
+
(await publish.nymph.inTransaction())
|
|
392
|
+
) {
|
|
393
|
+
notReadyTransactionPublishes.push(publish);
|
|
394
|
+
} else {
|
|
395
|
+
readyTransactionPublishes.push(publish);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!readyTransactionPublishes.length) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
this.transactionPublishes = notReadyTransactionPublishes;
|
|
404
|
+
|
|
405
|
+
let configMap = new Map<Config, string[]>();
|
|
406
|
+
|
|
407
|
+
for (let publish of readyTransactionPublishes) {
|
|
408
|
+
const arr = configMap.get(publish.config) || [];
|
|
409
|
+
arr.push(publish.payload);
|
|
410
|
+
configMap.set(publish.config, arr);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
for (let [config, payloads] of configMap.entries()) {
|
|
414
|
+
if (payloads.length === 1) {
|
|
415
|
+
this.publish(payloads[0], config);
|
|
416
|
+
} else {
|
|
417
|
+
this.publish(
|
|
418
|
+
JSON.stringify({
|
|
419
|
+
action: 'multiple',
|
|
420
|
+
messages: payloads.map((payload) => JSON.parse(payload)),
|
|
421
|
+
}),
|
|
422
|
+
config,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
358
426
|
}
|
|
359
427
|
|
|
360
428
|
private static removeTransactionPublishes(nymph: Nymph) {
|
|
361
429
|
this.transactionPublishes = this.transactionPublishes.filter((publish) => {
|
|
362
|
-
if (this.
|
|
430
|
+
if (this.isOrIsDescendant(nymph, publish.nymph)) {
|
|
363
431
|
return false;
|
|
364
432
|
}
|
|
365
433
|
return true;
|
|
@@ -371,11 +439,7 @@ export default class PubSub {
|
|
|
371
439
|
*
|
|
372
440
|
* @param config The PubSub configuration.
|
|
373
441
|
*/
|
|
374
|
-
public constructor(
|
|
375
|
-
config: Partial<Config>,
|
|
376
|
-
nymph: Nymph,
|
|
377
|
-
server: WebSocketServer
|
|
378
|
-
) {
|
|
442
|
+
public constructor(config: Partial<Config>, nymph: Nymph, server: ws.server) {
|
|
379
443
|
this.nymph = nymph;
|
|
380
444
|
this.config = { ...defaults, ...config };
|
|
381
445
|
this.server = server;
|
|
@@ -387,14 +451,14 @@ export default class PubSub {
|
|
|
387
451
|
this.server.shutDown();
|
|
388
452
|
}
|
|
389
453
|
|
|
390
|
-
public handleRequest(request: request) {
|
|
454
|
+
public handleRequest(request: ws.request) {
|
|
391
455
|
if (!this.config.originIsAllowed(request.origin)) {
|
|
392
456
|
// Make sure we only accept requests from an allowed origin
|
|
393
457
|
request.reject();
|
|
394
458
|
this.config.logger(
|
|
395
459
|
'log',
|
|
396
460
|
new Date().toISOString(),
|
|
397
|
-
'Client from origin ' + request.origin + ' was kicked by the bouncer.'
|
|
461
|
+
'Client from origin ' + request.origin + ' was kicked by the bouncer.',
|
|
398
462
|
);
|
|
399
463
|
return;
|
|
400
464
|
}
|
|
@@ -403,7 +467,7 @@ export default class PubSub {
|
|
|
403
467
|
this.config.logger(
|
|
404
468
|
'log',
|
|
405
469
|
new Date().toISOString(),
|
|
406
|
-
`Client joined the party! (${connection.remoteAddress})
|
|
470
|
+
`Client joined the party! (${connection.remoteAddress}).`,
|
|
407
471
|
);
|
|
408
472
|
|
|
409
473
|
connection.on('error', (err) => {
|
|
@@ -422,41 +486,65 @@ export default class PubSub {
|
|
|
422
486
|
/**
|
|
423
487
|
* Handle a message from a client.
|
|
424
488
|
*/
|
|
425
|
-
public async onMessage(from: connection, msg: Message) {
|
|
489
|
+
public async onMessage(from: ws.connection, msg: ws.Message) {
|
|
426
490
|
if (msg.type !== 'utf8') {
|
|
427
491
|
throw new Error("This server doesn't accept binary messages.");
|
|
428
492
|
}
|
|
429
493
|
const data: MessageData = JSON.parse(msg.utf8Data);
|
|
430
494
|
if (
|
|
431
495
|
!data.action ||
|
|
432
|
-
[
|
|
433
|
-
|
|
434
|
-
|
|
496
|
+
[
|
|
497
|
+
'authenticate',
|
|
498
|
+
'subscribe',
|
|
499
|
+
'unsubscribe',
|
|
500
|
+
'publish',
|
|
501
|
+
'multiple',
|
|
502
|
+
].indexOf(data.action) === -1
|
|
435
503
|
) {
|
|
436
504
|
return;
|
|
437
505
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
506
|
+
const handleMessageData = async (data: MessageData, dontRelay = false) => {
|
|
507
|
+
switch (data.action) {
|
|
508
|
+
case 'authenticate':
|
|
509
|
+
this.handleAuthentication(from, data);
|
|
510
|
+
break;
|
|
511
|
+
case 'subscribe':
|
|
512
|
+
case 'unsubscribe':
|
|
513
|
+
await this.handleSubscription(from, data);
|
|
514
|
+
break;
|
|
515
|
+
case 'publish':
|
|
516
|
+
await this.handlePublish(from, data);
|
|
517
|
+
if (!dontRelay) {
|
|
518
|
+
// Relay the publish to other servers.
|
|
519
|
+
this.relay(msg);
|
|
520
|
+
}
|
|
521
|
+
break;
|
|
522
|
+
case 'multiple':
|
|
523
|
+
for (let individual of data.messages) {
|
|
524
|
+
await handleMessageData(
|
|
525
|
+
individual,
|
|
526
|
+
// These will be published with the multiple payload.
|
|
527
|
+
individual.action === 'publish',
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
if (data.messages.find((msg) => msg.action === 'publish') != null) {
|
|
531
|
+
// Relay the publish to other servers.
|
|
532
|
+
this.relay(msg);
|
|
533
|
+
}
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
await handleMessageData(data);
|
|
450
538
|
}
|
|
451
539
|
|
|
452
540
|
/**
|
|
453
541
|
* Clean up after users who leave.
|
|
454
542
|
*/
|
|
455
|
-
public onClose(conn: connection, description: string) {
|
|
543
|
+
public onClose(conn: ws.connection, description: string) {
|
|
456
544
|
this.config.logger(
|
|
457
545
|
'log',
|
|
458
546
|
new Date().toISOString(),
|
|
459
|
-
`Client skedaddled. (${description}, ${conn.remoteAddress})
|
|
547
|
+
`Client skedaddled. (${description}, ${conn.remoteAddress})`,
|
|
460
548
|
);
|
|
461
549
|
|
|
462
550
|
let mess = 0;
|
|
@@ -484,7 +572,7 @@ export default class PubSub {
|
|
|
484
572
|
JSON.stringify({
|
|
485
573
|
query: curData.query,
|
|
486
574
|
count,
|
|
487
|
-
})
|
|
575
|
+
}),
|
|
488
576
|
);
|
|
489
577
|
}
|
|
490
578
|
}
|
|
@@ -514,7 +602,7 @@ export default class PubSub {
|
|
|
514
602
|
JSON.stringify({
|
|
515
603
|
uid: curUID,
|
|
516
604
|
count,
|
|
517
|
-
})
|
|
605
|
+
}),
|
|
518
606
|
);
|
|
519
607
|
}
|
|
520
608
|
}
|
|
@@ -533,16 +621,16 @@ export default class PubSub {
|
|
|
533
621
|
this.config.logger(
|
|
534
622
|
'log',
|
|
535
623
|
new Date().toISOString(),
|
|
536
|
-
`Cleaned up client's mess. (${mess}, ${conn.remoteAddress})
|
|
624
|
+
`Cleaned up client's mess. (${mess}, ${conn.remoteAddress})`,
|
|
537
625
|
);
|
|
538
626
|
}
|
|
539
627
|
}
|
|
540
628
|
|
|
541
|
-
public onError(conn: connection, e: Error) {
|
|
629
|
+
public onError(conn: ws.connection, e: Error) {
|
|
542
630
|
this.config.logger(
|
|
543
631
|
'error',
|
|
544
632
|
new Date().toISOString(),
|
|
545
|
-
`An error occured. (${e.message}, ${conn.remoteAddress})
|
|
633
|
+
`An error occured. (${e.message}, ${conn.remoteAddress})`,
|
|
546
634
|
);
|
|
547
635
|
}
|
|
548
636
|
|
|
@@ -550,13 +638,14 @@ export default class PubSub {
|
|
|
550
638
|
* Handle an authentication from a client.
|
|
551
639
|
*/
|
|
552
640
|
private handleAuthentication(
|
|
553
|
-
from: connection,
|
|
554
|
-
data: AuthenticateMessageData
|
|
641
|
+
from: ws.connection,
|
|
642
|
+
data: AuthenticateMessageData,
|
|
555
643
|
) {
|
|
556
644
|
// Save the user's auth token in session storage.
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
645
|
+
const authToken = data.authToken;
|
|
646
|
+
const switchToken = data.switchToken;
|
|
647
|
+
if (authToken != null) {
|
|
648
|
+
this.sessions.set(from, { authToken, switchToken });
|
|
560
649
|
} else if (this.sessions.has(from)) {
|
|
561
650
|
this.sessions.delete(from);
|
|
562
651
|
}
|
|
@@ -566,21 +655,31 @@ export default class PubSub {
|
|
|
566
655
|
* Handle a subscribe or unsubscribe from a client.
|
|
567
656
|
*/
|
|
568
657
|
private async handleSubscription(
|
|
569
|
-
from: connection,
|
|
570
|
-
data: SubscribeMessageData
|
|
658
|
+
from: ws.connection,
|
|
659
|
+
data: SubscribeMessageData,
|
|
571
660
|
) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
661
|
+
try {
|
|
662
|
+
if ('query' in data && data.query != null) {
|
|
663
|
+
// Request is for a query.
|
|
664
|
+
|
|
665
|
+
await this.handleSubscriptionQuery(from, data);
|
|
666
|
+
} else if (
|
|
667
|
+
'uid' in data &&
|
|
668
|
+
data.uid != null &&
|
|
669
|
+
typeof data.uid == 'string'
|
|
670
|
+
) {
|
|
671
|
+
// Request is for a UID.
|
|
672
|
+
|
|
673
|
+
await this.handleSubscriptionUid(from, data);
|
|
674
|
+
}
|
|
675
|
+
} catch (e: any) {
|
|
676
|
+
if ('query' in data && data.query != null) {
|
|
677
|
+
from.sendUTF(JSON.stringify({ query: data.query, error: e.message }));
|
|
678
|
+
} else if ('uid' in data && data.uid != null) {
|
|
679
|
+
from.sendUTF(JSON.stringify({ uid: data.uid, error: e.message }));
|
|
680
|
+
} else {
|
|
681
|
+
from.sendUTF(JSON.stringify({ error: e.message }));
|
|
682
|
+
}
|
|
584
683
|
}
|
|
585
684
|
}
|
|
586
685
|
|
|
@@ -588,48 +687,58 @@ export default class PubSub {
|
|
|
588
687
|
* Handle a subscribe or unsubscribe for a query from a client.
|
|
589
688
|
*/
|
|
590
689
|
private async handleSubscriptionQuery(
|
|
591
|
-
from: connection,
|
|
690
|
+
from: ws.connection,
|
|
592
691
|
data: QuerySubscribeMessageData,
|
|
593
692
|
qrefParent?: {
|
|
594
693
|
etype: string;
|
|
595
694
|
query: string;
|
|
596
|
-
}
|
|
695
|
+
},
|
|
597
696
|
) {
|
|
598
|
-
let args: [MessageOptions, ...Selector[]];
|
|
599
|
-
|
|
600
|
-
try {
|
|
601
|
-
args = JSON.parse(data.query);
|
|
602
|
-
EntityClass = this.nymph.getEntityClass(args[0].class);
|
|
603
|
-
} catch (e: any) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
const etype = EntityClass.ETYPE;
|
|
697
|
+
let args: [MessageOptions, ...Selector[]] = JSON.parse(data.query);
|
|
698
|
+
|
|
607
699
|
const serialArgs = JSON.stringify(args);
|
|
608
|
-
|
|
609
|
-
|
|
700
|
+
let [clientOptions, ...selectors] = args;
|
|
701
|
+
clientOptions = classNamesToEntityConstructors(
|
|
702
|
+
this.nymph,
|
|
703
|
+
clientOptions,
|
|
704
|
+
true,
|
|
705
|
+
);
|
|
706
|
+
selectors = classNamesToEntityConstructors(
|
|
707
|
+
this.nymph,
|
|
708
|
+
referencesToEntities(selectors, this.nymph, false, true),
|
|
709
|
+
true,
|
|
710
|
+
);
|
|
711
|
+
let EntityClass = clientOptions.class;
|
|
712
|
+
const etype = EntityClass.ETYPE;
|
|
713
|
+
const options: Options & { return: 'entity' } = {
|
|
610
714
|
...clientOptions,
|
|
611
715
|
class: EntityClass,
|
|
612
|
-
return: '
|
|
716
|
+
return: 'entity',
|
|
613
717
|
source: 'client',
|
|
614
718
|
};
|
|
615
719
|
// Find qref queries.
|
|
616
|
-
const qrefQueries = this.findQRefQueries(
|
|
720
|
+
const qrefQueries = this.findQRefQueries(...selectors);
|
|
617
721
|
|
|
618
722
|
if (data.action === 'subscribe') {
|
|
619
723
|
// Client is subscribing to a query.
|
|
620
724
|
|
|
621
725
|
// First subscribe to qrefQueries, giving this one as a reference.
|
|
622
726
|
for (const qrefQuery of qrefQueries) {
|
|
727
|
+
let [options, ...selectors] = qrefQuery;
|
|
728
|
+
options = entityConstructorsToClassNames(options);
|
|
729
|
+
selectors = entityConstructorsToClassNames(
|
|
730
|
+
entitiesToReferences(selectors),
|
|
731
|
+
);
|
|
623
732
|
await this.handleSubscriptionQuery(
|
|
624
733
|
from,
|
|
625
734
|
{
|
|
626
735
|
action: 'subscribe',
|
|
627
|
-
query: JSON.stringify(
|
|
736
|
+
query: JSON.stringify([options, ...selectors]),
|
|
628
737
|
},
|
|
629
738
|
{
|
|
630
739
|
etype,
|
|
631
740
|
query: serialArgs,
|
|
632
|
-
}
|
|
741
|
+
},
|
|
633
742
|
);
|
|
634
743
|
}
|
|
635
744
|
|
|
@@ -640,18 +749,33 @@ export default class PubSub {
|
|
|
640
749
|
if (!(serialArgs in this.querySubs[etype])) {
|
|
641
750
|
this.querySubs[etype][serialArgs] = new Map();
|
|
642
751
|
}
|
|
643
|
-
|
|
752
|
+
|
|
753
|
+
let authToken = null;
|
|
754
|
+
let switchToken = null;
|
|
644
755
|
if (this.sessions.has(from)) {
|
|
645
|
-
|
|
756
|
+
const session = this.sessions.get(from);
|
|
757
|
+
authToken = session?.authToken;
|
|
758
|
+
switchToken = session?.switchToken;
|
|
646
759
|
}
|
|
647
760
|
const nymph = this.nymph.clone();
|
|
648
|
-
if (nymph.tilmeld != null &&
|
|
649
|
-
const user = await nymph.tilmeld.extractToken(
|
|
650
|
-
if (user) {
|
|
651
|
-
|
|
652
|
-
|
|
761
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
762
|
+
const user = await nymph.tilmeld.extractToken(authToken);
|
|
763
|
+
if (user && user.enabled) {
|
|
764
|
+
if (switchToken != null) {
|
|
765
|
+
const switchUser = await nymph.tilmeld.extractToken(switchToken);
|
|
766
|
+
if (switchUser) {
|
|
767
|
+
// Log in the switchUser for access controls.
|
|
768
|
+
await nymph.tilmeld.fillSession(switchUser);
|
|
769
|
+
}
|
|
770
|
+
} else {
|
|
771
|
+
// Log in the user for access controls.
|
|
772
|
+
await nymph.tilmeld.fillSession(user);
|
|
773
|
+
}
|
|
653
774
|
}
|
|
654
775
|
}
|
|
776
|
+
|
|
777
|
+
let entities: EntityInterface[] = [];
|
|
778
|
+
let sendEntities = false;
|
|
655
779
|
const existingSub = this.querySubs[etype][serialArgs].get(from);
|
|
656
780
|
if (existingSub) {
|
|
657
781
|
if (qrefParent) {
|
|
@@ -663,23 +787,49 @@ export default class PubSub {
|
|
|
663
787
|
if (data.count && !existingSub.count) {
|
|
664
788
|
existingSub.count = true;
|
|
665
789
|
}
|
|
790
|
+
sendEntities = existingSub.direct;
|
|
791
|
+
if (sendEntities) {
|
|
792
|
+
entities = existingSub.current.length
|
|
793
|
+
? await nymph.getEntities(
|
|
794
|
+
{
|
|
795
|
+
class: EntityClass,
|
|
796
|
+
source: 'client',
|
|
797
|
+
},
|
|
798
|
+
{ type: '|', guid: existingSub.current },
|
|
799
|
+
)
|
|
800
|
+
: [];
|
|
801
|
+
}
|
|
666
802
|
} else {
|
|
803
|
+
entities = await nymph.getEntities(options, ...selectors);
|
|
667
804
|
this.querySubs[etype][serialArgs].set(from, {
|
|
668
|
-
current:
|
|
805
|
+
current: entities.map((e) => e.guid as string),
|
|
669
806
|
query: data.query,
|
|
670
807
|
qrefParents: qrefParent ? [qrefParent] : [],
|
|
671
808
|
direct: !qrefParent,
|
|
672
809
|
count: !!data.count,
|
|
673
810
|
});
|
|
811
|
+
sendEntities = !qrefParent;
|
|
674
812
|
}
|
|
675
|
-
|
|
813
|
+
|
|
814
|
+
if (sendEntities) {
|
|
815
|
+
// Notify the client of the current value.
|
|
816
|
+
from.sendUTF(
|
|
817
|
+
JSON.stringify({
|
|
818
|
+
query: data.query,
|
|
819
|
+
set: true,
|
|
820
|
+
data: entities,
|
|
821
|
+
}),
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
676
826
|
// Clear the user that was temporarily logged in.
|
|
677
827
|
nymph.tilmeld.clearSession();
|
|
678
828
|
}
|
|
679
829
|
this.config.logger(
|
|
680
830
|
'log',
|
|
681
831
|
new Date().toISOString(),
|
|
682
|
-
`Client subscribed to a query! (${serialArgs}, ${from.remoteAddress})
|
|
832
|
+
`Client subscribed to a query! (${serialArgs}, ${from.remoteAddress})`,
|
|
683
833
|
);
|
|
684
834
|
|
|
685
835
|
if (this.config.broadcastCounts) {
|
|
@@ -692,7 +842,7 @@ export default class PubSub {
|
|
|
692
842
|
JSON.stringify({
|
|
693
843
|
query: curData.query,
|
|
694
844
|
count,
|
|
695
|
-
})
|
|
845
|
+
}),
|
|
696
846
|
);
|
|
697
847
|
}
|
|
698
848
|
}
|
|
@@ -704,16 +854,21 @@ export default class PubSub {
|
|
|
704
854
|
|
|
705
855
|
// First unsubscribe from qrefQueries.
|
|
706
856
|
for (const qrefQuery of qrefQueries) {
|
|
857
|
+
let [options, ...selectors] = qrefQuery;
|
|
858
|
+
options = entityConstructorsToClassNames(options);
|
|
859
|
+
selectors = entityConstructorsToClassNames(
|
|
860
|
+
entitiesToReferences(selectors),
|
|
861
|
+
);
|
|
707
862
|
await this.handleSubscriptionQuery(
|
|
708
863
|
from,
|
|
709
864
|
{
|
|
710
865
|
action: 'unsubscribe',
|
|
711
|
-
query: JSON.stringify(
|
|
866
|
+
query: JSON.stringify([options, ...selectors]),
|
|
712
867
|
},
|
|
713
868
|
{
|
|
714
869
|
etype,
|
|
715
870
|
query: serialArgs,
|
|
716
|
-
}
|
|
871
|
+
},
|
|
717
872
|
);
|
|
718
873
|
}
|
|
719
874
|
|
|
@@ -735,7 +890,7 @@ export default class PubSub {
|
|
|
735
890
|
!(
|
|
736
891
|
qrefParent.etype === parent.etype &&
|
|
737
892
|
qrefParent.query === parent.query
|
|
738
|
-
)
|
|
893
|
+
),
|
|
739
894
|
);
|
|
740
895
|
}
|
|
741
896
|
if (!qrefParent) {
|
|
@@ -748,7 +903,7 @@ export default class PubSub {
|
|
|
748
903
|
this.config.logger(
|
|
749
904
|
'log',
|
|
750
905
|
new Date().toISOString(),
|
|
751
|
-
`Client unsubscribed from a query! (${serialArgs}, ${from.remoteAddress})
|
|
906
|
+
`Client unsubscribed from a query! (${serialArgs}, ${from.remoteAddress})`,
|
|
752
907
|
);
|
|
753
908
|
|
|
754
909
|
const count = this.querySubs[etype][serialArgs].size;
|
|
@@ -771,7 +926,7 @@ export default class PubSub {
|
|
|
771
926
|
JSON.stringify({
|
|
772
927
|
query: curData.query,
|
|
773
928
|
count,
|
|
774
|
-
})
|
|
929
|
+
}),
|
|
775
930
|
);
|
|
776
931
|
}
|
|
777
932
|
}
|
|
@@ -783,8 +938,8 @@ export default class PubSub {
|
|
|
783
938
|
* Handle a subscribe or unsubscribe for a UID from a client.
|
|
784
939
|
*/
|
|
785
940
|
private async handleSubscriptionUid(
|
|
786
|
-
from: connection,
|
|
787
|
-
data: UidSubscribeMessageData
|
|
941
|
+
from: ws.connection,
|
|
942
|
+
data: UidSubscribeMessageData,
|
|
788
943
|
) {
|
|
789
944
|
if (data.action === 'subscribe') {
|
|
790
945
|
// Client is subscribing to a UID.
|
|
@@ -794,10 +949,49 @@ export default class PubSub {
|
|
|
794
949
|
this.uidSubs[data['uid']].set(from, {
|
|
795
950
|
count: !!data.count,
|
|
796
951
|
});
|
|
952
|
+
|
|
953
|
+
let authToken = null;
|
|
954
|
+
let switchToken = null;
|
|
955
|
+
if (this.sessions.has(from)) {
|
|
956
|
+
const session = this.sessions.get(from);
|
|
957
|
+
authToken = session?.authToken;
|
|
958
|
+
switchToken = session?.switchToken;
|
|
959
|
+
}
|
|
960
|
+
const nymph = this.nymph.clone();
|
|
961
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
962
|
+
const user = await nymph.tilmeld.extractToken(authToken);
|
|
963
|
+
if (user && user.enabled) {
|
|
964
|
+
if (switchToken != null) {
|
|
965
|
+
const switchUser = await nymph.tilmeld.extractToken(switchToken);
|
|
966
|
+
if (switchUser) {
|
|
967
|
+
// Log in the switchUser for access controls.
|
|
968
|
+
await nymph.tilmeld.fillSession(switchUser);
|
|
969
|
+
}
|
|
970
|
+
} else {
|
|
971
|
+
// Log in the user for access controls.
|
|
972
|
+
await nymph.tilmeld.fillSession(user);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Notify the client of the current value.
|
|
978
|
+
from.sendUTF(
|
|
979
|
+
JSON.stringify({
|
|
980
|
+
uid: data.uid,
|
|
981
|
+
set: true,
|
|
982
|
+
data: await this.nymph.getUID(data.uid),
|
|
983
|
+
}),
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
987
|
+
// Clear the user that was temporarily logged in.
|
|
988
|
+
nymph.tilmeld.clearSession();
|
|
989
|
+
}
|
|
990
|
+
|
|
797
991
|
this.config.logger(
|
|
798
992
|
'log',
|
|
799
993
|
new Date().toISOString(),
|
|
800
|
-
`Client subscribed to a UID! (${data.uid}, ${from.remoteAddress})
|
|
994
|
+
`Client subscribed to a UID! (${data.uid}, ${from.remoteAddress})`,
|
|
801
995
|
);
|
|
802
996
|
|
|
803
997
|
if (this.config.broadcastCounts) {
|
|
@@ -810,7 +1004,7 @@ export default class PubSub {
|
|
|
810
1004
|
JSON.stringify({
|
|
811
1005
|
uid: data.uid,
|
|
812
1006
|
count,
|
|
813
|
-
})
|
|
1007
|
+
}),
|
|
814
1008
|
);
|
|
815
1009
|
}
|
|
816
1010
|
}
|
|
@@ -829,7 +1023,7 @@ export default class PubSub {
|
|
|
829
1023
|
this.config.logger(
|
|
830
1024
|
'log',
|
|
831
1025
|
new Date().toISOString(),
|
|
832
|
-
`Client unsubscribed from a UID! (${data.uid}, ${from.remoteAddress})
|
|
1026
|
+
`Client unsubscribed from a UID! (${data.uid}, ${from.remoteAddress})`,
|
|
833
1027
|
);
|
|
834
1028
|
|
|
835
1029
|
const count = this.uidSubs[data.uid].size;
|
|
@@ -849,7 +1043,7 @@ export default class PubSub {
|
|
|
849
1043
|
JSON.stringify({
|
|
850
1044
|
uid: data.uid,
|
|
851
1045
|
count,
|
|
852
|
-
})
|
|
1046
|
+
}),
|
|
853
1047
|
);
|
|
854
1048
|
}
|
|
855
1049
|
}
|
|
@@ -860,11 +1054,7 @@ export default class PubSub {
|
|
|
860
1054
|
/**
|
|
861
1055
|
* Handle a publish from a client.
|
|
862
1056
|
*/
|
|
863
|
-
private async handlePublish(
|
|
864
|
-
from: connection,
|
|
865
|
-
msg: Message,
|
|
866
|
-
data: PublishMessageData
|
|
867
|
-
) {
|
|
1057
|
+
private async handlePublish(from: ws.connection, data: PublishMessageData) {
|
|
868
1058
|
if (
|
|
869
1059
|
'guid' in data &&
|
|
870
1060
|
typeof data.guid === 'string' &&
|
|
@@ -877,9 +1067,6 @@ export default class PubSub {
|
|
|
877
1067
|
// Publish is an entity.
|
|
878
1068
|
|
|
879
1069
|
await this.handlePublishEntity(from, data);
|
|
880
|
-
|
|
881
|
-
// Relay the publish to other servers.
|
|
882
|
-
this.relay(msg);
|
|
883
1070
|
}
|
|
884
1071
|
|
|
885
1072
|
if (
|
|
@@ -893,9 +1080,6 @@ export default class PubSub {
|
|
|
893
1080
|
// Publish is a UID.
|
|
894
1081
|
|
|
895
1082
|
await this.handlePublishUid(from, data);
|
|
896
|
-
|
|
897
|
-
// Relay the publish to other servers.
|
|
898
|
-
this.relay(msg);
|
|
899
1083
|
}
|
|
900
1084
|
}
|
|
901
1085
|
|
|
@@ -903,13 +1087,13 @@ export default class PubSub {
|
|
|
903
1087
|
* Handle an entity publish from a client.
|
|
904
1088
|
*/
|
|
905
1089
|
private async handlePublishEntity(
|
|
906
|
-
from: connection,
|
|
907
|
-
data: PublishEntityMessageData
|
|
1090
|
+
from: ws.connection,
|
|
1091
|
+
data: PublishEntityMessageData,
|
|
908
1092
|
) {
|
|
909
1093
|
this.config.logger(
|
|
910
1094
|
'log',
|
|
911
1095
|
new Date().toISOString(),
|
|
912
|
-
`Received an entity publish! (${data.guid}, ${data.event}, ${from.remoteAddress})
|
|
1096
|
+
`Received an entity publish! (${data.guid}, ${data.event}, ${from.remoteAddress})`,
|
|
913
1097
|
);
|
|
914
1098
|
|
|
915
1099
|
const etype = data.etype;
|
|
@@ -920,7 +1104,7 @@ export default class PubSub {
|
|
|
920
1104
|
|
|
921
1105
|
for (let curQuery in this.querySubs[etype]) {
|
|
922
1106
|
const curClients = this.querySubs[etype][curQuery];
|
|
923
|
-
const updatedClients = new Set<connection>();
|
|
1107
|
+
const updatedClients = new Set<ws.connection>();
|
|
924
1108
|
|
|
925
1109
|
if (data.event === 'delete' || data.event === 'update') {
|
|
926
1110
|
// Check if it is in any client's currents.
|
|
@@ -931,7 +1115,7 @@ export default class PubSub {
|
|
|
931
1115
|
continue;
|
|
932
1116
|
}
|
|
933
1117
|
if (curData.current.indexOf(data.guid) !== -1) {
|
|
934
|
-
|
|
1118
|
+
this.addOutgoingClientMessage(curClient, curData, data);
|
|
935
1119
|
updatedClients.add(curClient);
|
|
936
1120
|
}
|
|
937
1121
|
}
|
|
@@ -939,7 +1123,7 @@ export default class PubSub {
|
|
|
939
1123
|
this.config.logger(
|
|
940
1124
|
'error',
|
|
941
1125
|
new Date().toISOString(),
|
|
942
|
-
`Error checking for client updates! (${e?.message})
|
|
1126
|
+
`Error checking for client updates! (${e?.message})`,
|
|
943
1127
|
);
|
|
944
1128
|
}
|
|
945
1129
|
}
|
|
@@ -947,29 +1131,48 @@ export default class PubSub {
|
|
|
947
1131
|
if ((data.event === 'create' || data.event === 'update') && data.entity) {
|
|
948
1132
|
// Check if it matches the query.
|
|
949
1133
|
try {
|
|
950
|
-
|
|
1134
|
+
let [clientOptions, ...selectors] = JSON.parse(curQuery);
|
|
1135
|
+
clientOptions = classNamesToEntityConstructors(
|
|
1136
|
+
this.nymph,
|
|
1137
|
+
clientOptions,
|
|
1138
|
+
true,
|
|
1139
|
+
);
|
|
1140
|
+
selectors = classNamesToEntityConstructors(
|
|
1141
|
+
this.nymph,
|
|
1142
|
+
referencesToEntities(selectors, this.nymph, false, true),
|
|
1143
|
+
true,
|
|
1144
|
+
);
|
|
951
1145
|
const qrefQueries = this.findQRefQueries(clientOptions, ...selectors);
|
|
952
|
-
const EntityClass =
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
1146
|
+
const EntityClass = clientOptions.class;
|
|
1147
|
+
const entityJson = classNamesToEntityConstructors(
|
|
1148
|
+
this.nymph,
|
|
1149
|
+
referencesToEntities(data.entity, this.nymph, false, true),
|
|
1150
|
+
true,
|
|
1151
|
+
);
|
|
1152
|
+
const entityData = entityJson.data;
|
|
1153
|
+
entityData.cdate = entityJson.cdate;
|
|
1154
|
+
entityData.mdate = entityJson.mdate;
|
|
956
1155
|
const entitySData: SerializedEntityData = {};
|
|
957
|
-
if (
|
|
1156
|
+
if (
|
|
1157
|
+
!(entityJson.class.prototype instanceof Entity) ||
|
|
1158
|
+
!entityJson.class.restEnabled
|
|
1159
|
+
) {
|
|
958
1160
|
throw new Error(
|
|
959
|
-
`Received entity data class is not valid: ${data.entity.class}
|
|
1161
|
+
`Received entity data class is not valid: ${data.entity.class}`,
|
|
960
1162
|
);
|
|
961
1163
|
}
|
|
962
|
-
const DataEntityClass =
|
|
1164
|
+
const DataEntityClass = entityJson.class as EntityConstructor;
|
|
963
1165
|
|
|
964
1166
|
if (
|
|
965
1167
|
EntityClass.ETYPE === DataEntityClass.ETYPE &&
|
|
966
1168
|
(qrefQueries.length ||
|
|
967
1169
|
this.nymph.driver.checkData(
|
|
1170
|
+
EntityClass.ETYPE,
|
|
968
1171
|
entityData,
|
|
969
1172
|
entitySData,
|
|
970
1173
|
selectors,
|
|
971
1174
|
data.guid,
|
|
972
|
-
|
|
1175
|
+
entityJson.tags ?? [],
|
|
973
1176
|
))
|
|
974
1177
|
) {
|
|
975
1178
|
// It either matches the query, or there are qref queries.
|
|
@@ -989,15 +1192,16 @@ export default class PubSub {
|
|
|
989
1192
|
if (qrefQueries.length) {
|
|
990
1193
|
const translatedSelectors = this.translateQRefSelectors(
|
|
991
1194
|
curClient,
|
|
992
|
-
selectors
|
|
1195
|
+
selectors,
|
|
993
1196
|
);
|
|
994
1197
|
if (
|
|
995
1198
|
!this.nymph.driver.checkData(
|
|
1199
|
+
EntityClass.ETYPE,
|
|
996
1200
|
entityData,
|
|
997
1201
|
entitySData,
|
|
998
1202
|
translatedSelectors,
|
|
999
1203
|
data.guid,
|
|
1000
|
-
|
|
1204
|
+
entityJson.tags ?? [],
|
|
1001
1205
|
)
|
|
1002
1206
|
) {
|
|
1003
1207
|
// The query doesn't match when the qref queries are filled.
|
|
@@ -1005,138 +1209,205 @@ export default class PubSub {
|
|
|
1005
1209
|
}
|
|
1006
1210
|
}
|
|
1007
1211
|
|
|
1008
|
-
|
|
1212
|
+
this.addOutgoingClientMessage(curClient, curData, data);
|
|
1009
1213
|
}
|
|
1010
1214
|
}
|
|
1011
1215
|
} catch (e: any) {
|
|
1012
1216
|
this.config.logger(
|
|
1013
1217
|
'error',
|
|
1014
1218
|
new Date().toISOString(),
|
|
1015
|
-
`Error checking for client updates! (${e?.message})
|
|
1219
|
+
`Error checking for client updates! (${e?.message})`,
|
|
1016
1220
|
);
|
|
1017
1221
|
}
|
|
1018
1222
|
}
|
|
1019
1223
|
}
|
|
1020
1224
|
}
|
|
1021
1225
|
|
|
1022
|
-
private
|
|
1023
|
-
curClient: connection,
|
|
1226
|
+
private addOutgoingClientMessage(
|
|
1227
|
+
curClient: ws.connection,
|
|
1024
1228
|
curData: QuerySubscriptionData,
|
|
1025
|
-
data: PublishEntityMessageData
|
|
1229
|
+
data: PublishEntityMessageData,
|
|
1026
1230
|
) {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
const options: Options = {
|
|
1034
|
-
...clientOptions,
|
|
1035
|
-
class: nymph.getEntityClass(clientOptions.class),
|
|
1036
|
-
return: 'entity',
|
|
1037
|
-
source: 'client',
|
|
1038
|
-
skipAc: false,
|
|
1039
|
-
};
|
|
1040
|
-
const selectors = classNamesToEntityConstructors(nymph, clientSelectors);
|
|
1041
|
-
if (this.sessions.has(curClient)) {
|
|
1042
|
-
token = this.sessions.get(curClient);
|
|
1043
|
-
}
|
|
1044
|
-
if (nymph.tilmeld != null && token != null) {
|
|
1045
|
-
const user = await nymph.tilmeld.extractToken(token);
|
|
1046
|
-
if (user) {
|
|
1047
|
-
// Log in the user for access controls.
|
|
1048
|
-
nymph.tilmeld.fillSession(user);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
current = await nymph.getEntities(options, ...selectors);
|
|
1052
|
-
} catch (e: any) {
|
|
1053
|
-
this.config.logger(
|
|
1054
|
-
'error',
|
|
1055
|
-
new Date().toISOString(),
|
|
1056
|
-
`Error updating client! (${e?.message}, ${curClient.remoteAddress})`
|
|
1057
|
-
);
|
|
1058
|
-
return;
|
|
1231
|
+
const arr = this.outgoingClientMessages.get(curClient) || [];
|
|
1232
|
+
arr.push({ subscription: curData, publish: data });
|
|
1233
|
+
this.outgoingClientMessages.set(curClient, arr);
|
|
1234
|
+
|
|
1235
|
+
if (this.outgoingClientMessagesTimeout) {
|
|
1236
|
+
clearImmediate(this.outgoingClientMessagesTimeout);
|
|
1059
1237
|
}
|
|
1060
1238
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
);
|
|
1064
|
-
|
|
1065
|
-
const removed = difference(curData.current, currentGuids);
|
|
1066
|
-
const added = difference(currentGuids, curData.current);
|
|
1239
|
+
this.outgoingClientMessagesTimeout = setImmediate(async () => {
|
|
1240
|
+
await this.updateClients();
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1067
1243
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
this.config.logger(
|
|
1072
|
-
'log',
|
|
1073
|
-
new Date().toISOString(),
|
|
1074
|
-
`Notifying client of removal! (${curClient.remoteAddress})`
|
|
1075
|
-
);
|
|
1076
|
-
curClient.sendUTF(
|
|
1077
|
-
JSON.stringify({
|
|
1078
|
-
query: curData.query,
|
|
1079
|
-
removed: guid,
|
|
1080
|
-
})
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1244
|
+
private async updateClients() {
|
|
1245
|
+
const outgoingClientMessages = [...this.outgoingClientMessages.entries()];
|
|
1246
|
+
this.outgoingClientMessages.clear();
|
|
1083
1247
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
`Notifying client of new match! (${curClient.remoteAddress})`
|
|
1091
|
-
);
|
|
1092
|
-
if (typeof entity.updateDataProtection === 'function') {
|
|
1093
|
-
entity.updateDataProtection();
|
|
1094
|
-
}
|
|
1095
|
-
curClient.sendUTF(
|
|
1096
|
-
JSON.stringify({
|
|
1097
|
-
query: curData.query,
|
|
1098
|
-
added: guid,
|
|
1099
|
-
data: entity,
|
|
1100
|
-
})
|
|
1101
|
-
);
|
|
1248
|
+
for (let [curClient, entries] of outgoingClientMessages) {
|
|
1249
|
+
const queryMap: { [k: string]: typeof entries } = {};
|
|
1250
|
+
for (let entry of entries) {
|
|
1251
|
+
const arr = queryMap[entry.subscription.query] || [];
|
|
1252
|
+
arr.push(entry);
|
|
1253
|
+
queryMap[entry.subscription.query] = arr;
|
|
1102
1254
|
}
|
|
1103
1255
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1256
|
+
for (let [query, queryEntries] of Object.entries(queryMap)) {
|
|
1257
|
+
// Update currents list.
|
|
1258
|
+
let current: EntityInterface[];
|
|
1259
|
+
let authToken: string | undefined;
|
|
1260
|
+
let switchToken: string | undefined;
|
|
1261
|
+
const nymph = this.nymph.clone();
|
|
1262
|
+
try {
|
|
1263
|
+
const [clientOptions, ...clientSelectors] = JSON.parse(query);
|
|
1264
|
+
const options: Options = {
|
|
1265
|
+
...clientOptions,
|
|
1266
|
+
class: classNamesToEntityConstructors(
|
|
1267
|
+
nymph,
|
|
1268
|
+
clientOptions.class,
|
|
1269
|
+
true,
|
|
1270
|
+
),
|
|
1271
|
+
return: 'entity',
|
|
1272
|
+
source: 'client',
|
|
1273
|
+
skipAc: false,
|
|
1274
|
+
};
|
|
1275
|
+
const selectors = classNamesToEntityConstructors(
|
|
1276
|
+
nymph,
|
|
1277
|
+
referencesToEntities(clientSelectors, nymph, false, true),
|
|
1278
|
+
true,
|
|
1279
|
+
);
|
|
1280
|
+
if (this.sessions.has(curClient)) {
|
|
1281
|
+
const session = this.sessions.get(curClient);
|
|
1282
|
+
authToken = session?.authToken;
|
|
1283
|
+
switchToken = session?.switchToken;
|
|
1284
|
+
}
|
|
1285
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
1286
|
+
const user = await nymph.tilmeld.extractToken(authToken);
|
|
1287
|
+
if (user && user.enabled) {
|
|
1288
|
+
if (switchToken != null) {
|
|
1289
|
+
const switchUser =
|
|
1290
|
+
await nymph.tilmeld.extractToken(switchToken);
|
|
1291
|
+
if (switchUser) {
|
|
1292
|
+
// Log in the switchUser for access controls.
|
|
1293
|
+
await nymph.tilmeld.fillSession(switchUser);
|
|
1294
|
+
}
|
|
1295
|
+
} else {
|
|
1296
|
+
// Log in the user for access controls.
|
|
1297
|
+
await nymph.tilmeld.fillSession(user);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
current = await nymph.getEntities(options, ...selectors);
|
|
1302
|
+
} catch (e: any) {
|
|
1303
|
+
this.config.logger(
|
|
1304
|
+
'error',
|
|
1305
|
+
new Date().toISOString(),
|
|
1306
|
+
`Error updating client! (${e?.message}, ${curClient.remoteAddress})`,
|
|
1307
|
+
);
|
|
1308
|
+
return;
|
|
1114
1309
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
updated: data.guid,
|
|
1119
|
-
data: entity,
|
|
1120
|
-
})
|
|
1310
|
+
|
|
1311
|
+
const entityMap = Object.fromEntries(
|
|
1312
|
+
current.map((entity) => [entity.guid, entity]),
|
|
1121
1313
|
);
|
|
1122
|
-
|
|
1123
|
-
|
|
1314
|
+
// These are the current GUIDs for this set of subscriptions/publishes.
|
|
1315
|
+
const currentGuids = current.map((entity) => entity.guid ?? '');
|
|
1316
|
+
|
|
1317
|
+
const clientUpdates: {
|
|
1318
|
+
query: string;
|
|
1319
|
+
removed?: string;
|
|
1320
|
+
added?: string;
|
|
1321
|
+
updated?: string;
|
|
1322
|
+
data?: any;
|
|
1323
|
+
}[] = [];
|
|
1324
|
+
|
|
1325
|
+
for (let { subscription, publish } of queryEntries) {
|
|
1326
|
+
const removed = difference(subscription.current, currentGuids);
|
|
1327
|
+
const added = difference(currentGuids, subscription.current);
|
|
1328
|
+
|
|
1329
|
+
if (subscription.direct) {
|
|
1330
|
+
for (let guid of removed) {
|
|
1331
|
+
// Notify subscriber.
|
|
1332
|
+
this.config.logger(
|
|
1333
|
+
'log',
|
|
1334
|
+
new Date().toISOString(),
|
|
1335
|
+
`Notifying client of removal! (${curClient.remoteAddress})`,
|
|
1336
|
+
);
|
|
1337
|
+
clientUpdates.push({
|
|
1338
|
+
query: subscription.query,
|
|
1339
|
+
removed: guid,
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1124
1342
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1343
|
+
for (let guid of added) {
|
|
1344
|
+
const entity = entityMap[guid];
|
|
1345
|
+
// Notify client.
|
|
1346
|
+
this.config.logger(
|
|
1347
|
+
'log',
|
|
1348
|
+
new Date().toISOString(),
|
|
1349
|
+
`Notifying client of new match! (${curClient.remoteAddress})`,
|
|
1350
|
+
);
|
|
1351
|
+
if (typeof entity.updateDataProtection === 'function') {
|
|
1352
|
+
entity.updateDataProtection();
|
|
1353
|
+
}
|
|
1354
|
+
clientUpdates.push({
|
|
1355
|
+
query: subscription.query,
|
|
1356
|
+
added: guid,
|
|
1357
|
+
data: entity.toJSON(),
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1127
1360
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1361
|
+
if (publish.event === 'update' && publish.guid in entityMap) {
|
|
1362
|
+
const entity = entityMap[publish.guid];
|
|
1363
|
+
// Notify subscriber.
|
|
1364
|
+
this.config.logger(
|
|
1365
|
+
'log',
|
|
1366
|
+
new Date().toISOString(),
|
|
1367
|
+
`Notifying client of update! (${curClient.remoteAddress})`,
|
|
1368
|
+
);
|
|
1369
|
+
if (typeof entity.updateDataProtection === 'function') {
|
|
1370
|
+
entity.updateDataProtection();
|
|
1371
|
+
}
|
|
1372
|
+
clientUpdates.push({
|
|
1373
|
+
query: subscription.query,
|
|
1374
|
+
updated: publish.guid,
|
|
1375
|
+
data: entity.toJSON(),
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// Update subscription.
|
|
1381
|
+
subscription.current = currentGuids;
|
|
1132
1382
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1383
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
1384
|
+
// Clear the user that was temporarily logged in.
|
|
1385
|
+
nymph.tilmeld.clearSession();
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
if (
|
|
1389
|
+
(removed.length || added.length) &&
|
|
1390
|
+
subscription.qrefParents.length
|
|
1391
|
+
) {
|
|
1392
|
+
// All qref parents need to be rerun.
|
|
1393
|
+
for (const qrefParent of subscription.qrefParents) {
|
|
1394
|
+
const subData =
|
|
1395
|
+
this.querySubs[qrefParent.etype][qrefParent.query].get(
|
|
1396
|
+
curClient,
|
|
1397
|
+
);
|
|
1398
|
+
if (subData) {
|
|
1399
|
+
this.addOutgoingClientMessage(curClient, subData, publish);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
if (clientUpdates.length === 1) {
|
|
1406
|
+
curClient.sendUTF(JSON.stringify(clientUpdates[0]));
|
|
1407
|
+
} else if (clientUpdates.length > 1) {
|
|
1408
|
+
curClient.sendUTF(
|
|
1409
|
+
JSON.stringify({ query, multiple: true, messages: clientUpdates }),
|
|
1410
|
+
);
|
|
1140
1411
|
}
|
|
1141
1412
|
}
|
|
1142
1413
|
}
|
|
@@ -1146,19 +1417,19 @@ export default class PubSub {
|
|
|
1146
1417
|
* Handle a UID publish from a client.
|
|
1147
1418
|
*/
|
|
1148
1419
|
private async handlePublishUid(
|
|
1149
|
-
from: connection,
|
|
1150
|
-
data: PublishUidMessageData
|
|
1420
|
+
from: ws.connection,
|
|
1421
|
+
data: PublishUidMessageData,
|
|
1151
1422
|
) {
|
|
1152
1423
|
this.config.logger(
|
|
1153
1424
|
'log',
|
|
1154
1425
|
new Date().toISOString(),
|
|
1155
1426
|
`Received a UID publish! (${
|
|
1156
1427
|
'name' in data ? data.name : `${data.oldName} => ${data.newName}`
|
|
1157
|
-
}, ${data.event}, ${from.remoteAddress})
|
|
1428
|
+
}, ${data.event}, ${from.remoteAddress})`,
|
|
1158
1429
|
);
|
|
1159
1430
|
|
|
1160
1431
|
const names = [data.name, data.oldName].filter(
|
|
1161
|
-
(name) => name != null
|
|
1432
|
+
(name) => name != null,
|
|
1162
1433
|
) as string[];
|
|
1163
1434
|
let value = data.value;
|
|
1164
1435
|
if (data.event === 'renameUID' && data.newName) {
|
|
@@ -1173,7 +1444,7 @@ export default class PubSub {
|
|
|
1173
1444
|
this.config.logger(
|
|
1174
1445
|
'log',
|
|
1175
1446
|
new Date().toISOString(),
|
|
1176
|
-
`Notifying client of ${data.event}! (${name}, ${curClient.remoteAddress})
|
|
1447
|
+
`Notifying client of ${data.event}! (${name}, ${curClient.remoteAddress})`,
|
|
1177
1448
|
);
|
|
1178
1449
|
const payload: {
|
|
1179
1450
|
uid: string;
|
|
@@ -1199,14 +1470,14 @@ export default class PubSub {
|
|
|
1199
1470
|
this.config.logger(
|
|
1200
1471
|
'log',
|
|
1201
1472
|
new Date().toISOString(),
|
|
1202
|
-
`Notifying client of new value after rename! (${data.newName}, ${curClient.remoteAddress})
|
|
1473
|
+
`Notifying client of new value after rename! (${data.newName}, ${curClient.remoteAddress})`,
|
|
1203
1474
|
);
|
|
1204
1475
|
curClient.sendUTF(
|
|
1205
1476
|
JSON.stringify({
|
|
1206
1477
|
uid: data.newName,
|
|
1207
1478
|
event: 'setUID',
|
|
1208
1479
|
value,
|
|
1209
|
-
})
|
|
1480
|
+
}),
|
|
1210
1481
|
);
|
|
1211
1482
|
}
|
|
1212
1483
|
}
|
|
@@ -1215,24 +1486,24 @@ export default class PubSub {
|
|
|
1215
1486
|
/**
|
|
1216
1487
|
* Relay publish data to other servers.
|
|
1217
1488
|
*/
|
|
1218
|
-
private relay(message: Message) {
|
|
1489
|
+
private relay(message: ws.Message) {
|
|
1219
1490
|
if (message.type !== 'utf8') {
|
|
1220
1491
|
this.config.logger(
|
|
1221
1492
|
'error',
|
|
1222
1493
|
new Date().toISOString(),
|
|
1223
|
-
`Can't relay non UTF8 message
|
|
1494
|
+
`Can't relay non UTF8 message.`,
|
|
1224
1495
|
);
|
|
1225
1496
|
return;
|
|
1226
1497
|
}
|
|
1227
1498
|
|
|
1228
1499
|
for (let host of this.config.relays) {
|
|
1229
|
-
const client = new
|
|
1500
|
+
const client = new ws.client();
|
|
1230
1501
|
|
|
1231
1502
|
client.on('connectFailed', (error) => {
|
|
1232
1503
|
this.config.logger(
|
|
1233
1504
|
'error',
|
|
1234
1505
|
new Date().toISOString(),
|
|
1235
|
-
`Relay connection failed. (${error.toString()}, ${host})
|
|
1506
|
+
`Relay connection failed. (${error.toString()}, ${host})`,
|
|
1236
1507
|
);
|
|
1237
1508
|
});
|
|
1238
1509
|
|
|
@@ -1241,7 +1512,7 @@ export default class PubSub {
|
|
|
1241
1512
|
this.config.logger(
|
|
1242
1513
|
'error',
|
|
1243
1514
|
new Date().toISOString(),
|
|
1244
|
-
`Relay connect error. (${error.toString()}, ${host})
|
|
1515
|
+
`Relay connect error. (${error.toString()}, ${host})`,
|
|
1245
1516
|
);
|
|
1246
1517
|
});
|
|
1247
1518
|
|
|
@@ -1256,8 +1527,8 @@ export default class PubSub {
|
|
|
1256
1527
|
}
|
|
1257
1528
|
}
|
|
1258
1529
|
|
|
1259
|
-
private findQRefQueries(
|
|
1260
|
-
const qrefQueries: [
|
|
1530
|
+
private findQRefQueries(...selectors: Selector[]) {
|
|
1531
|
+
const qrefQueries: [MessageOptions, ...Selector[]][] = [];
|
|
1261
1532
|
|
|
1262
1533
|
for (const curSelector of selectors) {
|
|
1263
1534
|
for (const k in curSelector) {
|
|
@@ -1268,7 +1539,7 @@ export default class PubSub {
|
|
|
1268
1539
|
continue;
|
|
1269
1540
|
}
|
|
1270
1541
|
|
|
1271
|
-
if (value
|
|
1542
|
+
if (value == null) {
|
|
1272
1543
|
continue;
|
|
1273
1544
|
}
|
|
1274
1545
|
|
|
@@ -1277,13 +1548,13 @@ export default class PubSub {
|
|
|
1277
1548
|
Array.isArray(((value as Selector['qref']) ?? [])[0])
|
|
1278
1549
|
? value
|
|
1279
1550
|
: [value]
|
|
1280
|
-
) as [string, [
|
|
1281
|
-
for (let
|
|
1282
|
-
qrefQueries.push(
|
|
1551
|
+
) as [string, [MessageOptions, ...Selector[]]][];
|
|
1552
|
+
for (let [_name, qRefQuery] of tmpArr) {
|
|
1553
|
+
qrefQueries.push(qRefQuery);
|
|
1283
1554
|
}
|
|
1284
1555
|
} else if (key === 'selector' || key === '!selector') {
|
|
1285
1556
|
const tmpArr = (Array.isArray(value) ? value : [value]) as Selector[];
|
|
1286
|
-
qrefQueries.push(...this.findQRefQueries(
|
|
1557
|
+
qrefQueries.push(...this.findQRefQueries(...tmpArr));
|
|
1287
1558
|
}
|
|
1288
1559
|
}
|
|
1289
1560
|
}
|
|
@@ -1295,7 +1566,7 @@ export default class PubSub {
|
|
|
1295
1566
|
* This translates qref selectors into ref selectors using the "current" GUID
|
|
1296
1567
|
* list in the existing subscriptions.
|
|
1297
1568
|
*/
|
|
1298
|
-
private translateQRefSelectors(client: connection, selectors: Selector[]) {
|
|
1569
|
+
private translateQRefSelectors(client: ws.connection, selectors: Selector[]) {
|
|
1299
1570
|
const newSelectors: Selector[] = [];
|
|
1300
1571
|
|
|
1301
1572
|
for (const curSelector of selectors) {
|
|
@@ -1323,29 +1594,59 @@ export default class PubSub {
|
|
|
1323
1594
|
for (let i = 0; i < tmpArr.length; i++) {
|
|
1324
1595
|
const name = tmpArr[i][0];
|
|
1325
1596
|
const [qrefOptions, ...qrefSelectors] = tmpArr[i][1];
|
|
1326
|
-
const query = JSON.stringify(
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1597
|
+
const query = JSON.stringify([
|
|
1598
|
+
entityConstructorsToClassNames(qrefOptions),
|
|
1599
|
+
...entityConstructorsToClassNames(
|
|
1600
|
+
entitiesToReferences(qrefSelectors),
|
|
1601
|
+
),
|
|
1602
|
+
]);
|
|
1603
|
+
const QrefEntityClass = qrefOptions.class ?? Entity;
|
|
1331
1604
|
const data =
|
|
1332
1605
|
this.querySubs[QrefEntityClass.ETYPE][query].get(client);
|
|
1333
1606
|
if (data) {
|
|
1334
1607
|
const guids = data.current;
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1608
|
+
if (newKey === '!ref') {
|
|
1609
|
+
// Insert the qref results as a not ref clause.
|
|
1610
|
+
let newValue: [string, string | EntityInterface][];
|
|
1611
|
+
const oldValue = newSelector[newKey];
|
|
1612
|
+
if (
|
|
1613
|
+
oldValue == null ||
|
|
1614
|
+
(Array.isArray(oldValue) && !oldValue.length)
|
|
1615
|
+
) {
|
|
1616
|
+
newValue = [];
|
|
1617
|
+
} else if (
|
|
1618
|
+
Array.isArray(oldValue) &&
|
|
1619
|
+
!Array.isArray(oldValue[0])
|
|
1620
|
+
) {
|
|
1621
|
+
newValue = [oldValue] as [string, string | EntityInterface][];
|
|
1622
|
+
} else {
|
|
1623
|
+
newValue = oldValue as [string, string | EntityInterface][];
|
|
1624
|
+
}
|
|
1625
|
+
newValue.push(
|
|
1626
|
+
...(guids.map((guid) => [name, guid]) as [string, string][]),
|
|
1627
|
+
);
|
|
1628
|
+
newSelector[newKey] = newValue;
|
|
1341
1629
|
} else {
|
|
1342
|
-
|
|
1630
|
+
// Insert the qref results as an "or" selector clause with a ref
|
|
1631
|
+
// selector.
|
|
1632
|
+
let newValue: Selector[];
|
|
1633
|
+
const oldValue = newSelector['selector'];
|
|
1634
|
+
if (
|
|
1635
|
+
oldValue == null ||
|
|
1636
|
+
(Array.isArray(oldValue) && !oldValue.length)
|
|
1637
|
+
) {
|
|
1638
|
+
newValue = [];
|
|
1639
|
+
} else if (!Array.isArray(oldValue)) {
|
|
1640
|
+
newValue = [oldValue] as Selector[];
|
|
1641
|
+
} else {
|
|
1642
|
+
newValue = oldValue as Selector[];
|
|
1643
|
+
}
|
|
1644
|
+
newValue.push({
|
|
1645
|
+
type: '|',
|
|
1646
|
+
ref: guids.map((guid) => [name, guid]) as [string, string][],
|
|
1647
|
+
});
|
|
1648
|
+
newSelector['selector'] = newValue;
|
|
1343
1649
|
}
|
|
1344
|
-
newValue.push(
|
|
1345
|
-
...(guids.map((guid) => [name, guid]) as [string, string][])
|
|
1346
|
-
);
|
|
1347
|
-
// Insert the qref results as a ref clause.
|
|
1348
|
-
newSelector[newKey] = newValue;
|
|
1349
1650
|
} else {
|
|
1350
1651
|
// Can't translate, so put the original back in.
|
|
1351
1652
|
if (!newSelector[key]) {
|
|
@@ -1358,9 +1659,22 @@ export default class PubSub {
|
|
|
1358
1659
|
}
|
|
1359
1660
|
}
|
|
1360
1661
|
} else if (key === 'selector' || key === '!selector') {
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1662
|
+
// This one gets inserted for qref queries, so it might be in there
|
|
1663
|
+
// already.
|
|
1664
|
+
const tmpArr = (
|
|
1665
|
+
Array.isArray(value ?? []) ? value : [value]
|
|
1666
|
+
) as Selector[];
|
|
1667
|
+
|
|
1668
|
+
if (!newSelector[key]) {
|
|
1669
|
+
newSelector[key] = [];
|
|
1670
|
+
}
|
|
1671
|
+
// It also needs to be translated.
|
|
1672
|
+
(newSelector[key] as Selector[]).push(
|
|
1673
|
+
...this.translateQRefSelectors(client, tmpArr),
|
|
1674
|
+
);
|
|
1675
|
+
} else if (key === '!ref') {
|
|
1676
|
+
// This one gets inserted for !qref queries, so it might be in there
|
|
1677
|
+
// already.
|
|
1364
1678
|
const tmpArr = (
|
|
1365
1679
|
Array.isArray(((value as Selector['ref']) ?? [])[0])
|
|
1366
1680
|
? value
|
|
@@ -1371,9 +1685,10 @@ export default class PubSub {
|
|
|
1371
1685
|
newSelector[key] = [];
|
|
1372
1686
|
}
|
|
1373
1687
|
(newSelector[key] as [string, string | EntityInterface][]).push(
|
|
1374
|
-
...tmpArr
|
|
1688
|
+
...tmpArr,
|
|
1375
1689
|
);
|
|
1376
1690
|
} else {
|
|
1691
|
+
// This is a clause we don't need to translate.
|
|
1377
1692
|
// @ts-ignore: ts doesn't know what value is here.
|
|
1378
1693
|
newSelector[key] = value;
|
|
1379
1694
|
}
|