@iebh/tera-fy 2.0.20 → 2.0.21

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.
@@ -69,9 +69,9 @@ export default class TeraFyServer {
69
69
  ...cloneDeep(message), // Need to clone to resolve promise nasties
70
70
  };
71
71
  e.source.postMessage(payload, this.settings.restrictOrigin);
72
- } catch (e) {
73
- this.debug('ERROR', 1, 'Attempted to dispatch payload server(via reply)->client', {payload, e});
74
- throw e;
72
+ } catch (err) {
73
+ this.debug('ERROR', 1, 'Attempted to dispatch payload server(via reply)->client', {payload, err});
74
+ throw err;
75
75
  }
76
76
  },
77
77
  });
@@ -127,14 +127,16 @@ export default class TeraFyServer {
127
127
  ...cloneDeep(message), // Need to clone to resolve promise nasties
128
128
  };
129
129
  iFrame.contentWindow.postMessage(payload, this.settings.restrictOrigin);
130
- } catch (e) {
131
- this.debug('ERROR', 1, 'Attempted to dispatch payload server(top level window)->cient(iframe)', {payload, e});
132
- throw e;
130
+ } catch (err) {
131
+ this.debug('ERROR', 1, 'Attempted to dispatch payload server(top level window)->cient(iframe)', {payload, err});
132
+ throw err;
133
133
  }
134
134
  },
135
135
  });
136
136
  }
137
137
  case TeraFyServer.SERVERMODE_POPUP:
138
+ // FIXME: Need implementation for POPUP mode?
139
+ throw new Error('SERVERMODE_POPUP getClientContext not implemented');
138
140
  }
139
141
  }
140
142
 
@@ -190,7 +192,7 @@ export default class TeraFyServer {
190
192
  * @returns {Promise<*>} A promise which resolves when the operation has completed with the remote reply
191
193
  */
192
194
  send(message) {
193
- if (!this.messageEvent) throw new Error('send() can only be used if given a context from `createContext()`');
195
+ if (!this.messageEvent?.source) throw new Error('send() requires a messageEvent with a source');
194
196
 
195
197
  let id = nanoid();
196
198
 
@@ -199,10 +201,12 @@ export default class TeraFyServer {
199
201
  Object.assign(this.acceptPostboxes[id], {
200
202
  resolve, reject,
201
203
  });
204
+
205
+ // Use sendRaw with the specific source from the stored messageEvent
202
206
  this.sendRaw({
203
207
  id,
204
208
  ...message,
205
- });
209
+ }, this.messageEvent?.source); // Pass the source explicitly
206
210
  });
207
211
 
208
212
  return this.acceptPostboxes[id].promise;
@@ -224,7 +228,10 @@ export default class TeraFyServer {
224
228
  ...cloneDeep(message), // Need to clone to resolve promise nasties
225
229
  };
226
230
  this.debug('INFO', 3, 'Dispatch response', message, '<=>', payload);
227
- (sendVia || globalThis.parent).postMessage(payload, this.settings.restrictOrigin);
231
+
232
+ let target = sendVia || globalThis.parent;
233
+ if (!target) this.debug('WARN', 1, 'Cannot sendRaw, no target window (sendVia or parent) found.');
234
+ target.postMessage(payload, this.settings.restrictOrigin);
228
235
  } catch (e) {
229
236
  this.debug('ERROR', 2, 'Attempted to dispatch response server->client', payload);
230
237
  this.debug('ERROR', 2, 'Message compose server->client:', e);
@@ -274,6 +281,7 @@ export default class TeraFyServer {
274
281
  } else {
275
282
  this.acceptPostboxes[message.id].resolve(message.response);
276
283
  }
284
+ delete this.acceptPostboxes[message.id]; // Clean up postbox
277
285
  } else if (message.action == 'rpc') { // Relay RPC calls
278
286
  if (!this[message.method]) throw new Error(`Unknown RPC method "${message.method}"`);
279
287
  return this[message.method].apply(this.createContext(rawMessage), message.args);
@@ -289,12 +297,16 @@ export default class TeraFyServer {
289
297
  }, rawMessage.source))
290
298
  .catch(e => {
291
299
  console.warn(`TERA-FY server threw on RPC:${message.method}:`, e);
292
- this.sendRaw({
293
- id: message.id,
294
- action: 'response',
295
- isError: true,
296
- response: e ? e.toString() : e,
297
- }, rawMessage.source);
300
+ if (message.action === 'rpc' && message.id && rawMessage.source) {
301
+ this.sendRaw({
302
+ id: message.id,
303
+ action: 'response',
304
+ isError: true,
305
+ response: e instanceof Error ? e.message : String(e), // Return error message to requester
306
+ }, rawMessage.source);
307
+ } else {
308
+ console.warn(`Unable to respond with errored RPC:${message.method} as reply postbox is invalid`);
309
+ }
298
310
  })
299
311
  }
300
312
 
@@ -315,6 +327,13 @@ export default class TeraFyServer {
315
327
  * @returns {Promise<*>} A promise which resolves with the resulting inner callback payload
316
328
  */
317
329
  requestFocus(cb) {
330
+ // Ensure messageEvent is set before calling senderRpc
331
+ if (!this.messageEvent && this.settings.serverMode != TeraFyServer.SERVERMODE_TERA) {
332
+ console.warn("requestFocus called without a messageEvent context. Cannot toggle focus.");
333
+ // Proceed without toggling focus if no context is available
334
+ return Promise.resolve().then(() => cb.call(this));
335
+ }
336
+
318
337
  return Promise.resolve()
319
338
  .then(()=> this.settings.serverMode != TeraFyServer.SERVERMODE_TERA && this.senderRpc('toggleFocus', true))
320
339
  .then(()=> cb.call(this))
@@ -403,7 +422,10 @@ export default class TeraFyServer {
403
422
  }
404
423
  : null
405
424
  )
406
- .catch(e => console.warn('getUser() catch', e))
425
+ .catch(e => {
426
+ console.warn('getUser() catch', e);
427
+ return null; // Return null on error
428
+ })
407
429
  }
408
430
 
409
431
 
@@ -517,8 +539,8 @@ export default class TeraFyServer {
517
539
  // Force refresh projects against the new user
518
540
  await app.service('$projects').refresh();
519
541
  return;
520
- } catch (e) {
521
- throw new Error(`Failed to decode local dev state - ${e.toString()}`);
542
+ } catch (err) {
543
+ throw new Error(`Failed to decode local dev state - ${err.toString()}`);
522
544
  }
523
545
  }
524
546
 
@@ -530,7 +552,7 @@ export default class TeraFyServer {
530
552
 
531
553
  // Attach click listner to internal button to re-popup the auth window (in case popups are blocked)
532
554
  focusContent.querySelector('a.btn').addEventListener('click', ()=>
533
- this.uiWindow(new URL(this.settings.sitePathLogin, this.settings.siteUrl))
555
+ this.uiWindow(new URL(this.settings.sitePathLogin, this.settings.siteUrl).toString())
534
556
  );
535
557
 
536
558
  // Create a deferred promise which will (eventually) resolve when the downstream window signals its ready
@@ -565,7 +587,7 @@ export default class TeraFyServer {
565
587
  // Go fullscreen, try to open the auth window + prompt the user to retry (if popups are blocked) and wait for resolution
566
588
  await this.requestFocus(()=> {
567
589
  // Try opening the popup automatically - this will likely fail if the user has popup blocking enabled
568
- this.uiWindow(new URL(this.settings.sitePathLogin, this.settings.siteUrl));
590
+ this.uiWindow(new URL(this.settings.sitePathLogin, this.settings.siteUrl).toString());
569
591
 
570
592
  // Display a message to the user, offering the ability to re-open the popup if it was originally denied
571
593
  this.uiSplat(focusContent, {logo: true});
@@ -685,7 +707,7 @@ export default class TeraFyServer {
685
707
  }))
686
708
  .then(project => resolve(project))
687
709
  .catch(e => {
688
- if (e == 'cancel') {
710
+ if (e.toLowerCase() == 'cancel') {
689
711
  return this.requestFocus(()=>
690
712
  app.service('$prompt').dialog({
691
713
  title: settings.noSelectTitle,
@@ -732,7 +754,7 @@ export default class TeraFyServer {
732
754
  app.service('$prompt').dialog({
733
755
  title: settings.title,
734
756
  component: 'projectsSelect',
735
- buttons: settings.allowCancel && ['cancel'],
757
+ buttons: settings.allowCancel ? ['cancel'] : false,
736
758
  })
737
759
  ))
738
760
  .then(project => settings.setActive
@@ -773,11 +795,11 @@ export default class TeraFyServer {
773
795
  * @returns {Promise<Object>} A promise which resolves to the namespace POJO state
774
796
  */
775
797
  setNamespace(name, state, options) {
776
- if (!/^[\w--]+$/.test(name)) throw new Error('Namespaces must be alphanumeric + hyphens + underscores');
798
+ if (!/^[\w-]+$/.test(name)) throw new Error('Namespaces must be alphanumeric + hyphens + underscores');
777
799
  if (typeof state != 'object') throw new Error('State must be an object');
778
800
 
779
801
  return app.service('$sync').setSnapshot(`project_namespaces::${app.service('$projects').active.id}}::${name}`, state, {
780
- method: options.method,
802
+ method: options.method ?? 'merge',
781
803
  });
782
804
  }
783
805
 
@@ -839,7 +861,6 @@ export default class TeraFyServer {
839
861
  */
840
862
  setProjectState(path, value, options) {
841
863
  let settings = {
842
- save: true,
843
864
  strategy: 'set',
844
865
  ...options,
845
866
  };
@@ -863,7 +884,8 @@ export default class TeraFyServer {
863
884
  },
864
885
  );
865
886
 
866
- return Promise.resolve(); // Sync functionality for the moment but could be async in the future
887
+ // Sync functionality for the moment but could be async in the future
888
+ return Promise.resolve(value);
867
889
  }
868
890
 
869
891
 
@@ -902,7 +924,7 @@ export default class TeraFyServer {
902
924
  newState: cloneDeep(target),
903
925
  });
904
926
 
905
- return value;
927
+ return Promise.resolve(value);
906
928
  }
907
929
  }
908
930
 
@@ -1011,7 +1033,7 @@ export default class TeraFyServer {
1011
1033
  },
1012
1034
  },
1013
1035
  modalDialogClass: 'modal-dialog-lg',
1014
- buttons: settings.allowCancel && ['cancel'],
1036
+ buttons: settings.allowCancel ? ['cancel'] : false,
1015
1037
  })
1016
1038
  ))
1017
1039
  }
@@ -1188,11 +1210,7 @@ export default class TeraFyServer {
1188
1210
  };
1189
1211
 
1190
1212
  return Promise.resolve()
1191
- .then(()=> {
1192
- console.log('Checking for project, is required:', settings.autoRequire);
1193
- settings.autoRequire && this.requireProject()
1194
- console.log('Project set!')
1195
- })
1213
+ .then(()=> settings.autoRequire && this.requireProject())
1196
1214
  .then(()=> {
1197
1215
  if (settings.id) return; // We already have a file ID specified - skip
1198
1216
 
@@ -1206,6 +1224,9 @@ export default class TeraFyServer {
1206
1224
  })
1207
1225
  .then(file => settings.id = file.id)
1208
1226
  })
1227
+ .then(()=> { // Final checks
1228
+ if (!settings.id) throw new Error("Could not determine file ID to save to.");
1229
+ })
1209
1230
  .then(()=> app.service('$supabase').fileSet(app.service('$projects').decodeFilePath(settings.id), contents, {
1210
1231
  overwrite: true,
1211
1232
  toast: false,
@@ -1242,7 +1263,7 @@ export default class TeraFyServer {
1242
1263
  autoRequire: true,
1243
1264
  filters: {
1244
1265
  library: true,
1245
- ...options?.filter,
1266
+ ...(options?.filters ?? {}), // Use filters from options if provided
1246
1267
  },
1247
1268
  ...options,
1248
1269
  };
@@ -1283,6 +1304,8 @@ export default class TeraFyServer {
1283
1304
  toast: false,
1284
1305
  }))
1285
1306
  .then(blob => {
1307
+ if (!blob) throw new Error(`File not found or empty: ${filePath}`);
1308
+
1286
1309
  switch (settings.format) {
1287
1310
  // NOTE: Any updates to the format list should also extend setProjectLibrary()
1288
1311
  case 'pojo':
@@ -1365,7 +1388,7 @@ export default class TeraFyServer {
1365
1388
  filePath = app.service('$projects').decodeFilePath(settings.id);
1366
1389
  })
1367
1390
  .then(()=> {
1368
- // Mutate settings.ref -> Blob or File format needed by Supabase
1391
+ // Mutate settings.refs -> Blob or File format needed by Supabase
1369
1392
  if (settings.format == 'auto') {
1370
1393
  settings.format =
1371
1394
  Array.isArray(settings.refs) ? 'pojo'
@@ -1388,7 +1411,7 @@ export default class TeraFyServer {
1388
1411
  if (!(settings.refs instanceof Blob)) throw new Error("setProjectLibrary({format: 'blob'} but non-Blob provided as `refs`");
1389
1412
  return new File([settings.refs], app.service('$supabase')._parsePath(filePath).basename);
1390
1413
  case 'file':
1391
- if (!(settings.ref instanceof File)) throw new Error("setProjectLibrary({format: 'file'} but non-File provided as `refs`");
1414
+ if (!(settings.refs instanceof File)) throw new Error("setProjectLibrary({format: 'file'} but non-File provided as `refs`");
1392
1415
  return settings.refs;
1393
1416
  default:
1394
1417
  throw new Error(`Unsupported library format "${settings.format}"`);
@@ -1396,7 +1419,7 @@ export default class TeraFyServer {
1396
1419
  })
1397
1420
  .then(fileBlob => app.service('$supabase').fileUpload(filePath, {
1398
1421
  file: fileBlob,
1399
- overwrite: true,
1422
+ overwrite: settings.overwrite,
1400
1423
  mode: 'encoded',
1401
1424
  }))
1402
1425
  .then(()=> null)
@@ -1484,11 +1507,9 @@ export default class TeraFyServer {
1484
1507
  app.service('$prompt').dialog({
1485
1508
  title: settings.title,
1486
1509
  body: settings.body,
1487
- buttons:
1488
- settings.buttons == 'ok' ? ['ok']
1489
- : false,
1510
+ buttons: settings.buttons == 'ok' ? ['ok'] : false,
1490
1511
  isHtml: settings.isHtml,
1491
- dialogClose: 'resolve',
1512
+ dialogClose: 'resolve', // Resolve promise when closed
1492
1513
  })
1493
1514
  );
1494
1515
  }
@@ -1527,7 +1548,7 @@ export default class TeraFyServer {
1527
1548
  {
1528
1549
  title: 'OK',
1529
1550
  class: 'btn btn-success',
1530
- click: 'resolve',
1551
+ click: 'resolve', // Resolve promise with default value (usually true or button index)
1531
1552
  },
1532
1553
  {
1533
1554
  title: 'Cancel',
@@ -1536,8 +1557,8 @@ export default class TeraFyServer {
1536
1557
  },
1537
1558
  ],
1538
1559
  })
1539
- .then(()=> 'OK')
1540
- .catch(()=> Promise.reject('CANCEL'))
1560
+ .then(()=> 'OK') // Resolve with 'OK' if OK button clicked
1561
+ .catch(()=> Promise.reject('CANCEL')) // Reject with 'CANCEL' if Cancel button clicked or closed
1541
1562
  );
1542
1563
  }
1543
1564
 
@@ -1557,7 +1578,7 @@ export default class TeraFyServer {
1557
1578
  * Display, update or dispose of windows for long running tasks
1558
1579
  * All options are cumulative - i.e. they are merged with other options previously provided
1559
1580
  *
1560
- * @param {Object|Boolean} [options] Additional options to mutate behaviour, if boolean false `{close: true}` is assumed
1581
+ * @param {Object|Boolean} [options] Additional options to mutate behaviour, if boolean false `close: true` is assumed
1561
1582
  * @param {String} [options.body=''] Window body text
1562
1583
  * @param {Boolean} [options.bodyHtml=false] If truthy, treat the body as HTML
1563
1584
  * @param {String} [options.title='TERA'] Window title, can only be set on the initial call
@@ -1648,7 +1669,7 @@ export default class TeraFyServer {
1648
1669
  return this.requestFocus(()=>
1649
1670
  app.service('$prompt').dialog({
1650
1671
  title: settings.title,
1651
- closable: true,
1672
+ closable: true, // Allow closing via backdrop click (will reject)
1652
1673
  component: 'UiPrompt',
1653
1674
  componentProps: {
1654
1675
  body: settings.body,
@@ -1662,7 +1683,7 @@ export default class TeraFyServer {
1662
1683
  icon: 'fas fa-check',
1663
1684
  title: 'Ok',
1664
1685
  click() {
1665
- return this.$prompt.close(true, this.newValue);
1686
+ return app.service('$prompt').close(true, this.newValue);
1666
1687
  },
1667
1688
  },
1668
1689
  'cancel',
@@ -1672,9 +1693,9 @@ export default class TeraFyServer {
1672
1693
  .then(answer => {
1673
1694
  if (answer) {
1674
1695
  return answer;
1675
- } else if (settings.required) {
1696
+ } else if (settings.required) { // Required answer but returned response is falsy
1676
1697
  return Promise.reject('CANCEL');
1677
- } else {
1698
+ } else { // Everything else - relay the raw value
1678
1699
  return answer;
1679
1700
  }
1680
1701
  })
@@ -1719,7 +1740,7 @@ export default class TeraFyServer {
1719
1740
  location: false,
1720
1741
  menubar: false,
1721
1742
  status: false,
1722
- scrolbars: false,
1743
+ scrollbars: false,
1723
1744
  },
1724
1745
  ...options,
1725
1746
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iebh/tera-fy",
3
- "version": "2.0.20",
3
+ "version": "2.0.21",
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 --serve --servedir=.",
@@ -86,7 +86,7 @@
86
86
  "uuid": "^11.1.0"
87
87
  },
88
88
  "devDependencies": {
89
- "@momsfriendlydevco/eslint-config": "^2.1.2",
89
+ "@momsfriendlydevco/eslint-config": "^2.1.3",
90
90
  "@release-it/conventional-changelog": "^10.0.0",
91
91
  "concurrently": "^9.1.2",
92
92
  "documentation": "^14.0.3",
@@ -117,8 +117,9 @@
117
117
  "before:init": "git reset HEAD -- api.md docs/ dist/",
118
118
  "before:bump": [
119
119
  "npm run build",
120
- "git add api.md docs/ dist/"
121
- ]
120
+ "git add -f api.md docs/ dist/"
121
+ ],
122
+ "after:release": "git rm --cached -r dist && echo 'Untracked dist/ for subsequent commits.'"
122
123
  },
123
124
  "plugins": {
124
125
  "@release-it/conventional-changelog": {
@@ -121,11 +121,9 @@ export function merge(target, path, value) {
121
121
  */
122
122
  export function defaults(target, path, value) {
123
123
  if (typeof path == 'string' || Array.isArray(path)) { // Called as (target, path, value)
124
- if (!has(target, path)) { // Target path doesn't exist at all
125
- return set(target, path, value);
126
- } else { // Target path exists - apply Lodash defaults
127
- return _defaults(get(target, path), value);
128
- }
124
+ return !has(target, path) // Target path doesn't exist at all
125
+ ? set(target, path, value)
126
+ : _defaults(get(target, path), value);
129
127
  } else { // Called as (target, value)
130
128
  return _defaults(target, path);
131
129
  }