coverage
.vscode
*.sqlite*
+static/*.gz
+static/*.br
## [Unreleased]
+## [v1.2.1]
+
+### Fixed
+
+- Minor issues and dependency updates.
+
## [v1.2.0] - 2021-08-28
### Added
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>',
- '©<time datetime="2021">ⅯⅯⅩⅩⅠ</time>',
+ '<small><span class="copyright">©<time datetime="2021">ⅯⅯⅩⅩⅠ</time></span></small>',
],
strictSecrets: false, // If true, reject requests with secrets but not over https
publicHub: true, // Accept publish requests as new topics.
"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",
{
"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"
},
"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",
// 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);
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 });
// 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));
}
- /**
- * @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
}
- /**
- * @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
</code>
</figure>
</li>
+ <li>
+ Ideally, these should be combined in one header.
+ <figure>
+ <figcaption>Example:</figcaption>
+ <code>
+ Link: <${hubURL}>; rel="hub", <https://example.com/feed/>; rel="self"
+ </code>
+ </figure>
+ </li>
</ul>
</div>
<div>
</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'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>
});
}); // 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);
})
}); // 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();