Skip to content

fix(ui): decode room id from URL and encode it when writing back - #254

Merged
jmattheis merged 1 commit into
screego:masterfrom
LandmineHQ:fix/room-id-url-encoding
Sep 27, 2026
Merged

jmattheis merged 1 commit into
screego:masterfrom
LandmineHQ:fix/room-id-url-encoding

Conversation

@LandmineHQ

Copy link
Copy Markdown
Contributor

fix(ui): decode the room id from the URL and encode it when writing back

Fixes #253

Problem

ui/src/useRoomID.ts reads and writes the room query parameter by hand — no decode on read, no
encode on write:

search.slice(1).split('&').find((param) => param.startsWith(`${key}=`))?.split('=')[1]
window.history.pushState({roomId: id}, '', id ? `?room=${id}` : '?');

Browsers percent-encode non-ASCII names when serializing a URL, so a room named 不辞 is shared as
?room=%E4%B8%8D%E8%BE%9E, but the client then joins the literal %E4%B8%8D%E8%BE%9E room and the
server closes the connection with room with id %E4%B8%8D%E8%BE%9E does not exist. Ids containing
&, =, #, + or % break in the same way, and writing the escaped value back adds another
escaping level on every share.

Changes

Only ui/src/useRoomID.ts: the query string now goes through URLSearchParams, which decodes once
on read and encodes once on write.

-export const getFromURL = (key: string, search = window.location.search): string | undefined =>
-    search
-        .slice(1)
-        .split('&')
-        .find((param) => param.startsWith(`${key}=`))
-        ?.split('=')[1];
+export const getFromURL = (key: string, search = window.location.search): string | undefined =>
+    new URLSearchParams(search).get(key) ?? undefined;

                     if (oldId !== id) {
-                        window.history.pushState({roomId: id}, '', id ? `?room=${id}` : '?');
+                        const params = new URLSearchParams();
+                        if (id) {
+                            params.set('room', id);
+                        }
+                        const query = params.toString();
+                        window.history.pushState({roomId: id}, '', query ? `?${query}` : '?');
                     }

No server change: the room id stays the raw string used by the create/join payload, and existing
?room=%E4%B8%8D%E8%BE%9E links work again without migration.

Verification

  • cd ui && yarn build (tsc + vite) passes
  • prettier --check src/useRoomID.ts passes
  • Browser: 不辞 ⇄ ?room=%E4%B8%8D%E8%BE%9E round-trips stably (repeated writes do not re-escape);
    my room, a+b, a&b=c and 100% round-trip correctly as well
  • Live 1.12.5 server: joining with the encoded id fails (room with id %E4%B8%8D%E8%BE%9E ... does not exist), joining with the decoded id succeeds, and the test room is removed after its owner leaves

The room id was read from the query string with a hand-rolled parser that
never decoded it, while the id was written back into the URL without any
encoding:

    getFromURL = (key, search) =>
        search.slice(1).split('&')
            .find((param) => param.startsWith(`${key}=`))?.split('=')[1];

    window.history.pushState({roomId: id}, '', id ? `?room=${id}` : '?');

Browsers percent-encode non-ASCII characters when a URL is serialized, so a
room named "不辞" is shared as `?room=%E4%B8%8D%E8%BE%9E`, but the client then
joins the literal `%E4%B8%8D%E8%BE%9E` room, which does not exist:

    -> {"type":"join","payload":{"id":"%E4%B8%8D%E8%BE%9E"}}
    <- close 1000 "room with id %E4%B8%8D%E8%BE%9E does not exist"

The same asymmetry also broke ids containing `&`, `=`, `+`, `#` or `%`, and
made ids that already contain percent escapes gain another encoding level on
every write, producing broken (double encoded) share links.

Use the platform URL API, which decodes on read and encodes on write exactly
once:

    new URLSearchParams(search).get(key) ?? undefined
    const params = new URLSearchParams();
    params.set('room', id);
    window.history.pushState({roomId: id}, '', `?${params.toString()}`);

No server change is needed: the room id stays the raw, human-readable string
that the ws `create`/`join` payload and the server-side room map already use,
and existing share links keep working.

Verified: URL round-trip for 不辞 / "my room" / "a+b" / "a&b=c" / "100%",
protocol-level e2e against a live server (encoded id -> "does not exist",
decoded id -> joins, cleanup on owner leave), `yarn build` (tsc + vite) and
`prettier --check src/useRoomID.ts`.

@jmattheis jmattheis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@jmattheis
jmattheis merged commit 4ae5f44 into screego:master Sep 27, 2026
1 check passed
Sign up for free to 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.

[BUG] Room id with non-ASCII characters is not URL-decoded, so share links cannot join the room

2 participants