fix(invites): build share URL on the client, not the server

server/invites.ts derived the share URL from c.req.url — i.e., from
the API request's host. In production the API and SPA share an
origin so this happened to work; in dev where the SPA runs on :5173
and the API on :3000, the generated link pointed at the API
(http://localhost:3000/invite/<token>) which serves nothing.

Fix: the server no longer returns a `url` field. The token is the
canonical artefact; the SPA builds the share link itself via
`${window.location.origin}/invite/${token}`, which is always the
right origin regardless of split-process dev or single-process prod.

  - shared/types.ts: InviteEntry.url removed
  - server/invites.ts: drop originOf() and the URL field in toEntry()
  - frontend Profile.svelte: new inviteUrl() helper; the displayed
    <code> and the clipboard payload both use it
  - tests/social.test.ts: assertion checks token shape instead of
    the URL field

93 tests still pass.
This commit is contained in:
Ole-Morten Duesund 2026-05-25 16:25:55 +02:00
commit e64d5450f8
4 changed files with 27 additions and 16 deletions

View file

@ -167,13 +167,22 @@
}
}
/** Build the shareable invite URL using the SPA's own origin. The server
* used to compute this but in dev that yielded the API host (port 3000),
* not the SPA host (port 5173). The token is the canonical artefact;
* the URL is just a presentation concern the SPA owns. */
function inviteUrl(inv: InviteEntry): string {
return `${window.location.origin}/invite/${inv.token}`;
}
async function copyInviteUrl(inv: InviteEntry) {
const url = inviteUrl(inv);
try {
await navigator.clipboard.writeText(inv.url);
await navigator.clipboard.writeText(url);
copiedToken = inv.token;
setTimeout(() => { copiedToken = null; }, 1500);
} catch {
window.prompt('Kopier denne lenken:', inv.url);
window.prompt('Kopier denne lenken:', url);
}
}
@ -372,7 +381,7 @@
{#each invites as inv (inv.token)}
<article class="card" style="margin-top: 0.5rem; {inv.claimed_at ? 'opacity: 0.7;' : ''}">
<div class="row" style="justify-content: space-between;">
<code style="font-size: 0.8rem; word-break: break-all;">{inv.url}</code>
<code style="font-size: 0.8rem; word-break: break-all;">{inviteUrl(inv)}</code>
<span class="muted" style="white-space: nowrap;">{formatDate(inv.created_at)}</span>
</div>
<div class="row" style="margin-top: 0.5rem;">