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

MSC2716 support #133

Merged
merged 6 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 12 additions & 1 deletion src/appservice/Intent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Appservice, IAppserviceOptions } from "./Appservice";

// noinspection TypeScriptPreferShortImport
import { timedIntentFunctionCall } from "../metrics/decorators";
import { UnstableAsApis } from "./UnstableAsApis";

/**
* An Intent is an intelligent client that tracks things like the user's membership
Expand All @@ -22,6 +23,7 @@ export class Intent {

private readonly client: MatrixClient;
private readonly storage: IAppserviceStorageProvider;
private readonly unstableApisInstance: UnstableAsApis;

private knownJoinedRooms: string[] = [];

Expand All @@ -35,7 +37,7 @@ export class Intent {
this.metrics = new Metrics(appservice.metrics);
this.client = new MatrixClient(options.homeserverUrl, options.registration.as_token);
this.client.metrics = new Metrics(appservice.metrics); // Metrics only go up by one parent

this.unstableApisInstance = new UnstableAsApis(this.client);
this.storage = options.storage;
if (impersonateUserId !== appservice.botUserId) this.client.impersonateUserId(impersonateUserId);
if (options.joinStrategy) this.client.setJoinStrategy(options.joinStrategy);
Expand All @@ -55,6 +57,15 @@ export class Intent {
return this.client;
}

/**
* Gets the unstable API access class. This is generally not recommended to be
* used by appservices.
* @return {UnstableAsApis} The unstable API access class.
*/
public get unstableApis(): UnstableAsApis {
return this.unstableApisInstance;
}

/**
* Gets the joined rooms for the intent. Note that by working around
* the intent to join rooms may yield inaccurate results.
Expand Down
63 changes: 63 additions & 0 deletions src/appservice/UnstableAsApis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { MatrixClient } from "../MatrixClient";

export interface MSC2716BatchSendResponse {
/**
* List of historical state event IDs that were inserted
*/
state_events?: string[];
/**
* List of historical event IDs that were inserted
*/
events?: string[];
/**
* Chunk ID to be used in the next `sendHistoricalEventBatch` call.
*/
next_chunk_id: string;
}

export interface MSC2716InsertionEventContent {
"org.matrix.msc2716.next_chunk_id": string;
"org.matrix.msc2716.historical": true;
}

export interface MSC2716ChunkEventContent {
"org.matrix.msc2716.chunk_id": string;
"org.matrix.msc2716.historical": true;
}

export interface MSC2716MarkerEventContent {
"org.matrix.msc2716.insertion_id": string;
"org.matrix.msc2716.historical": true;
}

/**
* Unstable APIs that shouldn't be used in most circumstances for appservices.
* @category Unstable APIs
*/
export class UnstableAsApis {
constructor(private client: MatrixClient) { }

/**
* Send several historical events into a room.
* @see https://github.com/matrix-org/matrix-doc/pull/2716
* @param roomId The roomID to send to.
* @param prevEventId The event ID where this batch will be inserted
* @param chunkId The chunk ID returned from a previous call. Leave empty if this is the first batch.
* @param events A set of event contents for events to be inserted into the room.
* @param stateEventsAtStart A set of state events to be inserted into the room.
* @returns A set of eventIds and the next chunk ID
*/
public async sendHistoricalEventBatch(roomId: string, prevEventId: string, events: any[], stateEventsAtStart: any[] = [], chunkId?: string): Promise<MSC2716BatchSendResponse> {
return this.client.doRequest(
"POST",
`/_matrix/client/unstable/org.matrix.msc2716/rooms/${encodeURIComponent(roomId)}/batch_send`,
{
prev_event: prevEventId,
chunk_id: chunkId,
}, {
events,
state_events_at_start: stateEventsAtStart,
}
);
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from "./appservice/Appservice";
export * from "./appservice/Intent";
export * from "./appservice/MatrixBridge";
export * from "./appservice/http_responses";
export * from "./appservice/UnstableAsApis";

// Helpers
export * from "./helpers/RichReply";
Expand Down
53 changes: 53 additions & 0 deletions test/appservice/UnstableAsApisTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as expect from "expect";
import { MatrixClient } from "../../src/MatrixClient";
import * as MockHttpBackend from 'matrix-mock-request';
import * as simple from "simple-mock";
import { IStorageProvider, MSC2716BatchSendResponse, UnstableApis, UnstableAsApis } from "../../src";
import { createTestClient } from "../MatrixClientTest";

export function createTestUnstableClient(storage: IStorageProvider = null): { client: UnstableAsApis, mxClient: MatrixClient, http: MockHttpBackend, hsUrl: string, accessToken: string } {
const result = createTestClient(storage);
const mxClient = result.client;
const client = new UnstableAsApis(mxClient);

delete result.client;

return {...result, client, mxClient};
}

describe('UnstableApis', () => {
describe('sendHistoricalEventBatch', () => {
it('should call the right endpoint', async () => {
const {client, http, hsUrl} = createTestUnstableClient();

const events = [{foo: 5}, {bar: 10}];
const stateEvents = [{baz: 20}, {pong: 30}];
const roomId = "!room:example.org";
const prevEventId = "$prevEvent:example.org";
const prevChunkId = "chunkychunkyids";
const expectedResponse = {
state_events: ["$stateEv1:example.org", "$stateEv2:example.org"],
events: ["$event1:example.org", "$event2:example.org"],
next_chunk_id: "evenchunkierid",
} as MSC2716BatchSendResponse;

http.when("POST", `/_matrix/client/unstable/org.matrix.msc2716/rooms/${encodeURIComponent(roomId)}/batch_send`).respond(200, (path, content, {opts}) => {
expect(path).toEqual(`${hsUrl}/_matrix/client/unstable/org.matrix.msc2716/rooms/${encodeURIComponent(roomId)}/batch_send`);
expect(opts.qs).toEqual({
prev_event: prevEventId,
chunk_id: prevChunkId,
})
expect(content).toEqual({
events: events,
state_events_at_start: stateEvents,
});

return expectedResponse;
});

http.flushAllExpected();
const result = await client.sendHistoricalEventBatch(roomId, prevEventId, events, stateEvents, prevChunkId);
expect(result).toEqual(expectedResponse);
});
});
});