From a2e2764108881f9ea97b5a9eefe122a7ffce9bc9 Mon Sep 17 00:00:00 2001 From: Sven Date: Mon, 16 Mar 2026 20:35:50 +0100 Subject: [PATCH] fix(patches): extend internal URL rewrite to microsub, activitypub, and @rmdes posts The localhost rewrite only covered endpoint-syndicate and endpoint-share. Add coverage for 4 more files that also self-fetch via the public URL: - @rmdes/indiekit-endpoint-microsub reader.js (2 fetch calls) - @rmdes/indiekit-endpoint-activitypub compose.js (2 fetch calls) - @rmdes/indiekit-endpoint-posts utils.js and endpoint.js Co-Authored-By: Claude Opus 4.6 --- scripts/patch-micropub-fetch-internal-url.mjs | 160 +++++++++++++++--- 1 file changed, 140 insertions(+), 20 deletions(-) diff --git a/scripts/patch-micropub-fetch-internal-url.mjs b/scripts/patch-micropub-fetch-internal-url.mjs index 7cdc52be..24cb17cf 100644 --- a/scripts/patch-micropub-fetch-internal-url.mjs +++ b/scripts/patch-micropub-fetch-internal-url.mjs @@ -1,13 +1,14 @@ /** - * Patch: rewrite micropub self-fetch URLs to localhost in endpoint-syndicate - * and endpoint-share. + * Patch: rewrite micropub/microsub self-fetch URLs to localhost. * - * Same issue as endpoint-posts: behind a reverse proxy (nginx in a separate - * FreeBSD jail), Node can't reach its own public HTTPS URL because port 443 - * only exists on the nginx jail. - * - * Rewrites fetch(application.micropubEndpoint, ...) to use + * Behind a reverse proxy (nginx in a separate FreeBSD jail), Node can't + * reach its own public HTTPS URL because port 443 only exists on the + * nginx jail. Rewrites self-referential fetch URLs to use * http://localhost: instead. + * + * Covers: endpoint-syndicate, endpoint-share, endpoint-microsub reader, + * endpoint-activitypub compose, endpoint-posts utils, and the @rmdes + * endpoint-posts endpoint.js copy. */ import { access, readFile, writeFile } from "node:fs/promises"; @@ -29,20 +30,136 @@ function _toInternalUrl(url) { } `; +// Each target defines one or more string replacements in a single file. +// The helper block is inserted after the last import statement. const targets = [ + // --- endpoint-syndicate --- { paths: [ "node_modules/@indiekit/endpoint-syndicate/lib/controllers/syndicate.js", ], - oldSnippet: ` const micropubResponse = await fetch(application.micropubEndpoint, {`, - newSnippet: ` const micropubResponse = await fetch(_toInternalUrl(application.micropubEndpoint), {`, + replacements: [ + { + old: ` const micropubResponse = await fetch(application.micropubEndpoint, {`, + new: ` const micropubResponse = await fetch(_toInternalUrl(application.micropubEndpoint), {`, + }, + ], }, + // --- endpoint-share --- { paths: [ "node_modules/@indiekit/endpoint-share/lib/controllers/share.js", ], - oldSnippet: ` const micropubResponse = await fetch(application.micropubEndpoint, {`, - newSnippet: ` const micropubResponse = await fetch(_toInternalUrl(application.micropubEndpoint), {`, + replacements: [ + { + old: ` const micropubResponse = await fetch(application.micropubEndpoint, {`, + new: ` const micropubResponse = await fetch(_toInternalUrl(application.micropubEndpoint), {`, + }, + ], + }, + // --- microsub reader: URL construction + 2 fetch calls --- + { + paths: [ + "node_modules/@rmdes/indiekit-endpoint-microsub/lib/controllers/reader.js", + ], + replacements: [ + // getSyndicationTargets: rewrite the built micropubUrl + { + old: ` const micropubUrl = micropubEndpoint.startsWith("http") + ? micropubEndpoint + : new URL(micropubEndpoint, application.url).href; + + const configUrl = \`\${micropubUrl}?q=config\`; + const configResponse = await fetch(configUrl, {`, + new: ` const micropubUrl = _toInternalUrl(micropubEndpoint.startsWith("http") + ? micropubEndpoint + : new URL(micropubEndpoint, application.url).href); + + const configUrl = \`\${micropubUrl}?q=config\`; + const configResponse = await fetch(configUrl, {`, + }, + // createPost: rewrite the built micropubUrl + { + old: ` const micropubUrl = micropubEndpoint.startsWith("http") + ? micropubEndpoint + : new URL(micropubEndpoint, application.url).href;`, + new: ` const micropubUrl = _toInternalUrl(micropubEndpoint.startsWith("http") + ? micropubEndpoint + : new URL(micropubEndpoint, application.url).href);`, + }, + ], + }, + // --- activitypub compose: URL construction + 2 fetch calls --- + { + paths: [ + "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/controllers/compose.js", + ], + replacements: [ + // getSyndicationTargets + { + old: ` const micropubUrl = micropubEndpoint.startsWith("http") + ? micropubEndpoint + : new URL(micropubEndpoint, application.url).href; + + const configUrl = \`\${micropubUrl}?q=config\`; + const configResponse = await fetch(configUrl, {`, + new: ` const micropubUrl = _toInternalUrl(micropubEndpoint.startsWith("http") + ? micropubEndpoint + : new URL(micropubEndpoint, application.url).href); + + const configUrl = \`\${micropubUrl}?q=config\`; + const configResponse = await fetch(configUrl, {`, + }, + // post handler: rewrite the built micropubUrl + { + old: ` const micropubUrl = micropubEndpoint.startsWith("http") + ? micropubEndpoint + : new URL(micropubEndpoint, application.url).href;`, + new: ` const micropubUrl = _toInternalUrl(micropubEndpoint.startsWith("http") + ? micropubEndpoint + : new URL(micropubEndpoint, application.url).href);`, + }, + ], + }, + // --- @rmdes endpoint-posts utils.js: URL built from micropubEndpoint --- + { + paths: [ + "node_modules/@rmdes/indiekit-endpoint-posts/lib/utils.js", + ], + replacements: [ + { + old: ` const micropubUrl = new URL(micropubEndpoint);`, + new: ` const micropubUrl = new URL(_toInternalUrl(micropubEndpoint));`, + }, + ], + }, + // --- @rmdes endpoint-posts endpoint.js (separate copy from @indiekit override) --- + { + paths: [ + "node_modules/@rmdes/indiekit-endpoint-posts/lib/endpoint.js", + ], + replacements: [ + { + old: ` const endpointResponse = await fetch(url, { + headers: { + accept: "application/json", + authorization: \`Bearer \${accessToken}\`, + }, + });`, + new: ` const endpointResponse = await fetch(_toInternalUrl(url), { + headers: { + accept: "application/json", + authorization: \`Bearer \${accessToken}\`, + }, + });`, + }, + { + old: ` const endpointResponse = await fetch(url, { + method: "POST",`, + new: ` const endpointResponse = await fetch(_toInternalUrl(url), { + method: "POST",`, + }, + ], }, ]; @@ -67,22 +184,23 @@ for (const target of targets) { continue; } - if (!source.includes(target.oldSnippet)) { - console.warn(`[postinstall] micropub-fetch-internal-url: snippet not found in ${filePath} — skipping`); + // Check that all old snippets exist before patching + const allFound = target.replacements.every((r) => source.includes(r.old)); + if (!allFound) { + const missing = target.replacements + .filter((r) => !source.includes(r.old)) + .map((r) => r.old.slice(0, 60) + "..."); + console.warn(`[postinstall] micropub-fetch-internal-url: snippet not found in ${filePath} — skipping (${missing.length} missing)`); continue; } - // Insert helper block after the last import statement. - // Find the last "from" keyword followed by a string and semicolon, - // which marks the end of the last import. - const importEndPattern = /;\s*\n/g; + // Insert helper block after the last import statement const allImportMatches = [...source.matchAll(/^import\s/gm)]; if (allImportMatches.length === 0) { console.warn(`[postinstall] micropub-fetch-internal-url: no imports found in ${filePath} — skipping`); continue; } - // Find the semicolon+newline that ends the last import block const lastImportStart = allImportMatches.at(-1).index; const afterLastImport = source.slice(lastImportStart); const fromMatch = afterLastImport.match(/from\s+["'][^"']+["']\s*;\s*\n/); @@ -97,8 +215,10 @@ for (const target of targets) { let updated = beforeHelper + "\n" + helperBlock + "\n" + afterHelper; - // Now replace the fetch call - updated = updated.replace(target.oldSnippet, target.newSnippet); + // Apply all replacements + for (const r of target.replacements) { + updated = updated.replace(r.old, r.new); + } await writeFile(filePath, updated, "utf8"); console.log(`[postinstall] Patched micropub-fetch-internal-url in ${filePath}`);