Skip to content

fix(local): raisely local hangs on every request and drops page overrides - #86

Open
k-r-a-s-s wants to merge 1 commit into
raisely:masterfrom
k-r-a-s-s:fix/local-proxy-hang-and-injection
Open

fix(local): raisely local hangs on every request and drops page overrides#86
k-r-a-s-s wants to merge 1 commit into
raisely:masterfrom
k-r-a-s-s:fix/local-proxy-hang-and-injection

Conversation

@k-r-a-s-s

@k-r-a-s-sk-r-a-s-s commented Aug 28, 2026

Copy link
Copy Markdown

raisely local is unusable in 2.1.0. Two independent bugs, both in src/local.js.

1. Every proxied request hangs

The proxy response handler is registered as onProxyRes — the http-proxy-middleware v2 option name. package.json pins 3.0.5, where handlers live under on: { proxyRes }. v3 ignores the unknown top-level option but still honours selfHandleResponse: true, so the response is handed to a handler that was never registered, nothing writes to res, and the request hangs until the client gives up.

This affects every URL, not just pages — /robots.txt hangs too. Only /reload still answers, because it's a local Express route that never reaches the proxy.

Isolated with a 3-case matrix against the same upstream:

optionsresult
onProxyRes + selfHandleResponse: truecurrenthangs
on: { proxyRes } + selfHandleResponse: trueHTTP 200, ~115 ms
onProxyRes, no selfHandleResponseHTTP 200, ~77 ms

followRedirects and the <path>.raisely.com<path>.raiselysite.com 302 both look suspicious and are both red herrings — the hang reproduces against a direct-200 target too.

2. Local page overrides silently never apply

The generated override payload is passed to String.prototype.replace as a replacement string:

output.replace(afterCampaignBootstrap,`$1${pageOverride}$2`)

The payload embeds page copy verbatim, so $1$9, $&, $`, $' and $$ appearing in that copy are interpreted as replacement patterns. A page on our campaign contains the copy "surviving on less than $2 a day". That $2 expands to capture group 2 — the matched <script>if (window.campaign) { text — which gets spliced into the middle of a JSON string inside the injected script. The script no longer parses, so it never runs and window.pageSchemas keeps the server's compiled templates.

Two things make this hard to spot:

  • It fails campaign-wide, not per page. Every page's compiled template ships in that one script, so a single page containing a dollar amount disables local page editing for every page.
  • It fails silently. The only signal is a console SyntaxError: Invalid or unexpected token on an unrelated-looking line.

Currency in page copy isn't an edge case on a fundraising platform.

Changes

  • Extract createCampaignProxy(); register the handler under on: { proxyRes }.
  • Extract injectPageOverride(); insert the payload with replacer functions, where $ has no special meaning. Applied the same fix to the two fallback injection branches (<!-- _footer_integrations_ --> and </body>) and the <head> API-redirect injection — same latent hazard.
  • Add tests/local-proxy.test.js.

Both new tests are true regression tests — I re-introduced each bug and confirmed they fail:

  • bug 2 → 7 failures across the $-token cases, including a full-path case that runs a real buildPageOverrideScript output through vm.Script to assert it parses
  • bug 1 → the proxy test fails with an explicit message rather than an opaque timeout

Verification

npm test: 116 passed (13 files).

Against a live campaign with the branch installed:

  • proxy returns in ~0.3 s (/robots.txt 200, /en/terms 200)
  • override map parses as valid JSON and carries 72 of 72 local page files
  • an un-deployed local edit to a page JSON renders at localhost:8015, and the pageSchemas entry is patched at DOMContentLoaded
  • no page errors

Note on formatting

I matched the surrounding file style rather than running Prettier. Upstream master doesn't currently pass prettier --check (9 files warn, no config committed), so a repo-wide reformat would have buried the fix. Happy to add one if you'd prefer.

Unrelated to (and independent of) #85 — different file, branches off master cleanly.


Note

Medium Risk
Touches the core local dev proxy and HTML rewriting path; mistakes could break all proxied traffic or corrupt injected scripts, but changes are narrowly scoped with dedicated regression tests.

Overview
Fixes two regressions that made raisely local hang on proxied requests and silently skip local page overrides.

Proxy: Introduces createCampaignProxy and wires the response interceptor under on: { proxyRes } (http-proxy-middleware v3). The old top-level onProxyRes was ignored while selfHandleResponse still applied, so nothing wrote a response.

HTML injection: Introduces injectPageOverride and uses String.replace replacer functions (not replacement strings) when inserting the page-override script and related head/body patches, so copy with $1, $2, $&, etc. is not mangled. The main dev server path now calls these helpers instead of inlined replace logic.

Adds tests/local-proxy.test.js with regression coverage for dollar-sign payloads, injection placement, and a live proxy round-trip.

Reviewed by Cursor Bugbot for commit cbb9608. Bugbot is set up for automated code reviews on this repo. Configure here.

…rrides
Two independent bugs make `raisely local` unusable in 2.1.0.
1. Every proxied request hangs.
The proxy response handler is registered as `onProxyRes`, which is the
http-proxy-middleware **v2** option name. package.json pins v3.0.5, where
handlers live under `on: { proxyRes }`. v3 ignores the unknown top-level
option but still honours `selfHandleResponse: true`, so the response is
handed to a handler that was never registered, nothing writes to `res`, and
the request hangs until the client times out.
This affects every URL, not just pages -- `/robots.txt` hangs too. Only
`/reload` still answers, because it is a local Express route that never
reaches the proxy.
2. Local page overrides silently never apply.
The generated override payload is passed to `String.prototype.replace` as a
REPLACEMENT STRING:
output.replace(afterCampaignBootstrap, `$1${pageOverride}$2`)
The payload embeds page copy verbatim, so `$1`..`$9`, `$&`, `` $` ``, `$'`
and `$$` in that copy are interpreted as replacement patterns. A campaign
page containing "surviving on less than $2 a day" expands `$2` to capture
group 2 -- the matched `<script>if (window.campaign) {` text -- splicing it
into the middle of a JSON string inside the injected script. The script no
longer parses, so it never runs and `window.pageSchemas` keeps the server's
compiled templates.
The failure is campaign-wide rather than page-scoped, because every page's
compiled template ships in that one script: a single page containing a
dollar amount disables local page editing for the whole campaign. It is also
silent -- the only signal is a console SyntaxError on an unrelated-looking
line. Currency in page copy is not an edge case on a fundraising platform.
Changes:
- Extract `createCampaignProxy()` and register the handler under
`on: { proxyRes }`.
- Extract `injectPageOverride()` and insert the payload with replacer
FUNCTIONS, where `$` has no special meaning. Same fix applied to the two
fallback injection branches and the `<head>` API-redirect injection, which
had the same latent hazard.
- Add tests/local-proxy.test.js. Both are true regression tests: restoring
either bug fails them (the proxy test with an explicit message rather than an
opaque timeout).
Formatting follows the surrounding file. Upstream does not currently pass
`prettier --check` (9 files), so no repo-wide reformat is included here.
Verified against a live campaign: proxy returns in ~0.3s, the override map
carries all 72 local pages, and an un-deployed local edit to a page JSON
renders at localhost:8015.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@k-r-a-s-s