## [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
---
-[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
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>',
+ '©<time datetime="2021">ⅯⅯⅩⅩⅠ</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.
{
"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": {
{
"name": "websub-hub",
- "version": "1.1.3",
+ "version": "1.1.4",
"description": "A WebSub Hub server implementation.",
"main": "server.js",
"scripts": {
"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",
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 });
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 });
const pageTitle = `${options.manager.pageTitle} - Topics`;
const headElements = [];
const navLinks = [];
+ const footerEntries = options.manager.footerEntries;
if (!ctx.topics) {
ctx.topics = [];
}
` </tbody>
</table>
</section>`,
- ]);
+ ], footerEntries);
};
\ No newline at end of file
text: '↑ All Topics',
},
];
+ const footerEntries = options.manager.footerEntries;
if (!ctx.subscriptions) {
ctx.subscriptions = [];
}
` </tbody>
</table>
</section>`,
- ]);
+ ], footerEntries);
};
\ No newline at end of file
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
/**
* 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>
- ©<time datetime="2021">ⅯⅯⅩⅩⅠ</time>
- </li>
- </ol>
+ <footer>` +
+ (footerEntries.length ? `
+ <ol>` + footerEntries.map((f) => ` <li>${f}</li>`).join('\n') + `
+ </ol>`
+ : '') + `
</footer>`;
}
* @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');
}
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
}
/**
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);
// No more work, wait a while and retry
this._recurr();
+
+ this.isProcessing = false;
}
}
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 () {
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);
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