@nymphjs/client 1.0.0-beta.13 → 1.0.0-beta.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +546 -0
  2. package/README.md +12 -8
  3. package/asyncitertest.js +53 -0
  4. package/dist/Entity.d.ts +156 -0
  5. package/{lib → dist}/Entity.js +244 -118
  6. package/dist/Entity.js.map +1 -0
  7. package/dist/Entity.types.d.ts +216 -0
  8. package/dist/Entity.types.js +2 -0
  9. package/{lib → dist}/EntityWeakCache.d.ts +1 -1
  10. package/{lib → dist}/EntityWeakCache.js +5 -9
  11. package/dist/EntityWeakCache.js.map +1 -0
  12. package/{lib → dist}/HttpRequester.d.ts +18 -4
  13. package/dist/HttpRequester.js +365 -0
  14. package/dist/HttpRequester.js.map +1 -0
  15. package/{lib → dist}/Nymph.d.ts +48 -11
  16. package/{lib → dist}/Nymph.js +172 -56
  17. package/dist/Nymph.js.map +1 -0
  18. package/dist/Nymph.types.d.ts +227 -0
  19. package/dist/Nymph.types.js +45 -0
  20. package/dist/Nymph.types.js.map +1 -0
  21. package/{lib → dist}/PubSub.d.ts +16 -10
  22. package/{lib → dist}/PubSub.js +207 -135
  23. package/dist/PubSub.js.map +1 -0
  24. package/{lib → dist}/PubSub.types.d.ts +19 -6
  25. package/dist/PubSub.types.js +2 -0
  26. package/{lib → dist}/entityRefresh.d.ts +1 -1
  27. package/{lib → dist}/entityRefresh.js +21 -13
  28. package/dist/entityRefresh.js.map +1 -0
  29. package/dist/index.d.ts +13 -0
  30. package/dist/index.js +13 -2
  31. package/dist/index.js.map +1 -1
  32. package/{lib → dist}/utils.d.ts +2 -1
  33. package/dist/utils.js +145 -0
  34. package/dist/utils.js.map +1 -0
  35. package/jest.config.js +31 -5
  36. package/package.json +36 -32
  37. package/src/Entity.ts +229 -129
  38. package/src/Entity.types.ts +37 -57
  39. package/src/EntityWeakCache.ts +8 -6
  40. package/src/HttpRequester.ts +268 -31
  41. package/src/Nymph.ts +203 -96
  42. package/src/Nymph.types.ts +98 -2
  43. package/src/PubSub.ts +264 -181
  44. package/src/PubSub.types.ts +33 -19
  45. package/src/entityRefresh.ts +6 -6
  46. package/src/index.ts +10 -10
  47. package/src/utils.ts +67 -14
  48. package/tsconfig.json +7 -4
  49. package/typedoc.json +4 -0
  50. package/dist/index.js.LICENSE.txt +0 -8
  51. package/lib/Entity.d.ts +0 -51
  52. package/lib/Entity.js.map +0 -1
  53. package/lib/Entity.types.d.ts +0 -65
  54. package/lib/Entity.types.js +0 -3
  55. package/lib/EntityWeakCache.js.map +0 -1
  56. package/lib/HttpRequester.js +0 -190
  57. package/lib/HttpRequester.js.map +0 -1
  58. package/lib/Nymph.js.map +0 -1
  59. package/lib/Nymph.types.d.ts +0 -98
  60. package/lib/Nymph.types.js +0 -3
  61. package/lib/Nymph.types.js.map +0 -1
  62. package/lib/PubSub.js.map +0 -1
  63. package/lib/PubSub.types.js +0 -3
  64. package/lib/entityRefresh.js.map +0 -1
  65. package/lib/index.d.ts +0 -13
  66. package/lib/index.js +0 -34
  67. package/lib/index.js.map +0 -1
  68. package/lib/utils.js +0 -86
  69. package/lib/utils.js.map +0 -1
  70. package/webpack.config.js +0 -28
  71. /package/{lib → dist}/Entity.types.js.map +0 -0
  72. /package/{lib → dist}/PubSub.types.js.map +0 -0
package/src/PubSub.ts CHANGED
@@ -1,7 +1,12 @@
1
- import Nymph, { InvalidRequestError } from './Nymph';
2
- import { NymphOptions, Options, Selector } from './Nymph.types';
3
- import { EntityConstructor, EntityInterface } from './Entity.types';
4
- import {
1
+ import Nymph, { InvalidRequestError } from './Nymph.js';
2
+ import type { NymphOptions, Options, Selector } from './Nymph.types.js';
3
+ import type { EntityInstanceType } from './Entity.js';
4
+ import type {
5
+ EntityConstructor,
6
+ EntityInterface,
7
+ EntityJson,
8
+ } from './Entity.types.js';
9
+ import type {
5
10
  PubSubCallbacks,
6
11
  PubSubConnectCallback,
7
12
  PubSubCountCallback,
@@ -9,17 +14,22 @@ import {
9
14
  PubSubEventType,
10
15
  PubSubRejectCallback,
11
16
  PubSubResolveCallback,
17
+ PubSubErrorCallback,
12
18
  PubSubSubscribable,
13
19
  PubSubUpdate,
14
- } from './PubSub.types';
15
- import { entityConstructorsToClassNames } from './utils';
16
-
17
- let authToken: string | null = null;
20
+ } from './PubSub.types.js';
21
+ import {
22
+ entityConstructorsToClassNames,
23
+ entitiesToReferences,
24
+ } from './utils.js';
25
+ import { ClientError } from './HttpRequester.js';
18
26
 
19
27
  export default class PubSub {
20
28
  private nymph: Nymph;
29
+ private authToken: string | null = null;
30
+ private switchToken: string | null = null;
21
31
  private connection: WebSocket | undefined;
22
- private waitForConnectionTimeout: NodeJS.Timeout | undefined;
32
+ private waitForConnectionTimeout: number | undefined;
23
33
  private pubsubUrl: string | undefined;
24
34
  private WebSocket: typeof WebSocket;
25
35
  private subscriptions: {
@@ -35,6 +45,7 @@ export default class PubSub {
35
45
  };
36
46
  private connectCallbacks: PubSubConnectCallback[] = [];
37
47
  private disconnectCallbacks: PubSubDisconnectCallback[] = [];
48
+ private errorCallbacks: PubSubErrorCallback[] = [];
38
49
  private noConsole = false;
39
50
 
40
51
  public constructor(nymphOptions: NymphOptions, nymph: Nymph) {
@@ -59,6 +70,8 @@ export default class PubSub {
59
70
  }
60
71
  }
61
72
 
73
+ // TODO: return: 'guid' doesn't work here because the server always returns
74
+ // entities.
62
75
  public subscribeEntities<T extends EntityConstructor = EntityConstructor>(
63
76
  options: Options<T> & { return: 'guid' },
64
77
  ...selectors: Selector[]
@@ -66,34 +79,35 @@ export default class PubSub {
66
79
  public subscribeEntities<T extends EntityConstructor = EntityConstructor>(
67
80
  options: Options<T>,
68
81
  ...selectors: Selector[]
69
- ): PubSubSubscribable<PubSubUpdate<ReturnType<T['factorySync']>[]>>;
82
+ ): PubSubSubscribable<PubSubUpdate<EntityInstanceType<T>[]>>;
70
83
  public subscribeEntities<T extends EntityConstructor = EntityConstructor>(
71
84
  options: Options<T>,
72
85
  ...selectors: Selector[]
73
- ): PubSubSubscribable<
74
- PubSubUpdate<ReturnType<T['factorySync']>[]> | PubSubUpdate<string[]>
75
- > {
76
- const promise = this.nymph.getEntities(options, ...selectors);
86
+ ):
87
+ | PubSubSubscribable<PubSubUpdate<EntityInstanceType<T>[]>>
88
+ | PubSubSubscribable<PubSubUpdate<string[]>> {
77
89
  const query = [
78
90
  entityConstructorsToClassNames(options),
79
- ...entityConstructorsToClassNames(selectors),
91
+ ...entityConstructorsToClassNames(entitiesToReferences(selectors)),
80
92
  ];
81
93
  const jsonQuery = JSON.stringify(query);
82
94
  const subscribe = (
83
95
  resolve?:
84
96
  | PubSubResolveCallback<
85
- | PubSubUpdate<ReturnType<T['factorySync']>[]>
86
- | PubSubUpdate<string[]>
97
+ PubSubUpdate<EntityInstanceType<T>[]> | PubSubUpdate<string[]>
87
98
  >
88
99
  | undefined,
89
100
  reject?: PubSubRejectCallback | undefined,
90
- count?: PubSubCountCallback | undefined
101
+ count?: PubSubCountCallback | undefined,
91
102
  ) => {
92
103
  const callbacks: PubSubCallbacks<
93
- PubSubUpdate<ReturnType<T['factorySync']>[]> | PubSubUpdate<string[]>
104
+ PubSubUpdate<EntityInstanceType<T>[]> | PubSubUpdate<string[]>
94
105
  > = [resolve, reject, count];
95
106
 
96
- promise.then(resolve, reject);
107
+ if (!this.isConnection()) {
108
+ // Fall back to a regular query if we're not connected.
109
+ this.nymph.getEntities(options, ...selectors).then(resolve, reject);
110
+ }
97
111
 
98
112
  this._subscribeQuery(jsonQuery, callbacks);
99
113
  return new PubSubSubscription(jsonQuery, callbacks, () => {
@@ -103,6 +117,8 @@ export default class PubSub {
103
117
  return subscribe;
104
118
  }
105
119
 
120
+ // TODO: return: 'guid' doesn't work here because the server always returns
121
+ // entities.
106
122
  public subscribeEntity<T extends EntityConstructor = EntityConstructor>(
107
123
  options: Options<T> & { return: 'guid' },
108
124
  ...selectors: Selector[]
@@ -110,29 +126,27 @@ export default class PubSub {
110
126
  public subscribeEntity<T extends EntityConstructor = EntityConstructor>(
111
127
  options: Options<T>,
112
128
  ...selectors: Selector[]
113
- ): PubSubSubscribable<PubSubUpdate<ReturnType<T['factorySync']> | null>>;
129
+ ): PubSubSubscribable<PubSubUpdate<EntityInstanceType<T> | null>>;
114
130
  public subscribeEntity<T extends EntityConstructor = EntityConstructor>(
115
131
  options: Options<T>,
116
132
  ...selectors: Selector[]
117
- ): PubSubSubscribable<
118
- | PubSubUpdate<ReturnType<T['factorySync']> | null>
119
- | PubSubUpdate<string | null>
120
- > {
121
- const promise = this.nymph.getEntity(options, ...selectors);
133
+ ):
134
+ | PubSubSubscribable<PubSubUpdate<EntityInstanceType<T> | null>>
135
+ | PubSubSubscribable<PubSubUpdate<string | null>> {
122
136
  const query = [
123
137
  { ...entityConstructorsToClassNames(options), limit: 1 },
124
- ...entityConstructorsToClassNames(selectors),
138
+ ...entityConstructorsToClassNames(entitiesToReferences(selectors)),
125
139
  ];
126
140
  const jsonQuery = JSON.stringify(query);
127
141
  const subscribe = (
128
142
  resolve?:
129
143
  | PubSubResolveCallback<
130
- | PubSubUpdate<ReturnType<T['factorySync']> | null>
144
+ | PubSubUpdate<EntityInstanceType<T> | null>
131
145
  | PubSubUpdate<string | null>
132
146
  >
133
147
  | undefined,
134
148
  reject?: PubSubRejectCallback | undefined,
135
- count?: PubSubCountCallback | undefined
149
+ count?: PubSubCountCallback | undefined,
136
150
  ) => {
137
151
  const newResolve = (args: any) => {
138
152
  if (!args.length) {
@@ -146,11 +160,13 @@ export default class PubSub {
146
160
  }
147
161
  };
148
162
  const callbacks: PubSubCallbacks<
149
- | PubSubUpdate<ReturnType<T['factorySync']> | null>
150
- | PubSubUpdate<string | null>
163
+ PubSubUpdate<EntityInstanceType<T> | null> | PubSubUpdate<string | null>
151
164
  > = [newResolve, reject, count];
152
165
 
153
- promise.then(resolve, reject);
166
+ if (!this.isConnection()) {
167
+ // Fall back to a regular query if we're not connected.
168
+ this.nymph.getEntity(options, ...selectors).then(resolve, reject);
169
+ }
154
170
 
155
171
  this._subscribeQuery(jsonQuery, callbacks);
156
172
  return new PubSubSubscription(jsonQuery, callbacks, () => {
@@ -161,15 +177,17 @@ export default class PubSub {
161
177
  }
162
178
 
163
179
  public subscribeUID(name: string) {
164
- const promise = this.nymph.getUID(name);
165
180
  const subscribe = (
166
181
  resolve?: PubSubResolveCallback<number> | undefined,
167
182
  reject?: PubSubRejectCallback | undefined,
168
- count?: PubSubCountCallback | undefined
183
+ count?: PubSubCountCallback | undefined,
169
184
  ) => {
170
185
  const callbacks: PubSubCallbacks<number> = [resolve, reject, count];
171
186
 
172
- promise.then(resolve, reject);
187
+ if (!this.isConnection()) {
188
+ // Fall back to a regular query if we're not connected.
189
+ this.nymph.getUID(name).then(resolve, reject);
190
+ }
173
191
 
174
192
  this._subscribeUID(name, callbacks);
175
193
  return {
@@ -185,30 +203,36 @@ export default class PubSub {
185
203
  entity: T,
186
204
  resolve?: PubSubResolveCallback<T> | undefined,
187
205
  reject?: PubSubRejectCallback | undefined,
188
- count?: PubSubCountCallback | undefined
206
+ count?: PubSubCountCallback | undefined,
189
207
  ) {
190
208
  if (!entity.guid) {
191
209
  throw new InvalidRequestError(
192
- "You can't subscribe to an entity with no GUID."
210
+ "You can't subscribe to an entity with no GUID.",
193
211
  );
194
212
  }
195
213
  const query = [
196
- { class: (entity.constructor as EntityConstructor).class, limit: 1 },
214
+ { class: entityConstructorsToClassNames(entity.constructor), limit: 1 },
197
215
  { type: '&', guid: entity.guid },
198
216
  ];
199
217
  const jsonQuery = JSON.stringify(query);
200
218
 
201
219
  const newResolve = (args: any) => {
202
220
  if (Array.isArray(args)) {
203
- if (args[0].length) {
204
- entity.$init(args[0]);
221
+ if (args.length) {
222
+ entity.$init(args[0].toJSON());
205
223
  } else {
206
224
  entity.guid = null;
207
225
  }
208
- } else if ('removed' in args) {
209
- entity.guid = null;
210
226
  } else {
211
- entity.$init(args.data);
227
+ let update = args;
228
+ if ('multiple' in args && args.multiple) {
229
+ update = args.messages.pop();
230
+ }
231
+ if ('removed' in update) {
232
+ entity.guid = null;
233
+ } else {
234
+ entity.$init(update.data);
235
+ }
212
236
  }
213
237
 
214
238
  if (resolve) {
@@ -245,31 +269,31 @@ export default class PubSub {
245
269
  return;
246
270
  }
247
271
 
248
- this.connection.close(4200, 'Closure requested by application.');
272
+ this.connection.close(1000, 'Closure requested by application.');
249
273
  }
250
274
 
251
- private _waitForConnection(attempts = 0) {
252
- // Wait 5 seconds, then check and attempt connection again if
253
- // unsuccessful. Keep repeating until successful.
254
- this.waitForConnectionTimeout = setTimeout(() => {
255
- if (
256
- this.connection &&
257
- this.connection.readyState !== this.WebSocket.OPEN
258
- ) {
259
- if (
260
- this.connection.readyState !== this.WebSocket.CONNECTING ||
261
- attempts >= 5
262
- ) {
263
- this.connection.close();
264
- this._waitForConnection();
265
- this._attemptConnect();
275
+ private _waitForConnection(attempts = 1) {
276
+ // Wait 5 seconds, then check and attempt connection again if unsuccessful.
277
+ // Keep repeating, adding attempts^2*5 seconds each time to a max of ten
278
+ // minutes, until successful.
279
+ this.waitForConnectionTimeout = setTimeout(
280
+ () => {
281
+ if (this.connection) {
282
+ if (this.connection.readyState !== this.WebSocket.OPEN) {
283
+ if (this.connection.readyState !== this.WebSocket.CONNECTING) {
284
+ this.connection.close();
285
+ this._waitForConnection(attempts + 1);
286
+ this._attemptConnect();
287
+ } else {
288
+ this._waitForConnection(attempts + 1);
289
+ }
290
+ }
266
291
  } else {
267
- this._waitForConnection(attempts + 1);
292
+ this._attemptConnect();
268
293
  }
269
- } else {
270
- this._attemptConnect();
271
- }
272
- }, 5000);
294
+ },
295
+ Math.max(Math.pow(attempts, 2) * 5000, 1000 * 60 * 10),
296
+ );
273
297
  }
274
298
 
275
299
  private _attemptConnect() {
@@ -285,6 +309,11 @@ export default class PubSub {
285
309
  if (typeof console !== 'undefined' && !this.noConsole) {
286
310
  console.log('Nymph-PubSub connection established!');
287
311
  }
312
+
313
+ if (this.waitForConnectionTimeout) {
314
+ clearTimeout(this.waitForConnectionTimeout);
315
+ }
316
+
288
317
  for (let i = 0; i < this.connectCallbacks.length; i++) {
289
318
  const callback = this.connectCallbacks[i];
290
319
  if (callback) {
@@ -292,10 +321,11 @@ export default class PubSub {
292
321
  }
293
322
  }
294
323
 
295
- if (authToken != null) {
324
+ if (this.authToken != null) {
296
325
  this._send({
297
326
  action: 'authenticate',
298
- token: authToken,
327
+ authToken: this.authToken,
328
+ switchToken: this.switchToken,
299
329
  });
300
330
  }
301
331
 
@@ -343,17 +373,23 @@ export default class PubSub {
343
373
  private _onmessage(e: WebSocketEventMap['message']) {
344
374
  let data = JSON.parse(e.data);
345
375
  let subs: PubSubCallbacks<any>[] = [];
376
+ let set = 'set' in data && data.set;
346
377
  let count = 'count' in data;
378
+ let error = 'error' in data;
347
379
  if (
348
380
  data.hasOwnProperty('query') &&
349
381
  this.subscriptions.queries.hasOwnProperty(data.query)
350
382
  ) {
351
383
  subs = [...this.subscriptions.queries[data.query]];
352
- if (!count) {
384
+ if (!count && !error) {
353
385
  for (let i = 0; i < subs.length; i++) {
354
386
  const callback = subs[i][0];
355
387
  if (typeof callback === 'function') {
356
- callback(data);
388
+ callback(
389
+ set
390
+ ? data.data.map((e: EntityJson) => this.nymph.initEntity(e))
391
+ : data,
392
+ );
357
393
  }
358
394
  }
359
395
  }
@@ -362,14 +398,36 @@ export default class PubSub {
362
398
  this.subscriptions.uids.hasOwnProperty(data.uid)
363
399
  ) {
364
400
  subs = [...this.subscriptions.uids[data.uid]];
365
- if (!count) {
401
+ if (!count && !error) {
366
402
  for (let i = 0; i < subs.length; i++) {
367
403
  const callback = subs[i][0];
368
- if (typeof callback === 'function') {
369
- callback(data.value ?? null, data.event);
404
+ const errCallback = subs[i][1];
405
+ if (set && data.data == null) {
406
+ if (typeof errCallback === 'function') {
407
+ errCallback(
408
+ new ClientError(
409
+ { status: 404, statusText: 'Not Found' } as Response,
410
+ { textStatus: 'Not Found' },
411
+ ),
412
+ );
413
+ }
414
+ } else if (typeof callback === 'function') {
415
+ if (set) {
416
+ callback(data.data);
417
+ } else {
418
+ callback(data.value ?? null, data.event);
419
+ }
370
420
  }
371
421
  }
372
422
  }
423
+ } else if (error) {
424
+ for (let i = 0; i < this.errorCallbacks.length; i++) {
425
+ const callback = this.errorCallbacks[i];
426
+ if (callback) {
427
+ callback(data.error);
428
+ }
429
+ }
430
+ return;
373
431
  }
374
432
  if (count) {
375
433
  for (let i = 0; i < subs.length; i++) {
@@ -379,6 +437,14 @@ export default class PubSub {
379
437
  }
380
438
  }
381
439
  }
440
+ if (error) {
441
+ for (let i = 0; i < subs.length; i++) {
442
+ const callback = subs[i][1];
443
+ if (typeof callback === 'function') {
444
+ callback(data.error);
445
+ }
446
+ }
447
+ }
382
448
  }
383
449
 
384
450
  private _onclose(e: WebSocketEventMap['close']) {
@@ -392,7 +458,7 @@ export default class PubSub {
392
458
  }
393
459
  }
394
460
  if (
395
- e.code !== 4200 &&
461
+ e.code !== 1000 &&
396
462
  (typeof navigator === 'undefined' || navigator.onLine)
397
463
  ) {
398
464
  if (this.connection) {
@@ -415,6 +481,17 @@ export default class PubSub {
415
481
  );
416
482
  }
417
483
 
484
+ public isConnectionConnecting() {
485
+ return !!(
486
+ this.connection &&
487
+ this.connection.readyState === this.WebSocket.CONNECTING
488
+ );
489
+ }
490
+
491
+ public isConnection() {
492
+ return this.isConnectionOpen() || this.isConnectionConnecting();
493
+ }
494
+
418
495
  private _subscribeQuery(query: string, callbacks: PubSubCallbacks<any>) {
419
496
  let isNewSubscription = false;
420
497
  if (!this.subscriptions.queries.hasOwnProperty(query)) {
@@ -552,126 +629,117 @@ export default class PubSub {
552
629
  }
553
630
 
554
631
  public updateArray(
555
- oldArr: EntityInterface[],
556
- update: PubSubUpdate<EntityInterface[]>
632
+ current: EntityInterface[],
633
+ update: PubSubUpdate<EntityInterface[]>,
557
634
  ) {
558
635
  if (Array.isArray(update)) {
559
636
  const newArr = [...update];
560
637
 
561
- if (oldArr.length === 0) {
638
+ if (current.length === 0) {
562
639
  // This will happen on the first update from a subscribe.
563
- oldArr.splice(0, 0, ...newArr);
640
+ current.splice(0, 0, ...newArr);
564
641
  return;
565
642
  }
566
643
 
567
- const idMap: { [k: string]: number } = {};
568
- const Entity = this.nymph.getEntityClass('Entity');
644
+ const idMap: { [k: string]: EntityInterface } = {};
645
+ const newEntities: EntityInterface[] = [];
646
+ while (current.length) {
647
+ const first = current.shift();
648
+ if (!first) {
649
+ continue;
650
+ }
651
+ const guid = first.guid;
652
+ if (!guid) {
653
+ newEntities.push(first);
654
+ continue;
655
+ }
656
+ idMap[guid] = first;
657
+ }
658
+
569
659
  for (let i = 0; i < newArr.length; i++) {
570
660
  const entity = newArr[i];
571
- if (entity instanceof Entity && entity.guid != null) {
572
- idMap[entity.guid] = i;
661
+ const guid = entity.guid;
662
+ if (guid == null) {
663
+ continue;
573
664
  }
574
- }
575
- const remove: number[] = [];
576
- for (let k in oldArr) {
577
- if (
578
- // This handles sparse arrays.
579
- typeof k === 'number' &&
580
- k <= 4294967294 &&
581
- /^0$|^[1-9]\d*$/.test(k) &&
582
- oldArr.hasOwnProperty(k)
583
- ) {
584
- const guid = oldArr[k].guid;
585
- if (guid != null) {
586
- if (!idMap.hasOwnProperty(guid)) {
587
- // It was deleted.
588
- remove.push(k);
589
- } else if (newArr[idMap[guid]].mdate !== oldArr[k].mdate) {
590
- // It was modified.
591
- oldArr[k].$init(newArr[idMap[guid]].toJSON());
592
- delete idMap[guid];
593
- } else {
594
- // Item wasn't modified.
595
- delete idMap[guid];
596
- }
597
- }
665
+
666
+ if (!idMap.hasOwnProperty(guid)) {
667
+ // It was added.
668
+ current.push(entity);
669
+ } else if (idMap[guid].mdate !== entity.mdate) {
670
+ // It was modified.
671
+ idMap[guid].$init(entity.toJSON());
672
+ current.push(idMap[guid]);
673
+ } else {
674
+ // Item wasn't modified.
675
+ current.push(idMap[guid]);
598
676
  }
599
677
  }
600
- // Now we must remove the deleted ones.
601
- remove.sort(function (a, b) {
602
- // Sort backwards so we can remove in reverse order. (Preserves
603
- // indices.)
604
- if (a > b) return -1;
605
- if (a < b) return 1;
606
- return 0;
607
- });
608
- for (let n = 0; n < remove.length; n++) {
609
- oldArr.splice(remove[n], 1);
610
- }
611
- // And add the new ones.
612
- for (let value of Object.values(idMap)) {
613
- oldArr.splice(oldArr.length, 0, newArr[value]);
614
- }
678
+
679
+ current.splice(current.length, 0, ...newEntities);
615
680
  } else if (update != null && update.hasOwnProperty('query')) {
616
- if ('removed' in update) {
617
- for (let i = 0; i < oldArr.length; i++) {
618
- if (oldArr[i] != null && oldArr[i].guid === update.removed) {
619
- oldArr.splice(i, 1);
620
- return;
681
+ const updates =
682
+ 'multiple' in update && update.multiple ? update.messages : [update];
683
+ for (let curUpdate of updates) {
684
+ if ('removed' in curUpdate) {
685
+ for (let i = 0; i < current.length; i++) {
686
+ if (current[i] != null && current[i].guid === curUpdate.removed) {
687
+ current.splice(i, 1);
688
+ continue;
689
+ }
621
690
  }
622
691
  }
623
- }
624
692
 
625
- // Get the entity.
626
- let entity: EntityInterface | null = null;
627
- if ('added' in update) {
628
- // Check for it in the array already.
629
- for (let i = 0; i < oldArr.length; i++) {
630
- if (oldArr[i] != null && oldArr[i].guid === update.added) {
631
- entity = oldArr.splice(i, 1)[0].$init(update.data);
693
+ // Get the entity.
694
+ let entity: EntityInterface | null = null;
695
+ if ('added' in curUpdate) {
696
+ // Check for it in the array already.
697
+ for (let i = 0; i < current.length; i++) {
698
+ if (current[i] != null && current[i].guid === curUpdate.added) {
699
+ entity = current.splice(i, 1)[0].$init(curUpdate.data);
700
+ }
701
+ }
702
+ if (entity == null) {
703
+ // A new entity.
704
+ entity = this.nymph.initEntity(curUpdate.data);
632
705
  }
633
706
  }
634
- if (entity == null) {
635
- // A new entity.
636
- entity = this.nymph.initEntity(update.data);
637
- }
638
- }
639
- if ('updated' in update) {
640
- // Extract it from the array.
641
- for (let i = 0; i < oldArr.length; i++) {
642
- if (oldArr[i] != null && oldArr[i].guid === update.updated) {
643
- entity = oldArr.splice(i, 1)[0].$init(update.data);
707
+ if ('updated' in curUpdate) {
708
+ // Extract it from the array.
709
+ for (let i = 0; i < current.length; i++) {
710
+ if (current[i] != null && current[i].guid === curUpdate.updated) {
711
+ entity = current.splice(i, 1)[0].$init(curUpdate.data);
712
+ }
644
713
  }
645
714
  }
646
- }
647
715
 
648
- const query = JSON.parse(update.query);
649
- if (entity != null) {
650
- // Insert the entity in order.
651
- const sort =
652
- 'sort' in query[0] ? (query[0].sort as 'cdate' | 'mdate') : 'cdate';
653
- const reverse = query[0].hasOwnProperty('reverse')
654
- ? query[0].reverse
655
- : false;
656
- let i;
657
-
658
- if (reverse) {
659
- for (
660
- i = 0;
661
- ((oldArr[i] ?? {})[sort] ?? 0) >= (entity[sort] ?? 0) &&
662
- i < oldArr.length;
663
- i++
664
- );
665
- } else {
666
- for (
667
- i = 0;
668
- ((oldArr[i] ?? {})[sort] ?? 0) < (entity[sort] ?? 0) &&
669
- i < oldArr.length;
670
- i++
671
- );
672
- }
716
+ if (entity != null) {
717
+ const query = JSON.parse(curUpdate.query);
718
+ // Insert the entity in order.
719
+ const sort = 'sort' in query[0] ? (query[0].sort as string) : 'cdate';
720
+ const reverse = query[0].hasOwnProperty('reverse')
721
+ ? query[0].reverse
722
+ : false;
723
+ let i;
724
+
725
+ if (reverse) {
726
+ for (
727
+ i = 0;
728
+ ((current[i] ?? {})[sort] ?? 0) >= (entity[sort] ?? 0) &&
729
+ i < current.length;
730
+ i++
731
+ );
732
+ } else {
733
+ for (
734
+ i = 0;
735
+ ((current[i] ?? {})[sort] ?? 0) < (entity[sort] ?? 0) &&
736
+ i < current.length;
737
+ i++
738
+ );
739
+ }
673
740
 
674
- oldArr.splice(i, 0, entity);
741
+ current.splice(i, 0, entity);
742
+ }
675
743
  }
676
744
  }
677
745
  }
@@ -681,17 +749,22 @@ export default class PubSub {
681
749
  callback: T extends 'connect'
682
750
  ? PubSubConnectCallback
683
751
  : T extends 'disconnect'
684
- ? PubSubDisconnectCallback
685
- : never
752
+ ? PubSubDisconnectCallback
753
+ : T extends 'error'
754
+ ? PubSubErrorCallback
755
+ : never,
686
756
  ) {
687
757
  const prop = (event + 'Callbacks') as T extends 'connect'
688
758
  ? 'connectCallbacks'
689
759
  : T extends 'disconnect'
690
- ? 'disconnectCallbacks'
691
- : never;
760
+ ? 'disconnectCallbacks'
761
+ : T extends 'error'
762
+ ? 'errorCallbacks'
763
+ : never;
692
764
  if (!(prop in this)) {
693
765
  throw new Error('Invalid event type.');
694
766
  }
767
+ // @ts-ignore: The callback should always be the right type here.
695
768
  this[prop].push(callback);
696
769
  return () => this.off(event, callback);
697
770
  }
@@ -701,17 +774,22 @@ export default class PubSub {
701
774
  callback: T extends 'connect'
702
775
  ? PubSubConnectCallback
703
776
  : T extends 'disconnect'
704
- ? PubSubDisconnectCallback
705
- : never
777
+ ? PubSubDisconnectCallback
778
+ : T extends 'error'
779
+ ? PubSubErrorCallback
780
+ : never,
706
781
  ) {
707
782
  const prop = (event + 'Callbacks') as T extends 'connect'
708
783
  ? 'connectCallbacks'
709
784
  : T extends 'disconnect'
710
- ? 'disconnectCallbacks'
711
- : never;
785
+ ? 'disconnectCallbacks'
786
+ : T extends 'error'
787
+ ? 'errorCallbacks'
788
+ : never;
712
789
  if (!(prop in this)) {
713
790
  return false;
714
791
  }
792
+ // @ts-ignore: The callback should always be the right type here.
715
793
  const i = this[prop].indexOf(callback);
716
794
  if (i > -1) {
717
795
  this[prop].splice(i, 1);
@@ -719,12 +797,17 @@ export default class PubSub {
719
797
  return true;
720
798
  }
721
799
 
722
- public setToken(token: string | null) {
723
- authToken = token;
800
+ public authenticate(
801
+ authToken: string | null,
802
+ switchToken: string | null = null,
803
+ ) {
804
+ this.authToken = authToken;
805
+ this.switchToken = switchToken;
724
806
  if (this.isConnectionOpen()) {
725
807
  this._send({
726
808
  action: 'authenticate',
727
- token: authToken,
809
+ authToken: this.authToken,
810
+ switchToken: this.switchToken,
728
811
  });
729
812
  }
730
813
  }
@@ -738,7 +821,7 @@ export class PubSubSubscription<T> {
738
821
  constructor(
739
822
  query: string,
740
823
  callbacks: PubSubCallbacks<T>,
741
- unsubscribe: () => void
824
+ unsubscribe: () => void,
742
825
  ) {
743
826
  this.query = query;
744
827
  this.callbacks = callbacks;