From 493ae2442c4778e8994762852b63a4366d124aef Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 5 Mar 2026 10:20:19 -0800 Subject: [PATCH] fix: replace broken Postiz reverse proxy with iframe embed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reverse proxy couldn't work for a Next.js SPA — it caused double space prefix in redirects and couldn't handle /_next/* static assets. Switch to iframe approach with demo.rsocials.online directly. Fix template literal bugs where getSchedulerUrl() was passed as a string literal instead of being evaluated. Co-Authored-By: Claude Opus 4.6 --- .../components/folk-socials-canvas.ts | 6 +- modules/rsocials/mod.ts | 92 +++---------------- 2 files changed, 18 insertions(+), 80 deletions(-) diff --git a/modules/rsocials/components/folk-socials-canvas.ts b/modules/rsocials/components/folk-socials-canvas.ts index eacbfed..6f01023 100644 --- a/modules/rsocials/components/folk-socials-canvas.ts +++ b/modules/rsocials/components/folk-socials-canvas.ts @@ -164,7 +164,7 @@ class FolkSocialsCanvas extends HTMLElement { } private getSchedulerUrl(): string { - return `${this.getApiBase()}/scheduler`; + return "https://demo.rsocials.online"; } private buildCanvasNodes() { @@ -407,7 +407,7 @@ class FolkSocialsCanvas extends HTMLElement { Postiz — Post Scheduler - + @@ -423,7 +423,7 @@ class FolkSocialsCanvas extends HTMLElement { const panel = this.shadow.getElementById("postiz-panel"); const iframe = panel?.querySelector("iframe"); if (panel) panel.classList.toggle("open", this.postizOpen); - if (iframe && this.postizOpen) iframe.src = "${this.getSchedulerUrl()}"; + if (iframe && this.postizOpen) iframe.src = this.getSchedulerUrl(); if (iframe && !this.postizOpen) iframe.src = "about:blank"; }); this.shadow.getElementById("close-postiz")?.addEventListener("click", () => { diff --git a/modules/rsocials/mod.ts b/modules/rsocials/mod.ts index f4242cd..04b3492 100644 --- a/modules/rsocials/mod.ts +++ b/modules/rsocials/mod.ts @@ -514,84 +514,22 @@ function renderDemoFeedHTML(): string { // ── Path-based sub-routes ── -// ── Postiz reverse proxy at /scheduler/* ── -// Proxies to the Postiz container on the Docker network, avoiding -// the Traefik priority conflict with rSpace's rsocials.online catch-all. -const POSTIZ_UPSTREAM = process.env.POSTIZ_URL || "http://postiz:5000"; +// ── Postiz scheduler — embedded via iframe ── +// Postiz runs at demo.rsocials.online (Traefik priority 130 > rSpace's 120). +// The /scheduler route renders a full-page iframe shell. +const POSTIZ_URL = process.env.POSTIZ_URL || "https://demo.rsocials.online"; -routes.all("/scheduler/*", async (c) => { - const subPath = c.req.path.replace(/^\/[^/]+\/rsocials\/scheduler/, "") || "/"; - const upstreamUrl = `${POSTIZ_UPSTREAM}${subPath}`; - const url = new URL(upstreamUrl); - // Preserve query string - const reqUrl = new URL(c.req.url); - url.search = reqUrl.search; - - try { - const headers = new Headers(c.req.raw.headers); - headers.delete("host"); - - const resp = await fetch(url.toString(), { - method: c.req.method, - headers, - body: c.req.method !== "GET" && c.req.method !== "HEAD" ? c.req.raw.body : undefined, - redirect: "manual", - }); - - // Rewrite Location headers to stay within /scheduler/ - const respHeaders = new Headers(resp.headers); - const location = respHeaders.get("location"); - if (location) { - try { - const locUrl = new URL(location, upstreamUrl); - if (locUrl.origin === new URL(POSTIZ_UPSTREAM).origin) { - const space = c.req.param("space") || "demo"; - respHeaders.set("location", `/${space}/rsocials/scheduler${locUrl.pathname}${locUrl.search}`); - } - } catch { /* keep original */ } - } - - return new Response(resp.body, { - status: resp.status, - headers: respHeaders, - }); - } catch { - return c.text("Postiz scheduler unavailable", 502); - } -}); - -routes.get("/scheduler", async (c) => { +routes.get("/scheduler", (c) => { const space = c.req.param("space") || "demo"; - const upstreamUrl = `${POSTIZ_UPSTREAM}/`; - try { - const headers = new Headers(c.req.raw.headers); - headers.delete("host"); - const reqUrl = new URL(c.req.url); - - const resp = await fetch(`${upstreamUrl}${reqUrl.search}`, { - method: "GET", - headers, - redirect: "manual", - }); - - const respHeaders = new Headers(resp.headers); - const location = respHeaders.get("location"); - if (location) { - try { - const locUrl = new URL(location, upstreamUrl); - if (locUrl.origin === new URL(POSTIZ_UPSTREAM).origin) { - respHeaders.set("location", `/${space}/rsocials/scheduler${locUrl.pathname}${locUrl.search}`); - } - } catch { /* keep original */ } - } - - return new Response(resp.body, { - status: resp.status, - headers: respHeaders, - }); - } catch { - return c.text("Postiz scheduler unavailable", 502); - } + return c.html(renderExternalAppShell({ + title: `Post Scheduler — rSocials | rSpace`, + moduleId: "rsocials", + spaceSlug: space, + modules: getModuleInfoList(), + theme: "dark", + appName: "Postiz", + appUrl: POSTIZ_URL, + })); }); routes.get("/feed", (c) => { @@ -660,7 +598,7 @@ export const socialsModule: RSpaceModule = { // Run migration for any existing file-based threads try { await migrateFileThreadsToAutomerge("demo"); } catch { /* ignore */ } }, - externalApp: { url: "https://demo.rsocials.online", name: "Postiz" }, + externalApp: { url: POSTIZ_URL, name: "Postiz" }, feeds: [ { id: "social-feed",