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