@nymphjs/pubsub 1.0.0-beta.11 → 1.0.0-beta.111
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 +454 -0
- package/README.md +8 -8
- package/dist/PubSub.d.ts +77 -12
- package/dist/PubSub.js +467 -103
- package/dist/PubSub.js.map +1 -1
- package/dist/PubSub.test.js +394 -65
- package/dist/PubSub.test.js.map +1 -1
- package/dist/PubSub.types.d.ts +6 -1
- package/dist/PubSub.types.js +1 -2
- package/dist/conf/d.d.ts +23 -0
- package/dist/conf/d.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 +2 -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 +11 -2
- package/package.json +23 -23
- package/src/PubSub.test.ts +467 -101
- package/src/PubSub.ts +548 -286
- package/src/PubSub.types.ts +2 -1
- package/src/conf/defaults.ts +2 -2
- package/src/conf/index.ts +2 -2
- package/src/createServer.ts +8 -8
- package/src/index.ts +4 -4
- package/tsconfig.json +5 -3
- package/typedoc.json +4 -0
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,7 +22,7 @@ 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.
|
|
@@ -53,207 +47,289 @@ 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 static transactionPublishes: {
|
|
65
|
+
nymph: Nymph;
|
|
66
|
+
payload: string;
|
|
67
|
+
config: Config;
|
|
68
|
+
}[] = [];
|
|
67
69
|
|
|
68
70
|
public static initPublisher(config: Partial<Config>, nymph: Nymph) {
|
|
69
71
|
const configWithDefaults: Config = { ...defaults, ...config };
|
|
72
|
+
const unsubscribers: (() => boolean)[] = [];
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
'beforeSaveEntity',
|
|
73
|
-
async (nymph: Nymph, entity: EntityInterface) => {
|
|
74
|
+
unsubscribers.push(
|
|
75
|
+
nymph.on('beforeSaveEntity', async (enymph, entity) => {
|
|
74
76
|
const guid = entity.guid;
|
|
75
|
-
const
|
|
77
|
+
const EntityClass = entity.constructor as EntityConstructor;
|
|
78
|
+
const etype = EntityClass.ETYPE;
|
|
76
79
|
|
|
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
|
-
);
|
|
80
|
+
if (!EntityClass.pubSubEnabled) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const off = enymph.on('afterSaveEntity', async (curNymph, result) => {
|
|
85
|
+
off();
|
|
86
|
+
off2();
|
|
87
|
+
if (!(await result)) {
|
|
88
|
+
return;
|
|
94
89
|
}
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
const payload = JSON.stringify({
|
|
91
|
+
action: 'publish',
|
|
92
|
+
event: guid == null ? 'create' : 'update',
|
|
93
|
+
guid: entity.guid,
|
|
94
|
+
entity: entity.toJSON(),
|
|
95
|
+
etype: etype,
|
|
96
|
+
});
|
|
97
|
+
this.transactionPublishes.push({
|
|
98
|
+
nymph: curNymph,
|
|
99
|
+
payload,
|
|
100
|
+
config: configWithDefaults,
|
|
101
|
+
});
|
|
102
|
+
await this.publishTransactionPublishes(curNymph);
|
|
103
|
+
});
|
|
104
|
+
const off2 = enymph.on('failedSaveEntity', async () => {
|
|
105
|
+
off();
|
|
106
|
+
off2();
|
|
107
|
+
});
|
|
108
|
+
}),
|
|
97
109
|
);
|
|
98
110
|
|
|
99
|
-
|
|
100
|
-
'beforeDeleteEntity',
|
|
101
|
-
async (nymph: Nymph, entity: EntityInterface) => {
|
|
111
|
+
unsubscribers.push(
|
|
112
|
+
nymph.on('beforeDeleteEntity', async (enymph, entity) => {
|
|
102
113
|
const guid = entity.guid;
|
|
103
|
-
const
|
|
114
|
+
const EntityClass = entity.constructor as EntityConstructor;
|
|
115
|
+
const etype = EntityClass.ETYPE;
|
|
104
116
|
|
|
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
|
-
);
|
|
117
|
+
if (!EntityClass.pubSubEnabled) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const off = enymph.on('afterDeleteEntity', async (curNymph, result) => {
|
|
122
|
+
off();
|
|
123
|
+
off2();
|
|
124
|
+
if (!(await result)) {
|
|
125
|
+
return;
|
|
121
126
|
}
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
const payload = JSON.stringify({
|
|
128
|
+
action: 'publish',
|
|
129
|
+
event: 'delete',
|
|
130
|
+
guid: guid,
|
|
131
|
+
etype: etype,
|
|
132
|
+
});
|
|
133
|
+
this.transactionPublishes.push({
|
|
134
|
+
nymph: curNymph,
|
|
135
|
+
payload,
|
|
136
|
+
config: configWithDefaults,
|
|
137
|
+
});
|
|
138
|
+
await this.publishTransactionPublishes(curNymph);
|
|
139
|
+
});
|
|
140
|
+
const off2 = enymph.on('failedDeleteEntity', async () => {
|
|
141
|
+
off();
|
|
142
|
+
off2();
|
|
143
|
+
});
|
|
144
|
+
}),
|
|
124
145
|
);
|
|
125
146
|
|
|
126
|
-
|
|
127
|
-
'beforeDeleteEntityByID',
|
|
128
|
-
async (nymph: Nymph, guid: string, className?: string) => {
|
|
147
|
+
unsubscribers.push(
|
|
148
|
+
nymph.on('beforeDeleteEntityByID', async (enymph, guid, className) => {
|
|
129
149
|
try {
|
|
130
|
-
const
|
|
150
|
+
const EntityClass = enymph.getEntityClass(className ?? 'Entity');
|
|
151
|
+
const etype = EntityClass.ETYPE;
|
|
131
152
|
|
|
132
|
-
|
|
153
|
+
if (!EntityClass.pubSubEnabled) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const off = enymph.on(
|
|
133
158
|
'afterDeleteEntityByID',
|
|
134
|
-
async (
|
|
159
|
+
async (curNymph, result) => {
|
|
135
160
|
off();
|
|
161
|
+
off2();
|
|
136
162
|
if (!(await result)) {
|
|
137
163
|
return;
|
|
138
164
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
165
|
+
const payload = JSON.stringify({
|
|
166
|
+
action: 'publish',
|
|
167
|
+
event: 'delete',
|
|
168
|
+
guid: guid,
|
|
169
|
+
etype: etype,
|
|
170
|
+
});
|
|
171
|
+
this.transactionPublishes.push({
|
|
172
|
+
nymph: curNymph,
|
|
173
|
+
payload,
|
|
174
|
+
config: configWithDefaults,
|
|
175
|
+
});
|
|
176
|
+
await this.publishTransactionPublishes(curNymph);
|
|
177
|
+
},
|
|
149
178
|
);
|
|
179
|
+
const off2 = enymph.on('failedDeleteEntityByID', async () => {
|
|
180
|
+
off();
|
|
181
|
+
off2();
|
|
182
|
+
});
|
|
150
183
|
} catch (e: any) {
|
|
151
184
|
return;
|
|
152
185
|
}
|
|
153
|
-
}
|
|
186
|
+
}),
|
|
154
187
|
);
|
|
155
188
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
'afterNewUID',
|
|
159
|
-
async (_nymph: Nymph, result: Promise<number | null>) => {
|
|
189
|
+
unsubscribers.push(
|
|
190
|
+
nymph.on('beforeNewUID', async (enymph, name) => {
|
|
191
|
+
const off = enymph.on('afterNewUID', async (curNymph, result) => {
|
|
160
192
|
off();
|
|
193
|
+
off2();
|
|
161
194
|
const value = await result;
|
|
162
195
|
if (value == null) {
|
|
163
196
|
return;
|
|
164
197
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
198
|
+
const payload = JSON.stringify({
|
|
199
|
+
action: 'publish',
|
|
200
|
+
event: 'newUID',
|
|
201
|
+
name: name,
|
|
202
|
+
value: value,
|
|
203
|
+
});
|
|
204
|
+
this.transactionPublishes.push({
|
|
205
|
+
nymph: curNymph,
|
|
206
|
+
payload,
|
|
207
|
+
config: configWithDefaults,
|
|
208
|
+
});
|
|
209
|
+
await this.publishTransactionPublishes(curNymph);
|
|
210
|
+
});
|
|
211
|
+
const off2 = enymph.on('failedNewUID', async () => {
|
|
212
|
+
off();
|
|
213
|
+
off2();
|
|
214
|
+
});
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
177
217
|
|
|
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
|
-
);
|
|
218
|
+
unsubscribers.push(
|
|
219
|
+
nymph.on('beforeSetUID', async (enymph, name, value) => {
|
|
220
|
+
const off = enymph.on('afterSetUID', async (curNymph, result) => {
|
|
221
|
+
off();
|
|
222
|
+
off2();
|
|
223
|
+
if (!(await result)) {
|
|
224
|
+
return;
|
|
197
225
|
}
|
|
198
|
-
|
|
199
|
-
|
|
226
|
+
const payload = JSON.stringify({
|
|
227
|
+
action: 'publish',
|
|
228
|
+
event: 'setUID',
|
|
229
|
+
name: name,
|
|
230
|
+
value: value,
|
|
231
|
+
});
|
|
232
|
+
this.transactionPublishes.push({
|
|
233
|
+
nymph: curNymph,
|
|
234
|
+
payload,
|
|
235
|
+
config: configWithDefaults,
|
|
236
|
+
});
|
|
237
|
+
await this.publishTransactionPublishes(curNymph);
|
|
238
|
+
});
|
|
239
|
+
const off2 = enymph.on('failedSetUID', async () => {
|
|
240
|
+
off();
|
|
241
|
+
off2();
|
|
242
|
+
});
|
|
243
|
+
}),
|
|
200
244
|
);
|
|
201
245
|
|
|
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
|
-
);
|
|
246
|
+
unsubscribers.push(
|
|
247
|
+
nymph.on('beforeRenameUID', async (enymph, oldName, newName) => {
|
|
248
|
+
const off = enymph.on('afterRenameUID', async (curNymph, result) => {
|
|
249
|
+
off();
|
|
250
|
+
off2();
|
|
251
|
+
if (!(await result)) {
|
|
252
|
+
return;
|
|
221
253
|
}
|
|
222
|
-
|
|
223
|
-
|
|
254
|
+
const payload = JSON.stringify({
|
|
255
|
+
action: 'publish',
|
|
256
|
+
event: 'renameUID',
|
|
257
|
+
oldName: oldName,
|
|
258
|
+
newName: newName,
|
|
259
|
+
});
|
|
260
|
+
this.transactionPublishes.push({
|
|
261
|
+
nymph: curNymph,
|
|
262
|
+
payload,
|
|
263
|
+
config: configWithDefaults,
|
|
264
|
+
});
|
|
265
|
+
await this.publishTransactionPublishes(curNymph);
|
|
266
|
+
});
|
|
267
|
+
const off2 = enymph.on('failedRenameUID', async () => {
|
|
268
|
+
off();
|
|
269
|
+
off2();
|
|
270
|
+
});
|
|
271
|
+
}),
|
|
224
272
|
);
|
|
225
273
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
'afterDeleteUID',
|
|
229
|
-
async (_nymph: Nymph, result: Promise<boolean>) => {
|
|
274
|
+
unsubscribers.push(
|
|
275
|
+
nymph.on('beforeDeleteUID', async (enymph, name) => {
|
|
276
|
+
const off = enymph.on('afterDeleteUID', async (curNymph, result) => {
|
|
230
277
|
off();
|
|
278
|
+
off2();
|
|
231
279
|
if (!(await result)) {
|
|
232
280
|
return;
|
|
233
281
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
282
|
+
const payload = JSON.stringify({
|
|
283
|
+
action: 'publish',
|
|
284
|
+
event: 'deleteUID',
|
|
285
|
+
name: name,
|
|
286
|
+
});
|
|
287
|
+
this.transactionPublishes.push({
|
|
288
|
+
nymph: curNymph,
|
|
289
|
+
payload,
|
|
290
|
+
config: configWithDefaults,
|
|
291
|
+
});
|
|
292
|
+
await this.publishTransactionPublishes(curNymph);
|
|
293
|
+
});
|
|
294
|
+
const off2 = enymph.on('failedDeleteUID', async () => {
|
|
295
|
+
off();
|
|
296
|
+
off2();
|
|
297
|
+
});
|
|
298
|
+
}),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
unsubscribers.push(
|
|
302
|
+
nymph.on('afterCommitTransaction', async (enymph, _name, result) => {
|
|
303
|
+
if (result) {
|
|
304
|
+
await this.publishTransactionPublishes(enymph);
|
|
242
305
|
}
|
|
243
|
-
)
|
|
244
|
-
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
unsubscribers.push(
|
|
310
|
+
nymph.on('afterRollbackTransaction', async (enymph) => {
|
|
311
|
+
this.removeTransactionPublishes(enymph);
|
|
312
|
+
}),
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return () => {
|
|
316
|
+
for (let unsubscribe of unsubscribers) {
|
|
317
|
+
unsubscribe();
|
|
318
|
+
}
|
|
319
|
+
this.transactionPublishes = [];
|
|
320
|
+
};
|
|
245
321
|
}
|
|
246
322
|
|
|
247
323
|
private static publish(message: string, config: Config) {
|
|
248
324
|
for (let host of config.entries ?? []) {
|
|
249
|
-
const client = new
|
|
325
|
+
const client = new ws.client();
|
|
250
326
|
|
|
251
327
|
client.on('connectFailed', (error) => {
|
|
252
328
|
if (config.logger) {
|
|
253
329
|
config.logger(
|
|
254
330
|
'error',
|
|
255
331
|
new Date().toISOString(),
|
|
256
|
-
`Publish connection failed. (${error.toString()}, ${host})
|
|
332
|
+
`Publish connection failed. (${error.toString()}, ${host})`,
|
|
257
333
|
);
|
|
258
334
|
}
|
|
259
335
|
});
|
|
@@ -264,7 +340,7 @@ export default class PubSub {
|
|
|
264
340
|
config.logger(
|
|
265
341
|
'error',
|
|
266
342
|
new Date().toISOString(),
|
|
267
|
-
`Publish connect error. (${error.toString()}, ${host})
|
|
343
|
+
`Publish connect error. (${error.toString()}, ${host})`,
|
|
268
344
|
);
|
|
269
345
|
}
|
|
270
346
|
});
|
|
@@ -280,16 +356,60 @@ export default class PubSub {
|
|
|
280
356
|
}
|
|
281
357
|
}
|
|
282
358
|
|
|
359
|
+
private static isOrIsDescendant(parent: Nymph, child: Nymph) {
|
|
360
|
+
let check: Nymph | null = child;
|
|
361
|
+
while (check) {
|
|
362
|
+
if (check === parent) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
check = check.parent;
|
|
366
|
+
}
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private static async publishTransactionPublishes(nymph: Nymph) {
|
|
371
|
+
if (await nymph.inTransaction()) {
|
|
372
|
+
// This instance is still in a transaction, so nothing gets published yet.
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this.transactionPublishes = (
|
|
377
|
+
await Promise.all(
|
|
378
|
+
this.transactionPublishes.map(async (publish) => {
|
|
379
|
+
// Check that this instance is a parent and the instance is not in a
|
|
380
|
+
// transaction.
|
|
381
|
+
if (
|
|
382
|
+
!this.isOrIsDescendant(nymph, publish.nymph) ||
|
|
383
|
+
(await publish.nymph.inTransaction())
|
|
384
|
+
) {
|
|
385
|
+
return publish;
|
|
386
|
+
}
|
|
387
|
+
this.publish(publish.payload, publish.config);
|
|
388
|
+
return null;
|
|
389
|
+
}),
|
|
390
|
+
)
|
|
391
|
+
).filter((value) => value != null) as {
|
|
392
|
+
nymph: Nymph;
|
|
393
|
+
payload: string;
|
|
394
|
+
config: Config;
|
|
395
|
+
}[];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private static removeTransactionPublishes(nymph: Nymph) {
|
|
399
|
+
this.transactionPublishes = this.transactionPublishes.filter((publish) => {
|
|
400
|
+
if (this.isOrIsDescendant(nymph, publish.nymph)) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
return true;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
283
407
|
/**
|
|
284
408
|
* Initialize Nymph PubSub.
|
|
285
409
|
*
|
|
286
410
|
* @param config The PubSub configuration.
|
|
287
411
|
*/
|
|
288
|
-
public constructor(
|
|
289
|
-
config: Partial<Config>,
|
|
290
|
-
nymph: Nymph,
|
|
291
|
-
server: WebSocketServer
|
|
292
|
-
) {
|
|
412
|
+
public constructor(config: Partial<Config>, nymph: Nymph, server: ws.server) {
|
|
293
413
|
this.nymph = nymph;
|
|
294
414
|
this.config = { ...defaults, ...config };
|
|
295
415
|
this.server = server;
|
|
@@ -301,14 +421,14 @@ export default class PubSub {
|
|
|
301
421
|
this.server.shutDown();
|
|
302
422
|
}
|
|
303
423
|
|
|
304
|
-
public handleRequest(request: request) {
|
|
424
|
+
public handleRequest(request: ws.request) {
|
|
305
425
|
if (!this.config.originIsAllowed(request.origin)) {
|
|
306
426
|
// Make sure we only accept requests from an allowed origin
|
|
307
427
|
request.reject();
|
|
308
428
|
this.config.logger(
|
|
309
429
|
'log',
|
|
310
430
|
new Date().toISOString(),
|
|
311
|
-
'Client from origin ' + request.origin + ' was kicked by the bouncer.'
|
|
431
|
+
'Client from origin ' + request.origin + ' was kicked by the bouncer.',
|
|
312
432
|
);
|
|
313
433
|
return;
|
|
314
434
|
}
|
|
@@ -317,7 +437,7 @@ export default class PubSub {
|
|
|
317
437
|
this.config.logger(
|
|
318
438
|
'log',
|
|
319
439
|
new Date().toISOString(),
|
|
320
|
-
`Client joined the party! (${connection.remoteAddress})
|
|
440
|
+
`Client joined the party! (${connection.remoteAddress}).`,
|
|
321
441
|
);
|
|
322
442
|
|
|
323
443
|
connection.on('error', (err) => {
|
|
@@ -336,7 +456,7 @@ export default class PubSub {
|
|
|
336
456
|
/**
|
|
337
457
|
* Handle a message from a client.
|
|
338
458
|
*/
|
|
339
|
-
public async onMessage(from: connection, msg: Message) {
|
|
459
|
+
public async onMessage(from: ws.connection, msg: ws.Message) {
|
|
340
460
|
if (msg.type !== 'utf8') {
|
|
341
461
|
throw new Error("This server doesn't accept binary messages.");
|
|
342
462
|
}
|
|
@@ -344,7 +464,7 @@ export default class PubSub {
|
|
|
344
464
|
if (
|
|
345
465
|
!data.action ||
|
|
346
466
|
['authenticate', 'subscribe', 'unsubscribe', 'publish'].indexOf(
|
|
347
|
-
data.action
|
|
467
|
+
data.action,
|
|
348
468
|
) === -1
|
|
349
469
|
) {
|
|
350
470
|
return;
|
|
@@ -366,11 +486,11 @@ export default class PubSub {
|
|
|
366
486
|
/**
|
|
367
487
|
* Clean up after users who leave.
|
|
368
488
|
*/
|
|
369
|
-
public onClose(conn: connection, description: string) {
|
|
489
|
+
public onClose(conn: ws.connection, description: string) {
|
|
370
490
|
this.config.logger(
|
|
371
491
|
'log',
|
|
372
492
|
new Date().toISOString(),
|
|
373
|
-
`Client skedaddled. (${description}, ${conn.remoteAddress})
|
|
493
|
+
`Client skedaddled. (${description}, ${conn.remoteAddress})`,
|
|
374
494
|
);
|
|
375
495
|
|
|
376
496
|
let mess = 0;
|
|
@@ -398,7 +518,7 @@ export default class PubSub {
|
|
|
398
518
|
JSON.stringify({
|
|
399
519
|
query: curData.query,
|
|
400
520
|
count,
|
|
401
|
-
})
|
|
521
|
+
}),
|
|
402
522
|
);
|
|
403
523
|
}
|
|
404
524
|
}
|
|
@@ -428,7 +548,7 @@ export default class PubSub {
|
|
|
428
548
|
JSON.stringify({
|
|
429
549
|
uid: curUID,
|
|
430
550
|
count,
|
|
431
|
-
})
|
|
551
|
+
}),
|
|
432
552
|
);
|
|
433
553
|
}
|
|
434
554
|
}
|
|
@@ -447,16 +567,16 @@ export default class PubSub {
|
|
|
447
567
|
this.config.logger(
|
|
448
568
|
'log',
|
|
449
569
|
new Date().toISOString(),
|
|
450
|
-
`Cleaned up client's mess. (${mess}, ${conn.remoteAddress})
|
|
570
|
+
`Cleaned up client's mess. (${mess}, ${conn.remoteAddress})`,
|
|
451
571
|
);
|
|
452
572
|
}
|
|
453
573
|
}
|
|
454
574
|
|
|
455
|
-
public onError(conn: connection, e: Error) {
|
|
575
|
+
public onError(conn: ws.connection, e: Error) {
|
|
456
576
|
this.config.logger(
|
|
457
577
|
'error',
|
|
458
578
|
new Date().toISOString(),
|
|
459
|
-
`An error occured. (${e.message}, ${conn.remoteAddress})
|
|
579
|
+
`An error occured. (${e.message}, ${conn.remoteAddress})`,
|
|
460
580
|
);
|
|
461
581
|
}
|
|
462
582
|
|
|
@@ -464,13 +584,14 @@ export default class PubSub {
|
|
|
464
584
|
* Handle an authentication from a client.
|
|
465
585
|
*/
|
|
466
586
|
private handleAuthentication(
|
|
467
|
-
from: connection,
|
|
468
|
-
data: AuthenticateMessageData
|
|
587
|
+
from: ws.connection,
|
|
588
|
+
data: AuthenticateMessageData,
|
|
469
589
|
) {
|
|
470
590
|
// Save the user's auth token in session storage.
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
591
|
+
const authToken = data.authToken;
|
|
592
|
+
const switchToken = data.switchToken;
|
|
593
|
+
if (authToken != null) {
|
|
594
|
+
this.sessions.set(from, { authToken, switchToken });
|
|
474
595
|
} else if (this.sessions.has(from)) {
|
|
475
596
|
this.sessions.delete(from);
|
|
476
597
|
}
|
|
@@ -480,21 +601,31 @@ export default class PubSub {
|
|
|
480
601
|
* Handle a subscribe or unsubscribe from a client.
|
|
481
602
|
*/
|
|
482
603
|
private async handleSubscription(
|
|
483
|
-
from: connection,
|
|
484
|
-
data: SubscribeMessageData
|
|
604
|
+
from: ws.connection,
|
|
605
|
+
data: SubscribeMessageData,
|
|
485
606
|
) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
607
|
+
try {
|
|
608
|
+
if ('query' in data && data.query != null) {
|
|
609
|
+
// Request is for a query.
|
|
610
|
+
|
|
611
|
+
await this.handleSubscriptionQuery(from, data);
|
|
612
|
+
} else if (
|
|
613
|
+
'uid' in data &&
|
|
614
|
+
data.uid != null &&
|
|
615
|
+
typeof data.uid == 'string'
|
|
616
|
+
) {
|
|
617
|
+
// Request is for a UID.
|
|
618
|
+
|
|
619
|
+
await this.handleSubscriptionUid(from, data);
|
|
620
|
+
}
|
|
621
|
+
} catch (e: any) {
|
|
622
|
+
if ('query' in data && data.query != null) {
|
|
623
|
+
from.sendUTF(JSON.stringify({ query: data.query, error: e.message }));
|
|
624
|
+
} else if ('uid' in data && data.uid != null) {
|
|
625
|
+
from.sendUTF(JSON.stringify({ uid: data.uid, error: e.message }));
|
|
626
|
+
} else {
|
|
627
|
+
from.sendUTF(JSON.stringify({ error: e.message }));
|
|
628
|
+
}
|
|
498
629
|
}
|
|
499
630
|
}
|
|
500
631
|
|
|
@@ -502,33 +633,39 @@ export default class PubSub {
|
|
|
502
633
|
* Handle a subscribe or unsubscribe for a query from a client.
|
|
503
634
|
*/
|
|
504
635
|
private async handleSubscriptionQuery(
|
|
505
|
-
from: connection,
|
|
636
|
+
from: ws.connection,
|
|
506
637
|
data: QuerySubscribeMessageData,
|
|
507
638
|
qrefParent?: {
|
|
508
639
|
etype: string;
|
|
509
640
|
query: string;
|
|
510
|
-
}
|
|
641
|
+
},
|
|
511
642
|
) {
|
|
512
|
-
let args: [MessageOptions, ...Selector[]];
|
|
513
|
-
let EntityClass
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
EntityClass = this.nymph.getEntityClass(args[0].class);
|
|
517
|
-
} catch (e: any) {
|
|
518
|
-
return;
|
|
643
|
+
let args: [MessageOptions, ...Selector[]] = JSON.parse(data.query);
|
|
644
|
+
let EntityClass = this.nymph.getEntityClass(args[0].class);
|
|
645
|
+
if (!EntityClass.restEnabled) {
|
|
646
|
+
throw new Error('Not accessible.');
|
|
519
647
|
}
|
|
520
648
|
const etype = EntityClass.ETYPE;
|
|
521
649
|
const serialArgs = JSON.stringify(args);
|
|
522
650
|
const [clientOptions, ...selectors] = args;
|
|
523
|
-
const options: Options & { return: '
|
|
651
|
+
const options: Options & { return: 'entity' } = {
|
|
524
652
|
...clientOptions,
|
|
525
653
|
class: EntityClass,
|
|
526
|
-
return: '
|
|
654
|
+
return: 'entity',
|
|
527
655
|
source: 'client',
|
|
528
656
|
};
|
|
529
657
|
// Find qref queries.
|
|
530
658
|
const qrefQueries = this.findQRefQueries(clientOptions, ...selectors);
|
|
531
659
|
|
|
660
|
+
// Check that all qref queries are accessible classes.
|
|
661
|
+
for (const qrefQuery of qrefQueries) {
|
|
662
|
+
const args = qrefQuery;
|
|
663
|
+
const EntityClass = this.nymph.getEntityClass(args[0].class);
|
|
664
|
+
if (!EntityClass.restEnabled) {
|
|
665
|
+
throw new Error('Not accessible.');
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
532
669
|
if (data.action === 'subscribe') {
|
|
533
670
|
// Client is subscribing to a query.
|
|
534
671
|
|
|
@@ -543,7 +680,7 @@ export default class PubSub {
|
|
|
543
680
|
{
|
|
544
681
|
etype,
|
|
545
682
|
query: serialArgs,
|
|
546
|
-
}
|
|
683
|
+
},
|
|
547
684
|
);
|
|
548
685
|
}
|
|
549
686
|
|
|
@@ -554,18 +691,33 @@ export default class PubSub {
|
|
|
554
691
|
if (!(serialArgs in this.querySubs[etype])) {
|
|
555
692
|
this.querySubs[etype][serialArgs] = new Map();
|
|
556
693
|
}
|
|
557
|
-
|
|
694
|
+
|
|
695
|
+
let authToken = null;
|
|
696
|
+
let switchToken = null;
|
|
558
697
|
if (this.sessions.has(from)) {
|
|
559
|
-
|
|
698
|
+
const session = this.sessions.get(from);
|
|
699
|
+
authToken = session?.authToken;
|
|
700
|
+
switchToken = session?.switchToken;
|
|
560
701
|
}
|
|
561
702
|
const nymph = this.nymph.clone();
|
|
562
|
-
if (nymph.tilmeld != null &&
|
|
563
|
-
const user = await nymph.tilmeld.extractToken(
|
|
564
|
-
if (user) {
|
|
565
|
-
|
|
566
|
-
|
|
703
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
704
|
+
const user = await nymph.tilmeld.extractToken(authToken);
|
|
705
|
+
if (user && user.enabled) {
|
|
706
|
+
if (switchToken != null) {
|
|
707
|
+
const switchUser = await nymph.tilmeld.extractToken(switchToken);
|
|
708
|
+
if (switchUser) {
|
|
709
|
+
// Log in the switchUser for access controls.
|
|
710
|
+
await nymph.tilmeld.fillSession(switchUser);
|
|
711
|
+
}
|
|
712
|
+
} else {
|
|
713
|
+
// Log in the user for access controls.
|
|
714
|
+
await nymph.tilmeld.fillSession(user);
|
|
715
|
+
}
|
|
567
716
|
}
|
|
568
717
|
}
|
|
718
|
+
|
|
719
|
+
let entities: EntityInterface[] = [];
|
|
720
|
+
let sendEntities = false;
|
|
569
721
|
const existingSub = this.querySubs[etype][serialArgs].get(from);
|
|
570
722
|
if (existingSub) {
|
|
571
723
|
if (qrefParent) {
|
|
@@ -577,23 +729,49 @@ export default class PubSub {
|
|
|
577
729
|
if (data.count && !existingSub.count) {
|
|
578
730
|
existingSub.count = true;
|
|
579
731
|
}
|
|
732
|
+
sendEntities = existingSub.direct;
|
|
733
|
+
if (sendEntities) {
|
|
734
|
+
entities = existingSub.current.length
|
|
735
|
+
? await nymph.getEntities(
|
|
736
|
+
{
|
|
737
|
+
class: EntityClass,
|
|
738
|
+
source: 'client',
|
|
739
|
+
},
|
|
740
|
+
{ type: '|', guid: existingSub.current },
|
|
741
|
+
)
|
|
742
|
+
: [];
|
|
743
|
+
}
|
|
580
744
|
} else {
|
|
745
|
+
entities = await nymph.getEntities(options, ...selectors);
|
|
581
746
|
this.querySubs[etype][serialArgs].set(from, {
|
|
582
|
-
current:
|
|
747
|
+
current: entities.map((e) => e.guid as string),
|
|
583
748
|
query: data.query,
|
|
584
749
|
qrefParents: qrefParent ? [qrefParent] : [],
|
|
585
750
|
direct: !qrefParent,
|
|
586
751
|
count: !!data.count,
|
|
587
752
|
});
|
|
753
|
+
sendEntities = !qrefParent;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (sendEntities) {
|
|
757
|
+
// Notify the client of the current value.
|
|
758
|
+
from.sendUTF(
|
|
759
|
+
JSON.stringify({
|
|
760
|
+
query: data.query,
|
|
761
|
+
set: true,
|
|
762
|
+
data: entities,
|
|
763
|
+
}),
|
|
764
|
+
);
|
|
588
765
|
}
|
|
589
|
-
|
|
766
|
+
|
|
767
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
590
768
|
// Clear the user that was temporarily logged in.
|
|
591
769
|
nymph.tilmeld.clearSession();
|
|
592
770
|
}
|
|
593
771
|
this.config.logger(
|
|
594
772
|
'log',
|
|
595
773
|
new Date().toISOString(),
|
|
596
|
-
`Client subscribed to a query! (${serialArgs}, ${from.remoteAddress})
|
|
774
|
+
`Client subscribed to a query! (${serialArgs}, ${from.remoteAddress})`,
|
|
597
775
|
);
|
|
598
776
|
|
|
599
777
|
if (this.config.broadcastCounts) {
|
|
@@ -606,7 +784,7 @@ export default class PubSub {
|
|
|
606
784
|
JSON.stringify({
|
|
607
785
|
query: curData.query,
|
|
608
786
|
count,
|
|
609
|
-
})
|
|
787
|
+
}),
|
|
610
788
|
);
|
|
611
789
|
}
|
|
612
790
|
}
|
|
@@ -627,7 +805,7 @@ export default class PubSub {
|
|
|
627
805
|
{
|
|
628
806
|
etype,
|
|
629
807
|
query: serialArgs,
|
|
630
|
-
}
|
|
808
|
+
},
|
|
631
809
|
);
|
|
632
810
|
}
|
|
633
811
|
|
|
@@ -649,7 +827,7 @@ export default class PubSub {
|
|
|
649
827
|
!(
|
|
650
828
|
qrefParent.etype === parent.etype &&
|
|
651
829
|
qrefParent.query === parent.query
|
|
652
|
-
)
|
|
830
|
+
),
|
|
653
831
|
);
|
|
654
832
|
}
|
|
655
833
|
if (!qrefParent) {
|
|
@@ -662,7 +840,7 @@ export default class PubSub {
|
|
|
662
840
|
this.config.logger(
|
|
663
841
|
'log',
|
|
664
842
|
new Date().toISOString(),
|
|
665
|
-
`Client unsubscribed from a query! (${serialArgs}, ${from.remoteAddress})
|
|
843
|
+
`Client unsubscribed from a query! (${serialArgs}, ${from.remoteAddress})`,
|
|
666
844
|
);
|
|
667
845
|
|
|
668
846
|
const count = this.querySubs[etype][serialArgs].size;
|
|
@@ -685,7 +863,7 @@ export default class PubSub {
|
|
|
685
863
|
JSON.stringify({
|
|
686
864
|
query: curData.query,
|
|
687
865
|
count,
|
|
688
|
-
})
|
|
866
|
+
}),
|
|
689
867
|
);
|
|
690
868
|
}
|
|
691
869
|
}
|
|
@@ -697,8 +875,8 @@ export default class PubSub {
|
|
|
697
875
|
* Handle a subscribe or unsubscribe for a UID from a client.
|
|
698
876
|
*/
|
|
699
877
|
private async handleSubscriptionUid(
|
|
700
|
-
from: connection,
|
|
701
|
-
data: UidSubscribeMessageData
|
|
878
|
+
from: ws.connection,
|
|
879
|
+
data: UidSubscribeMessageData,
|
|
702
880
|
) {
|
|
703
881
|
if (data.action === 'subscribe') {
|
|
704
882
|
// Client is subscribing to a UID.
|
|
@@ -708,10 +886,49 @@ export default class PubSub {
|
|
|
708
886
|
this.uidSubs[data['uid']].set(from, {
|
|
709
887
|
count: !!data.count,
|
|
710
888
|
});
|
|
889
|
+
|
|
890
|
+
let authToken = null;
|
|
891
|
+
let switchToken = null;
|
|
892
|
+
if (this.sessions.has(from)) {
|
|
893
|
+
const session = this.sessions.get(from);
|
|
894
|
+
authToken = session?.authToken;
|
|
895
|
+
switchToken = session?.switchToken;
|
|
896
|
+
}
|
|
897
|
+
const nymph = this.nymph.clone();
|
|
898
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
899
|
+
const user = await nymph.tilmeld.extractToken(authToken);
|
|
900
|
+
if (user && user.enabled) {
|
|
901
|
+
if (switchToken != null) {
|
|
902
|
+
const switchUser = await nymph.tilmeld.extractToken(switchToken);
|
|
903
|
+
if (switchUser) {
|
|
904
|
+
// Log in the switchUser for access controls.
|
|
905
|
+
await nymph.tilmeld.fillSession(switchUser);
|
|
906
|
+
}
|
|
907
|
+
} else {
|
|
908
|
+
// Log in the user for access controls.
|
|
909
|
+
await nymph.tilmeld.fillSession(user);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Notify the client of the current value.
|
|
915
|
+
from.sendUTF(
|
|
916
|
+
JSON.stringify({
|
|
917
|
+
uid: data.uid,
|
|
918
|
+
set: true,
|
|
919
|
+
data: await this.nymph.getUID(data.uid),
|
|
920
|
+
}),
|
|
921
|
+
);
|
|
922
|
+
|
|
923
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
924
|
+
// Clear the user that was temporarily logged in.
|
|
925
|
+
nymph.tilmeld.clearSession();
|
|
926
|
+
}
|
|
927
|
+
|
|
711
928
|
this.config.logger(
|
|
712
929
|
'log',
|
|
713
930
|
new Date().toISOString(),
|
|
714
|
-
`Client subscribed to a UID! (${data.uid}, ${from.remoteAddress})
|
|
931
|
+
`Client subscribed to a UID! (${data.uid}, ${from.remoteAddress})`,
|
|
715
932
|
);
|
|
716
933
|
|
|
717
934
|
if (this.config.broadcastCounts) {
|
|
@@ -724,7 +941,7 @@ export default class PubSub {
|
|
|
724
941
|
JSON.stringify({
|
|
725
942
|
uid: data.uid,
|
|
726
943
|
count,
|
|
727
|
-
})
|
|
944
|
+
}),
|
|
728
945
|
);
|
|
729
946
|
}
|
|
730
947
|
}
|
|
@@ -743,7 +960,7 @@ export default class PubSub {
|
|
|
743
960
|
this.config.logger(
|
|
744
961
|
'log',
|
|
745
962
|
new Date().toISOString(),
|
|
746
|
-
`Client unsubscribed from a UID! (${data.uid}, ${from.remoteAddress})
|
|
963
|
+
`Client unsubscribed from a UID! (${data.uid}, ${from.remoteAddress})`,
|
|
747
964
|
);
|
|
748
965
|
|
|
749
966
|
const count = this.uidSubs[data.uid].size;
|
|
@@ -763,7 +980,7 @@ export default class PubSub {
|
|
|
763
980
|
JSON.stringify({
|
|
764
981
|
uid: data.uid,
|
|
765
982
|
count,
|
|
766
|
-
})
|
|
983
|
+
}),
|
|
767
984
|
);
|
|
768
985
|
}
|
|
769
986
|
}
|
|
@@ -775,9 +992,9 @@ export default class PubSub {
|
|
|
775
992
|
* Handle a publish from a client.
|
|
776
993
|
*/
|
|
777
994
|
private async handlePublish(
|
|
778
|
-
from: connection,
|
|
779
|
-
msg: Message,
|
|
780
|
-
data: PublishMessageData
|
|
995
|
+
from: ws.connection,
|
|
996
|
+
msg: ws.Message,
|
|
997
|
+
data: PublishMessageData,
|
|
781
998
|
) {
|
|
782
999
|
if (
|
|
783
1000
|
'guid' in data &&
|
|
@@ -817,13 +1034,13 @@ export default class PubSub {
|
|
|
817
1034
|
* Handle an entity publish from a client.
|
|
818
1035
|
*/
|
|
819
1036
|
private async handlePublishEntity(
|
|
820
|
-
from: connection,
|
|
821
|
-
data: PublishEntityMessageData
|
|
1037
|
+
from: ws.connection,
|
|
1038
|
+
data: PublishEntityMessageData,
|
|
822
1039
|
) {
|
|
823
1040
|
this.config.logger(
|
|
824
1041
|
'log',
|
|
825
1042
|
new Date().toISOString(),
|
|
826
|
-
`Received an entity publish! (${data.guid}, ${data.event}, ${from.remoteAddress})
|
|
1043
|
+
`Received an entity publish! (${data.guid}, ${data.event}, ${from.remoteAddress})`,
|
|
827
1044
|
);
|
|
828
1045
|
|
|
829
1046
|
const etype = data.etype;
|
|
@@ -834,7 +1051,7 @@ export default class PubSub {
|
|
|
834
1051
|
|
|
835
1052
|
for (let curQuery in this.querySubs[etype]) {
|
|
836
1053
|
const curClients = this.querySubs[etype][curQuery];
|
|
837
|
-
const updatedClients = new Set<connection>();
|
|
1054
|
+
const updatedClients = new Set<ws.connection>();
|
|
838
1055
|
|
|
839
1056
|
if (data.event === 'delete' || data.event === 'update') {
|
|
840
1057
|
// Check if it is in any client's currents.
|
|
@@ -853,7 +1070,7 @@ export default class PubSub {
|
|
|
853
1070
|
this.config.logger(
|
|
854
1071
|
'error',
|
|
855
1072
|
new Date().toISOString(),
|
|
856
|
-
`Error checking for client updates! (${e?.message})
|
|
1073
|
+
`Error checking for client updates! (${e?.message})`,
|
|
857
1074
|
);
|
|
858
1075
|
}
|
|
859
1076
|
}
|
|
@@ -870,7 +1087,7 @@ export default class PubSub {
|
|
|
870
1087
|
const entitySData: SerializedEntityData = {};
|
|
871
1088
|
if (typeof data.entity.class !== 'string') {
|
|
872
1089
|
throw new Error(
|
|
873
|
-
`Received entity data class is not valid: ${data.entity.class}
|
|
1090
|
+
`Received entity data class is not valid: ${data.entity.class}`,
|
|
874
1091
|
);
|
|
875
1092
|
}
|
|
876
1093
|
const DataEntityClass = this.nymph.getEntityClass(data.entity.class);
|
|
@@ -879,11 +1096,12 @@ export default class PubSub {
|
|
|
879
1096
|
EntityClass.ETYPE === DataEntityClass.ETYPE &&
|
|
880
1097
|
(qrefQueries.length ||
|
|
881
1098
|
this.nymph.driver.checkData(
|
|
1099
|
+
EntityClass.ETYPE,
|
|
882
1100
|
entityData,
|
|
883
1101
|
entitySData,
|
|
884
1102
|
selectors,
|
|
885
1103
|
data.guid,
|
|
886
|
-
data.entity?.tags ?? []
|
|
1104
|
+
data.entity?.tags ?? [],
|
|
887
1105
|
))
|
|
888
1106
|
) {
|
|
889
1107
|
// It either matches the query, or there are qref queries.
|
|
@@ -903,15 +1121,16 @@ export default class PubSub {
|
|
|
903
1121
|
if (qrefQueries.length) {
|
|
904
1122
|
const translatedSelectors = this.translateQRefSelectors(
|
|
905
1123
|
curClient,
|
|
906
|
-
selectors
|
|
1124
|
+
selectors,
|
|
907
1125
|
);
|
|
908
1126
|
if (
|
|
909
1127
|
!this.nymph.driver.checkData(
|
|
1128
|
+
EntityClass.ETYPE,
|
|
910
1129
|
entityData,
|
|
911
1130
|
entitySData,
|
|
912
1131
|
translatedSelectors,
|
|
913
1132
|
data.guid,
|
|
914
|
-
data.entity?.tags ?? []
|
|
1133
|
+
data.entity?.tags ?? [],
|
|
915
1134
|
)
|
|
916
1135
|
) {
|
|
917
1136
|
// The query doesn't match when the qref queries are filled.
|
|
@@ -926,7 +1145,7 @@ export default class PubSub {
|
|
|
926
1145
|
this.config.logger(
|
|
927
1146
|
'error',
|
|
928
1147
|
new Date().toISOString(),
|
|
929
|
-
`Error checking for client updates! (${e?.message})
|
|
1148
|
+
`Error checking for client updates! (${e?.message})`,
|
|
930
1149
|
);
|
|
931
1150
|
}
|
|
932
1151
|
}
|
|
@@ -934,13 +1153,14 @@ export default class PubSub {
|
|
|
934
1153
|
}
|
|
935
1154
|
|
|
936
1155
|
private async updateClient(
|
|
937
|
-
curClient: connection,
|
|
1156
|
+
curClient: ws.connection,
|
|
938
1157
|
curData: QuerySubscriptionData,
|
|
939
|
-
data: PublishEntityMessageData
|
|
1158
|
+
data: PublishEntityMessageData,
|
|
940
1159
|
) {
|
|
941
1160
|
// Update currents list.
|
|
942
1161
|
let current: EntityInterface[];
|
|
943
|
-
let
|
|
1162
|
+
let authToken: string | undefined;
|
|
1163
|
+
let switchToken: string | undefined;
|
|
944
1164
|
const nymph = this.nymph.clone();
|
|
945
1165
|
try {
|
|
946
1166
|
const [clientOptions, ...clientSelectors] = JSON.parse(curData.query);
|
|
@@ -951,15 +1171,29 @@ export default class PubSub {
|
|
|
951
1171
|
source: 'client',
|
|
952
1172
|
skipAc: false,
|
|
953
1173
|
};
|
|
954
|
-
const selectors = classNamesToEntityConstructors(
|
|
1174
|
+
const selectors = classNamesToEntityConstructors(
|
|
1175
|
+
nymph,
|
|
1176
|
+
clientSelectors,
|
|
1177
|
+
true,
|
|
1178
|
+
);
|
|
955
1179
|
if (this.sessions.has(curClient)) {
|
|
956
|
-
|
|
1180
|
+
const session = this.sessions.get(curClient);
|
|
1181
|
+
authToken = session?.authToken;
|
|
1182
|
+
switchToken = session?.switchToken;
|
|
957
1183
|
}
|
|
958
|
-
if (nymph.tilmeld != null &&
|
|
959
|
-
const user = await nymph.tilmeld.extractToken(
|
|
960
|
-
if (user) {
|
|
961
|
-
|
|
962
|
-
|
|
1184
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
1185
|
+
const user = await nymph.tilmeld.extractToken(authToken);
|
|
1186
|
+
if (user && user.enabled) {
|
|
1187
|
+
if (switchToken != null) {
|
|
1188
|
+
const switchUser = await nymph.tilmeld.extractToken(switchToken);
|
|
1189
|
+
if (switchUser) {
|
|
1190
|
+
// Log in the switchUser for access controls.
|
|
1191
|
+
await nymph.tilmeld.fillSession(switchUser);
|
|
1192
|
+
}
|
|
1193
|
+
} else {
|
|
1194
|
+
// Log in the user for access controls.
|
|
1195
|
+
await nymph.tilmeld.fillSession(user);
|
|
1196
|
+
}
|
|
963
1197
|
}
|
|
964
1198
|
}
|
|
965
1199
|
current = await nymph.getEntities(options, ...selectors);
|
|
@@ -967,13 +1201,13 @@ export default class PubSub {
|
|
|
967
1201
|
this.config.logger(
|
|
968
1202
|
'error',
|
|
969
1203
|
new Date().toISOString(),
|
|
970
|
-
`Error updating client! (${e?.message}, ${curClient.remoteAddress})
|
|
1204
|
+
`Error updating client! (${e?.message}, ${curClient.remoteAddress})`,
|
|
971
1205
|
);
|
|
972
1206
|
return;
|
|
973
1207
|
}
|
|
974
1208
|
|
|
975
1209
|
const entityMap = Object.fromEntries(
|
|
976
|
-
current.map((entity) => [entity.guid, entity])
|
|
1210
|
+
current.map((entity) => [entity.guid, entity]),
|
|
977
1211
|
);
|
|
978
1212
|
const currentGuids = current.map((entity) => entity.guid ?? '');
|
|
979
1213
|
const removed = difference(curData.current, currentGuids);
|
|
@@ -985,13 +1219,13 @@ export default class PubSub {
|
|
|
985
1219
|
this.config.logger(
|
|
986
1220
|
'log',
|
|
987
1221
|
new Date().toISOString(),
|
|
988
|
-
`Notifying client of removal! (${curClient.remoteAddress})
|
|
1222
|
+
`Notifying client of removal! (${curClient.remoteAddress})`,
|
|
989
1223
|
);
|
|
990
1224
|
curClient.sendUTF(
|
|
991
1225
|
JSON.stringify({
|
|
992
1226
|
query: curData.query,
|
|
993
1227
|
removed: guid,
|
|
994
|
-
})
|
|
1228
|
+
}),
|
|
995
1229
|
);
|
|
996
1230
|
}
|
|
997
1231
|
|
|
@@ -1001,7 +1235,7 @@ export default class PubSub {
|
|
|
1001
1235
|
this.config.logger(
|
|
1002
1236
|
'log',
|
|
1003
1237
|
new Date().toISOString(),
|
|
1004
|
-
`Notifying client of new match! (${curClient.remoteAddress})
|
|
1238
|
+
`Notifying client of new match! (${curClient.remoteAddress})`,
|
|
1005
1239
|
);
|
|
1006
1240
|
if (typeof entity.updateDataProtection === 'function') {
|
|
1007
1241
|
entity.updateDataProtection();
|
|
@@ -1011,7 +1245,7 @@ export default class PubSub {
|
|
|
1011
1245
|
query: curData.query,
|
|
1012
1246
|
added: guid,
|
|
1013
1247
|
data: entity,
|
|
1014
|
-
})
|
|
1248
|
+
}),
|
|
1015
1249
|
);
|
|
1016
1250
|
}
|
|
1017
1251
|
|
|
@@ -1021,7 +1255,7 @@ export default class PubSub {
|
|
|
1021
1255
|
this.config.logger(
|
|
1022
1256
|
'log',
|
|
1023
1257
|
new Date().toISOString(),
|
|
1024
|
-
`Notifying client of update! (${curClient.remoteAddress})
|
|
1258
|
+
`Notifying client of update! (${curClient.remoteAddress})`,
|
|
1025
1259
|
);
|
|
1026
1260
|
if (typeof entity.updateDataProtection === 'function') {
|
|
1027
1261
|
entity.updateDataProtection();
|
|
@@ -1031,7 +1265,7 @@ export default class PubSub {
|
|
|
1031
1265
|
query: curData.query,
|
|
1032
1266
|
updated: data.guid,
|
|
1033
1267
|
data: entity,
|
|
1034
|
-
})
|
|
1268
|
+
}),
|
|
1035
1269
|
);
|
|
1036
1270
|
}
|
|
1037
1271
|
}
|
|
@@ -1039,7 +1273,7 @@ export default class PubSub {
|
|
|
1039
1273
|
// Update curData.
|
|
1040
1274
|
curData.current = currentGuids;
|
|
1041
1275
|
|
|
1042
|
-
if (nymph.tilmeld != null &&
|
|
1276
|
+
if (nymph.tilmeld != null && authToken != null) {
|
|
1043
1277
|
// Clear the user that was temporarily logged in.
|
|
1044
1278
|
nymph.tilmeld.clearSession();
|
|
1045
1279
|
}
|
|
@@ -1060,19 +1294,19 @@ export default class PubSub {
|
|
|
1060
1294
|
* Handle a UID publish from a client.
|
|
1061
1295
|
*/
|
|
1062
1296
|
private async handlePublishUid(
|
|
1063
|
-
from: connection,
|
|
1064
|
-
data: PublishUidMessageData
|
|
1297
|
+
from: ws.connection,
|
|
1298
|
+
data: PublishUidMessageData,
|
|
1065
1299
|
) {
|
|
1066
1300
|
this.config.logger(
|
|
1067
1301
|
'log',
|
|
1068
1302
|
new Date().toISOString(),
|
|
1069
1303
|
`Received a UID publish! (${
|
|
1070
1304
|
'name' in data ? data.name : `${data.oldName} => ${data.newName}`
|
|
1071
|
-
}, ${data.event}, ${from.remoteAddress})
|
|
1305
|
+
}, ${data.event}, ${from.remoteAddress})`,
|
|
1072
1306
|
);
|
|
1073
1307
|
|
|
1074
1308
|
const names = [data.name, data.oldName].filter(
|
|
1075
|
-
(name) => name != null
|
|
1309
|
+
(name) => name != null,
|
|
1076
1310
|
) as string[];
|
|
1077
1311
|
let value = data.value;
|
|
1078
1312
|
if (data.event === 'renameUID' && data.newName) {
|
|
@@ -1087,7 +1321,7 @@ export default class PubSub {
|
|
|
1087
1321
|
this.config.logger(
|
|
1088
1322
|
'log',
|
|
1089
1323
|
new Date().toISOString(),
|
|
1090
|
-
`Notifying client of ${data.event}! (${name}, ${curClient.remoteAddress})
|
|
1324
|
+
`Notifying client of ${data.event}! (${name}, ${curClient.remoteAddress})`,
|
|
1091
1325
|
);
|
|
1092
1326
|
const payload: {
|
|
1093
1327
|
uid: string;
|
|
@@ -1113,14 +1347,14 @@ export default class PubSub {
|
|
|
1113
1347
|
this.config.logger(
|
|
1114
1348
|
'log',
|
|
1115
1349
|
new Date().toISOString(),
|
|
1116
|
-
`Notifying client of new value after rename! (${data.newName}, ${curClient.remoteAddress})
|
|
1350
|
+
`Notifying client of new value after rename! (${data.newName}, ${curClient.remoteAddress})`,
|
|
1117
1351
|
);
|
|
1118
1352
|
curClient.sendUTF(
|
|
1119
1353
|
JSON.stringify({
|
|
1120
1354
|
uid: data.newName,
|
|
1121
1355
|
event: 'setUID',
|
|
1122
1356
|
value,
|
|
1123
|
-
})
|
|
1357
|
+
}),
|
|
1124
1358
|
);
|
|
1125
1359
|
}
|
|
1126
1360
|
}
|
|
@@ -1129,24 +1363,24 @@ export default class PubSub {
|
|
|
1129
1363
|
/**
|
|
1130
1364
|
* Relay publish data to other servers.
|
|
1131
1365
|
*/
|
|
1132
|
-
private relay(message: Message) {
|
|
1366
|
+
private relay(message: ws.Message) {
|
|
1133
1367
|
if (message.type !== 'utf8') {
|
|
1134
1368
|
this.config.logger(
|
|
1135
1369
|
'error',
|
|
1136
1370
|
new Date().toISOString(),
|
|
1137
|
-
`Can't relay non UTF8 message
|
|
1371
|
+
`Can't relay non UTF8 message.`,
|
|
1138
1372
|
);
|
|
1139
1373
|
return;
|
|
1140
1374
|
}
|
|
1141
1375
|
|
|
1142
1376
|
for (let host of this.config.relays) {
|
|
1143
|
-
const client = new
|
|
1377
|
+
const client = new ws.client();
|
|
1144
1378
|
|
|
1145
1379
|
client.on('connectFailed', (error) => {
|
|
1146
1380
|
this.config.logger(
|
|
1147
1381
|
'error',
|
|
1148
1382
|
new Date().toISOString(),
|
|
1149
|
-
`Relay connection failed. (${error.toString()}, ${host})
|
|
1383
|
+
`Relay connection failed. (${error.toString()}, ${host})`,
|
|
1150
1384
|
);
|
|
1151
1385
|
});
|
|
1152
1386
|
|
|
@@ -1155,7 +1389,7 @@ export default class PubSub {
|
|
|
1155
1389
|
this.config.logger(
|
|
1156
1390
|
'error',
|
|
1157
1391
|
new Date().toISOString(),
|
|
1158
|
-
`Relay connect error. (${error.toString()}, ${host})
|
|
1392
|
+
`Relay connect error. (${error.toString()}, ${host})`,
|
|
1159
1393
|
);
|
|
1160
1394
|
});
|
|
1161
1395
|
|
|
@@ -1170,8 +1404,8 @@ export default class PubSub {
|
|
|
1170
1404
|
}
|
|
1171
1405
|
}
|
|
1172
1406
|
|
|
1173
|
-
private findQRefQueries(options:
|
|
1174
|
-
const qrefQueries: [
|
|
1407
|
+
private findQRefQueries(options: MessageOptions, ...selectors: Selector[]) {
|
|
1408
|
+
const qrefQueries: [MessageOptions, ...Selector[]][] = [];
|
|
1175
1409
|
|
|
1176
1410
|
for (const curSelector of selectors) {
|
|
1177
1411
|
for (const k in curSelector) {
|
|
@@ -1191,7 +1425,7 @@ export default class PubSub {
|
|
|
1191
1425
|
Array.isArray(((value as Selector['qref']) ?? [])[0])
|
|
1192
1426
|
? value
|
|
1193
1427
|
: [value]
|
|
1194
|
-
) as [string, [
|
|
1428
|
+
) as [string, [MessageOptions, ...Selector[]]][];
|
|
1195
1429
|
for (let i = 0; i < tmpArr.length; i++) {
|
|
1196
1430
|
qrefQueries.push(tmpArr[i][1]);
|
|
1197
1431
|
}
|
|
@@ -1209,7 +1443,7 @@ export default class PubSub {
|
|
|
1209
1443
|
* This translates qref selectors into ref selectors using the "current" GUID
|
|
1210
1444
|
* list in the existing subscriptions.
|
|
1211
1445
|
*/
|
|
1212
|
-
private translateQRefSelectors(client: connection, selectors: Selector[]) {
|
|
1446
|
+
private translateQRefSelectors(client: ws.connection, selectors: Selector[]) {
|
|
1213
1447
|
const newSelectors: Selector[] = [];
|
|
1214
1448
|
|
|
1215
1449
|
for (const curSelector of selectors) {
|
|
@@ -1238,28 +1472,56 @@ export default class PubSub {
|
|
|
1238
1472
|
const name = tmpArr[i][0];
|
|
1239
1473
|
const [qrefOptions, ...qrefSelectors] = tmpArr[i][1];
|
|
1240
1474
|
const query = JSON.stringify(tmpArr[i][1]);
|
|
1241
|
-
const QrefEntityClass =
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
: qrefOptions.class ?? this.nymph.getEntityClass('Entity');
|
|
1475
|
+
const QrefEntityClass = qrefOptions.class
|
|
1476
|
+
? this.nymph.getEntityClass(qrefOptions.class)
|
|
1477
|
+
: this.nymph.getEntityClass('Entity');
|
|
1245
1478
|
const data =
|
|
1246
1479
|
this.querySubs[QrefEntityClass.ETYPE][query].get(client);
|
|
1247
1480
|
if (data) {
|
|
1248
1481
|
const guids = data.current;
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1482
|
+
if (newKey === '!ref') {
|
|
1483
|
+
// Insert the qref results as a not ref clause.
|
|
1484
|
+
let newValue: [string, string | EntityInterface][];
|
|
1485
|
+
const oldValue = newSelector[newKey];
|
|
1486
|
+
if (
|
|
1487
|
+
oldValue == null ||
|
|
1488
|
+
(Array.isArray(oldValue) && !oldValue.length)
|
|
1489
|
+
) {
|
|
1490
|
+
newValue = [];
|
|
1491
|
+
} else if (
|
|
1492
|
+
Array.isArray(oldValue) &&
|
|
1493
|
+
!Array.isArray(oldValue[0])
|
|
1494
|
+
) {
|
|
1495
|
+
newValue = [oldValue] as [string, string | EntityInterface][];
|
|
1496
|
+
} else {
|
|
1497
|
+
newValue = oldValue as [string, string | EntityInterface][];
|
|
1498
|
+
}
|
|
1499
|
+
newValue.push(
|
|
1500
|
+
...(guids.map((guid) => [name, guid]) as [string, string][]),
|
|
1501
|
+
);
|
|
1502
|
+
newSelector[newKey] = newValue;
|
|
1255
1503
|
} else {
|
|
1256
|
-
|
|
1504
|
+
// Insert the qref results as an "or" selector clause with a ref
|
|
1505
|
+
// selector.
|
|
1506
|
+
let newValue: Selector[];
|
|
1507
|
+
const oldValue = newSelector['selector'];
|
|
1508
|
+
delete newSelector[key];
|
|
1509
|
+
if (
|
|
1510
|
+
oldValue == null ||
|
|
1511
|
+
(Array.isArray(oldValue) && !oldValue.length)
|
|
1512
|
+
) {
|
|
1513
|
+
newValue = [];
|
|
1514
|
+
} else if (!Array.isArray(oldValue)) {
|
|
1515
|
+
newValue = [oldValue] as Selector[];
|
|
1516
|
+
} else {
|
|
1517
|
+
newValue = oldValue as Selector[];
|
|
1518
|
+
}
|
|
1519
|
+
newValue.push({
|
|
1520
|
+
type: '|',
|
|
1521
|
+
ref: guids.map((guid) => [name, guid]) as [string, string][],
|
|
1522
|
+
});
|
|
1523
|
+
newSelector['selector'] = newValue;
|
|
1257
1524
|
}
|
|
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
1525
|
} else {
|
|
1264
1526
|
// Can't translate, so put the original back in.
|
|
1265
1527
|
if (!newSelector[key]) {
|
|
@@ -1285,7 +1547,7 @@ export default class PubSub {
|
|
|
1285
1547
|
newSelector[key] = [];
|
|
1286
1548
|
}
|
|
1287
1549
|
(newSelector[key] as [string, string | EntityInterface][]).push(
|
|
1288
|
-
...tmpArr
|
|
1550
|
+
...tmpArr,
|
|
1289
1551
|
);
|
|
1290
1552
|
} else {
|
|
1291
1553
|
// @ts-ignore: ts doesn't know what value is here.
|