271 lines
7.9 KiB
JavaScript
271 lines
7.9 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
|
|
const urlJoin = require('url-join');
|
|
const { fetchEndpointAsJson } = require('../fetch-endpoint');
|
|
const parseViaServersFromUserInput = require('../parse-via-servers-from-user-input');
|
|
const { traceFunction } = require('../../tracing/trace-utilities');
|
|
|
|
const config = require('../config');
|
|
const matrixServerUrl = config.get('matrixServerUrl');
|
|
assert(matrixServerUrl);
|
|
|
|
function getStateEndpointForRoomIdAndEventType(roomId, eventType) {
|
|
return urlJoin(
|
|
matrixServerUrl,
|
|
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/state/${encodeURIComponent(
|
|
eventType
|
|
)}?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
|
|
// https://github.com/matrix-org/synapse/issues/15454
|
|
//
|
|
// TODO: Remove this when we have MSC3999 (because it's the only usage)
|
|
const removeMe_fetchRoomCreateEventId = traceFunction(async function (
|
|
matrixAccessToken,
|
|
roomId,
|
|
{ abortSignal } = {}
|
|
) {
|
|
const { data } = await fetchEndpointAsJson(
|
|
urlJoin(
|
|
matrixServerUrl,
|
|
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/messages?dir=f&limit1`
|
|
),
|
|
{
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}
|
|
);
|
|
|
|
const roomCreateEventId = data?.chunk?.[0]?.event_id;
|
|
|
|
return roomCreateEventId;
|
|
});
|
|
|
|
const fetchRoomCreationInfo = traceFunction(async function (
|
|
matrixAccessToken,
|
|
roomId,
|
|
{ abortSignal } = {}
|
|
) {
|
|
const [stateCreateResDataOutcome] = await Promise.allSettled([
|
|
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.create'), {
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}),
|
|
]);
|
|
|
|
let roomCreationTs;
|
|
let predecessorRoomId;
|
|
let predecessorLastKnownEventId;
|
|
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 };
|
|
});
|
|
|
|
const fetchPredecessorInfo = traceFunction(async function (
|
|
matrixAccessToken,
|
|
roomId,
|
|
{ abortSignal } = {}
|
|
) {
|
|
const [roomCreationInfoOutcome, statePredecessorResDataOutcome] = await Promise.allSettled([
|
|
fetchRoomCreationInfo(matrixAccessToken, roomId, { abortSignal }),
|
|
fetchEndpointAsJson(
|
|
getStateEndpointForRoomIdAndEventType(roomId, 'org.matrix.msc3946.room_predecessor'),
|
|
{
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}
|
|
),
|
|
]);
|
|
|
|
let predecessorRoomId;
|
|
let predecessorLastKnownEventId;
|
|
let predecessorViaServers;
|
|
// Prefer the dynamic predecessor from the dedicated state event
|
|
if (statePredecessorResDataOutcome.reason === undefined) {
|
|
const { data } = statePredecessorResDataOutcome.value;
|
|
predecessorRoomId = data?.content?.predecessor_room_id;
|
|
predecessorLastKnownEventId = data?.content?.last_known_event_id;
|
|
predecessorViaServers = parseViaServersFromUserInput(data?.content?.via_servers);
|
|
}
|
|
// Then fallback to the predecessor defined by the room creation event
|
|
else if (roomCreationInfoOutcome.reason === undefined) {
|
|
({ predecessorRoomId, predecessorLastKnownEventId } = roomCreationInfoOutcome.value);
|
|
}
|
|
|
|
const { roomCreationTs: currentRoomCreationTs } = roomCreationInfoOutcome;
|
|
|
|
return {
|
|
// This is prefixed with "current" so we don't get this confused with the
|
|
// predecessor room creation timestamp.
|
|
currentRoomCreationTs,
|
|
predecessorRoomId,
|
|
predecessorLastKnownEventId,
|
|
predecessorViaServers,
|
|
};
|
|
});
|
|
|
|
const fetchSuccessorInfo = traceFunction(async function (
|
|
matrixAccessToken,
|
|
roomId,
|
|
{ abortSignal } = {}
|
|
) {
|
|
const [stateTombstoneResDataOutcome] = await Promise.allSettled([
|
|
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.tombstone'), {
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}),
|
|
]);
|
|
|
|
let successorRoomId;
|
|
let successorSetTs;
|
|
if (stateTombstoneResDataOutcome.reason === undefined) {
|
|
const { data } = stateTombstoneResDataOutcome.value;
|
|
successorRoomId = data?.content?.replacement_room;
|
|
successorSetTs = data?.origin_server_ts;
|
|
}
|
|
|
|
return {
|
|
successorRoomId,
|
|
successorSetTs,
|
|
};
|
|
});
|
|
|
|
// eslint-disable-next-line max-statements
|
|
const fetchRoomData = traceFunction(async function (
|
|
matrixAccessToken,
|
|
roomId,
|
|
{ abortSignal } = {}
|
|
) {
|
|
assert(matrixAccessToken);
|
|
assert(roomId);
|
|
|
|
const [
|
|
stateNameResDataOutcome,
|
|
stateTopicResDataOutcome,
|
|
stateCanonicalAliasResDataOutcome,
|
|
stateAvatarResDataOutcome,
|
|
stateHistoryVisibilityResDataOutcome,
|
|
stateJoinRulesResDataOutcome,
|
|
predecessorInfoOutcome,
|
|
successorInfoOutcome,
|
|
] = await Promise.allSettled([
|
|
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.name'), {
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}),
|
|
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.topic'), {
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}),
|
|
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.canonical_alias'), {
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}),
|
|
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.avatar'), {
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}),
|
|
fetchEndpointAsJson(
|
|
getStateEndpointForRoomIdAndEventType(roomId, 'm.room.history_visibility'),
|
|
{
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}
|
|
),
|
|
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.join_rules'), {
|
|
accessToken: matrixAccessToken,
|
|
abortSignal,
|
|
}),
|
|
fetchPredecessorInfo(matrixAccessToken, roomId, { abortSignal }),
|
|
fetchSuccessorInfo(matrixAccessToken, roomId, { abortSignal }),
|
|
]);
|
|
|
|
let name;
|
|
if (stateNameResDataOutcome.reason === undefined) {
|
|
const { data } = stateNameResDataOutcome.value;
|
|
name = data?.content?.name;
|
|
}
|
|
|
|
let canonicalAlias;
|
|
if (stateCanonicalAliasResDataOutcome.reason === undefined) {
|
|
const { data } = stateCanonicalAliasResDataOutcome.value;
|
|
canonicalAlias = data?.content?.alias;
|
|
}
|
|
|
|
let topic;
|
|
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 {
|
|
id: roomId,
|
|
name,
|
|
topic,
|
|
canonicalAlias,
|
|
avatarUrl,
|
|
historyVisibility,
|
|
joinRule,
|
|
roomCreationTs,
|
|
predecessorRoomId,
|
|
predecessorLastKnownEventId,
|
|
predecessorViaServers,
|
|
successorRoomId,
|
|
successorSetTs,
|
|
};
|
|
});
|
|
|
|
module.exports = {
|
|
fetchRoomData,
|
|
fetchRoomCreationInfo,
|
|
fetchPredecessorInfo,
|
|
fetchSuccessorInfo,
|
|
removeMe_fetchRoomCreateEventId,
|
|
};
|