fix: serve real home page instead of redirecting, add templated pages#409
fix: serve real home page instead of redirecting, add templated pages#409CKodidela wants to merge 7 commits intocameri:mainfrom
Conversation
Previously, visiting the relay's home page resulted in a 301 permanent
redirect to /invoices when payments were enabled, or a bare text response
when payments were disabled. Neither gave relay operators a real home page,
and the 301 caused browsers to cache the redirect permanently — making it
impossible to change home page behaviour later without users clearing cache.
Changes:
- / now always serves index.html, a proper home page that works with and
without payments. When admission fees are enabled, a payment card and
link to /invoices is shown; otherwise an "open relay" card is shown.
Template variables: {{name}}, {{description}}, {{relay_url}}, {{amount}},
{{payments_section_class}}, {{no_payments_section_class}}, {{nonce}}.
- The old index.html (admission fee form) is renamed to get-invoice.html,
served by GET /invoices. The ToS link now points to /terms instead of
an embedded modal.
- The old invoices.html (payment result page) is renamed to post-invoice.html,
served after POST /invoices. The rename reflects what the page actually is.
- privacy.html template added and served at GET /privacy.
- Old index.html and invoices.html remain in resources/ for reference.
|
@cameri this PR up for review |
There was a problem hiding this comment.
Pull request overview
This PR replaces the relay’s previous root-path behavior (301 redirect to /invoices or plain text) with a real, user-modifiable, templated home page, and adds additional user-modifiable legal/payment templates served from resources/.
Changes:
- Serve a templated
resources/index.htmlatGET /, with conditional sections for payments-enabled vs open-relay mode. - Rename/split invoice templates into
get-invoice.html(GET /invoices) andpost-invoice.html(afterPOST /invoices), updating controllers accordingly. - Add
resources/privacy.htmland serve it atGET /privacy.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/routes/index.ts | Adds GET /privacy route wiring. |
| src/handlers/request-handlers/root-request-handler.ts | Stops redirect/text behavior; renders templated home page. |
| src/handlers/request-handlers/get-privacy-request-handler.ts | New handler that renders privacy.html. |
| src/controllers/invoices/get-invoice-controller.ts | Serves get-invoice.html instead of index.html. |
| src/controllers/invoices/post-invoice-controller.ts | Serves post-invoice.html instead of invoices.html. |
| resources/index.html | New home page template with conditional payments/open-relay sections. |
| resources/get-invoice.html | New “get invoice” page template (formerly home page admission form). |
| resources/post-invoice.html | New “post invoice” page template (formerly invoices.html). |
| resources/privacy.html | New privacy policy template. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@CKodidela Please address reviews by Copilot. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@cameri @phoenix-server I’ve addressed the issues raised by Copilot, please take a look when you get a chance. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const HTML_ESCAPES: Record<string, string> = { | ||
| '&': '&', | ||
| '<': '<', | ||
| '>': '>', | ||
| '"': '"', | ||
| "'": ''', | ||
| } | ||
|
|
||
| /** | ||
| * Escape a string for safe interpolation into HTML text or attribute values. | ||
| */ | ||
| export const escapeHtml = (value: string): string => | ||
| value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch]) | ||
|
|
||
| /** | ||
| * Serialize a value for safe embedding inside an inline <script> block. | ||
| * | ||
| * JSON.stringify alone is NOT sufficient: it leaves `<` unescaped, so a value | ||
| * containing `</script>` would terminate the script block and allow injection. | ||
| * After serializing, replace every `<` with the Unicode escape `\u003C`, which | ||
| * is valid JSON and prevents the browser from treating the character as markup. | ||
| */ | ||
| export const safeJsonForScript = (value: unknown): string => | ||
| JSON.stringify(value).replace(/</g, '\\u003C') |
There was a problem hiding this comment.
New escaping utilities (escapeHtml / safeJsonForScript) are security-critical and currently have no unit tests. Adding tests for common and edge cases (quotes/ampersands, '<' in JSON, undefined handling, etc.) would help prevent regressions.
| import { readFileSync } from 'fs' | ||
|
|
||
| const cache = new Map<string, string>() | ||
| const isProd = process.env.NODE_ENV === 'production' | ||
|
|
||
| /** | ||
| * Return the raw content of a template file. | ||
| * | ||
| * In production (NODE_ENV=production) the file is read from disk once and | ||
| * cached for the lifetime of the process — no per-request I/O. Operators who | ||
| * edit files under resources/ must restart the process for changes to take | ||
| * effect. | ||
| * | ||
| * Outside of production the cache is bypassed so template edits are reflected | ||
| * immediately without a restart. | ||
| */ | ||
| export const getTemplate = (path: string): string => { | ||
| if (isProd) { | ||
| let template = cache.get(path) | ||
| if (template === undefined) { | ||
| template = readFileSync(path, 'utf8') | ||
| cache.set(path, template) | ||
| } | ||
| return template | ||
| } | ||
|
|
||
| return readFileSync(path, 'utf8') | ||
| } |
There was a problem hiding this comment.
getTemplate introduces environment-dependent caching behavior but currently has no unit tests. Adding tests for production vs non-production behavior (cache hit/miss, bypass in dev) would help ensure templates render consistently and avoid regressions.
There was a problem hiding this comment.
Agreed with Copilot here: let's add unit tests.
|
@cameri should i add test coverage? |
|
@CKodidela Yes please, unit tests are key against regressions, code correctness, and overall quality. |
cache, request handlers, and invoice controller
…rs, post-invoice controller
|
I'm still working on some issues will tag when its ready |
|
Up for review @cameri @phoenix-server |
Previously, visiting the relay's home page resulted in a 301 permanent redirect to /invoices when payments were
enabled, or a bare text response when payments were disabled. Neither gave relay operators a real home page, and the
301 caused browsers to cache the redirect permanently making it impossible to change home page behaviour later
without users clearing cache.
Changes:
enabled, a payment card and link to /invoices is shown; otherwise an "open relay" card is shown. Template variables:
{{name}}, {{description}}, {{relay_url}}, {{amount}}, {{payments_section_class}}, {{no_payments_section_class}},
{{nonce}}.
points to /terms instead of an embedded modal.
reflects what the page actually is.
Description
Replaces the broken home page behaviour with a proper templated home page. All five templates (
index.html,get-invoice.html,post-invoice.html,terms.html,privacy.html) are user-modifiable files inresources/. Thehome page conditionally shows or hides the admission fee section using CSS class template variables, so it works
correctly whether payments are enabled or not.
Related Issue
Closes #269
Motivation and Context
The 301 redirect was a bug browsers cache it permanently, so any future change to the home page would be invisible
to returning visitors until they manually cleared their cache. Beyond the redirect fix, relay operators (especially
those running private/whitelist-only relays with no payments) had no home page at all, and there was no privacy policy
page despite many jurisdictions requiring one.
How Has This Been Tested?
Manually verified both payment-enabled and payment-disabled paths render the correct home page content. Verified
GET /invoicesservesget-invoice.html,POST /invoicesservespost-invoice.html,GET /termsandGET /privacyboth render correctly. Confirmed
application/nostr+jsonrequests to/still return the relay information documentunchanged.
Types of changes
Checklist