@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 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
+ ```
@@ -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,7 +186,7 @@ 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
@@ -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
- 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);
@@ -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('TERA EMBED FRAME READY');
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(), toggleFullscreen() {{{
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
- if (!this.settings.devMode) return;
322
- console.log(
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
  }
@@ -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,19 @@ 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
+ 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(res => this.sendRaw({
173
+ .then(response => this.sendRaw({
165
174
  id: message.id,
166
175
  action: 'response',
167
- response: res,
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
- if (!this.settings.devMode) return;
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.5",
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 lib/plugins/*.js >api.md",
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 {Promies<Reactive<Object>>} A reactive object representing the project state
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 context = this;
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
- app.config.globalProperties[settings.globalName] = {
94
- // Create project binding
95
- // state: context.bindProjectState(settings.stateOptions),
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
  };