From 22653dec84b13e9c6c25f07f108cfddcf9d0f3a8 Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Fri, 10 Nov 2023 14:46:27 +0000 Subject: [PATCH] feat(endpoint-webmention): wip --- indiekit.config.js | 1 + package-lock.json | 206 ++++++++++++++++++ packages/endpoint-webmention/assets/icon.svg | 4 + packages/endpoint-webmention/index.js | 70 ++++++ .../lib/webmention-handler.js | 23 ++ packages/endpoint-webmention/package.json | 43 ++++ packages/indiekit/package.json | 1 + 7 files changed, 348 insertions(+) create mode 100644 packages/endpoint-webmention/assets/icon.svg create mode 100644 packages/endpoint-webmention/index.js create mode 100644 packages/endpoint-webmention/lib/webmention-handler.js create mode 100644 packages/endpoint-webmention/package.json diff --git a/indiekit.config.js b/indiekit.config.js index a79d14716..d4a317df6 100644 --- a/indiekit.config.js +++ b/indiekit.config.js @@ -15,6 +15,7 @@ const config = { plugins: [ "@indiekit-test/frontend", "@indiekit/endpoint-json-feed", + "@indiekit/endpoint-webmention", "@indiekit/preset-jekyll", "@indiekit/store-github", "@indiekit/syndicator-internet-archive", diff --git a/package-lock.json b/package-lock.json index 08f35fa89..ce8ab26f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2185,6 +2185,10 @@ "resolved": "packages/endpoint-syndicate", "link": true }, + "node_modules/@indiekit/endpoint-webmention": { + "resolved": "packages/endpoint-webmention", + "link": true + }, "node_modules/@indiekit/error": { "resolved": "packages/error", "link": true @@ -5988,6 +5992,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -7661,6 +7670,52 @@ "resolved": "packages/create-indiekit", "link": true }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/cross-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/cross-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/cross-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7683,6 +7738,69 @@ "node": ">=12 || >=16" } }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/css-select/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/css-tree": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", @@ -7696,6 +7814,17 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -10075,6 +10204,14 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, "node_modules/header-case": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", @@ -13514,6 +13651,15 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-html-parser": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.4.2.tgz", + "integrity": "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==", + "dependencies": { + "css-select": "^4.2.1", + "he": "1.2.0" + } + }, "node_modules/node-machine-id": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", @@ -14143,6 +14289,17 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nunjucks": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", @@ -19313,6 +19470,41 @@ "node": ">=12" } }, + "node_modules/webmention-handler": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/webmention-handler/-/webmention-handler-0.0.8.tgz", + "integrity": "sha512-M+LlBmyjYwcoCuu2fCe+F+JauwLwg8l2706A8QrCP3H/Bz37S8NcE01wBEylxTToAgYHjowQ9TRNNbplYTwPQA==", + "dependencies": { + "cross-fetch": "^3.1.5", + "microformats-parser": "^1.4.1", + "node-html-parser": "^5.3.3" + } + }, + "node_modules/webmention-handler-mongodb": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/webmention-handler-mongodb/-/webmention-handler-mongodb-0.2.1.tgz", + "integrity": "sha512-rmFdYnDVbkZvTBSJVgLHHQf+0LDW94HXD8jKOHf1Lzf8+iCILNiF6TkiLfTD+L3KPE7OPe+iCTuSt3XRgQUakg==", + "dependencies": { + "mongodb": "^4.8.1", + "webmention-handler": "^0.0.8" + } + }, + "node_modules/webmention-handler/node_modules/microformats-parser": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/microformats-parser/-/microformats-parser-1.5.2.tgz", + "integrity": "sha512-EcHm8zxEm3CggOLgILfxCo2wDiJEOnACzpV/FXWGLaRk24ECei+JkoWNdKdo2vzo/Pww9EvrQNeQsdv4JuHy7Q==", + "dependencies": { + "parse5": "^6.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webmention-handler/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, "node_modules/well-known-symbols": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", @@ -20020,6 +20212,19 @@ "node": ">=20" } }, + "packages/endpoint-webmention": { + "name": "@indiekit/endpoint-webmention", + "version": "1.0.0-beta.6", + "license": "MIT", + "dependencies": { + "express": "^4.17.1", + "webmention-handler": "^0.0.8", + "webmention-handler-mongodb": "^0.2.1" + }, + "engines": { + "node": ">=20" + } + }, "packages/error": { "name": "@indiekit/error", "version": "1.0.0-beta.6", @@ -20092,6 +20297,7 @@ "@indiekit/endpoint-posts": "^1.0.0-beta.7", "@indiekit/endpoint-share": "^1.0.0-beta.6", "@indiekit/endpoint-syndicate": "^1.0.0-beta.6", + "@indiekit/endpoint-webmention": "^1.0.0-beta.6", "@indiekit/frontend": "^1.0.0-beta.7", "@indiekit/preset-jekyll": "^1.0.0-beta.6", "@indiekit/util": "^1.0.0-beta.6", diff --git a/packages/endpoint-webmention/assets/icon.svg b/packages/endpoint-webmention/assets/icon.svg new file mode 100644 index 000000000..1020e1ec3 --- /dev/null +++ b/packages/endpoint-webmention/assets/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/endpoint-webmention/index.js b/packages/endpoint-webmention/index.js new file mode 100644 index 000000000..015676b12 --- /dev/null +++ b/packages/endpoint-webmention/index.js @@ -0,0 +1,70 @@ +import express from "express"; +import { webmentionHandler } from "./lib/webmention-handler.js"; + +const defaults = { mountPath: "/webmention" }; +const router = express.Router(); // eslint-disable-line new-cap + +export default class MicropubEndpoint { + constructor(options = {}) { + this.id = "endpoint-webmention"; + this.meta = import.meta; + this.name = "Webmention endpoint"; + this.options = { ...defaults, ...options }; + this.mountPath = this.options.mountPath; + } + + _routes(indiekitConfig) { + const { me } = indiekitConfig.publication; + const { mongodbUrl } = indiekitConfig.application; + + router.post("/", async function (request, response) { + const hander = webmentionHandler(me, mongodbUrl); + const { source, target } = request.body; + + try { + const mentionResponse = await hander.addPendingMention(source, target); + + response.status(mentionResponse.code).send("Accepted"); + } catch (error) { + response.status(400).send(error.message); + } + }); + + router.post("/", async function (request, response) { + const hander = webmentionHandler(me, mongodbUrl); + const { source, target } = request.body; + console.error("source", source); + + try { + const mentionResponse = await hander.addPendingMention(source, target); + + response.status(mentionResponse.code).send("Accepted"); + } catch (error) { + console.error(error); + response.status(400).send(error.message); + } + }); + + router.get("/process", async function (request, response) { + const hander = webmentionHandler(me, mongodbUrl); + + try { + setInterval(() => hander.processPendingMentions(), 0); + + response.status(200).send("Done"); + } catch (error) { + response.status(500).send(error.message); + } + }); + + return router; + } + + init(Indiekit) { + Indiekit.addEndpoint(this); + + // Use private value to register Webmention endpoint path + Indiekit.config.application._webmentionEndpointPath = + this.options.mountPath; + } +} diff --git a/packages/endpoint-webmention/lib/webmention-handler.js b/packages/endpoint-webmention/lib/webmention-handler.js new file mode 100644 index 000000000..183947c6a --- /dev/null +++ b/packages/endpoint-webmention/lib/webmention-handler.js @@ -0,0 +1,23 @@ +import { WebMentionHandler } from "webmention-handler"; +import { MongoWebMentionStorage } from "webmention-handler-mongodb"; + +export const webmentionHandler = (me, mongodbUrl) => { + const { host } = new URL(me); + + const storage = new MongoWebMentionStorage({ + databaseUri: mongodbUrl, + dbName: "indiekit", + mentionCollection: "mentions", + pendingCollection: "mentions-pending", + maxPendingFetch: 100, + limitMentionsPerPageFetch: 50, + }); + + const handler = new WebMentionHandler({ + supportedHosts: [host], + storageHandler: storage, + // requiredProtocol: "https", + }); + + return handler; +}; diff --git a/packages/endpoint-webmention/package.json b/packages/endpoint-webmention/package.json new file mode 100644 index 000000000..e73e4f4e7 --- /dev/null +++ b/packages/endpoint-webmention/package.json @@ -0,0 +1,43 @@ +{ + "name": "@indiekit/endpoint-webmention", + "version": "1.0.0-beta.6", + "description": "Webmention endpoint for Indiekit.", + "keywords": [ + "indiekit", + "indiekit-plugin", + "indieweb", + "webmention" + ], + "homepage": "https://getindiekit.com", + "author": { + "name": "Paul Robert Lloyd", + "url": "https://paulrobertlloyd.com" + }, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "type": "module", + "main": "index.js", + "files": [ + "assets", + "lib", + "index.js" + ], + "bugs": { + "url": "https://github.com/getindiekit/indiekit/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/getindiekit/indiekit.git", + "directory": "packages/endpoint-webmention" + }, + "dependencies": { + "express": "^4.17.1", + "webmention-handler": "^0.0.8", + "webmention-handler-mongodb": "^0.2.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/indiekit/package.json b/packages/indiekit/package.json index 26d7073b5..df753bd96 100644 --- a/packages/indiekit/package.json +++ b/packages/indiekit/package.json @@ -46,6 +46,7 @@ "@indiekit/endpoint-posts": "^1.0.0-beta.7", "@indiekit/endpoint-share": "^1.0.0-beta.6", "@indiekit/endpoint-syndicate": "^1.0.0-beta.6", + "@indiekit/endpoint-webmention": "^1.0.0-beta.6", "@indiekit/frontend": "^1.0.0-beta.7", "@indiekit/preset-jekyll": "^1.0.0-beta.6", "@indiekit/util": "^1.0.0-beta.6",