Skip to content

Commit d3deaa4

Browse files
markets/: fix the share-entry regex that killed the client, and guard the class
ui.js is one big server-side template literal, so a backslash escape is consumed before the browser ever sees it: my /\/m\// share-path regex arrived as //m// and the ENTIRE app died on "Unexpected token 'const'" — the market list hung on "Loading markets…" and nothing worked. Rewritten with plain string operations, which are immune. The meta-tag tests could not see this: they assert on <head> while the damage was in <script>. Added a parse guard that compiles every inline script with new Function (compile, not execute) across both the plain and per-market render paths, so a template-literal escape can never take the app down silently again. 78 tests.
1 parent 6b21bfc commit d3deaa4

2 files changed

Lines changed: 24 additions & 2 deletions

File tree

markets/test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,23 @@ describe('markets plugin', () => {
341341
assert.ok(sorted, 'ordered by net worth, descending');
342342
});
343343

344+
it('the emitted client script actually parses (template-literal escapes bite)', async () => {
345+
// ui.js is one big server-side template literal, so a backslash escape
346+
// is consumed before the browser sees it: a regex like /\\/m\\// arrives
347+
// as //m// and the ENTIRE app dies with "Unexpected token". Meta-tag
348+
// assertions cannot see that; parsing can.
349+
const { renderUi } = await import('./ui.js');
350+
for (const opts of [{}, { accounts: true, brand: 'x', market: { id: 'a', title: 'T', category: 'C', status: 'open', closesAt: Date.now() + 8.64e7, outcomes: ['Yes', 'No'], prices: [0.5, 0.5] } }]) {
351+
const html = renderUi('/markets', opts);
352+
const scripts = [...html.matchAll(/<script(?![^>]*src=)[^>]*>([\s\S]*?)<\/script>/g)].map((m) => m[1]);
353+
assert.ok(scripts.length >= 1, 'the page ships an inline script');
354+
for (const src of scripts) {
355+
// new Function COMPILES without executing: a SyntaxError throws here.
356+
assert.doesNotThrow(() => new Function(src), 'inline client script must parse');
357+
}
358+
}
359+
});
360+
344361
it('exposes the category facets for topic browsing', async () => {
345362
const { categories } = await json(await call(null, 'GET', '/categories'), 200);
346363
assert.ok(Array.isArray(categories), 'a facet list is public');

markets/ui.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,9 +1618,14 @@ ${ogMeta}
16181618
// Both server-visible forms are honoured: the path (/m/<id>) and the
16191619
// query (?m=<id>). A fragment never reaches the server, which is why
16201620
// shared #m/ links unfurled as the generic card.
1621-
const byPath = /\/m\/([A-Za-z0-9_-]+)\/?$/.exec(location.pathname);
1621+
// NO REGEX HERE: this file is one big server-side template literal, so
1622+
// a backslash escape is consumed before the browser ever sees it and
1623+
// /\/m\// arrives as //m// — which is a syntax error that takes the
1624+
// whole app down. Plain string work is immune.
1625+
const seg = location.pathname.split('/').filter(Boolean);
1626+
const byPath = seg.length >= 2 && seg[seg.length - 2] === 'm' ? seg[seg.length - 1] : null;
16221627
const byQuery = new URLSearchParams(location.search).get('m');
1623-
const id = (byPath && byPath[1]) || byQuery;
1628+
const id = byPath || byQuery;
16241629
if (!id || location.hash) return;
16251630
history.replaceState(null, '', (PREFIX || '/') + '#m/' + id);
16261631
})();

0 commit comments

Comments
 (0)