2023-06-30 02:08:32 -06:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const assert = require('assert');
|
|
|
|
|
|
|
|
const urlJoin = require('url-join');
|
2023-07-14 14:52:35 -06:00
|
|
|
const { DIRECTION } = require('matrix-viewer-shared/lib/reference-values');
|
2023-06-30 02:08:32 -06:00
|
|
|
const { fetchEndpointAsJson } = require('../fetch-endpoint');
|
|
|
|
const { traceFunction } = require('../../tracing/trace-utilities');
|
|
|
|
|
|
|
|
const config = require('../config');
|
|
|
|
const matrixServerUrl = config.get('matrixServerUrl');
|
|
|
|
assert(matrixServerUrl);
|
|
|
|
|
|
|
|
// The number of requests we should make to try to fill the limit before bailing out
|
|
|
|
const NUM_MAX_REQUESTS = 10;
|
|
|
|
|
|
|
|
async function requestPublicRooms(
|
|
|
|
accessToken,
|
|
|
|
{ server, searchTerm, paginationToken, limit, abortSignal } = {}
|
|
|
|
) {
|
|
|
|
let qs = new URLSearchParams();
|
|
|
|
if (server) {
|
|
|
|
qs.append('server', server);
|
|
|
|
}
|
|
|
|
|
|
|
|
const publicRoomsEndpoint = urlJoin(
|
|
|
|
matrixServerUrl,
|
|
|
|
`_matrix/client/v3/publicRooms?${qs.toString()}`
|
|
|
|
);
|
|
|
|
|
|
|
|
const { data: publicRoomsRes } = await fetchEndpointAsJson(publicRoomsEndpoint, {
|
|
|
|
method: 'POST',
|
|
|
|
body: {
|
|
|
|
include_all_networks: true,
|
|
|
|
filter: {
|
|
|
|
generic_search_term: searchTerm,
|
|
|
|
},
|
|
|
|
since: paginationToken,
|
|
|
|
limit,
|
|
|
|
},
|
|
|
|
accessToken,
|
|
|
|
abortSignal,
|
|
|
|
});
|
|
|
|
|
|
|
|
return publicRoomsRes;
|
|
|
|
}
|
|
|
|
|
|
|
|
// eslint-disable-next-line complexity, max-statements
|
|
|
|
async function fetchAccessibleRooms(
|
|
|
|
accessToken,
|
|
|
|
{
|
|
|
|
server,
|
|
|
|
searchTerm,
|
|
|
|
// Direction is baked into the pagination token but we're unable to decipher it from
|
|
|
|
// the opaque token, we also have to pass it in explicitly.
|
|
|
|
paginationToken,
|
|
|
|
direction = DIRECTION.forward,
|
|
|
|
limit,
|
|
|
|
abortSignal,
|
|
|
|
} = {}
|
|
|
|
) {
|
|
|
|
assert(accessToken);
|
|
|
|
assert([DIRECTION.forward, DIRECTION.backward].includes(direction), 'direction must be [f|b]');
|
|
|
|
|
|
|
|
// Based off of the matrix.org room directory, only 42% of rooms are world_readable,
|
|
|
|
// which means our best bet to fill up the results to the limit is to request at least
|
|
|
|
// 2.4 times as many. I've doubled and rounded it up to 5 times as many so we can have
|
|
|
|
// less round-trips.
|
|
|
|
const bulkPaginationLimit = Math.ceil(5 * limit);
|
|
|
|
|
|
|
|
let accessibleRooms = [];
|
|
|
|
|
|
|
|
let firstResponse;
|
|
|
|
let lastResponse;
|
|
|
|
|
|
|
|
let loopToken = paginationToken;
|
|
|
|
let lastLoopToken;
|
|
|
|
let continuationIndex;
|
|
|
|
let currentRequestCount = 0;
|
|
|
|
while (
|
|
|
|
// Stop if we have reached the limit of rooms we want to fetch
|
|
|
|
accessibleRooms.length < limit &&
|
|
|
|
// And bail if we're already gone through a bunch of pages to try to fill the limit
|
|
|
|
currentRequestCount < NUM_MAX_REQUESTS &&
|
|
|
|
// And bail if we've reached the end of the pagination
|
|
|
|
// Always do the first request
|
|
|
|
(currentRequestCount === 0 ||
|
|
|
|
// If we have a next token, we can do another request
|
|
|
|
(currentRequestCount > 0 && loopToken))
|
|
|
|
) {
|
|
|
|
const publicRoomsRes = await requestPublicRooms(accessToken, {
|
|
|
|
server,
|
|
|
|
searchTerm,
|
|
|
|
paginationToken: loopToken,
|
|
|
|
limit: bulkPaginationLimit,
|
|
|
|
abortSignal,
|
|
|
|
});
|
|
|
|
lastLoopToken = loopToken;
|
|
|
|
lastResponse = publicRoomsRes;
|
|
|
|
|
|
|
|
if (currentRequestCount === 0) {
|
|
|
|
firstResponse = publicRoomsRes;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the token ready for the next loop
|
|
|
|
loopToken =
|
|
|
|
direction === DIRECTION.forward ? publicRoomsRes.next_batch : publicRoomsRes.prev_batch;
|
|
|
|
|
|
|
|
const fetchedRooms = publicRoomsRes.chunk;
|
|
|
|
const fetchedRoomsInDirection =
|
|
|
|
direction === DIRECTION.forward ? fetchedRooms : fetchedRooms.reverse();
|
|
|
|
|
2023-07-14 14:52:35 -06:00
|
|
|
// We only want to see world_readable rooms
|
2023-06-30 02:08:32 -06:00
|
|
|
let index = 0;
|
|
|
|
for (let room of fetchedRoomsInDirection) {
|
|
|
|
if (room.world_readable) {
|
|
|
|
if (direction === DIRECTION.forward) {
|
|
|
|
accessibleRooms.push(room);
|
|
|
|
} else if (direction === DIRECTION.backward) {
|
|
|
|
accessibleRooms.unshift(room);
|
|
|
|
} else {
|
|
|
|
throw new Error(`Invalid direction: ${direction}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (accessibleRooms.length === limit && !continuationIndex) {
|
|
|
|
continuationIndex = index;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop after we've reached the limit
|
|
|
|
if (accessibleRooms.length >= limit) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
index += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
currentRequestCount += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Back-track to get the perfect continuation point and show exactly the limit of
|
|
|
|
// rooms in the grid.
|
|
|
|
//
|
|
|
|
// Alternatively, we could just not worry about and show more than the limit of rooms
|
|
|
|
//
|
|
|
|
// XXX: Since the room directory order is not stable, this is slightly flawed as the
|
|
|
|
// results could have shifted slightly from when we made the last request to now but
|
|
|
|
// we assume it's good enough.
|
|
|
|
let nextPaginationToken;
|
|
|
|
let prevPaginationToken;
|
|
|
|
if (continuationIndex) {
|
|
|
|
const publicRoomsRes = await requestPublicRooms(accessToken, {
|
|
|
|
server,
|
|
|
|
searchTerm,
|
|
|
|
// Start from the last request
|
|
|
|
paginationToken: lastLoopToken,
|
|
|
|
// Then only go out as far out as the continuation index (the point when we filled
|
|
|
|
// the limit)
|
|
|
|
limit: continuationIndex + 1,
|
|
|
|
abortSignal,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (direction === DIRECTION.forward) {
|
|
|
|
prevPaginationToken = firstResponse.prev_batch;
|
|
|
|
nextPaginationToken = publicRoomsRes.next_batch;
|
|
|
|
} else if (direction === DIRECTION.backward) {
|
|
|
|
prevPaginationToken = publicRoomsRes.prev_batch;
|
|
|
|
nextPaginationToken = firstResponse.next_batch;
|
|
|
|
} else {
|
|
|
|
throw new Error(`Invalid direction: ${direction}`);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (direction === DIRECTION.forward) {
|
|
|
|
prevPaginationToken = firstResponse.prev_batch;
|
|
|
|
nextPaginationToken = lastResponse.next_batch;
|
|
|
|
} else if (direction === DIRECTION.backward) {
|
|
|
|
prevPaginationToken = lastResponse.prev_batch;
|
|
|
|
nextPaginationToken = firstResponse.next_batch;
|
|
|
|
} else {
|
|
|
|
throw new Error(`Invalid direction: ${direction}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
rooms: accessibleRooms,
|
|
|
|
prevPaginationToken,
|
|
|
|
nextPaginationToken,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = traceFunction(fetchAccessibleRooms);
|