Merge branch 'v1.1-dev' as v1.1.4 v1.1.4
authorJustin Wind <justin.wind+git@gmail.com>
Mon, 16 Aug 2021 22:37:50 +0000 (15:37 -0700)
committerJustin Wind <justin.wind+git@gmail.com>
Mon, 16 Aug 2021 22:38:49 +0000 (15:38 -0700)
14 files changed:
CHANGELOG.md
config/default.js
package-lock.json
package.json
src/db/base.js
src/manager.js
src/template/admin-overview-html.js
src/template/admin-topic-details-html.js
src/template/root-html.js
src/template/template-helper.js
src/worker.js
test/src/db/postgres-listener.js
test/src/manager.js
test/src/worker.js

index 0378b17264c01b6b390e76aba426144260f4416d..e7fb95b086b3187f83f133aa48ce44d95e4806c2 100644 (file)
@@ -4,6 +4,16 @@ Releases and notable changes to this project are documented here.
 
 ## [Unreleased]
 
+## [v1.1.4] - 2021-08-16
+
+### Fixed
+
+- Prevent task processor from being re-invoked if it is already running.
+
+### Added
+
+- Allow more configuration of html page content.
+
 ## [v1.1.3] - 2021-08-13
 
 ### Fixed
@@ -41,7 +51,8 @@ Releases and notable changes to this project are documented here.
 
 ---
 
-[Unreleased]: https://git.squeep.com/?p=websub-hub;a=commitdiff;h=HEAD;hp=v1.1.3
+[Unreleased]: https://git.squeep.com/?p=websub-hub;a=commitdiff;h=HEAD;hp=v1.1.4
+[v1.1.4]: https://git.squeep.com/?p=websub-hub;a=commitdiff;h=v1.1.4;hp=v1.1.3
 [v1.1.3]: https://git.squeep.com/?p=websub-hub;a=commitdiff;h=v1.1.3;hp=v1.1.2
 [v1.1.2]: https://git.squeep.com/?p=websub-hub;a=commitdiff;h=v1.1.2;hp=v1.1.1
 [v1.1.1]: https://git.squeep.com/?p=websub-hub;a=commitdiff;h=v1.1.1;hp=v1.1.0
index 1905315cec7a1b69eb0e962513923745ff05db64..70837b1c34aa3fa5bdec4d8804df22ed50576847 100644 (file)
@@ -44,6 +44,10 @@ const defaultOptions = {
 
   manager: {
     pageTitle: packageName, // title on html pages
+    footerEntries: [ // common footers on all html pages
+      '<a href="https://git.squeep.com/?p=websub-hub;a=tree">Development Repository</a> / <a href="https://github.com/thylacine/websub-hub/">GitHub mirror</a>',
+      '&copy;<time datetime="2021">&#8559;&#8559;&#8553;&#8553;&#8544;</time>',
+    ],
     strictSecrets: false, // If true, reject requests with secrets but not over https
     publicHub: true, // Accept publish requests as new topics.
     processImmediately: true, // If true, immediately attempt to process requests when accepted.
index bf057462cf7f7f537936dc7a2a97dd6d19fa6631..40533b6b2e29ee80d4dada3bdf4a8ea1cec2bcb4 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "websub-hub",
-  "version": "1.1.3",
+  "version": "1.1.4",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
       }
     },
     "eslint-plugin-sonarjs": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.9.1.tgz",
-      "integrity": "sha512-KKFofk1LPjGHWeAZijYWv32c/C4mz+OAeBNVxhxHu1hknrTOhu415MWC8qKdAdsmOlBPShs9evM4mI1o7MNMhw==",
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.10.0.tgz",
+      "integrity": "sha512-FBRIBmWQh2UAfuLSnuYEfmle33jIup9hfkR0X8pkfjeCKNpHUG8qyZI63ahs3aw8CJrv47QJ9ccdK3ZxKH016A==",
       "dev": true
     },
     "eslint-scope": {
index 2b0d1958b022c119161a613a7cc96bc3fc5e37bb..1b3587f12db95ec286fe8486a621dfa9dd68bb53 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "websub-hub",
-  "version": "1.1.3",
+  "version": "1.1.4",
   "description": "A WebSub Hub server implementation.",
   "main": "server.js",
   "scripts": {
@@ -46,7 +46,7 @@
     "eslint": "^7.32.0",
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-security": "^1.4.0",
-    "eslint-plugin-sonarjs": "^0.9.1",
+    "eslint-plugin-sonarjs": "^0.10.0",
     "mocha": "^9.0.3",
     "mocha-steps": "^1.3.0",
     "nyc": "^15.1.0",
index 21e26642fad6ab703ef5c0a7a094180b4b3d19bc..95c901068092eb93ea8218cca55e775b1ffa5f10 100644 (file)
@@ -96,7 +96,7 @@ class Database {
     const current = svh.schemaVersionObjectToNumber(currentSchema);
     const min = svh.schemaVersionObjectToNumber(this.schemaVersionsSupported.min);
     const max = svh.schemaVersionObjectToNumber(this.schemaVersionsSupported.max);
-    if (min >= current && max <= current) {
+    if (current >= min && current <= max) {
       this.logger.debug(_scope, 'schema supported', { currentSchema, schemaVersionsSupported: this.schemaVersionsSupported });
     } else {
       this.logger.error(_scope, 'schema not supported', { currentSchema, schemaVersionsSupported: this.schemaVersionsSupported });
index d7a07f117b9eae96aebb808382625d237409732f..fddc8e45ae6f272a346c2131a05d909c190d03d9 100644 (file)
@@ -666,7 +666,9 @@ class Manager {
     this.logger.debug(_scope, 'called', { ctx });
 
     // N.B. no await on this
-    this.communication.worker.process();
+    this.communication.worker.process().catch((e) => {
+      this.logger.error(_scope, 'failed', { error: e, ctx });
+    });
 
     res.end();
     this.logger.info(_scope, 'invoked worker process', { ctx });
index b86f7af600e741e25805a89923f2a898d1127161..3d4f62c22e1c000e5e6714345f3e2184dc20ede7 100644 (file)
@@ -15,6 +15,7 @@ module.exports = (ctx, options) => {
   const pageTitle = `${options.manager.pageTitle} - Topics`;
   const headElements = [];
   const navLinks = [];
+  const footerEntries = options.manager.footerEntries;
   if (!ctx.topics) {
     ctx.topics = [];
   }
@@ -30,5 +31,5 @@ module.exports = (ctx, options) => {
     `        </tbody>
         </table>
       </section>`,
-  ]);
+  ], footerEntries);
 };
\ No newline at end of file
index 448de09efdb369a346deff79cffbaa5f307c11c7..df13f210f7af70c2686b3955ee3d744f4b2b43db 100644 (file)
@@ -21,6 +21,7 @@ module.exports = (ctx, options) => {
       text: '&uarr; All Topics',
     },
   ];
+  const footerEntries = options.manager.footerEntries;
   if (!ctx.subscriptions) {
     ctx.subscriptions = [];
   }
@@ -46,5 +47,5 @@ module.exports = (ctx, options) => {
     `          </tbody>
         </table>
       </section>`,
-  ]);
+  ], footerEntries);
 };
\ No newline at end of file
index d1939b84cd6bb690c07c0c65cd5cd7bbab6dd9c7..c68d52a33313810d581029eb2fabf456429a4a54 100644 (file)
@@ -120,12 +120,15 @@ module.exports = (ctx, options) => {
   const pageTitle = options.manager.pageTitle;
   const isPublicHub = options.manager.publicHub;
   const contactHTML = options.adminContactHTML;
+  const footerEntries = options.manager.footerEntries;
   const hubURL = options.dingus.selfBaseUrl || '<s>https://hub.example.com/</s>';
   const headElements = [];
   const navLinks = [];
-  return th.htmlTemplate(1, pageTitle, headElements, navLinks, [
+  const mainContent = [
     aboutSection(),
     usageSection(isPublicHub, hubURL),
     contactSection(contactHTML),
-  ]);
+  ];
+  return th.htmlTemplate(1, pageTitle, headElements, navLinks, mainContent, footerEntries,
+  );
 };
\ No newline at end of file
index d3962f99f263a07c485501236d0b1b3c3797848d..f36829d7bd1f9bd0eeb7adfe9daf956babce954f 100644 (file)
@@ -221,22 +221,16 @@ function htmlHeader(pageTitle, navLinks = []) {
 
 /**
  * Close the main section and finish off with boilerplate.
+ * @param {String[]} footerEntries
  * @returns {String}
  */
-function htmlFooter() {
+function htmlFooter(footerEntries = []) {
   return `    </main>
-    <footer>
-      <ol>
-        <li>
-          <a href="https://git.squeep.com/?p=websub-hub;a=tree">Development Repository</a> / <a href="https://github.com/thylacine/websub-hub/">GitHub mirror</a>
-        </li>
-        <li>
-          <a href="https://squeep.com/">A Squeep Infrastructure Component</a>
-        </li>
-        <li>
-          &copy;<time datetime="2021">&#8559;&#8559;&#8553;&#8553;&#8544;</time>
-        </li>
-      </ol>
+    <footer>` +
+    (footerEntries.length ? `
+      <ol>` + footerEntries.map((f) => `        <li>${f}</li>`).join('\n') + `
+      </ol>`
+      : '') + `
     </footer>`;
 }
 
@@ -248,14 +242,15 @@ function htmlFooter() {
  * @param {String[]} headElements
  * @param {Object[]} navLinks
  * @param {String[]} main
+ * @param {String[]} footerEntries
  * @returns {String}
  */
-function htmlTemplate(pagePathLevel, pageTitle, headElements = [], navLinks = [], main = []) {
+function htmlTemplate(pagePathLevel, pageTitle, headElements = [], navLinks = [], main = [], footerEntries = []) {
   return [
     htmlHead(pagePathLevel, pageTitle, headElements),
     htmlHeader(pageTitle, navLinks),
     ...main,
-    htmlFooter(),
+    htmlFooter(footerEntries),
     htmlTail(),
   ].join('\n');
 }
index 9b6fd7ab882752f00dc76ae693db8980e8eadc76..4f566ad9f97209bcc538692cf87b6e412ccbe877 100644 (file)
@@ -42,6 +42,7 @@ class Worker {
     this.inFlight = []; // Our work heap of Promises  
     this.nextTimeout = undefined; // Allow clearTimeout() to reset waiting period.
     this.running = false;
+    this.isProcessing = false; // Only let one process() method execute on the work heap at a time
   }
 
   /**
@@ -177,7 +178,13 @@ class Worker {
   async process() {
     const _scope = _fileScope('process');
 
-    this.logger.debug(_scope, 'called', {});
+    this.logger.debug(_scope, 'called', { isProcessing: this.isProcessing });
+
+
+    if (this.isProcessing) {
+      return;
+    }
+    this.isProcessing = true;
 
     // Interrupt any pending sleep, if we were called out of timeout-cycle.
     clearTimeout(this.nextTimeout);
@@ -218,6 +225,8 @@ class Worker {
 
     // No more work, wait a while and retry
     this._recurr();
+
+    this.isProcessing = false;
   }
 
 }
index 7926746ae95fe9e2c5e87e7541ee66c270394ed8..ecd2fe2ce4c06aabb47635c6edae4fcb5df80bfd 100644 (file)
@@ -52,10 +52,11 @@ describe('Postgres Listener', function () {
       await listener.stop();
     });
     it('cancels pending reconnect', async function() {
+      this.slow(300);
       const pendingReconnect = sinon.stub();
       listener.reconnectPending = setTimeout(pendingReconnect, 100);
       await listener.stop();
-      snooze(110);
+      await snooze(110);
       assert(!pendingReconnect.called);
     });
     it('closes existing connection', async function () {
index d8c09219c2cf323ecea93917013f597573588128..7870a55b45b62df6d4a8c3192501467c20b1e5c1 100644 (file)
@@ -609,7 +609,13 @@ describe('Manager', function () {
 
   describe('processTasks', function () {
     it('covers', async function () {
-      sinon.stub(manager.communication.worker, 'process');
+      sinon.stub(manager.communication.worker, 'process').resolves();
+      await manager.processTasks(res, ctx);
+      assert(manager.communication.worker.process.called);
+      assert(res.end.called);
+    });
+    it('covers error', async function () {
+      sinon.stub(manager.communication.worker, 'process').rejects();
       await manager.processTasks(res, ctx);
       assert(manager.communication.worker.process.called);
       assert(res.end.called);
index 32bd0656665bd854933240eb5323a440e1e438c5..5c97c0b24dd33de7dabdceb02ed298320d971188 100644 (file)
@@ -204,6 +204,18 @@ describe('Worker', function () {
       assert.strictEqual(worker._getWork.callCount, 0);
       assert.strictEqual(worker._recurr.callCount, 1);
     });
+    it('covers double invocation', async function () {
+      const snooze = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
+
+      this.slow(300);
+      worker.inFlight = [
+        Worker.watchedPromise(snooze(100)),
+      ];
+
+      await Promise.all([worker.process(), worker.process()]);
+      assert.strictEqual(worker._getWork.callCount, 2);
+      assert.strictEqual(worker._recurr.callCount, 1);
+    });
   }); // process
 
 }); // Worker