@iebh/tera-fy 1.0.7 → 1.0.8
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/lib/terafy.client.js +139 -75
- package/lib/terafy.server.js +13 -3
- package/package.json +1 -1
- package/plugins/vue.js +16 -14
package/lib/terafy.client.js
CHANGED
|
@@ -15,13 +15,17 @@ export default class TeraFy {
|
|
|
15
15
|
*
|
|
16
16
|
* @type {Object}
|
|
17
17
|
* @property {Boolean} devMode Operate in devMode - i.e. force outer refresh when encountering an existing TeraFy instance
|
|
18
|
+
* @property {'detect'|'parent'|'child'} 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 fallsback to 'child'
|
|
19
|
+
* @property {Number} modeTimeout How long entities have in 'detect' mode to identify themselves
|
|
18
20
|
* @property {String} siteUrl The TERA URL to connect to
|
|
19
21
|
* @property {String} restrictOrigin URL to restrict communications to
|
|
20
22
|
*/
|
|
21
23
|
settings = {
|
|
22
24
|
devMode: true,
|
|
25
|
+
mode: 'detect',
|
|
26
|
+
modeTimeout: 300,
|
|
23
27
|
siteUrl: 'https://tera-tools.com/embed',
|
|
24
|
-
restrictOrigin: '*', //
|
|
28
|
+
restrictOrigin: '*', // FIXME: Need to restrict this to TERA site
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
|
|
@@ -112,7 +116,16 @@ export default class TeraFy {
|
|
|
112
116
|
id: message.id || nanoid(),
|
|
113
117
|
...cloneDeep(message), // Need to clone to resolve promise nasties
|
|
114
118
|
};
|
|
115
|
-
|
|
119
|
+
|
|
120
|
+
if (this.settings.mode == 'parent') {
|
|
121
|
+
window.parent.postMessage(payload, this.settings.restrictOrigin);
|
|
122
|
+
} else if (this.settings.mode == 'child') {
|
|
123
|
+
this.dom.iframe.contentWindow.postMessage(payload, this.settings.restrictOrigin);
|
|
124
|
+
} else if (this.settings.mode == 'detect') {
|
|
125
|
+
throw new Error('Call init() or detectMode() before trying to send data to determine the mode');
|
|
126
|
+
} else {
|
|
127
|
+
throw new Error(`Unknown TERA communication mode "${this.settings.mode}"`);
|
|
128
|
+
}
|
|
116
129
|
} catch (e) {
|
|
117
130
|
this.debug('ERROR', 'Message compose client->server:', e);
|
|
118
131
|
this.debug('ERROR', 'Attempted to dispatch payload client->server', payload);
|
|
@@ -144,6 +157,8 @@ export default class TeraFy {
|
|
|
144
157
|
* @param {MessageEvent} Raw message event to process
|
|
145
158
|
*/
|
|
146
159
|
acceptMessage(rawMessage) {
|
|
160
|
+
if (rawMessage.origin == window.location.origin) return; // Message came from us
|
|
161
|
+
|
|
147
162
|
let message = rawMessage.data;
|
|
148
163
|
if (!message.TERA || !message.id) return; // Ignore non-TERA signed messages
|
|
149
164
|
this.debug('Recieved', message);
|
|
@@ -200,23 +215,56 @@ export default class TeraFy {
|
|
|
200
215
|
|
|
201
216
|
/**
|
|
202
217
|
* Initalize the TERA client singleton
|
|
218
|
+
* This function can only be called once and will return the existing init() worker Promise if its called againt
|
|
203
219
|
*
|
|
204
220
|
* @returns {Promise<TeraFy>} An eventual promise which will resovle with this terafy instance
|
|
205
221
|
*/
|
|
206
222
|
init() {
|
|
223
|
+
if (this.init.promise) return this.init.promise; // Aleady been called - return init promise
|
|
224
|
+
|
|
207
225
|
window.addEventListener('message', this.acceptMessage.bind(this));
|
|
208
226
|
|
|
209
|
-
return Promise.
|
|
210
|
-
|
|
211
|
-
this.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
227
|
+
return this.init.promise = Promise.resolve()
|
|
228
|
+
.then(()=> this.detectMode())
|
|
229
|
+
.then(mode => this.settings.mode = mode)
|
|
230
|
+
.then(()=> Promise.all([
|
|
231
|
+
// Init core functions async
|
|
232
|
+
this.injectComms(),
|
|
233
|
+
this.injectStylesheet(),
|
|
234
|
+
this.injectMethods(),
|
|
235
|
+
|
|
236
|
+
// Init all plugins
|
|
237
|
+
...this.plugins
|
|
238
|
+
.map(plugin => plugin.init()),
|
|
239
|
+
]))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Populate `settings.mode`
|
|
246
|
+
* Try to communicate with a parent frame, if none assume we need to fallback to child mode
|
|
247
|
+
*
|
|
248
|
+
* @returns {Promise<String>} A promise which will resolve with the detected mode to use
|
|
249
|
+
*/
|
|
250
|
+
detectMode() {
|
|
251
|
+
if (this.settings.mode != 'detect') { // Dev has specified a forced mode to use
|
|
252
|
+
return this.settings.mode;
|
|
253
|
+
} else if (window.self === window.parent) { // This frame is already at the top
|
|
254
|
+
return 'child';
|
|
255
|
+
} else { // No idea - try messaging
|
|
256
|
+
return Promise.resolve()
|
|
257
|
+
.then(()=> this.settings.mode = 'parent') // Switch to parent mode...
|
|
258
|
+
.then(()=> new Promise((resolve, reject) => { // And try messaging with a timeout
|
|
259
|
+
let timeoutHandle = setTimeout(()=> reject(), this.settings.modeTimeout);
|
|
260
|
+
|
|
261
|
+
this.rpc('handshake')
|
|
262
|
+
.then(()=> clearTimeout(timeoutHandle))
|
|
263
|
+
.then(()=> resolve())
|
|
264
|
+
}))
|
|
265
|
+
.then(()=> 'parent')
|
|
266
|
+
.catch(()=> 'child')
|
|
267
|
+
}
|
|
220
268
|
}
|
|
221
269
|
|
|
222
270
|
|
|
@@ -226,23 +274,32 @@ export default class TeraFy {
|
|
|
226
274
|
* @returns {Promise} A promise which will resolve when the loading has completed and we have found a parent TERA instance or initiallized a child
|
|
227
275
|
*/
|
|
228
276
|
injectComms() { return new Promise(resolve => {
|
|
229
|
-
this.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
277
|
+
switch (this.settings.mode) {
|
|
278
|
+
case 'child':
|
|
279
|
+
this.dom.el = document.createElement('div')
|
|
280
|
+
this.dom.el.id = 'tera-fy';
|
|
281
|
+
this.dom.el.classList.toggle('dev-mode', this.settings.devMode);
|
|
282
|
+
document.body.append(this.dom.el);
|
|
283
|
+
|
|
284
|
+
this.dom.iframe = document.createElement('iframe')
|
|
285
|
+
|
|
286
|
+
// Queue up event chain when document loads
|
|
287
|
+
this.dom.iframe.setAttribute('sandbox', 'allow-downloads allow-scripts allow-same-origin');
|
|
288
|
+
this.dom.iframe.addEventListener('load', ()=> {
|
|
289
|
+
this.debug('Embed frame ready');
|
|
290
|
+
resolve();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Start document load sequence + append to DOM
|
|
294
|
+
this.dom.iframe.src = this.settings.siteUrl;
|
|
295
|
+
this.dom.el.append(this.dom.iframe);
|
|
296
|
+
break;
|
|
297
|
+
case 'parent':
|
|
298
|
+
resolve();
|
|
299
|
+
break;
|
|
300
|
+
default:
|
|
301
|
+
throw new Error(`Unsupported mode "${this.settings.mode}" when calling injectComms()`);
|
|
302
|
+
}
|
|
246
303
|
})}
|
|
247
304
|
|
|
248
305
|
|
|
@@ -250,50 +307,58 @@ export default class TeraFy {
|
|
|
250
307
|
* Inject a local stylesheet to handle TERA server functionality
|
|
251
308
|
*/
|
|
252
309
|
injectStylesheet() {
|
|
253
|
-
this.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
310
|
+
switch (this.settings.mode) {
|
|
311
|
+
case 'child':
|
|
312
|
+
this.dom.stylesheet = document.createElement('style');
|
|
313
|
+
this.dom.stylesheet.innerHTML = [
|
|
314
|
+
':root {',
|
|
315
|
+
'--TERA-accent: #4d659c;',
|
|
316
|
+
'}',
|
|
317
|
+
|
|
318
|
+
'#tera-fy {',
|
|
319
|
+
'display: none;',
|
|
320
|
+
'position: fixed;',
|
|
321
|
+
'right: 50px;',
|
|
322
|
+
'bottom: 50px;',
|
|
323
|
+
'width: 300px;',
|
|
324
|
+
'height: 150px;',
|
|
325
|
+
'background: transparent;',
|
|
326
|
+
|
|
327
|
+
'&.dev-mode {',
|
|
328
|
+
'display: flex;',
|
|
329
|
+
'border: 5px solid var(--TERA-accent);',
|
|
330
|
+
'background: #FFF;',
|
|
331
|
+
'}',
|
|
332
|
+
|
|
333
|
+
'& > iframe {',
|
|
334
|
+
'width: 100%;',
|
|
335
|
+
'height: 100%;',
|
|
336
|
+
'}',
|
|
337
|
+
'}',
|
|
338
|
+
|
|
339
|
+
// Fullscreen functionality {{{
|
|
340
|
+
'body.tera-fy-focus {',
|
|
341
|
+
'overflow: hidden;',
|
|
342
|
+
|
|
343
|
+
'& #tera-fy {',
|
|
344
|
+
'display: flex !important;',
|
|
345
|
+
'position: fixed !important;',
|
|
346
|
+
'top: 0px !important;',
|
|
347
|
+
'width: 100vw !important;',
|
|
348
|
+
'height: 100vh !important;',
|
|
349
|
+
'left: 0px !important;',
|
|
350
|
+
'z-index: 10000 !important;',
|
|
351
|
+
'}',
|
|
352
|
+
'}',
|
|
353
|
+
// }}}
|
|
354
|
+
].join('\n');
|
|
355
|
+
document.head.appendChild(this.dom.stylesheet);
|
|
356
|
+
break;
|
|
357
|
+
case 'parent':
|
|
358
|
+
break;
|
|
359
|
+
default:
|
|
360
|
+
throw new Error(`Unsupported mode "${this.settings.mode}" when injectStylesheet()`);
|
|
361
|
+
}
|
|
297
362
|
}
|
|
298
363
|
|
|
299
364
|
|
|
@@ -386,7 +451,6 @@ export default class TeraFy {
|
|
|
386
451
|
Object.getOwnPropertyNames(Object.getPrototypeOf(source))
|
|
387
452
|
.filter(prop => !['constructor', 'prototype', 'name'].includes(prop))
|
|
388
453
|
.forEach((prop) => {
|
|
389
|
-
console.log('Merge method', prop);
|
|
390
454
|
Object.defineProperty(
|
|
391
455
|
target,
|
|
392
456
|
prop,
|
package/lib/terafy.server.js
CHANGED
|
@@ -126,8 +126,9 @@ export default class TeraFyServer {
|
|
|
126
126
|
* Send raw message content to the client
|
|
127
127
|
*
|
|
128
128
|
* @param {Object} message Message object to send
|
|
129
|
+
* @param {Window} Window context to dispatch the message via if its not the same as the regular window
|
|
129
130
|
*/
|
|
130
|
-
sendRaw(message) {
|
|
131
|
+
sendRaw(message, sendVia) {
|
|
131
132
|
let payload;
|
|
132
133
|
try {
|
|
133
134
|
payload = {
|
|
@@ -135,7 +136,7 @@ export default class TeraFyServer {
|
|
|
135
136
|
...cloneDeep(message), // Need to clone to resolve promise nasties
|
|
136
137
|
};
|
|
137
138
|
this.debug('INFO', 'Parent reply', message, '<=>', payload);
|
|
138
|
-
globalThis.parent.postMessage(payload, this.settings.restrictOrigin);
|
|
139
|
+
(sendVia || globalThis.parent).postMessage(payload, this.settings.restrictOrigin);
|
|
139
140
|
} catch (e) {
|
|
140
141
|
this.debug('ERROR', 'Attempted to dispatch payload server->client', payload);
|
|
141
142
|
this.debug('ERROR', 'Message compose server->client:', e);
|
|
@@ -150,6 +151,8 @@ export default class TeraFyServer {
|
|
|
150
151
|
* @param {MessageEvent} Raw message event to process
|
|
151
152
|
*/
|
|
152
153
|
acceptMessage(rawMessage) {
|
|
154
|
+
if (rawMessage.origin == window.location.origin) return; // Message came from us
|
|
155
|
+
|
|
153
156
|
let message = rawMessage.data;
|
|
154
157
|
if (!message.TERA) return; // Ignore non-TERA signed messages
|
|
155
158
|
this.debug('Recieved', message);
|
|
@@ -174,7 +177,7 @@ export default class TeraFyServer {
|
|
|
174
177
|
id: message.id,
|
|
175
178
|
action: 'response',
|
|
176
179
|
response,
|
|
177
|
-
}))
|
|
180
|
+
}, rawMessage.source))
|
|
178
181
|
.catch(e => {
|
|
179
182
|
console.warn(`TERA-FY server threw on RPC:${message.method}:`, e);
|
|
180
183
|
this.sendRaw({
|
|
@@ -370,6 +373,7 @@ export default class TeraFyServer {
|
|
|
370
373
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
371
374
|
* @param {String} [options.title="Select a project to work with"] The title of the dialog to display
|
|
372
375
|
* @param {Boolean} [options.allowCancel=true] Advertise cancelling the operation, the dialog can still be cancelled by closing it
|
|
376
|
+
* @param {Boolean} [options.setActive=false] Also set the project as active when selected
|
|
373
377
|
*
|
|
374
378
|
* @returns {Promise<Project>} The active project
|
|
375
379
|
*/
|
|
@@ -377,6 +381,7 @@ export default class TeraFyServer {
|
|
|
377
381
|
let settings = {
|
|
378
382
|
title: 'Select a project to work with',
|
|
379
383
|
allowCancel: true,
|
|
384
|
+
setActive: false,
|
|
380
385
|
...options,
|
|
381
386
|
};
|
|
382
387
|
|
|
@@ -388,6 +393,11 @@ export default class TeraFyServer {
|
|
|
388
393
|
buttons: settings.allowCancel && ['cancel'],
|
|
389
394
|
})
|
|
390
395
|
))
|
|
396
|
+
.then(project => settings.setActive
|
|
397
|
+
? this.setActiveProject(project)
|
|
398
|
+
.then(()=> project)
|
|
399
|
+
: project
|
|
400
|
+
)
|
|
391
401
|
}
|
|
392
402
|
|
|
393
403
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iebh/tera-fy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
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=.",
|
package/plugins/vue.js
CHANGED
|
@@ -111,6 +111,7 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
111
111
|
* Install into Vue as a generic Vue@3 plugin
|
|
112
112
|
*
|
|
113
113
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
114
|
+
* @param {Boolean} [options.autoInit=true] Call Init() during the `statePromiseable` cycle if its not already been called
|
|
114
115
|
* @param {String} [options.globalName='$tera'] Globa property to allocate this service as
|
|
115
116
|
* @param {Objecct} [options.bindOptions] Options passed to `bindProjectState()`
|
|
116
117
|
*
|
|
@@ -118,9 +119,9 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
118
119
|
*/
|
|
119
120
|
install(app, options) {
|
|
120
121
|
let settings = {
|
|
122
|
+
autoInit: true,
|
|
121
123
|
globalName: '$tera',
|
|
122
124
|
stateOptions: {
|
|
123
|
-
autoRequire: true,
|
|
124
125
|
write: true,
|
|
125
126
|
},
|
|
126
127
|
...options,
|
|
@@ -131,19 +132,20 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
131
132
|
$tera.state = null;
|
|
132
133
|
|
|
133
134
|
// $tera.statePromisable becomes the promise we are waiting on to resolve
|
|
134
|
-
$tera.statePromisable = Promise.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
135
|
+
$tera.statePromisable = Promise.resolve()
|
|
136
|
+
.then(()=> settings.autoInit && $tera.init())
|
|
137
|
+
.then(()=> Promise.all([
|
|
138
|
+
// Bind available project and wait on it
|
|
139
|
+
$tera.bindProjectState(settings.stateOptions)
|
|
140
|
+
.then(state => $tera.state = state)
|
|
141
|
+
.then(()=> $tera.debug('INFO', 'Loaded project state', $tera.state)),
|
|
142
|
+
|
|
143
|
+
// Fetch available projects
|
|
144
|
+
// TODO: It would be nice if this was responsive to remote changes
|
|
145
|
+
$tera.getProjects()
|
|
146
|
+
.then(projects => $tera.projects = reactive(projects))
|
|
147
|
+
.then(()=> $tera.debug('INFO', 'Loaded projects', $tera.projects)),
|
|
148
|
+
]))
|
|
147
149
|
|
|
148
150
|
|
|
149
151
|
// Make this module available globally
|