Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip room join until we run into a 403 Forbidden and peek into world_readable rooms #272

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions server/lib/matrix-utils/create-retry-fn-if-not-joined.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict';

const assert = require('assert');

const { HTTPResponseError } = require('../fetch-endpoint');
const ensureRoomJoined = require('./ensure-room-joined');

const JOIN_STATES = {
unknown: 'unknown',
joining: 'joining',
joined: 'joined',
failed: 'failed',
};
const joinStateValues = Object.values(JOIN_STATES);

// Optimistically use the Matrix API assuming you're already joined to the room or
// accessing a `world_readable` room that doesn't require joining. If we see a 403
// Forbidden, then try joining the room and retrying the API call.
//
// Usage: Call this once to first create a helper utility that will retry a given
// function appropriately.
function createRetryFnIfNotJoined(
accessToken,
roomIdOrAlias,
{ viaServers = new Set(), abortSignal } = {}
) {
assert(accessToken);
assert(roomIdOrAlias);
// We use a `Set` to ensure that we don't have duplicate servers in the list
assert(viaServers instanceof Set);

let joinState = JOIN_STATES.unknown;
let joinPromise = null;

return async function retryFnIfNotJoined(fn) {
assert(
joinStateValues.includes(joinState),
`Unexpected internal join state when using createRetryFnIfNotJoined(...) (joinState=${joinState}). ` +
`This is a bug in the Matrix Public Archive. Please report`
);

if (joinState === JOIN_STATES.joining) {
// Wait for the join to finish before trying
await joinPromise;
} else if (joinState === JOIN_STATES.failed) {
// If we failed to join the room, then there is no way any other call is going
// to succeed so just immediately return an error. We return `joinPromise`
// which will resolve to the join error that occured
return joinPromise;
}

try {
return await Promise.resolve(fn());
} catch (errFromFn) {
const isForbiddenError =
errFromFn instanceof HTTPResponseError && errFromFn.response.status === 403;

// If we're in the middle of joining, try again
if (joinState === JOIN_STATES.joining) {
return await retryFnIfNotJoined(fn);
}
// Try joining the room if we see a 403 Forbidden error as we may just not
// be part of the room yet. We can't distinguish between a room that has
// banned us vs a room we haven't joined yet so we just try joining the
// room in any case.
else if (
isForbiddenError &&
// Only try joining if we haven't tried joining yet
joinState === JOIN_STATES.unknown
) {
joinState = JOIN_STATES.joining;
joinPromise = ensureRoomJoined(accessToken, roomIdOrAlias, {
viaServers,
abortSignal,
});

try {
await joinPromise;
joinState = JOIN_STATES.joined;
return await retryFnIfNotJoined(fn);
} catch (err) {
joinState = JOIN_STATES.failed;
throw err;
}
}

throw errFromFn;
}
};
}

module.exports = createRetryFnIfNotJoined;
2 changes: 2 additions & 0 deletions server/lib/matrix-utils/ensure-room-joined.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ async function ensureRoomJoined(
roomIdOrAlias,
{ viaServers = new Set(), abortSignal } = {}
) {
assert(accessToken);
assert(roomIdOrAlias);
// We use a `Set` to ensure that we don't have duplicate servers in the list
assert(viaServers instanceof Set);

Expand Down
9 changes: 5 additions & 4 deletions server/lib/matrix-utils/fetch-public-rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ async function fetchPublicRooms(
abortSignal,
});

// We only want to see public rooms in the archive
// We only want to see public or world_readable rooms in the archive. A room can be
// world_readable without being public. For example someone might have an invite only
// room where only privileged users are allowed to join and talk but anyone can view
// the room.
const accessibleRooms = publicRoomsRes.chunk.filter((room) => {
// `room.world_readable` is also accessible here but we only use history
// `world_readable` to determine search indexing.
return room.join_rule === 'public';
return room.world_readable || room.join_rule === 'public';
});

return {
Expand Down
Loading