Add support for client-side room alias hash `#` redirects to the correct URL (#111)
This helps when someone just pastes a room alias on the end of the domain, - `/#room-alias:server` -> `/r/room-alias:server` - `/r/#room-alias:server/date/2022/10/27` -> `/r/room-alias:server/date/2022/10/27` Since these redirects happen on the client, we can't write any e2e tests. Those e2e tests do everything but run client-side JavaScript. Follow-up to https://github.com/matrix-org/matrix-public-archive/pull/107 Part of https://github.com/matrix-org/matrix-public-archive/issues/25
This commit is contained in:
parent
7a88ea0c19
commit
2b4ecb737a
|
@ -19,6 +19,7 @@ const generateViteConfigForEntryPoint = require('./generate-vite-config-for-entr
|
|||
const entryPoints = [
|
||||
path.resolve(__dirname, '../public/js/entry-client-hydrogen.js'),
|
||||
path.resolve(__dirname, '../public/js/entry-client-room-directory.js'),
|
||||
path.resolve(__dirname, '../public/js/entry-client-room-alias-hash-redirect.js'),
|
||||
];
|
||||
|
||||
async function buildClientScripts(extraConfig = {}) {
|
||||
|
|
|
@ -155,6 +155,32 @@
|
|||
color: initial;
|
||||
}
|
||||
|
||||
.RoomDirectoryView_notificationToast {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
max-width: 450px;
|
||||
margin-right: 20px;
|
||||
padding: 20px;
|
||||
|
||||
background: hsl(207deg 36% 18% / 90%);
|
||||
border-radius: 8px;
|
||||
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.RoomDirectoryView_notificationToastTitle {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.RoomDirectoryView_notificationToastDescription {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.RoomDirectoryView_mainContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import assert from 'matrix-public-archive-shared/lib/assert';
|
||||
import MatrixPublicArchiveURLCreator from 'matrix-public-archive-shared/lib/url-creator';
|
||||
import redirectIfRoomAliasInHash from 'matrix-public-archive-shared/lib/redirect-if-room-alias-in-hash';
|
||||
|
||||
const config = window.matrixPublicArchiveContext.config;
|
||||
assert(config);
|
||||
assert(config.basePath);
|
||||
|
||||
const matrixPublicArchiveURLCreator = new MatrixPublicArchiveURLCreator(config.basePath);
|
||||
|
||||
console.log(`Trying to redirect based on pageHash=${window.location.hash}`);
|
||||
const isRedirecting = redirectIfRoomAliasInHash(matrixPublicArchiveURLCreator);
|
||||
|
||||
// Show the message while we're trying to redirect or if we found nothing, remove the
|
||||
// message
|
||||
document.querySelector('.js-try-redirect-message').style.display = isRedirecting
|
||||
? 'inline'
|
||||
: 'none';
|
|
@ -0,0 +1,62 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const urlJoin = require('url-join');
|
||||
const safeJson = require('../lib/safe-json');
|
||||
const sanitizeHtml = require('../lib/sanitize-html');
|
||||
|
||||
const config = require('../lib/config');
|
||||
const basePath = config.get('basePath');
|
||||
assert(basePath);
|
||||
|
||||
// Since everything after the hash (`#`) won't make it to the server, let's serve a 404
|
||||
// page that will potentially redirect them to the correct place if they tried
|
||||
// `/r/#room-alias:server/date/2022/10/27` -> `/r/room-alias:server/date/2022/10/27`
|
||||
function clientSideRoomAliasHashRedirectRoute(req, res) {
|
||||
const cspNonce = res.locals.cspNonce;
|
||||
const hydrogenStylesUrl = urlJoin(basePath, '/hydrogen-styles.css');
|
||||
const stylesUrl = urlJoin(basePath, '/css/styles.css');
|
||||
const jsBundleUrl = urlJoin(basePath, '/js/entry-client-room-alias-hash-redirect.es.js');
|
||||
|
||||
const context = {
|
||||
config: {
|
||||
basePath,
|
||||
},
|
||||
};
|
||||
const serializedContext = JSON.stringify(context);
|
||||
|
||||
const pageHtml = `
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Page not found - Matrix Public Archive</title>
|
||||
<link href="${hydrogenStylesUrl}" rel="stylesheet" nonce="${cspNonce}">
|
||||
<link href="${stylesUrl}" rel="stylesheet" nonce="${cspNonce}">
|
||||
</head>
|
||||
${/* We add the .hydrogen class here just to get normal body styles */ ''}
|
||||
<body class="hydrogen">
|
||||
<h1>
|
||||
404: Page not found.
|
||||
<span class="js-try-redirect-message" style="display: none">One sec while we try to redirect you to the right place.</span>
|
||||
</h1>
|
||||
<p>If there was a #room_alias:server hash in the URL, we tried redirecting you to the right place.</p>
|
||||
<p>
|
||||
Otherwise, you're simply in a place that does not exist.
|
||||
You can ${sanitizeHtml(`<a href="${basePath}">go back to the homepage</a>.`)}
|
||||
</p>
|
||||
|
||||
<script type="text/javascript" nonce="${cspNonce}">
|
||||
window.matrixPublicArchiveContext = ${safeJson(serializedContext)}
|
||||
</script>
|
||||
<script type="text/javascript" src="${jsBundleUrl}" nonce="${cspNonce}"></script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
res.status(404);
|
||||
res.set('Content-Type', 'text/html');
|
||||
res.send(pageHtml);
|
||||
}
|
||||
|
||||
module.exports = clientSideRoomAliasHashRedirectRoute;
|
|
@ -8,6 +8,7 @@ const { handleTracingMiddleware } = require('../tracing/tracing-middleware');
|
|||
const getVersionTags = require('../lib/get-version-tags');
|
||||
const preventClickjackingMiddleware = require('./prevent-clickjacking-middleware');
|
||||
const contentSecurityPolicyMiddleware = require('./content-security-policy-middleware');
|
||||
const clientSideRoomAliasHashRedirectRoute = require('./client-side-room-alias-hash-redirect-route');
|
||||
const redirectToCorrectArchiveUrlIfBadSigil = require('./redirect-to-correct-archive-url-if-bad-sigil-middleware');
|
||||
|
||||
function installRoutes(app) {
|
||||
|
@ -62,6 +63,11 @@ function installRoutes(app) {
|
|||
// For room aliases (/r) or room ID's (/roomid)
|
||||
app.use('/:entityDescriptor(r|roomid)/:roomIdOrAliasDirty', require('./room-routes'));
|
||||
|
||||
// Since everything after the hash (`#`) won't make it to the server, let's serve a 404
|
||||
// page that will potentially redirect them to the correct place if they tried
|
||||
// `/r/#room-alias:server/date/2022/10/27` -> `/r/room-alias:server/date/2022/10/27`
|
||||
app.use('/:entityDescriptor(r|roomid)', clientSideRoomAliasHashRedirectRoute);
|
||||
|
||||
// Correct any honest mistakes: If someone accidentally put the sigil in the URL, then
|
||||
// redirect them to the correct URL without the sigil to the correct path above.
|
||||
app.use('/:roomIdOrAliasDirty', redirectToCorrectArchiveUrlIfBadSigil);
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
'use strict';
|
||||
|
||||
// https://spec.matrix.org/v1.1/appendices/#room-aliases
|
||||
// - `#room_alias:domain`
|
||||
// - `#room-alias:server/date/2022/10/27`
|
||||
const BASIC_ROOM_ALIAS_REGEX = /^(#(?:[^/:]+):(?:[^/]+))/;
|
||||
|
||||
// Returns `true` if redirecting, otherwise `false`
|
||||
function redirectIfRoomAliasInHash(matrixPublicArchiveURLCreator, redirectCallback) {
|
||||
function handleHashChange() {
|
||||
const pageHash = window.location.hash;
|
||||
|
||||
const match = pageHash.match(BASIC_ROOM_ALIAS_REGEX);
|
||||
if (match) {
|
||||
const roomAlias = match[0];
|
||||
const newLocation = matrixPublicArchiveURLCreator.archiveUrlForRoom(roomAlias);
|
||||
console.log(`Saw room alias in hash, redirecting to newLocation=${newLocation}`);
|
||||
window.location = newLocation;
|
||||
if (redirectCallback) {
|
||||
redirectCallback();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const eventHandler = {
|
||||
handleEvent(e) {
|
||||
if (e.type === 'hashchange') {
|
||||
handleHashChange();
|
||||
}
|
||||
},
|
||||
};
|
||||
window.addEventListener('hashchange', eventHandler);
|
||||
|
||||
// Handle the initial hash
|
||||
if (window.location) {
|
||||
return handleHashChange();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = redirectIfRoomAliasInHash;
|
|
@ -11,6 +11,7 @@ const { Platform, Navigation, createRouter } = require('hydrogen-view-sdk');
|
|||
const MatrixPublicArchiveURLCreator = require('matrix-public-archive-shared/lib/url-creator');
|
||||
const ArchiveHistory = require('matrix-public-archive-shared/lib/archive-history');
|
||||
const supressBlankAnchorsReloadingThePage = require('matrix-public-archive-shared/lib/supress-blank-anchors-reloading-the-page');
|
||||
const redirectIfRoomAliasInHash = require('matrix-public-archive-shared/lib/redirect-if-room-alias-in-hash');
|
||||
|
||||
const RoomDirectoryView = require('matrix-public-archive-shared/views/RoomDirectoryView');
|
||||
const RoomDirectoryViewModel = require('matrix-public-archive-shared/viewmodels/RoomDirectoryViewModel');
|
||||
|
@ -31,6 +32,15 @@ const matrixPublicArchiveURLCreator = new MatrixPublicArchiveURLCreator(config.b
|
|||
|
||||
supressBlankAnchorsReloadingThePage();
|
||||
|
||||
let roomDirectoryViewModel;
|
||||
let isRedirecting = false;
|
||||
isRedirecting = redirectIfRoomAliasInHash(matrixPublicArchiveURLCreator, () => {
|
||||
isRedirecting = true;
|
||||
if (roomDirectoryViewModel) {
|
||||
roomDirectoryViewModel.setPageRedirectingFromUrlHash(true);
|
||||
}
|
||||
});
|
||||
|
||||
async function mountHydrogen() {
|
||||
console.log('Mounting Hydrogen...');
|
||||
console.time('Completed mounting Hydrogen');
|
||||
|
@ -69,7 +79,7 @@ async function mountHydrogen() {
|
|||
// page don't say `undefined`.
|
||||
urlRouter.attach();
|
||||
|
||||
const roomDirectoryViewModel = new RoomDirectoryViewModel({
|
||||
roomDirectoryViewModel = new RoomDirectoryViewModel({
|
||||
// Hydrogen options
|
||||
navigation: navigation,
|
||||
urlCreator: urlRouter,
|
||||
|
@ -84,6 +94,8 @@ async function mountHydrogen() {
|
|||
nextPaginationToken,
|
||||
prevPaginationToken,
|
||||
});
|
||||
// Update the model with the initial value
|
||||
roomDirectoryViewModel.setPageRedirectingFromUrlHash(isRedirecting);
|
||||
|
||||
const view = new RoomDirectoryView(roomDirectoryViewModel);
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ class RoomDirectoryViewModel extends ViewModel {
|
|||
this._homeserverName = homeserverName;
|
||||
this._matrixPublicArchiveURLCreator = matrixPublicArchiveURLCreator;
|
||||
|
||||
this._isPageRedirectingFromUrlHash = false;
|
||||
|
||||
this._pageSearchParameters = pageSearchParameters;
|
||||
// Default to what the page started with
|
||||
this._searchTerm = pageSearchParameters.searchTerm;
|
||||
|
@ -106,6 +108,15 @@ class RoomDirectoryViewModel extends ViewModel {
|
|||
this.homeserverSelectionModalViewModel.setOpen(shouldShowAddServerModal);
|
||||
}
|
||||
|
||||
setPageRedirectingFromUrlHash(newValue) {
|
||||
this._isPageRedirectingFromUrlHash = newValue;
|
||||
this.emitChange('isPageRedirectingFromUrlHash');
|
||||
}
|
||||
|
||||
get isPageRedirectingFromUrlHash() {
|
||||
return this._isPageRedirectingFromUrlHash;
|
||||
}
|
||||
|
||||
get homeserverUrl() {
|
||||
return this._homeserverUrl;
|
||||
}
|
||||
|
@ -266,7 +277,7 @@ class RoomDirectoryViewModel extends ViewModel {
|
|||
const deduplicatedHomeserverList = Object.keys(deduplicatedHomeserverMap);
|
||||
|
||||
this._availableHomeserverList = deduplicatedHomeserverList;
|
||||
this.emit('availableHomeserverList');
|
||||
this.emitChange('availableHomeserverList');
|
||||
}
|
||||
|
||||
get availableHomeserverList() {
|
||||
|
|
|
@ -288,6 +288,21 @@ class RoomDirectoryView extends TemplateView {
|
|||
t.a({ className: 'RoomDirectoryView_paginationButton', href: vm.nextPageUrl }, 'Next'),
|
||||
]),
|
||||
]),
|
||||
t.if(
|
||||
(vm) => vm.isPageRedirectingFromUrlHash,
|
||||
(t /*, vm*/) => {
|
||||
return t.div({ className: 'RoomDirectoryView_notificationToast', role: 'alert' }, [
|
||||
t.h5(
|
||||
{ className: 'RoomDirectoryView_notificationToastTitle' },
|
||||
'Found room alias in URL #hash'
|
||||
),
|
||||
t.p(
|
||||
{ className: 'RoomDirectoryView_notificationToastDescription' },
|
||||
'One sec while we try to redirect you to the right place.'
|
||||
),
|
||||
]);
|
||||
}
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue