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