Make sure fetchRoomData throws 403 errors

Before, it would swallow all of the errors and just return empty data
which was fine but now we want it to retry fetching if we weren't in
the room yet so we need that 403 error being thrown signal.
This commit is contained in:
Eric Eastwood 2023-06-22 21:00:18 -05:00
parent 79a468b81f
commit 2c2ce3e656
2 changed files with 109 additions and 121 deletions

View File

@ -77,10 +77,8 @@ function createRetryFnIfNotJoined(
try { try {
await joinPromise; await joinPromise;
joinState = JOIN_STATES.joined; joinState = JOIN_STATES.joined;
console.log('retryAfterJoin');
return await retryFnIfNotJoined(fn); return await retryFnIfNotJoined(fn);
} catch (err) { } catch (err) {
console.log('FAILED retryAfterJoin');
joinState = JOIN_STATES.failed; joinState = JOIN_STATES.failed;
throw err; throw err;
} }

View File

@ -3,7 +3,7 @@
const assert = require('assert'); const assert = require('assert');
const urlJoin = require('url-join'); const urlJoin = require('url-join');
const { fetchEndpointAsJson } = require('../fetch-endpoint'); const { HTTPResponseError, fetchEndpointAsJson } = require('../fetch-endpoint');
const parseViaServersFromUserInput = require('../parse-via-servers-from-user-input'); const parseViaServersFromUserInput = require('../parse-via-servers-from-user-input');
const { traceFunction } = require('../../tracing/trace-utilities'); const { traceFunction } = require('../../tracing/trace-utilities');
@ -20,13 +20,43 @@ function getStateEndpointForRoomIdAndEventType(roomId, eventType) {
); );
} }
const fetchPotentiallyMissingStateEvent = traceFunction(async function ({
accessToken,
roomId,
stateEventType,
abortSignal,
} = {}) {
assert(accessToken);
assert(roomId);
assert(stateEventType);
try {
const { data } = await fetchEndpointAsJson(
getStateEndpointForRoomIdAndEventType(roomId, stateEventType),
{
accessToken,
abortSignal,
}
);
return data;
} catch (err) {
const is404Error = err instanceof HTTPResponseError && err.response.status === 404;
// Ignore 404 errors, because it just means that the room doesn't have that state
// event (which is normal).
if (!is404Error) {
throw err;
}
}
});
// Unfortunately, we can't just get the event ID from the `/state?format=event` // Unfortunately, we can't just get the event ID from the `/state?format=event`
// endpoint, so we have to do this trick. Related to // endpoint, so we have to do this trick. Related to
// https://github.com/matrix-org/synapse/issues/15454 // https://github.com/matrix-org/synapse/issues/15454
// //
// TODO: Remove this when we have MSC3999 (because it's the only usage) // TODO: Remove this when we have MSC3999 (because it's the only usage)
const removeMe_fetchRoomCreateEventId = traceFunction(async function ( const removeMe_fetchRoomCreateEventId = traceFunction(async function (
matrixAccessToken, accessToken,
roomId, roomId,
{ abortSignal } = {} { abortSignal } = {}
) { ) {
@ -36,7 +66,7 @@ const removeMe_fetchRoomCreateEventId = traceFunction(async function (
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/messages?dir=f&limit1` `_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/messages?dir=f&limit1`
), ),
{ {
accessToken: matrixAccessToken, accessToken,
abortSignal, abortSignal,
} }
); );
@ -51,22 +81,16 @@ const fetchRoomCreationInfo = traceFunction(async function (
roomId, roomId,
{ abortSignal } = {} { abortSignal } = {}
) { ) {
const [stateCreateResDataOutcome] = await Promise.allSettled([ const stateCreateResData = await fetchPotentiallyMissingStateEvent({
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.create'), { accessToken: matrixAccessToken,
accessToken: matrixAccessToken, roomId,
abortSignal, stateEventType: 'm.room.create',
}), abortSignal,
]); });
let roomCreationTs; const roomCreationTs = stateCreateResData?.origin_server_ts;
let predecessorRoomId; const predecessorRoomId = stateCreateResData?.content?.predecessor?.room_id;
let predecessorLastKnownEventId; const predecessorLastKnownEventId = stateCreateResData?.content?.event_id;
if (stateCreateResDataOutcome.reason === undefined) {
const { data } = stateCreateResDataOutcome.value;
roomCreationTs = data?.origin_server_ts;
predecessorLastKnownEventId = data?.content?.event_id;
predecessorRoomId = data?.content?.predecessor?.room_id;
}
return { roomCreationTs, predecessorRoomId, predecessorLastKnownEventId }; return { roomCreationTs, predecessorRoomId, predecessorLastKnownEventId };
}); });
@ -76,33 +100,33 @@ const fetchPredecessorInfo = traceFunction(async function (
roomId, roomId,
{ abortSignal } = {} { abortSignal } = {}
) { ) {
const [roomCreationInfoOutcome, statePredecessorResDataOutcome] = await Promise.allSettled([ const [roomCreationInfo, statePredecessorResData] = await Promise.all([
fetchRoomCreationInfo(matrixAccessToken, roomId, { abortSignal }), fetchRoomCreationInfo(matrixAccessToken, roomId, { abortSignal }),
fetchEndpointAsJson( fetchPotentiallyMissingStateEvent({
getStateEndpointForRoomIdAndEventType(roomId, 'org.matrix.msc3946.room_predecessor'), accessToken: matrixAccessToken,
{ roomId,
accessToken: matrixAccessToken, stateEventType: 'org.matrix.msc3946.room_predecessor',
abortSignal, abortSignal,
} }),
),
]); ]);
let predecessorRoomId; let predecessorRoomId;
let predecessorLastKnownEventId; let predecessorLastKnownEventId;
let predecessorViaServers; let predecessorViaServers;
// Prefer the dynamic predecessor from the dedicated state event // Prefer the dynamic predecessor from the dedicated state event
if (statePredecessorResDataOutcome.reason === undefined) { if (statePredecessorResData) {
const { data } = statePredecessorResDataOutcome.value; predecessorRoomId = statePredecessorResData?.content?.predecessor_room_id;
predecessorRoomId = data?.content?.predecessor_room_id; predecessorLastKnownEventId = statePredecessorResData?.content?.last_known_event_id;
predecessorLastKnownEventId = data?.content?.last_known_event_id; predecessorViaServers = parseViaServersFromUserInput(
predecessorViaServers = parseViaServersFromUserInput(data?.content?.via_servers); statePredecessorResData?.content?.via_servers
);
} }
// Then fallback to the predecessor defined by the room creation event // Then fallback to the predecessor defined by the room creation event
else if (roomCreationInfoOutcome.reason === undefined) { else if (roomCreationInfo) {
({ predecessorRoomId, predecessorLastKnownEventId } = roomCreationInfoOutcome.value); ({ predecessorRoomId, predecessorLastKnownEventId } = roomCreationInfo);
} }
const { roomCreationTs: currentRoomCreationTs } = roomCreationInfoOutcome; const { roomCreationTs: currentRoomCreationTs } = roomCreationInfo;
return { return {
// This is prefixed with "current" so we don't get this confused with the // This is prefixed with "current" so we don't get this confused with the
@ -119,20 +143,15 @@ const fetchSuccessorInfo = traceFunction(async function (
roomId, roomId,
{ abortSignal } = {} { abortSignal } = {}
) { ) {
const [stateTombstoneResDataOutcome] = await Promise.allSettled([ const stateTombstoneResData = await fetchPotentiallyMissingStateEvent({
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.tombstone'), { accessToken: matrixAccessToken,
accessToken: matrixAccessToken, roomId,
abortSignal, stateEventType: 'm.room.tombstone',
}), abortSignal,
]); });
let successorRoomId; const successorRoomId = stateTombstoneResData?.content?.replacement_room;
let successorSetTs; const successorSetTs = stateTombstoneResData?.origin_server_ts;
if (stateTombstoneResDataOutcome.reason === undefined) {
const { data } = stateTombstoneResDataOutcome.value;
successorRoomId = data?.content?.replacement_room;
successorSetTs = data?.origin_server_ts;
}
return { return {
successorRoomId, successorRoomId,
@ -150,99 +169,70 @@ const fetchRoomData = traceFunction(async function (
assert(roomId); assert(roomId);
const [ const [
stateNameResDataOutcome, stateNameResData,
stateTopicResDataOutcome, stateTopicResData,
stateCanonicalAliasResDataOutcome, stateCanonicalAliasResData,
stateAvatarResDataOutcome, stateAvatarResData,
stateHistoryVisibilityResDataOutcome, stateHistoryVisibilityResData,
stateJoinRulesResDataOutcome, stateJoinRulesResData,
predecessorInfoOutcome, predecessorInfo,
successorInfoOutcome, successorInfo,
] = await Promise.allSettled([ ] = await Promise.all([
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.name'), { fetchPotentiallyMissingStateEvent({
accessToken: matrixAccessToken, accessToken: matrixAccessToken,
roomId,
stateEventType: 'm.room.name',
abortSignal, abortSignal,
}), }),
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.topic'), { fetchPotentiallyMissingStateEvent({
accessToken: matrixAccessToken, accessToken: matrixAccessToken,
roomId,
stateEventType: 'm.room.topic',
abortSignal, abortSignal,
}), }),
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.canonical_alias'), { fetchPotentiallyMissingStateEvent({
accessToken: matrixAccessToken, accessToken: matrixAccessToken,
roomId,
stateEventType: 'm.room.canonical_alias',
abortSignal, abortSignal,
}), }),
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.avatar'), { fetchPotentiallyMissingStateEvent({
accessToken: matrixAccessToken, accessToken: matrixAccessToken,
roomId,
stateEventType: 'm.room.avatar',
abortSignal, abortSignal,
}), }),
fetchEndpointAsJson( fetchPotentiallyMissingStateEvent({
getStateEndpointForRoomIdAndEventType(roomId, 'm.room.history_visibility'),
{
accessToken: matrixAccessToken,
abortSignal,
}
),
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.join_rules'), {
accessToken: matrixAccessToken, accessToken: matrixAccessToken,
roomId,
stateEventType: 'm.room.history_visibility',
abortSignal,
}),
fetchPotentiallyMissingStateEvent({
accessToken: matrixAccessToken,
roomId,
stateEventType: 'm.room.join_rules',
abortSignal, abortSignal,
}), }),
fetchPredecessorInfo(matrixAccessToken, roomId, { abortSignal }), fetchPredecessorInfo(matrixAccessToken, roomId, { abortSignal }),
fetchSuccessorInfo(matrixAccessToken, roomId, { abortSignal }), fetchSuccessorInfo(matrixAccessToken, roomId, { abortSignal }),
]); ]);
let name; let name = stateNameResData?.content?.name;
if (stateNameResDataOutcome.reason === undefined) { let canonicalAlias = stateCanonicalAliasResData?.content?.alias;
const { data } = stateNameResDataOutcome.value; let topic = stateTopicResData?.content?.topic;
name = data?.content?.name; let avatarUrl = stateAvatarResData?.content?.url;
} let historyVisibility = stateHistoryVisibilityResData?.content?.history_visibility;
let joinRule = stateJoinRulesResData?.content?.join_rule;
let canonicalAlias; const {
if (stateCanonicalAliasResDataOutcome.reason === undefined) { currentRoomCreationTs: roomCreationTs,
const { data } = stateCanonicalAliasResDataOutcome.value; predecessorRoomId,
canonicalAlias = data?.content?.alias; predecessorLastKnownEventId,
} predecessorViaServers,
} = predecessorInfo;
let topic; const { successorRoomId, successorSetTs } = successorInfo;
if (stateTopicResDataOutcome.reason === undefined) {
const { data } = stateTopicResDataOutcome.value;
topic = data?.content?.topic;
}
let avatarUrl;
if (stateAvatarResDataOutcome.reason === undefined) {
const { data } = stateAvatarResDataOutcome.value;
avatarUrl = data?.content?.url;
}
let historyVisibility;
if (stateHistoryVisibilityResDataOutcome.reason === undefined) {
const { data } = stateHistoryVisibilityResDataOutcome.value;
historyVisibility = data?.content?.history_visibility;
}
let joinRule;
if (stateJoinRulesResDataOutcome.reason === undefined) {
const { data } = stateJoinRulesResDataOutcome.value;
joinRule = data?.content?.join_rule;
}
let roomCreationTs;
let predecessorRoomId;
let predecessorLastKnownEventId;
let predecessorViaServers;
if (predecessorInfoOutcome.reason === undefined) {
({
currentRoomCreationTs: roomCreationTs,
predecessorRoomId,
predecessorLastKnownEventId,
predecessorViaServers,
} = predecessorInfoOutcome.value);
}
let successorRoomId;
let successorSetTs;
if (successorInfoOutcome.reason === undefined) {
({ successorRoomId, successorSetTs } = successorInfoOutcome.value);
}
return { return {
id: roomId, id: roomId,