openvibers/openvibe.live Commits
No redirect from /<username>: a channel is only ever /@<username>, so usernames and page routes never compete for the same paths (a user named like a route, or a route added later, can never change where a link goes). A bare username path is a 404 again. The 677 stored Network notifications that linked to /<username> were rewritten to /@<username> at the source instead (backup taken first)
Changes
@@@ -137,25 +137,6 @@ function channelStatus(segment) {} catch { return OK; }}--/**-- * Channel links used to be written without the @ (openvibe.live/<username>, in go-live-- * notifications, Discord posts and follower notices): the canonical `/@<username>[/<slot>]` for-- * a path that names an existing user that way, else null. Only asked for paths no route knows.-- */--const LEGACY_CHANNEL_RE = /^[A-Za-z0-9_]{3,24}$/;--function legacyChannelPath(req) {-- const segs = String(req.path || '/').split('/').filter(Boolean);-- if (!segs.length || segs.length > 2 || !LEGACY_CHANNEL_RE.test(segs[0])) return null;-- if (segs[1] !== undefined && !SLUG_RE.test(segs[1])) return null;-- let row = null;-- try { row = db.getChannelByUsername(segs[0]) || db.getUserByUsername(segs[0]); } catch { return null; }-- const name = row && row.username;-- if (!name || !LEGACY_CHANNEL_RE.test(name)) return null;-- const q = String(req.originalUrl || req.url || '').indexOf('?');-- const query = q >= 0 ? String(req.originalUrl || req.url).slice(q) : '';-- return `/@${name}${segs[1] ? `/${segs[1]}` : ''}${query}`;--}--function streamStatus(id) {if (!ID_RE.test(id)) return NOT_FOUND;try { return db.getStreamById(Number(id)) ? OK : NOT_FOUND; } catch { return OK; }@@@ -201,8 +182,6 @@ function spaFallback(sendShell) {}const status = await statusFor(req);if (res.headersSent) return;-- const channel = status === NOT_FOUND ? legacyChannelPath(req) : null;-- if (channel) return res.redirect(301, channel);try {res.status(status);if (!sendShell(res, req.path)) res.status(503).type('text/plain').send('Site shell unavailable');@@@ -213,4 +192,4 @@ function spaFallback(sendShell) {/** Forget cached lookups (tests; or after a change that must show at once). */function clearCache() { _cache.clear(); }--module.exports = { statusFor, spaFallback, legacyChannelPath, clearCache, EXACT, PREFIX, LOOKUP_DEADLINE_MS };++module.exports = { statusFor, spaFallback, clearCache, EXACT, PREFIX, LOOKUP_DEADLINE_MS };@@@ -145,21 +145,15 @@ server.listen(0, '127.0.0.1', async () => {await check('unknown paths answer 404', () => expectAll(['/nope', '/search', '/login', '/wp-login.php', '/favicon-missing.ico', '/Vods', '/vods/extra', '/updates/1',-- '/@nobody', '/@ab', '/@alice/main/extra', '/@bad.name', '/nobody', '/nobody/main', '/alice/main/extra', '/alice/bad!slot',++ '/@nobody', '/@ab', '/@alice/main/extra', '/@bad.name', '/nobody', '/nobody/main','/vod', '/vod/999', '/vod/abc', '/vod/100/extra', '/clip/999', '/clip/-1','/p/missing', '/p/bad!slug', '/p/abc123/extra','/recap/999999', '/recap/x', '/stream/999999', '/stream',], 404));-- await check('a channel link without the @ redirects to the channel page (old notifications)', async () => {-- for (const [from, to] of [['/alice', '/@alice'], ['/Alice/', '/@alice'], ['/alice/main', '/@alice/main'], ['/bob?stream=5', '/@bob?stream=5']]) {-- for (const html of [false, true]) {-- const r = await get(from, { html });-- assert.strictEqual(r.status, 301, `${from}${html ? ' (html)' : ''} → ${r.status}`);-- assert.strictEqual(r.location, to, `${from} → ${r.location}`);-- }-- }-- assert.strictEqual((await get('/vods')).status, 200, 'a route name is never taken for a user');++ await check('a channel is only ever /@<username>: a bare username is not a page, even for a user that exists', async () => {++ await expectAll(['/alice', '/Alice/', '/alice/main', '/bob'], 404);++ for (const p of ['/alice', '/alice/main']) assert.strictEqual((await get(p)).location, null, `${p} does not redirect`);});await check('a private VOD or clip is a 404 to strangers, with or without a sign-in', async () => {
@@ -137,25 +137,6 @@ function channelStatus(segment) {
@@ -137,25 +137,6 @@ function channelStatus(segment) {
} catch { return OK; }
} catch { return OK; }
}
}
-/**
- * Channel links used to be written without the @ (openvibe.live/<username>, in go-live
- * notifications, Discord posts and follower notices): the canonical `/@<username>[/<slot>]` for
- * a path that names an existing user that way, else null. Only asked for paths no route knows.
- */
-const LEGACY_CHANNEL_RE = /^[A-Za-z0-9_]{3,24}$/;
-function legacyChannelPath(req) {
- const segs = String(req.path || '/').split('/').filter(Boolean);
- if (!segs.length || segs.length > 2 || !LEGACY_CHANNEL_RE.test(segs[0])) return null;
- if (segs[1] !== undefined && !SLUG_RE.test(segs[1])) return null;
- let row = null;
- try { row = db.getChannelByUsername(segs[0]) || db.getUserByUsername(segs[0]); } catch { return null; }
- const name = row && row.username;
- if (!name || !LEGACY_CHANNEL_RE.test(name)) return null;
- const q = String(req.originalUrl || req.url || '').indexOf('?');
- const query = q >= 0 ? String(req.originalUrl || req.url).slice(q) : '';
- return `/@${name}${segs[1] ? `/${segs[1]}` : ''}${query}`;
-}
-
function streamStatus(id) {
function streamStatus(id) {
if (!ID_RE.test(id)) return NOT_FOUND;
if (!ID_RE.test(id)) return NOT_FOUND;
try { return db.getStreamById(Number(id)) ? OK : NOT_FOUND; } catch { return OK; }
try { return db.getStreamById(Number(id)) ? OK : NOT_FOUND; } catch { return OK; }
@@ -201,8 +182,6 @@ function spaFallback(sendShell) {
@@ -201,8 +182,6 @@ function spaFallback(sendShell) {
}
}
const status = await statusFor(req);
const status = await statusFor(req);
if (res.headersSent) return;
if (res.headersSent) return;
- const channel = status === NOT_FOUND ? legacyChannelPath(req) : null;
- if (channel) return res.redirect(301, channel);
try {
try {
res.status(status);
res.status(status);
if (!sendShell(res, req.path)) res.status(503).type('text/plain').send('Site shell unavailable');
if (!sendShell(res, req.path)) res.status(503).type('text/plain').send('Site shell unavailable');
@@ -213,4 +192,4 @@ function spaFallback(sendShell) {
@@ -213,4 +192,4 @@ function spaFallback(sendShell) {
/** Forget cached lookups (tests; or after a change that must show at once). */
/** Forget cached lookups (tests; or after a change that must show at once). */
function clearCache() { _cache.clear(); }
function clearCache() { _cache.clear(); }
-module.exports = { statusFor, spaFallback, legacyChannelPath, clearCache, EXACT, PREFIX, LOOKUP_DEADLINE_MS };
+module.exports = { statusFor, spaFallback, clearCache, EXACT, PREFIX, LOOKUP_DEADLINE_MS };
@@ -145,21 +145,15 @@ server.listen(0, '127.0.0.1', async () => {
@@ -145,21 +145,15 @@ server.listen(0, '127.0.0.1', async () => {
await check('unknown paths answer 404', () => expectAll([
await check('unknown paths answer 404', () => expectAll([
'/nope', '/search', '/login', '/wp-login.php', '/favicon-missing.ico', '/Vods', '/vods/extra', '/updates/1',
'/nope', '/search', '/login', '/wp-login.php', '/favicon-missing.ico', '/Vods', '/vods/extra', '/updates/1',
- '/@nobody', '/@ab', '/@alice/main/extra', '/@bad.name', '/nobody', '/nobody/main', '/alice/main/extra', '/alice/bad!slot',
+ '/@nobody', '/@ab', '/@alice/main/extra', '/@bad.name', '/nobody', '/nobody/main',
'/vod', '/vod/999', '/vod/abc', '/vod/100/extra', '/clip/999', '/clip/-1',
'/vod', '/vod/999', '/vod/abc', '/vod/100/extra', '/clip/999', '/clip/-1',
'/p/missing', '/p/bad!slug', '/p/abc123/extra',
'/p/missing', '/p/bad!slug', '/p/abc123/extra',
'/recap/999999', '/recap/x', '/stream/999999', '/stream',
'/recap/999999', '/recap/x', '/stream/999999', '/stream',
], 404));
], 404));
- await check('a channel link without the @ redirects to the channel page (old notifications)', async () => {
- for (const [from, to] of [['/alice', '/@alice'], ['/Alice/', '/@alice'], ['/alice/main', '/@alice/main'], ['/bob?stream=5', '/@bob?stream=5']]) {
- for (const html of [false, true]) {
- const r = await get(from, { html });
- assert.strictEqual(r.status, 301, `${from}${html ? ' (html)' : ''} → ${r.status}`);
- assert.strictEqual(r.location, to, `${from} → ${r.location}`);
- }
- }
- assert.strictEqual((await get('/vods')).status, 200, 'a route name is never taken for a user');
+ await check('a channel is only ever /@<username>: a bare username is not a page, even for a user that exists', async () => {
+ await expectAll(['/alice', '/Alice/', '/alice/main', '/bob'], 404);
+ for (const p of ['/alice', '/alice/main']) assert.strictEqual((await get(p)).location, null, `${p} does not redirect`);
});
});
await check('a private VOD or clip is a 404 to strangers, with or without a sign-in', async () => {
await check('a private VOD or clip is a 404 to strangers, with or without a sign-in', async () => {