@nymphjs/client 1.0.0-beta.7 → 1.0.0-beta.70

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/src/PubSub.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import Nymph, { InvalidRequestError } from './Nymph';
2
- import { NymphOptions, Options, Selector } from './Nymph.types';
3
- import { EntityConstructor, EntityInterface } from './Entity.types';
4
- import {
2
+ import type { NymphOptions, Options, Selector } from './Nymph.types';
3
+ import type { EntityInstanceType } from './Entity';
4
+ import type {
5
+ EntityConstructor,
6
+ EntityInterface,
7
+ EntityJson,
8
+ } from './Entity.types';
9
+ import type {
5
10
  PubSubCallbacks,
6
11
  PubSubConnectCallback,
7
12
  PubSubCountCallback,
@@ -9,15 +14,17 @@ import {
9
14
  PubSubEventType,
10
15
  PubSubRejectCallback,
11
16
  PubSubResolveCallback,
17
+ PubSubErrorCallback,
12
18
  PubSubSubscribable,
13
19
  PubSubUpdate,
14
20
  } from './PubSub.types';
15
21
  import { entityConstructorsToClassNames } from './utils';
16
-
17
- let authToken: string | null = null;
22
+ import { ClientError } from './HttpRequester';
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
29
  private waitForConnectionTimeout: NodeJS.Timeout | undefined;
23
30
  private pubsubUrl: string | undefined;
@@ -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,8 +215,8 @@ 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
  }
@@ -242,31 +260,31 @@ export default class PubSub {
242
260
  return;
243
261
  }
244
262
 
245
- this.connection.close(4200, 'Closure requested by application.');
263
+ this.connection.close(1000, 'Closure requested by application.');
246
264
  }
247
265
 
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();
266
+ private _waitForConnection(attempts = 1) {
267
+ // Wait 5 seconds, then check and attempt connection again if unsuccessful.
268
+ // Keep repeating, adding attempts^2*5 seconds each time to a max of ten
269
+ // minutes, until successful.
270
+ this.waitForConnectionTimeout = setTimeout(
271
+ () => {
272
+ if (this.connection) {
273
+ if (this.connection.readyState !== this.WebSocket.OPEN) {
274
+ if (this.connection.readyState !== this.WebSocket.CONNECTING) {
275
+ this.connection.close();
276
+ this._waitForConnection(attempts + 1);
277
+ this._attemptConnect();
278
+ } else {
279
+ this._waitForConnection(attempts + 1);
280
+ }
281
+ }
263
282
  } else {
264
- this._waitForConnection(attempts + 1);
283
+ this._attemptConnect();
265
284
  }
266
- } else {
267
- this._attemptConnect();
268
- }
269
- }, 5000);
285
+ },
286
+ Math.max(Math.pow(attempts, 2) * 5000, 1000 * 60 * 10),
287
+ );
270
288
  }
271
289
 
272
290
  private _attemptConnect() {
@@ -282,6 +300,11 @@ export default class PubSub {
282
300
  if (typeof console !== 'undefined' && !this.noConsole) {
283
301
  console.log('Nymph-PubSub connection established!');
284
302
  }
303
+
304
+ if (this.waitForConnectionTimeout) {
305
+ clearTimeout(this.waitForConnectionTimeout);
306
+ }
307
+
285
308
  for (let i = 0; i < this.connectCallbacks.length; i++) {
286
309
  const callback = this.connectCallbacks[i];
287
310
  if (callback) {
@@ -289,10 +312,11 @@ export default class PubSub {
289
312
  }
290
313
  }
291
314
 
292
- if (authToken != null) {
315
+ if (this.authToken != null) {
293
316
  this._send({
294
317
  action: 'authenticate',
295
- token: authToken,
318
+ authToken: this.authToken,
319
+ switchToken: this.switchToken,
296
320
  });
297
321
  }
298
322
 
@@ -340,17 +364,23 @@ export default class PubSub {
340
364
  private _onmessage(e: WebSocketEventMap['message']) {
341
365
  let data = JSON.parse(e.data);
342
366
  let subs: PubSubCallbacks<any>[] = [];
367
+ let set = 'set' in data && data.set;
343
368
  let count = 'count' in data;
369
+ let error = 'error' in data;
344
370
  if (
345
371
  data.hasOwnProperty('query') &&
346
372
  this.subscriptions.queries.hasOwnProperty(data.query)
347
373
  ) {
348
374
  subs = [...this.subscriptions.queries[data.query]];
349
- if (!count) {
375
+ if (!count && !error) {
350
376
  for (let i = 0; i < subs.length; i++) {
351
377
  const callback = subs[i][0];
352
378
  if (typeof callback === 'function') {
353
- callback(data);
379
+ callback(
380
+ set
381
+ ? data.data.map((e: EntityJson) => this.nymph.initEntity(e))
382
+ : data,
383
+ );
354
384
  }
355
385
  }
356
386
  }
@@ -359,14 +389,36 @@ export default class PubSub {
359
389
  this.subscriptions.uids.hasOwnProperty(data.uid)
360
390
  ) {
361
391
  subs = [...this.subscriptions.uids[data.uid]];
362
- if (!count) {
392
+ if (!count && !error) {
363
393
  for (let i = 0; i < subs.length; i++) {
364
394
  const callback = subs[i][0];
365
- if (typeof callback === 'function') {
366
- callback(data.value ?? null, data.event);
395
+ const errCallback = subs[i][1];
396
+ if (set && data.data == null) {
397
+ if (typeof errCallback === 'function') {
398
+ errCallback(
399
+ new ClientError(
400
+ { status: 404, statusText: 'Not Found' } as Response,
401
+ { textStatus: 'Not Found' },
402
+ ),
403
+ );
404
+ }
405
+ } else if (typeof callback === 'function') {
406
+ if (set) {
407
+ callback(data.data);
408
+ } else {
409
+ callback(data.value ?? null, data.event);
410
+ }
367
411
  }
368
412
  }
369
413
  }
414
+ } else if (error) {
415
+ for (let i = 0; i < this.errorCallbacks.length; i++) {
416
+ const callback = this.errorCallbacks[i];
417
+ if (callback) {
418
+ callback(data.error);
419
+ }
420
+ }
421
+ return;
370
422
  }
371
423
  if (count) {
372
424
  for (let i = 0; i < subs.length; i++) {
@@ -376,6 +428,14 @@ export default class PubSub {
376
428
  }
377
429
  }
378
430
  }
431
+ if (error) {
432
+ for (let i = 0; i < subs.length; i++) {
433
+ const callback = subs[i][1];
434
+ if (typeof callback === 'function') {
435
+ callback(data.error);
436
+ }
437
+ }
438
+ }
379
439
  }
380
440
 
381
441
  private _onclose(e: WebSocketEventMap['close']) {
@@ -389,7 +449,7 @@ export default class PubSub {
389
449
  }
390
450
  }
391
451
  if (
392
- e.code !== 4200 &&
452
+ e.code !== 1000 &&
393
453
  (typeof navigator === 'undefined' || navigator.onLine)
394
454
  ) {
395
455
  if (this.connection) {
@@ -412,6 +472,17 @@ export default class PubSub {
412
472
  );
413
473
  }
414
474
 
475
+ public isConnectionConnecting() {
476
+ return !!(
477
+ this.connection &&
478
+ this.connection.readyState === this.WebSocket.CONNECTING
479
+ );
480
+ }
481
+
482
+ public isConnection() {
483
+ return this.isConnectionOpen() || this.isConnectionConnecting();
484
+ }
485
+
415
486
  private _subscribeQuery(query: string, callbacks: PubSubCallbacks<any>) {
416
487
  let isNewSubscription = false;
417
488
  if (!this.subscriptions.queries.hasOwnProperty(query)) {
@@ -549,71 +620,59 @@ export default class PubSub {
549
620
  }
550
621
 
551
622
  public updateArray(
552
- oldArr: EntityInterface[],
553
- update: PubSubUpdate<EntityInterface[]>
623
+ current: EntityInterface[],
624
+ update: PubSubUpdate<EntityInterface[]>,
554
625
  ) {
555
626
  if (Array.isArray(update)) {
556
627
  const newArr = [...update];
557
628
 
558
- if (oldArr.length === 0) {
629
+ if (current.length === 0) {
559
630
  // This will happen on the first update from a subscribe.
560
- oldArr.splice(0, 0, ...newArr);
631
+ current.splice(0, 0, ...newArr);
561
632
  return;
562
633
  }
563
634
 
564
- const idMap: { [k: string]: number } = {};
565
- const Entity = this.nymph.getEntityClass('Entity');
635
+ const idMap: { [k: string]: EntityInterface } = {};
636
+ const newEntities: EntityInterface[] = [];
637
+ while (current.length) {
638
+ const first = current.shift();
639
+ if (!first) {
640
+ continue;
641
+ }
642
+ const guid = first.guid;
643
+ if (!guid) {
644
+ newEntities.push(first);
645
+ continue;
646
+ }
647
+ idMap[guid] = first;
648
+ }
649
+
566
650
  for (let i = 0; i < newArr.length; i++) {
567
651
  const entity = newArr[i];
568
- if (entity instanceof Entity && entity.guid != null) {
569
- idMap[entity.guid] = i;
652
+ const guid = entity.guid;
653
+ if (guid == null) {
654
+ continue;
570
655
  }
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
- }
656
+
657
+ if (!idMap.hasOwnProperty(guid)) {
658
+ // It was added.
659
+ current.push(entity);
660
+ } else if (idMap[guid].mdate !== entity.mdate) {
661
+ // It was modified.
662
+ idMap[guid].$init(entity.toJSON());
663
+ current.push(idMap[guid]);
664
+ } else {
665
+ // Item wasn't modified.
666
+ current.push(idMap[guid]);
595
667
  }
596
668
  }
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
- }
669
+
670
+ current.splice(current.length, 0, ...newEntities);
612
671
  } else if (update != null && update.hasOwnProperty('query')) {
613
672
  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);
673
+ for (let i = 0; i < current.length; i++) {
674
+ if (current[i] != null && current[i].guid === update.removed) {
675
+ current.splice(i, 1);
617
676
  return;
618
677
  }
619
678
  }
@@ -623,9 +682,9 @@ export default class PubSub {
623
682
  let entity: EntityInterface | null = null;
624
683
  if ('added' in update) {
625
684
  // 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);
685
+ for (let i = 0; i < current.length; i++) {
686
+ if (current[i] != null && current[i].guid === update.added) {
687
+ entity = current.splice(i, 1)[0].$init(update.data);
629
688
  }
630
689
  }
631
690
  if (entity == null) {
@@ -635,9 +694,9 @@ export default class PubSub {
635
694
  }
636
695
  if ('updated' in update) {
637
696
  // 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);
697
+ for (let i = 0; i < current.length; i++) {
698
+ if (current[i] != null && current[i].guid === update.updated) {
699
+ entity = current.splice(i, 1)[0].$init(update.data);
641
700
  }
642
701
  }
643
702
  }
@@ -645,8 +704,7 @@ export default class PubSub {
645
704
  const query = JSON.parse(update.query);
646
705
  if (entity != null) {
647
706
  // Insert the entity in order.
648
- const sort =
649
- 'sort' in query[0] ? (query[0].sort as 'cdate' | 'mdate') : 'cdate';
707
+ const sort = 'sort' in query[0] ? (query[0].sort as string) : 'cdate';
650
708
  const reverse = query[0].hasOwnProperty('reverse')
651
709
  ? query[0].reverse
652
710
  : false;
@@ -655,20 +713,20 @@ export default class PubSub {
655
713
  if (reverse) {
656
714
  for (
657
715
  i = 0;
658
- ((oldArr[i] ?? {})[sort] ?? 0) >= (entity[sort] ?? 0) &&
659
- i < oldArr.length;
716
+ ((current[i] ?? {})[sort] ?? 0) >= (entity[sort] ?? 0) &&
717
+ i < current.length;
660
718
  i++
661
719
  );
662
720
  } else {
663
721
  for (
664
722
  i = 0;
665
- ((oldArr[i] ?? {})[sort] ?? 0) < (entity[sort] ?? 0) &&
666
- i < oldArr.length;
723
+ ((current[i] ?? {})[sort] ?? 0) < (entity[sort] ?? 0) &&
724
+ i < current.length;
667
725
  i++
668
726
  );
669
727
  }
670
728
 
671
- oldArr.splice(i, 0, entity);
729
+ current.splice(i, 0, entity);
672
730
  }
673
731
  }
674
732
  }
@@ -679,16 +737,21 @@ export default class PubSub {
679
737
  ? PubSubConnectCallback
680
738
  : T extends 'disconnect'
681
739
  ? PubSubDisconnectCallback
682
- : never
740
+ : T extends 'error'
741
+ ? PubSubErrorCallback
742
+ : never,
683
743
  ) {
684
744
  const prop = (event + 'Callbacks') as T extends 'connect'
685
745
  ? 'connectCallbacks'
686
746
  : T extends 'disconnect'
687
747
  ? 'disconnectCallbacks'
748
+ : T extends 'error'
749
+ ? 'errorCallbacks'
688
750
  : never;
689
751
  if (!(prop in this)) {
690
752
  throw new Error('Invalid event type.');
691
753
  }
754
+ // @ts-ignore: The callback should always be the right type here.
692
755
  this[prop].push(callback);
693
756
  return () => this.off(event, callback);
694
757
  }
@@ -699,16 +762,21 @@ export default class PubSub {
699
762
  ? PubSubConnectCallback
700
763
  : T extends 'disconnect'
701
764
  ? PubSubDisconnectCallback
702
- : never
765
+ : T extends 'error'
766
+ ? PubSubErrorCallback
767
+ : never,
703
768
  ) {
704
769
  const prop = (event + 'Callbacks') as T extends 'connect'
705
770
  ? 'connectCallbacks'
706
771
  : T extends 'disconnect'
707
772
  ? 'disconnectCallbacks'
773
+ : T extends 'error'
774
+ ? 'errorCallbacks'
708
775
  : never;
709
776
  if (!(prop in this)) {
710
777
  return false;
711
778
  }
779
+ // @ts-ignore: The callback should always be the right type here.
712
780
  const i = this[prop].indexOf(callback);
713
781
  if (i > -1) {
714
782
  this[prop].splice(i, 1);
@@ -716,12 +784,17 @@ export default class PubSub {
716
784
  return true;
717
785
  }
718
786
 
719
- public setToken(token: string | null) {
720
- authToken = token;
787
+ public authenticate(
788
+ authToken: string | null,
789
+ switchToken: string | null = null,
790
+ ) {
791
+ this.authToken = authToken;
792
+ this.switchToken = switchToken;
721
793
  if (this.isConnectionOpen()) {
722
794
  this._send({
723
795
  action: 'authenticate',
724
- token: authToken,
796
+ authToken: this.authToken,
797
+ switchToken: this.switchToken,
725
798
  });
726
799
  }
727
800
  }
@@ -735,7 +808,7 @@ export class PubSubSubscription<T> {
735
808
  constructor(
736
809
  query: string,
737
810
  callbacks: PubSubCallbacks<T>,
738
- unsubscribe: () => void
811
+ unsubscribe: () => void,
739
812
  ) {
740
813
  this.query = query;
741
814
  this.callbacks = callbacks;
@@ -7,11 +7,16 @@ export type PubSubCountCallback = (count: number) => void;
7
7
  export type PubSubCallbacks<T> = [
8
8
  PubSubResolveCallback<T> | undefined,
9
9
  PubSubRejectCallback | undefined,
10
- PubSubCountCallback | undefined
10
+ PubSubCountCallback | undefined,
11
11
  ];
12
- export type PubSubEventType = 'connect' | 'disconnect';
12
+ export type PubSubEventType = 'connect' | 'disconnect' | 'error';
13
13
  export type PubSubConnectCallback = () => void;
14
14
  export type PubSubDisconnectCallback = () => void;
15
+ /**
16
+ * The error event is for unknown errors. Query errors fire their own reject
17
+ * callbacks.
18
+ */
19
+ export type PubSubErrorCallback = (err: any) => void;
15
20
  export type PubSubUpdate<T> =
16
21
  | T
17
22
  | {
@@ -31,5 +36,5 @@ export type PubSubUpdate<T> =
31
36
  export type PubSubSubscribable<T> = (
32
37
  resolve?: PubSubResolveCallback<T> | undefined,
33
38
  reject?: PubSubRejectCallback | undefined,
34
- count?: PubSubCountCallback | undefined
39
+ count?: PubSubCountCallback | undefined,
35
40
  ) => PubSubSubscription<T>;