@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 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
+ ```
@@ -20,7 +20,7 @@ export default class TeraFy {
20
20
  */
21
21
  settings = {
22
22
  devMode: true,
23
- siteUrl: 'http://localhost:5173/embed',
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
- this.dom.iframe.contentWindow.postMessage(
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(), toggleDevMode(), init(), injectMain(), injectStylesheet(), injectMethods() {{{
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
- Object.assign(this.settings, options);
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
- this.injectMain();
220
- this.injectStylesheet();
221
- this.injectMethods();
222
- return Promise.all(this.plugins
223
- .map(plugin => plugin.init())
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
- injectMain() {
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(), toggleFullscreen() {{{
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
- if (!this.settings.devMode) return;
322
- console.log(
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
  }
@@ -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 instance hack from https://stackoverflow.com/a/44782052/1295040
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
- this.debug('Send to message source', e.origin, {message});
39
- e.source.postMessage(
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
- this.debug('SendRaw', {message});
128
- globalThis.parent.postMessage(
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
- if (!this.settings.devMode) return;
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.4",
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 lib/plugins/*.js >api.md",
10
- "lint": "eslint ./lib"
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
- ".": "./lib/terafy.client.js",
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/*": "./lib/plugins/*.js"
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