@iebh/tera-fy 2.3.0 → 2.3.2

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 (66) hide show
  1. package/.vscode/settings.json +5 -0
  2. package/CHANGELOG.md +22 -0
  3. package/api.md +35 -36
  4. package/dist/lib/projectFile.d.ts +3 -3
  5. package/dist/lib/projectFile.js +4 -3
  6. package/dist/lib/projectFile.js.map +1 -1
  7. package/dist/lib/syncro/entities.js +11 -10
  8. package/dist/lib/syncro/entities.js.map +1 -1
  9. package/dist/lib/syncro/keyed.d.ts +2 -2
  10. package/dist/lib/syncro/keyed.js +23 -23
  11. package/dist/lib/syncro/keyed.js.map +1 -1
  12. package/dist/lib/syncro/syncro.d.ts +15 -13
  13. package/dist/lib/syncro/syncro.js +84 -59
  14. package/dist/lib/syncro/syncro.js.map +1 -1
  15. package/dist/lib/terafy.bootstrapper.d.ts +2 -2
  16. package/dist/lib/terafy.bootstrapper.js +15 -16
  17. package/dist/lib/terafy.bootstrapper.js.map +1 -1
  18. package/dist/lib/terafy.client.d.ts +24 -25
  19. package/dist/lib/terafy.client.js +50 -48
  20. package/dist/lib/terafy.client.js.map +1 -1
  21. package/dist/lib/terafy.proxy.js +4 -2
  22. package/dist/lib/terafy.proxy.js.map +1 -1
  23. package/dist/lib/terafy.server.d.ts +22 -8
  24. package/dist/lib/terafy.server.js +54 -58
  25. package/dist/lib/terafy.server.js.map +1 -1
  26. package/dist/plugin.vue2.es2019.js +210 -1224
  27. package/dist/plugins/base.d.ts +2 -2
  28. package/dist/plugins/base.js +1 -0
  29. package/dist/plugins/base.js.map +1 -1
  30. package/dist/plugins/firebase.d.ts +4 -4
  31. package/dist/plugins/firebase.js +7 -7
  32. package/dist/plugins/firebase.js.map +1 -1
  33. package/dist/plugins/vue2.d.ts +1 -1
  34. package/dist/plugins/vue2.js +6 -5
  35. package/dist/plugins/vue2.js.map +1 -1
  36. package/dist/plugins/vue3.js +6 -5
  37. package/dist/plugins/vue3.js.map +1 -1
  38. package/dist/terafy.bootstrapper.es2019.js +2 -2
  39. package/dist/terafy.bootstrapper.js +2 -2
  40. package/dist/terafy.es2019.js +2 -2
  41. package/dist/terafy.js +2 -2
  42. package/dist/utils/mixin.js +1 -1
  43. package/dist/utils/mixin.js.map +1 -1
  44. package/dist/utils/pDefer.d.ts +5 -1
  45. package/dist/utils/pDefer.js +6 -1
  46. package/dist/utils/pDefer.js.map +1 -1
  47. package/dist/utils/pathTools.d.ts +1 -1
  48. package/dist/utils/pathTools.js +2 -2
  49. package/dist/utils/pathTools.js.map +1 -1
  50. package/eslint.config.js +21 -7
  51. package/lib/projectFile.ts +5 -4
  52. package/lib/syncro/entities.ts +11 -10
  53. package/lib/syncro/keyed.ts +24 -24
  54. package/lib/syncro/syncro.ts +97 -60
  55. package/lib/terafy.bootstrapper.ts +15 -16
  56. package/lib/terafy.client.ts +62 -62
  57. package/lib/terafy.proxy.ts +8 -5
  58. package/lib/terafy.server.ts +75 -64
  59. package/package.json +5 -3
  60. package/plugins/base.ts +3 -2
  61. package/plugins/firebase.ts +12 -11
  62. package/plugins/vue2.ts +7 -6
  63. package/plugins/vue3.ts +6 -5
  64. package/utils/mixin.ts +1 -1
  65. package/utils/pDefer.ts +7 -2
  66. package/utils/pathTools.ts +3 -3
@@ -1,5 +1,5 @@
1
- // @ts-nocheck
2
-
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable no-unused-vars */
3
3
  import {
4
4
  isEmpty,
5
5
  cloneDeep,
@@ -18,7 +18,7 @@ import {
18
18
  Firestore,
19
19
  Unsubscribe,
20
20
  } from 'firebase/firestore';
21
- // @ts-ignore
21
+ // @ts-expect-error No declaration file for marshal
22
22
  import marshal from '@momsfriendlydevco/marshal';
23
23
  import {nanoid} from 'nanoid';
24
24
  import PromiseRetry from 'p-retry';
@@ -84,7 +84,7 @@ export default class Syncro {
84
84
 
85
85
 
86
86
  /**
87
- * OPTIONAL SyncroEntiries from './entiries.js' if its required
87
+ * OPTIONAL SyncroEntries from './entities.ts' if its required
88
88
  * This only gets populated if `config.forceLocalInit` is truthy and we've mounted at least one Syncro
89
89
  *
90
90
  * @type {Record<string, any>}
@@ -129,7 +129,7 @@ export default class Syncro {
129
129
  * Various Misc config for the Syncro instance
130
130
  *
131
131
  * @type {Object}
132
- * @property {Number} heartbeatinterval Time in milliseconds between heartbeat beacons
132
+ * @property {Number} heartbeatInterval Time in milliseconds between heartbeat beacons
133
133
  * @property {String} syncroRegistryUrl The prefix Sync worker URL, used to populate Syncros and determine their active status
134
134
  * @property {Object} context Additional named parameters to pass to callbacks like initState
135
135
  */
@@ -142,7 +142,7 @@ export default class Syncro {
142
142
 
143
143
  /**
144
144
  * Whether the next heartbeat should be marked as 'dirty'
145
- * This indicates that at least one change has occured since the last hearbeat and the server should perform a flush (but not a clean)
145
+ * This indicates that at least one change has occurred since the last heartbeat and the server should perform a flush (but not a clean)
146
146
  * This flag is only transmitted once in the next heartbeat before being reset
147
147
  *
148
148
  * @see markDirty()
@@ -158,7 +158,7 @@ export default class Syncro {
158
158
  *
159
159
  * @param {*...} [msg] The message to output
160
160
  */
161
- debug(...msg: any[]) {} // eslint-disable-line no-unused-vars
161
+ debug(...msg: any[]) {}
162
162
 
163
163
 
164
164
  /**
@@ -213,14 +213,14 @@ export default class Syncro {
213
213
  * Actions to preform when we are destroying this instance
214
214
  * This is an array of function callbacks to execute in parallel when `destroy()` is called
215
215
  *
216
- * @type {Array<() => void>}
216
+ * @type {Array<function>}
217
217
  */
218
218
  _destroyActions: Array<() => void> = [];
219
219
 
220
220
 
221
221
  /**
222
222
  * Function to return whatever the local framework uses as a reactive object
223
- * This should respond with an object of mandatory functions to watch for changes and remerge them
223
+ * This should respond with an object of mandatory functions to watch for changes and re-merge them
224
224
  *
225
225
  * @param {Object} value Initial value of the reactive
226
226
  *
@@ -232,7 +232,7 @@ export default class Syncro {
232
232
  */
233
233
  getReactive(value: any): ReactiveWrapper {
234
234
  console.warn('Syncro.getReactive has not been subclassed, assuming a POJO response');
235
- let doc: Record<string, any> = {...value};
235
+ const doc: Record<string, any> = {...value};
236
236
  return {
237
237
  doc,
238
238
  setState(state: any) {
@@ -243,7 +243,7 @@ export default class Syncro {
243
243
  getState() {
244
244
  return cloneDeep(doc);
245
245
  },
246
- watch(cb: (newState: any) => void) { // eslint-disable-line no-unused-vars
246
+ watch(cb: (newState: any) => void) {
247
247
  // Stub
248
248
  },
249
249
  };
@@ -252,7 +252,7 @@ export default class Syncro {
252
252
 
253
253
  /**
254
254
  * Returns the split entity + ID relationship from a given session path
255
- * This funciton checks for valid UUID format strings + that the entity is a known/supported entity (see `knownEntities`)
255
+ * This function checks for valid UUID format strings + that the entity is a known/supported entity (see `knownEntities`)
256
256
  * NOTE: When used by itself (i.e. ignoring response) this function can also act as a guard that a path is valid
257
257
  *
258
258
  * INPUT: `widgets::UUID` -> `{entity:'widgets', id:UUID}`
@@ -265,12 +265,12 @@ export default class Syncro {
265
265
  * @returns {PathSplitResult} An object composed of the session path components
266
266
  */
267
267
  static pathSplit(path: string, options?: any): PathSplitResult {
268
- let settings = {
268
+ const settings = {
269
269
  allowAsterisk: false,
270
270
  ...options,
271
271
  };
272
272
 
273
- let pathMatcher = new RegExp(
273
+ const pathMatcher = new RegExp(
274
274
  // Compose the patch matching expression - note double escapes for backslashes to avoid encoding as raw string values
275
275
  '^'
276
276
  + '(?<entity>\\w+?)' // Any alpha-numeric sequence as the entity name (non-greedy capture)
@@ -284,7 +284,7 @@ export default class Syncro {
284
284
  + '$'
285
285
  );
286
286
 
287
- let extracted = { ...pathMatcher.exec(path)?.groups } as { entity?: string, id?: string, relation?: string };
287
+ const extracted = { ...pathMatcher.exec(path)?.groups } as { entity?: string, id?: string, relation?: string };
288
288
 
289
289
  if (!extracted || !extracted.entity || !extracted.id) throw new Error(`Invalid session path syntax "${path}"`);
290
290
  if (Syncro.SyncroEntities && !(extracted.entity in Syncro.SyncroEntities)) throw new Error(`Unsupported entity "${path}" -> Entity="${extracted.entity}"`);
@@ -306,7 +306,7 @@ export default class Syncro {
306
306
  * This applies the following mutations to the incoming object:
307
307
  *
308
308
  * 1. Arrays are converted to Objects (Firestore cannot store nested arrays)
309
- * 2. All non-POJO objects (e.g. Dates) to a symetric object
309
+ * 2. All non-POJO objects (e.g. Dates) to a symmetric object
310
310
  *
311
311
  * @param {Object} snapshot The current state to convert
312
312
  * @returns {Object} A Firebase compatible object
@@ -382,24 +382,35 @@ export default class Syncro {
382
382
  * @returns {Object} A JavaScript POJO representing the converted state
383
383
  */
384
384
  static fromFirestoreFields(fields: any = {}): any {
385
- let result: Record<string, any> = {};
386
- for (let key in fields) {
387
- let value = fields[key];
388
- let isDocumentType = [
385
+ const result: Record<string, any> = {};
386
+ for (const key in fields) {
387
+ const value = fields[key];
388
+ const isDocumentType = [
389
389
  'stringValue', 'booleanValue', 'doubleValue',
390
390
  'integerValue', 'timestampValue', 'mapValue', 'arrayValue', 'nullValue', // Added nullValue
391
391
  ].find(t => t === Object.keys(value)[0]); // Check the first key of the value object
392
392
 
393
393
  if (isDocumentType) {
394
- if (isDocumentType === 'mapValue') {
394
+ switch (isDocumentType) {
395
+ case 'mapValue':
395
396
  result[key] = Syncro.fromFirestoreFields(value.mapValue.fields || {});
396
- } else if (isDocumentType === 'arrayValue') {
397
- let list = value.arrayValue.values;
397
+
398
+ break;
399
+
400
+ case 'arrayValue': {
401
+ const list = value.arrayValue.values;
398
402
  result[key] = !!list ? list.map((l: any) => Syncro.fromFirestoreFields(l)) : [];
399
- } else if (isDocumentType === 'nullValue') {
403
+
404
+ break;
405
+ }
406
+ case 'nullValue':
400
407
  result[key] = null;
401
- } else {
408
+
409
+ break;
410
+
411
+ default:
402
412
  result[key] = value[isDocumentType];
413
+
403
414
  }
404
415
  } else {
405
416
  // This case might not be standard Firestore field structure, but handle recursively
@@ -417,8 +428,8 @@ export default class Syncro {
417
428
  *
418
429
  * @returns {Promise<Object|Null>} An eventual snapshot of the given path, if the entity doesn't exist null is returned
419
430
  */
420
- static getSnapshot(path: string): Promise<any | null> {
421
- let {fsCollection, fsId} = Syncro.pathSplit(path);
431
+ static getSnapshot(path: string): Promise<object | null> {
432
+ const {fsCollection, fsId} = Syncro.pathSplit(path);
422
433
 
423
434
  return Promise.resolve()
424
435
  .then(async ()=> FirestoreGetDoc( // Set up binding and wait for it to come ready
@@ -443,11 +454,11 @@ export default class Syncro {
443
454
  * @returns {Promise<*>} The state object after it has been applied
444
455
  */
445
456
  static setSnapshot(path: string, state: any, options?: { method?: 'merge' | 'set' }): Promise<any> {
446
- let settings = {
457
+ const settings = {
447
458
  method: 'merge',
448
459
  ...options,
449
460
  };
450
- let {fsCollection, fsId} = Syncro.pathSplit(path);
461
+ const {fsCollection, fsId} = Syncro.pathSplit(path);
451
462
  const docRef = FirestoreDocRef(Syncro.firestore, fsCollection, fsId);
452
463
  const firestoreData = Syncro.toFirestore(state);
453
464
 
@@ -467,32 +478,56 @@ export default class Syncro {
467
478
  * Mount the remote Firestore document against this Syncro instance
468
479
  *
469
480
  * @param {Object} [options] Additional options to mutate behaviour
470
- * @param {Object} [options.initalState] State to use if no state is already loaded, overrides the entities own `initState` function fetcher
481
+ * @param {Object} [options.initialState] State to use if no state is already loaded, overrides the entities own `initState` function fetcher
471
482
  * @param {Number} [options.retries=3] Number of times to retry if a mounted Syncro fails its sanity checks
472
483
  * @returns {Promise<Syncro>} A promise which resolves as this syncro instance when completed
473
484
  */
474
485
  mount(options?: any): Promise<Syncro> {
475
- let settings = {
486
+ const settings = {
476
487
  initialState: null,
477
488
  retries: 5,
478
489
  retryMinTime: 250,
479
490
  ...options,
480
491
  };
481
492
 
482
- let {fsCollection, fsId, entity} = Syncro.pathSplit(this.path);
483
- let reactive: ReactiveWrapper; // Eventual response from reactive() with the intitial value
493
+ const {fsCollection, fsId, entity} = Syncro.pathSplit(this.path);
494
+ let reactive: ReactiveWrapper; // Eventual response from reactive() with the initial value
484
495
  let doc: any; // Eventual Firebase document
485
496
 
486
497
  return PromiseRetry(
487
- async (): Promise<Syncro> => { // Added async here for await
488
- await this.setHeartbeat(false); // Disable any existing heartbeat - this only really applies if we're changing path for some reason
498
+ async (): Promise<Syncro> => {
499
+ await this.setHeartbeat(false); // Disable any existing heartbeat
489
500
 
490
501
  // Set up binding and wait for it to come ready
491
502
  this.docRef = FirestoreDocRef(Syncro.firestore, fsCollection, fsId);
492
503
 
493
- // Initalize state
504
+ // Initialize state
494
505
  let initialState = await this.getFirestoreState();
495
506
 
507
+ // If we have a project that has `users` (written by the invite system)
508
+ // but lacks core data like `name` 9implying a full sync hasn't happened),
509
+ // we assume the document is corrupted/partial.
510
+ if (
511
+ this.path.startsWith('projects::') &&
512
+ initialState &&
513
+ !isEmpty(initialState) &&
514
+ initialState.users &&
515
+ (!initialState.type && !initialState.name && !initialState.created)
516
+ ) {
517
+ this.debugError('Zombie state detected (Partial document). Forcing repair...');
518
+
519
+ // Force the backend to wipe and re-sync this entity
520
+ const repairRes = await fetch(`${this.config.syncroRegistryUrl}/${this.path}?drop=1&force=1`);
521
+
522
+ if (repairRes.ok) {
523
+ this.debug('Repair signal sent successfully. Reloading local state...');
524
+ // Fetch the newly corrected state from Firestore
525
+ initialState = await this.getFirestoreState();
526
+ } else {
527
+ console.error('[Syncro] Self-healing failed', repairRes.statusText);
528
+ }
529
+ }
530
+
496
531
  // Construct a reactive component
497
532
  reactive = this.getReactive(initialState);
498
533
  if (!reactive.doc || !reactive.setState || !reactive.getState || !reactive.watch) throw new Error('Syncro.getReactive() requires a returned `doc`, `setState()`, `getState()` + `watch()`');
@@ -502,25 +537,25 @@ export default class Syncro {
502
537
 
503
538
  // Subscribe to remote updates
504
539
  const unsubscribe: Unsubscribe = FirestoreOnSnapshot(this.docRef, snapshot => {
505
- let snapshotData = Syncro.fromFirestore(snapshot.data());
540
+ const snapshotData = Syncro.fromFirestore(snapshot.data());
506
541
  this.debug('Incoming snapshot', {snapshotData});
507
542
  reactive.setState(snapshotData);
508
543
  });
509
- this._destroyActions.push(unsubscribe); // Add the unsubscribe handle to the list of destroyAction promises we call on `destroy()`
544
+ this._destroyActions.push(unsubscribe);
510
545
 
511
546
  // Optionally create the doc if it has no content
512
547
  if (!isEmpty(doc)) { // Doc already has content - skip
513
548
  // Do nothing
514
- } else if (settings.initialState) { // Provided an intiailState - use that instead of the entities own method
549
+ } else if (settings.initialState) { // Provided an initialState
515
550
  this.debug('Populate initial Syncro state (from provided initialState)');
516
551
  await this.setFirestoreState(settings.initialState, {method: 'set'});
517
552
  } else {
553
+ // Doc is empty (or was missing).
518
554
  this.debug(`Populate initial Syncro state (from "${entity}" Syncro worker)`);
519
555
  const response = await fetch(`${this.config.syncroRegistryUrl}/${this.path}`);
520
556
  if (!response.ok) {
521
557
  throw new Error(`Failed to check Syncro "${fsCollection}::${fsId}" status - ${response.statusText}`);
522
558
  }
523
- // Assuming the fetch populates the syncro state server-side, no local state set needed here
524
559
  }
525
560
 
526
561
  // Setup local state watcher
@@ -543,7 +578,7 @@ export default class Syncro {
543
578
  factor: 3,
544
579
  onFailedAttempt: async (e: any) => {
545
580
  this.debugError(`[Attempt ${e.attemptNumber}/${e.attemptNumber + e.retriesLeft - 1}] to mount syncro`, e);
546
- await this.destroy(); // Ensure cleanup on failed attempt before retry
581
+ await this.destroy();
547
582
  },
548
583
  },
549
584
  );
@@ -580,7 +615,7 @@ export default class Syncro {
580
615
  }
581
616
  // }}}
582
617
 
583
- let settings = {
618
+ const settings = {
584
619
  delta: true,
585
620
  flush: true,
586
621
  forceFlush: false,
@@ -617,13 +652,14 @@ export default class Syncro {
617
652
  * Schedule Syncro heartbeats
618
653
  * This populates the `sync` presence meta-information
619
654
  *
620
- * @param {Boolean} [enable=true] Whether to enable heartbeating
655
+ * @param {Boolean} [enable=true] Whether to enable heartbeats
621
656
  *
622
657
  * @param {Object} [options] Additional options to mutate behaviour
623
658
  * @param {Boolean} [options.immediate=false] Fire a heartbeat as soon as this function is called, this is only really useful on mount
659
+ * @returns Promise that resolves to void or void
624
660
  */
625
- setHeartbeat(enable: boolean = true, options?: any): Promise<void> | void { // Return type adjusted
626
- let settings = {
661
+ setHeartbeat(enable: boolean = true, options?: any): Promise<void> | void {
662
+ const settings = {
627
663
  immediate: true,
628
664
  ...options,
629
665
  };
@@ -648,7 +684,7 @@ export default class Syncro {
648
684
 
649
685
 
650
686
  /**
651
- * Perform one heartbeat pulse to the server to indicate presense within this Syncro
687
+ * Perform one heartbeat pulse to the server to indicate presence within this Syncro
652
688
  * This function is automatically called by a timer if `setHeartbeat(true)` (the default behaviour)
653
689
  *
654
690
  * @returns {Promise} A promise which resolves when the operation has completed
@@ -686,10 +722,11 @@ export default class Syncro {
686
722
  * @param {Object} [options] Additional options to mutate behaviour
687
723
  * @param {'merge'|'set'} [options.method='merge'] How to apply the new state. 'merge' (merge in partial data to an existing Syncro), 'set' (overwrite the entire Syncro state)
688
724
  *
725
+ * @param {number} retries How many tries to take before erroring
689
726
  * @returns {Promise} A promise which resolves when the operation has completed
690
727
  */
691
728
  async setFirestoreState(state: any, options?: { method?: 'merge' | 'set' }, retries = 0): Promise<void> {
692
- let settings = {
729
+ const settings = {
693
730
  method: 'merge',
694
731
  ...options,
695
732
  };
@@ -760,7 +797,7 @@ export default class Syncro {
760
797
  * @returns {Promise} A promise which resolves when the operation has completed
761
798
  */
762
799
  flush(options?: any): Promise<void | null> {
763
- let settings = {
800
+ const settings = {
764
801
  destroy: false,
765
802
  ...options,
766
803
  };
@@ -768,7 +805,7 @@ export default class Syncro {
768
805
  return fetch(`${this.config.syncroRegistryUrl}/${this.path}/flush` + (settings.destroy ? '?destroy=1' : ''))
769
806
  .then(response => response.ok
770
807
  ? null
771
- : Promise.reject(response.statusText || 'An error occured')
808
+ : Promise.reject(response.statusText || 'An error occurred')
772
809
  );
773
810
  }
774
811
 
@@ -784,14 +821,14 @@ export default class Syncro {
784
821
 
785
822
  /**
786
823
  * Build a chaotic random tree structure based on dice rolls
787
- * This funciton is mainly used for sync testing
824
+ * This function is mainly used for sync testing
788
825
  *
789
826
  * @param {Number} [depth=0] The current depth we are starting at, changes the nature of branches based on probability
790
827
  *
791
- * @returns {*} The current branch conotents
828
+ * @returns {*} The current branch contents
792
829
  */
793
830
  export function randomBranch(depth: number = 0): any {
794
- let dice = // Roll a dice to pick the content
831
+ const dice = // Roll a dice to pick the content
795
832
  depth == 0 ? 10 // first roll is always '10'
796
833
  : random(0, 11 - depth, false); // Subsequent rolls bias downwards based on depth (to avoid recursion)
797
834
 
@@ -826,10 +863,10 @@ const marshalBaseConfig = {
826
863
  { // Flatten arrays into something Firebase can handle
827
864
  id: `~array`,
828
865
  recursive: true,
829
- test: v => Array.isArray(v),
830
- serialize: v => ({_: '~array', ...v}),
831
- deserialize: v => {
832
- let arr = Array.from({length: Object.keys(v).length - 1});
866
+ test: (v: any) => Array.isArray(v),
867
+ serialize: (v: any) => ({_: '~array', ...v}),
868
+ deserialize: (v: any) => {
869
+ const arr = Array.from({length: Object.keys(v).length - 1});
833
870
 
834
871
  Object.entries(v)
835
872
  .filter(([k]) => k !== '_')
@@ -840,16 +877,16 @@ const marshalBaseConfig = {
840
877
  },
841
878
  { // Strip Functions during {,de-}serialization
842
879
  id: '~function',
843
- test: v => typeof v == 'function',
844
- serialize: (v, path) => {
880
+ test: (v: any) => typeof v == 'function',
881
+ serialize: (v: any, path: string[]) => {
845
882
  console.warn('Marshal Warning: Stripping function from path', path.join('.'));
846
883
  throw new Error('Function serializing is forbidden');
847
884
  },
848
- deserialize: (v, path) => {
885
+ deserialize: (v: any, path: string[]) => {
849
886
  console.warn('Marshal Warning: Stripping function from path', path.join('.'));
850
887
  },
851
888
  },
852
889
  ...marshal.settings.modules // Use default Marshal modules excepting...
853
- .filter(mod => mod.id != '~function') // Remove the Marshal function module as this upsets Cloudflare workers by using the `eval` built-in
890
+ .filter((mod: any) => mod.id != '~function') // Remove the Marshal function module as this upsets Cloudflare workers by using the `eval` built-in
854
891
  ],
855
892
  };
@@ -24,14 +24,14 @@ export default class TeraFy {
24
24
 
25
25
 
26
26
  /**
27
- * Download the remote TERA-fy client, initalize it and mix it in with this class instance
27
+ * Download the remote TERA-fy client, initialize it and mix it in with this class instance
28
28
  *
29
29
  * @param {Object} [options] Additional options to merge into `settings` via `set`
30
- * @returns {Promise<TeraFy>} An eventual promise which will resovle with this terafy instance
30
+ * @returns {Promise<TeraFy>} An eventual promise which will resolve with this terafy instance
31
31
  */
32
32
  init(options?: any): Promise<this> {
33
33
  // FIXME: Note this needs to point at the live site
34
- let getUrl = (client: string) => `https://dev.tera-tools.com/api/tera-fy/${client}.js`;
34
+ const getUrl = (client: string) => `https://dev.tera-tools.com/api/tera-fy/${client}.js`;
35
35
 
36
36
  return Promise.resolve()
37
37
  .then(()=>
@@ -41,8 +41,8 @@ export default class TeraFy {
41
41
  : Promise.reject(`Unsupported TERA-fy clientType "${this.settings.clientType}"`)
42
42
  )
43
43
  .then((TeraClient: TeraClientConstructor) => {
44
- let tc = new TeraClient();
45
- if (!tc.mixin) throw new Error('TERA-fy client doesnt expose a mixin() method');
44
+ const tc = new TeraClient();
45
+ if (!tc.mixin) throw new Error("TERA-fy client doesn't expose a mixin() method");
46
46
 
47
47
  tc.mixin(this, tc);
48
48
 
@@ -51,10 +51,10 @@ export default class TeraFy {
51
51
  })
52
52
  .then(()=> { // Sanity checks
53
53
  console.log('IAM', this);
54
- if (!(this as any).init || typeof (this as any).init != 'function') throw new Error('Newly mixed-in TERA-fy client doesnt expose a init() method');
55
- if (!(this as any).detectMode || typeof (this as any).detectMode != 'function') throw new Error('Newly mixed-in TERA-fy client doesnt expose a detectMode() method');
54
+ if (!(this as any).init || typeof (this as any).init != 'function') throw new Error("Newly mixed-in TERA-fy client doesn't expose a init() method");
55
+ if (!(this as any).detectMode || typeof (this as any).detectMode != 'function') throw new Error("Newly mixed-in TERA-fy client doesn't expose a detectMode() method");
56
56
  })
57
- // Run all deferred methods as an sequencial promise chain
57
+ // Run all deferred methods as an sequential promise chain
58
58
  .then(() => this.bootstrapperDeferredMethods.reduce((chain: Promise<any>, dm: DeferredMethod) => {
59
59
  return chain.then(() => {
60
60
  if (dm.method == 'use' && typeof dm.args[0] == 'string') { // Wrap `use(pluginClient:String,options:Object)` method to fetch plugin from remote
@@ -68,7 +68,7 @@ export default class TeraFy {
68
68
  return Promise.resolve((this as any)[dm.method].apply(this, dm.args));
69
69
  }
70
70
  });
71
- }, Promise.resolve<any>(undefined))) // Initialize reduce chain correctly
71
+ }, Promise.resolve())) // Initialize reduce chain correctly
72
72
  .then(()=> { delete (this as any).bootstrapperDeferredMethods; }) // Use type assertion for delete
73
73
  .then(()=> console.log('TYBS', 'Init'))
74
74
  // Call the *actual* init method mixed in from the client
@@ -107,11 +107,11 @@ export default class TeraFy {
107
107
  `;
108
108
 
109
109
  // Create cleanup function
110
- let cleanup = () => {
110
+ const cleanup = () => {
111
111
  // console.warn('CLEANUP', moduleId); // Keep console.warn commented out unless needed
112
112
  delete (window as any)[`installMod${moduleId}`]; // Use type assertion for delete
113
113
  if (script.parentNode) { // Check if script is still in DOM
114
- script.parentNode.removeChild(script);
114
+ script.remove();
115
115
  }
116
116
  }
117
117
 
@@ -123,14 +123,13 @@ export default class TeraFy {
123
123
  };
124
124
 
125
125
 
126
- // FIXME: Not sure if this is actually detecting errors? addEventListener instead maybe?
127
- script.onerror = (event: Event | string, source?: string, lineno?: number, colno?: number, error?: Error) => {
128
- reject(new Error(`Failed to load module from ${url} - ${error ? error.toString() : event}`));
126
+ script.addEventListener('error', (event: Event) => {
127
+ reject(new Error(`Failed to load module script from ${url}. Event type: ${event.type}`));
129
128
  cleanup();
130
- };
129
+ });
131
130
 
132
131
  // Append stub script element and quit
133
- document.head.appendChild(script);
132
+ document.head.append(script);
134
133
  });
135
134
  }
136
135