Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b722f55
fix(room): accept v11 create events without content.creator
sampaiodiego Aug 5, 2026
89ace0f
fix(room): allow the room creator's first join in v11 rooms
sampaiodiego Aug 5, 2026
eb627be
fix(core): verify inbound signatures with room-version-aware redaction
sampaiodiego Aug 5, 2026
5a299c7
fix(sdk): remove the duplicate, version-blind event-ID implementation
sampaiodiego Aug 6, 2026
8f8ee7a
fix(room): put m.room.redaction's redacts where the room version expe…
sampaiodiego Aug 6, 2026
2de5e03
fix(sdk): do not reject events from rooms without a dedicated schema set
sampaiodiego Aug 6, 2026
9a6ebaa
feat(sdk): add a v11 event schema set
sampaiodiego Aug 6, 2026
b5cd5b5
fix(sdk): stop putting a legacy top level origin on events
sampaiodiego Aug 6, 2026
60fe057
fix(sdk): build events with the room's version, not the default
sampaiodiego Aug 6, 2026
39dbc2b
feat(room): create rooms at version 11 by default
sampaiodiego Aug 6, 2026
312ec96
fix(sdk): advertise every supported version when joining a remote room
sampaiodiego Aug 6, 2026
bbf7a00
chore(sdk): export the v11 schema set alongside the v10 one
sampaiodiego Aug 6, 2026
5fa8122
fix(sdk): report a room creator for v11 rooms too
sampaiodiego Aug 6, 2026
d80ec5e
expore way to get redact event
sampaiodiego Aug 6, 2026
36d0a80
refactor: remove getRoomInformation in favor of getRoomVersion
sampaiodiego Aug 6, 2026
fa5a596
remove createSignedEvent and state pruneEventDict is v10 only
sampaiodiego Aug 11, 2026
2f5eb11
remove origin field on make_leave event
sampaiodiego Aug 11, 2026
6358e9e
test: add redact tests
sampaiodiego Aug 11, 2026
380bbda
test: document and test top-level redacts field
sampaiodiego Aug 11, 2026
e734797
fix make_join room version error
sampaiodiego Aug 11, 2026
c83f37c
test: overall test improvements
sampaiodiego Aug 11, 2026
63749de
test: improve tests
sampaiodiego Aug 11, 2026
b34a00b
assign room version to variable
sampaiodiego Aug 13, 2026
e845440
ci: add missing test files to ci
sampaiodiego Aug 13, 2026
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
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ jobs:
- run: bun lint
- run: bun tsc --noEmit
- uses: supercharge/mongodb-github-action@1.12.1
- run: RUN_MONGO_TESTS=1 bun test packages/federation-sdk/src/services/state.service.spec.ts packages/federation-sdk/src/services/room.service.spec.ts
- run: |
RUN_MONGO_TESTS=1 bun test \
packages/federation-sdk/src/services/invite.service.spec.ts \
packages/federation-sdk/src/services/room-version-coexistence.spec.ts \
packages/federation-sdk/src/services/room.service.spec.ts \
packages/federation-sdk/src/services/state.service.spec.ts
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/events/eventBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export type RedactedEvent = EventBase & {
};

export const isRedactedEvent = (event: Pdu): event is PduForType<'m.room.redaction'> => {
return event.type === 'm.room.redaction' && 'redacts' in event;
// the target is a top level field before room version 11 and part of content from v11 on
return event.type === 'm.room.redaction';
};

// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/events/homeserver-event-signatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export type HomeserverEventSignatures = {
'homeserver.matrix.redaction': {
event_id: EventID;
event: PduForType<'m.room.redaction'>;
// resolved for the room's version, since v11 moved the target from the top level into content
redacts: EventID | undefined;
};
'homeserver.matrix.membership': {
event_id: EventID;
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/events/m.reaction.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect, test } from 'bun:test';

import { PersistentEventFactory } from '@rocket.chat/federation-room';

import { reactionEvent } from './m.reaction';
import { generateId } from '../utils/generateId';
import { generateKeyPairsFromString } from '../utils/keys';
import { signEvent } from '../utils/signEvent';

Expand Down Expand Up @@ -52,8 +53,11 @@ test('reactionEvent', async () => {
});

const signedReaction = await signEvent(reaction, signature, 'rc1');
const reactionEventId = generateId(signedReaction);
const reactionEventId = PersistentEventFactory.createFromRawEvent(signedReaction as any, '10').eventId;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

expect(signedReaction).toMatchObject(finalEvent);
expect(reactionEventId).toBe(reactionEventId);

// pinned from this implementation rather than captured from Synapse: it guards against
// unintended changes to the id computation, it does not prove the id is spec correct
expect(reactionEventId).toBe('$M01iR1pDCIvC7MqCClelXqsEvFK6iO_pA2d1aJc6Oh4');
});
3 changes: 0 additions & 3 deletions packages/core/src/events/m.reaction.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { EventID } from '@rocket.chat/federation-room';

import { type EventBase, createEventBase } from './eventBase';
import { createEventWithId } from './utils/createSignedEvent';

declare module './eventBase' {
interface Events {
Expand Down Expand Up @@ -88,5 +87,3 @@ export const reactionEvent = ({
unsigned: { ...unsigned, age_ts: ts },
});
};

export const createReactionEvent = createEventWithId(reactionEvent);
3 changes: 1 addition & 2 deletions packages/core/src/events/m.room.create.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { PersistentEventFactory } from '@rocket.chat/federation-room';

import type { SignedEvent } from '../types';
import { type RoomCreateEvent, isRoomCreateEvent, roomCreateEvent } from './m.room.create';
import { generateId } from '../utils/generateId';
import { generateKeyPairsFromString } from '../utils/keys';
import { signEvent } from '../utils/signEvent';

Expand Down Expand Up @@ -52,7 +51,7 @@ test('roomCreateEvent', async () => {
'rmnvsWlTL+JP8Sk9767UR0svF4IrzC9zhUPbT+y4u31r/qtIaF9OtT1FP8tD/yFGD92qoTcRb4Oo8DRbLRXcAg',
);

const eventId = generateId(signed);
const { eventId } = PersistentEventFactory.createFromRawEvent(signed as any, '10');

expect(eventId).toBe(finalEventId);
});
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/events/m.room.create.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { type EventBase, createEventBase } from './eventBase';
import { createEventWithId } from './utils/createSignedEvent';

export interface RoomCreateEvent extends EventBase {
content: {
Expand Down Expand Up @@ -38,6 +37,4 @@ export const roomCreateEvent = ({ roomId, sender, ts = Date.now() }: RoomCreateE
unsigned: { age_ts: ts },
});

export const createRoomCreateEvent = createEventWithId((...args: any[]) => roomCreateEvent(args[0]));

export const isRoomCreateEvent = (event: EventBase): event is RoomCreateEvent => event.type === 'm.room.create';
5 changes: 3 additions & 2 deletions packages/core/src/events/m.room.guest_access.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect, test } from 'bun:test';

import { PersistentEventFactory } from '@rocket.chat/federation-room';

import { roomGuestAccessEvent } from './m.room.guest_access';
import { generateId } from '../utils/generateId';
import { generateKeyPairsFromString } from '../utils/keys';
import { signEvent } from '../utils/signEvent';

Expand Down Expand Up @@ -54,7 +55,7 @@ test('roomGuestAccessEvent', async () => {
'PLaE7un6a+pzrsU/0kiB/tvneZp5/dEda4+uE7UK411hNaM4W4ZUo52ua6AGO9q5gLBjSmnR90/tPf714HiTBw',
);

const eventId = generateId(signed);
const { eventId } = PersistentEventFactory.createFromRawEvent(signed as any, '10');

expect(eventId).toBe(finalEventId);
});
3 changes: 0 additions & 3 deletions packages/core/src/events/m.room.guest_access.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { EventID } from '@rocket.chat/federation-room';

import { type EventBase, createEventBase } from './eventBase';
import { createEventWithId } from './utils/createSignedEvent';

declare module './eventBase' {
interface Events {
Expand Down Expand Up @@ -44,5 +43,3 @@ export const roomGuestAccessEvent = ({
unsigned: { age_ts: ts },
});
};

export const createRoomGuestAccessEvent = createEventWithId(roomGuestAccessEvent);
5 changes: 3 additions & 2 deletions packages/core/src/events/m.room.history_visibility.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect, test } from 'bun:test';

import { PersistentEventFactory } from '@rocket.chat/federation-room';

import { roomHistoryVisibilityEvent } from './m.room.history_visibility';
import { generateId } from '../utils/generateId';
import { generateKeyPairsFromString } from '../utils/keys';
import { signEvent } from '../utils/signEvent';

Expand Down Expand Up @@ -56,7 +57,7 @@ test('roomHistoryVisibilityEvent', async () => {
'ZHzOfPU2BYDilKSrt5zqMBC9ohZtHph4uLldOIzBY/oTO1pZCp3D9CRr04h5eJ7zkkuzkNv4y8+N0TDPNMHFBg',
);

const eventId = generateId(signed);
const { eventId } = PersistentEventFactory.createFromRawEvent(signed as any, '10');

expect(eventId).toBe(finalEventId);
});
3 changes: 0 additions & 3 deletions packages/core/src/events/m.room.history_visibility.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { EventID } from '@rocket.chat/federation-room';

import { type EventBase, createEventBase } from './eventBase';
import { createEventWithId } from './utils/createSignedEvent';

declare module './eventBase' {
interface Events {
Expand Down Expand Up @@ -46,5 +45,3 @@ export const roomHistoryVisibilityEvent = ({
unsigned: { age_ts: ts },
});
};

export const createRoomHistoryVisibilityEvent = createEventWithId(roomHistoryVisibilityEvent);
5 changes: 3 additions & 2 deletions packages/core/src/events/m.room.join_rules.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect, test } from 'bun:test';

import { PersistentEventFactory } from '@rocket.chat/federation-room';

import { isRoomJoinRulesEvent, roomJoinRulesEvent } from './m.room.join_rules';
import { generateId } from '../utils/generateId';
import { generateKeyPairsFromString } from '../utils/keys';
import { signEvent } from '../utils/signEvent';

Expand Down Expand Up @@ -54,7 +55,7 @@ test('roomJoinRulesEvent', async () => {
'egXzghr88RZMZYG4/DUrIf92NiUiC59GhgmvB1zV5oSuDuCGXgYnVBmXOfQ54ElXx1AFc8ajwPmfupXoYkHaAg',
);

const eventId = generateId(signed);
const { eventId } = PersistentEventFactory.createFromRawEvent(signed as any, '10');

expect(eventId).toBe(finalEventId);
});
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/events/m.room.join_rules.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { EventID } from '@rocket.chat/federation-room';

import { type EventBase, createEventBase } from './eventBase';
import { createEventWithId } from './utils/createSignedEvent';

declare module './eventBase' {
interface Events {
Expand Down Expand Up @@ -49,8 +48,6 @@ export const roomJoinRulesEvent = ({
});
};

export const createRoomJoinRulesEvent = createEventWithId(roomJoinRulesEvent);

export const isRoomJoinRulesEvent = (event: EventBase): event is RoomJoinRulesEvent => {
return event.type === 'm.room.join_rules';
};
5 changes: 3 additions & 2 deletions packages/core/src/events/m.room.member-invite.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect, test } from 'bun:test';

import { PersistentEventFactory } from '@rocket.chat/federation-room';

import { roomMemberEvent } from './m.room.member';
import { generateId } from '../utils/generateId';
import { generateKeyPairsFromString } from '../utils/keys';
import { signEvent } from '../utils/signEvent';

Expand Down Expand Up @@ -117,7 +118,7 @@ test('roomMemberInviteEvent', async () => {
'8/qPp2d0PTc4bVMNdbTl32OSFnNqXan9ACQr1QcDV3SgdsDnm+sZv2mXW8rdhIOLOohRG2cED0+1aNxV7VH2Cw',
);

const memberEventId = generateId(signed);
const memberEventId = PersistentEventFactory.createFromRawEvent(signed as any, '10').eventId;

expect(memberEventId).toBe(finalEventId);
});
36 changes: 20 additions & 16 deletions packages/core/src/events/m.room.member.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { expect, test } from 'bun:test';

import type { Pdu } from '@rocket.chat/federation-room';
import { PersistentEventFactory } from '@rocket.chat/federation-room';

import { roomCreateEvent } from './m.room.create';
import { roomMemberEvent } from './m.room.member';
import { generateId } from '../utils/generateId';
import { generateKeyPairsFromString } from '../utils/keys';
import { signEvent } from '../utils/signEvent';

const eventIdOf = (event: unknown) => PersistentEventFactory.createFromRawEvent(event as Pdu, '10').eventId;

const finalEventId = '$tZRt2bwceX4sG913Ee67tJiwe-gk859kY2mCeYSncw8';
const finalEvent = {
auth_events: ['$0AQU5dG_mtjH6qavAxYrQsDC0a_-6T3DHs1yoxf5fz4'],
Expand Down Expand Up @@ -37,7 +41,7 @@ test('roomMemberEvent', async () => {
});
const signedCreateEvent = await signEvent(createEvent, signature, 'hs1');

const createEventId = generateId(signedCreateEvent);
const createEventId = eventIdOf(signedCreateEvent);
const memberEvent = roomMemberEvent({
membership: 'join',
roomId: '!uTqsSSWabZzthsSCNf:hs1',
Expand All @@ -62,7 +66,7 @@ test('roomMemberEvent', async () => {
'y/qV5T9PeXvqgwRafZDSygtk4XRMstdt04qusZWJSu77Juxzzz4Ijyk+JsJ5NNV0/WWYMT9IhmVb7/EEBH4vDQ',
);

const memberEventId = generateId(signed);
const memberEventId = eventIdOf(signed);

expect(memberEventId).toBe(finalEventId);
});
Expand All @@ -80,7 +84,7 @@ test('roomMemberEvent - leave', async () => {
ts: ts - 1000,
});
const signedCreateEvent = await signEvent(createEventPayload, signature, serverName);
const createEventId = generateId(signedCreateEvent);
const createEventId = eventIdOf(signedCreateEvent);

// A user usually joins before they can leave
const joinMemberEventPayload = roomMemberEvent({
Expand All @@ -96,7 +100,7 @@ test('roomMemberEvent - leave', async () => {
origin: serverName,
});
const signedJoinEvent = await signEvent(joinMemberEventPayload, signature, serverName);
const joinEventId = generateId(signedJoinEvent);
const joinEventId = eventIdOf(signedJoinEvent);

// Now, the leave event
const leaveMemberEventPayload = roomMemberEvent({
Expand All @@ -118,7 +122,7 @@ test('roomMemberEvent - leave', async () => {
});

const signedLeaveEvent = await signEvent(leaveMemberEventPayload, signature, serverName);
const leaveEventId = generateId(signedLeaveEvent);
const leaveEventId = eventIdOf(signedLeaveEvent);

expect(signedLeaveEvent.type).toBe('m.room.member');
expect(signedLeaveEvent.room_id).toBe(roomId);
Expand Down Expand Up @@ -152,7 +156,7 @@ test('roomMemberEvent - kick', async () => {
ts: ts - 4000,
});
const signedCreateEvent = await signEvent(createEventPayload, kickerSignature, serverName);
const createEventId = generateId(signedCreateEvent);
const createEventId = eventIdOf(signedCreateEvent);
let lastEventId = createEventId;
let currentDepth = 1;

Expand Down Expand Up @@ -186,7 +190,7 @@ test('roomMemberEvent - kick', async () => {
origin_server_ts: ts - 3000,
};
const signedPowerLevelsEvent = await signEvent(powerLevelsEventPayload, kickerSignature, serverName);
const powerLevelsEventId = generateId(signedPowerLevelsEvent);
const powerLevelsEventId = eventIdOf(signedPowerLevelsEvent);
lastEventId = powerLevelsEventId;

// 3. Kicker Joins (sent by kicker)
Expand All @@ -206,7 +210,7 @@ test('roomMemberEvent - kick', async () => {
origin: serverName,
});
const signedKickerJoinEvent = await signEvent(kickerJoinEventPayload, kickerSignature, serverName);
const kickerJoinEventId = generateId(signedKickerJoinEvent);
const kickerJoinEventId = eventIdOf(signedKickerJoinEvent);
lastEventId = kickerJoinEventId;

// 4. UserToKick Joins (sent by userToKick)
Expand All @@ -226,7 +230,7 @@ test('roomMemberEvent - kick', async () => {
origin: serverName,
});
const signedUserToKickJoinEvent = await signEvent(userToKickJoinEventPayload, userToKickSignature, serverName);
const userToKickJoinEventId = generateId(signedUserToKickJoinEvent);
const userToKickJoinEventId = eventIdOf(signedUserToKickJoinEvent);
lastEventId = userToKickJoinEventId;

// 5. Kick Event (sent by kicker, targets userToKick)
Expand All @@ -252,7 +256,7 @@ test('roomMemberEvent - kick', async () => {
});

const signedKickEvent = await signEvent(kickMemberEventPayload, kickerSignature, serverName);
const kickEventId = generateId(signedKickEvent);
const kickEventId = eventIdOf(signedKickEvent);

// Assertions
expect(signedKickEvent.type).toBe('m.room.member');
Expand Down Expand Up @@ -292,7 +296,7 @@ test('roomMemberEvent - ban', async () => {
ts: ts - 4000,
});
const signedCreateEvent = await signEvent(createEventPayload, bannerSignature, serverName);
const createEventId = generateId(signedCreateEvent);
const createEventId = eventIdOf(signedCreateEvent);
let lastEventId = createEventId;
let currentDepth = 1;

Expand Down Expand Up @@ -326,7 +330,7 @@ test('roomMemberEvent - ban', async () => {
origin_server_ts: ts - 3000,
};
const signedPowerLevelsEvent = await signEvent(powerLevelsEventPayload, bannerSignature, serverName);
const powerLevelsEventId = generateId(signedPowerLevelsEvent);
const powerLevelsEventId = eventIdOf(signedPowerLevelsEvent);
lastEventId = powerLevelsEventId;

// 3. Banner Joins (sent by banner)
Expand All @@ -346,7 +350,7 @@ test('roomMemberEvent - ban', async () => {
origin: serverName,
});
const signedBannerJoinEvent = await signEvent(bannerJoinEventPayload, bannerSignature, serverName);
const bannerJoinEventId = generateId(signedBannerJoinEvent);
const bannerJoinEventId = eventIdOf(signedBannerJoinEvent);
lastEventId = bannerJoinEventId;

// 4. UserToBan Joins (sent by userToBan)
Expand All @@ -366,7 +370,7 @@ test('roomMemberEvent - ban', async () => {
origin: serverName,
});
const signedUserToBanJoinEvent = await signEvent(userToBanJoinEventPayload, userToBanSignature, serverName);
const userToBanJoinEventId = generateId(signedUserToBanJoinEvent);
const userToBanJoinEventId = eventIdOf(signedUserToBanJoinEvent);
lastEventId = userToBanJoinEventId;

// 5. Ban Event (sent by banner, targets userToBan)
Expand All @@ -392,7 +396,7 @@ test('roomMemberEvent - ban', async () => {
});

const signedBanEvent = await signEvent(banMemberEventPayload, bannerSignature, serverName);
const banEventId = generateId(signedBanEvent);
const banEventId = eventIdOf(signedBanEvent);

// Assertions
expect(signedBanEvent.type).toBe('m.room.member');
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/events/m.room.member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { EventID } from '@rocket.chat/federation-room';

import { createEventBase } from './eventBase';
import type { Membership, RoomMemberEvent } from './isRoomMemberEvent';
import { createEventWithId } from './utils/createSignedEvent';

declare module './eventBase' {
interface Events {
Expand Down Expand Up @@ -80,5 +79,3 @@ export const roomMemberEvent = ({
unsigned: { age_ts: ts, ...unsigned },
});
};

export const createRoomMemberEvent = createEventWithId(roomMemberEvent);
Loading
Loading