@iebh/tera-fy 1.0.5 → 1.0.7
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 +64 -38
- package/lib/terafy.server.js +36 -16
- package/package.json +2 -2
- package/plugins/vue.js +60 -6
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
|
@@ -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,7 +186,7 @@ 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
|
|
@@ -193,42 +198,34 @@ export default class TeraFy {
|
|
|
193
198
|
}
|
|
194
199
|
|
|
195
200
|
|
|
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;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
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);
|
|
@@ -239,13 +236,14 @@ export default class TeraFy {
|
|
|
239
236
|
// Queue up event chain when document loads
|
|
240
237
|
this.dom.iframe.setAttribute('sandbox', 'allow-downloads allow-scripts allow-same-origin');
|
|
241
238
|
this.dom.iframe.addEventListener('load', ()=> {
|
|
242
|
-
this.debug('
|
|
239
|
+
this.debug('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
|
/**
|
|
@@ -309,17 +307,29 @@ export default class TeraFy {
|
|
|
309
307
|
}
|
|
310
308
|
// }}}
|
|
311
309
|
|
|
312
|
-
// Utility - debug(), use(), mixin(),
|
|
310
|
+
// Utility - debug(), use(), mixin(), toggleDevMode(), toggleFocus() {{{
|
|
313
311
|
|
|
314
312
|
/**
|
|
315
313
|
* Debugging output function
|
|
316
314
|
* This function will only act if `settings.devMode` is truthy
|
|
317
315
|
*
|
|
316
|
+
* @param {'VERBOSE'|'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
317
|
* @param {String} [msg...] Output to show
|
|
319
318
|
*/
|
|
320
319
|
debug(...msg) {
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
let method = 'log';
|
|
321
|
+
// Argument mangling for prefixing method {{{
|
|
322
|
+
if (typeof msg[0] == 'string' && ['INFO', 'LOG', 'WARN', 'ERROR'].includes(msg[0])) {
|
|
323
|
+
method = msg.shift().toLowerCase();
|
|
324
|
+
}
|
|
325
|
+
// }}}
|
|
326
|
+
|
|
327
|
+
if (
|
|
328
|
+
['INFO', 'LOG'].includes(method)
|
|
329
|
+
&& !this.settings.devMode
|
|
330
|
+
) return;
|
|
331
|
+
|
|
332
|
+
console[method](
|
|
323
333
|
'%c[TERA-FY CLIENT]',
|
|
324
334
|
'font-weight: bold; color: #ff5722;',
|
|
325
335
|
...msg,
|
|
@@ -389,18 +399,34 @@ export default class TeraFy {
|
|
|
389
399
|
}
|
|
390
400
|
|
|
391
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Set or toggle devMode
|
|
404
|
+
*
|
|
405
|
+
* @param {String|Boolean} [devModeEnabled='toggle'] Optional boolean to force dev mode
|
|
406
|
+
*
|
|
407
|
+
* @returns {TeraFy} This chainable terafy instance
|
|
408
|
+
*/
|
|
409
|
+
toggleDevMode(devModeEnabled = 'toggle') {
|
|
410
|
+
this.settings.devMode = devModeEnabled === 'toggle'
|
|
411
|
+
? !this.settings.devMode
|
|
412
|
+
: devModeEnabled;
|
|
413
|
+
|
|
414
|
+
if (this.dom.el) // Have we actually set up yet?
|
|
415
|
+
this.dom.el.classList.toggle('dev-mode', this.settings.devMode);
|
|
416
|
+
|
|
417
|
+
return this;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
|
|
392
421
|
/**
|
|
393
422
|
* Fit the nested TERA server to a full-screen
|
|
394
423
|
* This is usually because the server component wants to perform some user activity like calling $prompt
|
|
395
424
|
*
|
|
396
425
|
* @param {String|Boolean} [isFocused='toggle'] Whether to fullscreen the embedded component
|
|
397
|
-
*
|
|
398
|
-
* @returns {TeraFy} This chainable terafy instance
|
|
399
426
|
*/
|
|
400
427
|
toggleFocus(isFocused = 'toggle') {
|
|
401
428
|
this.debug('Request focus', {isFocused});
|
|
402
429
|
globalThis.document.body.classList.toggle('tera-fy-focus', isFocused === 'toggle' ? undefined : isFocused);
|
|
403
|
-
return this;
|
|
404
430
|
}
|
|
405
431
|
// }}}
|
|
406
432
|
}
|
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,19 @@ 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.
|
|
134
|
-
|
|
136
|
+
};
|
|
137
|
+
this.debug('INFO', 'Parent reply', message, '<=>', payload);
|
|
138
|
+
globalThis.parent.postMessage(payload, this.settings.restrictOrigin);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
this.debug('ERROR', 'Attempted to dispatch payload server->client', payload);
|
|
141
|
+
this.debug('ERROR', 'Message compose server->client:', e);
|
|
142
|
+
}
|
|
143
|
+
|
|
135
144
|
}
|
|
136
145
|
|
|
137
146
|
|
|
@@ -161,10 +170,10 @@ export default class TeraFyServer {
|
|
|
161
170
|
throw new Error('Unknown message format');
|
|
162
171
|
}
|
|
163
172
|
})
|
|
164
|
-
.then(
|
|
173
|
+
.then(response => this.sendRaw({
|
|
165
174
|
id: message.id,
|
|
166
175
|
action: 'response',
|
|
167
|
-
response
|
|
176
|
+
response,
|
|
168
177
|
}))
|
|
169
178
|
.catch(e => {
|
|
170
179
|
console.warn(`TERA-FY server threw on RPC:${message.method}:`, e);
|
|
@@ -497,7 +506,18 @@ export default class TeraFyServer {
|
|
|
497
506
|
* @param {String} [msg...] Output to show
|
|
498
507
|
*/
|
|
499
508
|
debug(...msg) {
|
|
500
|
-
|
|
509
|
+
let method = 'log';
|
|
510
|
+
// Argument mangling for prefixing method {{{
|
|
511
|
+
if (typeof msg[0] == 'string' && ['INFO', 'LOG', 'WARN', 'ERROR'].includes(msg[0])) {
|
|
512
|
+
method = msg.shift().toLowerCase();
|
|
513
|
+
}
|
|
514
|
+
// }}}
|
|
515
|
+
|
|
516
|
+
if (
|
|
517
|
+
['INFO', 'LOG'].includes(method)
|
|
518
|
+
&& !this.settings.devMode
|
|
519
|
+
) return;
|
|
520
|
+
|
|
501
521
|
console.log(
|
|
502
522
|
'%c[TERA-FY SERVER]',
|
|
503
523
|
'font-weight: bold; color: #4d659c;',
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iebh/tera-fy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
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
|
|
9
|
+
"build:docs": "jsdoc2md --files lib/terafy.*.js plugins/*.js >api.md",
|
|
10
10
|
"lint": "eslint ."
|
|
11
11
|
},
|
|
12
12
|
"type": "module",
|
package/plugins/vue.js
CHANGED
|
@@ -20,7 +20,7 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
20
20
|
* @param {Boolean} [options.write=true] Allow local reactivity to writes - send these to the server
|
|
21
21
|
* @param {Array<String>} Paths to subscribe to e.g. ['/users/'],
|
|
22
22
|
*
|
|
23
|
-
* @returns {
|
|
23
|
+
* @returns {Promie<Reactive<Object>>} A reactive object representing the project state
|
|
24
24
|
*/
|
|
25
25
|
bindProjectState(options) {
|
|
26
26
|
let settings = {
|
|
@@ -36,6 +36,8 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
36
36
|
paths: settings.paths,
|
|
37
37
|
}))
|
|
38
38
|
.then(snapshot => {
|
|
39
|
+
this.debug('Got project snapshot', snapshot);
|
|
40
|
+
|
|
39
41
|
// Create initial reactive
|
|
40
42
|
let stateReactive = reactive(snapshot);
|
|
41
43
|
|
|
@@ -63,11 +65,45 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
|
|
68
|
+
/**
|
|
69
|
+
* List of available projects for the current session
|
|
70
|
+
* @type {VueReactive<Array<Object>>}
|
|
71
|
+
*/
|
|
72
|
+
projects = reactive([]);
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The bound, reactive state of a Vue project
|
|
77
|
+
* When loaded this represents the state of a project as an object
|
|
78
|
+
* @type {Object}
|
|
79
|
+
*/
|
|
80
|
+
state = null;
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Promise used when binding to state
|
|
85
|
+
* @type {Promise}
|
|
86
|
+
*/
|
|
87
|
+
statePromisable = null;
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Utility function which returns an awaitable promise when the state is loading or being refreshed
|
|
92
|
+
* This is used in place of `statePromisable` as it has a slightly more logical syntax as a function
|
|
93
|
+
*
|
|
94
|
+
* @example Await the state loading
|
|
95
|
+
* await $tera.statePromise();
|
|
96
|
+
*/
|
|
97
|
+
statePromise() {
|
|
98
|
+
return this.statePromisable;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
66
102
|
/**
|
|
67
103
|
* Provide a Vue@3 compatible plugin
|
|
68
104
|
*/
|
|
69
105
|
vuePlugin() {
|
|
70
|
-
let
|
|
106
|
+
let $tera = this;
|
|
71
107
|
|
|
72
108
|
return {
|
|
73
109
|
|
|
@@ -90,10 +126,28 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
90
126
|
...options,
|
|
91
127
|
};
|
|
92
128
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
129
|
+
// Bind $tera.state to the active project
|
|
130
|
+
// Initialize state to null
|
|
131
|
+
$tera.state = null;
|
|
132
|
+
|
|
133
|
+
// $tera.statePromisable becomes the promise we are waiting on to resolve
|
|
134
|
+
$tera.statePromisable = Promise.all([
|
|
135
|
+
|
|
136
|
+
// Bind available project and wait on it
|
|
137
|
+
$tera.bindProjectState(settings.stateOptions)
|
|
138
|
+
.then(state => $tera.state = state)
|
|
139
|
+
.then(()=> $tera.debug('INFO', 'Loaded project state', $tera.state)),
|
|
140
|
+
|
|
141
|
+
// Fetch available projects
|
|
142
|
+
// TODO: It would be nice if this was responsive to remote changes
|
|
143
|
+
$tera.getProjects()
|
|
144
|
+
.then(projects => $tera.projects = projects)
|
|
145
|
+
.then(()=> $tera.debug('INFO', 'Loaded projects', $tera.list)),
|
|
146
|
+
])
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
// Make this module available globally
|
|
150
|
+
app.config.globalProperties[settings.globalName] = $tera;
|
|
97
151
|
},
|
|
98
152
|
|
|
99
153
|
};
|