diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index 5e5b6291..07b05a51 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -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 @@ -22,6 +23,7 @@ export class Intent { private readonly client: MatrixClient; private readonly storage: IAppserviceStorageProvider; + private readonly unstableApisInstance: UnstableAsApis; private knownJoinedRooms: string[] = []; @@ -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); @@ -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. diff --git a/src/appservice/UnstableAsApis.ts b/src/appservice/UnstableAsApis.ts new file mode 100644 index 00000000..991a5d5a --- /dev/null +++ b/src/appservice/UnstableAsApis.ts @@ -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 { + 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, + } + ); + } +} diff --git a/src/index.ts b/src/index.ts index f9abf7b2..5f179ad8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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"; diff --git a/test/appservice/UnstableAsApisTest.ts b/test/appservice/UnstableAsApisTest.ts new file mode 100644 index 00000000..79b683de --- /dev/null +++ b/test/appservice/UnstableAsApisTest.ts @@ -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); + }); + }); +});