@iebh/tera-fy 1.0.4 → 1.0.6
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/README.md +25 -1
- package/lib/terafy.client.js +92 -41
- package/lib/terafy.server.js +33 -14
- package/package.json +10 -5
- package/{lib/plugins → plugins}/vue.js +43 -1
- /package/{lib/plugins → plugins}/base.js +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
TERA-fy
|
|
2
2
|
=======
|
|
3
|
-
TERA website worker.
|
|
3
|
+
TERA website worker, intended to be embedded with TERA tools.
|
|
4
4
|
|
|
5
5
|
* [API workbench](https://iebh.github.io/TERA-fy/)
|
|
6
6
|
* [TERA-fy Class API](./api.md)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
TERA-fy is a component intended to be dropped into a sub-site / tool used with in the main [TERA](https://tera-tools.com) project. It provides various functionality like data sync with the parent TERA instance.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Quick Start
|
|
13
|
+
-----------
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import TeraFy from '@iebh/tera-fy';
|
|
17
|
+
import TerafyVue from '@iebh/tera-fy/plugins/vue';
|
|
18
|
+
let terafy = new TeraFy()
|
|
19
|
+
.set('devMode', true) // Set this option to see debugging messages
|
|
20
|
+
.use(TerafyVue); // Add the Vue plugin
|
|
21
|
+
|
|
22
|
+
// Initialize everything
|
|
23
|
+
await terafy.init();
|
|
24
|
+
|
|
25
|
+
// Require that the active session has a project selected
|
|
26
|
+
await terafy.requireProject();
|
|
27
|
+
|
|
28
|
+
// Go fetch the state of the active project
|
|
29
|
+
let projectState = await terafy.getProjectState(); //= Object representing the active project
|
|
30
|
+
```
|
package/lib/terafy.client.js
CHANGED
|
@@ -20,7 +20,7 @@ export default class TeraFy {
|
|
|
20
20
|
*/
|
|
21
21
|
settings = {
|
|
22
22
|
devMode: true,
|
|
23
|
-
siteUrl: '
|
|
23
|
+
siteUrl: 'https://tera-tools.com/embed',
|
|
24
24
|
restrictOrigin: '*', // DEBUG: Need to restrict this to TERA site
|
|
25
25
|
};
|
|
26
26
|
|
|
@@ -105,14 +105,19 @@ export default class TeraFy {
|
|
|
105
105
|
* @param {Object} message Message object to send
|
|
106
106
|
*/
|
|
107
107
|
sendRaw(message) {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
let payload;
|
|
109
|
+
try {
|
|
110
|
+
payload = {
|
|
110
111
|
TERA: 1,
|
|
111
112
|
id: message.id || nanoid(),
|
|
112
113
|
...cloneDeep(message), // Need to clone to resolve promise nasties
|
|
113
|
-
}
|
|
114
|
-
this.settings.restrictOrigin
|
|
115
|
-
)
|
|
114
|
+
};
|
|
115
|
+
this.dom.iframe.contentWindow.postMessage(payload, this.settings.restrictOrigin);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
this.debug('ERROR', 'Message compose client->server:', e);
|
|
118
|
+
this.debug('ERROR', 'Attempted to dispatch payload client->server', payload);
|
|
119
|
+
throw e;
|
|
120
|
+
}
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
|
|
@@ -181,54 +186,46 @@ export default class TeraFy {
|
|
|
181
186
|
|
|
182
187
|
// }}}
|
|
183
188
|
|
|
184
|
-
// Init - constructor(),
|
|
189
|
+
// Init - constructor(), init(), inject*() {{{
|
|
185
190
|
|
|
186
191
|
/**
|
|
187
192
|
* Setup the TERA-fy client singleton
|
|
188
193
|
*
|
|
189
|
-
* @param {Object} [options] Additional options to merge into `settings`
|
|
194
|
+
* @param {Object} [options] Additional options to merge into `settings` via `set`
|
|
190
195
|
*/
|
|
191
196
|
constructor(options) {
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Set or toggle devMode
|
|
198
|
-
*
|
|
199
|
-
* @param {String|Boolean} [devModeEnabled='toggle'] Optional boolean to force dev mode
|
|
200
|
-
*
|
|
201
|
-
* @returns {TeraFy} This chainable terafy instance
|
|
202
|
-
*/
|
|
203
|
-
toggleDevMode(devModeEnabled = 'toggle') {
|
|
204
|
-
this.settings.devMode = devModeEnabled === 'toggle'
|
|
205
|
-
? !this.settings.devMode
|
|
206
|
-
: devModeEnabled;
|
|
207
|
-
|
|
208
|
-
this.dom.el.classList.toggle('dev-mode', this.settings.devMode);
|
|
209
|
-
return this;
|
|
197
|
+
if (options) this.set(options);
|
|
210
198
|
}
|
|
211
199
|
|
|
212
200
|
|
|
213
201
|
/**
|
|
214
202
|
* Initalize the TERA client singleton
|
|
203
|
+
*
|
|
204
|
+
* @returns {Promise<TeraFy>} An eventual promise which will resovle with this terafy instance
|
|
215
205
|
*/
|
|
216
206
|
init() {
|
|
217
207
|
window.addEventListener('message', this.acceptMessage.bind(this));
|
|
218
208
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
.
|
|
224
|
-
|
|
209
|
+
return Promise.all([
|
|
210
|
+
// Init core functions async
|
|
211
|
+
this.injectComms(),
|
|
212
|
+
this.injectStylesheet(),
|
|
213
|
+
this.injectMethods(),
|
|
214
|
+
|
|
215
|
+
// Init all plugins
|
|
216
|
+
...this.plugins
|
|
217
|
+
.map(plugin => plugin.init()),
|
|
218
|
+
])
|
|
219
|
+
.then(()=> this)
|
|
225
220
|
}
|
|
226
221
|
|
|
227
222
|
|
|
228
223
|
/**
|
|
229
224
|
* Find an existing active TERA server OR initalize one
|
|
225
|
+
*
|
|
226
|
+
* @returns {Promise} A promise which will resolve when the loading has completed and we have found a parent TERA instance or initiallized a child
|
|
230
227
|
*/
|
|
231
|
-
|
|
228
|
+
injectComms() { return new Promise(resolve => {
|
|
232
229
|
this.dom.el = document.createElement('div')
|
|
233
230
|
this.dom.el.id = 'tera-fy';
|
|
234
231
|
this.dom.el.classList.toggle('dev-mode', this.settings.devMode);
|
|
@@ -240,12 +237,13 @@ export default class TeraFy {
|
|
|
240
237
|
this.dom.iframe.setAttribute('sandbox', 'allow-downloads allow-scripts allow-same-origin');
|
|
241
238
|
this.dom.iframe.addEventListener('load', ()=> {
|
|
242
239
|
this.debug('TERA EMBED FRAME READY');
|
|
240
|
+
resolve();
|
|
243
241
|
});
|
|
244
242
|
|
|
245
243
|
// Start document load sequence + append to DOM
|
|
246
244
|
this.dom.iframe.src = this.settings.siteUrl;
|
|
247
245
|
this.dom.el.append(this.dom.iframe);
|
|
248
|
-
}
|
|
246
|
+
})}
|
|
249
247
|
|
|
250
248
|
|
|
251
249
|
/**
|
|
@@ -306,20 +304,33 @@ export default class TeraFy {
|
|
|
306
304
|
this.methods.forEach(method =>
|
|
307
305
|
this[method] = this.rpc.bind(this, method)
|
|
308
306
|
);
|
|
307
|
+
this.debug('Remote methods installed:', this.methods);
|
|
309
308
|
}
|
|
310
309
|
// }}}
|
|
311
310
|
|
|
312
|
-
// Utility - debug(), use(),
|
|
311
|
+
// Utility - debug(), use(), mixin(), toggleDevMode(), toggleFocus() {{{
|
|
313
312
|
|
|
314
313
|
/**
|
|
315
314
|
* Debugging output function
|
|
316
315
|
* This function will only act if `settings.devMode` is truthy
|
|
317
316
|
*
|
|
317
|
+
* @param {'INFO'|'LOG'|'WARN'|'ERROR'} [status] Optional prefixing level to mark the message as. 'WARN' and 'ERROR' will always show reguardless of devMode being enabled
|
|
318
318
|
* @param {String} [msg...] Output to show
|
|
319
319
|
*/
|
|
320
320
|
debug(...msg) {
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
let method = 'log';
|
|
322
|
+
// Argument mangling for prefixing method {{{
|
|
323
|
+
if (typeof msg[0] == 'string' && ['INFO', 'LOG', 'WARN', 'ERROR'].includes(msg[0])) {
|
|
324
|
+
method = msg.shift().toLowerCase();
|
|
325
|
+
}
|
|
326
|
+
// }}}
|
|
327
|
+
|
|
328
|
+
if (
|
|
329
|
+
['INFO', 'LOG'].includes(method)
|
|
330
|
+
&& !this.settings.devMode
|
|
331
|
+
) return;
|
|
332
|
+
|
|
333
|
+
console[method](
|
|
323
334
|
'%c[TERA-FY CLIENT]',
|
|
324
335
|
'font-weight: bold; color: #ff5722;',
|
|
325
336
|
...msg,
|
|
@@ -359,24 +370,64 @@ export default class TeraFy {
|
|
|
359
370
|
if (typeof mod != 'function') throw new Error('Expected use() call to be provided with a class initalizer');
|
|
360
371
|
|
|
361
372
|
let singleton = new mod(this, options);
|
|
373
|
+
this.mixin(this, singleton);
|
|
374
|
+
|
|
362
375
|
this.plugins.push(singleton);
|
|
363
376
|
return this;
|
|
364
377
|
}
|
|
365
378
|
|
|
366
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Internal function used by use() to merge an external declared singleton against this object
|
|
382
|
+
*
|
|
383
|
+
* @param {Object} target Initalied class instance to extend
|
|
384
|
+
* @param {Object} source Initalized source object to extend from
|
|
385
|
+
*/
|
|
386
|
+
mixin(target, source) {
|
|
387
|
+
Object.getOwnPropertyNames(Object.getPrototypeOf(source))
|
|
388
|
+
.filter(prop => !['constructor', 'prototype', 'name'].includes(prop))
|
|
389
|
+
.forEach((prop) => {
|
|
390
|
+
console.log('Merge method', prop);
|
|
391
|
+
Object.defineProperty(
|
|
392
|
+
target,
|
|
393
|
+
prop,
|
|
394
|
+
{
|
|
395
|
+
value: source[prop].bind(target),
|
|
396
|
+
enumerable: false,
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Set or toggle devMode
|
|
405
|
+
*
|
|
406
|
+
* @param {String|Boolean} [devModeEnabled='toggle'] Optional boolean to force dev mode
|
|
407
|
+
*
|
|
408
|
+
* @returns {TeraFy} This chainable terafy instance
|
|
409
|
+
*/
|
|
410
|
+
toggleDevMode(devModeEnabled = 'toggle') {
|
|
411
|
+
this.settings.devMode = devModeEnabled === 'toggle'
|
|
412
|
+
? !this.settings.devMode
|
|
413
|
+
: devModeEnabled;
|
|
414
|
+
|
|
415
|
+
if (this.dom.el) // Have we actually set up yet?
|
|
416
|
+
this.dom.el.classList.toggle('dev-mode', this.settings.devMode);
|
|
417
|
+
|
|
418
|
+
return this;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
|
|
367
422
|
/**
|
|
368
423
|
* Fit the nested TERA server to a full-screen
|
|
369
424
|
* This is usually because the server component wants to perform some user activity like calling $prompt
|
|
370
425
|
*
|
|
371
426
|
* @param {String|Boolean} [isFocused='toggle'] Whether to fullscreen the embedded component
|
|
372
|
-
*
|
|
373
|
-
* @returns {TeraFy} This chainable terafy instance
|
|
374
427
|
*/
|
|
375
428
|
toggleFocus(isFocused = 'toggle') {
|
|
376
429
|
this.debug('Request focus', {isFocused});
|
|
377
430
|
globalThis.document.body.classList.toggle('tera-fy-focus', isFocused === 'toggle' ? undefined : isFocused);
|
|
378
|
-
return this;
|
|
379
431
|
}
|
|
380
|
-
|
|
381
432
|
// }}}
|
|
382
433
|
}
|
package/lib/terafy.server.js
CHANGED
|
@@ -31,18 +31,22 @@ export default class TeraFyServer {
|
|
|
31
31
|
* @returns {Object} A context, which is this instance extended with additional properties
|
|
32
32
|
*/
|
|
33
33
|
createContext(e) {
|
|
34
|
-
// Rather ugly shallow-copy-of
|
|
34
|
+
// Rather ugly shallow-copy-of instancr hack from https://stackoverflow.com/a/44782052/1295040
|
|
35
35
|
return Object.assign(Object.create(Object.getPrototypeOf(this)), this, {
|
|
36
36
|
messageEvent: e,
|
|
37
37
|
sendRaw(message) { // Override sendRaw because we can't do this inline for security reasons
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
38
|
+
let payload;
|
|
39
|
+
try {
|
|
40
|
+
payload = {
|
|
41
41
|
TERA: 1,
|
|
42
42
|
...cloneDeep(message), // Need to clone to resolve promise nasties
|
|
43
|
-
}
|
|
44
|
-
this.settings.restrictOrigin
|
|
45
|
-
)
|
|
43
|
+
};
|
|
44
|
+
e.source.postMessage(payload, this.settings.restrictOrigin);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
this.debug('ERROR', 'Message compose/reply via server->cient:', e);
|
|
47
|
+
this.debug('ERROR', 'Attempted to dispatch payload server(via reply)->client', payload);
|
|
48
|
+
throw e;
|
|
49
|
+
}
|
|
46
50
|
},
|
|
47
51
|
});
|
|
48
52
|
}
|
|
@@ -124,14 +128,18 @@ export default class TeraFyServer {
|
|
|
124
128
|
* @param {Object} message Message object to send
|
|
125
129
|
*/
|
|
126
130
|
sendRaw(message) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{
|
|
131
|
+
let payload;
|
|
132
|
+
try {
|
|
133
|
+
payload = {
|
|
130
134
|
TERA: 1,
|
|
131
135
|
...cloneDeep(message), // Need to clone to resolve promise nasties
|
|
132
|
-
}
|
|
133
|
-
this.settings.restrictOrigin
|
|
134
|
-
)
|
|
136
|
+
};
|
|
137
|
+
globalThis.parent.postMessage(payload, this.settings.restrictOrigin);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
this.debug('ERROR', 'Attempted to dispatch payload server->client', payload);
|
|
140
|
+
this.debug('ERROR', 'Message compose server->client:', e);
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
|
|
@@ -497,7 +505,18 @@ export default class TeraFyServer {
|
|
|
497
505
|
* @param {String} [msg...] Output to show
|
|
498
506
|
*/
|
|
499
507
|
debug(...msg) {
|
|
500
|
-
|
|
508
|
+
let method = 'log';
|
|
509
|
+
// Argument mangling for prefixing method {{{
|
|
510
|
+
if (typeof msg[0] == 'string' && ['INFO', 'LOG', 'WARN', 'ERROR'].includes(msg[0])) {
|
|
511
|
+
method = msg.shift().toLowerCase();
|
|
512
|
+
}
|
|
513
|
+
// }}}
|
|
514
|
+
|
|
515
|
+
if (
|
|
516
|
+
['INFO', 'LOG'].includes(method)
|
|
517
|
+
&& !this.settings.devMode
|
|
518
|
+
) return;
|
|
519
|
+
|
|
501
520
|
console.log(
|
|
502
521
|
'%c[TERA-FY SERVER]',
|
|
503
522
|
'font-weight: bold; color: #4d659c;',
|
package/package.json
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iebh/tera-fy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "TERA website worker",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "esbuild --platform=browser --format=esm --bundle lib/terafy.client.js --outfile=dist/terafy.js --minify --sourcemap --serve --servedir=.",
|
|
7
7
|
"build": "concurrently 'npm:build:*'",
|
|
8
8
|
"build:client": "esbuild --platform=browser --format=esm --bundle lib/terafy.client.js --outfile=dist/terafy.js --minify --sourcemap",
|
|
9
|
-
"build:docs": "jsdoc2md --files lib/terafy.*.js
|
|
10
|
-
"lint": "eslint
|
|
9
|
+
"build:docs": "jsdoc2md --files lib/terafy.*.js plugins/*.js >api.md",
|
|
10
|
+
"lint": "eslint ."
|
|
11
11
|
},
|
|
12
12
|
"type": "module",
|
|
13
13
|
"imports": {
|
|
14
14
|
"#terafy": "./lib/terafy.client.js"
|
|
15
15
|
},
|
|
16
16
|
"exports": {
|
|
17
|
-
".":
|
|
17
|
+
".": {
|
|
18
|
+
"browser": "./lib/terafy.client.js",
|
|
19
|
+
"import": "./lib/terafy.client.js",
|
|
20
|
+
"require": "./dist/terafy.js",
|
|
21
|
+
"default": "./dist/terafy.js"
|
|
22
|
+
},
|
|
18
23
|
"./server": "./lib/terafy.server.js",
|
|
19
|
-
"./plugins/*": "./
|
|
24
|
+
"./plugins/*": "./plugins/*.js"
|
|
20
25
|
},
|
|
21
26
|
"repository": {
|
|
22
27
|
"type": "git",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import TeraFyPluginBase from './base.js';
|
|
2
|
-
import diff from 'just-diff';
|
|
2
|
+
import {diff} from 'just-diff';
|
|
3
3
|
import {reactive, watch} from 'vue';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -62,4 +62,46 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
62
62
|
})
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Provide a Vue@3 compatible plugin
|
|
68
|
+
*/
|
|
69
|
+
vuePlugin() {
|
|
70
|
+
let $tera = this;
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Install into Vue as a generic Vue@3 plugin
|
|
76
|
+
*
|
|
77
|
+
* @param {Object} [options] Additional options to mutate behaviour
|
|
78
|
+
* @param {String} [options.globalName='$tera'] Globa property to allocate this service as
|
|
79
|
+
* @param {Objecct} [options.bindOptions] Options passed to `bindProjectState()`
|
|
80
|
+
*
|
|
81
|
+
* @returns {VuePlugin} A plugin matching the Vue@3 spec
|
|
82
|
+
*/
|
|
83
|
+
install(app, options) {
|
|
84
|
+
let settings = {
|
|
85
|
+
globalName: '$tera',
|
|
86
|
+
stateOptions: {
|
|
87
|
+
autoRequire: true,
|
|
88
|
+
write: true,
|
|
89
|
+
},
|
|
90
|
+
...options,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Make this module available globally
|
|
94
|
+
app.config.globalProperties[settings.globalName] = $tera;
|
|
95
|
+
|
|
96
|
+
// Bind $tera.state to the active project
|
|
97
|
+
// TODO: context.bindProjectState(settings.stateOptions),
|
|
98
|
+
$tera.state = {
|
|
99
|
+
id: 'TERAPROJ',
|
|
100
|
+
name: 'A fake project',
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
65
107
|
}
|
|
File without changes
|