Merge branch 'v1.2-dev' as v1.2.1 v1.2.1
authorJustin Wind <justin.wind+git@gmail.com>
Fri, 10 Sep 2021 18:46:29 +0000 (11:46 -0700)
committerJustin Wind <justin.wind+git@gmail.com>
Fri, 10 Sep 2021 18:46:29 +0000 (11:46 -0700)
.gitignore
CHANGELOG.md
config/default.js
package-lock.json
package.json
src/communication.js
src/link-helper.js
src/service.js
src/template/root-html.js
test/src/service.js

index 622f9a7e6c366ad772872ca4515ae17ae5f0aad4..88f74fe4932c899944f210f070951e7eef5b2971 100644 (file)
@@ -3,3 +3,5 @@ node_modules
 coverage
 .vscode
 *.sqlite*
+static/*.gz
+static/*.br
index 1b458a4ba7c1ed104bc75dcfe999da2f42eea02d..0f153a9bd7514f601dd3f0788b799399b87d0781 100644 (file)
@@ -4,6 +4,12 @@ Releases and notable changes to this project are documented here.
 
 ## [Unreleased]
 
+## [v1.2.1]
+
+### Fixed
+
+- Minor issues and dependency updates.
+
 ## [v1.2.0] - 2021-08-28
 
 ### Added
index 70837b1c34aa3fa5bdec4d8804df22ed50576847..64f45eca22399ba7b44ebae0f586a7306e078650 100644 (file)
@@ -46,7 +46,7 @@ const defaultOptions = {
     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>',
+      '<small><span class="copyright">&copy;<time datetime="2021">&#8559;&#8559;&#8553;&#8553;&#8544;</time></span></small>',
     ],
     strictSecrets: false, // If true, reject requests with secrets but not over https
     publicHub: true, // Accept publish requests as new topics.
index 002506d48522ea5e05dcb00b4b7d4b7e0534191a..cca2037efc66ed4a424fed82ad9a5ab0ebc8131b 100644 (file)
       "dev": true
     },
     "@squeep/api-dingus": {
-      "version": "git+https://git.squeep.com/squeep-api-dingus/#ca35f167826c0732571da5f35e3c25881d138b79",
-      "from": "git+https://git.squeep.com/squeep-api-dingus/#v1.1.0",
+      "version": "git+https://git.squeep.com/squeep-api-dingus/#842a9b1e5b62aa642a53269a8466fd1e021e4ff2",
+      "from": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.0",
       "requires": {
         "mime-db": "^1.49.0",
         "uuid": "^8.3.2"
       "dev": true
     },
     "axios": {
-      "version": "0.21.1",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
-      "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
+      "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
       "requires": {
-        "follow-redirects": "^1.10.0"
+        "follow-redirects": "^1.14.0"
       }
     },
     "balanced-match": {
       "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
     },
     "domhandler": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz",
-      "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz",
+      "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==",
       "requires": {
         "domelementtype": "^2.2.0"
       }
     },
     "domutils": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
-      "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
+      "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
       "requires": {
         "dom-serializer": "^1.0.1",
         "domelementtype": "^2.2.0",
       "dev": true
     },
     "follow-redirects": {
-      "version": "1.14.1",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
-      "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg=="
+      "version": "1.14.3",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz",
+      "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw=="
     },
     "foreground-child": {
       "version": "2.0.0",
       "dev": true
     },
     "htmlparser2": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.0.0.tgz",
-      "integrity": "sha512-IhdltX9BWhYQft4UPA92jFasNajskja0om6vU0DaIEL4OseCg5zE+mHAMr51AT89TbzzECrQWJ4CZ5NVYTPlKw==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.1.1.tgz",
+      "integrity": "sha512-hZb0lfG0hbhR/hB879zbBr8Opv0Be9Zp+JYHgqTw5epF++aotu/zmMTPLy/60iJyR1MaD/3pYRp7xYteXsZMEA==",
       "requires": {
         "domelementtype": "^2.0.1",
         "domhandler": "^4.0.0",
-        "domutils": "^2.5.2",
+        "domutils": "^2.8.0",
         "entities": "^3.0.1"
       }
     },
       "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
     },
     "mocha": {
-      "version": "9.1.0",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.0.tgz",
-      "integrity": "sha512-Kjg/XxYOFFUi0h/FwMOeb6RoroiZ+P1yOfya6NK7h3dNhahrJx1r2XIT3ge4ZQvJM86mdjNA+W5phqRQh7DwCg==",
+      "version": "9.1.1",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.1.tgz",
+      "integrity": "sha512-0wE74YMgOkCgBUj8VyIDwmLUjTsS13WV1Pg7l0SHea2qzZzlq7MDnfbPsHKcELBRk3+izEVkRofjmClpycudCA==",
       "dev": true,
       "requires": {
         "@ungap/promise-all-settled": "1.1.2",
       }
     },
     "tar": {
-      "version": "6.1.6",
-      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.6.tgz",
-      "integrity": "sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==",
+      "version": "6.1.11",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
+      "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==",
       "requires": {
         "chownr": "^2.0.0",
         "fs-minipass": "^2.0.0",
index a16ad0e3a4997e3f76590c21ea11cc3e66e12c2a..94a4957d8a089562e03202f125d51542b8d45852 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "websub-hub",
-  "version": "1.2.0",
+  "version": "1.2.1",
   "description": "A WebSub Hub server implementation.",
   "main": "server.js",
   "scripts": {
     "coverage-check"
   ],
   "dependencies": {
-    "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.1.0",
+    "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.0",
     "@squeep/web-linking": "git+https://git.squeep.com/squeep-web-linking/#v1.0.0",
     "argon2": "^0.28.2",
-    "axios": "^0.21.1",
+    "axios": "^0.21.4",
     "better-sqlite3": "^7.4.3",
     "feedparser": "^2.2.10",
-    "htmlparser2": "^7.0.0",
+    "htmlparser2": "^7.1.1",
     "iconv": "^3.0.0",
     "pg-promise": "^10.11.0"
   },
@@ -47,7 +47,7 @@
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-security": "^1.4.0",
     "eslint-plugin-sonarjs": "^0.10.0",
-    "mocha": "^9.1.0",
+    "mocha": "^9.1.1",
     "mocha-steps": "^1.3.0",
     "nyc": "^15.1.0",
     "pre-commit": "^1.2.2",
index 097da637bd418d4d655f04aa65651ad5df709e5d..b11237794b7b340da470c7b2740bf3d2b5087c25 100644 (file)
@@ -443,7 +443,7 @@ class Communication {
     // Cull any expired subscriptions
     await this.db.subscriptionDeleteExpired(dbCtx, topicId);
 
-    logInfoData.url = topicId.url;
+    logInfoData.url = topic.url;
 
     if (topic.isDeleted) {
       this.logger.debug(_scope, 'topic deleted, skipping update request', logInfoData);
index 2b6a8334acce4b8c879448093fc1a522b5629380..93a947be58f284d63939000f5f342a3bf4fc5770 100644 (file)
@@ -60,7 +60,7 @@ class LinkHelper {
     if (nonUTF8Charset) {
       const iconv = new Iconv(nonUTF8Charset, 'utf-8//translit//ignore');
       try {
-        body = iconv.convert(body);
+        body = iconv.convert(body).toString('utf8');
       } catch (e) {
         /* istanbul ignore next */
         this.logger.error(_scope, 'iconv conversion error', { error: e, contentType, url });
index c69f6fb7c891366dfbe02e659a54414a05da401d..df56ba0ba79137fea5265b91008ef002ae53503c 100644 (file)
@@ -40,14 +40,14 @@ class Service extends Dingus {
 
     // These routes are intended for accessing static content during development.
     // In production, a proxy server would likely handle these first.
-    this.on(['GET', 'HEAD'], '/static', (req, res, ctx) => this.handlerRedirect(req, res, ctx, `${options.dingus.proxyPrefix}/static/`));
-    this.on(['GET', 'HEAD'], '/static/', (req, res, ctx) => this.handlerGetStaticFile(req, res, ctx, 'index.html'));
+    this.on(['GET', 'HEAD'], '/static', this.handlerRedirect.bind(this), `${options.dingus.proxyPrefix}/static/`);
+    this.on(['GET', 'HEAD'], '/static/', this.handlerGetStaticFile.bind(this), 'index.html');
     this.on(['GET', 'HEAD'], '/static/:file', this.handlerGetStaticFile.bind(this));
-    this.on(['GET', 'HEAD'], '/favicon.ico', (req, res, ctx) => this.handlerGetStaticFile(req, res, ctx, 'favicon.ico'));
-    this.on(['GET', 'HEAD'], '/robots.txt', (req, res, ctx) => this.handlerGetStaticFile(req, res, ctx, 'robots.txt'));
+    this.on(['GET', 'HEAD'], '/favicon.ico', this.handlerGetStaticFile.bind(this), 'favicon.ico');
+    this.on(['GET', 'HEAD'], '/robots.txt', this.handlerGetStaticFile.bind(this), 'robots.txt');
 
     // Private informational endpoints
-    this.on(['GET', 'HEAD'], '/admin', (req, res, ctx) => this.handlerRedirect(req, res, ctx, `${options.dingus.proxyPrefix}/admin/`));
+    this.on(['GET', 'HEAD'], '/admin', this.handlerRedirect.bind(this), `${options.dingus.proxyPrefix}/admin/`);
     this.on(['GET', 'HEAD'], '/admin/', this.handlerGetAdminOverview.bind(this));
     this.on(['GET', 'HEAD'], '/admin/topic/:topicId', this.handlerGetAdminTopicDetails.bind(this));
 
@@ -60,22 +60,6 @@ class Service extends Dingus {
   }
 
 
-  /**
-   * @param {http.ClientRequest} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
-   * @param {String} newPath
-  */
-  async handlerRedirect(req, res, ctx, newPath) {
-    const _scope = _fileScope('handlerRedirect');
-    this.logger.debug(_scope, 'called', { req: common.requestLogData(req), ctx });
-
-    res.setHeader(Enum.Header.Location, newPath);
-    res.statusCode = 307; // Temporary Redirect
-    res.end();
-  }
-
-
   /**
    * @param {http.ClientRequest} req 
    * @param {http.ServerResponse} res 
@@ -240,25 +224,6 @@ class Service extends Dingus {
   }
   
 
-  /**
-   * @param {http.ClientRequest} req
-   * @param {http.ServerResponse} res
-   * @param {object} ctx
-   */
-  async handlerGetStaticFile(req, res, ctx, file) {
-    const _scope = _fileScope('handlerGetStaticFile');
-    this.logger.debug(_scope, 'called', { req: common.requestLogData(req), ctx, file });
-
-    Dingus.setHeadHandler(req, res, ctx);
-
-    // Set a default response type to handle any errors; will be re-set to serve actual static content type.
-    this.setResponseType(this.responseTypes, req, res, ctx);
-
-    await this.serveFile(req, res, ctx, this.staticPath, file || ctx.params.file);
-    this.logger.info(_scope, 'finished', { ctx: { ...ctx, responseBody: common.logTruncate((ctx.responseBody || '').toString(), 100) } });
-  }
-
-
   /**
    * @param {http.ClientRequest} req
    * @param {http.ServerResponse} res
index c68d52a33313810d581029eb2fabf456429a4a54..8797564e54e93b1ef816de4c4e4b6e01269e3b7c 100644 (file)
@@ -46,6 +46,15 @@ function usageSection(isPublicHub, hubURL) {
               </code>
             </figure>
           </li>
+          <li>
+            Ideally, these should be combined in one header.
+            <figure>
+              <figcaption>Example:</figcaption>
+              <code>
+                Link: &lt;${hubURL}&gt;; rel="hub", &lt;https://example.com/feed/&gt;; rel="self"
+              </code>
+            </figure>
+          </li>
         </ul>
       </div>
       <div>
@@ -73,15 +82,21 @@ function usageSection(isPublicHub, hubURL) {
       </div>
       <div>
         <h3>Publishing Updates</h3>
-        Send a <code>POST</code> request to this hub with Form Data:
+        To notify the Hub that a topic&apos;s content has been updated and should be distributed to subscribers, send a <code>POST</code> request with Form Data (<code>application/x-www-form-urlencoded</code>):
         <ul>
           <li>
             <code>hub.mode</code> set to <code>publish</code>
           </li>
           <li>
-            <code>hub.url</code> set to the <code>self</code> link relation of the content
+            <code>hub.url</code> set to the <code>self</code> link relation of the content (this value may be set multiple times, to update more than one topic)
           </li>
         </ul>
+        <figure>
+          <figcaption>Example:</figcaption>
+          <code>
+            curl ${hubURL} -d'hub.mode=publish' -d'hub.url=https://example.com/blog_one/feed' -d'hub.url=https://example.com/blog_two/feed'
+          </code>
+        </figure>
       </div>`
     : `
       <h2>Private Hub</h2>
index a00b2cea49d882790e8be6e427d913be467af6ff..7a97cf5366f88a70a32702170658b4f2e50c7c97 100644 (file)
@@ -60,14 +60,6 @@ describe('Service', function () {
     });
   }); // maybeIngestBody
 
-  describe('handlerRedirect', function () {
-    it('covers', async function () {
-      await service.handlerRedirect(req, res, ctx, '/');
-      assert(res.end.called);
-      assert.strictEqual(res.statusCode, 307);
-    });
-  }); // handlerRedirect
-
   describe('handlerPostRoot', function () {
     it('covers public mode', async function () {
       await service.handlerPostRoot(req, res, ctx);
@@ -123,14 +115,6 @@ describe('Service', function () {
     })
   }); // handlerGetAdminTopicDetails
 
-  describe('handlerGetStaticFile', function () {
-    it('covers', async function () {
-      service.serveFile.resolves();
-      await service.handlerGetStaticFile(req, res, ctx);
-      assert(service.serveFile.called);
-    });
-  }); // handlerGetStaticFile
-
   describe('handlerPostAdminProcess', function () {
     it('covers', async function () {
       service.serveFile.resolves();