@iebh/tera-fy 1.14.2 → 1.15.0
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/CHANGELOG.md +20 -0
- package/api.md +446 -398
- package/dist/plugin.vue2.es2019.js +1 -1
- package/dist/terafy.es2019.js +2 -2
- package/dist/terafy.js +2 -2
- package/lib/terafy.client.js +53 -12
- package/lib/terafy.server.js +30 -2
- package/package.json +1 -1
- package/plugins/vue2.js +30 -15
- package/plugins/vue3.js +26 -14
package/lib/terafy.client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {diff} from 'just-diff';
|
|
2
|
-
import {cloneDeep
|
|
2
|
+
import {cloneDeep} from 'lodash-es';
|
|
3
3
|
import Mitt from 'mitt';
|
|
4
4
|
import {nanoid} from 'nanoid';
|
|
5
5
|
import ProjectFile from './projectFile.js';
|
|
@@ -17,11 +17,12 @@ export default class TeraFy {
|
|
|
17
17
|
* Various settings to configure behaviour
|
|
18
18
|
*
|
|
19
19
|
* @type {Object}
|
|
20
|
+
* @property {String} session Unique session signature for this instance of TeraFy, used to sign server messages, if falsy `getEntropicString(16)` is used to populate
|
|
20
21
|
* @property {Boolean} devMode Operate in Dev-Mode - i.e. force outer refresh when encountering an existing TeraFy instance
|
|
21
22
|
* @property {Number} verbosity Verbosity level, the higher the more chatty TeraFY will be. Set to zero to disable all `debug()` call output
|
|
22
23
|
* @property {'detect'|'parent'|'child'|'popup'} mode How to communicate with TERA. 'parent' assumes that the parent of the current document is TERA, 'child' spawns an iFrame and uses TERA there, 'detect' tries parent and switches to `modeFallback` if communication fails
|
|
23
24
|
* @property {String} modeFallback Method to use when all method detection fails
|
|
24
|
-
* @property {Object<Object<
|
|
25
|
+
* @property {Object<Object<Function>>} modeOverrides Functions to run when switching to specific modes, these are typically used to augment config. Called as `(config:Object)`
|
|
25
26
|
* @property {Number} modeTimeout How long entities have in 'detect' mode to identify themselves
|
|
26
27
|
* @property {String} siteUrl The TERA URL to connect to
|
|
27
28
|
* @property {String} restrictOrigin URL to restrict communications to
|
|
@@ -31,15 +32,19 @@ export default class TeraFy {
|
|
|
31
32
|
* @property {Array<String|Array<String>>} [debugPaths] List of paths (in either dotted or array notation) to enter debugging mode if a change is detected in dev mode e.g. `{debugPaths: ['foo.bar.baz']}`. This really slows down state writes so should only be used for debugging
|
|
32
33
|
*/
|
|
33
34
|
settings = {
|
|
35
|
+
session: null,
|
|
34
36
|
devMode: true,
|
|
35
37
|
verbosity: 1,
|
|
36
38
|
mode: 'detect',
|
|
37
39
|
modeTimeout: 300,
|
|
38
40
|
modeFallback: 'child', // ENUM: 'child' (use iframes), 'popup' (use popup windows)
|
|
39
41
|
modeOverrides: {
|
|
40
|
-
child
|
|
41
|
-
siteUrl
|
|
42
|
-
|
|
42
|
+
child(config) { // When we're in child mode assume a local dev environment and use the dev.tera-tools.com site instead to work around CORS restrictions
|
|
43
|
+
if (config.siteUrl == 'https://tera-tools.com/embed') { // Only if we're using the default URL...
|
|
44
|
+
config.siteUrl = 'https://dev.tera-tools.com/embed'; // Repoint URL to dev site
|
|
45
|
+
} else { // If we're using some weird upstream allow all origins for postMessage
|
|
46
|
+
config.restrictOrigin = '*'; // Allow all upstream iframes
|
|
47
|
+
}
|
|
43
48
|
},
|
|
44
49
|
},
|
|
45
50
|
siteUrl: 'https://tera-tools.com/embed',
|
|
@@ -312,6 +317,13 @@ export default class TeraFy {
|
|
|
312
317
|
}
|
|
313
318
|
|
|
314
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Transmit a patch to the remote server
|
|
322
|
+
* This function also enters debugging mode if any of the `settings.debugPaths` are operated on
|
|
323
|
+
*
|
|
324
|
+
* @param {Array} patch Patch to apply
|
|
325
|
+
* @returns {Promise} A promise which resolves when the operation has completed
|
|
326
|
+
*/
|
|
315
327
|
applyProjectStatePatch(patch) {
|
|
316
328
|
if (this.settings.devMode && this.settings.debugPaths) {
|
|
317
329
|
if (!Array.isArray(this.settings.debugPaths)) throw new Error('teraFyClient.settings.debugPaths should be either null or an Array<String>');
|
|
@@ -328,7 +340,9 @@ export default class TeraFy {
|
|
|
328
340
|
}
|
|
329
341
|
}
|
|
330
342
|
|
|
331
|
-
return this.rpc('applyProjectStatePatch', patch
|
|
343
|
+
return this.rpc('applyProjectStatePatch', patch, {
|
|
344
|
+
session: this.settings.session,
|
|
345
|
+
});
|
|
332
346
|
}
|
|
333
347
|
|
|
334
348
|
|
|
@@ -373,7 +387,8 @@ export default class TeraFy {
|
|
|
373
387
|
|
|
374
388
|
const context = this;
|
|
375
389
|
return this.init.promise = Promise.resolve()
|
|
376
|
-
.then(()=> this.
|
|
390
|
+
.then(()=> this.settings.session ||= 'tfy-' + this.getEntropicString(16))
|
|
391
|
+
.then(()=> this.debug('INFO', 4, '[0/6] Init', 'Session', this.settings.session, 'against', this.settings.siteUrl))
|
|
377
392
|
.then(()=> { // Init various options for optimized access
|
|
378
393
|
if (!this.settings.devMode) return; // Not in dev mode
|
|
379
394
|
this.settings.debugPaths =
|
|
@@ -390,10 +405,12 @@ export default class TeraFy {
|
|
|
390
405
|
.then(()=> this.detectMode())
|
|
391
406
|
.then(mode => {
|
|
392
407
|
this.debug('INFO', 4, '[1/6] Setting client mode to', mode);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
408
|
+
this.settings.mode = mode;
|
|
409
|
+
|
|
410
|
+
if (this.settings.modeOverrides[mode]) {
|
|
411
|
+
this.debug('INFO', 4, '[1/6] Applying specific config overrides for mode', mode);
|
|
412
|
+
return this.settings.modeOverrides[mode](this.settings);
|
|
413
|
+
}
|
|
397
414
|
})
|
|
398
415
|
.then(()=> this.debug('INFO', 4, '[2/6] Injecting comms + styles + methods'))
|
|
399
416
|
.then(()=> Promise.all([
|
|
@@ -493,7 +510,7 @@ export default class TeraFy {
|
|
|
493
510
|
|
|
494
511
|
case 'parent':
|
|
495
512
|
this.debug('INFO', 2, 'Using TERA window parent');
|
|
496
|
-
|
|
513
|
+
return Promise.resolve();
|
|
497
514
|
|
|
498
515
|
case 'popup':
|
|
499
516
|
this.debug('INFO', 2, 'Injecting TERA site as a popup window');
|
|
@@ -863,6 +880,22 @@ export default class TeraFy {
|
|
|
863
880
|
this.debug('INFO', 2, 'Request focus', {isFocused});
|
|
864
881
|
globalThis.document.body.classList.toggle('tera-fy-focus', isFocused === 'toggle' ? undefined : isFocused);
|
|
865
882
|
}
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Generate random entropic character string in Base64
|
|
888
|
+
*
|
|
889
|
+
* @param {Number} [maxLength=32] Maximum lengh of the genrated string
|
|
890
|
+
* @returns {String}
|
|
891
|
+
*/
|
|
892
|
+
getEntropicString(maxLength = 32) {
|
|
893
|
+
const array = new Uint32Array(4);
|
|
894
|
+
window.crypto.getRandomValues(array);
|
|
895
|
+
return btoa(String.fromCharCode(...new Uint8Array(array.buffer)))
|
|
896
|
+
.replace(/[+/]/g, '') // Remove + and / characters
|
|
897
|
+
.slice(0, maxLength) // Trim
|
|
898
|
+
}
|
|
866
899
|
// }}}
|
|
867
900
|
|
|
868
901
|
// Stub documentation carried over from ./terafy.server.js {{{
|
|
@@ -1341,6 +1374,14 @@ export default class TeraFy {
|
|
|
1341
1374
|
*/
|
|
1342
1375
|
|
|
1343
1376
|
|
|
1377
|
+
/**
|
|
1378
|
+
* Trigger a fatal error, killing the outer TERA site
|
|
1379
|
+
*
|
|
1380
|
+
* @function uiDie
|
|
1381
|
+
* @param {String} [text] Text to display
|
|
1382
|
+
*/
|
|
1383
|
+
|
|
1384
|
+
|
|
1344
1385
|
/**
|
|
1345
1386
|
* Display, update or dispose of windows for long running tasks
|
|
1346
1387
|
* All options are cumulative - i.e. they are merged with other options previously provided
|
package/lib/terafy.server.js
CHANGED
|
@@ -878,12 +878,29 @@ export default class TeraFyServer {
|
|
|
878
878
|
* Apply a computed `just-diff` patch to the current project state
|
|
879
879
|
*
|
|
880
880
|
* @param {Object} patch Patch to apply
|
|
881
|
+
*
|
|
882
|
+
* @param {Object} [options] Additional options to mutate behaviour
|
|
883
|
+
* @param {String} [options.session] The transmitting session, if available. Avoids that session recieving a patch downwards after this one
|
|
884
|
+
*
|
|
881
885
|
* @returns {Promise} A promise which resolves when the operation has completed
|
|
882
886
|
*/
|
|
883
|
-
applyProjectStatePatch(patch) {
|
|
887
|
+
applyProjectStatePatch(patch, options) {
|
|
884
888
|
if (!app.service('$projects').active) throw new Error('No active project to patch');
|
|
885
889
|
this.debug('INFO', 1, 'Applying', patch.length, 'project state patch', patch);
|
|
886
|
-
|
|
890
|
+
|
|
891
|
+
let $projects = app.service('$projects');
|
|
892
|
+
diffApply($projects.active, patch);
|
|
893
|
+
|
|
894
|
+
// Populate information about the last patch
|
|
895
|
+
$projects.active.lastPatch = {
|
|
896
|
+
date: (new Date).toJSON(),
|
|
897
|
+
user: app.service('$auth').user.id,
|
|
898
|
+
...(options?.session && { // Have session info?
|
|
899
|
+
session: options.session,
|
|
900
|
+
}),
|
|
901
|
+
patches: patch.length,
|
|
902
|
+
};
|
|
903
|
+
this.debug('INFO', 3, 'Applied patch to lastPatch status', $projects.active.lastPatch);
|
|
887
904
|
|
|
888
905
|
return Promise.resolve();
|
|
889
906
|
}
|
|
@@ -1504,6 +1521,17 @@ export default class TeraFyServer {
|
|
|
1504
1521
|
}
|
|
1505
1522
|
|
|
1506
1523
|
|
|
1524
|
+
/**
|
|
1525
|
+
* Trigger a fatal error, killing the outer TERA site
|
|
1526
|
+
*
|
|
1527
|
+
* @function uiDie
|
|
1528
|
+
* @param {String} [text] Text to display
|
|
1529
|
+
*/
|
|
1530
|
+
uiDie(text) {
|
|
1531
|
+
window.die(text);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
|
|
1507
1535
|
/**
|
|
1508
1536
|
* Display, update or dispose of windows for long running tasks
|
|
1509
1537
|
* All options are cumulative - i.e. they are merged with other options previously provided
|
package/package.json
CHANGED
package/plugins/vue2.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {cloneDeep, isPlainObject} from 'lodash-es';
|
|
1
|
+
import {cloneDeep, debounce, isPlainObject} from 'lodash-es';
|
|
2
2
|
import TeraFyPluginBase from './base.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -45,7 +45,9 @@ export default class TeraFyPluginVue2 extends TeraFyPluginBase {
|
|
|
45
45
|
* @param {String} [options.componentKey] Key within the component to attach the state. Defaults to a random string
|
|
46
46
|
* @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
|
|
47
47
|
* @param {String|Boolean} [options.bindKey='project'] If set, creates the binding also as the specified key within the main Tera object, if falsy just returns the observable
|
|
48
|
+
* @param {Boolean} [options.read=true] Allow remote reactivity - update the local state when the server changes
|
|
48
49
|
* @param {Boolean} [options.write=true] Allow local reactivity to writes - send these to the server
|
|
50
|
+
* @param {Object} [options.throttle] Lodash debounce options + `wait` key used to throttle all writes, set to falsy to disable
|
|
49
51
|
*
|
|
50
52
|
* @returns {Promie<VueObservable<Object>>} A Vue.Observable object representing the project state
|
|
51
53
|
*/
|
|
@@ -54,7 +56,14 @@ export default class TeraFyPluginVue2 extends TeraFyPluginBase {
|
|
|
54
56
|
component: null,
|
|
55
57
|
componentKey: null,
|
|
56
58
|
autoRequire: true,
|
|
59
|
+
read: true,
|
|
57
60
|
write: true,
|
|
61
|
+
throttle: {
|
|
62
|
+
wait: 200,
|
|
63
|
+
maxWait: 2000,
|
|
64
|
+
leading: false,
|
|
65
|
+
trailing: true,
|
|
66
|
+
},
|
|
58
67
|
...options,
|
|
59
68
|
};
|
|
60
69
|
|
|
@@ -89,11 +98,17 @@ export default class TeraFyPluginVue2 extends TeraFyPluginBase {
|
|
|
89
98
|
settings.component[settings.componentKey] = stateObservable;
|
|
90
99
|
|
|
91
100
|
// Watch for remote changes and update
|
|
92
|
-
let skipUpdate = 0; // How many subsequent WRITE operations to ignore (set when reading)
|
|
93
101
|
if (settings.read) {
|
|
94
102
|
this.events.on(`update:projects/${stateObservable.id}`, newState => {
|
|
95
|
-
|
|
103
|
+
if (
|
|
104
|
+
newState?.lastPatch?.session // Last state change had a session worth noting
|
|
105
|
+
&& newState.lastPatch.session == this.settings.session // The last state update was made FROM INSIDE THE BUILDING! BUWHAHAHA!
|
|
106
|
+
)
|
|
107
|
+
return; // Discard it, we don't care
|
|
108
|
+
|
|
109
|
+
// Everything else - patch the remote state locally
|
|
96
110
|
this.debug('INFO', 5, 'Update Vue2 Remote->Local', {new: newState, old: stateObservable});
|
|
111
|
+
|
|
97
112
|
this.merge(stateObservable, newState);
|
|
98
113
|
});
|
|
99
114
|
}
|
|
@@ -107,19 +122,19 @@ export default class TeraFyPluginVue2 extends TeraFyPluginBase {
|
|
|
107
122
|
// snapshot
|
|
108
123
|
let oldVal = cloneDeep(snapshot);
|
|
109
124
|
|
|
125
|
+
// Function to handle the state update (can be debounced)
|
|
126
|
+
let watchHandle = newVal => {
|
|
127
|
+
this.debug('INFO', 5, 'Update Vue2 Local->Remote', {new: newVal, old: oldVal});
|
|
128
|
+
this.createProjectStatePatch(newVal, oldVal);
|
|
129
|
+
oldVal = cloneDeep(snapshot);
|
|
130
|
+
};
|
|
131
|
+
|
|
110
132
|
settings.component.$watch(
|
|
111
|
-
settings.componentKey,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
this.debug('INFO', 5, 'Update Vue2 Local->Remote', {new: newVal, old: oldVal});
|
|
119
|
-
this.createProjectStatePatch(newVal, oldVal);
|
|
120
|
-
oldVal = cloneDeep(snapshot);
|
|
121
|
-
},
|
|
122
|
-
{
|
|
133
|
+
settings.componentKey, // State to watch
|
|
134
|
+
settings.throttle // Pointer to watchHandle which takes the new state (optionally throttled)
|
|
135
|
+
? debounce(watchHandle, settings.throttle.wait, settings.throttle)
|
|
136
|
+
: watchHandle,
|
|
137
|
+
{ // Watch options
|
|
123
138
|
deep: true,
|
|
124
139
|
},
|
|
125
140
|
);
|
package/plugins/vue3.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {cloneDeep} from 'lodash-es';
|
|
1
|
+
import {cloneDeep, debounce} from 'lodash-es';
|
|
2
2
|
import TeraFyPluginBase from './base.js';
|
|
3
3
|
import {reactive, watch} from 'vue';
|
|
4
4
|
|
|
@@ -33,6 +33,7 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
33
33
|
* @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
|
|
34
34
|
* @param {Boolean} [options.read=true] Allow remote reactivity - update the local state when the server changes
|
|
35
35
|
* @param {Boolean} [options.write=true] Allow local reactivity to writes - send these to the server
|
|
36
|
+
* @param {Object} [options.throttle] Lodash debounce options + `wait` key used to throttle all writes, set to falsy to disable
|
|
36
37
|
*
|
|
37
38
|
* @returns {Promie<Reactive<Object>>} A reactive object representing the project state
|
|
38
39
|
*/
|
|
@@ -41,6 +42,12 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
41
42
|
autoRequire: true,
|
|
42
43
|
read: true,
|
|
43
44
|
write: true,
|
|
45
|
+
throttle: {
|
|
46
|
+
wait: 200,
|
|
47
|
+
maxWait: 2000,
|
|
48
|
+
leading: false,
|
|
49
|
+
trailing: true,
|
|
50
|
+
},
|
|
44
51
|
...options,
|
|
45
52
|
};
|
|
46
53
|
|
|
@@ -53,10 +60,15 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
53
60
|
let stateReactive = reactive(snapshot);
|
|
54
61
|
|
|
55
62
|
// Watch for remote changes and update
|
|
56
|
-
let skipUpdate = 0; // How many subsequent WRITE operations to ignore (set when reading)
|
|
57
63
|
if (settings.read) {
|
|
58
64
|
this.events.on(`update:projects/${stateReactive.id}`, newState => {
|
|
59
|
-
|
|
65
|
+
if (
|
|
66
|
+
newState?.lastPatch?.session // Last state change had a session worth noting
|
|
67
|
+
&& newState.lastPatch.session == this.settings.session // The last state update was made FROM INSIDE THE BUILDING! BUWHAHAHA!
|
|
68
|
+
)
|
|
69
|
+
return; // Discard it, we don't care
|
|
70
|
+
|
|
71
|
+
// Everything else - patch the remote state locally
|
|
60
72
|
Object.assign(stateReactive, newState);
|
|
61
73
|
});
|
|
62
74
|
}
|
|
@@ -69,18 +81,18 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
69
81
|
// snapshot
|
|
70
82
|
let oldVal = cloneDeep(snapshot);
|
|
71
83
|
|
|
84
|
+
// Function to handle the state update (can be debounced)
|
|
85
|
+
let watchHandle = newVal => {
|
|
86
|
+
this.createProjectStatePatch(newVal, oldVal);
|
|
87
|
+
oldVal = cloneDeep(newVal); // Update old state the the last seen value
|
|
88
|
+
};
|
|
89
|
+
|
|
72
90
|
watch(
|
|
73
|
-
stateReactive,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.createProjectStatePatch(newVal, oldVal);
|
|
81
|
-
oldVal = cloneDeep(newVal); // Update old state the the last seen value
|
|
82
|
-
},
|
|
83
|
-
{
|
|
91
|
+
stateReactive, // State to watch
|
|
92
|
+
settings.throttle // Pointer to watchHandle which takes the new state (optionally throttled)
|
|
93
|
+
? debounce(watchHandle, settings.throttle.wait, settings.throttle)
|
|
94
|
+
: watchHandle,
|
|
95
|
+
{ // Watch options
|
|
84
96
|
deep: true,
|
|
85
97
|
},
|
|
86
98
|
);
|