@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.
- package/.vscode/settings.json +5 -0
- package/CHANGELOG.md +22 -0
- package/api.md +35 -36
- package/dist/lib/projectFile.d.ts +3 -3
- package/dist/lib/projectFile.js +4 -3
- package/dist/lib/projectFile.js.map +1 -1
- package/dist/lib/syncro/entities.js +11 -10
- package/dist/lib/syncro/entities.js.map +1 -1
- package/dist/lib/syncro/keyed.d.ts +2 -2
- package/dist/lib/syncro/keyed.js +23 -23
- package/dist/lib/syncro/keyed.js.map +1 -1
- package/dist/lib/syncro/syncro.d.ts +15 -13
- package/dist/lib/syncro/syncro.js +84 -59
- package/dist/lib/syncro/syncro.js.map +1 -1
- package/dist/lib/terafy.bootstrapper.d.ts +2 -2
- package/dist/lib/terafy.bootstrapper.js +15 -16
- package/dist/lib/terafy.bootstrapper.js.map +1 -1
- package/dist/lib/terafy.client.d.ts +24 -25
- package/dist/lib/terafy.client.js +50 -48
- package/dist/lib/terafy.client.js.map +1 -1
- package/dist/lib/terafy.proxy.js +4 -2
- package/dist/lib/terafy.proxy.js.map +1 -1
- package/dist/lib/terafy.server.d.ts +22 -8
- package/dist/lib/terafy.server.js +54 -58
- package/dist/lib/terafy.server.js.map +1 -1
- package/dist/plugin.vue2.es2019.js +210 -1224
- package/dist/plugins/base.d.ts +2 -2
- package/dist/plugins/base.js +1 -0
- package/dist/plugins/base.js.map +1 -1
- package/dist/plugins/firebase.d.ts +4 -4
- package/dist/plugins/firebase.js +7 -7
- package/dist/plugins/firebase.js.map +1 -1
- package/dist/plugins/vue2.d.ts +1 -1
- package/dist/plugins/vue2.js +6 -5
- package/dist/plugins/vue2.js.map +1 -1
- package/dist/plugins/vue3.js +6 -5
- package/dist/plugins/vue3.js.map +1 -1
- package/dist/terafy.bootstrapper.es2019.js +2 -2
- package/dist/terafy.bootstrapper.js +2 -2
- package/dist/terafy.es2019.js +2 -2
- package/dist/terafy.js +2 -2
- package/dist/utils/mixin.js +1 -1
- package/dist/utils/mixin.js.map +1 -1
- package/dist/utils/pDefer.d.ts +5 -1
- package/dist/utils/pDefer.js +6 -1
- package/dist/utils/pDefer.js.map +1 -1
- package/dist/utils/pathTools.d.ts +1 -1
- package/dist/utils/pathTools.js +2 -2
- package/dist/utils/pathTools.js.map +1 -1
- package/eslint.config.js +21 -7
- package/lib/projectFile.ts +5 -4
- package/lib/syncro/entities.ts +11 -10
- package/lib/syncro/keyed.ts +24 -24
- package/lib/syncro/syncro.ts +97 -60
- package/lib/terafy.bootstrapper.ts +15 -16
- package/lib/terafy.client.ts +62 -62
- package/lib/terafy.proxy.ts +8 -5
- package/lib/terafy.server.ts +75 -64
- package/package.json +5 -3
- package/plugins/base.ts +3 -2
- package/plugins/firebase.ts +12 -11
- package/plugins/vue2.ts +7 -6
- package/plugins/vue3.ts +6 -5
- package/utils/mixin.ts +1 -1
- package/utils/pDefer.ts +7 -2
- package/utils/pathTools.ts +3 -3
package/lib/syncro/syncro.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
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-
|
|
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
|
|
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}
|
|
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
|
|
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[]) {}
|
|
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<
|
|
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
|
|
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
|
-
|
|
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) {
|
|
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
|
|
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
|
-
|
|
268
|
+
const settings = {
|
|
269
269
|
allowAsterisk: false,
|
|
270
270
|
...options,
|
|
271
271
|
};
|
|
272
272
|
|
|
273
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
386
|
-
for (
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
394
|
+
switch (isDocumentType) {
|
|
395
|
+
case 'mapValue':
|
|
395
396
|
result[key] = Syncro.fromFirestoreFields(value.mapValue.fields || {});
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
403
|
+
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
case 'nullValue':
|
|
400
407
|
result[key] = null;
|
|
401
|
-
|
|
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<
|
|
421
|
-
|
|
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
|
-
|
|
457
|
+
const settings = {
|
|
447
458
|
method: 'merge',
|
|
448
459
|
...options,
|
|
449
460
|
};
|
|
450
|
-
|
|
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.
|
|
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
|
-
|
|
486
|
+
const settings = {
|
|
476
487
|
initialState: null,
|
|
477
488
|
retries: 5,
|
|
478
489
|
retryMinTime: 250,
|
|
479
490
|
...options,
|
|
480
491
|
};
|
|
481
492
|
|
|
482
|
-
|
|
483
|
-
let reactive: ReactiveWrapper; // Eventual response from reactive() with the
|
|
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> => {
|
|
488
|
-
await this.setHeartbeat(false); // Disable any existing heartbeat
|
|
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
|
-
//
|
|
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
|
-
|
|
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);
|
|
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
|
|
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();
|
|
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
|
-
|
|
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
|
|
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 {
|
|
626
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
828
|
+
* @returns {*} The current branch contents
|
|
792
829
|
*/
|
|
793
830
|
export function randomBranch(depth: number = 0): any {
|
|
794
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
if (!tc.mixin) throw new Error(
|
|
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(
|
|
55
|
-
if (!(this as any).detectMode || typeof (this as any).detectMode != 'function') throw new Error(
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
114
|
+
script.remove();
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -123,14 +123,13 @@ export default class TeraFy {
|
|
|
123
123
|
};
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
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.
|
|
132
|
+
document.head.append(script);
|
|
134
133
|
});
|
|
135
134
|
}
|
|
136
135
|
|