From bb37727f188cff4f430e9241e334ac2d0d949dea Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 4 Mar 2026 21:36:03 -0800 Subject: [PATCH] feat: reverse-proxy Postiz at /rsocials/scheduler instead of iframe The rSpace Traefik router catches demo.rsocials.online (priority 120) before the Postiz container can serve it, making iframe embedding fail. Replace the iframe approach with a reverse proxy: /rsocials/scheduler/* proxies to the Postiz container (postiz:5000) on the Docker network. Rewrites redirect headers to stay within the scheduler path. Co-Authored-By: Claude Opus 4.6 --- .../components/folk-socials-canvas.ts | 8 +- modules/rsocials/mod.ts | 87 ++++++++++++++++--- 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/modules/rsocials/components/folk-socials-canvas.ts b/modules/rsocials/components/folk-socials-canvas.ts index 484289a..eacbfed 100644 --- a/modules/rsocials/components/folk-socials-canvas.ts +++ b/modules/rsocials/components/folk-socials-canvas.ts @@ -163,6 +163,10 @@ class FolkSocialsCanvas extends HTMLElement { return match ? match[0] : ""; } + private getSchedulerUrl(): string { + return `${this.getApiBase()}/scheduler`; + } + private buildCanvasNodes() { this.canvasNodes = []; for (const c of this.campaigns) { @@ -403,7 +407,7 @@ class FolkSocialsCanvas extends HTMLElement { Postiz — Post Scheduler - + @@ -419,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 = "https://demo.rsocials.online"; + 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 ceab505..f4242cd 100644 --- a/modules/rsocials/mod.ts +++ b/modules/rsocials/mod.ts @@ -514,17 +514,84 @@ function renderDemoFeedHTML(): string { // ── Path-based sub-routes ── -routes.get("/scheduler", (c) => { +// ── 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"; + +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) => { const space = c.req.param("space") || "demo"; - return c.html(renderExternalAppShell({ - title: `${space} — Postiz | rSpace`, - moduleId: "rsocials", - spaceSlug: space, - modules: getModuleInfoList(), - appUrl: "https://demo.rsocials.online", - appName: "Postiz", - theme: "dark", - })); + 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); + } }); routes.get("/feed", (c) => {