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

Follow tombstone and predecessor history #167

Merged
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d5272e3
Draft: mostly test setup
MadLittleMods Apr 10, 2023
359f95d
Draft: jumping to predecessor/successor
MadLittleMods Apr 11, 2023
536512b
Merge branch 'main' into madlittlemods/59-follow-tombstone-and-predec…
MadLittleMods Apr 11, 2023
7f98efa
Better log when event is missing from expected
MadLittleMods Apr 11, 2023
63fac48
Add note on alternative place to start from
MadLittleMods Apr 12, 2023
d27b6c2
Add comment explaining what we're parsing here
MadLittleMods Apr 12, 2023
0e21449
Crude working to jump backwards to predecessor in first test case
MadLittleMods Apr 13, 2023
14f6e72
Fix m.room.create predecessor format
MadLittleMods Apr 13, 2023
4549cc9
Slight refactor
MadLittleMods Apr 13, 2023
ed66667
Add note about assuming we are already joined
MadLittleMods Apr 13, 2023
60b11dd
Re-usable URL converting utilities
MadLittleMods Apr 13, 2023
bd94901
Add test for same day
MadLittleMods Apr 13, 2023
078fa0a
Change up tactic to display the given day of the predecessor room
MadLittleMods Apr 13, 2023
54d7feb
Bail earlier
MadLittleMods Apr 13, 2023
f0bbc2b
Cleaner fetching
MadLittleMods Apr 13, 2023
b8a80fd
Working predecessor tests
MadLittleMods Apr 13, 2023
17a045a
Fix jumping forward when there is a multiple day gap
MadLittleMods Apr 18, 2023
0834f3f
Fix assertions in jump forward successor test
MadLittleMods Apr 18, 2023
c91a326
Fix predecessor not being picked up
MadLittleMods Apr 18, 2023
4fd584d
Add more unconfirmed tests
MadLittleMods Apr 18, 2023
e9704a6
Revert back gap time so paginate out further as expected
MadLittleMods Apr 18, 2023
decb777
Fix jumping backwards when messages from the same hour
MadLittleMods Apr 19, 2023
4d2a130
Add some tests that fail starting from larger precision
MadLittleMods Apr 19, 2023
4a2bbcb
Try `ts` and doTimestampsStartFromSameUtcX(ts, tsForClosestEvent)
MadLittleMods Apr 19, 2023
43bdc7d
Passing tests and maybe good logic
MadLittleMods Apr 19, 2023
a969b4d
Simplify and tests still pass
MadLittleMods Apr 19, 2023
198a4b4
Add missing tests (all tests passing)
MadLittleMods Apr 19, 2023
d00cbfe
Remove `doTimestampsShareRoundedUpUtcX` utilities and rename `doTimes…
MadLittleMods Apr 19, 2023
1d89487
Remove debug logging
MadLittleMods Apr 19, 2023
81bc77a
Prefer tracking just by searching MSC3999 everywhere
MadLittleMods Apr 19, 2023
9389bb2
Add comment thread reference for future reading
MadLittleMods Apr 19, 2023
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
11 changes: 9 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"start": "node server/server.js",
"start-dev": "node server/start-dev.js",
"test": "npm run mocha -- test/**/*-tests.js --timeout 15000",
"test-e2e-interactive": "npm run mocha -- test/e2e-tests.js --timeout 15000 --interactive",
"test-e2e-interactive": "npm run mocha -- test/e2e-tests.js --timeout 15000 --bail --interactive",
"nodemon": "nodemon",
"vite": "vite",
"mocha": "mocha",
Expand All @@ -23,6 +23,7 @@
"node": ">=16.0.0"
},
"devDependencies": {
"chalk": "^4.1.2",
"escape-string-regexp": "^4.0.0",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
Expand Down
205 changes: 166 additions & 39 deletions server/lib/matrix-utils/fetch-room-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,89 +4,204 @@ 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);

async function fetchRoomData(accessToken, roomId) {
assert(accessToken);
assert(roomId);

const stateNameEndpoint = urlJoin(
matrixServerUrl,
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/state/m.room.name`
);
const canoncialAliasEndpoint = urlJoin(
matrixServerUrl,
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/state/m.room.canonical_alias`
);
const stateAvatarEndpoint = urlJoin(
function getStateEndpointForRoomIdAndEventType(roomId, eventType) {
return urlJoin(
matrixServerUrl,
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/state/m.room.avatar`
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/state/${encodeURIComponent(
eventType
)}?format=event`
);
const stateHistoryVisibilityEndpoint = urlJoin(
matrixServerUrl,
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/state/m.room.history_visibility`
);
const stateJoinRulesEndpoint = urlJoin(
matrixServerUrl,
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/state/m.room.join_rules`
}

// 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) {
const { data } = await fetchEndpointAsJson(
urlJoin(
matrixServerUrl,
`_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/messages?dir=f&limit1`
),
{
accessToken: matrixAccessToken,
}
);

const roomCreateEventId = data?.chunk?.[0]?.event_id;

return roomCreateEventId;
});

const fetchRoomCreationInfo = traceFunction(async function (matrixAccessToken, roomId) {
const [stateCreateResDataOutcome] = await Promise.allSettled([
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.create'), {
accessToken: matrixAccessToken,
}),
]);

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) {
const [roomCreationInfoOutcome, statePredecessorResDataOutcome] = await Promise.allSettled([
fetchRoomCreationInfo(matrixAccessToken, roomId),
fetchEndpointAsJson(
getStateEndpointForRoomIdAndEventType(roomId, 'org.matrix.msc3946.room_predecessor'),
{
accessToken: matrixAccessToken,
}
),
]);

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) {
const [stateTombstoneResDataOutcome] = await Promise.allSettled([
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.tombstone'), {
accessToken: matrixAccessToken,
}),
]);

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) {
assert(matrixAccessToken);
assert(roomId);

const [
stateNameResDataOutcome,
stateCanonicalAliasResDataOutcome,
stateAvatarResDataOutcome,
stateHistoryVisibilityResDataOutcome,
stateJoinRulesResDataOutcome,
predecessorInfoOutcome,
successorInfoOutcome,
] = await Promise.allSettled([
fetchEndpointAsJson(stateNameEndpoint, {
accessToken,
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.name'), {
accessToken: matrixAccessToken,
}),
fetchEndpointAsJson(canoncialAliasEndpoint, {
accessToken,
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.canonical_alias'), {
accessToken: matrixAccessToken,
}),
fetchEndpointAsJson(stateAvatarEndpoint, {
accessToken,
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.avatar'), {
accessToken: matrixAccessToken,
}),
fetchEndpointAsJson(stateHistoryVisibilityEndpoint, {
accessToken,
}),
fetchEndpointAsJson(stateJoinRulesEndpoint, {
accessToken,
fetchEndpointAsJson(
getStateEndpointForRoomIdAndEventType(roomId, 'm.room.history_visibility'),
{
accessToken: matrixAccessToken,
}
),
fetchEndpointAsJson(getStateEndpointForRoomIdAndEventType(roomId, 'm.room.join_rules'), {
accessToken: matrixAccessToken,
}),
fetchPredecessorInfo(matrixAccessToken, roomId),
fetchSuccessorInfo(matrixAccessToken, roomId),
]);

let name;
if (stateNameResDataOutcome.reason === undefined) {
const { data } = stateNameResDataOutcome.value;
name = data.name;
name = data?.content?.name;
}

let canonicalAlias;
if (stateCanonicalAliasResDataOutcome.reason === undefined) {
const { data } = stateCanonicalAliasResDataOutcome.value;
canonicalAlias = data.alias;
canonicalAlias = data?.content?.alias;
}

let avatarUrl;
if (stateAvatarResDataOutcome.reason === undefined) {
const { data } = stateAvatarResDataOutcome.value;
avatarUrl = data.url;
avatarUrl = data?.content?.url;
}

let historyVisibility;
if (stateHistoryVisibilityResDataOutcome.reason === undefined) {
const { data } = stateHistoryVisibilityResDataOutcome.value;
historyVisibility = data.history_visibility;
historyVisibility = data?.content?.history_visibility;
}

let joinRule;
if (stateJoinRulesResDataOutcome.reason === undefined) {
const { data } = stateJoinRulesResDataOutcome.value;
joinRule = data.join_rule;
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 {
Expand All @@ -96,7 +211,19 @@ async function fetchRoomData(accessToken, roomId) {
avatarUrl,
historyVisibility,
joinRule,
roomCreationTs,
predecessorRoomId,
predecessorLastKnownEventId,
predecessorViaServers,
successorRoomId,
successorSetTs,
};
}
});

module.exports = traceFunction(fetchRoomData);
module.exports = {
fetchRoomData,
fetchRoomCreationInfo,
fetchPredecessorInfo,
fetchSuccessorInfo,
removeMe_fetchRoomCreateEventId,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const assert = require('assert');

// See https://spec.matrix.org/v1.5/appendices/#server-name
function getServerNameFromMatrixRoomIdOrAlias(roomIdOrAlias) {
// `roomIdOrAlias` looks like `!foo:matrix.org` or `#foo:matrix.org` or even something
// as crazy as `!foo:[1234:5678::abcd]:1234` where `[1234:5678::abcd]:1234` is the
// server name part we're trying to parse out (see tests for more examples)
assert(roomIdOrAlias);

const pieces = roomIdOrAlias.split(':');
Expand Down
Loading