From 9a2bf4980a3d5144365d0dde06935758960cd99e Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 14:37:32 +0100 Subject: [PATCH 01/12] add prettier config --- package.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/package.json b/package.json index 7cd7862..5add654 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,22 @@ "lint:workflows": "find .github/workflows -type f \\( -iname '*.yaml' -o -iname '*.yml' \\) | xargs -I {} sh -c 'echo \"Linting {}\"; action-validator \"{}\"'", "test": "jest" }, + "prettier": { + "printWidth": 120, + "tabWidth": 2, + "quoteProps": "consistent", + "trailingComma": "all", + "overrides": [ + { + "files": [ + "src/**/*.ts" + ], + "options": { + "tabWidth": 4 + } + } + ] + }, "files": [ "src", "lib", From b5415744347452e42f4d326ae4c3f7fca5bfe986 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 17:14:52 +0100 Subject: [PATCH 02/12] Prettier in test files --- package.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5add654..30c250e 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,20 @@ "overrides": [ { "files": [ - "src/**/*.ts" + "src/**/*.ts", + "test/**/*.ts" ], "options": { "tabWidth": 4 } + }, + { + "files": [ + "test/**/*.ts" + ], + "options": { + "singleQuote": true + } } ] }, From b9737bf9382119a7de4da6fe3ebbf15a2fa996b1 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 17:17:58 +0100 Subject: [PATCH 03/12] apply prettier --- .babelrc | 11 +- .eslintrc.js | 92 +-- .github/workflows/build.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/sonarqube.yml | 24 +- README.md | 20 +- examples/widget/index.css | 25 +- examples/widget/index.html | 62 +- examples/widget/utils.js | 14 +- jest.config.ts | 12 +- package.json | 1 + renovate.json | 4 +- src/ClientWidgetApi.ts | 384 +++++++------ src/WidgetApi.ts | 223 ++++---- src/driver/WidgetDriver.ts | 33 +- src/interfaces/Capabilities.ts | 20 +- src/interfaces/DownloadFileAction.ts | 22 +- src/interfaces/GetMediaConfigAction.ts | 20 +- src/interfaces/IWidgetApiErrorResponse.ts | 9 +- src/interfaces/LanguageChangeAction.ts | 8 +- src/interfaces/ReadRelationsAction.ts | 2 +- src/interfaces/SendEventAction.ts | 3 +- src/interfaces/StickerAction.ts | 3 +- src/interfaces/ThemeChangeAction.ts | 2 +- src/interfaces/TurnServerActions.ts | 3 +- src/interfaces/UploadFileAction.ts | 22 +- src/models/WidgetEventCapability.ts | 24 +- src/models/WidgetParser.ts | 14 +- src/templating/url-template.ts | 24 +- src/transport/ITransport.ts | 7 +- src/transport/PostmessageTransport.ts | 19 +- test/ClientWidgetApi-test.ts | 512 +++++++---------- test/WidgetApi-test.ts | 655 ++++++++++------------ test/url-template-test.ts | 22 +- tsconfig-dev.json | 4 +- tsconfig.json | 9 +- yarn.lock | 5 + 37 files changed, 1070 insertions(+), 1248 deletions(-) diff --git a/.babelrc b/.babelrc index 028dc10..e0f4da4 100644 --- a/.babelrc +++ b/.babelrc @@ -1,10 +1,5 @@ { - "sourceMaps": true, - "presets": [ - "@babel/preset-env", - "@babel/preset-typescript" - ], - "plugins": [ - "@babel/plugin-proposal-class-properties" - ] + "sourceMaps": true, + "presets": ["@babel/preset-env", "@babel/preset-typescript"], + "plugins": ["@babel/plugin-proposal-class-properties"] } diff --git a/.eslintrc.js b/.eslintrc.js index 2ce44f3..02a10fe 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,50 +1,52 @@ module.exports = { - plugins: [ - "matrix-org", + plugins: ["matrix-org"], + extends: ["plugin:matrix-org/babel"], + parserOptions: { + project: ["./tsconfig-dev.json"], + }, + env: { + browser: true, + }, + rules: { + "no-var": ["warn"], + "prefer-rest-params": ["warn"], + "prefer-spread": ["warn"], + "one-var": ["warn"], + "padded-blocks": ["warn"], + "no-extend-native": ["warn"], + "camelcase": ["warn"], + "no-multi-spaces": ["error", { ignoreEOLComments: true }], + "space-before-function-paren": [ + "error", + { + anonymous: "never", + named: "never", + asyncArrow: "always", + }, ], - extends: [ - "plugin:matrix-org/babel", - ], - parserOptions: { - project: ["./tsconfig-dev.json"], - }, - env: { - browser: true, - }, - rules: { - "no-var": ["warn"], - "prefer-rest-params": ["warn"], - "prefer-spread": ["warn"], - "one-var": ["warn"], - "padded-blocks": ["warn"], - "no-extend-native": ["warn"], - "camelcase": ["warn"], - "no-multi-spaces": ["error", { "ignoreEOLComments": true }], - "space-before-function-paren": ["error", { - "anonymous": "never", - "named": "never", - "asyncArrow": "always", - }], - "arrow-parens": "off", - "prefer-promise-reject-errors": "off", + "arrow-parens": "off", + "prefer-promise-reject-errors": "off", + "quotes": "off", + "indent": "off", + "no-constant-condition": "off", + "no-async-promise-executor": "off", + }, + overrides: [ + { + files: ["src/**/*.ts", "test/**/*.ts"], + extends: ["plugin:matrix-org/typescript"], + rules: { + // TypeScript has its own version of this + "babel/no-invalid-this": "off", + "quotes": "off", - "indent": "off", - "no-constant-condition": "off", - "no-async-promise-executor": "off", + }, }, - overrides: [{ - "files": ["src/**/*.ts", "test/**/*.ts"], - "extends": ["plugin:matrix-org/typescript"], - "rules": { - // TypeScript has its own version of this - "babel/no-invalid-this": "off", - - "quotes": "off", - }, - }, { - "files": ["src/interfaces/**/*.ts"], - "rules": { - "@typescript-eslint/no-empty-object-type": "off", - }, - }], + { + files: ["src/interfaces/**/*.ts"], + rules: { + "@typescript-eslint/no-empty-object-type": "off", + }, + }, + ], }; diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d9a3e29..77c1982 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,7 +15,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - cache: 'yarn' + cache: "yarn" - name: Install NPM packages run: yarn install --frozen-lockfile diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fbc5277..f862c2c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -27,7 +27,7 @@ jobs: - name: 🔧 Set up node environment uses: actions/setup-node@v4 with: - cache: 'yarn' + cache: "yarn" - name: 🛠️ Setup run: yarn install --pure-lockfile diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index c539966..2773eaa 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,16 +1,16 @@ name: SonarQube on: - workflow_run: - workflows: ["Build and test"] - types: - - completed + workflow_run: + workflows: ["Build and test"] + types: + - completed concurrency: - group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true jobs: - sonarqube: - name: 🩻 SonarQube - uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop - secrets: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + sonarqube: + name: 🩻 SonarQube + uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/README.md b/README.md index 56bef5f..6016876 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ JavaScript/TypeScript SDK for widgets & clients to communicate. For help and support, visit [#matrix-widgets:matrix.org](https://matrix.to/#/#matrix-widgets:matrix.org) on Matrix. -*Disclaimer: Widgets are not yet in the Matrix spec, so this library may not work with other implementations.* +_Disclaimer: Widgets are not yet in the Matrix spec, so this library may not work with other implementations._ ## Building @@ -45,14 +45,14 @@ api.requestCapabilities(StickerpickerCapabilities); // Add custom action handlers (if needed) api.on(`action:${WidgetApiToWidgetAction.UpdateVisibility}`, (ev: CustomEvent) => { - ev.preventDefault(); // we're handling it, so stop the widget API from doing something. - console.log(ev.detail); // custom handling here - api.transport.reply(ev.detail, {}); + ev.preventDefault(); // we're handling it, so stop the widget API from doing something. + console.log(ev.detail); // custom handling here + api.transport.reply(ev.detail, {}); }); api.on("action:com.example.my_action", (ev: CustomEvent) => { - ev.preventDefault(); // we're handling it, so stop the widget API from doing something. - console.log(ev.detail); // custom handling here - api.transport.reply(ev.detail, {custom: "reply"}); + ev.preventDefault(); // we're handling it, so stop the widget API from doing something. + console.log(ev.detail); // custom handling here + api.transport.reply(ev.detail, { custom: "reply" }); }); // Start the messaging @@ -63,7 +63,7 @@ api.sendContentLoaded(); // Later, do something else (if needed) api.setAlwaysOnScreen(true); -api.transport.send("com.example.my_action", {isExample: true}); +api.transport.send("com.example.my_action", { isExample: true }); ``` For a more complete example, see the `examples` directory of this repo. @@ -82,8 +82,8 @@ const api = new ClientWidgetApi(widget, iframe, driver); // The API is automatically started, so we just have to wait for a ready before doing something api.on("ready", () => { - api.updateVisibility(true).then(() => console.log("Widget knows it is visible now")); - api.transport.send("com.example.my_action", {isExample: true}); + api.updateVisibility(true).then(() => console.log("Widget knows it is visible now")); + api.transport.send("com.example.my_action", { isExample: true }); }); // Eventually, stop the API handling diff --git a/examples/widget/index.css b/examples/widget/index.css index b20b561..f8c9db1 100644 --- a/examples/widget/index.css +++ b/examples/widget/index.css @@ -14,25 +14,26 @@ * limitations under the License. */ -html, body { - background-color: #ffffff; - color: #000000; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +html, +body { + background-color: #ffffff; + color: #000000; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; } body { - padding: 20px; + padding: 20px; } button { - border: none; - color: #ffffff; - background-color: #2a9d8f; - border-radius: 4px; - padding: 6px 12px; - cursor: pointer; + border: none; + color: #ffffff; + background-color: #2a9d8f; + border-radius: 4px; + padding: 6px 12px; + cursor: pointer; } #stickyState { - color: #3d5a80; + color: #3d5a80; } diff --git a/examples/widget/index.html b/examples/widget/index.html index 6e1c682..9b9ceef 100644 --- a/examples/widget/index.html +++ b/examples/widget/index.html @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - + - + Example Widget - - + +
Loading...
@@ -55,55 +55,59 @@ - + diff --git a/examples/widget/utils.js b/examples/widget/utils.js index 2d8076b..0a3cd49 100644 --- a/examples/widget/utils.js +++ b/examples/widget/utils.js @@ -15,17 +15,17 @@ */ function parseFragment() { - const fragmentString = (window.location.hash || "?"); - return new URLSearchParams(fragmentString.substring(Math.max(fragmentString.indexOf('?'), 0))); + const fragmentString = window.location.hash || "?"; + return new URLSearchParams(fragmentString.substring(Math.max(fragmentString.indexOf("?"), 0))); } function assertParam(fragment, name) { - const val = fragment.get(name); - if (!val) throw new Error(`${name} is not present in URL - cannot load widget`); - return val; + const val = fragment.get(name); + if (!val) throw new Error(`${name} is not present in URL - cannot load widget`); + return val; } function handleError(e) { - console.error(e); - document.getElementById("container").innerText = "There was an error with the widget. See JS console for details."; + console.error(e); + document.getElementById("container").innerText = "There was an error with the widget. See JS console for details."; } diff --git a/jest.config.ts b/jest.config.ts index 6c6af37..99641ec 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -19,16 +19,16 @@ import { env } from "process"; import type { Config } from "jest"; const config: Config = { - testEnvironment: "jsdom", - testMatch: ["/test/**/*-test.[jt]s?(x)"], - collectCoverageFrom: ["/src/**/*.{js,ts,tsx}"], - coverageReporters: ["text-summary", "lcov"], - testResultsProcessor: "@casualbot/jest-sonar-reporter", + testEnvironment: "jsdom", + testMatch: ["/test/**/*-test.[jt]s?(x)"], + collectCoverageFrom: ["/src/**/*.{js,ts,tsx}"], + coverageReporters: ["text-summary", "lcov"], + testResultsProcessor: "@casualbot/jest-sonar-reporter", }; // if we're running under GHA, enable the GHA reporter if (env["GITHUB_ACTIONS"] !== undefined) { - config.reporters = [["github-actions", { silent: false }], "summary"]; + config.reporters = [["github-actions", { silent: false }], "summary"]; } export default config; diff --git a/package.json b/package.json index 30c250e..4851ef9 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "eslint-plugin-unicorn": "^56.0.0", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", + "prettier": "3.4.2", "rimraf": "^3.0.2", "tinyify": "^3.0.0", "ts-node": "^10.9.1", diff --git a/renovate.json b/renovate.json index 5db72dd..22a9943 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,4 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ] + "extends": ["config:recommended"] } diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index 1fd0cd0..d3bf49d 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -174,12 +174,7 @@ export class ClientWidgetApi extends EventEmitter { if (!driver) { throw new Error("Invalid driver"); } - this.transport = new PostmessageTransport( - WidgetApiDirection.ToWidget, - widget.id, - iframe.contentWindow, - window, - ); + this.transport = new PostmessageTransport(WidgetApiDirection.ToWidget, widget.id, iframe.contentWindow, window); this.transport.targetOrigin = widget.origin; this.transport.on("message", this.handleMessage.bind(this)); @@ -193,36 +188,38 @@ export class ClientWidgetApi extends EventEmitter { } public canUseRoomTimeline(roomId: string | Symbols.AnyRoom): boolean { - return this.hasCapability(`org.matrix.msc2762.timeline:${Symbols.AnyRoom}`) - || this.hasCapability(`org.matrix.msc2762.timeline:${roomId}`); + return ( + this.hasCapability(`org.matrix.msc2762.timeline:${Symbols.AnyRoom}`) || + this.hasCapability(`org.matrix.msc2762.timeline:${roomId}`) + ); } public canSendRoomEvent(eventType: string, msgtype: string | null = null): boolean { - return this.allowedEvents.some(e => e.matchesAsRoomEvent(EventDirection.Send, eventType, msgtype)); + return this.allowedEvents.some((e) => e.matchesAsRoomEvent(EventDirection.Send, eventType, msgtype)); } public canSendStateEvent(eventType: string, stateKey: string): boolean { - return this.allowedEvents.some(e => e.matchesAsStateEvent(EventDirection.Send, eventType, stateKey)); + return this.allowedEvents.some((e) => e.matchesAsStateEvent(EventDirection.Send, eventType, stateKey)); } public canSendToDeviceEvent(eventType: string): boolean { - return this.allowedEvents.some(e => e.matchesAsToDeviceEvent(EventDirection.Send, eventType)); + return this.allowedEvents.some((e) => e.matchesAsToDeviceEvent(EventDirection.Send, eventType)); } public canReceiveRoomEvent(eventType: string, msgtype: string | null = null): boolean { - return this.allowedEvents.some(e => e.matchesAsRoomEvent(EventDirection.Receive, eventType, msgtype)); + return this.allowedEvents.some((e) => e.matchesAsRoomEvent(EventDirection.Receive, eventType, msgtype)); } public canReceiveStateEvent(eventType: string, stateKey: string | null): boolean { - return this.allowedEvents.some(e => e.matchesAsStateEvent(EventDirection.Receive, eventType, stateKey)); + return this.allowedEvents.some((e) => e.matchesAsStateEvent(EventDirection.Receive, eventType, stateKey)); } public canReceiveToDeviceEvent(eventType: string): boolean { - return this.allowedEvents.some(e => e.matchesAsToDeviceEvent(EventDirection.Receive, eventType)); + return this.allowedEvents.some((e) => e.matchesAsToDeviceEvent(EventDirection.Receive, eventType)); } public canReceiveRoomAccountData(eventType: string): boolean { - return this.allowedEvents.some(e => e.matchesAsRoomAccountData(EventDirection.Receive, eventType)); + return this.allowedEvents.some((e) => e.matchesAsRoomAccountData(EventDirection.Receive, eventType)); } public stop(): void { @@ -235,17 +232,19 @@ export class ClientWidgetApi extends EventEmitter { this.emit("preparing"); let requestedCaps: Capability[]; - this.transport.send( - WidgetApiToWidgetAction.Capabilities, {}, - ).then(caps => { - requestedCaps = caps.capabilities; - return this.driver.validateCapabilities(new Set(caps.capabilities)); - }).then(allowedCaps => { - this.allowCapabilities([...allowedCaps], requestedCaps); - this.emit("ready"); - }).catch(e => { - this.emit("error:preparing", e); - }); + this.transport + .send(WidgetApiToWidgetAction.Capabilities, {}) + .then((caps) => { + requestedCaps = caps.capabilities; + return this.driver.validateCapabilities(new Set(caps.capabilities)); + }) + .then((allowedCaps) => { + this.allowCapabilities([...allowedCaps], requestedCaps); + this.emit("ready"); + }) + .catch((e) => { + this.emit("error:preparing", e); + }); } private allowCapabilities(allowed: string[], requested: string[]): void { @@ -255,14 +254,17 @@ export class ClientWidgetApi extends EventEmitter { const allowedEvents = WidgetEventCapability.findEventCapabilities(allowed); this.allowedEvents.push(...allowedEvents); - this.transport.send(WidgetApiToWidgetAction.NotifyCapabilities, { - requested, - approved: Array.from(this.allowedCapabilities), - }).catch(e => { - console.warn("non-fatal error notifying widget of approved capabilities:", e); - }).then(() => { - this.emit("capabilitiesNotified"); - }); + this.transport + .send(WidgetApiToWidgetAction.NotifyCapabilities, { + requested, + approved: Array.from(this.allowedCapabilities), + }) + .catch((e) => { + console.warn("non-fatal error notifying widget of approved capabilities:", e); + }) + .then(() => { + this.emit("capabilitiesNotified"); + }); // Push the initial room state for all rooms with a timeline capability for (const c of allowed) { @@ -307,14 +309,17 @@ export class ClientWidgetApi extends EventEmitter { this.contentLoadedWaitTimer = undefined; } if (this.contentLoadedActionSent) { - throw new Error("Improper sequence: ContentLoaded Action can only be sent once after the widget loaded " - +"and should only be used if waitForIframeLoad is false (default=true)"); + throw new Error( + "Improper sequence: ContentLoaded Action can only be sent once after the widget loaded " + + "and should only be used if waitForIframeLoad is false (default=true)", + ); } if (this.widget.waitForIframeLoad) { this.transport.reply(action, { error: { - message: "Improper sequence: not expecting ContentLoaded event if " - +"waitForIframeLoad is true (default=true)", + message: + "Improper sequence: not expecting ContentLoaded event if " + + "waitForIframeLoad is true (default=true)", }, }); } else { @@ -335,26 +340,27 @@ export class ClientWidgetApi extends EventEmitter { this.transport.reply(request, {}); const requested = request.data?.capabilities || []; - const newlyRequested = new Set(requested.filter(r => !this.hasCapability(r))); + const newlyRequested = new Set(requested.filter((r) => !this.hasCapability(r))); if (newlyRequested.size === 0) { // Nothing to do - skip validation this.allowCapabilities([], []); } - this.driver.validateCapabilities(newlyRequested) - .then(allowed => this.allowCapabilities([...allowed], [...newlyRequested])); + this.driver + .validateCapabilities(newlyRequested) + .then((allowed) => this.allowCapabilities([...allowed], [...newlyRequested])); } private handleNavigate(request: INavigateActionRequest): void { if (!this.hasCapability(MatrixCapabilities.MSC2931Navigate)) { return this.transport.reply(request, { - error: {message: "Missing capability"}, + error: { message: "Missing capability" }, }); } if (!request.data?.uri || !request.data?.uri.toString().startsWith("https://matrix.to/#")) { return this.transport.reply(request, { - error: {message: "Invalid matrix.to URI"}, + error: { message: "Invalid matrix.to URI" }, }); } @@ -364,9 +370,12 @@ export class ClientWidgetApi extends EventEmitter { }; try { - this.driver.navigate(request.data.uri.toString()).catch((e: unknown) => onErr(e)).then(() => { - return this.transport.reply(request, {}); - }); + this.driver + .navigate(request.data.uri.toString()) + .catch((e: unknown) => onErr(e)) + .then(() => { + return this.transport.reply(request, {}); + }); } catch (e) { return onErr(e); } @@ -375,7 +384,10 @@ export class ClientWidgetApi extends EventEmitter { private handleOIDC(request: IGetOpenIDActionRequest): void { let phase = 1; // 1 = initial request, 2 = after user manual confirmation - const replyState = (state: OpenIDRequestState, credential?: IOpenIDCredentials): void | Promise => { + const replyState = ( + state: OpenIDRequestState, + credential?: IOpenIDCredentials, + ): void | Promise => { credential = credential || {}; if (phase > 1) { return this.transport.send( @@ -402,12 +414,12 @@ export class ClientWidgetApi extends EventEmitter { return replyState(OpenIDRequestState.Blocked); } else { return this.transport.reply(request, { - error: {message: msg}, + error: { message: msg }, }); } }; - const observer = new SimpleObservable(update => { + const observer = new SimpleObservable((update) => { if (update.state === OpenIDRequestState.PendingUserConfirmation && phase > 1) { observer.close(); return replyError("client provided out-of-phase response to OIDC flow"); @@ -438,24 +450,24 @@ export class ClientWidgetApi extends EventEmitter { if (!this.canReceiveRoomAccountData(request.data.type)) { return this.transport.reply(request, { - error: {message: "Cannot read room account data of this type"}, + error: { message: "Cannot read room account data of this type" }, }); } return events.then((evs) => { - this.transport.reply(request, {events: evs}); + this.transport.reply(request, { events: evs }); }); } private async handleReadEvents(request: IReadEventFromWidgetActionRequest): Promise { if (!request.data.type) { return this.transport.reply(request, { - error: {message: "Invalid request - missing event type"}, + error: { message: "Invalid request - missing event type" }, }); } if (request.data.limit !== undefined && (!request.data.limit || request.data.limit < 0)) { return this.transport.reply(request, { - error: {message: "Invalid request - limit out of range"}, + error: { message: "Invalid request - limit out of range" }, }); } @@ -463,13 +475,13 @@ export class ClientWidgetApi extends EventEmitter { if (request.data.room_ids === undefined) { askRoomIds = this.viewedRoomId === null ? [] : [this.viewedRoomId]; } else if (request.data.room_ids === Symbols.AnyRoom) { - askRoomIds = this.driver.getKnownRooms().filter(roomId => this.canUseRoomTimeline(roomId)); + askRoomIds = this.driver.getKnownRooms().filter((roomId) => this.canUseRoomTimeline(roomId)); } else { askRoomIds = request.data.room_ids; for (const roomId of askRoomIds) { if (!this.canUseRoomTimeline(roomId)) { return this.transport.reply(request, { - error: {message: `Unable to access room timeline: ${roomId}`}, + error: { message: `Unable to access room timeline: ${roomId}` }, }); } } @@ -484,14 +496,14 @@ export class ClientWidgetApi extends EventEmitter { stateKey = request.data.state_key === true ? undefined : request.data.state_key.toString(); if (!this.canReceiveStateEvent(request.data.type, stateKey ?? null)) { return this.transport.reply(request, { - error: {message: "Cannot read state events of this type"}, + error: { message: "Cannot read state events of this type" }, }); } } else { msgtype = request.data.msgtype; if (!this.canReceiveRoomEvent(request.data.type, msgtype)) { return this.transport.reply(request, { - error: {message: "Cannot read room events of this type"}, + error: { message: "Cannot read room events of this type" }, }); } } @@ -499,45 +511,46 @@ export class ClientWidgetApi extends EventEmitter { // For backwards compatibility we still call the deprecated // readRoomEvents and readStateEvents methods in case the client isn't // letting us know the currently viewed room via setViewedRoomId - const events = request.data.room_ids === undefined && askRoomIds.length === 0 - ? await ( - request.data.state_key === undefined - ? this.driver.readRoomEvents(request.data.type, msgtype, limit, null, since) - : this.driver.readStateEvents(request.data.type, stateKey, limit, null) - ) - : ( - await Promise.all(askRoomIds.map(roomId => - this.driver.readRoomTimeline(roomId, request.data.type, msgtype, stateKey, limit, since), - )) - ).flat(1); + const events = + request.data.room_ids === undefined && askRoomIds.length === 0 + ? await (request.data.state_key === undefined + ? this.driver.readRoomEvents(request.data.type, msgtype, limit, null, since) + : this.driver.readStateEvents(request.data.type, stateKey, limit, null)) + : ( + await Promise.all( + askRoomIds.map((roomId) => + this.driver.readRoomTimeline(roomId, request.data.type, msgtype, stateKey, limit, since), + ), + ) + ).flat(1); this.transport.reply(request, { events }); } private handleSendEvent(request: ISendEventFromWidgetActionRequest): void { if (!request.data.type) { return this.transport.reply(request, { - error: {message: "Invalid request - missing event type"}, + error: { message: "Invalid request - missing event type" }, }); } if (!!request.data.room_id && !this.canUseRoomTimeline(request.data.room_id)) { return this.transport.reply(request, { - error: {message: `Unable to access room timeline: ${request.data.room_id}`}, + error: { message: `Unable to access room timeline: ${request.data.room_id}` }, }); } const isDelayedEvent = request.data.delay !== undefined || request.data.parent_delay_id !== undefined; if (isDelayedEvent && !this.hasCapability(MatrixCapabilities.MSC4157SendDelayedEvent)) { return this.transport.reply(request, { - error: {message: "Missing capability"}, + error: { message: "Missing capability" }, }); } - let sendEventPromise: Promise; + let sendEventPromise: Promise; if (request.data.state_key !== undefined) { if (!this.canSendStateEvent(request.data.type, request.data.state_key)) { return this.transport.reply(request, { - error: {message: "Cannot send state events of this type"}, + error: { message: "Cannot send state events of this type" }, }); } @@ -559,11 +572,11 @@ export class ClientWidgetApi extends EventEmitter { ); } } else { - const content = request.data.content as { msgtype?: string } || {}; - const msgtype = content['msgtype']; + const content = (request.data.content as { msgtype?: string }) || {}; + const msgtype = content["msgtype"]; if (!this.canSendRoomEvent(request.data.type, msgtype)) { return this.transport.reply(request, { - error: {message: "Cannot send room events of this type"}, + error: { message: "Cannot send room events of this type" }, }); } @@ -586,31 +599,35 @@ export class ClientWidgetApi extends EventEmitter { } } - sendEventPromise.then(sentEvent => { - return this.transport.reply(request, { - room_id: sentEvent.roomId, - ...("eventId" in sentEvent ? { - event_id: sentEvent.eventId, - } : { - delay_id: sentEvent.delayId, - }), + sendEventPromise + .then((sentEvent) => { + return this.transport.reply(request, { + room_id: sentEvent.roomId, + ...("eventId" in sentEvent + ? { + event_id: sentEvent.eventId, + } + : { + delay_id: sentEvent.delayId, + }), + }); + }) + .catch((e: unknown) => { + console.error("error sending event: ", e); + this.handleDriverError(e, request, "Error sending event"); }); - }).catch((e: unknown) => { - console.error("error sending event: ", e); - this.handleDriverError(e, request, "Error sending event"); - }); } private handleUpdateDelayedEvent(request: IUpdateDelayedEventFromWidgetActionRequest): void { if (!request.data.delay_id) { return this.transport.reply(request, { - error: {message: "Invalid request - missing delay_id"}, + error: { message: "Invalid request - missing delay_id" }, }); } if (!this.hasCapability(MatrixCapabilities.MSC4157UpdateDelayedEvent)) { return this.transport.reply(request, { - error: {message: "Missing capability"}, + error: { message: "Missing capability" }, }); } @@ -618,16 +635,19 @@ export class ClientWidgetApi extends EventEmitter { case UpdateDelayedEventAction.Cancel: case UpdateDelayedEventAction.Restart: case UpdateDelayedEventAction.Send: - this.driver.updateDelayedEvent(request.data.delay_id, request.data.action).then(() => { - return this.transport.reply(request, {}); - }).catch((e: unknown) => { - console.error("error updating delayed event: ", e); - this.handleDriverError(e, request, "Error updating delayed event"); - }); + this.driver + .updateDelayedEvent(request.data.delay_id, request.data.action) + .then(() => { + return this.transport.reply(request, {}); + }) + .catch((e: unknown) => { + console.error("error updating delayed event: ", e); + this.handleDriverError(e, request, "Error updating delayed event"); + }); break; default: return this.transport.reply(request, { - error: {message: "Invalid request - unsupported action"}, + error: { message: "Invalid request - unsupported action" }, }); } } @@ -635,19 +655,19 @@ export class ClientWidgetApi extends EventEmitter { private async handleSendToDevice(request: ISendToDeviceFromWidgetActionRequest): Promise { if (!request.data.type) { await this.transport.reply(request, { - error: {message: "Invalid request - missing event type"}, + error: { message: "Invalid request - missing event type" }, }); } else if (!request.data.messages) { await this.transport.reply(request, { - error: {message: "Invalid request - missing event contents"}, + error: { message: "Invalid request - missing event contents" }, }); } else if (typeof request.data.encrypted !== "boolean") { await this.transport.reply(request, { - error: {message: "Invalid request - missing encryption flag"}, + error: { message: "Invalid request - missing encryption flag" }, }); } else if (!this.canSendToDeviceEvent(request.data.type)) { await this.transport.reply(request, { - error: {message: "Cannot send to-device events of this type"}, + error: { message: "Cannot send to-device events of this type" }, }); } else { try { @@ -682,7 +702,7 @@ export class ClientWidgetApi extends EventEmitter { private async handleWatchTurnServers(request: IWatchTurnServersRequest): Promise { if (!this.hasCapability(MatrixCapabilities.MSC3846TurnServers)) { await this.transport.reply(request, { - error: {message: "Missing capability"}, + error: { message: "Missing capability" }, }); } else if (this.turnServers) { // We're already polling, so this is a no-op @@ -703,7 +723,7 @@ export class ClientWidgetApi extends EventEmitter { } catch (e) { console.error("error getting first TURN server results", e); await this.transport.reply(request, { - error: {message: "TURN servers not available"}, + error: { message: "TURN servers not available" }, }); } } @@ -712,7 +732,7 @@ export class ClientWidgetApi extends EventEmitter { private async handleUnwatchTurnServers(request: IUnwatchTurnServersRequest): Promise { if (!this.hasCapability(MatrixCapabilities.MSC3846TurnServers)) { await this.transport.reply(request, { - error: {message: "Missing capability"}, + error: { message: "Missing capability" }, }); } else if (!this.turnServers) { // We weren't polling anyways, so this is a no-op @@ -746,28 +766,30 @@ export class ClientWidgetApi extends EventEmitter { try { const result = await this.driver.readEventRelations( - request.data.event_id, request.data.room_id, request.data.rel_type, - request.data.event_type, request.data.from, request.data.to, - request.data.limit, request.data.direction, + request.data.event_id, + request.data.room_id, + request.data.rel_type, + request.data.event_type, + request.data.from, + request.data.to, + request.data.limit, + request.data.direction, ); // only return events that the user has the permission to receive - const chunk = result.chunk.filter(e => { + const chunk = result.chunk.filter((e) => { if (e.state_key !== undefined) { return this.canReceiveStateEvent(e.type, e.state_key); } else { - return this.canReceiveRoomEvent(e.type, (e.content as { msgtype?: string })['msgtype']); + return this.canReceiveRoomEvent(e.type, (e.content as { msgtype?: string })["msgtype"]); } }); - return this.transport.reply( - request, - { - chunk, - prev_batch: result.prevBatch, - next_batch: result.nextBatch, - }, - ); + return this.transport.reply(request, { + chunk, + prev_batch: result.prevBatch, + next_batch: result.nextBatch, + }); } catch (e) { console.error("error getting the relations", e); this.handleDriverError(e, request, "Unexpected error while reading relations"); @@ -781,7 +803,7 @@ export class ClientWidgetApi extends EventEmitter { }); } - if (typeof request.data.search_term !== 'string') { + if (typeof request.data.search_term !== "string") { return this.transport.reply(request, { error: { message: "Invalid request - missing search term" }, }); @@ -794,21 +816,16 @@ export class ClientWidgetApi extends EventEmitter { } try { - const result = await this.driver.searchUserDirectory( - request.data.search_term, request.data.limit, - ); - - return this.transport.reply( - request, - { - limited: result.limited, - results: result.results.map(r => ({ - user_id: r.userId, - display_name: r.displayName, - avatar_url: r.avatarUrl, - })), - }, - ); + const result = await this.driver.searchUserDirectory(request.data.search_term, request.data.limit); + + return this.transport.reply(request, { + limited: result.limited, + results: result.results.map((r) => ({ + user_id: r.userId, + display_name: r.displayName, + avatar_url: r.avatarUrl, + })), + }); } catch (e) { console.error("error searching in the user directory", e); this.handleDriverError(e, request, "Unexpected error while searching in the user directory"); @@ -825,10 +842,7 @@ export class ClientWidgetApi extends EventEmitter { try { const result = await this.driver.getMediaConfig(); - return this.transport.reply( - request, - result, - ); + return this.transport.reply(request, result); } catch (e) { console.error("error while getting the media configuration", e); this.handleDriverError(e, request, "Unexpected error while getting the media configuration"); @@ -845,10 +859,9 @@ export class ClientWidgetApi extends EventEmitter { try { const result = await this.driver.uploadFile(request.data.file); - return this.transport.reply( - request, - { content_uri: result.contentUri }, - ); + return this.transport.reply(request, { + content_uri: result.contentUri, + }); } catch (e) { console.error("error while uploading a file", e); this.handleDriverError(e, request, "Unexpected error while uploading a file"); @@ -865,10 +878,7 @@ export class ClientWidgetApi extends EventEmitter { try { const result = await this.driver.downloadFile(request.data.content_uri); - return this.transport.reply( - request, - { file: result.file }, - ); + return this.transport.reply(request, { file: result.file }); } catch (e) { console.error("error while downloading a file", e); this.handleDriverError(e, request, "Unexpected error while downloading a file"); @@ -980,15 +990,13 @@ export class ClientWidgetApi extends EventEmitter { } public notifyModalWidgetButtonClicked(id: IModalWidgetOpenRequestDataButton["id"]): Promise { - return this.transport.send( - WidgetApiToWidgetAction.ButtonClicked, {id}, - ).then(); + return this.transport + .send(WidgetApiToWidgetAction.ButtonClicked, { id }) + .then(); } public notifyModalWidgetClose(data: IModalWidgetReturnData): Promise { - return this.transport.send( - WidgetApiToWidgetAction.CloseModalWidget, data, - ).then(); + return this.transport.send(WidgetApiToWidgetAction.CloseModalWidget, data).then(); } /** @@ -1087,10 +1095,9 @@ export class ClientWidgetApi extends EventEmitter { events.push(...stateKeyMap.values()); } } - await this.transport.send( - WidgetApiToWidgetAction.UpdateState, - { state: events }, - ); + await this.transport.send(WidgetApiToWidgetAction.UpdateState, { + state: events, + }); } finally { this.flushRoomStateTask = null; } @@ -1105,38 +1112,42 @@ export class ClientWidgetApi extends EventEmitter { if (cap.kind === EventKind.State && cap.direction === EventDirection.Receive) { // Initiate the task const events = this.driver.readRoomState(roomId, cap.eventType, cap.keyStr ?? undefined); - const task = events.then( - events => { - // When complete, queue the resulting events to be - // pushed to the widget - for (const event of events) { - let eventTypeMap = this.pushRoomStateResult.get(roomId); - if (eventTypeMap === undefined) { - eventTypeMap = new Map(); - this.pushRoomStateResult.set(roomId, eventTypeMap); - } - let stateKeyMap = eventTypeMap.get(cap.eventType); - if (stateKeyMap === undefined) { - stateKeyMap = new Map(); - eventTypeMap.set(cap.eventType, stateKeyMap); + const task = events + .then( + (events) => { + // When complete, queue the resulting events to be + // pushed to the widget + for (const event of events) { + let eventTypeMap = this.pushRoomStateResult.get(roomId); + if (eventTypeMap === undefined) { + eventTypeMap = new Map(); + this.pushRoomStateResult.set(roomId, eventTypeMap); + } + let stateKeyMap = eventTypeMap.get(cap.eventType); + if (stateKeyMap === undefined) { + stateKeyMap = new Map(); + eventTypeMap.set(cap.eventType, stateKeyMap); + } + if (!stateKeyMap.has(event.state_key!)) stateKeyMap.set(event.state_key!, event); } - if (!stateKeyMap.has(event.state_key!)) stateKeyMap.set(event.state_key!, event); - } - }, - e => console.error(`Failed to read room state for ${roomId} (${ - cap.eventType - }, ${cap.keyStr})`, e), - ).then(() => { - // Mark request as no longer pending - this.pushRoomStateTasks.delete(task); - }); + }, + (e) => + console.error( + `Failed to read room state for ${roomId} (${cap.eventType}, ${cap.keyStr})`, + e, + ), + ) + .then(() => { + // Mark request as no longer pending + this.pushRoomStateTasks.delete(task); + }); // Mark task as pending this.pushRoomStateTasks.add(task); // Assuming no other tasks are already happening concurrently, // schedule the widget action that actually pushes the events this.flushRoomStateTask ??= this.flushRoomState(); - this.flushRoomStateTask.catch(e => console.error('Failed to push room state', e)); + this.flushRoomStateTask.catch((e) => console.error("Failed to push room state", e)); } } } @@ -1152,18 +1163,17 @@ export class ClientWidgetApi extends EventEmitter { widget failed to handle the update. */ public async feedStateUpdate(rawEvent: IRoomEvent): Promise { - if (rawEvent.state_key === undefined) throw new Error('Not a state event'); + if (rawEvent.state_key === undefined) throw new Error("Not a state event"); if ( - (rawEvent.room_id === this.viewedRoomId || this.canUseRoomTimeline(rawEvent.room_id)) - && this.canReceiveStateEvent(rawEvent.type, rawEvent.state_key) + (rawEvent.room_id === this.viewedRoomId || this.canUseRoomTimeline(rawEvent.room_id)) && + this.canReceiveStateEvent(rawEvent.type, rawEvent.state_key) ) { // Updates could race with the initial push of the room's state if (this.pushRoomStateTasks.size === 0) { // No initial push tasks are pending; safe to send immediately - await this.transport.send( - WidgetApiToWidgetAction.UpdateState, - { state: [rawEvent] }, - ); + await this.transport.send(WidgetApiToWidgetAction.UpdateState, { + state: [rawEvent], + }); } else { // Lump the update in with whatever data will be sent in the // initial push later. Even if we set it to an "outdated" entry diff --git a/src/WidgetApi.ts b/src/WidgetApi.ts index 508682a..44f0de9 100644 --- a/src/WidgetApi.ts +++ b/src/WidgetApi.ts @@ -67,7 +67,7 @@ import { IReadRoomAccountDataFromWidgetResponseData, } from "./interfaces/ReadRoomAccountDataAction"; import { IRoomEvent } from "./interfaces/IRoomEvent"; -import {IRoomAccountData} from "./interfaces/IRoomAccountData"; +import { IRoomAccountData } from "./interfaces/IRoomAccountData"; import { ITurnServer, IUpdateTurnServersRequest } from "./interfaces/TurnServerActions"; import { Symbols } from "./Symbols"; import { @@ -142,17 +142,15 @@ export class WidgetApi extends EventEmitter { * the API will use the widget ID from the first valid request it receives. * @param {string} clientOrigin The origin of the client, or null if not known. */ - public constructor(widgetId: string | null = null, private clientOrigin: string | null = null) { + public constructor( + widgetId: string | null = null, + private clientOrigin: string | null = null, + ) { super(); if (!window.parent) { throw new Error("No parent window. This widget doesn't appear to be embedded properly."); } - this.transport = new PostmessageTransport( - WidgetApiDirection.FromWidget, - widgetId, - window.parent, - window, - ); + this.transport = new PostmessageTransport(WidgetApiDirection.FromWidget, widgetId, window.parent, window); this.transport.targetOrigin = clientOrigin; this.transport.on("message", this.handleMessage.bind(this)); } @@ -193,7 +191,7 @@ export class WidgetApi extends EventEmitter { * @throws Throws if the capabilities negotiation has already started. */ public requestCapabilities(capabilities: Capability[]): void { - capabilities.forEach(cap => this.requestCapability(cap)); + capabilities.forEach((cap) => this.requestCapability(cap)); } /** @@ -309,40 +307,44 @@ export class WidgetApi extends EventEmitter { */ public requestOpenIDConnectToken(): Promise { return new Promise((resolve, reject) => { - this.transport.sendComplete( - WidgetApiFromWidgetAction.GetOpenIDCredentials, {}, - ).then(response => { - const rdata = response.response; - if (rdata.state === OpenIDRequestState.Allowed) { - resolve(rdata); - } else if (rdata.state === OpenIDRequestState.Blocked) { - reject(new Error("User declined to verify their identity")); - } else if (rdata.state === OpenIDRequestState.PendingUserConfirmation) { - const handlerFn = (ev: CustomEvent): void => { - ev.preventDefault(); - const request = ev.detail; - if (request.data.original_request_id !== response.requestId) return; - if (request.data.state === OpenIDRequestState.Allowed) { - resolve(request.data); - this.transport.reply(request, {}); // ack - } else if (request.data.state === OpenIDRequestState.Blocked) { - reject(new Error("User declined to verify their identity")); - this.transport.reply(request, {}); // ack - } else { - reject(new Error("Invalid state on reply: " + rdata.state)); - this.transport.reply(request, { - error: { - message: "Invalid state", - }, - }); - } - this.off(`action:${WidgetApiToWidgetAction.OpenIDCredentials}`, handlerFn); - }; - this.on(`action:${WidgetApiToWidgetAction.OpenIDCredentials}`, handlerFn); - } else { - reject(new Error("Invalid state: " + rdata.state)); - } - }).catch(reject); + this.transport + .sendComplete( + WidgetApiFromWidgetAction.GetOpenIDCredentials, + {}, + ) + .then((response) => { + const rdata = response.response; + if (rdata.state === OpenIDRequestState.Allowed) { + resolve(rdata); + } else if (rdata.state === OpenIDRequestState.Blocked) { + reject(new Error("User declined to verify their identity")); + } else if (rdata.state === OpenIDRequestState.PendingUserConfirmation) { + const handlerFn = (ev: CustomEvent): void => { + ev.preventDefault(); + const request = ev.detail; + if (request.data.original_request_id !== response.requestId) return; + if (request.data.state === OpenIDRequestState.Allowed) { + resolve(request.data); + this.transport.reply(request, {}); // ack + } else if (request.data.state === OpenIDRequestState.Blocked) { + reject(new Error("User declined to verify their identity")); + this.transport.reply(request, {}); // ack + } else { + reject(new Error("Invalid state on reply: " + rdata.state)); + this.transport.reply(request, { + error: { + message: "Invalid state", + }, + }); + } + this.off(`action:${WidgetApiToWidgetAction.OpenIDCredentials}`, handlerFn); + }; + this.on(`action:${WidgetApiToWidgetAction.OpenIDCredentials}`, handlerFn); + } else { + reject(new Error("Invalid state: " + rdata.state)); + } + }) + .catch(reject); }); } @@ -354,10 +356,11 @@ export class WidgetApi extends EventEmitter { * Use the WidgetApiToWidgetAction.NotifyCapabilities action to detect changes. */ public updateRequestedCapabilities(): Promise { - return this.transport.send(WidgetApiFromWidgetAction.MSC2974RenegotiateCapabilities, - { + return this.transport + .send(WidgetApiFromWidgetAction.MSC2974RenegotiateCapabilities, { capabilities: this.requestedCapabilities, - }).then(); + }) + .then(); } /** @@ -384,9 +387,12 @@ export class WidgetApi extends EventEmitter { * the request, resolves to false otherwise. Rejects if an error occurred. */ public setAlwaysOnScreen(value: boolean): Promise { - return this.transport.send( - WidgetApiFromWidgetAction.UpdateAlwaysOnScreen, {value}, - ).then(res => res.success); + return this.transport + .send< + IStickyActionRequestData, + IStickyActionResponseData + >(WidgetApiFromWidgetAction.UpdateAlwaysOnScreen, { value }) + .then((res) => res.success); } /** @@ -405,9 +411,15 @@ export class WidgetApi extends EventEmitter { data: IModalWidgetCreateData = {}, type: WidgetType = MatrixWidgetType.Custom, ): Promise { - return this.transport.send( - WidgetApiFromWidgetAction.OpenModalWidget, { type, url, name, buttons, data }, - ).then(); + return this.transport + .send(WidgetApiFromWidgetAction.OpenModalWidget, { + type, + url, + name, + buttons, + data, + }) + .then(); } /** @@ -491,15 +503,12 @@ export class WidgetApi extends EventEmitter { ): Promise { return this.transport.send( WidgetApiFromWidgetAction.SendToDevice, - {type: eventType, encrypted, messages: contentMap}, + { type: eventType, encrypted, messages: contentMap }, ); } - public readRoomAccountData( - eventType: string, - roomIds?: (string | Symbols.AnyRoom)[], - ): Promise { - const data: IReadEventFromWidgetRequestData = {type: eventType}; + public readRoomAccountData(eventType: string, roomIds?: (string | Symbols.AnyRoom)[]): Promise { + const data: IReadEventFromWidgetRequestData = { type: eventType }; if (roomIds) { if (roomIds.includes(Symbols.AnyRoom)) { @@ -508,13 +517,12 @@ export class WidgetApi extends EventEmitter { data.room_ids = roomIds; } } - return this.transport.send< - IReadRoomAccountDataFromWidgetRequestData, - IReadRoomAccountDataFromWidgetResponseData - >( - WidgetApiFromWidgetAction.BeeperReadRoomAccountData, - data, - ).then(r => r.events); + return this.transport + .send< + IReadRoomAccountDataFromWidgetRequestData, + IReadRoomAccountDataFromWidgetResponseData + >(WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data) + .then((r) => r.events); } public readRoomEvents( @@ -524,7 +532,7 @@ export class WidgetApi extends EventEmitter { roomIds?: (string | Symbols.AnyRoom)[], since?: string | undefined, ): Promise { - const data: IReadEventFromWidgetRequestData = {type: eventType, msgtype: msgtype}; + const data: IReadEventFromWidgetRequestData = { type: eventType, msgtype: msgtype }; if (limit !== undefined) { data.limit = limit; } @@ -538,10 +546,12 @@ export class WidgetApi extends EventEmitter { if (since) { data.since = since; } - return this.transport.send( - WidgetApiFromWidgetAction.MSC2876ReadEvents, - data, - ).then(r => r.events); + return this.transport + .send< + IReadEventFromWidgetRequestData, + IReadEventFromWidgetResponseData + >(WidgetApiFromWidgetAction.MSC2876ReadEvents, data) + .then((r) => r.events); } /** @@ -571,7 +581,7 @@ export class WidgetApi extends EventEmitter { limit?: number, from?: string, to?: string, - direction?: 'f' | 'b', + direction?: "f" | "b", ): Promise { const versions = await this.getClientVersions(); if (!versions.includes(UnstableApiVersion.MSC3869)) { @@ -615,10 +625,12 @@ export class WidgetApi extends EventEmitter { data.room_ids = roomIds; } } - return this.transport.send( - WidgetApiFromWidgetAction.MSC2876ReadEvents, - data, - ).then(r => r.events); + return this.transport + .send< + IReadEventFromWidgetRequestData, + IReadEventFromWidgetResponseData + >(WidgetApiFromWidgetAction.MSC2876ReadEvents, data) + .then((r) => r.events); } /** @@ -632,9 +644,12 @@ export class WidgetApi extends EventEmitter { if (buttonId === BuiltInModalButtonID.Close) { throw new Error("The close button cannot be disabled"); } - return this.transport.send( - WidgetApiFromWidgetAction.SetModalButtonEnabled, {button: buttonId, enabled: isEnabled}, - ).then(); + return this.transport + .send(WidgetApiFromWidgetAction.SetModalButtonEnabled, { + button: buttonId, + enabled: isEnabled, + }) + .then(); } /** @@ -650,9 +665,9 @@ export class WidgetApi extends EventEmitter { throw new Error("Invalid matrix.to URI"); } - return this.transport.send( - WidgetApiFromWidgetAction.MSC2931Navigate, {uri}, - ).then(); + return this.transport + .send(WidgetApiFromWidgetAction.MSC2931Navigate, { uri }) + .then(); } /** @@ -660,7 +675,7 @@ export class WidgetApi extends EventEmitter { * and thereafter yielding new credentials whenever the previous ones expire. * @yields {ITurnServer} The TURN server URIs and credentials currently available to the widget. */ - public async* getTurnServers(): AsyncGenerator { + public async *getTurnServers(): AsyncGenerator { let setTurnServer: (server: ITurnServer) => void; const onUpdateTurnServers = async (ev: CustomEvent): Promise => { @@ -687,7 +702,7 @@ export class WidgetApi extends EventEmitter { try { // Watch for new data indefinitely (until this generator's return method is called) while (true) { - yield await new Promise(resolve => setTurnServer = resolve); + yield await new Promise((resolve) => (setTurnServer = resolve)); } } finally { // The loop was broken by the caller - clean up @@ -762,10 +777,10 @@ export class WidgetApi extends EventEmitter { file, }; - return this.transport.send< - IUploadFileActionFromWidgetRequestData, - IUploadFileActionFromWidgetResponseData - >(WidgetApiFromWidgetAction.MSC4039UploadFileAction, data); + return this.transport.send( + WidgetApiFromWidgetAction.MSC4039UploadFileAction, + data, + ); } /** @@ -783,10 +798,10 @@ export class WidgetApi extends EventEmitter { content_uri: contentUri, }; - return this.transport.send< - IDownloadFileActionFromWidgetRequestData, - IDownloadFileActionFromWidgetResponseData - >(WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data); + return this.transport.send( + WidgetApiFromWidgetAction.MSC4039DownloadFileAction, + data, + ); } /** @@ -795,7 +810,7 @@ export class WidgetApi extends EventEmitter { */ public start(): void { this.transport.start(); - this.getClientVersions().then(v => { + this.getClientVersions().then((v) => { if (v.includes(UnstableApiVersion.MSC2974)) { this.supportsMSC2974Renegotiate = true; } @@ -839,15 +854,19 @@ export class WidgetApi extends EventEmitter { return Promise.resolve(this.cachedClientVersions); } - return this.transport.send( - WidgetApiFromWidgetAction.SupportedApiVersions, {}, - ).then(r => { - this.cachedClientVersions = r.supported_versions; - return r.supported_versions; - }).catch(e => { - console.warn("non-fatal error getting supported client versions: ", e); - return []; - }); + return this.transport + .send( + WidgetApiFromWidgetAction.SupportedApiVersions, + {}, + ) + .then((r) => { + this.cachedClientVersions = r.supported_versions; + return r.supported_versions; + }) + .catch((e) => { + console.warn("non-fatal error getting supported client versions: ", e); + return []; + }); } private handleCapabilities(request: ICapabilitiesActionRequest): void | Promise { @@ -860,7 +879,7 @@ export class WidgetApi extends EventEmitter { } // See if we can expect a capabilities notification or not - return this.getClientVersions().then(v => { + return this.getClientVersions().then((v) => { if (v.includes(UnstableApiVersion.MSC2871)) { this.once( `action:${WidgetApiToWidgetAction.NotifyCapabilities}`, diff --git a/src/driver/WidgetDriver.ts b/src/driver/WidgetDriver.ts index f638dac..df92c03 100644 --- a/src/driver/WidgetDriver.ts +++ b/src/driver/WidgetDriver.ts @@ -145,10 +145,7 @@ export abstract class WidgetDriver { * Run the specified {@link action} for the delayed event matching the provided {@link delayId}. * @throws Rejected when there is no matching delayed event, or when the action failed to run. */ - public updateDelayedEvent( - delayId: string, - action: UpdateDelayedEventAction, - ): Promise { + public updateDelayedEvent(delayId: string, action: UpdateDelayedEventAction): Promise { return Promise.reject(new Error("Failed to override function")); } @@ -178,10 +175,7 @@ export abstract class WidgetDriver { * to look within, possibly containing Symbols.AnyRoom to denote all known rooms. * @returns {Promise} Resolves to the element of room account data, or an empty array. */ - public readRoomAccountData( - eventType: string, - roomIds: string[] | null = null, - ): Promise { + public readRoomAccountData(eventType: string, roomIds: string[] | null = null): Promise { return Promise.resolve([]); } @@ -281,11 +275,7 @@ export abstract class WidgetDriver { * @returns {Promise} Resolves to the events representing the * current values of the room state entries. */ - public readRoomState( - roomId: string, - eventType: string, - stateKey: string | undefined, - ): Promise { + public readRoomState(roomId: string, eventType: string, stateKey: string | undefined): Promise { return Promise.resolve([]); } @@ -321,7 +311,7 @@ export abstract class WidgetDriver { from?: string, to?: string, limit?: number, - direction?: 'f' | 'b', + direction?: "f" | "b", ): Promise { return Promise.resolve({ chunk: [] }); } @@ -340,7 +330,7 @@ export abstract class WidgetDriver { * @param {SimpleObservable} observer The observable to feed updates into. */ public askOpenID(observer: SimpleObservable): void { - observer.update({state: OpenIDRequestState.Blocked}); + observer.update({ state: OpenIDRequestState.Blocked }); } /** @@ -372,10 +362,7 @@ export abstract class WidgetDriver { * @param limit The maximum number of results to return. If not supplied, the * @returns Resolves to the search results. */ - public searchUserDirectory( - searchTerm: string, - limit?: number, - ): Promise { + public searchUserDirectory(searchTerm: string, limit?: number): Promise { return Promise.resolve({ limited: false, results: [] }); } @@ -393,9 +380,7 @@ export abstract class WidgetDriver { * XMLHttpRequest.send (typically a File). * @returns Resolves to the location of the uploaded file. */ - public uploadFile( - file: XMLHttpRequestBodyInit, - ): Promise<{ contentUri: string }> { + public uploadFile(file: XMLHttpRequestBodyInit): Promise<{ contentUri: string }> { throw new Error("Upload file is not implemented"); } @@ -404,9 +389,7 @@ export abstract class WidgetDriver { * @param contentUri - MXC URI of the file to download. * @returns Resolves to the contents of the file. */ - public downloadFile( - contentUri: string, - ): Promise<{ file: XMLHttpRequestBodyInit }> { + public downloadFile(contentUri: string): Promise<{ file: XMLHttpRequestBodyInit }> { throw new Error("Download file is not implemented"); } diff --git a/src/interfaces/Capabilities.ts b/src/interfaces/Capabilities.ts index 9baaae1..f541ac5 100644 --- a/src/interfaces/Capabilities.ts +++ b/src/interfaces/Capabilities.ts @@ -31,24 +31,24 @@ export enum MatrixCapabilities { MSC2931Navigate = "org.matrix.msc2931.navigate", MSC3846TurnServers = "town.robin.msc3846.turn_servers", /** - * @deprecated It is not recommended to rely on this existing - it can be removed without notice. - */ + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ MSC3973UserDirectorySearch = "org.matrix.msc3973.user_directory_search", /** - * @deprecated It is not recommended to rely on this existing - it can be removed without notice. - */ + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ MSC4039UploadFile = "org.matrix.msc4039.upload_file", /** - * @deprecated It is not recommended to rely on this existing - it can be removed without notice. - */ + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ MSC4039DownloadFile = "org.matrix.msc4039.download_file", /** - * @deprecated It is not recommended to rely on this existing - it can be removed without notice. - */ + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ MSC4157SendDelayedEvent = "org.matrix.msc4157.send.delayed_event", /** - * @deprecated It is not recommended to rely on this existing - it can be removed without notice. - */ + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ MSC4157UpdateDelayedEvent = "org.matrix.msc4157.update_delayed_event", } diff --git a/src/interfaces/DownloadFileAction.ts b/src/interfaces/DownloadFileAction.ts index 48ba6dd..f3eed2e 100644 --- a/src/interfaces/DownloadFileAction.ts +++ b/src/interfaces/DownloadFileAction.ts @@ -18,23 +18,19 @@ import { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; import { IWidgetApiResponseData } from "./IWidgetApiResponse"; import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; -export interface IDownloadFileActionFromWidgetRequestData - extends IWidgetApiRequestData { - content_uri: string; // eslint-disable-line camelcase +export interface IDownloadFileActionFromWidgetRequestData extends IWidgetApiRequestData { + content_uri: string; // eslint-disable-line camelcase } -export interface IDownloadFileActionFromWidgetActionRequest - extends IWidgetApiRequest { - action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction; - data: IDownloadFileActionFromWidgetRequestData; +export interface IDownloadFileActionFromWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction; + data: IDownloadFileActionFromWidgetRequestData; } -export interface IDownloadFileActionFromWidgetResponseData - extends IWidgetApiResponseData { - file: XMLHttpRequestBodyInit; +export interface IDownloadFileActionFromWidgetResponseData extends IWidgetApiResponseData { + file: XMLHttpRequestBodyInit; } -export interface IDownloadFileActionFromWidgetActionResponse - extends IDownloadFileActionFromWidgetActionRequest { - response: IDownloadFileActionFromWidgetResponseData; +export interface IDownloadFileActionFromWidgetActionResponse extends IDownloadFileActionFromWidgetActionRequest { + response: IDownloadFileActionFromWidgetResponseData; } diff --git a/src/interfaces/GetMediaConfigAction.ts b/src/interfaces/GetMediaConfigAction.ts index f67c2c8..71f19d1 100644 --- a/src/interfaces/GetMediaConfigAction.ts +++ b/src/interfaces/GetMediaConfigAction.ts @@ -18,21 +18,17 @@ import { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; import { IWidgetApiResponseData } from "./IWidgetApiResponse"; import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; -export interface IGetMediaConfigActionFromWidgetRequestData - extends IWidgetApiRequestData {} +export interface IGetMediaConfigActionFromWidgetRequestData extends IWidgetApiRequestData {} -export interface IGetMediaConfigActionFromWidgetActionRequest - extends IWidgetApiRequest { - action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction; - data: IGetMediaConfigActionFromWidgetRequestData; +export interface IGetMediaConfigActionFromWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction; + data: IGetMediaConfigActionFromWidgetRequestData; } -export interface IGetMediaConfigActionFromWidgetResponseData - extends IWidgetApiResponseData { - "m.upload.size"?: number; +export interface IGetMediaConfigActionFromWidgetResponseData extends IWidgetApiResponseData { + "m.upload.size"?: number; } -export interface IGetMediaConfigActionFromWidgetActionResponse - extends IGetMediaConfigActionFromWidgetActionRequest { - response: IGetMediaConfigActionFromWidgetResponseData; +export interface IGetMediaConfigActionFromWidgetActionResponse extends IGetMediaConfigActionFromWidgetActionRequest { + response: IGetMediaConfigActionFromWidgetResponseData; } diff --git a/src/interfaces/IWidgetApiErrorResponse.ts b/src/interfaces/IWidgetApiErrorResponse.ts index 89a29de..a215c2a 100644 --- a/src/interfaces/IWidgetApiErrorResponse.ts +++ b/src/interfaces/IWidgetApiErrorResponse.ts @@ -22,9 +22,9 @@ import { IWidgetApiResponse, IWidgetApiResponseData } from "./IWidgetApiResponse */ export interface IMatrixApiError { /** The HTTP status code of the associated request. */ - http_status: number; // eslint-disable-line camelcase + http_status: number; // eslint-disable-line camelcase /** Any HTTP response headers that are relevant to the error. */ - http_headers: {[name: string]: string}; // eslint-disable-line camelcase + http_headers: { [name: string]: string }; // eslint-disable-line camelcase /** The URL of the failed request. */ url: string; /** @see {@link https://spec.matrix.org/latest/client-server-api/#standard-error-response} */ @@ -36,7 +36,7 @@ export interface IMatrixApiError { export interface IWidgetApiErrorResponseDataDetails { /** Set if the error came from a Matrix API request made by a widget driver */ - matrix_api_error?: IMatrixApiError; // eslint-disable-line camelcase + matrix_api_error?: IMatrixApiError; // eslint-disable-line camelcase } export interface IWidgetApiErrorResponseData extends IWidgetApiResponseData { @@ -52,6 +52,5 @@ export interface IWidgetApiErrorResponse extends IWidgetApiResponse { export function isErrorResponse(responseData: IWidgetApiResponseData): responseData is IWidgetApiErrorResponseData { const error = responseData.error; - return typeof error === "object" && error !== null && - "message" in error && typeof error.message === "string"; + return typeof error === "object" && error !== null && "message" in error && typeof error.message === "string"; } diff --git a/src/interfaces/LanguageChangeAction.ts b/src/interfaces/LanguageChangeAction.ts index f2453c0..8b5de3a 100644 --- a/src/interfaces/LanguageChangeAction.ts +++ b/src/interfaces/LanguageChangeAction.ts @@ -19,10 +19,10 @@ import { WidgetApiToWidgetAction } from "./WidgetApiAction"; import { IWidgetApiAcknowledgeResponseData } from "./IWidgetApiResponse"; export interface ILanguageChangeActionRequestData extends IWidgetApiRequestData { - /** - * The BCP 47 identifier for the client's current language. - */ - lang: string; + /** + * The BCP 47 identifier for the client's current language. + */ + lang: string; } export interface ILanguageChangeActionRequest extends IWidgetApiRequest { diff --git a/src/interfaces/ReadRelationsAction.ts b/src/interfaces/ReadRelationsAction.ts index 76a041a..d89d538 100644 --- a/src/interfaces/ReadRelationsAction.ts +++ b/src/interfaces/ReadRelationsAction.ts @@ -28,7 +28,7 @@ export interface IReadRelationsFromWidgetRequestData extends IWidgetApiRequestDa limit?: number; from?: string; to?: string; - direction?: 'f' | 'b'; + direction?: "f" | "b"; } export interface IReadRelationsFromWidgetActionRequest extends IWidgetApiRequest { diff --git a/src/interfaces/SendEventAction.ts b/src/interfaces/SendEventAction.ts index 45963ad..4631dac 100644 --- a/src/interfaces/SendEventAction.ts +++ b/src/interfaces/SendEventAction.ts @@ -47,8 +47,7 @@ export interface ISendEventFromWidgetActionResponse extends ISendEventFromWidget response: ISendEventFromWidgetResponseData; } -export interface ISendEventToWidgetRequestData extends IWidgetApiRequestData, IRoomEvent { -} +export interface ISendEventToWidgetRequestData extends IWidgetApiRequestData, IRoomEvent {} export interface ISendEventToWidgetActionRequest extends IWidgetApiRequest { action: WidgetApiToWidgetAction.SendEvent; diff --git a/src/interfaces/StickerAction.ts b/src/interfaces/StickerAction.ts index 13cb94a..c7293e3 100644 --- a/src/interfaces/StickerAction.ts +++ b/src/interfaces/StickerAction.ts @@ -28,7 +28,8 @@ export interface IStickerActionRequestData extends IWidgetApiRequestData { w?: number; mimetype?: string; size?: number; - thumbnail_info?: { // eslint-disable-line camelcase + thumbnail_info?: { + // eslint-disable-line camelcase h?: number; w?: number; mimetype?: string; diff --git a/src/interfaces/ThemeChangeAction.ts b/src/interfaces/ThemeChangeAction.ts index 30138e9..292f58e 100644 --- a/src/interfaces/ThemeChangeAction.ts +++ b/src/interfaces/ThemeChangeAction.ts @@ -19,7 +19,7 @@ import { WidgetApiToWidgetAction } from "./WidgetApiAction"; import { IWidgetApiAcknowledgeResponseData } from "./IWidgetApiResponse"; export interface IThemeChangeActionRequestData extends IWidgetApiRequestData { - // The format of a theme is deliberately unstandardized + // The format of a theme is deliberately unstandardized } export interface IThemeChangeActionRequest extends IWidgetApiRequest { diff --git a/src/interfaces/TurnServerActions.ts b/src/interfaces/TurnServerActions.ts index 3a9bd29..36f664a 100644 --- a/src/interfaces/TurnServerActions.ts +++ b/src/interfaces/TurnServerActions.ts @@ -42,8 +42,7 @@ export interface IUnwatchTurnServersResponse extends IWidgetApiResponse { response: IWidgetApiAcknowledgeResponseData; } -export interface IUpdateTurnServersRequestData extends IWidgetApiRequestData, ITurnServer { -} +export interface IUpdateTurnServersRequestData extends IWidgetApiRequestData, ITurnServer {} export interface IUpdateTurnServersRequest extends IWidgetApiRequest { action: WidgetApiToWidgetAction.UpdateTurnServers; diff --git a/src/interfaces/UploadFileAction.ts b/src/interfaces/UploadFileAction.ts index 86d529f..9d120b6 100644 --- a/src/interfaces/UploadFileAction.ts +++ b/src/interfaces/UploadFileAction.ts @@ -18,23 +18,19 @@ import { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; import { IWidgetApiResponseData } from "./IWidgetApiResponse"; import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; -export interface IUploadFileActionFromWidgetRequestData - extends IWidgetApiRequestData { - file: XMLHttpRequestBodyInit; +export interface IUploadFileActionFromWidgetRequestData extends IWidgetApiRequestData { + file: XMLHttpRequestBodyInit; } -export interface IUploadFileActionFromWidgetActionRequest - extends IWidgetApiRequest { - action: WidgetApiFromWidgetAction.MSC4039UploadFileAction; - data: IUploadFileActionFromWidgetRequestData; +export interface IUploadFileActionFromWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.MSC4039UploadFileAction; + data: IUploadFileActionFromWidgetRequestData; } -export interface IUploadFileActionFromWidgetResponseData - extends IWidgetApiResponseData { - content_uri: string; // eslint-disable-line camelcase +export interface IUploadFileActionFromWidgetResponseData extends IWidgetApiResponseData { + content_uri: string; // eslint-disable-line camelcase } -export interface IUploadFileActionFromWidgetActionResponse - extends IUploadFileActionFromWidgetActionRequest { - response: IUploadFileActionFromWidgetResponseData; +export interface IUploadFileActionFromWidgetActionResponse extends IUploadFileActionFromWidgetActionRequest { + response: IUploadFileActionFromWidgetResponseData; } diff --git a/src/models/WidgetEventCapability.ts b/src/models/WidgetEventCapability.ts index 93998c2..1190606 100644 --- a/src/models/WidgetEventCapability.ts +++ b/src/models/WidgetEventCapability.ts @@ -35,8 +35,7 @@ export class WidgetEventCapability { public readonly kind: EventKind, public readonly keyStr: string | null, public readonly raw: string, - ) { - } + ) {} public matchesAsStateEvent(direction: EventDirection, eventType: string, stateKey: string | null): boolean { if (this.kind !== EventKind.State) return false; // not a state event @@ -90,8 +89,8 @@ export class WidgetEventCapability { ): WidgetEventCapability { // TODO: Enable support for m.* namespace once the MSC lands. // https://github.com/matrix-org/matrix-widget-api/issues/22 - eventType = eventType.replace(/#/g, '\\#'); - stateKey = stateKey !== null && stateKey !== undefined ? `#${stateKey}` : ''; + eventType = eventType.replace(/#/g, "\\#"); + stateKey = stateKey !== null && stateKey !== undefined ? `#${stateKey}` : ""; const str = `org.matrix.msc2762.${direction}.state_event:${eventType}${stateKey}`; // cheat by sending it through the processor @@ -119,7 +118,7 @@ export class WidgetEventCapability { public static forRoomMessageEvent(direction: EventDirection, msgtype?: string): WidgetEventCapability { // TODO: Enable support for m.* namespace once the MSC lands. // https://github.com/matrix-org/matrix-widget-api/issues/22 - msgtype = msgtype === null || msgtype === undefined ? '' : msgtype; + msgtype = msgtype === null || msgtype === undefined ? "" : msgtype; const str = `org.matrix.msc2762.${direction}.event:m.room.message#${msgtype}`; // cheat by sending it through the processor @@ -186,7 +185,7 @@ export class WidgetEventCapability { // Eg: `m.room.message##m.text` is "m.room.message" event with msgtype "#m.text". const expectingKeyStr = eventSegment.startsWith("m.room.message#") || kind === EventKind.State; let keyStr: string | null = null; - if (eventSegment.includes('#') && expectingKeyStr) { + if (eventSegment.includes("#") && expectingKeyStr) { // Dev note: regex is difficult to write, so instead the rules are manually written // out. This is probably just as understandable as a boring regex though, so win-win? @@ -202,19 +201,20 @@ export class WidgetEventCapability { // m.room.message\\###test m.room.message\# #test // First step: explode the string - const parts = eventSegment.split('#'); + const parts = eventSegment.split("#"); // To form the eventSegment, we'll keep finding parts of the exploded string until // there's one that doesn't end with the escape character (\). We'll then join those // segments together with the exploding character. We have to remember to consume the // escape character as well. - const idx = parts.findIndex(p => !p.endsWith("\\")); - eventSegment = parts.slice(0, idx + 1) - .map(p => p.endsWith('\\') ? p.substring(0, p.length - 1) : p) - .join('#'); + const idx = parts.findIndex((p) => !p.endsWith("\\")); + eventSegment = parts + .slice(0, idx + 1) + .map((p) => (p.endsWith("\\") ? p.substring(0, p.length - 1) : p)) + .join("#"); // The keyStr is whatever is left over. - keyStr = parts.slice(idx + 1).join('#'); + keyStr = parts.slice(idx + 1).join("#"); } parsed.push(new WidgetEventCapability(direction, eventSegment, kind, keyStr, cap)); diff --git a/src/models/WidgetParser.ts b/src/models/WidgetParser.ts index f93c077..07ced72 100644 --- a/src/models/WidgetParser.ts +++ b/src/models/WidgetParser.ts @@ -116,17 +116,17 @@ export class WidgetParser { // is done against the requirements of the interface because not everyone // will have an interface to validate against. - const content = stateEvent.content as IWidget || {}; + const content = (stateEvent.content as IWidget) || {}; // Form our best approximation of a widget with the information we have const estimatedWidget: IWidget = { id: stateEvent.state_key, - creatorUserId: content['creatorUserId'] || stateEvent.sender, - name: content['name'], - type: content['type'], - url: content['url'], - waitForIframeLoad: content['waitForIframeLoad'], - data: content['data'], + creatorUserId: content["creatorUserId"] || stateEvent.sender, + name: content["name"], + type: content["type"], + url: content["url"], + waitForIframeLoad: content["waitForIframeLoad"], + data: content["data"], }; // Finally, process that widget diff --git a/src/templating/url-template.ts b/src/templating/url-template.ts index 6bf4ffa..b700a9b 100644 --- a/src/templating/url-template.ts +++ b/src/templating/url-template.ts @@ -31,28 +31,28 @@ export interface ITemplateParams { export function runTemplate(url: string, widget: IWidget, params: ITemplateParams): string { // Always apply the supplied params over top of data to ensure the data can't lie about them. const variables = Object.assign({}, widget.data, { - 'matrix_room_id': params.widgetRoomId || "", - 'matrix_user_id': params.currentUserId, - 'matrix_display_name': params.userDisplayName || params.currentUserId, - 'matrix_avatar_url': params.userHttpAvatarUrl || "", - 'matrix_widget_id': widget.id, + "matrix_room_id": params.widgetRoomId || "", + "matrix_user_id": params.currentUserId, + "matrix_display_name": params.userDisplayName || params.currentUserId, + "matrix_avatar_url": params.userHttpAvatarUrl || "", + "matrix_widget_id": widget.id, // TODO: Convert to stable (https://github.com/matrix-org/matrix-doc/pull/2873) - 'org.matrix.msc2873.client_id': params.clientId || "", - 'org.matrix.msc2873.client_theme': params.clientTheme || "", - 'org.matrix.msc2873.client_language': params.clientLanguage || "", + "org.matrix.msc2873.client_id": params.clientId || "", + "org.matrix.msc2873.client_theme": params.clientTheme || "", + "org.matrix.msc2873.client_language": params.clientLanguage || "", // TODO: Convert to stable (https://github.com/matrix-org/matrix-spec-proposals/pull/3819) - 'org.matrix.msc3819.matrix_device_id': params.deviceId || "", + "org.matrix.msc3819.matrix_device_id": params.deviceId || "", // TODO: Convert to stable (https://github.com/matrix-org/matrix-spec-proposals/pull/4039) - 'org.matrix.msc4039.matrix_base_url': params.baseUrl || "", + "org.matrix.msc4039.matrix_base_url": params.baseUrl || "", }); let result = url; for (const key of Object.keys(variables)) { // Regex escape from https://stackoverflow.com/a/6969486/7037379 - const pattern = `$${key}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string - const rexp = new RegExp(pattern, 'g'); + const pattern = `$${key}`.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + const rexp = new RegExp(pattern, "g"); // This is technically not what we're supposed to do for a couple of reasons: // 1. We are assuming that there won't later be a $key match after we replace a variable. diff --git a/src/transport/ITransport.ts b/src/transport/ITransport.ts index b75cf9b..3446e6a 100644 --- a/src/transport/ITransport.ts +++ b/src/transport/ITransport.ts @@ -81,7 +81,7 @@ export interface ITransport extends EventEmitter { */ send( action: WidgetApiAction, - data: T + data: T, ): Promise; /** @@ -95,7 +95,10 @@ export interface ITransport extends EventEmitter { * @throws {WidgetApiResponseError} if the request failed with error details * that can be communicated to the Widget API. */ - sendComplete(action: WidgetApiAction, data: T): Promise; + sendComplete( + action: WidgetApiAction, + data: T, + ): Promise; /** * Replies to a request. diff --git a/src/transport/PostmessageTransport.ts b/src/transport/PostmessageTransport.ts index aede279..4589735 100644 --- a/src/transport/PostmessageTransport.ts +++ b/src/transport/PostmessageTransport.ts @@ -94,13 +94,15 @@ export class PostmessageTransport extends EventEmitter implements ITransport { } public send( - action: WidgetApiAction, data: T, + action: WidgetApiAction, + data: T, ): Promise { - return this.sendComplete(action, data).then(r => r.response); + return this.sendComplete(action, data).then((r) => r.response); } public sendComplete( - action: WidgetApiAction, data: T, + action: WidgetApiAction, + data: T, ): Promise { if (!this.ready || !this.widgetId) { return Promise.reject(new Error("Not ready or unknown widget ID")); @@ -113,7 +115,7 @@ export class PostmessageTransport extends EventEmitter implements ITransport { data: data, }; if (action === WidgetApiToWidgetAction.UpdateVisibility) { - request['visible'] = data['visible']; + request["visible"] = data["visible"]; } return new Promise((prResolve, prReject) => { const resolve = (response: IWidgetApiResponse): void => { @@ -125,10 +127,7 @@ export class PostmessageTransport extends EventEmitter implements ITransport { prReject(err); }; - const timerId = setTimeout( - () => reject(new Error("Request timed out")), - (this.timeoutSeconds || 1) * 1000, - ); + const timerId = setTimeout(() => reject(new Error("Request timed out")), (this.timeoutSeconds || 1) * 1000); const onStop = (): void => reject(new Error("Transport stopped")); this.stopController.signal.addEventListener("abort", onStop); @@ -185,7 +184,7 @@ export class PostmessageTransport extends EventEmitter implements ITransport { this._widgetId = request.widgetId; } - this.emit("message", new CustomEvent("message", {detail: request})); + this.emit("message", new CustomEvent("message", { detail: request })); } private handleResponse(response: IWidgetApiResponse): void { @@ -195,7 +194,7 @@ export class PostmessageTransport extends EventEmitter implements ITransport { if (!req) return; // response to an unknown request if (isErrorResponse(response.response)) { - const {message, ...data} = response.response.error; + const { message, ...data } = response.response.error; req.reject(new WidgetApiResponseError(message, data)); } else { req.resolve(response); diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index fd446d7..15bec9e 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -17,8 +17,8 @@ import { waitFor } from '@testing-library/dom'; -import { ClientWidgetApi } from "../src/ClientWidgetApi"; -import { WidgetDriver } from "../src/driver/WidgetDriver"; +import { ClientWidgetApi } from '../src/ClientWidgetApi'; +import { WidgetDriver } from '../src/driver/WidgetDriver'; import { UnstableApiVersion } from '../src/interfaces/ApiVersion'; import { Capability } from '../src/interfaces/Capabilities'; import { IRoomEvent } from '../src/interfaces/IRoomEvent'; @@ -80,18 +80,20 @@ class CustomMatrixError extends Error { } function processCustomMatrixError(e: unknown): IWidgetApiErrorResponseDataDetails | undefined { - return e instanceof CustomMatrixError ? { - matrix_api_error: { - http_status: e.httpStatus, - http_headers: {}, - url: '', - response: { - errcode: e.name, - error: e.message, - ...e.data, - }, - }, - } : undefined; + return e instanceof CustomMatrixError + ? { + matrix_api_error: { + http_status: e.httpStatus, + http_headers: {}, + url: '', + response: { + errcode: e.name, + error: e.message, + ...e.data, + }, + }, + } + : undefined; } describe('ClientWidgetApi', () => { @@ -100,12 +102,12 @@ describe('ClientWidgetApi', () => { let driver: jest.Mocked; let clientWidgetApi: ClientWidgetApi; let transport: PostmessageTransport; - let emitEvent: Parameters["1"]; + let emitEvent: Parameters['1']; async function loadIframe(caps: Capability[] = []): Promise { capabilities = caps; - const ready = new Promise(resolve => { + const ready = new Promise((resolve) => { clientWidgetApi.once('ready', resolve); }); @@ -141,10 +143,10 @@ describe('ClientWidgetApi', () => { clientWidgetApi = new ClientWidgetApi( new Widget({ - id: "test", - creatorUserId: "@alice:example.org", - type: "example", - url: "https://example.org", + id: 'test', + creatorUserId: '@alice:example.org', + type: 'example', + url: 'https://example.org', }), iframe, driver, @@ -154,9 +156,7 @@ describe('ClientWidgetApi', () => { emitEvent = jest.mocked(transport.on).mock.calls[0][1]; jest.mocked(transport.send).mockResolvedValue({}); - jest.mocked(driver.validateCapabilities).mockImplementation( - async () => new Set(capabilities), - ); + jest.mocked(driver.validateCapabilities).mockImplementation(async () => new Set(capabilities)); }); afterEach(() => { @@ -193,9 +193,7 @@ describe('ClientWidgetApi', () => { expect(transport.reply).toHaveBeenCalledWith(event, {}); }); - expect(driver.navigate).toHaveBeenCalledWith( - event.data.uri, - ); + expect(driver.navigate).toHaveBeenCalledWith(event.data.uri); }); it('fails to navigate', async () => { @@ -247,9 +245,7 @@ describe('ClientWidgetApi', () => { }); it('should reject requests when the driver throws an exception', async () => { - driver.navigate.mockRejectedValue( - new Error("M_UNKNOWN: Unknown error"), - ); + driver.navigate.mockRejectedValue(new Error('M_UNKNOWN: Unknown error')); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, @@ -276,14 +272,9 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.navigate.mockRejectedValue( - new CustomMatrixError( - 'failed to navigate', - 400, - 'M_UNKNOWN', - { - reason: 'Unknown error', - }, - ), + new CustomMatrixError('failed to navigate', 400, 'M_UNKNOWN', { + reason: 'Unknown error', + }), ); const event: INavigateActionRequest = { @@ -356,12 +347,7 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.sendEvent).toHaveBeenCalledWith( - event.data.type, - event.data.content, - null, - roomId, - ); + expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, null, roomId); }); it('sends state events', async () => { @@ -400,20 +386,13 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.sendEvent).toHaveBeenCalledWith( - event.data.type, - event.data.content, - '', - roomId, - ); + expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, '', roomId); }); it('should reject requests when the driver throws an exception', async () => { const roomId = '!room:example.org'; - driver.sendEvent.mockRejectedValue( - new Error("M_BAD_JSON: Content must be a JSON object"), - ); + driver.sendEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -447,14 +426,9 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.sendEvent.mockRejectedValue( - new CustomMatrixError( - 'failed to send event', - 400, - 'M_NOT_JSON', - { - reason: 'Content must be a JSON object.', - }, - ), + new CustomMatrixError('failed to send event', 400, 'M_NOT_JSON', { + reason: 'Content must be a JSON object.', + }), ); const event: ISendEventFromWidgetActionRequest = { @@ -632,9 +606,7 @@ describe('ClientWidgetApi', () => { it('should reject requests when the driver throws an exception', async () => { const roomId = '!room:example.org'; - driver.sendDelayedEvent.mockRejectedValue( - new Error("M_BAD_JSON: Content must be a JSON object"), - ); + driver.sendDelayedEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -671,14 +643,9 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.sendDelayedEvent.mockRejectedValue( - new CustomMatrixError( - 'failed to send event', - 400, - 'M_NOT_JSON', - { - reason: 'Content must be a JSON object.', - }, - ), + new CustomMatrixError('failed to send event', 400, 'M_NOT_JSON', { + reason: 'Content must be a JSON object.', + }), ); const event: ISendEventFromWidgetActionRequest = { @@ -819,7 +786,7 @@ describe('ClientWidgetApi', () => { // Artificially delay the delivery of the join rules event let resolveJoinRules: () => void; - const joinRules = new Promise(resolve => resolveJoinRules = resolve); + const joinRules = new Promise((resolve) => (resolveJoinRules = resolve)); driver.readRoomState.mockImplementation(async (rId, eventType, stateKey) => { if (rId === roomId) { @@ -856,15 +823,13 @@ describe('ClientWidgetApi', () => { await waitFor(() => { // The initial topic and name should have been pushed - expect(transport.send).toHaveBeenCalledWith( - WidgetApiToWidgetAction.UpdateState, - { state: [topicEvent, nameEvent, newJoinRulesEvent] }, - ); + expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.UpdateState, { + state: [topicEvent, nameEvent, newJoinRulesEvent], + }); // Only the updated join rules should have been delivered - expect(transport.send).not.toHaveBeenCalledWith( - WidgetApiToWidgetAction.UpdateState, - { state: expect.arrayContaining([joinRules]) }, - ); + expect(transport.send).not.toHaveBeenCalledWith(WidgetApiToWidgetAction.UpdateState, { + state: expect.arrayContaining([joinRules]), + }); }); // Check that further updates to room state are pushed to the widget @@ -878,28 +843,25 @@ describe('ClientWidgetApi', () => { clientWidgetApi.feedStateUpdate(newTopicEvent); await waitFor(() => { - expect(transport.send).toHaveBeenCalledWith( - WidgetApiToWidgetAction.UpdateState, - { state: [newTopicEvent] }, - ); + expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.UpdateState, { + state: [newTopicEvent], + }); }); // Up to this point we should not have received any state for the // other (unviewed) room - expect(transport.send).not.toHaveBeenCalledWith( - WidgetApiToWidgetAction.UpdateState, - { state: expect.arrayContaining([otherRoomNameEvent]) }, - ); + expect(transport.send).not.toHaveBeenCalledWith(WidgetApiToWidgetAction.UpdateState, { + state: expect.arrayContaining([otherRoomNameEvent]), + }); // Now view the other room clientWidgetApi.setViewedRoomId(otherRoomId); (transport.send as unknown as jest.SpyInstance).mockClear(); await waitFor(() => { // The state of the other room should now be pushed - expect(transport.send).toHaveBeenCalledWith( - WidgetApiToWidgetAction.UpdateState, - { state: expect.arrayContaining([otherRoomNameEvent]) }, - ); + expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.UpdateState, { + state: expect.arrayContaining([otherRoomNameEvent]), + }); }); }); }); @@ -982,17 +944,12 @@ describe('ClientWidgetApi', () => { expect(transport.reply).toHaveBeenCalledWith(event, {}); }); - expect(driver.updateDelayedEvent).toHaveBeenCalledWith( - event.data.delay_id, - event.data.action, - ); + expect(driver.updateDelayedEvent).toHaveBeenCalledWith(event.data.delay_id, event.data.action); } }); it('should reject requests when the driver throws an exception', async () => { - driver.updateDelayedEvent.mockRejectedValue( - new Error("M_BAD_JSON: Content must be a JSON object"), - ); + driver.updateDelayedEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -1020,14 +977,9 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.updateDelayedEvent.mockRejectedValue( - new CustomMatrixError( - 'failed to update delayed event', - 400, - 'M_NOT_JSON', - { - reason: 'Content must be a JSON object.', - }, - ), + new CustomMatrixError('failed to update delayed event', 400, 'M_NOT_JSON', { + reason: 'Content must be a JSON object.', + }), ); const event: IUpdateDelayedEventFromWidgetActionRequest = { @@ -1077,8 +1029,8 @@ describe('ClientWidgetApi', () => { encrypted: false, messages: { '@foo:bar.com': { - 'DEVICEID': { - 'example_content_key': 'value', + DEVICEID: { + example_content_key: 'value', }, }, }, @@ -1110,8 +1062,8 @@ describe('ClientWidgetApi', () => { encrypted: false, messages: { '@foo:bar.com': { - 'DEVICEID': { - 'example_content_key': 'value', + DEVICEID: { + example_content_key: 'value', }, }, }, @@ -1166,8 +1118,8 @@ describe('ClientWidgetApi', () => { type: 'net.example.test', messages: { '@foo:bar.com': { - 'DEVICEID': { - 'example_content_key': 'value', + DEVICEID: { + example_content_key: 'value', }, }, }, @@ -1198,8 +1150,8 @@ describe('ClientWidgetApi', () => { encrypted: false, messages: { '@foo:bar.com': { - 'DEVICEID': { - 'example_content_key': 'value', + DEVICEID: { + example_content_key: 'value', }, }, }, @@ -1234,8 +1186,8 @@ describe('ClientWidgetApi', () => { encrypted: false, messages: { '@foo:bar.com': { - 'DEVICEID': { - 'example_content_key': 'value', + DEVICEID: { + example_content_key: 'value', }, }, }, @@ -1257,14 +1209,9 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.sendToDevice.mockRejectedValue( - new CustomMatrixError( - 'failed to send event', - 400, - 'M_FORBIDDEN', - { - reason: "You don't have permission to send to-device events", - }, - ), + new CustomMatrixError('failed to send event', 400, 'M_FORBIDDEN', { + reason: "You don't have permission to send to-device events", + }), ); const event: ISendToDeviceFromWidgetActionRequest = { @@ -1277,8 +1224,8 @@ describe('ClientWidgetApi', () => { encrypted: false, messages: { '@foo:bar.com': { - 'DEVICEID': { - 'example_content_key': 'value', + DEVICEID: { + example_content_key: 'value', }, }, }, @@ -1315,7 +1262,7 @@ describe('ClientWidgetApi', () => { observable.update({ state: OpenIDRequestState.Allowed, token: { - access_token: "access_token", + access_token: 'access_token', }, }); }); @@ -1335,7 +1282,7 @@ describe('ClientWidgetApi', () => { await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { state: OpenIDRequestState.Allowed, - access_token: "access_token", + access_token: 'access_token', }); }); @@ -1376,11 +1323,13 @@ describe('ClientWidgetApi', () => { const type = 'net.example.test'; const roomId = '!room:example.org'; - driver.readRoomAccountData.mockResolvedValue([{ - type, - room_id: roomId, - content: {}, - }]); + driver.readRoomAccountData.mockResolvedValue([ + { + type, + room_id: roomId, + content: {}, + }, + ]); const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -1393,19 +1342,19 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - `com.beeper.capabilities.receive.room_account_data:${type}`, - ]); + await loadIframe([`com.beeper.capabilities.receive.room_account_data:${type}`]); emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - events: [{ - type, - room_id: roomId, - content: {}, - }], + events: [ + { + type, + room_id: roomId, + content: {}, + }, + ], }); }); @@ -1416,11 +1365,13 @@ describe('ClientWidgetApi', () => { const type = 'net.example.test'; const roomId = '!room:example.org'; - driver.readRoomAccountData.mockResolvedValue([{ - type, - room_id: roomId, - content: {}, - }]); + driver.readRoomAccountData.mockResolvedValue([ + { + type, + room_id: roomId, + content: {}, + }, + ]); const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -1482,7 +1433,12 @@ describe('ClientWidgetApi', () => { }); expect(driver.readRoomTimeline).toHaveBeenCalledWith( - roomId, 'net.example.test', undefined, undefined, 0, undefined, + roomId, + 'net.example.test', + undefined, + undefined, + 0, + undefined, ); }); @@ -1524,10 +1480,20 @@ describe('ClientWidgetApi', () => { }); expect(driver.readRoomTimeline).toHaveBeenCalledWith( - roomId, 'net.example.test', undefined, undefined, 0, undefined, + roomId, + 'net.example.test', + undefined, + undefined, + 0, + undefined, ); expect(driver.readRoomTimeline).toHaveBeenCalledWith( - otherRoomId, 'net.example.test', undefined, undefined, 0, undefined, + otherRoomId, + 'net.example.test', + undefined, + undefined, + 0, + undefined, ); }); @@ -1563,7 +1529,12 @@ describe('ClientWidgetApi', () => { }); expect(driver.readRoomTimeline).toBeCalledWith( - '!room-id', 'net.example.test', undefined, undefined, 0, undefined, + '!room-id', + 'net.example.test', + undefined, + undefined, + 0, + undefined, ); }); @@ -1593,9 +1564,7 @@ describe('ClientWidgetApi', () => { }); it('reads state events with a specific state key', async () => { - driver.readRoomTimeline.mockResolvedValue([ - createRoomEvent({ type: 'net.example.test', state_key: 'B' }), - ]); + driver.readRoomTimeline.mockResolvedValue([createRoomEvent({ type: 'net.example.test', state_key: 'B' })]); const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -1615,14 +1584,17 @@ describe('ClientWidgetApi', () => { await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - events: [ - createRoomEvent({ type: 'net.example.test', state_key: 'B' }), - ], + events: [createRoomEvent({ type: 'net.example.test', state_key: 'B' })], }); }); expect(driver.readRoomTimeline).toBeCalledWith( - '!room-id', 'net.example.test', undefined, 'B', 0, undefined, + '!room-id', + 'net.example.test', + undefined, + 'B', + 0, + undefined, ); }); @@ -1666,9 +1638,7 @@ describe('ClientWidgetApi', () => { emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - supported_versions: expect.arrayContaining([ - UnstableApiVersion.MSC3869, - ]), + supported_versions: expect.arrayContaining([UnstableApiVersion.MSC3869]), }); }); @@ -1685,9 +1655,7 @@ describe('ClientWidgetApi', () => { data: { event_id: '$event' }, }; - await loadIframe([ - 'org.matrix.msc2762.receive.event:m.room.message', - ]); + await loadIframe(['org.matrix.msc2762.receive.event:m.room.message']); emitEvent(new CustomEvent('', { detail: event })); @@ -1698,8 +1666,14 @@ describe('ClientWidgetApi', () => { }); expect(driver.readEventRelations).toBeCalledWith( - '$event', undefined, undefined, undefined, undefined, undefined, - undefined, undefined, + '$event', + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, ); }); @@ -1730,16 +1704,19 @@ describe('ClientWidgetApi', () => { await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - chunk: [ - createRoomEvent(), - createRoomEvent({ type: 'net.example.test', state_key: 'A' }), - ], + chunk: [createRoomEvent(), createRoomEvent({ type: 'net.example.test', state_key: 'A' })], }); }); expect(driver.readEventRelations).toBeCalledWith( - '$event', undefined, undefined, undefined, undefined, undefined, - undefined, undefined, + '$event', + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, ); }); @@ -1765,9 +1742,7 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - 'org.matrix.msc2762.timeline:!room-id', - ]); + await loadIframe(['org.matrix.msc2762.timeline:!room-id']); emitEvent(new CustomEvent('', { detail: event })); @@ -1778,8 +1753,14 @@ describe('ClientWidgetApi', () => { }); expect(driver.readEventRelations).toBeCalledWith( - '$event', '!room-id', 'm.reference', 'm.room.message', - 'from-token', 'to-token', 25, 'f', + '$event', + '!room-id', + 'm.reference', + 'm.room.message', + 'from-token', + 'to-token', + 25, + 'f', ); }); @@ -1865,14 +1846,9 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.readEventRelations.mockRejectedValue( - new CustomMatrixError( - 'failed to read relations', - 403, - 'M_FORBIDDEN', - { - reason: "You don't have permission to access that event", - }, - ), + new CustomMatrixError('failed to read relations', 403, 'M_FORBIDDEN', { + reason: "You don't have permission to access that event", + }), ); const event: IReadRelationsFromWidgetActionRequest = { @@ -1920,18 +1896,18 @@ describe('ClientWidgetApi', () => { emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - supported_versions: expect.arrayContaining([ - UnstableApiVersion.MSC3973, - ]), + supported_versions: expect.arrayContaining([UnstableApiVersion.MSC3973]), }); }); it('should handle and process the request', async () => { driver.searchUserDirectory.mockResolvedValue({ limited: true, - results: [{ - userId: '@foo:bar.com', - }], + results: [ + { + userId: '@foo:bar.com', + }, + ], }); const event: IUserDirectorySearchFromWidgetActionRequest = { @@ -1942,20 +1918,20 @@ describe('ClientWidgetApi', () => { data: { search_term: 'foo' }, }; - await loadIframe([ - 'org.matrix.msc3973.user_directory_search', - ]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { limited: true, - results: [{ - user_id: '@foo:bar.com', - display_name: undefined, - avatar_url: undefined, - }], + results: [ + { + user_id: '@foo:bar.com', + display_name: undefined, + avatar_url: undefined, + }, + ], }); }); @@ -1988,9 +1964,7 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - 'org.matrix.msc3973.user_directory_search', - ]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); emitEvent(new CustomEvent('', { detail: event })); @@ -2029,9 +2003,7 @@ describe('ClientWidgetApi', () => { data: { search_term: '' }, }; - await loadIframe([ - 'org.matrix.msc3973.user_directory_search', - ]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); emitEvent(new CustomEvent('', { detail: event })); @@ -2072,9 +2044,7 @@ describe('ClientWidgetApi', () => { data: {}, }; - await loadIframe([ - 'org.matrix.msc3973.user_directory_search', - ]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); emitEvent(new CustomEvent('', { detail: event })); @@ -2097,9 +2067,7 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - 'org.matrix.msc3973.user_directory_search', - ]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); emitEvent(new CustomEvent('', { detail: event })); @@ -2111,9 +2079,7 @@ describe('ClientWidgetApi', () => { }); it('should reject requests when the driver throws an exception', async () => { - driver.searchUserDirectory.mockRejectedValue( - new Error("M_LIMIT_EXCEEDED: Too many requests"), - ); + driver.searchUserDirectory.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -2123,9 +2089,7 @@ describe('ClientWidgetApi', () => { data: { search_term: 'foo' }, }; - await loadIframe([ - 'org.matrix.msc3973.user_directory_search', - ]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); emitEvent(new CustomEvent('', { detail: event })); @@ -2140,15 +2104,10 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.searchUserDirectory.mockRejectedValue( - new CustomMatrixError( - 'failed to search the user directory', - 429, - 'M_LIMIT_EXCEEDED', - { - reason: 'Too many requests', - retry_after_ms: 2000, - }, - ), + new CustomMatrixError('failed to search the user directory', 429, 'M_LIMIT_EXCEEDED', { + reason: 'Too many requests', + retry_after_ms: 2000, + }), ); const event: IUserDirectorySearchFromWidgetActionRequest = { @@ -2159,9 +2118,7 @@ describe('ClientWidgetApi', () => { data: { search_term: 'foo' }, }; - await loadIframe([ - 'org.matrix.msc3973.user_directory_search', - ]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); emitEvent(new CustomEvent('', { detail: event })); @@ -2199,9 +2156,7 @@ describe('ClientWidgetApi', () => { emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - supported_versions: expect.arrayContaining([ - UnstableApiVersion.MSC4039, - ]), + supported_versions: expect.arrayContaining([UnstableApiVersion.MSC4039]), }); }); @@ -2218,9 +2173,7 @@ describe('ClientWidgetApi', () => { data: {}, }; - await loadIframe([ - 'org.matrix.msc4039.upload_file', - ]); + await loadIframe(['org.matrix.msc4039.upload_file']); emitEvent(new CustomEvent('', { detail: event })); @@ -2252,9 +2205,7 @@ describe('ClientWidgetApi', () => { }); it('should reject requests when the driver throws an exception', async () => { - driver.getMediaConfig.mockRejectedValue( - new Error("M_LIMIT_EXCEEDED: Too many requests"), - ); + driver.getMediaConfig.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -2264,9 +2215,7 @@ describe('ClientWidgetApi', () => { data: {}, }; - await loadIframe([ - 'org.matrix.msc4039.upload_file', - ]); + await loadIframe(['org.matrix.msc4039.upload_file']); emitEvent(new CustomEvent('', { detail: event })); @@ -2281,15 +2230,10 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.getMediaConfig.mockRejectedValue( - new CustomMatrixError( - 'failed to get the media configuration', - 429, - 'M_LIMIT_EXCEEDED', - { - reason: 'Too many requests', - retry_after_ms: 2000, - }, - ), + new CustomMatrixError('failed to get the media configuration', 429, 'M_LIMIT_EXCEEDED', { + reason: 'Too many requests', + retry_after_ms: 2000, + }), ); const event: IGetMediaConfigActionFromWidgetActionRequest = { @@ -2300,9 +2244,7 @@ describe('ClientWidgetApi', () => { data: {}, }; - await loadIframe([ - 'org.matrix.msc4039.upload_file', - ]); + await loadIframe(['org.matrix.msc4039.upload_file']); emitEvent(new CustomEvent('', { detail: event })); @@ -2340,9 +2282,7 @@ describe('ClientWidgetApi', () => { emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - supported_versions: expect.arrayContaining([ - UnstableApiVersion.MSC4039, - ]), + supported_versions: expect.arrayContaining([UnstableApiVersion.MSC4039]), }); }); }); @@ -2363,9 +2303,7 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - 'org.matrix.msc4039.upload_file', - ]); + await loadIframe(['org.matrix.msc4039.upload_file']); emitEvent(new CustomEvent('', { detail: event })); @@ -2399,9 +2337,7 @@ describe('ClientWidgetApi', () => { }); it('should reject requests when the driver throws an exception', async () => { - driver.uploadFile.mockRejectedValue( - new Error("M_LIMIT_EXCEEDED: Too many requests"), - ); + driver.uploadFile.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -2413,9 +2349,7 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - 'org.matrix.msc4039.upload_file', - ]); + await loadIframe(['org.matrix.msc4039.upload_file']); emitEvent(new CustomEvent('', { detail: event })); @@ -2430,15 +2364,10 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.uploadFile.mockRejectedValue( - new CustomMatrixError( - 'failed to upload a file', - 429, - 'M_LIMIT_EXCEEDED', - { - reason: 'Too many requests', - retry_after_ms: 2000, - }, - ), + new CustomMatrixError('failed to upload a file', 429, 'M_LIMIT_EXCEEDED', { + reason: 'Too many requests', + retry_after_ms: 2000, + }), ); const event: IUploadFileActionFromWidgetActionRequest = { @@ -2451,9 +2380,7 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - 'org.matrix.msc4039.upload_file', - ]); + await loadIframe(['org.matrix.msc4039.upload_file']); emitEvent(new CustomEvent('', { detail: event })); @@ -2494,9 +2421,7 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - 'org.matrix.msc4039.download_file', - ]); + await loadIframe(['org.matrix.msc4039.download_file']); emitEvent(new CustomEvent('', { detail: event })); @@ -2506,7 +2431,7 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.downloadFile).toHaveBeenCalledWith( 'mxc://example.com/test_file'); + expect(driver.downloadFile).toHaveBeenCalledWith('mxc://example.com/test_file'); }); it('should reject requests when the capability was not requested', async () => { @@ -2530,9 +2455,7 @@ describe('ClientWidgetApi', () => { }); it('should reject requests when the driver throws an exception', async () => { - driver.downloadFile.mockRejectedValue( - new Error("M_LIMIT_EXCEEDED: Too many requests"), - ); + driver.downloadFile.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, @@ -2544,9 +2467,7 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - 'org.matrix.msc4039.download_file', - ]); + await loadIframe(['org.matrix.msc4039.download_file']); emitEvent(new CustomEvent('', { detail: event })); @@ -2561,15 +2482,10 @@ describe('ClientWidgetApi', () => { driver.processError.mockImplementation(processCustomMatrixError); driver.downloadFile.mockRejectedValue( - new CustomMatrixError( - 'failed to download a file', - 429, - 'M_LIMIT_EXCEEDED', - { - reason: 'Too many requests', - retry_after_ms: 2000, - }, - ), + new CustomMatrixError('failed to download a file', 429, 'M_LIMIT_EXCEEDED', { + reason: 'Too many requests', + retry_after_ms: 2000, + }), ); const event: IDownloadFileActionFromWidgetActionRequest = { @@ -2582,9 +2498,7 @@ describe('ClientWidgetApi', () => { }, }; - await loadIframe([ - 'org.matrix.msc4039.download_file', - ]); + await loadIframe(['org.matrix.msc4039.download_file']); emitEvent(new CustomEvent('', { detail: event })); diff --git a/test/WidgetApi-test.ts b/test/WidgetApi-test.ts index d95dcb8..f458fb1 100644 --- a/test/WidgetApi-test.ts +++ b/test/WidgetApi-test.ts @@ -73,7 +73,7 @@ class ClientTransportHelper { public constructor(private channels: TransportChannels) {} public trackRequest(action: WidgetApiFromWidgetAction, data: IWidgetApiRequestData): void { - this.channels.requestQueue.push({action, data}); + this.channels.requestQueue.push({ action, data }); } public nextQueuedResponse(): IWidgetApiRequestData | undefined { @@ -93,13 +93,10 @@ describe('WidgetApi', () => { clientListener = (e: MessageEvent): void => { if (!e.data.action || !e.data.requestId || !e.data.widgetId) return; // invalid request/response - if ("response" in e.data || e.data.api !== WidgetApiDirection.FromWidget) return; // not a request + if ('response' in e.data || e.data.api !== WidgetApiDirection.FromWidget) return; // not a request const request = e.data; - clientTrafficHelper.trackRequest( - request.action as WidgetApiFromWidgetAction, - request.data, - ); + clientTrafficHelper.trackRequest(request.action as WidgetApiFromWidgetAction, request.data); const response = clientTrafficHelper.nextQueuedResponse(); if (response) { @@ -108,35 +105,41 @@ describe('WidgetApi', () => { ...request, response: response, } satisfies IWidgetApiResponse, - "*", + '*', ); } }; - window.addEventListener("message", clientListener); + window.addEventListener('message', clientListener); - widgetApi = new WidgetApi("WidgetApi-test", "*"); + widgetApi = new WidgetApi('WidgetApi-test', '*'); widgetApi.start(); }); afterEach(() => { - window.removeEventListener("message", clientListener); + window.removeEventListener('message', clientListener); }); describe('readEventRelations', () => { it('should forward the request to the ClientWidgetApi', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC3869] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { - chunk: [], - } as IReadRelationsFromWidgetResponseData, - ); - - await expect(widgetApi.readEventRelations( - '$event', '!room-id', 'm.reference', 'm.room.message', 25, - 'from-token', 'to-token', 'f', - )).resolves.toEqual({ + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC3869], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ + chunk: [], + } as IReadRelationsFromWidgetResponseData); + + await expect( + widgetApi.readEventRelations( + '$event', + '!room-id', + 'm.reference', + 'm.room.message', + 25, + 'from-token', + 'to-token', + 'f', + ), + ).resolves.toEqual({ chunk: [], }); @@ -157,14 +160,20 @@ describe('WidgetApi', () => { }); it('should reject the request if the api is not supported', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); - - await expect(widgetApi.readEventRelations( - '$event', '!room-id', 'm.reference', 'm.room.message', 25, - 'from-token', 'to-token', 'f', - )).rejects.toThrow("The read_relations action is not supported by the client."); + widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); + + await expect( + widgetApi.readEventRelations( + '$event', + '!room-id', + 'm.reference', + 'm.room.message', + 25, + 'from-token', + 'to-token', + 'f', + ), + ).rejects.toThrow('The read_relations action is not supported by the client.'); const request = widgetTransportHelper.nextTrackedRequest(); expect(request).not.toBeUndefined(); @@ -175,100 +184,99 @@ describe('WidgetApi', () => { }); it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC3869] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } } as IWidgetApiErrorResponseData, - ); - - await expect(widgetApi.readEventRelations( - '$event', '!room-id', 'm.reference', 'm.room.message', 25, - 'from-token', 'to-token', 'f', - )).rejects.toThrow('An error occurred'); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC3869], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ + error: { message: 'An error occurred' }, + } as IWidgetApiErrorResponseData); + + await expect( + widgetApi.readEventRelations( + '$event', + '!room-id', + 'm.reference', + 'm.room.message', + 25, + 'from-token', + 'to-token', + 'f', + ), + ).rejects.toThrow('An error occurred'); }); it('should handle an error with details', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC3869] } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC3869], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_UNKNOWN", + errcode: 'M_UNKNOWN', error: 'Unknown error', }, }, }; - widgetTransportHelper.queueResponse( - { - error: { - message: 'An error occurred', - ...errorDetails, - }, - } as IWidgetApiErrorResponseData, - ); - - await expect(widgetApi.readEventRelations( - '$event', '!room-id', 'm.reference', 'm.room.message', 25, - 'from-token', 'to-token', 'f', - )).rejects.toThrow(new WidgetApiResponseError('An error occurred', errorDetails)); + widgetTransportHelper.queueResponse({ + error: { + message: 'An error occurred', + ...errorDetails, + }, + } as IWidgetApiErrorResponseData); + + await expect( + widgetApi.readEventRelations( + '$event', + '!room-id', + 'm.reference', + 'm.room.message', + 25, + 'from-token', + 'to-token', + 'f', + ), + ).rejects.toThrow(new WidgetApiResponseError('An error occurred', errorDetails)); }); }); describe('sendEvent', () => { it('sends message events', async () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - event_id: '$event', - } as ISendEventFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + room_id: '!room-id', + event_id: '$event', + } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendRoomEvent( - 'm.room.message', - {}, - '!room-id', - )).resolves.toEqual({ + await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id')).resolves.toEqual({ room_id: '!room-id', event_id: '$event', }); }); it('sends state events', async () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - event_id: '$event', - } as ISendEventFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + room_id: '!room-id', + event_id: '$event', + } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendStateEvent( - 'm.room.topic', - "", - {}, - '!room-id', - )).resolves.toEqual({ + await expect(widgetApi.sendStateEvent('m.room.topic', '', {}, '!room-id')).resolves.toEqual({ room_id: '!room-id', event_id: '$event', }); }); it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { message: 'An error occurred' }, + } as IWidgetApiErrorResponseData); - await expect(widgetApi.sendRoomEvent( - 'm.room.message', - {}, - '!room-id', - )).rejects.toThrow('An error occurred'); + await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id')).rejects.toThrow( + 'An error occurred', + ); }); it('should handle an error with details', async () => { @@ -276,124 +284,86 @@ describe('WidgetApi', () => { matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_UNKNOWN", + errcode: 'M_UNKNOWN', error: 'Unknown error', }, }, }; - widgetTransportHelper.queueResponse( - { - error: { - message: 'An error occurred', - ...errorDetails, - }, - } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { + message: 'An error occurred', + ...errorDetails, + }, + } as IWidgetApiErrorResponseData); - await expect(widgetApi.sendRoomEvent( - 'm.room.message', - {}, - '!room-id', - )).rejects.toThrow(new WidgetApiResponseError('An error occurred', errorDetails)); + await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id')).rejects.toThrow( + new WidgetApiResponseError('An error occurred', errorDetails), + ); }); }); describe('delayed sendEvent', () => { it('sends delayed message events', async () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - delay_id: 'id', - } as ISendEventFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + room_id: '!room-id', + delay_id: 'id', + } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendRoomEvent( - 'm.room.message', - {}, - '!room-id', - 2000, - )).resolves.toEqual({ + await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id', 2000)).resolves.toEqual({ room_id: '!room-id', delay_id: 'id', }); }); it('sends delayed state events', async () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - delay_id: 'id', - } as ISendEventFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + room_id: '!room-id', + delay_id: 'id', + } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendStateEvent( - 'm.room.topic', - "", - {}, - '!room-id', - 2000, - )).resolves.toEqual({ + await expect(widgetApi.sendStateEvent('m.room.topic', '', {}, '!room-id', 2000)).resolves.toEqual({ room_id: '!room-id', delay_id: 'id', }); }); it('sends delayed child action message events', async () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - delay_id: 'id', - } as ISendEventFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + room_id: '!room-id', + delay_id: 'id', + } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendRoomEvent( - 'm.room.message', - {}, - '!room-id', - 1000, - undefined, - )).resolves.toEqual({ + await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id', 1000, undefined)).resolves.toEqual({ room_id: '!room-id', delay_id: 'id', }); }); it('sends delayed child action state events', async () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - delay_id: 'id', - } as ISendEventFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + room_id: '!room-id', + delay_id: 'id', + } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendStateEvent( - 'm.room.topic', - "", - {}, - '!room-id', - 1000, - undefined, - )).resolves.toEqual({ + await expect( + widgetApi.sendStateEvent('m.room.topic', '', {}, '!room-id', 1000, undefined), + ).resolves.toEqual({ room_id: '!room-id', delay_id: 'id', }); }); it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { message: 'An error occurred' }, + } as IWidgetApiErrorResponseData); - await expect(widgetApi.sendRoomEvent( - 'm.room.message', - {}, - '!room-id', - 1000, - undefined, - )).rejects.toThrow('An error occurred'); + await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id', 1000, undefined)).rejects.toThrow( + 'An error occurred', + ); }); it('should handle an error with details', async () => { @@ -401,51 +371,41 @@ describe('WidgetApi', () => { matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_UNKNOWN", + errcode: 'M_UNKNOWN', error: 'Unknown error', }, }, }; - widgetTransportHelper.queueResponse( - { - error: { - message: 'An error occurred', - ...errorDetails, - }, - } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { + message: 'An error occurred', + ...errorDetails, + }, + } as IWidgetApiErrorResponseData); - await expect(widgetApi.sendRoomEvent( - 'm.room.message', - {}, - '!room-id', - 1000, - undefined, - )).rejects.toThrow(new WidgetApiResponseError('An error occurred', errorDetails)); + await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id', 1000, undefined)).rejects.toThrow( + new WidgetApiResponseError('An error occurred', errorDetails), + ); }); }); describe('updateDelayedEvent', () => { it('updates delayed events', async () => { widgetTransportHelper.queueResponse({}); - await expect(widgetApi.updateDelayedEvent( - 'id', - UpdateDelayedEventAction.Send, - )).resolves.toEqual({}); + await expect(widgetApi.updateDelayedEvent('id', UpdateDelayedEventAction.Send)).resolves.toEqual({}); }); it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { message: 'An error occurred' }, + } as IWidgetApiErrorResponseData); - await expect(widgetApi.updateDelayedEvent( - 'id', - UpdateDelayedEventAction.Send, - )).rejects.toThrow('An error occurred'); + await expect(widgetApi.updateDelayedEvent('id', UpdateDelayedEventAction.Send)).rejects.toThrow( + 'An error occurred', + ); }); it('should handle an error with details', async () => { @@ -453,55 +413,42 @@ describe('WidgetApi', () => { matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_UNKNOWN", + errcode: 'M_UNKNOWN', error: 'Unknown error', }, }, }; - widgetTransportHelper.queueResponse( - { - error: { - message: 'An error occurred', - ...errorDetails, - }, - } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { + message: 'An error occurred', + ...errorDetails, + }, + } as IWidgetApiErrorResponseData); - await expect(widgetApi.updateDelayedEvent( - 'id', - UpdateDelayedEventAction.Send, - )).rejects.toThrow(new WidgetApiResponseError('An error occurred', errorDetails)); + await expect(widgetApi.updateDelayedEvent('id', UpdateDelayedEventAction.Send)).rejects.toThrow( + new WidgetApiResponseError('An error occurred', errorDetails), + ); }); }); describe('getClientVersions', () => { beforeEach(() => { - widgetTransportHelper.queueResponse( - { - supported_versions: [ - UnstableApiVersion.MSC3869, UnstableApiVersion.MSC2762, - ], - } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC3869, UnstableApiVersion.MSC2762], + } as ISupportedVersionsActionResponseData); }); it('should request supported client versions', async () => { - await expect(widgetApi.getClientVersions()).resolves.toEqual([ - 'org.matrix.msc3869', 'org.matrix.msc2762', - ]); + await expect(widgetApi.getClientVersions()).resolves.toEqual(['org.matrix.msc3869', 'org.matrix.msc2762']); }); it('should cache supported client versions on successive calls', async () => { - await expect(widgetApi.getClientVersions()).resolves.toEqual([ - 'org.matrix.msc3869', 'org.matrix.msc2762', - ]); + await expect(widgetApi.getClientVersions()).resolves.toEqual(['org.matrix.msc3869', 'org.matrix.msc2762']); - await expect(widgetApi.getClientVersions()).resolves.toEqual([ - 'org.matrix.msc3869', 'org.matrix.msc2762', - ]); + await expect(widgetApi.getClientVersions()).resolves.toEqual(['org.matrix.msc3869', 'org.matrix.msc2762']); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); expect(widgetTransportHelper.nextTrackedRequest()).toBeUndefined(); @@ -510,19 +457,15 @@ describe('WidgetApi', () => { describe('searchUserDirectory', () => { it('should forward the request to the ClientWidgetApi', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC3973] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { - limited: false, - results: [], - } as IUserDirectorySearchFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC3973], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ + limited: false, + results: [], + } as IUserDirectorySearchFromWidgetResponseData); - await expect(widgetApi.searchUserDirectory( - 'foo', 10, - )).resolves.toEqual({ + await expect(widgetApi.searchUserDirectory('foo', 10)).resolves.toEqual({ limited: false, results: [], }); @@ -538,13 +481,11 @@ describe('WidgetApi', () => { }); it('should reject the request if the api is not supported', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); - await expect(widgetApi.searchUserDirectory( - 'foo', 10, - )).rejects.toThrow("The user_directory_search action is not supported by the client."); + await expect(widgetApi.searchUserDirectory('foo', 10)).rejects.toThrow( + 'The user_directory_search action is not supported by the client.', + ); const request = widgetTransportHelper.nextTrackedRequest(); expect(request).not.toBeUndefined(); @@ -555,58 +496,52 @@ describe('WidgetApi', () => { }); it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC3973] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } }, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC3973], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ error: { message: 'An error occurred' } }); - await expect(widgetApi.searchUserDirectory( - 'foo', 10, - )).rejects.toThrow('An error occurred'); + await expect(widgetApi.searchUserDirectory('foo', 10)).rejects.toThrow('An error occurred'); }); it('should handle an error with details', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC3973] } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC3973], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_UNKNOWN", + errcode: 'M_UNKNOWN', error: 'Unknown error', }, }, }; - widgetTransportHelper.queueResponse( - { - error: { - message: 'An error occurred', - ...errorDetails, - }, - } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { + message: 'An error occurred', + ...errorDetails, + }, + } as IWidgetApiErrorResponseData); - await expect(widgetApi.searchUserDirectory( - 'foo', 10, - )).rejects.toThrow(new WidgetApiResponseError('An error occurred', errorDetails)); + await expect(widgetApi.searchUserDirectory('foo', 10)).rejects.toThrow( + new WidgetApiResponseError('An error occurred', errorDetails), + ); }); }); describe('getMediaConfig', () => { it('should forward the request to the ClientWidgetApi', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { 'm.upload.size': 1000 } as IGetMediaConfigActionFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ + 'm.upload.size': 1000, + } as IGetMediaConfigActionFromWidgetResponseData); await expect(widgetApi.getMediaConfig()).resolves.toEqual({ 'm.upload.size': 1000, @@ -620,12 +555,10 @@ describe('WidgetApi', () => { }); it('should reject the request if the api is not supported', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); await expect(widgetApi.getMediaConfig()).rejects.toThrow( - "The get_media_config action is not supported by the client.", + 'The get_media_config action is not supported by the client.', ); const request = widgetTransportHelper.nextTrackedRequest(); @@ -637,43 +570,37 @@ describe('WidgetApi', () => { }); it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } }, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ error: { message: 'An error occurred' } }); - await expect(widgetApi.getMediaConfig()).rejects.toThrow( - 'An error occurred', - ); + await expect(widgetApi.getMediaConfig()).rejects.toThrow('An error occurred'); }); it('should handle an error with details', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_UNKNOWN", + errcode: 'M_UNKNOWN', error: 'Unknown error', }, }, }; - widgetTransportHelper.queueResponse( - { - error: { - message: 'An error occurred', - ...errorDetails, - }, - } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { + message: 'An error occurred', + ...errorDetails, + }, + } as IWidgetApiErrorResponseData); await expect(widgetApi.getMediaConfig()).rejects.toThrow( new WidgetApiResponseError('An error occurred', errorDetails), @@ -683,31 +610,29 @@ describe('WidgetApi', () => { describe('uploadFile', () => { it('should forward the request to the ClientWidgetApi', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { content_uri: 'mxc://...' } as IUploadFileActionFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ + content_uri: 'mxc://...', + } as IUploadFileActionFromWidgetResponseData); - await expect(widgetApi.uploadFile("data")).resolves.toEqual({ + await expect(widgetApi.uploadFile('data')).resolves.toEqual({ content_uri: 'mxc://...', }); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); expect(widgetTransportHelper.nextTrackedRequest()).toEqual({ action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, - data: { file: "data" }, + data: { file: 'data' }, } satisfies SendRequestArgs); }); it('should reject the request if the api is not supported', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); - await expect(widgetApi.uploadFile("data")).rejects.toThrow( - "The upload_file action is not supported by the client.", + await expect(widgetApi.uploadFile('data')).rejects.toThrow( + 'The upload_file action is not supported by the client.', ); const request = widgetTransportHelper.nextTrackedRequest(); @@ -719,45 +644,39 @@ describe('WidgetApi', () => { }); it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } }, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ error: { message: 'An error occurred' } }); - await expect(widgetApi.uploadFile("data")).rejects.toThrow( - 'An error occurred', - ); + await expect(widgetApi.uploadFile('data')).rejects.toThrow('An error occurred'); }); it('should handle an error with details', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_UNKNOWN", + errcode: 'M_UNKNOWN', error: 'Unknown error', }, }, }; - widgetTransportHelper.queueResponse( - { - error: { - message: 'An error occurred', - ...errorDetails, - }, - } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { + message: 'An error occurred', + ...errorDetails, + }, + } as IWidgetApiErrorResponseData); - await expect(widgetApi.uploadFile("data")).rejects.toThrow( + await expect(widgetApi.uploadFile('data')).rejects.toThrow( new WidgetApiResponseError('An error occurred', errorDetails), ); }); @@ -765,31 +684,27 @@ describe('WidgetApi', () => { describe('downloadFile', () => { it('should forward the request to the ClientWidgetApi', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { file: 'test contents' } as IDownloadFileActionFromWidgetResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ file: 'test contents' } as IDownloadFileActionFromWidgetResponseData); - await expect(widgetApi.downloadFile("mxc://example.com/test_file")).resolves.toEqual({ + await expect(widgetApi.downloadFile('mxc://example.com/test_file')).resolves.toEqual({ file: 'test contents', }); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); expect(widgetTransportHelper.nextTrackedRequest()).toEqual({ action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, - data: { content_uri: "mxc://example.com/test_file" }, + data: { content_uri: 'mxc://example.com/test_file' }, } satisfies SendRequestArgs); }); it('should reject the request if the api is not supported', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); - await expect(widgetApi.downloadFile("mxc://example.com/test_file")).rejects.toThrow( - "The download_file action is not supported by the client.", + await expect(widgetApi.downloadFile('mxc://example.com/test_file')).rejects.toThrow( + 'The download_file action is not supported by the client.', ); const request = widgetTransportHelper.nextTrackedRequest(); @@ -801,45 +716,39 @@ describe('WidgetApi', () => { }); it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } }, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); + widgetTransportHelper.queueResponse({ error: { message: 'An error occurred' } }); - await expect(widgetApi.downloadFile("mxc://example.com/test_file")).rejects.toThrow( - 'An error occurred', - ); + await expect(widgetApi.downloadFile('mxc://example.com/test_file')).rejects.toThrow('An error occurred'); }); it('should handle an error with details', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_UNKNOWN", + errcode: 'M_UNKNOWN', error: 'Unknown error', }, }, }; - widgetTransportHelper.queueResponse( - { - error: { - message: 'An error occurred', - ...errorDetails, - }, - } as IWidgetApiErrorResponseData, - ); + widgetTransportHelper.queueResponse({ + error: { + message: 'An error occurred', + ...errorDetails, + }, + } as IWidgetApiErrorResponseData); - await expect(widgetApi.downloadFile("mxc://example.com/test_file")).rejects.toThrow( + await expect(widgetApi.downloadFile('mxc://example.com/test_file')).rejects.toThrow( new WidgetApiResponseError('An error occurred', errorDetails), ); }); diff --git a/test/url-template-test.ts b/test/url-template-test.ts index b1db0fe..cb4bafa 100644 --- a/test/url-template-test.ts +++ b/test/url-template-test.ts @@ -14,34 +14,34 @@ * limitations under the License. */ -import { runTemplate } from "../src"; +import { runTemplate } from '../src'; -describe("runTemplate", () => { - it("should replace device id template in url", () => { - const url = "https://localhost/?my-query#device_id=$org.matrix.msc3819.matrix_device_id"; +describe('runTemplate', () => { + it('should replace device id template in url', () => { + const url = 'https://localhost/?my-query#device_id=$org.matrix.msc3819.matrix_device_id'; const replacedUrl = runTemplate( url, { - id: "widget-id", + id: 'widget-id', creatorUserId: '@user-id', type: 'type', url, }, { - deviceId: "my-device-id", + deviceId: 'my-device-id', currentUserId: '@user-id', }, ); - expect(replacedUrl).toBe("https://localhost/?my-query#device_id=my-device-id"); + expect(replacedUrl).toBe('https://localhost/?my-query#device_id=my-device-id'); }); - it("should replace base url template in url", () => { - const url = "https://localhost/?my-query#base_url=$org.matrix.msc4039.matrix_base_url"; + it('should replace base url template in url', () => { + const url = 'https://localhost/?my-query#base_url=$org.matrix.msc4039.matrix_base_url'; const replacedUrl = runTemplate( url, { - id: "widget-id", + id: 'widget-id', creatorUserId: '@user-id', type: 'type', url, @@ -52,6 +52,6 @@ describe("runTemplate", () => { }, ); - expect(replacedUrl).toBe("https://localhost/?my-query#base_url=https%3A%2F%2Flocalhost%2Fapi"); + expect(replacedUrl).toBe('https://localhost/?my-query#base_url=https%3A%2F%2Flocalhost%2Fapi'); }); }); diff --git a/tsconfig-dev.json b/tsconfig-dev.json index 4ca2a2a..5ef424e 100644 --- a/tsconfig-dev.json +++ b/tsconfig-dev.json @@ -1,6 +1,4 @@ { "extends": "./tsconfig.json", - "include": [ - "./test/**/*.ts" - ] + "include": ["./test/**/*.ts"] } diff --git a/tsconfig.json b/tsconfig.json index 5abd7b9..f58ceb1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,13 +10,8 @@ "outDir": "./lib", "declaration": true, "types": ["jest"], - "lib": [ - "es2020", - "dom" - ], + "lib": ["es2020", "dom"], "strict": true }, - "include": [ - "./src/**/*.ts" - ] + "include": ["./src/**/*.ts"] } diff --git a/yarn.lock b/yarn.lock index 2ac0950..6c49d1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5849,6 +5849,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== +prettier@3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" + integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== + pretty-format@^27.0.2: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" From bb93831571330231e276804c772358dc2509b186 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 17:19:49 +0100 Subject: [PATCH 04/12] use double quotes in tests as well. --- package.json | 8 - test/ClientWidgetApi-test.ts | 1338 +++++++++++++++++----------------- test/WidgetApi-test.ts | 430 +++++------ test/url-template-test.ts | 36 +- 4 files changed, 902 insertions(+), 910 deletions(-) diff --git a/package.json b/package.json index 4851ef9..3e4a189 100644 --- a/package.json +++ b/package.json @@ -37,14 +37,6 @@ "options": { "tabWidth": 4 } - }, - { - "files": [ - "test/**/*.ts" - ], - "options": { - "singleQuote": true - } } ] }, diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index 15bec9e..0a261f2 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -15,21 +15,21 @@ * limitations under the License. */ -import { waitFor } from '@testing-library/dom'; - -import { ClientWidgetApi } from '../src/ClientWidgetApi'; -import { WidgetDriver } from '../src/driver/WidgetDriver'; -import { UnstableApiVersion } from '../src/interfaces/ApiVersion'; -import { Capability } from '../src/interfaces/Capabilities'; -import { IRoomEvent } from '../src/interfaces/IRoomEvent'; -import { IWidgetApiRequest } from '../src/interfaces/IWidgetApiRequest'; -import { IReadRelationsFromWidgetActionRequest } from '../src/interfaces/ReadRelationsAction'; -import { ISupportedVersionsActionRequest } from '../src/interfaces/SupportedVersionsAction'; -import { IUserDirectorySearchFromWidgetActionRequest } from '../src/interfaces/UserDirectorySearchAction'; -import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from '../src/interfaces/WidgetApiAction'; -import { WidgetApiDirection } from '../src/interfaces/WidgetApiDirection'; -import { Widget } from '../src/models/Widget'; -import { PostmessageTransport } from '../src/transport/PostmessageTransport'; +import { waitFor } from "@testing-library/dom"; + +import { ClientWidgetApi } from "../src/ClientWidgetApi"; +import { WidgetDriver } from "../src/driver/WidgetDriver"; +import { UnstableApiVersion } from "../src/interfaces/ApiVersion"; +import { Capability } from "../src/interfaces/Capabilities"; +import { IRoomEvent } from "../src/interfaces/IRoomEvent"; +import { IWidgetApiRequest } from "../src/interfaces/IWidgetApiRequest"; +import { IReadRelationsFromWidgetActionRequest } from "../src/interfaces/ReadRelationsAction"; +import { ISupportedVersionsActionRequest } from "../src/interfaces/SupportedVersionsAction"; +import { IUserDirectorySearchFromWidgetActionRequest } from "../src/interfaces/UserDirectorySearchAction"; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "../src/interfaces/WidgetApiAction"; +import { WidgetApiDirection } from "../src/interfaces/WidgetApiDirection"; +import { Widget } from "../src/models/Widget"; +import { PostmessageTransport } from "../src/transport/PostmessageTransport"; import { IDownloadFileActionFromWidgetActionRequest, IGetOpenIDActionRequest, @@ -45,11 +45,11 @@ import { SimpleObservable, Symbols, UpdateDelayedEventAction, -} from '../src'; -import { IGetMediaConfigActionFromWidgetActionRequest } from '../src/interfaces/GetMediaConfigAction'; -import { IReadRoomAccountDataFromWidgetActionRequest } from '../src/interfaces/ReadRoomAccountDataAction'; +} from "../src"; +import { IGetMediaConfigActionFromWidgetActionRequest } from "../src/interfaces/GetMediaConfigAction"; +import { IReadRoomAccountDataFromWidgetActionRequest } from "../src/interfaces/ReadRoomAccountDataAction"; -jest.mock('../src/transport/PostmessageTransport'); +jest.mock("../src/transport/PostmessageTransport"); afterEach(() => { jest.resetAllMocks(); @@ -57,12 +57,12 @@ afterEach(() => { function createRoomEvent(event: Partial = {}): IRoomEvent { return { - type: 'm.room.message', - sender: 'user-id', + type: "m.room.message", + sender: "user-id", content: {}, origin_server_ts: 0, - event_id: 'id-0', - room_id: '!room-id', + event_id: "id-0", + room_id: "!room-id", unsigned: {}, ...event, }; @@ -85,7 +85,7 @@ function processCustomMatrixError(e: unknown): IWidgetApiErrorResponseDataDetail matrix_api_error: { http_status: e.httpStatus, http_headers: {}, - url: '', + url: "", response: { errcode: e.name, error: e.message, @@ -96,29 +96,29 @@ function processCustomMatrixError(e: unknown): IWidgetApiErrorResponseDataDetail : undefined; } -describe('ClientWidgetApi', () => { +describe("ClientWidgetApi", () => { let capabilities: Capability[]; let iframe: HTMLIFrameElement; let driver: jest.Mocked; let clientWidgetApi: ClientWidgetApi; let transport: PostmessageTransport; - let emitEvent: Parameters['1']; + let emitEvent: Parameters["1"]; async function loadIframe(caps: Capability[] = []): Promise { capabilities = caps; const ready = new Promise((resolve) => { - clientWidgetApi.once('ready', resolve); + clientWidgetApi.once("ready", resolve); }); - iframe.dispatchEvent(new Event('load')); + iframe.dispatchEvent(new Event("load")); await ready; } beforeEach(() => { capabilities = []; - iframe = document.createElement('iframe'); + iframe = document.createElement("iframe"); document.body.appendChild(iframe); driver = { @@ -143,10 +143,10 @@ describe('ClientWidgetApi', () => { clientWidgetApi = new ClientWidgetApi( new Widget({ - id: 'test', - creatorUserId: '@alice:example.org', - type: 'example', - url: 'https://example.org', + id: "test", + creatorUserId: "@alice:example.org", + type: "example", + url: "https://example.org", }), iframe, driver, @@ -164,30 +164,30 @@ describe('ClientWidgetApi', () => { iframe.remove(); }); - it('should initiate capabilities', async () => { - await loadIframe(['m.always_on_screen']); + it("should initiate capabilities", async () => { + await loadIframe(["m.always_on_screen"]); - expect(clientWidgetApi.hasCapability('m.always_on_screen')).toBe(true); - expect(clientWidgetApi.hasCapability('m.sticker')).toBe(false); + expect(clientWidgetApi.hasCapability("m.always_on_screen")).toBe(true); + expect(clientWidgetApi.hasCapability("m.sticker")).toBe(false); }); - describe('navigate action', () => { - it('navigates', async () => { + describe("navigate action", () => { + it("navigates", async () => { driver.navigate.mockResolvedValue(Promise.resolve()); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://matrix.to/#/#room:example.net', + uri: "https://matrix.to/#/#room:example.net", }, }; - await loadIframe(['org.matrix.msc2931.navigate']); + await loadIframe(["org.matrix.msc2931.navigate"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, {}); @@ -196,113 +196,113 @@ describe('ClientWidgetApi', () => { expect(driver.navigate).toHaveBeenCalledWith(event.data.uri); }); - it('fails to navigate', async () => { + it("fails to navigate", async () => { const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://matrix.to/#/#room:example.net', + uri: "https://matrix.to/#/#room:example.net", }, }; await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); }); expect(driver.navigate).not.toBeCalled(); }); - it('fails to navigate to an unsupported URI', async () => { + it("fails to navigate to an unsupported URI", async () => { const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://example.net', + uri: "https://example.net", }, }; - await loadIframe(['org.matrix.msc2931.navigate']); + await loadIframe(["org.matrix.msc2931.navigate"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid matrix.to URI' }, + error: { message: "Invalid matrix.to URI" }, }); }); expect(driver.navigate).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.navigate.mockRejectedValue(new Error('M_UNKNOWN: Unknown error')); + it("should reject requests when the driver throws an exception", async () => { + driver.navigate.mockRejectedValue(new Error("M_UNKNOWN: Unknown error")); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://matrix.to/#/#room:example.net', + uri: "https://matrix.to/#/#room:example.net", }, }; - await loadIframe(['org.matrix.msc2931.navigate']); + await loadIframe(["org.matrix.msc2931.navigate"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error handling navigation' }, + error: { message: "Error handling navigation" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.navigate.mockRejectedValue( - new CustomMatrixError('failed to navigate', 400, 'M_UNKNOWN', { - reason: 'Unknown error', + new CustomMatrixError("failed to navigate", 400, "M_UNKNOWN", { + reason: "Unknown error", }), ); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://matrix.to/#/#room:example.net', + uri: "https://matrix.to/#/#room:example.net", }, }; - await loadIframe(['org.matrix.msc2931.navigate']); + await loadIframe(["org.matrix.msc2931.navigate"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error handling navigation', + message: "Error handling navigation", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'failed to navigate', - reason: 'Unknown error', + errcode: "M_UNKNOWN", + error: "failed to navigate", + reason: "Unknown error", }, } satisfies IMatrixApiError, }, @@ -311,10 +311,10 @@ describe('ClientWidgetApi', () => { }); }); - describe('send_event action', () => { - it('sends message events', async () => { - const roomId = '!room:example.org'; - const eventId = '$event:example.org'; + describe("send_event action", () => { + it("sends message events", async () => { + const roomId = "!room:example.org"; + const eventId = "$event:example.org"; driver.sendEvent.mockResolvedValue({ roomId, @@ -323,11 +323,11 @@ describe('ClientWidgetApi', () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', + type: "m.room.message", content: {}, room_id: roomId, }, @@ -338,7 +338,7 @@ describe('ClientWidgetApi', () => { `org.matrix.msc2762.send.event:${event.data.type}`, ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -350,9 +350,9 @@ describe('ClientWidgetApi', () => { expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, null, roomId); }); - it('sends state events', async () => { - const roomId = '!room:example.org'; - const eventId = '$event:example.org'; + it("sends state events", async () => { + const roomId = "!room:example.org"; + const eventId = "$event:example.org"; driver.sendEvent.mockResolvedValue({ roomId, @@ -361,13 +361,13 @@ describe('ClientWidgetApi', () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.topic', + type: "m.room.topic", content: {}, - state_key: '', + state_key: "", room_id: roomId, }, }; @@ -377,7 +377,7 @@ describe('ClientWidgetApi', () => { `org.matrix.msc2762.send.state_event:${event.data.type}`, ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -386,22 +386,22 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, '', roomId); + expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, "", roomId); }); - it('should reject requests when the driver throws an exception', async () => { - const roomId = '!room:example.org'; + it("should reject requests when the driver throws an exception", async () => { + const roomId = "!room:example.org"; - driver.sendEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); + driver.sendEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object")); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', - content: 'hello', + type: "m.room.message", + content: "hello", room_id: roomId, }, }; @@ -411,34 +411,34 @@ describe('ClientWidgetApi', () => { `org.matrix.msc2762.send.event:${event.data.type}`, ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error sending event' }, + error: { message: "Error sending event" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { - const roomId = '!room:example.org'; + it("should reject with Matrix API error response thrown by driver", async () => { + const roomId = "!room:example.org"; driver.processError.mockImplementation(processCustomMatrixError); driver.sendEvent.mockRejectedValue( - new CustomMatrixError('failed to send event', 400, 'M_NOT_JSON', { - reason: 'Content must be a JSON object.', + new CustomMatrixError("failed to send event", 400, "M_NOT_JSON", { + reason: "Content must be a JSON object.", }), ); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', - content: 'hello', + type: "m.room.message", + content: "hello", room_id: roomId, }, }; @@ -448,20 +448,20 @@ describe('ClientWidgetApi', () => { `org.matrix.msc2762.send.event:${event.data.type}`, ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error sending event', + message: "Error sending event", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_NOT_JSON', - error: 'failed to send event', - reason: 'Content must be a JSON object.', + errcode: "M_NOT_JSON", + error: "failed to send event", + reason: "Content must be a JSON object.", }, } satisfies IMatrixApiError, }, @@ -470,17 +470,17 @@ describe('ClientWidgetApi', () => { }); }); - describe('send_event action for delayed events', () => { - it('fails to send delayed events', async () => { - const roomId = '!room:example.org'; + describe("send_event action for delayed events", () => { + it("fails to send delayed events", async () => { + const roomId = "!room:example.org"; const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', + type: "m.room.message", content: {}, delay: 5000, room_id: roomId, @@ -493,7 +493,7 @@ describe('ClientWidgetApi', () => { // Without the required capability ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -504,10 +504,10 @@ describe('ClientWidgetApi', () => { expect(driver.sendDelayedEvent).not.toBeCalled(); }); - it('sends delayed message events', async () => { - const roomId = '!room:example.org'; - const parentDelayId = 'fp'; - const timeoutDelayId = 'ft'; + it("sends delayed message events", async () => { + const roomId = "!room:example.org"; + const parentDelayId = "fp"; + const timeoutDelayId = "ft"; driver.sendDelayedEvent.mockResolvedValue({ roomId, @@ -516,11 +516,11 @@ describe('ClientWidgetApi', () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', + type: "m.room.message", content: {}, room_id: roomId, delay: 5000, @@ -531,10 +531,10 @@ describe('ClientWidgetApi', () => { await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, - 'org.matrix.msc4157.send.delayed_event', + "org.matrix.msc4157.send.delayed_event", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -553,10 +553,10 @@ describe('ClientWidgetApi', () => { ); }); - it('sends delayed state events', async () => { - const roomId = '!room:example.org'; - const parentDelayId = 'fp'; - const timeoutDelayId = 'ft'; + it("sends delayed state events", async () => { + const roomId = "!room:example.org"; + const parentDelayId = "fp"; + const timeoutDelayId = "ft"; driver.sendDelayedEvent.mockResolvedValue({ roomId, @@ -565,13 +565,13 @@ describe('ClientWidgetApi', () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.topic', + type: "m.room.topic", content: {}, - state_key: '', + state_key: "", room_id: roomId, delay: 5000, parent_delay_id: parentDelayId, @@ -581,10 +581,10 @@ describe('ClientWidgetApi', () => { await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.state_event:${event.data.type}`, - 'org.matrix.msc4157.send.delayed_event', + "org.matrix.msc4157.send.delayed_event", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -598,90 +598,90 @@ describe('ClientWidgetApi', () => { event.data.parent_delay_id, event.data.type, event.data.content, - '', + "", roomId, ); }); - it('should reject requests when the driver throws an exception', async () => { - const roomId = '!room:example.org'; + it("should reject requests when the driver throws an exception", async () => { + const roomId = "!room:example.org"; - driver.sendDelayedEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); + driver.sendDelayedEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object")); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', - content: 'hello', + type: "m.room.message", + content: "hello", room_id: roomId, delay: 5000, - parent_delay_id: 'fp', + parent_delay_id: "fp", }, }; await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, - 'org.matrix.msc4157.send.delayed_event', + "org.matrix.msc4157.send.delayed_event", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error sending event' }, + error: { message: "Error sending event" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { - const roomId = '!room:example.org'; + it("should reject with Matrix API error response thrown by driver", async () => { + const roomId = "!room:example.org"; driver.processError.mockImplementation(processCustomMatrixError); driver.sendDelayedEvent.mockRejectedValue( - new CustomMatrixError('failed to send event', 400, 'M_NOT_JSON', { - reason: 'Content must be a JSON object.', + new CustomMatrixError("failed to send event", 400, "M_NOT_JSON", { + reason: "Content must be a JSON object.", }), ); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', - content: 'hello', + type: "m.room.message", + content: "hello", room_id: roomId, delay: 5000, - parent_delay_id: 'fp', + parent_delay_id: "fp", }, }; await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, - 'org.matrix.msc4157.send.delayed_event', + "org.matrix.msc4157.send.delayed_event", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error sending event', + message: "Error sending event", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_NOT_JSON', - error: 'failed to send event', - reason: 'Content must be a JSON object.', + errcode: "M_NOT_JSON", + error: "failed to send event", + reason: "Content must be a JSON object.", }, } satisfies IMatrixApiError, }, @@ -690,21 +690,21 @@ describe('ClientWidgetApi', () => { }); }); - describe('receiving events', () => { - const roomId = '!room:example.org'; - const otherRoomId = '!other-room:example.org'; - const event = createRoomEvent({ room_id: roomId, type: 'm.room.message', content: 'hello' }); + describe("receiving events", () => { + const roomId = "!room:example.org"; + const otherRoomId = "!other-room:example.org"; + const event = createRoomEvent({ room_id: roomId, type: "m.room.message", content: "hello" }); const eventFromOtherRoom = createRoomEvent({ room_id: otherRoomId, - type: 'm.room.message', - content: 'test', + type: "m.room.message", + content: "test", }); - it('forwards events to the widget from one room only', async () => { + it("forwards events to the widget from one room only", async () => { // Give the widget capabilities to receive from just one room await loadIframe([ `org.matrix.msc2762.timeline:${roomId}`, - 'org.matrix.msc2762.receive.event:m.room.message', + "org.matrix.msc2762.receive.event:m.room.message", ]); // Event from the matching room should be forwarded @@ -716,13 +716,13 @@ describe('ClientWidgetApi', () => { expect(transport.send).not.toHaveBeenCalledWith(WidgetApiToWidgetAction.SendEvent, eventFromOtherRoom); }); - it('forwards events to the widget from the currently viewed room', async () => { + it("forwards events to the widget from the currently viewed room", async () => { clientWidgetApi.setViewedRoomId(roomId); // Give the widget capabilities to receive events without specifying // any rooms that it can read await loadIframe([ `org.matrix.msc2762.timeline:${roomId}`, - 'org.matrix.msc2762.receive.event:m.room.message', + "org.matrix.msc2762.receive.event:m.room.message", ]); // Event from the viewed room should be forwarded @@ -739,11 +739,11 @@ describe('ClientWidgetApi', () => { expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.SendEvent, eventFromOtherRoom); }); - it('forwards events to the widget from all rooms', async () => { + it("forwards events to the widget from all rooms", async () => { // Give the widget capabilities to receive from any known room await loadIframe([ `org.matrix.msc2762.timeline:${Symbols.AnyRoom}`, - 'org.matrix.msc2762.receive.event:m.room.message', + "org.matrix.msc2762.receive.event:m.room.message", ]); // Events from both rooms should be forwarded @@ -754,34 +754,34 @@ describe('ClientWidgetApi', () => { }); }); - describe('receiving room state', () => { - it('syncs initial state and feeds updates', async () => { - const roomId = '!room:example.org'; - const otherRoomId = '!other-room:example.org'; + describe("receiving room state", () => { + it("syncs initial state and feeds updates", async () => { + const roomId = "!room:example.org"; + const otherRoomId = "!other-room:example.org"; clientWidgetApi.setViewedRoomId(roomId); const topicEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.topic', - state_key: '', - content: { topic: 'Hello world!' }, + type: "m.room.topic", + state_key: "", + content: { topic: "Hello world!" }, }); const nameEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.name', - state_key: '', - content: { name: 'Test room' }, + type: "m.room.name", + state_key: "", + content: { name: "Test room" }, }); const joinRulesEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.join_rules', - state_key: '', - content: { join_rule: 'public' }, + type: "m.room.join_rules", + state_key: "", + content: { join_rule: "public" }, }); const otherRoomNameEvent = createRoomEvent({ room_id: otherRoomId, - type: 'm.room.name', - state_key: '', - content: { name: 'Other room' }, + type: "m.room.name", + state_key: "", + content: { name: "Other room" }, }); // Artificially delay the delivery of the join rules event @@ -790,31 +790,31 @@ describe('ClientWidgetApi', () => { driver.readRoomState.mockImplementation(async (rId, eventType, stateKey) => { if (rId === roomId) { - if (eventType === 'm.room.topic' && stateKey === '') return [topicEvent]; - if (eventType === 'm.room.name' && stateKey === '') return [nameEvent]; - if (eventType === 'm.room.join_rules' && stateKey === '') { + if (eventType === "m.room.topic" && stateKey === "") return [topicEvent]; + if (eventType === "m.room.name" && stateKey === "") return [nameEvent]; + if (eventType === "m.room.join_rules" && stateKey === "") { await joinRules; return [joinRulesEvent]; } } else if (rId === otherRoomId) { - if (eventType === 'm.room.name' && stateKey === '') return [otherRoomNameEvent]; + if (eventType === "m.room.name" && stateKey === "") return [otherRoomNameEvent]; } return []; }); await loadIframe([ - 'org.matrix.msc2762.receive.state_event:m.room.topic#', - 'org.matrix.msc2762.receive.state_event:m.room.name#', - 'org.matrix.msc2762.receive.state_event:m.room.join_rules#', + "org.matrix.msc2762.receive.state_event:m.room.topic#", + "org.matrix.msc2762.receive.state_event:m.room.name#", + "org.matrix.msc2762.receive.state_event:m.room.join_rules#", ]); // Simulate a race between reading the original join rules event and // the join rules being updated at the same time const newJoinRulesEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.join_rules', - state_key: '', - content: { join_rule: 'invite' }, + type: "m.room.join_rules", + state_key: "", + content: { join_rule: "invite" }, }); clientWidgetApi.feedStateUpdate(newJoinRulesEvent); // What happens if the original join rules are delivered after the @@ -836,9 +836,9 @@ describe('ClientWidgetApi', () => { // as expected const newTopicEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.topic', - state_key: '', - content: { topic: 'Our new topic' }, + type: "m.room.topic", + state_key: "", + content: { topic: "Our new topic" }, }); clientWidgetApi.feedStateUpdate(newTopicEvent); @@ -866,22 +866,22 @@ describe('ClientWidgetApi', () => { }); }); - describe('update_delayed_event action', () => { - it('fails to update delayed events', async () => { + describe("update_delayed_event action", () => { + it("fails to update delayed events", async () => { const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', + delay_id: "f", action: UpdateDelayedEventAction.Send, }, }; await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -892,21 +892,21 @@ describe('ClientWidgetApi', () => { expect(driver.updateDelayedEvent).not.toBeCalled(); }); - it('fails to update delayed events with unsupported action', async () => { + it("fails to update delayed events with unsupported action", async () => { const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', - action: 'unknown' as UpdateDelayedEventAction, + delay_id: "f", + action: "unknown" as UpdateDelayedEventAction, }, }; - await loadIframe(['org.matrix.msc4157.update_delayed_event']); + await loadIframe(["org.matrix.msc4157.update_delayed_event"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -917,7 +917,7 @@ describe('ClientWidgetApi', () => { expect(driver.updateDelayedEvent).not.toBeCalled(); }); - it('updates delayed events', async () => { + it("updates delayed events", async () => { driver.updateDelayedEvent.mockResolvedValue(undefined); for (const action of [ @@ -927,18 +927,18 @@ describe('ClientWidgetApi', () => { ]) { const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', + delay_id: "f", action, }, }; - await loadIframe(['org.matrix.msc4157.update_delayed_event']); + await loadIframe(["org.matrix.msc4157.update_delayed_event"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, {}); @@ -948,67 +948,67 @@ describe('ClientWidgetApi', () => { } }); - it('should reject requests when the driver throws an exception', async () => { - driver.updateDelayedEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); + it("should reject requests when the driver throws an exception", async () => { + driver.updateDelayedEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object")); const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', + delay_id: "f", action: UpdateDelayedEventAction.Send, }, }; - await loadIframe(['org.matrix.msc4157.update_delayed_event']); + await loadIframe(["org.matrix.msc4157.update_delayed_event"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error updating delayed event' }, + error: { message: "Error updating delayed event" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.updateDelayedEvent.mockRejectedValue( - new CustomMatrixError('failed to update delayed event', 400, 'M_NOT_JSON', { - reason: 'Content must be a JSON object.', + new CustomMatrixError("failed to update delayed event", 400, "M_NOT_JSON", { + reason: "Content must be a JSON object.", }), ); const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', + delay_id: "f", action: UpdateDelayedEventAction.Send, }, }; - await loadIframe(['org.matrix.msc4157.update_delayed_event']); + await loadIframe(["org.matrix.msc4157.update_delayed_event"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error updating delayed event', + message: "Error updating delayed event", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_NOT_JSON', - error: 'failed to update delayed event', - reason: 'Content must be a JSON object.', + errcode: "M_NOT_JSON", + error: "failed to update delayed event", + reason: "Content must be a JSON object.", }, } satisfies IMatrixApiError, }, @@ -1017,20 +1017,20 @@ describe('ClientWidgetApi', () => { }); }); - describe('send_to_device action', () => { - it('sends unencrypted to-device events', async () => { + describe("send_to_device action", () => { + it("sends unencrypted to-device events", async () => { const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1039,7 +1039,7 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, {}); @@ -1052,18 +1052,18 @@ describe('ClientWidgetApi', () => { ); }); - it('fails to send to-device events without event type', async () => { + it("fails to send to-device events without event type", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1072,54 +1072,54 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing event type' }, + error: { message: "Invalid request - missing event type" }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it('fails to send to-device events without event contents', async () => { + it("fails to send to-device events without event contents", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, }, }; await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing event contents' }, + error: { message: "Invalid request - missing event contents" }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it('fails to send to-device events without encryption flag', async () => { + it("fails to send to-device events without encryption flag", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1128,30 +1128,30 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing encryption flag' }, + error: { message: "Invalid request - missing encryption flag" }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it('fails to send to-device events with any event type', async () => { + it("fails to send to-device events with any event type", async () => { const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1160,34 +1160,34 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}_different`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Cannot send to-device events of this type' }, + error: { message: "Cannot send to-device events of this type" }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { + it("should reject requests when the driver throws an exception", async () => { driver.sendToDevice.mockRejectedValue( new Error("M_FORBIDDEN: You don't have permission to send to-device events"), ); const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1196,36 +1196,36 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error sending event' }, + error: { message: "Error sending event" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.sendToDevice.mockRejectedValue( - new CustomMatrixError('failed to send event', 400, 'M_FORBIDDEN', { + new CustomMatrixError("failed to send event", 400, "M_FORBIDDEN", { reason: "You don't have permission to send to-device events", }), ); const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1234,19 +1234,19 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error sending event', + message: "Error sending event", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_FORBIDDEN', - error: 'failed to send event', + errcode: "M_FORBIDDEN", + error: "failed to send event", reason: "You don't have permission to send to-device events", }, } satisfies IMatrixApiError, @@ -1256,40 +1256,40 @@ describe('ClientWidgetApi', () => { }); }); - describe('get_openid action', () => { - it('gets info', async () => { + describe("get_openid action", () => { + it("gets info", async () => { driver.askOpenID.mockImplementation((observable) => { observable.update({ state: OpenIDRequestState.Allowed, token: { - access_token: 'access_token', + access_token: "access_token", }, }); }); const event: IGetOpenIDActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.GetOpenIDCredentials, data: {}, }; await loadIframe([]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { state: OpenIDRequestState.Allowed, - access_token: 'access_token', + access_token: "access_token", }); }); expect(driver.askOpenID).toHaveBeenCalledWith(expect.any(SimpleObservable)); }); - it('fails when client provided invalid token', async () => { + it("fails when client provided invalid token", async () => { driver.askOpenID.mockImplementation((observable) => { observable.update({ state: OpenIDRequestState.Allowed, @@ -1298,19 +1298,19 @@ describe('ClientWidgetApi', () => { const event: IGetOpenIDActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.GetOpenIDCredentials, data: {}, }; await loadIframe([]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - error: { message: 'client provided invalid OIDC token for an allowed request' }, + error: { message: "client provided invalid OIDC token for an allowed request" }, }); }); @@ -1318,10 +1318,10 @@ describe('ClientWidgetApi', () => { }); }); - describe('com.beeper.read_room_account_data action', () => { - it('reads room account data', async () => { - const type = 'net.example.test'; - const roomId = '!room:example.org'; + describe("com.beeper.read_room_account_data action", () => { + it("reads room account data", async () => { + const type = "net.example.test"; + const roomId = "!room:example.org"; driver.readRoomAccountData.mockResolvedValue([ { @@ -1333,8 +1333,8 @@ describe('ClientWidgetApi', () => { const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data: { room_ids: [roomId], @@ -1344,7 +1344,7 @@ describe('ClientWidgetApi', () => { await loadIframe([`com.beeper.capabilities.receive.room_account_data:${type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -1361,9 +1361,9 @@ describe('ClientWidgetApi', () => { expect(driver.readRoomAccountData).toHaveBeenCalledWith(event.data.type); }); - it('does not read room account data', async () => { - const type = 'net.example.test'; - const roomId = '!room:example.org'; + it("does not read room account data", async () => { + const type = "net.example.test"; + const roomId = "!room:example.org"; driver.readRoomAccountData.mockResolvedValue([ { @@ -1375,8 +1375,8 @@ describe('ClientWidgetApi', () => { const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data: { room_ids: [roomId], @@ -1386,11 +1386,11 @@ describe('ClientWidgetApi', () => { await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - error: { message: 'Cannot read room account data of this type' }, + error: { message: "Cannot read room account data of this type" }, }); }); @@ -1398,10 +1398,10 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc2876.read_events action', () => { - it('reads events from a specific room', async () => { - const roomId = '!room:example.org'; - const event = createRoomEvent({ room_id: roomId, type: 'net.example.test', content: 'test' }); + describe("org.matrix.msc2876.read_events action", () => { + it("reads events from a specific room", async () => { + const roomId = "!room:example.org"; + const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: "test" }); driver.readRoomTimeline.mockImplementation(async (rId) => { if (rId === roomId) return [event]; return []; @@ -1409,22 +1409,22 @@ describe('ClientWidgetApi', () => { const request: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', + type: "net.example.test", room_ids: [roomId], }, }; await loadIframe([ `org.matrix.msc2762.timeline:${roomId}`, - 'org.matrix.msc2762.receive.event:net.example.test', + "org.matrix.msc2762.receive.event:net.example.test", ]); clientWidgetApi.setViewedRoomId(roomId); - emitEvent(new CustomEvent('', { detail: request })); + emitEvent(new CustomEvent("", { detail: request })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(request, { @@ -1434,7 +1434,7 @@ describe('ClientWidgetApi', () => { expect(driver.readRoomTimeline).toHaveBeenCalledWith( roomId, - 'net.example.test', + "net.example.test", undefined, undefined, 0, @@ -1442,11 +1442,11 @@ describe('ClientWidgetApi', () => { ); }); - it('reads events from all rooms', async () => { - const roomId = '!room:example.org'; - const otherRoomId = '!other-room:example.org'; - const event = createRoomEvent({ room_id: roomId, type: 'net.example.test', content: 'test' }); - const otherRoomEvent = createRoomEvent({ room_id: otherRoomId, type: 'net.example.test', content: 'hi' }); + it("reads events from all rooms", async () => { + const roomId = "!room:example.org"; + const otherRoomId = "!other-room:example.org"; + const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: "test" }); + const otherRoomEvent = createRoomEvent({ room_id: otherRoomId, type: "net.example.test", content: "hi" }); driver.getKnownRooms.mockReturnValue([roomId, otherRoomId]); driver.readRoomTimeline.mockImplementation(async (rId) => { if (rId === roomId) return [event]; @@ -1456,22 +1456,22 @@ describe('ClientWidgetApi', () => { const request: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', + type: "net.example.test", room_ids: Symbols.AnyRoom, }, }; await loadIframe([ `org.matrix.msc2762.timeline:${Symbols.AnyRoom}`, - 'org.matrix.msc2762.receive.event:net.example.test', + "org.matrix.msc2762.receive.event:net.example.test", ]); clientWidgetApi.setViewedRoomId(roomId); - emitEvent(new CustomEvent('', { detail: request })); + emitEvent(new CustomEvent("", { detail: request })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(request, { @@ -1481,7 +1481,7 @@ describe('ClientWidgetApi', () => { expect(driver.readRoomTimeline).toHaveBeenCalledWith( roomId, - 'net.example.test', + "net.example.test", undefined, undefined, 0, @@ -1489,7 +1489,7 @@ describe('ClientWidgetApi', () => { ); expect(driver.readRoomTimeline).toHaveBeenCalledWith( otherRoomId, - 'net.example.test', + "net.example.test", undefined, undefined, 0, @@ -1497,40 +1497,40 @@ describe('ClientWidgetApi', () => { ); }); - it('reads state events with any state key', async () => { + it("reads state events with any state key", async () => { driver.readRoomTimeline.mockResolvedValue([ - createRoomEvent({ type: 'net.example.test', state_key: 'A' }), - createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + createRoomEvent({ type: "net.example.test", state_key: "A" }), + createRoomEvent({ type: "net.example.test", state_key: "B" }), ]); const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', + type: "net.example.test", state_key: true, }, }; - await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test']); - clientWidgetApi.setViewedRoomId('!room-id'); + await loadIframe(["org.matrix.msc2762.receive.state_event:net.example.test"]); + clientWidgetApi.setViewedRoomId("!room-id"); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { events: [ - createRoomEvent({ type: 'net.example.test', state_key: 'A' }), - createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + createRoomEvent({ type: "net.example.test", state_key: "A" }), + createRoomEvent({ type: "net.example.test", state_key: "B" }), ], }); }); expect(driver.readRoomTimeline).toBeCalledWith( - '!room-id', - 'net.example.test', + "!room-id", + "net.example.test", undefined, undefined, 0, @@ -1538,21 +1538,21 @@ describe('ClientWidgetApi', () => { ); }); - it('fails to read state events with any state key', async () => { + it("fails to read state events with any state key", async () => { const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', + type: "net.example.test", state_key: true, }, }; await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1563,57 +1563,57 @@ describe('ClientWidgetApi', () => { expect(driver.readRoomTimeline).not.toBeCalled(); }); - it('reads state events with a specific state key', async () => { - driver.readRoomTimeline.mockResolvedValue([createRoomEvent({ type: 'net.example.test', state_key: 'B' })]); + it("reads state events with a specific state key", async () => { + driver.readRoomTimeline.mockResolvedValue([createRoomEvent({ type: "net.example.test", state_key: "B" })]); const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', - state_key: 'B', + type: "net.example.test", + state_key: "B", }, }; - await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test#B']); - clientWidgetApi.setViewedRoomId('!room-id'); + await loadIframe(["org.matrix.msc2762.receive.state_event:net.example.test#B"]); + clientWidgetApi.setViewedRoomId("!room-id"); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - events: [createRoomEvent({ type: 'net.example.test', state_key: 'B' })], + events: [createRoomEvent({ type: "net.example.test", state_key: "B" })], }); }); expect(driver.readRoomTimeline).toBeCalledWith( - '!room-id', - 'net.example.test', + "!room-id", + "net.example.test", undefined, - 'B', + "B", 0, undefined, ); }); - it('fails to read state events with a specific state key', async () => { + it("fails to read state events with a specific state key", async () => { const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', - state_key: 'B', + type: "net.example.test", + state_key: "B", }, }; // Request the capability for the wrong state key - await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test#A']); + await loadIframe(["org.matrix.msc2762.receive.state_event:net.example.test#A"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1625,39 +1625,39 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc3869.read_relations action', () => { - it('should present as supported api version', () => { + describe("org.matrix.msc3869.read_relations action", () => { + it("should present as supported api version", () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC3869]), }); }); - it('should handle and process the request', async () => { + it("should handle and process the request", async () => { driver.readEventRelations.mockResolvedValue({ chunk: [createRoomEvent()], }); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: '$event' }, + data: { event_id: "$event" }, }; - await loadIframe(['org.matrix.msc2762.receive.event:m.room.message']); + await loadIframe(["org.matrix.msc2762.receive.event:m.room.message"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1666,7 +1666,7 @@ describe('ClientWidgetApi', () => { }); expect(driver.readEventRelations).toBeCalledWith( - '$event', + "$event", undefined, undefined, undefined, @@ -1677,39 +1677,39 @@ describe('ClientWidgetApi', () => { ); }); - it('should only return events that match requested capabilities', async () => { + it("should only return events that match requested capabilities", async () => { driver.readEventRelations.mockResolvedValue({ chunk: [ createRoomEvent(), - createRoomEvent({ type: 'm.reaction' }), - createRoomEvent({ type: 'net.example.test', state_key: 'A' }), - createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + createRoomEvent({ type: "m.reaction" }), + createRoomEvent({ type: "net.example.test", state_key: "A" }), + createRoomEvent({ type: "net.example.test", state_key: "B" }), ], }); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: '$event' }, + data: { event_id: "$event" }, }; await loadIframe([ - 'org.matrix.msc2762.receive.event:m.room.message', - 'org.matrix.msc2762.receive.state_event:net.example.test#A', + "org.matrix.msc2762.receive.event:m.room.message", + "org.matrix.msc2762.receive.state_event:net.example.test#A", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - chunk: [createRoomEvent(), createRoomEvent({ type: 'net.example.test', state_key: 'A' })], + chunk: [createRoomEvent(), createRoomEvent({ type: "net.example.test", state_key: "A" })], }); }); expect(driver.readEventRelations).toBeCalledWith( - '$event', + "$event", undefined, undefined, undefined, @@ -1720,31 +1720,31 @@ describe('ClientWidgetApi', () => { ); }); - it('should accept all options and pass it to the driver', async () => { + it("should accept all options and pass it to the driver", async () => { driver.readEventRelations.mockResolvedValue({ chunk: [], }); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: '$event', - room_id: '!room-id', - event_type: 'm.room.message', - rel_type: 'm.reference', + event_id: "$event", + room_id: "!room-id", + event_type: "m.room.message", + rel_type: "m.reference", limit: 25, - from: 'from-token', - to: 'to-token', - direction: 'f', + from: "from-token", + to: "to-token", + direction: "f", }, }; - await loadIframe(['org.matrix.msc2762.timeline:!room-id']); + await loadIframe(["org.matrix.msc2762.timeline:!room-id"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1753,127 +1753,127 @@ describe('ClientWidgetApi', () => { }); expect(driver.readEventRelations).toBeCalledWith( - '$event', - '!room-id', - 'm.reference', - 'm.room.message', - 'from-token', - 'to-token', + "$event", + "!room-id", + "m.reference", + "m.room.message", + "from-token", + "to-token", 25, - 'f', + "f", ); }); - it('should reject requests without event_id', async () => { + it("should reject requests without event_id", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing event ID' }, + error: { message: "Invalid request - missing event ID" }, }); }); - it('should reject requests with a negative limit', async () => { + it("should reject requests with a negative limit", async () => { const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: '$event', + event_id: "$event", limit: -1, }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - limit out of range' }, + error: { message: "Invalid request - limit out of range" }, }); }); - it('should reject requests when the room timeline was not requested', async () => { + it("should reject requests when the room timeline was not requested", async () => { const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: '$event', - room_id: '!another-room-id', + event_id: "$event", + room_id: "!another-room-id", }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unable to access room timeline: !another-room-id' }, + error: { message: "Unable to access room timeline: !another-room-id" }, }); }); - it('should reject requests when the driver throws an exception', async () => { + it("should reject requests when the driver throws an exception", async () => { driver.readEventRelations.mockRejectedValue( new Error("M_FORBIDDEN: You don't have permission to access that event"), ); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: '$event' }, + data: { event_id: "$event" }, }; await loadIframe(); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while reading relations' }, + error: { message: "Unexpected error while reading relations" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.readEventRelations.mockRejectedValue( - new CustomMatrixError('failed to read relations', 403, 'M_FORBIDDEN', { + new CustomMatrixError("failed to read relations", 403, "M_FORBIDDEN", { reason: "You don't have permission to access that event", }), ); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: '$event' }, + data: { event_id: "$event" }, }; await loadIframe(); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while reading relations', + message: "Unexpected error while reading relations", matrix_api_error: { http_status: 403, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_FORBIDDEN', - error: 'failed to read relations', + errcode: "M_FORBIDDEN", + error: "failed to read relations", reason: "You don't have permission to access that event", }, } satisfies IMatrixApiError, @@ -1883,51 +1883,51 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc3973.user_directory_search action', () => { - it('should present as supported api version', () => { + describe("org.matrix.msc3973.user_directory_search action", () => { + it("should present as supported api version", () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC3973]), }); }); - it('should handle and process the request', async () => { + it("should handle and process the request", async () => { driver.searchUserDirectory.mockResolvedValue({ limited: true, results: [ { - userId: '@foo:bar.com', + userId: "@foo:bar.com", }, ], }); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: 'foo' }, + data: { search_term: "foo" }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { limited: true, results: [ { - user_id: '@foo:bar.com', + user_id: "@foo:bar.com", display_name: undefined, avatar_url: undefined, }, @@ -1935,61 +1935,61 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.searchUserDirectory).toBeCalledWith('foo', undefined); + expect(driver.searchUserDirectory).toBeCalledWith("foo", undefined); }); - it('should accept all options and pass it to the driver', async () => { + it("should accept all options and pass it to the driver", async () => { driver.searchUserDirectory.mockResolvedValue({ limited: false, results: [ { - userId: '@foo:bar.com', + userId: "@foo:bar.com", }, { - userId: '@bar:foo.com', - displayName: 'Bar', - avatarUrl: 'mxc://...', + userId: "@bar:foo.com", + displayName: "Bar", + avatarUrl: "mxc://...", }, ], }); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: { - search_term: 'foo', + search_term: "foo", limit: 5, }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { limited: false, results: [ { - user_id: '@foo:bar.com', + user_id: "@foo:bar.com", display_name: undefined, avatar_url: undefined, }, { - user_id: '@bar:foo.com', - display_name: 'Bar', - avatar_url: 'mxc://...', + user_id: "@bar:foo.com", + display_name: "Bar", + avatar_url: "mxc://...", }, ], }); }); - expect(driver.searchUserDirectory).toBeCalledWith('foo', 5); + expect(driver.searchUserDirectory).toBeCalledWith("foo", 5); }); - it('should accept empty search_term', async () => { + it("should accept empty search_term", async () => { driver.searchUserDirectory.mockResolvedValue({ limited: false, results: [], @@ -1997,15 +1997,15 @@ describe('ClientWidgetApi', () => { const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: '' }, + data: { search_term: "" }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -2014,126 +2014,126 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.searchUserDirectory).toBeCalledWith('', undefined); + expect(driver.searchUserDirectory).toBeCalledWith("", undefined); }); - it('should reject requests when the capability was not requested', async () => { + it("should reject requests when the capability was not requested", async () => { const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: 'foo' }, + data: { search_term: "foo" }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); expect(driver.searchUserDirectory).not.toBeCalled(); }); - it('should reject requests without search_term', async () => { + it("should reject requests without search_term", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: {}, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing search term' }, + error: { message: "Invalid request - missing search term" }, }); expect(driver.searchUserDirectory).not.toBeCalled(); }); - it('should reject requests with a negative limit', async () => { + it("should reject requests with a negative limit", async () => { const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: { - search_term: 'foo', + search_term: "foo", limit: -1, }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - limit out of range' }, + error: { message: "Invalid request - limit out of range" }, }); expect(driver.searchUserDirectory).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.searchUserDirectory.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); + it("should reject requests when the driver throws an exception", async () => { + driver.searchUserDirectory.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: 'foo' }, + data: { search_term: "foo" }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while searching in the user directory' }, + error: { message: "Unexpected error while searching in the user directory" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.searchUserDirectory.mockRejectedValue( - new CustomMatrixError('failed to search the user directory', 429, 'M_LIMIT_EXCEEDED', { - reason: 'Too many requests', + new CustomMatrixError("failed to search the user directory", 429, "M_LIMIT_EXCEEDED", { + reason: "Too many requests", retry_after_ms: 2000, }), ); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: 'foo' }, + data: { search_term: "foo" }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while searching in the user directory', + message: "Unexpected error while searching in the user directory", matrix_api_error: { http_status: 429, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_LIMIT_EXCEEDED', - error: 'failed to search the user directory', - reason: 'Too many requests', + errcode: "M_LIMIT_EXCEEDED", + error: "failed to search the user directory", + reason: "Too many requests", retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2143,123 +2143,123 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc4039.get_media_config action', () => { - it('should present as supported api version', () => { + describe("org.matrix.msc4039.get_media_config action", () => { + it("should present as supported api version", () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC4039]), }); }); - it('should handle and process the request', async () => { + it("should handle and process the request", async () => { driver.getMediaConfig.mockResolvedValue({ - 'm.upload.size': 1000, + "m.upload.size": 1000, }); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - 'm.upload.size': 1000, + "m.upload.size": 1000, }); }); expect(driver.getMediaConfig).toBeCalled(); }); - it('should reject requests when the capability was not requested', async () => { + it("should reject requests when the capability was not requested", async () => { const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); expect(driver.getMediaConfig).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.getMediaConfig.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); + it("should reject requests when the driver throws an exception", async () => { + driver.getMediaConfig.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while getting the media configuration' }, + error: { message: "Unexpected error while getting the media configuration" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.getMediaConfig.mockRejectedValue( - new CustomMatrixError('failed to get the media configuration', 429, 'M_LIMIT_EXCEEDED', { - reason: 'Too many requests', + new CustomMatrixError("failed to get the media configuration", 429, "M_LIMIT_EXCEEDED", { + reason: "Too many requests", retry_after_ms: 2000, }), ); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while getting the media configuration', + message: "Unexpected error while getting the media configuration", matrix_api_error: { http_status: 429, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_LIMIT_EXCEEDED', - error: 'failed to get the media configuration', - reason: 'Too many requests', + errcode: "M_LIMIT_EXCEEDED", + error: "failed to get the media configuration", + reason: "Too many requests", retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2269,17 +2269,17 @@ describe('ClientWidgetApi', () => { }); }); - describe('MSC4039', () => { - it('should present as supported api version', () => { + describe("MSC4039", () => { + it("should present as supported api version", () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC4039]), @@ -2287,115 +2287,115 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc4039.upload_file action', () => { - it('should handle and process the request', async () => { + describe("org.matrix.msc4039.upload_file action", () => { + it("should handle and process the request", async () => { driver.uploadFile.mockResolvedValue({ - contentUri: 'mxc://...', + contentUri: "mxc://...", }); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: 'data', + file: "data", }, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - content_uri: 'mxc://...', + content_uri: "mxc://...", }); }); expect(driver.uploadFile).toBeCalled(); }); - it('should reject requests when the capability was not requested', async () => { + it("should reject requests when the capability was not requested", async () => { const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: 'data', + file: "data", }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); expect(driver.uploadFile).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.uploadFile.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); + it("should reject requests when the driver throws an exception", async () => { + driver.uploadFile.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: 'data', + file: "data", }, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while uploading a file' }, + error: { message: "Unexpected error while uploading a file" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.uploadFile.mockRejectedValue( - new CustomMatrixError('failed to upload a file', 429, 'M_LIMIT_EXCEEDED', { - reason: 'Too many requests', + new CustomMatrixError("failed to upload a file", 429, "M_LIMIT_EXCEEDED", { + reason: "Too many requests", retry_after_ms: 2000, }), ); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: 'data', + file: "data", }, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while uploading a file', + message: "Unexpected error while uploading a file", matrix_api_error: { http_status: 429, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_LIMIT_EXCEEDED', - error: 'failed to upload a file', - reason: 'Too many requests', + errcode: "M_LIMIT_EXCEEDED", + error: "failed to upload a file", + reason: "Too many requests", retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2405,115 +2405,115 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc4039.download_file action', () => { - it('should handle and process the request', async () => { + describe("org.matrix.msc4039.download_file action", () => { + it("should handle and process the request", async () => { driver.downloadFile.mockResolvedValue({ - file: 'test contents', + file: "test contents", }); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: 'mxc://example.com/test_file', + content_uri: "mxc://example.com/test_file", }, }; - await loadIframe(['org.matrix.msc4039.download_file']); + await loadIframe(["org.matrix.msc4039.download_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - file: 'test contents', + file: "test contents", }); }); - expect(driver.downloadFile).toHaveBeenCalledWith('mxc://example.com/test_file'); + expect(driver.downloadFile).toHaveBeenCalledWith("mxc://example.com/test_file"); }); - it('should reject requests when the capability was not requested', async () => { + it("should reject requests when the capability was not requested", async () => { const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: 'mxc://example.com/test_file', + content_uri: "mxc://example.com/test_file", }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); expect(driver.uploadFile).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.downloadFile.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); + it("should reject requests when the driver throws an exception", async () => { + driver.downloadFile.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: 'mxc://example.com/test_file', + content_uri: "mxc://example.com/test_file", }, }; - await loadIframe(['org.matrix.msc4039.download_file']); + await loadIframe(["org.matrix.msc4039.download_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while downloading a file' }, + error: { message: "Unexpected error while downloading a file" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.downloadFile.mockRejectedValue( - new CustomMatrixError('failed to download a file', 429, 'M_LIMIT_EXCEEDED', { - reason: 'Too many requests', + new CustomMatrixError("failed to download a file", 429, "M_LIMIT_EXCEEDED", { + reason: "Too many requests", retry_after_ms: 2000, }), ); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: 'mxc://example.com/test_file', + content_uri: "mxc://example.com/test_file", }, }; - await loadIframe(['org.matrix.msc4039.download_file']); + await loadIframe(["org.matrix.msc4039.download_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while downloading a file', + message: "Unexpected error while downloading a file", matrix_api_error: { http_status: 429, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_LIMIT_EXCEEDED', - error: 'failed to download a file', - reason: 'Too many requests', + errcode: "M_LIMIT_EXCEEDED", + error: "failed to download a file", + reason: "Too many requests", retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2523,13 +2523,13 @@ describe('ClientWidgetApi', () => { }); }); - it('updates theme', () => { - clientWidgetApi.updateTheme({ name: 'dark' }); - expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.ThemeChange, { name: 'dark' }); + it("updates theme", () => { + clientWidgetApi.updateTheme({ name: "dark" }); + expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.ThemeChange, { name: "dark" }); }); - it('updates language', () => { - clientWidgetApi.updateLanguage('tlh'); - expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.LanguageChange, { lang: 'tlh' }); + it("updates language", () => { + clientWidgetApi.updateLanguage("tlh"); + expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.LanguageChange, { lang: "tlh" }); }); }); diff --git a/test/WidgetApi-test.ts b/test/WidgetApi-test.ts index f458fb1..b128e1c 100644 --- a/test/WidgetApi-test.ts +++ b/test/WidgetApi-test.ts @@ -15,16 +15,16 @@ * limitations under the License. */ -import { UnstableApiVersion } from '../src/interfaces/ApiVersion'; -import { IGetMediaConfigActionFromWidgetResponseData } from '../src/interfaces/GetMediaConfigAction'; -import { IReadRelationsFromWidgetResponseData } from '../src/interfaces/ReadRelationsAction'; -import { ISendEventFromWidgetResponseData } from '../src/interfaces/SendEventAction'; -import { ISupportedVersionsActionResponseData } from '../src/interfaces/SupportedVersionsAction'; -import { IUploadFileActionFromWidgetResponseData } from '../src/interfaces/UploadFileAction'; -import { IDownloadFileActionFromWidgetResponseData } from '../src/interfaces/DownloadFileAction'; -import { IUserDirectorySearchFromWidgetResponseData } from '../src/interfaces/UserDirectorySearchAction'; -import { WidgetApiFromWidgetAction } from '../src/interfaces/WidgetApiAction'; -import { WidgetApi, WidgetApiResponseError } from '../src/WidgetApi'; +import { UnstableApiVersion } from "../src/interfaces/ApiVersion"; +import { IGetMediaConfigActionFromWidgetResponseData } from "../src/interfaces/GetMediaConfigAction"; +import { IReadRelationsFromWidgetResponseData } from "../src/interfaces/ReadRelationsAction"; +import { ISendEventFromWidgetResponseData } from "../src/interfaces/SendEventAction"; +import { ISupportedVersionsActionResponseData } from "../src/interfaces/SupportedVersionsAction"; +import { IUploadFileActionFromWidgetResponseData } from "../src/interfaces/UploadFileAction"; +import { IDownloadFileActionFromWidgetResponseData } from "../src/interfaces/DownloadFileAction"; +import { IUserDirectorySearchFromWidgetResponseData } from "../src/interfaces/UserDirectorySearchAction"; +import { WidgetApiFromWidgetAction } from "../src/interfaces/WidgetApiAction"; +import { WidgetApi, WidgetApiResponseError } from "../src/WidgetApi"; import { IWidgetApiErrorResponseData, IWidgetApiErrorResponseDataDetails, @@ -34,7 +34,7 @@ import { IWidgetApiResponseData, UpdateDelayedEventAction, WidgetApiDirection, -} from '../src'; +} from "../src"; type SendRequestArgs = { action: WidgetApiFromWidgetAction; @@ -81,7 +81,7 @@ class ClientTransportHelper { } } -describe('WidgetApi', () => { +describe("WidgetApi", () => { let widgetApi: WidgetApi; let widgetTransportHelper: WidgetTransportHelper; let clientListener: (e: MessageEvent) => void; @@ -93,7 +93,7 @@ describe('WidgetApi', () => { clientListener = (e: MessageEvent): void => { if (!e.data.action || !e.data.requestId || !e.data.widgetId) return; // invalid request/response - if ('response' in e.data || e.data.api !== WidgetApiDirection.FromWidget) return; // not a request + if ("response" in e.data || e.data.api !== WidgetApiDirection.FromWidget) return; // not a request const request = e.data; clientTrafficHelper.trackRequest(request.action as WidgetApiFromWidgetAction, request.data); @@ -105,22 +105,22 @@ describe('WidgetApi', () => { ...request, response: response, } satisfies IWidgetApiResponse, - '*', + "*", ); } }; - window.addEventListener('message', clientListener); + window.addEventListener("message", clientListener); - widgetApi = new WidgetApi('WidgetApi-test', '*'); + widgetApi = new WidgetApi("WidgetApi-test", "*"); widgetApi.start(); }); afterEach(() => { - window.removeEventListener('message', clientListener); + window.removeEventListener("message", clientListener); }); - describe('readEventRelations', () => { - it('should forward the request to the ClientWidgetApi', async () => { + describe("readEventRelations", () => { + it("should forward the request to the ClientWidgetApi", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC3869], } as ISupportedVersionsActionResponseData); @@ -130,14 +130,14 @@ describe('WidgetApi', () => { await expect( widgetApi.readEventRelations( - '$event', - '!room-id', - 'm.reference', - 'm.room.message', + "$event", + "!room-id", + "m.reference", + "m.room.message", 25, - 'from-token', - 'to-token', - 'f', + "from-token", + "to-token", + "f", ), ).resolves.toEqual({ chunk: [], @@ -147,33 +147,33 @@ describe('WidgetApi', () => { expect(widgetTransportHelper.nextTrackedRequest()).toEqual({ action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: '$event', - room_id: '!room-id', - rel_type: 'm.reference', - event_type: 'm.room.message', + event_id: "$event", + room_id: "!room-id", + rel_type: "m.reference", + event_type: "m.room.message", limit: 25, - from: 'from-token', - to: 'to-token', - direction: 'f', + from: "from-token", + to: "to-token", + direction: "f", }, } satisfies SendRequestArgs); }); - it('should reject the request if the api is not supported', async () => { + it("should reject the request if the api is not supported", async () => { widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); await expect( widgetApi.readEventRelations( - '$event', - '!room-id', - 'm.reference', - 'm.room.message', + "$event", + "!room-id", + "m.reference", + "m.room.message", 25, - 'from-token', - 'to-token', - 'f', + "from-token", + "to-token", + "f", ), - ).rejects.toThrow('The read_relations action is not supported by the client.'); + ).rejects.toThrow("The read_relations action is not supported by the client."); const request = widgetTransportHelper.nextTrackedRequest(); expect(request).not.toBeUndefined(); @@ -183,29 +183,29 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should handle an error', async () => { + it("should handle an error", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC3869], } as ISupportedVersionsActionResponseData); widgetTransportHelper.queueResponse({ - error: { message: 'An error occurred' }, + error: { message: "An error occurred" }, } as IWidgetApiErrorResponseData); await expect( widgetApi.readEventRelations( - '$event', - '!room-id', - 'm.reference', - 'm.room.message', + "$event", + "!room-id", + "m.reference", + "m.room.message", 25, - 'from-token', - 'to-token', - 'f', + "from-token", + "to-token", + "f", ), - ).rejects.toThrow('An error occurred'); + ).rejects.toThrow("An error occurred"); }); - it('should handle an error with details', async () => { + it("should handle an error with details", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC3869], } as ISupportedVersionsActionResponseData); @@ -214,249 +214,249 @@ describe('WidgetApi', () => { matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'Unknown error', + errcode: "M_UNKNOWN", + error: "Unknown error", }, }, }; widgetTransportHelper.queueResponse({ error: { - message: 'An error occurred', + message: "An error occurred", ...errorDetails, }, } as IWidgetApiErrorResponseData); await expect( widgetApi.readEventRelations( - '$event', - '!room-id', - 'm.reference', - 'm.room.message', + "$event", + "!room-id", + "m.reference", + "m.room.message", 25, - 'from-token', - 'to-token', - 'f', + "from-token", + "to-token", + "f", ), - ).rejects.toThrow(new WidgetApiResponseError('An error occurred', errorDetails)); + ).rejects.toThrow(new WidgetApiResponseError("An error occurred", errorDetails)); }); }); - describe('sendEvent', () => { - it('sends message events', async () => { + describe("sendEvent", () => { + it("sends message events", async () => { widgetTransportHelper.queueResponse({ - room_id: '!room-id', - event_id: '$event', + room_id: "!room-id", + event_id: "$event", } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id')).resolves.toEqual({ - room_id: '!room-id', - event_id: '$event', + await expect(widgetApi.sendRoomEvent("m.room.message", {}, "!room-id")).resolves.toEqual({ + room_id: "!room-id", + event_id: "$event", }); }); - it('sends state events', async () => { + it("sends state events", async () => { widgetTransportHelper.queueResponse({ - room_id: '!room-id', - event_id: '$event', + room_id: "!room-id", + event_id: "$event", } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendStateEvent('m.room.topic', '', {}, '!room-id')).resolves.toEqual({ - room_id: '!room-id', - event_id: '$event', + await expect(widgetApi.sendStateEvent("m.room.topic", "", {}, "!room-id")).resolves.toEqual({ + room_id: "!room-id", + event_id: "$event", }); }); - it('should handle an error', async () => { + it("should handle an error", async () => { widgetTransportHelper.queueResponse({ - error: { message: 'An error occurred' }, + error: { message: "An error occurred" }, } as IWidgetApiErrorResponseData); - await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id')).rejects.toThrow( - 'An error occurred', + await expect(widgetApi.sendRoomEvent("m.room.message", {}, "!room-id")).rejects.toThrow( + "An error occurred", ); }); - it('should handle an error with details', async () => { + it("should handle an error with details", async () => { const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'Unknown error', + errcode: "M_UNKNOWN", + error: "Unknown error", }, }, }; widgetTransportHelper.queueResponse({ error: { - message: 'An error occurred', + message: "An error occurred", ...errorDetails, }, } as IWidgetApiErrorResponseData); - await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id')).rejects.toThrow( - new WidgetApiResponseError('An error occurred', errorDetails), + await expect(widgetApi.sendRoomEvent("m.room.message", {}, "!room-id")).rejects.toThrow( + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); - describe('delayed sendEvent', () => { - it('sends delayed message events', async () => { + describe("delayed sendEvent", () => { + it("sends delayed message events", async () => { widgetTransportHelper.queueResponse({ - room_id: '!room-id', - delay_id: 'id', + room_id: "!room-id", + delay_id: "id", } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id', 2000)).resolves.toEqual({ - room_id: '!room-id', - delay_id: 'id', + await expect(widgetApi.sendRoomEvent("m.room.message", {}, "!room-id", 2000)).resolves.toEqual({ + room_id: "!room-id", + delay_id: "id", }); }); - it('sends delayed state events', async () => { + it("sends delayed state events", async () => { widgetTransportHelper.queueResponse({ - room_id: '!room-id', - delay_id: 'id', + room_id: "!room-id", + delay_id: "id", } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendStateEvent('m.room.topic', '', {}, '!room-id', 2000)).resolves.toEqual({ - room_id: '!room-id', - delay_id: 'id', + await expect(widgetApi.sendStateEvent("m.room.topic", "", {}, "!room-id", 2000)).resolves.toEqual({ + room_id: "!room-id", + delay_id: "id", }); }); - it('sends delayed child action message events', async () => { + it("sends delayed child action message events", async () => { widgetTransportHelper.queueResponse({ - room_id: '!room-id', - delay_id: 'id', + room_id: "!room-id", + delay_id: "id", } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id', 1000, undefined)).resolves.toEqual({ - room_id: '!room-id', - delay_id: 'id', + await expect(widgetApi.sendRoomEvent("m.room.message", {}, "!room-id", 1000, undefined)).resolves.toEqual({ + room_id: "!room-id", + delay_id: "id", }); }); - it('sends delayed child action state events', async () => { + it("sends delayed child action state events", async () => { widgetTransportHelper.queueResponse({ - room_id: '!room-id', - delay_id: 'id', + room_id: "!room-id", + delay_id: "id", } as ISendEventFromWidgetResponseData); await expect( - widgetApi.sendStateEvent('m.room.topic', '', {}, '!room-id', 1000, undefined), + widgetApi.sendStateEvent("m.room.topic", "", {}, "!room-id", 1000, undefined), ).resolves.toEqual({ - room_id: '!room-id', - delay_id: 'id', + room_id: "!room-id", + delay_id: "id", }); }); - it('should handle an error', async () => { + it("should handle an error", async () => { widgetTransportHelper.queueResponse({ - error: { message: 'An error occurred' }, + error: { message: "An error occurred" }, } as IWidgetApiErrorResponseData); - await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id', 1000, undefined)).rejects.toThrow( - 'An error occurred', + await expect(widgetApi.sendRoomEvent("m.room.message", {}, "!room-id", 1000, undefined)).rejects.toThrow( + "An error occurred", ); }); - it('should handle an error with details', async () => { + it("should handle an error with details", async () => { const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'Unknown error', + errcode: "M_UNKNOWN", + error: "Unknown error", }, }, }; widgetTransportHelper.queueResponse({ error: { - message: 'An error occurred', + message: "An error occurred", ...errorDetails, }, } as IWidgetApiErrorResponseData); - await expect(widgetApi.sendRoomEvent('m.room.message', {}, '!room-id', 1000, undefined)).rejects.toThrow( - new WidgetApiResponseError('An error occurred', errorDetails), + await expect(widgetApi.sendRoomEvent("m.room.message", {}, "!room-id", 1000, undefined)).rejects.toThrow( + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); - describe('updateDelayedEvent', () => { - it('updates delayed events', async () => { + describe("updateDelayedEvent", () => { + it("updates delayed events", async () => { widgetTransportHelper.queueResponse({}); - await expect(widgetApi.updateDelayedEvent('id', UpdateDelayedEventAction.Send)).resolves.toEqual({}); + await expect(widgetApi.updateDelayedEvent("id", UpdateDelayedEventAction.Send)).resolves.toEqual({}); }); - it('should handle an error', async () => { + it("should handle an error", async () => { widgetTransportHelper.queueResponse({ - error: { message: 'An error occurred' }, + error: { message: "An error occurred" }, } as IWidgetApiErrorResponseData); - await expect(widgetApi.updateDelayedEvent('id', UpdateDelayedEventAction.Send)).rejects.toThrow( - 'An error occurred', + await expect(widgetApi.updateDelayedEvent("id", UpdateDelayedEventAction.Send)).rejects.toThrow( + "An error occurred", ); }); - it('should handle an error with details', async () => { + it("should handle an error with details", async () => { const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'Unknown error', + errcode: "M_UNKNOWN", + error: "Unknown error", }, }, }; widgetTransportHelper.queueResponse({ error: { - message: 'An error occurred', + message: "An error occurred", ...errorDetails, }, } as IWidgetApiErrorResponseData); - await expect(widgetApi.updateDelayedEvent('id', UpdateDelayedEventAction.Send)).rejects.toThrow( - new WidgetApiResponseError('An error occurred', errorDetails), + await expect(widgetApi.updateDelayedEvent("id", UpdateDelayedEventAction.Send)).rejects.toThrow( + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); - describe('getClientVersions', () => { + describe("getClientVersions", () => { beforeEach(() => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC3869, UnstableApiVersion.MSC2762], } as ISupportedVersionsActionResponseData); }); - it('should request supported client versions', async () => { - await expect(widgetApi.getClientVersions()).resolves.toEqual(['org.matrix.msc3869', 'org.matrix.msc2762']); + it("should request supported client versions", async () => { + await expect(widgetApi.getClientVersions()).resolves.toEqual(["org.matrix.msc3869", "org.matrix.msc2762"]); }); - it('should cache supported client versions on successive calls', async () => { - await expect(widgetApi.getClientVersions()).resolves.toEqual(['org.matrix.msc3869', 'org.matrix.msc2762']); + it("should cache supported client versions on successive calls", async () => { + await expect(widgetApi.getClientVersions()).resolves.toEqual(["org.matrix.msc3869", "org.matrix.msc2762"]); - await expect(widgetApi.getClientVersions()).resolves.toEqual(['org.matrix.msc3869', 'org.matrix.msc2762']); + await expect(widgetApi.getClientVersions()).resolves.toEqual(["org.matrix.msc3869", "org.matrix.msc2762"]); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); expect(widgetTransportHelper.nextTrackedRequest()).toBeUndefined(); }); }); - describe('searchUserDirectory', () => { - it('should forward the request to the ClientWidgetApi', async () => { + describe("searchUserDirectory", () => { + it("should forward the request to the ClientWidgetApi", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC3973], } as ISupportedVersionsActionResponseData); @@ -465,7 +465,7 @@ describe('WidgetApi', () => { results: [], } as IUserDirectorySearchFromWidgetResponseData); - await expect(widgetApi.searchUserDirectory('foo', 10)).resolves.toEqual({ + await expect(widgetApi.searchUserDirectory("foo", 10)).resolves.toEqual({ limited: false, results: [], }); @@ -474,17 +474,17 @@ describe('WidgetApi', () => { expect(widgetTransportHelper.nextTrackedRequest()).toEqual({ action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: { - search_term: 'foo', + search_term: "foo", limit: 10, }, } satisfies SendRequestArgs); }); - it('should reject the request if the api is not supported', async () => { + it("should reject the request if the api is not supported", async () => { widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); - await expect(widgetApi.searchUserDirectory('foo', 10)).rejects.toThrow( - 'The user_directory_search action is not supported by the client.', + await expect(widgetApi.searchUserDirectory("foo", 10)).rejects.toThrow( + "The user_directory_search action is not supported by the client.", ); const request = widgetTransportHelper.nextTrackedRequest(); @@ -495,16 +495,16 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should handle an error', async () => { + it("should handle an error", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC3973], } as ISupportedVersionsActionResponseData); - widgetTransportHelper.queueResponse({ error: { message: 'An error occurred' } }); + widgetTransportHelper.queueResponse({ error: { message: "An error occurred" } }); - await expect(widgetApi.searchUserDirectory('foo', 10)).rejects.toThrow('An error occurred'); + await expect(widgetApi.searchUserDirectory("foo", 10)).rejects.toThrow("An error occurred"); }); - it('should handle an error with details', async () => { + it("should handle an error with details", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC3973], } as ISupportedVersionsActionResponseData); @@ -513,38 +513,38 @@ describe('WidgetApi', () => { matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'Unknown error', + errcode: "M_UNKNOWN", + error: "Unknown error", }, }, }; widgetTransportHelper.queueResponse({ error: { - message: 'An error occurred', + message: "An error occurred", ...errorDetails, }, } as IWidgetApiErrorResponseData); - await expect(widgetApi.searchUserDirectory('foo', 10)).rejects.toThrow( - new WidgetApiResponseError('An error occurred', errorDetails), + await expect(widgetApi.searchUserDirectory("foo", 10)).rejects.toThrow( + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); - describe('getMediaConfig', () => { - it('should forward the request to the ClientWidgetApi', async () => { + describe("getMediaConfig", () => { + it("should forward the request to the ClientWidgetApi", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC4039], } as ISupportedVersionsActionResponseData); widgetTransportHelper.queueResponse({ - 'm.upload.size': 1000, + "m.upload.size": 1000, } as IGetMediaConfigActionFromWidgetResponseData); await expect(widgetApi.getMediaConfig()).resolves.toEqual({ - 'm.upload.size': 1000, + "m.upload.size": 1000, }); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); @@ -554,11 +554,11 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should reject the request if the api is not supported', async () => { + it("should reject the request if the api is not supported", async () => { widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); await expect(widgetApi.getMediaConfig()).rejects.toThrow( - 'The get_media_config action is not supported by the client.', + "The get_media_config action is not supported by the client.", ); const request = widgetTransportHelper.nextTrackedRequest(); @@ -569,16 +569,16 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should handle an error', async () => { + it("should handle an error", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC4039], } as ISupportedVersionsActionResponseData); - widgetTransportHelper.queueResponse({ error: { message: 'An error occurred' } }); + widgetTransportHelper.queueResponse({ error: { message: "An error occurred" } }); - await expect(widgetApi.getMediaConfig()).rejects.toThrow('An error occurred'); + await expect(widgetApi.getMediaConfig()).rejects.toThrow("An error occurred"); }); - it('should handle an error with details', async () => { + it("should handle an error with details", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC4039], } as ISupportedVersionsActionResponseData); @@ -587,52 +587,52 @@ describe('WidgetApi', () => { matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'Unknown error', + errcode: "M_UNKNOWN", + error: "Unknown error", }, }, }; widgetTransportHelper.queueResponse({ error: { - message: 'An error occurred', + message: "An error occurred", ...errorDetails, }, } as IWidgetApiErrorResponseData); await expect(widgetApi.getMediaConfig()).rejects.toThrow( - new WidgetApiResponseError('An error occurred', errorDetails), + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); - describe('uploadFile', () => { - it('should forward the request to the ClientWidgetApi', async () => { + describe("uploadFile", () => { + it("should forward the request to the ClientWidgetApi", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC4039], } as ISupportedVersionsActionResponseData); widgetTransportHelper.queueResponse({ - content_uri: 'mxc://...', + content_uri: "mxc://...", } as IUploadFileActionFromWidgetResponseData); - await expect(widgetApi.uploadFile('data')).resolves.toEqual({ - content_uri: 'mxc://...', + await expect(widgetApi.uploadFile("data")).resolves.toEqual({ + content_uri: "mxc://...", }); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); expect(widgetTransportHelper.nextTrackedRequest()).toEqual({ action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, - data: { file: 'data' }, + data: { file: "data" }, } satisfies SendRequestArgs); }); - it('should reject the request if the api is not supported', async () => { + it("should reject the request if the api is not supported", async () => { widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); - await expect(widgetApi.uploadFile('data')).rejects.toThrow( - 'The upload_file action is not supported by the client.', + await expect(widgetApi.uploadFile("data")).rejects.toThrow( + "The upload_file action is not supported by the client.", ); const request = widgetTransportHelper.nextTrackedRequest(); @@ -643,16 +643,16 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should handle an error', async () => { + it("should handle an error", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC4039], } as ISupportedVersionsActionResponseData); - widgetTransportHelper.queueResponse({ error: { message: 'An error occurred' } }); + widgetTransportHelper.queueResponse({ error: { message: "An error occurred" } }); - await expect(widgetApi.uploadFile('data')).rejects.toThrow('An error occurred'); + await expect(widgetApi.uploadFile("data")).rejects.toThrow("An error occurred"); }); - it('should handle an error with details', async () => { + it("should handle an error with details", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC4039], } as ISupportedVersionsActionResponseData); @@ -661,50 +661,50 @@ describe('WidgetApi', () => { matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'Unknown error', + errcode: "M_UNKNOWN", + error: "Unknown error", }, }, }; widgetTransportHelper.queueResponse({ error: { - message: 'An error occurred', + message: "An error occurred", ...errorDetails, }, } as IWidgetApiErrorResponseData); - await expect(widgetApi.uploadFile('data')).rejects.toThrow( - new WidgetApiResponseError('An error occurred', errorDetails), + await expect(widgetApi.uploadFile("data")).rejects.toThrow( + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); - describe('downloadFile', () => { - it('should forward the request to the ClientWidgetApi', async () => { + describe("downloadFile", () => { + it("should forward the request to the ClientWidgetApi", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC4039], } as ISupportedVersionsActionResponseData); - widgetTransportHelper.queueResponse({ file: 'test contents' } as IDownloadFileActionFromWidgetResponseData); + widgetTransportHelper.queueResponse({ file: "test contents" } as IDownloadFileActionFromWidgetResponseData); - await expect(widgetApi.downloadFile('mxc://example.com/test_file')).resolves.toEqual({ - file: 'test contents', + await expect(widgetApi.downloadFile("mxc://example.com/test_file")).resolves.toEqual({ + file: "test contents", }); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); expect(widgetTransportHelper.nextTrackedRequest()).toEqual({ action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, - data: { content_uri: 'mxc://example.com/test_file' }, + data: { content_uri: "mxc://example.com/test_file" }, } satisfies SendRequestArgs); }); - it('should reject the request if the api is not supported', async () => { + it("should reject the request if the api is not supported", async () => { widgetTransportHelper.queueResponse({ supported_versions: [] } as ISupportedVersionsActionResponseData); - await expect(widgetApi.downloadFile('mxc://example.com/test_file')).rejects.toThrow( - 'The download_file action is not supported by the client.', + await expect(widgetApi.downloadFile("mxc://example.com/test_file")).rejects.toThrow( + "The download_file action is not supported by the client.", ); const request = widgetTransportHelper.nextTrackedRequest(); @@ -715,16 +715,16 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should handle an error', async () => { + it("should handle an error", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC4039], } as ISupportedVersionsActionResponseData); - widgetTransportHelper.queueResponse({ error: { message: 'An error occurred' } }); + widgetTransportHelper.queueResponse({ error: { message: "An error occurred" } }); - await expect(widgetApi.downloadFile('mxc://example.com/test_file')).rejects.toThrow('An error occurred'); + await expect(widgetApi.downloadFile("mxc://example.com/test_file")).rejects.toThrow("An error occurred"); }); - it('should handle an error with details', async () => { + it("should handle an error with details", async () => { widgetTransportHelper.queueResponse({ supported_versions: [UnstableApiVersion.MSC4039], } as ISupportedVersionsActionResponseData); @@ -733,23 +733,23 @@ describe('WidgetApi', () => { matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'Unknown error', + errcode: "M_UNKNOWN", + error: "Unknown error", }, }, }; widgetTransportHelper.queueResponse({ error: { - message: 'An error occurred', + message: "An error occurred", ...errorDetails, }, } as IWidgetApiErrorResponseData); - await expect(widgetApi.downloadFile('mxc://example.com/test_file')).rejects.toThrow( - new WidgetApiResponseError('An error occurred', errorDetails), + await expect(widgetApi.downloadFile("mxc://example.com/test_file")).rejects.toThrow( + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); diff --git a/test/url-template-test.ts b/test/url-template-test.ts index cb4bafa..3f28df8 100644 --- a/test/url-template-test.ts +++ b/test/url-template-test.ts @@ -14,44 +14,44 @@ * limitations under the License. */ -import { runTemplate } from '../src'; +import { runTemplate } from "../src"; -describe('runTemplate', () => { - it('should replace device id template in url', () => { - const url = 'https://localhost/?my-query#device_id=$org.matrix.msc3819.matrix_device_id'; +describe("runTemplate", () => { + it("should replace device id template in url", () => { + const url = "https://localhost/?my-query#device_id=$org.matrix.msc3819.matrix_device_id"; const replacedUrl = runTemplate( url, { - id: 'widget-id', - creatorUserId: '@user-id', - type: 'type', + id: "widget-id", + creatorUserId: "@user-id", + type: "type", url, }, { - deviceId: 'my-device-id', - currentUserId: '@user-id', + deviceId: "my-device-id", + currentUserId: "@user-id", }, ); - expect(replacedUrl).toBe('https://localhost/?my-query#device_id=my-device-id'); + expect(replacedUrl).toBe("https://localhost/?my-query#device_id=my-device-id"); }); - it('should replace base url template in url', () => { - const url = 'https://localhost/?my-query#base_url=$org.matrix.msc4039.matrix_base_url'; + it("should replace base url template in url", () => { + const url = "https://localhost/?my-query#base_url=$org.matrix.msc4039.matrix_base_url"; const replacedUrl = runTemplate( url, { - id: 'widget-id', - creatorUserId: '@user-id', - type: 'type', + id: "widget-id", + creatorUserId: "@user-id", + type: "type", url, }, { - currentUserId: '@user-id', - baseUrl: 'https://localhost/api', + currentUserId: "@user-id", + baseUrl: "https://localhost/api", }, ); - expect(replacedUrl).toBe('https://localhost/?my-query#base_url=https%3A%2F%2Flocalhost%2Fapi'); + expect(replacedUrl).toBe("https://localhost/?my-query#base_url=https%3A%2F%2Flocalhost%2Fapi"); }); }); From 5e9810f2930267d93681b4e91682a26ce8a38a28 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 17:23:51 +0100 Subject: [PATCH 05/12] more 4 tab files --- .eslintrc.js | 96 ++++++++++++++++----------------- .github/workflows/sonarqube.yml | 24 ++++----- examples/widget/utils.js | 14 ++--- package.json | 4 +- 4 files changed, 70 insertions(+), 68 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 02a10fe..96ca83a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,52 +1,52 @@ module.exports = { - plugins: ["matrix-org"], - extends: ["plugin:matrix-org/babel"], - parserOptions: { - project: ["./tsconfig-dev.json"], - }, - env: { - browser: true, - }, - rules: { - "no-var": ["warn"], - "prefer-rest-params": ["warn"], - "prefer-spread": ["warn"], - "one-var": ["warn"], - "padded-blocks": ["warn"], - "no-extend-native": ["warn"], - "camelcase": ["warn"], - "no-multi-spaces": ["error", { ignoreEOLComments: true }], - "space-before-function-paren": [ - "error", - { - anonymous: "never", - named: "never", - asyncArrow: "always", - }, - ], - "arrow-parens": "off", - "prefer-promise-reject-errors": "off", - "quotes": "off", - "indent": "off", - "no-constant-condition": "off", - "no-async-promise-executor": "off", - }, - overrides: [ - { - files: ["src/**/*.ts", "test/**/*.ts"], - extends: ["plugin:matrix-org/typescript"], - rules: { - // TypeScript has its own version of this - "babel/no-invalid-this": "off", - - "quotes": "off", - }, + plugins: ["matrix-org"], + extends: ["plugin:matrix-org/babel"], + parserOptions: { + project: ["./tsconfig-dev.json"], }, - { - files: ["src/interfaces/**/*.ts"], - rules: { - "@typescript-eslint/no-empty-object-type": "off", - }, + env: { + browser: true, }, - ], + rules: { + "no-var": ["warn"], + "prefer-rest-params": ["warn"], + "prefer-spread": ["warn"], + "one-var": ["warn"], + "padded-blocks": ["warn"], + "no-extend-native": ["warn"], + "camelcase": ["warn"], + "no-multi-spaces": ["error", { ignoreEOLComments: true }], + "space-before-function-paren": [ + "error", + { + anonymous: "never", + named: "never", + asyncArrow: "always", + }, + ], + "arrow-parens": "off", + "prefer-promise-reject-errors": "off", + "quotes": "off", + "indent": "off", + "no-constant-condition": "off", + "no-async-promise-executor": "off", + }, + overrides: [ + { + files: ["src/**/*.ts", "test/**/*.ts"], + extends: ["plugin:matrix-org/typescript"], + rules: { + // TypeScript has its own version of this + "babel/no-invalid-this": "off", + + "quotes": "off", + }, + }, + { + files: ["src/interfaces/**/*.ts"], + rules: { + "@typescript-eslint/no-empty-object-type": "off", + }, + }, + ], }; diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 2773eaa..c539966 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,16 +1,16 @@ name: SonarQube on: - workflow_run: - workflows: ["Build and test"] - types: - - completed + workflow_run: + workflows: ["Build and test"] + types: + - completed concurrency: - group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true jobs: - sonarqube: - name: 🩻 SonarQube - uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop - secrets: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + sonarqube: + name: 🩻 SonarQube + uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/examples/widget/utils.js b/examples/widget/utils.js index 0a3cd49..705a6f0 100644 --- a/examples/widget/utils.js +++ b/examples/widget/utils.js @@ -15,17 +15,17 @@ */ function parseFragment() { - const fragmentString = window.location.hash || "?"; - return new URLSearchParams(fragmentString.substring(Math.max(fragmentString.indexOf("?"), 0))); + const fragmentString = window.location.hash || "?"; + return new URLSearchParams(fragmentString.substring(Math.max(fragmentString.indexOf("?"), 0))); } function assertParam(fragment, name) { - const val = fragment.get(name); - if (!val) throw new Error(`${name} is not present in URL - cannot load widget`); - return val; + const val = fragment.get(name); + if (!val) throw new Error(`${name} is not present in URL - cannot load widget`); + return val; } function handleError(e) { - console.error(e); - document.getElementById("container").innerText = "There was an error with the widget. See JS console for details."; + console.error(e); + document.getElementById("container").innerText = "There was an error with the widget. See JS console for details."; } diff --git a/package.json b/package.json index 3e4a189..d7d78c2 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ { "files": [ "src/**/*.ts", - "test/**/*.ts" + "*.js", + "test/**/*.ts", + "*.yml" ], "options": { "tabWidth": 4 From 8980351784fbd99917a106f36c6c70c79719d5ee Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 14:46:29 +0100 Subject: [PATCH 06/12] prettier --- src/ClientWidgetApi.ts | 14 +++--- src/WidgetApi.ts | 108 ++++++++++++++++++++--------------------- 2 files changed, 60 insertions(+), 62 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index d3bf49d..75cdd4d 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -985,18 +985,16 @@ export class ClientWidgetApi extends EventEmitter { }); } - public sendWidgetConfig(data: IModalWidgetOpenRequestData): Promise { - return this.transport.send(WidgetApiToWidgetAction.WidgetConfig, data).then(); + public async sendWidgetConfig(data: IModalWidgetOpenRequestData): Promise { + await this.transport.send(WidgetApiToWidgetAction.WidgetConfig, data); } - public notifyModalWidgetButtonClicked(id: IModalWidgetOpenRequestDataButton["id"]): Promise { - return this.transport - .send(WidgetApiToWidgetAction.ButtonClicked, { id }) - .then(); + public async notifyModalWidgetButtonClicked(id: IModalWidgetOpenRequestDataButton["id"]): Promise { + await this.transport.send(WidgetApiToWidgetAction.ButtonClicked, { id }); } - public notifyModalWidgetClose(data: IModalWidgetReturnData): Promise { - return this.transport.send(WidgetApiToWidgetAction.CloseModalWidget, data).then(); + public async notifyModalWidgetClose(data: IModalWidgetReturnData): Promise { + await this.transport.send(WidgetApiToWidgetAction.CloseModalWidget, data); } /** diff --git a/src/WidgetApi.ts b/src/WidgetApi.ts index 44f0de9..d9ac4a3 100644 --- a/src/WidgetApi.ts +++ b/src/WidgetApi.ts @@ -355,7 +355,7 @@ export class WidgetApi extends EventEmitter { * the capabilities request has gone through, not when the capabilities are approved/denied. * Use the WidgetApiToWidgetAction.NotifyCapabilities action to detect changes. */ - public updateRequestedCapabilities(): Promise { + public async updateRequestedCapabilities(): Promise { return this.transport .send(WidgetApiFromWidgetAction.MSC2974RenegotiateCapabilities, { capabilities: this.requestedCapabilities, @@ -367,7 +367,7 @@ export class WidgetApi extends EventEmitter { * Tell the client that the content has been loaded. * @returns {Promise} Resolves when the client acknowledges the request. */ - public sendContentLoaded(): Promise { + public async sendContentLoaded(): Promise { return this.transport.send(WidgetApiFromWidgetAction.ContentLoaded, {}).then(); } @@ -376,7 +376,7 @@ export class WidgetApi extends EventEmitter { * @param {IStickerActionRequestData} sticker The sticker to send. * @returns {Promise} Resolves when the client acknowledges the request. */ - public sendSticker(sticker: IStickerActionRequestData): Promise { + public async sendSticker(sticker: IStickerActionRequestData): Promise { return this.transport.send(WidgetApiFromWidgetAction.SendSticker, sticker).then(); } @@ -386,12 +386,11 @@ export class WidgetApi extends EventEmitter { * @returns {Promise} Resolve with true if the client was able to fulfill * the request, resolves to false otherwise. Rejects if an error occurred. */ - public setAlwaysOnScreen(value: boolean): Promise { + public async setAlwaysOnScreen(value: boolean): Promise { return this.transport - .send< - IStickyActionRequestData, - IStickyActionResponseData - >(WidgetApiFromWidgetAction.UpdateAlwaysOnScreen, { value }) + .send(WidgetApiFromWidgetAction.UpdateAlwaysOnScreen, { + value, + }) .then((res) => res.success); } @@ -404,7 +403,7 @@ export class WidgetApi extends EventEmitter { * @param {WidgetType} type The type of modal widget. * @returns {Promise} Resolves when the modal widget has been opened. */ - public openModalWidget( + public async openModalWidget( url: string, name: string, buttons: IModalWidgetOpenRequestDataButton[] = [], @@ -427,7 +426,7 @@ export class WidgetApi extends EventEmitter { * @param {IModalWidgetReturnData} data Optional data to close the modal widget with. * @returns {Promise} Resolves when complete. */ - public closeModalWidget(data: IModalWidgetReturnData = {}): Promise { + public async closeModalWidget(data: IModalWidgetReturnData = {}): Promise { return this.transport.send(WidgetApiFromWidgetAction.CloseModalWidget, data).then(); } @@ -503,11 +502,18 @@ export class WidgetApi extends EventEmitter { ): Promise { return this.transport.send( WidgetApiFromWidgetAction.SendToDevice, - { type: eventType, encrypted, messages: contentMap }, + { + type: eventType, + encrypted, + messages: contentMap, + }, ); } - public readRoomAccountData(eventType: string, roomIds?: (string | Symbols.AnyRoom)[]): Promise { + public async readRoomAccountData( + eventType: string, + roomIds?: (string | Symbols.AnyRoom)[], + ): Promise { const data: IReadEventFromWidgetRequestData = { type: eventType }; if (roomIds) { @@ -517,15 +523,14 @@ export class WidgetApi extends EventEmitter { data.room_ids = roomIds; } } - return this.transport - .send< - IReadRoomAccountDataFromWidgetRequestData, - IReadRoomAccountDataFromWidgetResponseData - >(WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data) - .then((r) => r.events); + const r = await this.transport.send< + IReadRoomAccountDataFromWidgetRequestData, + IReadRoomAccountDataFromWidgetResponseData + >(WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data); + return r.events; } - public readRoomEvents( + public async readRoomEvents( eventType: string, limit?: number, msgtype?: string, @@ -546,12 +551,11 @@ export class WidgetApi extends EventEmitter { if (since) { data.since = since; } - return this.transport - .send< - IReadEventFromWidgetRequestData, - IReadEventFromWidgetResponseData - >(WidgetApiFromWidgetAction.MSC2876ReadEvents, data) - .then((r) => r.events); + const r = await this.transport.send( + WidgetApiFromWidgetAction.MSC2876ReadEvents, + data, + ); + return r.events; } /** @@ -605,7 +609,7 @@ export class WidgetApi extends EventEmitter { ); } - public readStateEvents( + public async readStateEvents( eventType: string, limit?: number, stateKey?: string, @@ -625,12 +629,11 @@ export class WidgetApi extends EventEmitter { data.room_ids = roomIds; } } - return this.transport - .send< - IReadEventFromWidgetRequestData, - IReadEventFromWidgetResponseData - >(WidgetApiFromWidgetAction.MSC2876ReadEvents, data) - .then((r) => r.events); + const r = await this.transport.send( + WidgetApiFromWidgetAction.MSC2876ReadEvents, + data, + ); + return r.events; } /** @@ -640,16 +643,17 @@ export class WidgetApi extends EventEmitter { * @returns {Promise} Resolves when complete. * @throws Throws if the button cannot be disabled, or the client refuses to disable the button. */ - public setModalButtonEnabled(buttonId: ModalButtonID, isEnabled: boolean): Promise { + public async setModalButtonEnabled(buttonId: ModalButtonID, isEnabled: boolean): Promise { if (buttonId === BuiltInModalButtonID.Close) { throw new Error("The close button cannot be disabled"); } - return this.transport - .send(WidgetApiFromWidgetAction.SetModalButtonEnabled, { + await this.transport.send( + WidgetApiFromWidgetAction.SetModalButtonEnabled, + { button: buttonId, enabled: isEnabled, - }) - .then(); + }, + ); } /** @@ -660,14 +664,12 @@ export class WidgetApi extends EventEmitter { * @throws Throws if the URI is invalid or cannot be processed. * @deprecated This currently relies on an unstable MSC (MSC2931). */ - public navigateTo(uri: string): Promise { + public async navigateTo(uri: string): Promise { if (!uri || !uri.startsWith("https://matrix.to/#")) { throw new Error("Invalid matrix.to URI"); } - return this.transport - .send(WidgetApiFromWidgetAction.MSC2931Navigate, { uri }) - .then(); + await this.transport.send(WidgetApiFromWidgetAction.MSC2931Navigate, { uri }); } /** @@ -681,7 +683,7 @@ export class WidgetApi extends EventEmitter { const onUpdateTurnServers = async (ev: CustomEvent): Promise => { ev.preventDefault(); setTurnServer(ev.detail.data); - await this.transport.reply(ev.detail, {}); + this.transport.reply(ev.detail, {}); }; // Start listening for updates before we even start watching, to catch @@ -849,24 +851,22 @@ export class WidgetApi extends EventEmitter { }); } - public getClientVersions(): Promise { + public async getClientVersions(): Promise { if (Array.isArray(this.cachedClientVersions)) { return Promise.resolve(this.cachedClientVersions); } - return this.transport - .send( + try { + const r = await this.transport.send( WidgetApiFromWidgetAction.SupportedApiVersions, {}, - ) - .then((r) => { - this.cachedClientVersions = r.supported_versions; - return r.supported_versions; - }) - .catch((e) => { - console.warn("non-fatal error getting supported client versions: ", e); - return []; - }); + ); + this.cachedClientVersions = r.supported_versions; + return r.supported_versions; + } catch (e) { + console.warn("non-fatal error getting supported client versions: ", e); + return []; + } } private handleCapabilities(request: ICapabilitiesActionRequest): void | Promise { From d6e682dc9b40abc39d7ca9ade6564745e9fd67f1 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 16:33:09 +0100 Subject: [PATCH 07/12] Add new version for UPDATE_STATE --- src/interfaces/ApiVersion.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/interfaces/ApiVersion.ts b/src/interfaces/ApiVersion.ts index ab0546e..8c7fd9a 100644 --- a/src/interfaces/ApiVersion.ts +++ b/src/interfaces/ApiVersion.ts @@ -22,6 +22,7 @@ export enum MatrixApiVersion { export enum UnstableApiVersion { MSC2762 = "org.matrix.msc2762", + MSC2762_UPDATE_STATE = "org.matrix.msc2762_update_state", MSC2871 = "org.matrix.msc2871", MSC2873 = "org.matrix.msc2873", MSC2931 = "org.matrix.msc2931", @@ -41,6 +42,7 @@ export const CurrentApiVersions: ApiVersion[] = [ MatrixApiVersion.Prerelease2, //MatrixApiVersion.V010, UnstableApiVersion.MSC2762, + UnstableApiVersion.MSC2762_UPDATE_STATE, UnstableApiVersion.MSC2871, UnstableApiVersion.MSC2873, UnstableApiVersion.MSC2931, From 01626e6bdcd914dde584413c9f3b263f58af7d3f Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 16:34:27 +0100 Subject: [PATCH 08/12] Let the client only send `UpdateState` if the widget supports it. --- src/ClientWidgetApi.ts | 43 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index 75cdd4d..6400e61 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -40,7 +40,7 @@ import { ISupportedVersionsActionRequest, ISupportedVersionsActionResponseData, } from "./interfaces/SupportedVersionsAction"; -import { CurrentApiVersions } from "./interfaces/ApiVersion"; +import { ApiVersion, CurrentApiVersions, UnstableApiVersion } from "./interfaces/ApiVersion"; import { IScreenshotActionResponseData } from "./interfaces/ScreenshotAction"; import { IVisibilityActionRequestData } from "./interfaces/VisibilityAction"; import { IWidgetApiAcknowledgeResponseData, IWidgetApiResponseData } from "./interfaces/IWidgetApiResponse"; @@ -138,6 +138,7 @@ import { IUpdateStateToWidgetRequestData } from "./interfaces/UpdateStateAction" export class ClientWidgetApi extends EventEmitter { public readonly transport: ITransport; + private cachedWidgetVersions: ApiVersion[] | null = null; // contentLoadedActionSent is used to check that only one ContentLoaded request is send. private contentLoadedActionSent = false; private allowedCapabilities = new Set(); @@ -227,6 +228,24 @@ export class ClientWidgetApi extends EventEmitter { this.transport.stop(); } + public async getWidgetVersions(): Promise { + if (Array.isArray(this.cachedWidgetVersions)) { + return Promise.resolve(this.cachedWidgetVersions); + } + + try { + const r = await this.transport.send( + WidgetApiToWidgetAction.SupportedApiVersions, + {}, + ); + this.cachedWidgetVersions = r.supported_versions; + return r.supported_versions; + } catch (e) { + console.warn("non-fatal error getting supported widget versions: ", e); + return []; + } + } + private beginCapabilities(): void { // widget has loaded - tell all the listeners that this.emit("preparing"); @@ -1013,7 +1032,7 @@ export class ClientWidgetApi extends EventEmitter { public async feedEvent(rawEvent: IRoomEvent, currentViewedRoomId: string): Promise; /** * Feeds an event to the widget. As a client you are expected to call this - * for every new event in every room to which you are joined or invited. + * for every new event (including state events) in every room to which you are joined or invited. * @param {IRoomEvent} rawEvent The event to (try to) send to the widget. * @returns {Promise} Resolves when delivered or if the widget is not * able to read the event due to permissions, rejects if the widget failed @@ -1082,6 +1101,7 @@ export class ClientWidgetApi extends EventEmitter { } private async flushRoomState(): Promise { + const useUpdateState = (await this.getWidgetVersions()).includes(UnstableApiVersion.MSC2762_UPDATE_STATE); try { // Only send a single action once all concurrent tasks have completed do await Promise.all([...this.pushRoomStateTasks]); @@ -1093,9 +1113,11 @@ export class ClientWidgetApi extends EventEmitter { events.push(...stateKeyMap.values()); } } - await this.transport.send(WidgetApiToWidgetAction.UpdateState, { - state: events, - }); + if (useUpdateState) { + await this.transport.send(WidgetApiToWidgetAction.UpdateState, { + state: events, + }); + } } finally { this.flushRoomStateTask = null; } @@ -1161,6 +1183,8 @@ export class ClientWidgetApi extends EventEmitter { widget failed to handle the update. */ public async feedStateUpdate(rawEvent: IRoomEvent): Promise { + const useUpdateState = (await this.getWidgetVersions()).includes(UnstableApiVersion.MSC2762_UPDATE_STATE); + if (rawEvent.state_key === undefined) throw new Error("Not a state event"); if ( (rawEvent.room_id === this.viewedRoomId || this.canUseRoomTimeline(rawEvent.room_id)) && @@ -1169,9 +1193,12 @@ export class ClientWidgetApi extends EventEmitter { // Updates could race with the initial push of the room's state if (this.pushRoomStateTasks.size === 0) { // No initial push tasks are pending; safe to send immediately - await this.transport.send(WidgetApiToWidgetAction.UpdateState, { - state: [rawEvent], - }); + if (useUpdateState) { + // Only send state updates when using UpdateState. Otherwise we will use SendEvent. + await this.transport.send(WidgetApiToWidgetAction.UpdateState, { + state: [rawEvent], + }); + } } else { // Lump the update in with whatever data will be sent in the // initial push later. Even if we set it to an "outdated" entry From 52cb7a859db85e98d7519073514703f73980f7b0 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 17:01:34 +0100 Subject: [PATCH 09/12] fix tests --- test/ClientWidgetApi-test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index 0a261f2..3aa50b1 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -19,7 +19,7 @@ import { waitFor } from "@testing-library/dom"; import { ClientWidgetApi } from "../src/ClientWidgetApi"; import { WidgetDriver } from "../src/driver/WidgetDriver"; -import { UnstableApiVersion } from "../src/interfaces/ApiVersion"; +import { CurrentApiVersions, UnstableApiVersion } from "../src/interfaces/ApiVersion"; import { Capability } from "../src/interfaces/Capabilities"; import { IRoomEvent } from "../src/interfaces/IRoomEvent"; import { IWidgetApiRequest } from "../src/interfaces/IWidgetApiRequest"; @@ -759,6 +759,14 @@ describe("ClientWidgetApi", () => { const roomId = "!room:example.org"; const otherRoomId = "!other-room:example.org"; clientWidgetApi.setViewedRoomId(roomId); + + jest.spyOn(transport, "send").mockImplementation((action, data) => { + if (action === WidgetApiToWidgetAction.SupportedApiVersions) { + return Promise.resolve({ supported_versions: CurrentApiVersions }); + } + return Promise.resolve({}); + }); + const topicEvent = createRoomEvent({ room_id: roomId, type: "m.room.topic", From 5312be1eccab03262f21cfd8cb9975f909a31f9b Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 17:10:48 +0100 Subject: [PATCH 10/12] smaller diff --- package.json | 8 + test/ClientWidgetApi-test.ts | 1340 +++++++++++++++++----------------- 2 files changed, 678 insertions(+), 670 deletions(-) diff --git a/package.json b/package.json index d7d78c2..b7d496b 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,14 @@ "options": { "tabWidth": 4 } + }, + { + "files": [ + "test/**/*.ts" + ], + "options": { + "singleQuote": true + } } ] }, diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index 3aa50b1..e19bdb7 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -15,21 +15,21 @@ * limitations under the License. */ -import { waitFor } from "@testing-library/dom"; - -import { ClientWidgetApi } from "../src/ClientWidgetApi"; -import { WidgetDriver } from "../src/driver/WidgetDriver"; -import { CurrentApiVersions, UnstableApiVersion } from "../src/interfaces/ApiVersion"; -import { Capability } from "../src/interfaces/Capabilities"; -import { IRoomEvent } from "../src/interfaces/IRoomEvent"; -import { IWidgetApiRequest } from "../src/interfaces/IWidgetApiRequest"; -import { IReadRelationsFromWidgetActionRequest } from "../src/interfaces/ReadRelationsAction"; -import { ISupportedVersionsActionRequest } from "../src/interfaces/SupportedVersionsAction"; -import { IUserDirectorySearchFromWidgetActionRequest } from "../src/interfaces/UserDirectorySearchAction"; -import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "../src/interfaces/WidgetApiAction"; -import { WidgetApiDirection } from "../src/interfaces/WidgetApiDirection"; -import { Widget } from "../src/models/Widget"; -import { PostmessageTransport } from "../src/transport/PostmessageTransport"; +import { waitFor } from '@testing-library/dom'; + +import { ClientWidgetApi } from '../src/ClientWidgetApi'; +import { WidgetDriver } from '../src/driver/WidgetDriver'; +import { CurrentApiVersions, UnstableApiVersion } from '../src/interfaces/ApiVersion'; +import { Capability } from '../src/interfaces/Capabilities'; +import { IRoomEvent } from '../src/interfaces/IRoomEvent'; +import { IWidgetApiRequest } from '../src/interfaces/IWidgetApiRequest'; +import { IReadRelationsFromWidgetActionRequest } from '../src/interfaces/ReadRelationsAction'; +import { ISupportedVersionsActionRequest } from '../src/interfaces/SupportedVersionsAction'; +import { IUserDirectorySearchFromWidgetActionRequest } from '../src/interfaces/UserDirectorySearchAction'; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from '../src/interfaces/WidgetApiAction'; +import { WidgetApiDirection } from '../src/interfaces/WidgetApiDirection'; +import { Widget } from '../src/models/Widget'; +import { PostmessageTransport } from '../src/transport/PostmessageTransport'; import { IDownloadFileActionFromWidgetActionRequest, IGetOpenIDActionRequest, @@ -45,11 +45,11 @@ import { SimpleObservable, Symbols, UpdateDelayedEventAction, -} from "../src"; -import { IGetMediaConfigActionFromWidgetActionRequest } from "../src/interfaces/GetMediaConfigAction"; -import { IReadRoomAccountDataFromWidgetActionRequest } from "../src/interfaces/ReadRoomAccountDataAction"; +} from '../src'; +import { IGetMediaConfigActionFromWidgetActionRequest } from '../src/interfaces/GetMediaConfigAction'; +import { IReadRoomAccountDataFromWidgetActionRequest } from '../src/interfaces/ReadRoomAccountDataAction'; -jest.mock("../src/transport/PostmessageTransport"); +jest.mock('../src/transport/PostmessageTransport'); afterEach(() => { jest.resetAllMocks(); @@ -57,12 +57,12 @@ afterEach(() => { function createRoomEvent(event: Partial = {}): IRoomEvent { return { - type: "m.room.message", - sender: "user-id", + type: 'm.room.message', + sender: 'user-id', content: {}, origin_server_ts: 0, - event_id: "id-0", - room_id: "!room-id", + event_id: 'id-0', + room_id: '!room-id', unsigned: {}, ...event, }; @@ -85,7 +85,7 @@ function processCustomMatrixError(e: unknown): IWidgetApiErrorResponseDataDetail matrix_api_error: { http_status: e.httpStatus, http_headers: {}, - url: "", + url: '', response: { errcode: e.name, error: e.message, @@ -96,29 +96,29 @@ function processCustomMatrixError(e: unknown): IWidgetApiErrorResponseDataDetail : undefined; } -describe("ClientWidgetApi", () => { +describe('ClientWidgetApi', () => { let capabilities: Capability[]; let iframe: HTMLIFrameElement; let driver: jest.Mocked; let clientWidgetApi: ClientWidgetApi; let transport: PostmessageTransport; - let emitEvent: Parameters["1"]; + let emitEvent: Parameters['1']; async function loadIframe(caps: Capability[] = []): Promise { capabilities = caps; const ready = new Promise((resolve) => { - clientWidgetApi.once("ready", resolve); + clientWidgetApi.once('ready', resolve); }); - iframe.dispatchEvent(new Event("load")); + iframe.dispatchEvent(new Event('load')); await ready; } beforeEach(() => { capabilities = []; - iframe = document.createElement("iframe"); + iframe = document.createElement('iframe'); document.body.appendChild(iframe); driver = { @@ -143,10 +143,10 @@ describe("ClientWidgetApi", () => { clientWidgetApi = new ClientWidgetApi( new Widget({ - id: "test", - creatorUserId: "@alice:example.org", - type: "example", - url: "https://example.org", + id: 'test', + creatorUserId: '@alice:example.org', + type: 'example', + url: 'https://example.org', }), iframe, driver, @@ -164,30 +164,30 @@ describe("ClientWidgetApi", () => { iframe.remove(); }); - it("should initiate capabilities", async () => { - await loadIframe(["m.always_on_screen"]); + it('should initiate capabilities', async () => { + await loadIframe(['m.always_on_screen']); - expect(clientWidgetApi.hasCapability("m.always_on_screen")).toBe(true); - expect(clientWidgetApi.hasCapability("m.sticker")).toBe(false); + expect(clientWidgetApi.hasCapability('m.always_on_screen')).toBe(true); + expect(clientWidgetApi.hasCapability('m.sticker')).toBe(false); }); - describe("navigate action", () => { - it("navigates", async () => { + describe('navigate action', () => { + it('navigates', async () => { driver.navigate.mockResolvedValue(Promise.resolve()); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: "https://matrix.to/#/#room:example.net", + uri: 'https://matrix.to/#/#room:example.net', }, }; - await loadIframe(["org.matrix.msc2931.navigate"]); + await loadIframe(['org.matrix.msc2931.navigate']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, {}); @@ -196,113 +196,113 @@ describe("ClientWidgetApi", () => { expect(driver.navigate).toHaveBeenCalledWith(event.data.uri); }); - it("fails to navigate", async () => { + it('fails to navigate', async () => { const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: "https://matrix.to/#/#room:example.net", + uri: 'https://matrix.to/#/#room:example.net', }, }; await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Missing capability" }, + error: { message: 'Missing capability' }, }); }); expect(driver.navigate).not.toBeCalled(); }); - it("fails to navigate to an unsupported URI", async () => { + it('fails to navigate to an unsupported URI', async () => { const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: "https://example.net", + uri: 'https://example.net', }, }; - await loadIframe(["org.matrix.msc2931.navigate"]); + await loadIframe(['org.matrix.msc2931.navigate']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Invalid matrix.to URI" }, + error: { message: 'Invalid matrix.to URI' }, }); }); expect(driver.navigate).not.toBeCalled(); }); - it("should reject requests when the driver throws an exception", async () => { - driver.navigate.mockRejectedValue(new Error("M_UNKNOWN: Unknown error")); + it('should reject requests when the driver throws an exception', async () => { + driver.navigate.mockRejectedValue(new Error('M_UNKNOWN: Unknown error')); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: "https://matrix.to/#/#room:example.net", + uri: 'https://matrix.to/#/#room:example.net', }, }; - await loadIframe(["org.matrix.msc2931.navigate"]); + await loadIframe(['org.matrix.msc2931.navigate']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Error handling navigation" }, + error: { message: 'Error handling navigation' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { + it('should reject with Matrix API error response thrown by driver', async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.navigate.mockRejectedValue( - new CustomMatrixError("failed to navigate", 400, "M_UNKNOWN", { - reason: "Unknown error", + new CustomMatrixError('failed to navigate', 400, 'M_UNKNOWN', { + reason: 'Unknown error', }), ); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: "https://matrix.to/#/#room:example.net", + uri: 'https://matrix.to/#/#room:example.net', }, }; - await loadIframe(["org.matrix.msc2931.navigate"]); + await loadIframe(['org.matrix.msc2931.navigate']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Error handling navigation", + message: 'Error handling navigation', matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_UNKNOWN", - error: "failed to navigate", - reason: "Unknown error", + errcode: 'M_UNKNOWN', + error: 'failed to navigate', + reason: 'Unknown error', }, } satisfies IMatrixApiError, }, @@ -311,10 +311,10 @@ describe("ClientWidgetApi", () => { }); }); - describe("send_event action", () => { - it("sends message events", async () => { - const roomId = "!room:example.org"; - const eventId = "$event:example.org"; + describe('send_event action', () => { + it('sends message events', async () => { + const roomId = '!room:example.org'; + const eventId = '$event:example.org'; driver.sendEvent.mockResolvedValue({ roomId, @@ -323,11 +323,11 @@ describe("ClientWidgetApi", () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendEvent, data: { - type: "m.room.message", + type: 'm.room.message', content: {}, room_id: roomId, }, @@ -338,7 +338,7 @@ describe("ClientWidgetApi", () => { `org.matrix.msc2762.send.event:${event.data.type}`, ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -350,9 +350,9 @@ describe("ClientWidgetApi", () => { expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, null, roomId); }); - it("sends state events", async () => { - const roomId = "!room:example.org"; - const eventId = "$event:example.org"; + it('sends state events', async () => { + const roomId = '!room:example.org'; + const eventId = '$event:example.org'; driver.sendEvent.mockResolvedValue({ roomId, @@ -361,13 +361,13 @@ describe("ClientWidgetApi", () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendEvent, data: { - type: "m.room.topic", + type: 'm.room.topic', content: {}, - state_key: "", + state_key: '', room_id: roomId, }, }; @@ -377,7 +377,7 @@ describe("ClientWidgetApi", () => { `org.matrix.msc2762.send.state_event:${event.data.type}`, ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -386,22 +386,22 @@ describe("ClientWidgetApi", () => { }); }); - expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, "", roomId); + expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, '', roomId); }); - it("should reject requests when the driver throws an exception", async () => { - const roomId = "!room:example.org"; + it('should reject requests when the driver throws an exception', async () => { + const roomId = '!room:example.org'; - driver.sendEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object")); + driver.sendEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendEvent, data: { - type: "m.room.message", - content: "hello", + type: 'm.room.message', + content: 'hello', room_id: roomId, }, }; @@ -411,34 +411,34 @@ describe("ClientWidgetApi", () => { `org.matrix.msc2762.send.event:${event.data.type}`, ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Error sending event" }, + error: { message: 'Error sending event' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { - const roomId = "!room:example.org"; + it('should reject with Matrix API error response thrown by driver', async () => { + const roomId = '!room:example.org'; driver.processError.mockImplementation(processCustomMatrixError); driver.sendEvent.mockRejectedValue( - new CustomMatrixError("failed to send event", 400, "M_NOT_JSON", { - reason: "Content must be a JSON object.", + new CustomMatrixError('failed to send event', 400, 'M_NOT_JSON', { + reason: 'Content must be a JSON object.', }), ); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendEvent, data: { - type: "m.room.message", - content: "hello", + type: 'm.room.message', + content: 'hello', room_id: roomId, }, }; @@ -448,20 +448,20 @@ describe("ClientWidgetApi", () => { `org.matrix.msc2762.send.event:${event.data.type}`, ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Error sending event", + message: 'Error sending event', matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_NOT_JSON", - error: "failed to send event", - reason: "Content must be a JSON object.", + errcode: 'M_NOT_JSON', + error: 'failed to send event', + reason: 'Content must be a JSON object.', }, } satisfies IMatrixApiError, }, @@ -470,17 +470,17 @@ describe("ClientWidgetApi", () => { }); }); - describe("send_event action for delayed events", () => { - it("fails to send delayed events", async () => { - const roomId = "!room:example.org"; + describe('send_event action for delayed events', () => { + it('fails to send delayed events', async () => { + const roomId = '!room:example.org'; const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendEvent, data: { - type: "m.room.message", + type: 'm.room.message', content: {}, delay: 5000, room_id: roomId, @@ -493,7 +493,7 @@ describe("ClientWidgetApi", () => { // Without the required capability ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -504,10 +504,10 @@ describe("ClientWidgetApi", () => { expect(driver.sendDelayedEvent).not.toBeCalled(); }); - it("sends delayed message events", async () => { - const roomId = "!room:example.org"; - const parentDelayId = "fp"; - const timeoutDelayId = "ft"; + it('sends delayed message events', async () => { + const roomId = '!room:example.org'; + const parentDelayId = 'fp'; + const timeoutDelayId = 'ft'; driver.sendDelayedEvent.mockResolvedValue({ roomId, @@ -516,11 +516,11 @@ describe("ClientWidgetApi", () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendEvent, data: { - type: "m.room.message", + type: 'm.room.message', content: {}, room_id: roomId, delay: 5000, @@ -531,10 +531,10 @@ describe("ClientWidgetApi", () => { await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, - "org.matrix.msc4157.send.delayed_event", + 'org.matrix.msc4157.send.delayed_event', ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -553,10 +553,10 @@ describe("ClientWidgetApi", () => { ); }); - it("sends delayed state events", async () => { - const roomId = "!room:example.org"; - const parentDelayId = "fp"; - const timeoutDelayId = "ft"; + it('sends delayed state events', async () => { + const roomId = '!room:example.org'; + const parentDelayId = 'fp'; + const timeoutDelayId = 'ft'; driver.sendDelayedEvent.mockResolvedValue({ roomId, @@ -565,13 +565,13 @@ describe("ClientWidgetApi", () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendEvent, data: { - type: "m.room.topic", + type: 'm.room.topic', content: {}, - state_key: "", + state_key: '', room_id: roomId, delay: 5000, parent_delay_id: parentDelayId, @@ -581,10 +581,10 @@ describe("ClientWidgetApi", () => { await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.state_event:${event.data.type}`, - "org.matrix.msc4157.send.delayed_event", + 'org.matrix.msc4157.send.delayed_event', ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -598,90 +598,90 @@ describe("ClientWidgetApi", () => { event.data.parent_delay_id, event.data.type, event.data.content, - "", + '', roomId, ); }); - it("should reject requests when the driver throws an exception", async () => { - const roomId = "!room:example.org"; + it('should reject requests when the driver throws an exception', async () => { + const roomId = '!room:example.org'; - driver.sendDelayedEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object")); + driver.sendDelayedEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendEvent, data: { - type: "m.room.message", - content: "hello", + type: 'm.room.message', + content: 'hello', room_id: roomId, delay: 5000, - parent_delay_id: "fp", + parent_delay_id: 'fp', }, }; await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, - "org.matrix.msc4157.send.delayed_event", + 'org.matrix.msc4157.send.delayed_event', ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Error sending event" }, + error: { message: 'Error sending event' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { - const roomId = "!room:example.org"; + it('should reject with Matrix API error response thrown by driver', async () => { + const roomId = '!room:example.org'; driver.processError.mockImplementation(processCustomMatrixError); driver.sendDelayedEvent.mockRejectedValue( - new CustomMatrixError("failed to send event", 400, "M_NOT_JSON", { - reason: "Content must be a JSON object.", + new CustomMatrixError('failed to send event', 400, 'M_NOT_JSON', { + reason: 'Content must be a JSON object.', }), ); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendEvent, data: { - type: "m.room.message", - content: "hello", + type: 'm.room.message', + content: 'hello', room_id: roomId, delay: 5000, - parent_delay_id: "fp", + parent_delay_id: 'fp', }, }; await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, - "org.matrix.msc4157.send.delayed_event", + 'org.matrix.msc4157.send.delayed_event', ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Error sending event", + message: 'Error sending event', matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_NOT_JSON", - error: "failed to send event", - reason: "Content must be a JSON object.", + errcode: 'M_NOT_JSON', + error: 'failed to send event', + reason: 'Content must be a JSON object.', }, } satisfies IMatrixApiError, }, @@ -690,21 +690,21 @@ describe("ClientWidgetApi", () => { }); }); - describe("receiving events", () => { - const roomId = "!room:example.org"; - const otherRoomId = "!other-room:example.org"; - const event = createRoomEvent({ room_id: roomId, type: "m.room.message", content: "hello" }); + describe('receiving events', () => { + const roomId = '!room:example.org'; + const otherRoomId = '!other-room:example.org'; + const event = createRoomEvent({ room_id: roomId, type: 'm.room.message', content: 'hello' }); const eventFromOtherRoom = createRoomEvent({ room_id: otherRoomId, - type: "m.room.message", - content: "test", + type: 'm.room.message', + content: 'test', }); - it("forwards events to the widget from one room only", async () => { + it('forwards events to the widget from one room only', async () => { // Give the widget capabilities to receive from just one room await loadIframe([ `org.matrix.msc2762.timeline:${roomId}`, - "org.matrix.msc2762.receive.event:m.room.message", + 'org.matrix.msc2762.receive.event:m.room.message', ]); // Event from the matching room should be forwarded @@ -716,13 +716,13 @@ describe("ClientWidgetApi", () => { expect(transport.send).not.toHaveBeenCalledWith(WidgetApiToWidgetAction.SendEvent, eventFromOtherRoom); }); - it("forwards events to the widget from the currently viewed room", async () => { + it('forwards events to the widget from the currently viewed room', async () => { clientWidgetApi.setViewedRoomId(roomId); // Give the widget capabilities to receive events without specifying // any rooms that it can read await loadIframe([ `org.matrix.msc2762.timeline:${roomId}`, - "org.matrix.msc2762.receive.event:m.room.message", + 'org.matrix.msc2762.receive.event:m.room.message', ]); // Event from the viewed room should be forwarded @@ -739,11 +739,11 @@ describe("ClientWidgetApi", () => { expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.SendEvent, eventFromOtherRoom); }); - it("forwards events to the widget from all rooms", async () => { + it('forwards events to the widget from all rooms', async () => { // Give the widget capabilities to receive from any known room await loadIframe([ `org.matrix.msc2762.timeline:${Symbols.AnyRoom}`, - "org.matrix.msc2762.receive.event:m.room.message", + 'org.matrix.msc2762.receive.event:m.room.message', ]); // Events from both rooms should be forwarded @@ -754,13 +754,13 @@ describe("ClientWidgetApi", () => { }); }); - describe("receiving room state", () => { - it("syncs initial state and feeds updates", async () => { - const roomId = "!room:example.org"; - const otherRoomId = "!other-room:example.org"; + describe('receiving room state', () => { + it('syncs initial state and feeds updates', async () => { + const roomId = '!room:example.org'; + const otherRoomId = '!other-room:example.org'; clientWidgetApi.setViewedRoomId(roomId); - jest.spyOn(transport, "send").mockImplementation((action, data) => { + jest.spyOn(transport, 'send').mockImplementation((action, data) => { if (action === WidgetApiToWidgetAction.SupportedApiVersions) { return Promise.resolve({ supported_versions: CurrentApiVersions }); } @@ -769,27 +769,27 @@ describe("ClientWidgetApi", () => { const topicEvent = createRoomEvent({ room_id: roomId, - type: "m.room.topic", - state_key: "", - content: { topic: "Hello world!" }, + type: 'm.room.topic', + state_key: '', + content: { topic: 'Hello world!' }, }); const nameEvent = createRoomEvent({ room_id: roomId, - type: "m.room.name", - state_key: "", - content: { name: "Test room" }, + type: 'm.room.name', + state_key: '', + content: { name: 'Test room' }, }); const joinRulesEvent = createRoomEvent({ room_id: roomId, - type: "m.room.join_rules", - state_key: "", - content: { join_rule: "public" }, + type: 'm.room.join_rules', + state_key: '', + content: { join_rule: 'public' }, }); const otherRoomNameEvent = createRoomEvent({ room_id: otherRoomId, - type: "m.room.name", - state_key: "", - content: { name: "Other room" }, + type: 'm.room.name', + state_key: '', + content: { name: 'Other room' }, }); // Artificially delay the delivery of the join rules event @@ -798,31 +798,31 @@ describe("ClientWidgetApi", () => { driver.readRoomState.mockImplementation(async (rId, eventType, stateKey) => { if (rId === roomId) { - if (eventType === "m.room.topic" && stateKey === "") return [topicEvent]; - if (eventType === "m.room.name" && stateKey === "") return [nameEvent]; - if (eventType === "m.room.join_rules" && stateKey === "") { + if (eventType === 'm.room.topic' && stateKey === '') return [topicEvent]; + if (eventType === 'm.room.name' && stateKey === '') return [nameEvent]; + if (eventType === 'm.room.join_rules' && stateKey === '') { await joinRules; return [joinRulesEvent]; } } else if (rId === otherRoomId) { - if (eventType === "m.room.name" && stateKey === "") return [otherRoomNameEvent]; + if (eventType === 'm.room.name' && stateKey === '') return [otherRoomNameEvent]; } return []; }); await loadIframe([ - "org.matrix.msc2762.receive.state_event:m.room.topic#", - "org.matrix.msc2762.receive.state_event:m.room.name#", - "org.matrix.msc2762.receive.state_event:m.room.join_rules#", + 'org.matrix.msc2762.receive.state_event:m.room.topic#', + 'org.matrix.msc2762.receive.state_event:m.room.name#', + 'org.matrix.msc2762.receive.state_event:m.room.join_rules#', ]); // Simulate a race between reading the original join rules event and // the join rules being updated at the same time const newJoinRulesEvent = createRoomEvent({ room_id: roomId, - type: "m.room.join_rules", - state_key: "", - content: { join_rule: "invite" }, + type: 'm.room.join_rules', + state_key: '', + content: { join_rule: 'invite' }, }); clientWidgetApi.feedStateUpdate(newJoinRulesEvent); // What happens if the original join rules are delivered after the @@ -844,9 +844,9 @@ describe("ClientWidgetApi", () => { // as expected const newTopicEvent = createRoomEvent({ room_id: roomId, - type: "m.room.topic", - state_key: "", - content: { topic: "Our new topic" }, + type: 'm.room.topic', + state_key: '', + content: { topic: 'Our new topic' }, }); clientWidgetApi.feedStateUpdate(newTopicEvent); @@ -874,22 +874,22 @@ describe("ClientWidgetApi", () => { }); }); - describe("update_delayed_event action", () => { - it("fails to update delayed events", async () => { + describe('update_delayed_event action', () => { + it('fails to update delayed events', async () => { const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: "f", + delay_id: 'f', action: UpdateDelayedEventAction.Send, }, }; await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -900,21 +900,21 @@ describe("ClientWidgetApi", () => { expect(driver.updateDelayedEvent).not.toBeCalled(); }); - it("fails to update delayed events with unsupported action", async () => { + it('fails to update delayed events with unsupported action', async () => { const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: "f", - action: "unknown" as UpdateDelayedEventAction, + delay_id: 'f', + action: 'unknown' as UpdateDelayedEventAction, }, }; - await loadIframe(["org.matrix.msc4157.update_delayed_event"]); + await loadIframe(['org.matrix.msc4157.update_delayed_event']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -925,7 +925,7 @@ describe("ClientWidgetApi", () => { expect(driver.updateDelayedEvent).not.toBeCalled(); }); - it("updates delayed events", async () => { + it('updates delayed events', async () => { driver.updateDelayedEvent.mockResolvedValue(undefined); for (const action of [ @@ -935,18 +935,18 @@ describe("ClientWidgetApi", () => { ]) { const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: "f", + delay_id: 'f', action, }, }; - await loadIframe(["org.matrix.msc4157.update_delayed_event"]); + await loadIframe(['org.matrix.msc4157.update_delayed_event']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, {}); @@ -956,67 +956,67 @@ describe("ClientWidgetApi", () => { } }); - it("should reject requests when the driver throws an exception", async () => { - driver.updateDelayedEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object")); + it('should reject requests when the driver throws an exception', async () => { + driver.updateDelayedEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: "f", + delay_id: 'f', action: UpdateDelayedEventAction.Send, }, }; - await loadIframe(["org.matrix.msc4157.update_delayed_event"]); + await loadIframe(['org.matrix.msc4157.update_delayed_event']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Error updating delayed event" }, + error: { message: 'Error updating delayed event' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { + it('should reject with Matrix API error response thrown by driver', async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.updateDelayedEvent.mockRejectedValue( - new CustomMatrixError("failed to update delayed event", 400, "M_NOT_JSON", { - reason: "Content must be a JSON object.", + new CustomMatrixError('failed to update delayed event', 400, 'M_NOT_JSON', { + reason: 'Content must be a JSON object.', }), ); const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: "f", + delay_id: 'f', action: UpdateDelayedEventAction.Send, }, }; - await loadIframe(["org.matrix.msc4157.update_delayed_event"]); + await loadIframe(['org.matrix.msc4157.update_delayed_event']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Error updating delayed event", + message: 'Error updating delayed event', matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_NOT_JSON", - error: "failed to update delayed event", - reason: "Content must be a JSON object.", + errcode: 'M_NOT_JSON', + error: 'failed to update delayed event', + reason: 'Content must be a JSON object.', }, } satisfies IMatrixApiError, }, @@ -1025,20 +1025,20 @@ describe("ClientWidgetApi", () => { }); }); - describe("send_to_device action", () => { - it("sends unencrypted to-device events", async () => { + describe('send_to_device action', () => { + it('sends unencrypted to-device events', async () => { const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: "net.example.test", + type: 'net.example.test', encrypted: false, messages: { - "@foo:bar.com": { + '@foo:bar.com': { DEVICEID: { - example_content_key: "value", + example_content_key: 'value', }, }, }, @@ -1047,7 +1047,7 @@ describe("ClientWidgetApi", () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, {}); @@ -1060,18 +1060,18 @@ describe("ClientWidgetApi", () => { ); }); - it("fails to send to-device events without event type", async () => { + it('fails to send to-device events without event type', async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendToDevice, data: { encrypted: false, messages: { - "@foo:bar.com": { + '@foo:bar.com': { DEVICEID: { - example_content_key: "value", + example_content_key: 'value', }, }, }, @@ -1080,54 +1080,54 @@ describe("ClientWidgetApi", () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Invalid request - missing event type" }, + error: { message: 'Invalid request - missing event type' }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it("fails to send to-device events without event contents", async () => { + it('fails to send to-device events without event contents', async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: "net.example.test", + type: 'net.example.test', encrypted: false, }, }; await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Invalid request - missing event contents" }, + error: { message: 'Invalid request - missing event contents' }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it("fails to send to-device events without encryption flag", async () => { + it('fails to send to-device events without encryption flag', async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: "net.example.test", + type: 'net.example.test', messages: { - "@foo:bar.com": { + '@foo:bar.com': { DEVICEID: { - example_content_key: "value", + example_content_key: 'value', }, }, }, @@ -1136,30 +1136,30 @@ describe("ClientWidgetApi", () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Invalid request - missing encryption flag" }, + error: { message: 'Invalid request - missing encryption flag' }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it("fails to send to-device events with any event type", async () => { + it('fails to send to-device events with any event type', async () => { const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: "net.example.test", + type: 'net.example.test', encrypted: false, messages: { - "@foo:bar.com": { + '@foo:bar.com': { DEVICEID: { - example_content_key: "value", + example_content_key: 'value', }, }, }, @@ -1168,34 +1168,34 @@ describe("ClientWidgetApi", () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}_different`]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Cannot send to-device events of this type" }, + error: { message: 'Cannot send to-device events of this type' }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it("should reject requests when the driver throws an exception", async () => { + it('should reject requests when the driver throws an exception', async () => { driver.sendToDevice.mockRejectedValue( new Error("M_FORBIDDEN: You don't have permission to send to-device events"), ); const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: "net.example.test", + type: 'net.example.test', encrypted: false, messages: { - "@foo:bar.com": { + '@foo:bar.com': { DEVICEID: { - example_content_key: "value", + example_content_key: 'value', }, }, }, @@ -1204,36 +1204,36 @@ describe("ClientWidgetApi", () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Error sending event" }, + error: { message: 'Error sending event' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { + it('should reject with Matrix API error response thrown by driver', async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.sendToDevice.mockRejectedValue( - new CustomMatrixError("failed to send event", 400, "M_FORBIDDEN", { + new CustomMatrixError('failed to send event', 400, 'M_FORBIDDEN', { reason: "You don't have permission to send to-device events", }), ); const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: "net.example.test", + type: 'net.example.test', encrypted: false, messages: { - "@foo:bar.com": { + '@foo:bar.com': { DEVICEID: { - example_content_key: "value", + example_content_key: 'value', }, }, }, @@ -1242,19 +1242,19 @@ describe("ClientWidgetApi", () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Error sending event", + message: 'Error sending event', matrix_api_error: { http_status: 400, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_FORBIDDEN", - error: "failed to send event", + errcode: 'M_FORBIDDEN', + error: 'failed to send event', reason: "You don't have permission to send to-device events", }, } satisfies IMatrixApiError, @@ -1264,40 +1264,40 @@ describe("ClientWidgetApi", () => { }); }); - describe("get_openid action", () => { - it("gets info", async () => { + describe('get_openid action', () => { + it('gets info', async () => { driver.askOpenID.mockImplementation((observable) => { observable.update({ state: OpenIDRequestState.Allowed, token: { - access_token: "access_token", + access_token: 'access_token', }, }); }); const event: IGetOpenIDActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.GetOpenIDCredentials, data: {}, }; await loadIframe([]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { state: OpenIDRequestState.Allowed, - access_token: "access_token", + access_token: 'access_token', }); }); expect(driver.askOpenID).toHaveBeenCalledWith(expect.any(SimpleObservable)); }); - it("fails when client provided invalid token", async () => { + it('fails when client provided invalid token', async () => { driver.askOpenID.mockImplementation((observable) => { observable.update({ state: OpenIDRequestState.Allowed, @@ -1306,19 +1306,19 @@ describe("ClientWidgetApi", () => { const event: IGetOpenIDActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.GetOpenIDCredentials, data: {}, }; await loadIframe([]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - error: { message: "client provided invalid OIDC token for an allowed request" }, + error: { message: 'client provided invalid OIDC token for an allowed request' }, }); }); @@ -1326,10 +1326,10 @@ describe("ClientWidgetApi", () => { }); }); - describe("com.beeper.read_room_account_data action", () => { - it("reads room account data", async () => { - const type = "net.example.test"; - const roomId = "!room:example.org"; + describe('com.beeper.read_room_account_data action', () => { + it('reads room account data', async () => { + const type = 'net.example.test'; + const roomId = '!room:example.org'; driver.readRoomAccountData.mockResolvedValue([ { @@ -1341,8 +1341,8 @@ describe("ClientWidgetApi", () => { const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data: { room_ids: [roomId], @@ -1352,7 +1352,7 @@ describe("ClientWidgetApi", () => { await loadIframe([`com.beeper.capabilities.receive.room_account_data:${type}`]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -1369,9 +1369,9 @@ describe("ClientWidgetApi", () => { expect(driver.readRoomAccountData).toHaveBeenCalledWith(event.data.type); }); - it("does not read room account data", async () => { - const type = "net.example.test"; - const roomId = "!room:example.org"; + it('does not read room account data', async () => { + const type = 'net.example.test'; + const roomId = '!room:example.org'; driver.readRoomAccountData.mockResolvedValue([ { @@ -1383,8 +1383,8 @@ describe("ClientWidgetApi", () => { const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data: { room_ids: [roomId], @@ -1394,11 +1394,11 @@ describe("ClientWidgetApi", () => { await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - error: { message: "Cannot read room account data of this type" }, + error: { message: 'Cannot read room account data of this type' }, }); }); @@ -1406,10 +1406,10 @@ describe("ClientWidgetApi", () => { }); }); - describe("org.matrix.msc2876.read_events action", () => { - it("reads events from a specific room", async () => { - const roomId = "!room:example.org"; - const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: "test" }); + describe('org.matrix.msc2876.read_events action', () => { + it('reads events from a specific room', async () => { + const roomId = '!room:example.org'; + const event = createRoomEvent({ room_id: roomId, type: 'net.example.test', content: 'test' }); driver.readRoomTimeline.mockImplementation(async (rId) => { if (rId === roomId) return [event]; return []; @@ -1417,22 +1417,22 @@ describe("ClientWidgetApi", () => { const request: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: "net.example.test", + type: 'net.example.test', room_ids: [roomId], }, }; await loadIframe([ `org.matrix.msc2762.timeline:${roomId}`, - "org.matrix.msc2762.receive.event:net.example.test", + 'org.matrix.msc2762.receive.event:net.example.test', ]); clientWidgetApi.setViewedRoomId(roomId); - emitEvent(new CustomEvent("", { detail: request })); + emitEvent(new CustomEvent('', { detail: request })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(request, { @@ -1442,7 +1442,7 @@ describe("ClientWidgetApi", () => { expect(driver.readRoomTimeline).toHaveBeenCalledWith( roomId, - "net.example.test", + 'net.example.test', undefined, undefined, 0, @@ -1450,11 +1450,11 @@ describe("ClientWidgetApi", () => { ); }); - it("reads events from all rooms", async () => { - const roomId = "!room:example.org"; - const otherRoomId = "!other-room:example.org"; - const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: "test" }); - const otherRoomEvent = createRoomEvent({ room_id: otherRoomId, type: "net.example.test", content: "hi" }); + it('reads events from all rooms', async () => { + const roomId = '!room:example.org'; + const otherRoomId = '!other-room:example.org'; + const event = createRoomEvent({ room_id: roomId, type: 'net.example.test', content: 'test' }); + const otherRoomEvent = createRoomEvent({ room_id: otherRoomId, type: 'net.example.test', content: 'hi' }); driver.getKnownRooms.mockReturnValue([roomId, otherRoomId]); driver.readRoomTimeline.mockImplementation(async (rId) => { if (rId === roomId) return [event]; @@ -1464,22 +1464,22 @@ describe("ClientWidgetApi", () => { const request: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: "net.example.test", + type: 'net.example.test', room_ids: Symbols.AnyRoom, }, }; await loadIframe([ `org.matrix.msc2762.timeline:${Symbols.AnyRoom}`, - "org.matrix.msc2762.receive.event:net.example.test", + 'org.matrix.msc2762.receive.event:net.example.test', ]); clientWidgetApi.setViewedRoomId(roomId); - emitEvent(new CustomEvent("", { detail: request })); + emitEvent(new CustomEvent('', { detail: request })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(request, { @@ -1489,7 +1489,7 @@ describe("ClientWidgetApi", () => { expect(driver.readRoomTimeline).toHaveBeenCalledWith( roomId, - "net.example.test", + 'net.example.test', undefined, undefined, 0, @@ -1497,7 +1497,7 @@ describe("ClientWidgetApi", () => { ); expect(driver.readRoomTimeline).toHaveBeenCalledWith( otherRoomId, - "net.example.test", + 'net.example.test', undefined, undefined, 0, @@ -1505,40 +1505,40 @@ describe("ClientWidgetApi", () => { ); }); - it("reads state events with any state key", async () => { + it('reads state events with any state key', async () => { driver.readRoomTimeline.mockResolvedValue([ - createRoomEvent({ type: "net.example.test", state_key: "A" }), - createRoomEvent({ type: "net.example.test", state_key: "B" }), + createRoomEvent({ type: 'net.example.test', state_key: 'A' }), + createRoomEvent({ type: 'net.example.test', state_key: 'B' }), ]); const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: "net.example.test", + type: 'net.example.test', state_key: true, }, }; - await loadIframe(["org.matrix.msc2762.receive.state_event:net.example.test"]); - clientWidgetApi.setViewedRoomId("!room-id"); + await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test']); + clientWidgetApi.setViewedRoomId('!room-id'); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { events: [ - createRoomEvent({ type: "net.example.test", state_key: "A" }), - createRoomEvent({ type: "net.example.test", state_key: "B" }), + createRoomEvent({ type: 'net.example.test', state_key: 'A' }), + createRoomEvent({ type: 'net.example.test', state_key: 'B' }), ], }); }); expect(driver.readRoomTimeline).toBeCalledWith( - "!room-id", - "net.example.test", + '!room-id', + 'net.example.test', undefined, undefined, 0, @@ -1546,21 +1546,21 @@ describe("ClientWidgetApi", () => { ); }); - it("fails to read state events with any state key", async () => { + it('fails to read state events with any state key', async () => { const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: "net.example.test", + type: 'net.example.test', state_key: true, }, }; await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1571,57 +1571,57 @@ describe("ClientWidgetApi", () => { expect(driver.readRoomTimeline).not.toBeCalled(); }); - it("reads state events with a specific state key", async () => { - driver.readRoomTimeline.mockResolvedValue([createRoomEvent({ type: "net.example.test", state_key: "B" })]); + it('reads state events with a specific state key', async () => { + driver.readRoomTimeline.mockResolvedValue([createRoomEvent({ type: 'net.example.test', state_key: 'B' })]); const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: "net.example.test", - state_key: "B", + type: 'net.example.test', + state_key: 'B', }, }; - await loadIframe(["org.matrix.msc2762.receive.state_event:net.example.test#B"]); - clientWidgetApi.setViewedRoomId("!room-id"); + await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test#B']); + clientWidgetApi.setViewedRoomId('!room-id'); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - events: [createRoomEvent({ type: "net.example.test", state_key: "B" })], + events: [createRoomEvent({ type: 'net.example.test', state_key: 'B' })], }); }); expect(driver.readRoomTimeline).toBeCalledWith( - "!room-id", - "net.example.test", + '!room-id', + 'net.example.test', undefined, - "B", + 'B', 0, undefined, ); }); - it("fails to read state events with a specific state key", async () => { + it('fails to read state events with a specific state key', async () => { const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: "net.example.test", - state_key: "B", + type: 'net.example.test', + state_key: 'B', }, }; // Request the capability for the wrong state key - await loadIframe(["org.matrix.msc2762.receive.state_event:net.example.test#A"]); + await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test#A']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1633,39 +1633,39 @@ describe("ClientWidgetApi", () => { }); }); - describe("org.matrix.msc3869.read_relations action", () => { - it("should present as supported api version", () => { + describe('org.matrix.msc3869.read_relations action', () => { + it('should present as supported api version', () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC3869]), }); }); - it("should handle and process the request", async () => { + it('should handle and process the request', async () => { driver.readEventRelations.mockResolvedValue({ chunk: [createRoomEvent()], }); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: "$event" }, + data: { event_id: '$event' }, }; - await loadIframe(["org.matrix.msc2762.receive.event:m.room.message"]); + await loadIframe(['org.matrix.msc2762.receive.event:m.room.message']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1674,7 +1674,7 @@ describe("ClientWidgetApi", () => { }); expect(driver.readEventRelations).toBeCalledWith( - "$event", + '$event', undefined, undefined, undefined, @@ -1685,39 +1685,39 @@ describe("ClientWidgetApi", () => { ); }); - it("should only return events that match requested capabilities", async () => { + it('should only return events that match requested capabilities', async () => { driver.readEventRelations.mockResolvedValue({ chunk: [ createRoomEvent(), - createRoomEvent({ type: "m.reaction" }), - createRoomEvent({ type: "net.example.test", state_key: "A" }), - createRoomEvent({ type: "net.example.test", state_key: "B" }), + createRoomEvent({ type: 'm.reaction' }), + createRoomEvent({ type: 'net.example.test', state_key: 'A' }), + createRoomEvent({ type: 'net.example.test', state_key: 'B' }), ], }); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: "$event" }, + data: { event_id: '$event' }, }; await loadIframe([ - "org.matrix.msc2762.receive.event:m.room.message", - "org.matrix.msc2762.receive.state_event:net.example.test#A", + 'org.matrix.msc2762.receive.event:m.room.message', + 'org.matrix.msc2762.receive.state_event:net.example.test#A', ]); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - chunk: [createRoomEvent(), createRoomEvent({ type: "net.example.test", state_key: "A" })], + chunk: [createRoomEvent(), createRoomEvent({ type: 'net.example.test', state_key: 'A' })], }); }); expect(driver.readEventRelations).toBeCalledWith( - "$event", + '$event', undefined, undefined, undefined, @@ -1728,31 +1728,31 @@ describe("ClientWidgetApi", () => { ); }); - it("should accept all options and pass it to the driver", async () => { + it('should accept all options and pass it to the driver', async () => { driver.readEventRelations.mockResolvedValue({ chunk: [], }); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: "$event", - room_id: "!room-id", - event_type: "m.room.message", - rel_type: "m.reference", + event_id: '$event', + room_id: '!room-id', + event_type: 'm.room.message', + rel_type: 'm.reference', limit: 25, - from: "from-token", - to: "to-token", - direction: "f", + from: 'from-token', + to: 'to-token', + direction: 'f', }, }; - await loadIframe(["org.matrix.msc2762.timeline:!room-id"]); + await loadIframe(['org.matrix.msc2762.timeline:!room-id']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1761,127 +1761,127 @@ describe("ClientWidgetApi", () => { }); expect(driver.readEventRelations).toBeCalledWith( - "$event", - "!room-id", - "m.reference", - "m.room.message", - "from-token", - "to-token", + '$event', + '!room-id', + 'm.reference', + 'm.room.message', + 'from-token', + 'to-token', 25, - "f", + 'f', ); }); - it("should reject requests without event_id", async () => { + it('should reject requests without event_id', async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: {}, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: "Invalid request - missing event ID" }, + error: { message: 'Invalid request - missing event ID' }, }); }); - it("should reject requests with a negative limit", async () => { + it('should reject requests with a negative limit', async () => { const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: "$event", + event_id: '$event', limit: -1, }, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: "Invalid request - limit out of range" }, + error: { message: 'Invalid request - limit out of range' }, }); }); - it("should reject requests when the room timeline was not requested", async () => { + it('should reject requests when the room timeline was not requested', async () => { const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: "$event", - room_id: "!another-room-id", + event_id: '$event', + room_id: '!another-room-id', }, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: "Unable to access room timeline: !another-room-id" }, + error: { message: 'Unable to access room timeline: !another-room-id' }, }); }); - it("should reject requests when the driver throws an exception", async () => { + it('should reject requests when the driver throws an exception', async () => { driver.readEventRelations.mockRejectedValue( new Error("M_FORBIDDEN: You don't have permission to access that event"), ); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: "$event" }, + data: { event_id: '$event' }, }; await loadIframe(); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Unexpected error while reading relations" }, + error: { message: 'Unexpected error while reading relations' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { + it('should reject with Matrix API error response thrown by driver', async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.readEventRelations.mockRejectedValue( - new CustomMatrixError("failed to read relations", 403, "M_FORBIDDEN", { + new CustomMatrixError('failed to read relations', 403, 'M_FORBIDDEN', { reason: "You don't have permission to access that event", }), ); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: "$event" }, + data: { event_id: '$event' }, }; await loadIframe(); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Unexpected error while reading relations", + message: 'Unexpected error while reading relations', matrix_api_error: { http_status: 403, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_FORBIDDEN", - error: "failed to read relations", + errcode: 'M_FORBIDDEN', + error: 'failed to read relations', reason: "You don't have permission to access that event", }, } satisfies IMatrixApiError, @@ -1891,51 +1891,51 @@ describe("ClientWidgetApi", () => { }); }); - describe("org.matrix.msc3973.user_directory_search action", () => { - it("should present as supported api version", () => { + describe('org.matrix.msc3973.user_directory_search action', () => { + it('should present as supported api version', () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC3973]), }); }); - it("should handle and process the request", async () => { + it('should handle and process the request', async () => { driver.searchUserDirectory.mockResolvedValue({ limited: true, results: [ { - userId: "@foo:bar.com", + userId: '@foo:bar.com', }, ], }); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: "foo" }, + data: { search_term: 'foo' }, }; - await loadIframe(["org.matrix.msc3973.user_directory_search"]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { limited: true, results: [ { - user_id: "@foo:bar.com", + user_id: '@foo:bar.com', display_name: undefined, avatar_url: undefined, }, @@ -1943,61 +1943,61 @@ describe("ClientWidgetApi", () => { }); }); - expect(driver.searchUserDirectory).toBeCalledWith("foo", undefined); + expect(driver.searchUserDirectory).toBeCalledWith('foo', undefined); }); - it("should accept all options and pass it to the driver", async () => { + it('should accept all options and pass it to the driver', async () => { driver.searchUserDirectory.mockResolvedValue({ limited: false, results: [ { - userId: "@foo:bar.com", + userId: '@foo:bar.com', }, { - userId: "@bar:foo.com", - displayName: "Bar", - avatarUrl: "mxc://...", + userId: '@bar:foo.com', + displayName: 'Bar', + avatarUrl: 'mxc://...', }, ], }); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: { - search_term: "foo", + search_term: 'foo', limit: 5, }, }; - await loadIframe(["org.matrix.msc3973.user_directory_search"]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { limited: false, results: [ { - user_id: "@foo:bar.com", + user_id: '@foo:bar.com', display_name: undefined, avatar_url: undefined, }, { - user_id: "@bar:foo.com", - display_name: "Bar", - avatar_url: "mxc://...", + user_id: '@bar:foo.com', + display_name: 'Bar', + avatar_url: 'mxc://...', }, ], }); }); - expect(driver.searchUserDirectory).toBeCalledWith("foo", 5); + expect(driver.searchUserDirectory).toBeCalledWith('foo', 5); }); - it("should accept empty search_term", async () => { + it('should accept empty search_term', async () => { driver.searchUserDirectory.mockResolvedValue({ limited: false, results: [], @@ -2005,15 +2005,15 @@ describe("ClientWidgetApi", () => { const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: "" }, + data: { search_term: '' }, }; - await loadIframe(["org.matrix.msc3973.user_directory_search"]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -2022,126 +2022,126 @@ describe("ClientWidgetApi", () => { }); }); - expect(driver.searchUserDirectory).toBeCalledWith("", undefined); + expect(driver.searchUserDirectory).toBeCalledWith('', undefined); }); - it("should reject requests when the capability was not requested", async () => { + it('should reject requests when the capability was not requested', async () => { const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: "foo" }, + data: { search_term: 'foo' }, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: "Missing capability" }, + error: { message: 'Missing capability' }, }); expect(driver.searchUserDirectory).not.toBeCalled(); }); - it("should reject requests without search_term", async () => { + it('should reject requests without search_term', async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: {}, }; - await loadIframe(["org.matrix.msc3973.user_directory_search"]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: "Invalid request - missing search term" }, + error: { message: 'Invalid request - missing search term' }, }); expect(driver.searchUserDirectory).not.toBeCalled(); }); - it("should reject requests with a negative limit", async () => { + it('should reject requests with a negative limit', async () => { const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: { - search_term: "foo", + search_term: 'foo', limit: -1, }, }; - await loadIframe(["org.matrix.msc3973.user_directory_search"]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: "Invalid request - limit out of range" }, + error: { message: 'Invalid request - limit out of range' }, }); expect(driver.searchUserDirectory).not.toBeCalled(); }); - it("should reject requests when the driver throws an exception", async () => { - driver.searchUserDirectory.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); + it('should reject requests when the driver throws an exception', async () => { + driver.searchUserDirectory.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: "foo" }, + data: { search_term: 'foo' }, }; - await loadIframe(["org.matrix.msc3973.user_directory_search"]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Unexpected error while searching in the user directory" }, + error: { message: 'Unexpected error while searching in the user directory' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { + it('should reject with Matrix API error response thrown by driver', async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.searchUserDirectory.mockRejectedValue( - new CustomMatrixError("failed to search the user directory", 429, "M_LIMIT_EXCEEDED", { - reason: "Too many requests", + new CustomMatrixError('failed to search the user directory', 429, 'M_LIMIT_EXCEEDED', { + reason: 'Too many requests', retry_after_ms: 2000, }), ); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: "foo" }, + data: { search_term: 'foo' }, }; - await loadIframe(["org.matrix.msc3973.user_directory_search"]); + await loadIframe(['org.matrix.msc3973.user_directory_search']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Unexpected error while searching in the user directory", + message: 'Unexpected error while searching in the user directory', matrix_api_error: { http_status: 429, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_LIMIT_EXCEEDED", - error: "failed to search the user directory", - reason: "Too many requests", + errcode: 'M_LIMIT_EXCEEDED', + error: 'failed to search the user directory', + reason: 'Too many requests', retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2151,123 +2151,123 @@ describe("ClientWidgetApi", () => { }); }); - describe("org.matrix.msc4039.get_media_config action", () => { - it("should present as supported api version", () => { + describe('org.matrix.msc4039.get_media_config action', () => { + it('should present as supported api version', () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC4039]), }); }); - it("should handle and process the request", async () => { + it('should handle and process the request', async () => { driver.getMediaConfig.mockResolvedValue({ - "m.upload.size": 1000, + 'm.upload.size': 1000, }); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - await loadIframe(["org.matrix.msc4039.upload_file"]); + await loadIframe(['org.matrix.msc4039.upload_file']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - "m.upload.size": 1000, + 'm.upload.size': 1000, }); }); expect(driver.getMediaConfig).toBeCalled(); }); - it("should reject requests when the capability was not requested", async () => { + it('should reject requests when the capability was not requested', async () => { const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: "Missing capability" }, + error: { message: 'Missing capability' }, }); expect(driver.getMediaConfig).not.toBeCalled(); }); - it("should reject requests when the driver throws an exception", async () => { - driver.getMediaConfig.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); + it('should reject requests when the driver throws an exception', async () => { + driver.getMediaConfig.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - await loadIframe(["org.matrix.msc4039.upload_file"]); + await loadIframe(['org.matrix.msc4039.upload_file']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Unexpected error while getting the media configuration" }, + error: { message: 'Unexpected error while getting the media configuration' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { + it('should reject with Matrix API error response thrown by driver', async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.getMediaConfig.mockRejectedValue( - new CustomMatrixError("failed to get the media configuration", 429, "M_LIMIT_EXCEEDED", { - reason: "Too many requests", + new CustomMatrixError('failed to get the media configuration', 429, 'M_LIMIT_EXCEEDED', { + reason: 'Too many requests', retry_after_ms: 2000, }), ); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - await loadIframe(["org.matrix.msc4039.upload_file"]); + await loadIframe(['org.matrix.msc4039.upload_file']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Unexpected error while getting the media configuration", + message: 'Unexpected error while getting the media configuration', matrix_api_error: { http_status: 429, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_LIMIT_EXCEEDED", - error: "failed to get the media configuration", - reason: "Too many requests", + errcode: 'M_LIMIT_EXCEEDED', + error: 'failed to get the media configuration', + reason: 'Too many requests', retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2277,17 +2277,17 @@ describe("ClientWidgetApi", () => { }); }); - describe("MSC4039", () => { - it("should present as supported api version", () => { + describe('MSC4039', () => { + it('should present as supported api version', () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC4039]), @@ -2295,115 +2295,115 @@ describe("ClientWidgetApi", () => { }); }); - describe("org.matrix.msc4039.upload_file action", () => { - it("should handle and process the request", async () => { + describe('org.matrix.msc4039.upload_file action', () => { + it('should handle and process the request', async () => { driver.uploadFile.mockResolvedValue({ - contentUri: "mxc://...", + contentUri: 'mxc://...', }); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: "data", + file: 'data', }, }; - await loadIframe(["org.matrix.msc4039.upload_file"]); + await loadIframe(['org.matrix.msc4039.upload_file']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - content_uri: "mxc://...", + content_uri: 'mxc://...', }); }); expect(driver.uploadFile).toBeCalled(); }); - it("should reject requests when the capability was not requested", async () => { + it('should reject requests when the capability was not requested', async () => { const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: "data", + file: 'data', }, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: "Missing capability" }, + error: { message: 'Missing capability' }, }); expect(driver.uploadFile).not.toBeCalled(); }); - it("should reject requests when the driver throws an exception", async () => { - driver.uploadFile.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); + it('should reject requests when the driver throws an exception', async () => { + driver.uploadFile.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: "data", + file: 'data', }, }; - await loadIframe(["org.matrix.msc4039.upload_file"]); + await loadIframe(['org.matrix.msc4039.upload_file']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Unexpected error while uploading a file" }, + error: { message: 'Unexpected error while uploading a file' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { + it('should reject with Matrix API error response thrown by driver', async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.uploadFile.mockRejectedValue( - new CustomMatrixError("failed to upload a file", 429, "M_LIMIT_EXCEEDED", { - reason: "Too many requests", + new CustomMatrixError('failed to upload a file', 429, 'M_LIMIT_EXCEEDED', { + reason: 'Too many requests', retry_after_ms: 2000, }), ); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: "data", + file: 'data', }, }; - await loadIframe(["org.matrix.msc4039.upload_file"]); + await loadIframe(['org.matrix.msc4039.upload_file']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Unexpected error while uploading a file", + message: 'Unexpected error while uploading a file', matrix_api_error: { http_status: 429, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_LIMIT_EXCEEDED", - error: "failed to upload a file", - reason: "Too many requests", + errcode: 'M_LIMIT_EXCEEDED', + error: 'failed to upload a file', + reason: 'Too many requests', retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2413,115 +2413,115 @@ describe("ClientWidgetApi", () => { }); }); - describe("org.matrix.msc4039.download_file action", () => { - it("should handle and process the request", async () => { + describe('org.matrix.msc4039.download_file action', () => { + it('should handle and process the request', async () => { driver.downloadFile.mockResolvedValue({ - file: "test contents", + file: 'test contents', }); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: "mxc://example.com/test_file", + content_uri: 'mxc://example.com/test_file', }, }; - await loadIframe(["org.matrix.msc4039.download_file"]); + await loadIframe(['org.matrix.msc4039.download_file']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - file: "test contents", + file: 'test contents', }); }); - expect(driver.downloadFile).toHaveBeenCalledWith("mxc://example.com/test_file"); + expect(driver.downloadFile).toHaveBeenCalledWith('mxc://example.com/test_file'); }); - it("should reject requests when the capability was not requested", async () => { + it('should reject requests when the capability was not requested', async () => { const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: "mxc://example.com/test_file", + content_uri: 'mxc://example.com/test_file', }, }; - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: "Missing capability" }, + error: { message: 'Missing capability' }, }); expect(driver.uploadFile).not.toBeCalled(); }); - it("should reject requests when the driver throws an exception", async () => { - driver.downloadFile.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); + it('should reject requests when the driver throws an exception', async () => { + driver.downloadFile.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: "mxc://example.com/test_file", + content_uri: 'mxc://example.com/test_file', }, }; - await loadIframe(["org.matrix.msc4039.download_file"]); + await loadIframe(['org.matrix.msc4039.download_file']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: "Unexpected error while downloading a file" }, + error: { message: 'Unexpected error while downloading a file' }, }); }); }); - it("should reject with Matrix API error response thrown by driver", async () => { + it('should reject with Matrix API error response thrown by driver', async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.downloadFile.mockRejectedValue( - new CustomMatrixError("failed to download a file", 429, "M_LIMIT_EXCEEDED", { - reason: "Too many requests", + new CustomMatrixError('failed to download a file', 429, 'M_LIMIT_EXCEEDED', { + reason: 'Too many requests', retry_after_ms: 2000, }), ); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: "test", - requestId: "0", + widgetId: 'test', + requestId: '0', action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: "mxc://example.com/test_file", + content_uri: 'mxc://example.com/test_file', }, }; - await loadIframe(["org.matrix.msc4039.download_file"]); + await loadIframe(['org.matrix.msc4039.download_file']); - emitEvent(new CustomEvent("", { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: "Unexpected error while downloading a file", + message: 'Unexpected error while downloading a file', matrix_api_error: { http_status: 429, http_headers: {}, - url: "", + url: '', response: { - errcode: "M_LIMIT_EXCEEDED", - error: "failed to download a file", - reason: "Too many requests", + errcode: 'M_LIMIT_EXCEEDED', + error: 'failed to download a file', + reason: 'Too many requests', retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2531,13 +2531,13 @@ describe("ClientWidgetApi", () => { }); }); - it("updates theme", () => { - clientWidgetApi.updateTheme({ name: "dark" }); - expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.ThemeChange, { name: "dark" }); + it('updates theme', () => { + clientWidgetApi.updateTheme({ name: 'dark' }); + expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.ThemeChange, { name: 'dark' }); }); - it("updates language", () => { - clientWidgetApi.updateLanguage("tlh"); - expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.LanguageChange, { lang: "tlh" }); + it('updates language', () => { + clientWidgetApi.updateLanguage('tlh'); + expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.LanguageChange, { lang: 'tlh' }); }); }); From aa492019909474e3a353a621b93cc770e82476b7 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 17:30:29 +0100 Subject: [PATCH 11/12] same quotes for tests --- package.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/package.json b/package.json index b7d496b..d7d78c2 100644 --- a/package.json +++ b/package.json @@ -39,14 +39,6 @@ "options": { "tabWidth": 4 } - }, - { - "files": [ - "test/**/*.ts" - ], - "options": { - "singleQuote": true - } } ] }, From 2bad788d11778fae163092c38d4f7a28e6c22e5d Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 23 Jan 2025 17:30:39 +0100 Subject: [PATCH 12/12] same quotes for tests --- test/ClientWidgetApi-test.ts | 1340 +++++++++++++++++----------------- 1 file changed, 670 insertions(+), 670 deletions(-) diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index e19bdb7..3aa50b1 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -15,21 +15,21 @@ * limitations under the License. */ -import { waitFor } from '@testing-library/dom'; - -import { ClientWidgetApi } from '../src/ClientWidgetApi'; -import { WidgetDriver } from '../src/driver/WidgetDriver'; -import { CurrentApiVersions, UnstableApiVersion } from '../src/interfaces/ApiVersion'; -import { Capability } from '../src/interfaces/Capabilities'; -import { IRoomEvent } from '../src/interfaces/IRoomEvent'; -import { IWidgetApiRequest } from '../src/interfaces/IWidgetApiRequest'; -import { IReadRelationsFromWidgetActionRequest } from '../src/interfaces/ReadRelationsAction'; -import { ISupportedVersionsActionRequest } from '../src/interfaces/SupportedVersionsAction'; -import { IUserDirectorySearchFromWidgetActionRequest } from '../src/interfaces/UserDirectorySearchAction'; -import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from '../src/interfaces/WidgetApiAction'; -import { WidgetApiDirection } from '../src/interfaces/WidgetApiDirection'; -import { Widget } from '../src/models/Widget'; -import { PostmessageTransport } from '../src/transport/PostmessageTransport'; +import { waitFor } from "@testing-library/dom"; + +import { ClientWidgetApi } from "../src/ClientWidgetApi"; +import { WidgetDriver } from "../src/driver/WidgetDriver"; +import { CurrentApiVersions, UnstableApiVersion } from "../src/interfaces/ApiVersion"; +import { Capability } from "../src/interfaces/Capabilities"; +import { IRoomEvent } from "../src/interfaces/IRoomEvent"; +import { IWidgetApiRequest } from "../src/interfaces/IWidgetApiRequest"; +import { IReadRelationsFromWidgetActionRequest } from "../src/interfaces/ReadRelationsAction"; +import { ISupportedVersionsActionRequest } from "../src/interfaces/SupportedVersionsAction"; +import { IUserDirectorySearchFromWidgetActionRequest } from "../src/interfaces/UserDirectorySearchAction"; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "../src/interfaces/WidgetApiAction"; +import { WidgetApiDirection } from "../src/interfaces/WidgetApiDirection"; +import { Widget } from "../src/models/Widget"; +import { PostmessageTransport } from "../src/transport/PostmessageTransport"; import { IDownloadFileActionFromWidgetActionRequest, IGetOpenIDActionRequest, @@ -45,11 +45,11 @@ import { SimpleObservable, Symbols, UpdateDelayedEventAction, -} from '../src'; -import { IGetMediaConfigActionFromWidgetActionRequest } from '../src/interfaces/GetMediaConfigAction'; -import { IReadRoomAccountDataFromWidgetActionRequest } from '../src/interfaces/ReadRoomAccountDataAction'; +} from "../src"; +import { IGetMediaConfigActionFromWidgetActionRequest } from "../src/interfaces/GetMediaConfigAction"; +import { IReadRoomAccountDataFromWidgetActionRequest } from "../src/interfaces/ReadRoomAccountDataAction"; -jest.mock('../src/transport/PostmessageTransport'); +jest.mock("../src/transport/PostmessageTransport"); afterEach(() => { jest.resetAllMocks(); @@ -57,12 +57,12 @@ afterEach(() => { function createRoomEvent(event: Partial = {}): IRoomEvent { return { - type: 'm.room.message', - sender: 'user-id', + type: "m.room.message", + sender: "user-id", content: {}, origin_server_ts: 0, - event_id: 'id-0', - room_id: '!room-id', + event_id: "id-0", + room_id: "!room-id", unsigned: {}, ...event, }; @@ -85,7 +85,7 @@ function processCustomMatrixError(e: unknown): IWidgetApiErrorResponseDataDetail matrix_api_error: { http_status: e.httpStatus, http_headers: {}, - url: '', + url: "", response: { errcode: e.name, error: e.message, @@ -96,29 +96,29 @@ function processCustomMatrixError(e: unknown): IWidgetApiErrorResponseDataDetail : undefined; } -describe('ClientWidgetApi', () => { +describe("ClientWidgetApi", () => { let capabilities: Capability[]; let iframe: HTMLIFrameElement; let driver: jest.Mocked; let clientWidgetApi: ClientWidgetApi; let transport: PostmessageTransport; - let emitEvent: Parameters['1']; + let emitEvent: Parameters["1"]; async function loadIframe(caps: Capability[] = []): Promise { capabilities = caps; const ready = new Promise((resolve) => { - clientWidgetApi.once('ready', resolve); + clientWidgetApi.once("ready", resolve); }); - iframe.dispatchEvent(new Event('load')); + iframe.dispatchEvent(new Event("load")); await ready; } beforeEach(() => { capabilities = []; - iframe = document.createElement('iframe'); + iframe = document.createElement("iframe"); document.body.appendChild(iframe); driver = { @@ -143,10 +143,10 @@ describe('ClientWidgetApi', () => { clientWidgetApi = new ClientWidgetApi( new Widget({ - id: 'test', - creatorUserId: '@alice:example.org', - type: 'example', - url: 'https://example.org', + id: "test", + creatorUserId: "@alice:example.org", + type: "example", + url: "https://example.org", }), iframe, driver, @@ -164,30 +164,30 @@ describe('ClientWidgetApi', () => { iframe.remove(); }); - it('should initiate capabilities', async () => { - await loadIframe(['m.always_on_screen']); + it("should initiate capabilities", async () => { + await loadIframe(["m.always_on_screen"]); - expect(clientWidgetApi.hasCapability('m.always_on_screen')).toBe(true); - expect(clientWidgetApi.hasCapability('m.sticker')).toBe(false); + expect(clientWidgetApi.hasCapability("m.always_on_screen")).toBe(true); + expect(clientWidgetApi.hasCapability("m.sticker")).toBe(false); }); - describe('navigate action', () => { - it('navigates', async () => { + describe("navigate action", () => { + it("navigates", async () => { driver.navigate.mockResolvedValue(Promise.resolve()); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://matrix.to/#/#room:example.net', + uri: "https://matrix.to/#/#room:example.net", }, }; - await loadIframe(['org.matrix.msc2931.navigate']); + await loadIframe(["org.matrix.msc2931.navigate"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, {}); @@ -196,113 +196,113 @@ describe('ClientWidgetApi', () => { expect(driver.navigate).toHaveBeenCalledWith(event.data.uri); }); - it('fails to navigate', async () => { + it("fails to navigate", async () => { const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://matrix.to/#/#room:example.net', + uri: "https://matrix.to/#/#room:example.net", }, }; await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); }); expect(driver.navigate).not.toBeCalled(); }); - it('fails to navigate to an unsupported URI', async () => { + it("fails to navigate to an unsupported URI", async () => { const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://example.net', + uri: "https://example.net", }, }; - await loadIframe(['org.matrix.msc2931.navigate']); + await loadIframe(["org.matrix.msc2931.navigate"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid matrix.to URI' }, + error: { message: "Invalid matrix.to URI" }, }); }); expect(driver.navigate).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.navigate.mockRejectedValue(new Error('M_UNKNOWN: Unknown error')); + it("should reject requests when the driver throws an exception", async () => { + driver.navigate.mockRejectedValue(new Error("M_UNKNOWN: Unknown error")); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://matrix.to/#/#room:example.net', + uri: "https://matrix.to/#/#room:example.net", }, }; - await loadIframe(['org.matrix.msc2931.navigate']); + await loadIframe(["org.matrix.msc2931.navigate"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error handling navigation' }, + error: { message: "Error handling navigation" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.navigate.mockRejectedValue( - new CustomMatrixError('failed to navigate', 400, 'M_UNKNOWN', { - reason: 'Unknown error', + new CustomMatrixError("failed to navigate", 400, "M_UNKNOWN", { + reason: "Unknown error", }), ); const event: INavigateActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2931Navigate, data: { - uri: 'https://matrix.to/#/#room:example.net', + uri: "https://matrix.to/#/#room:example.net", }, }; - await loadIframe(['org.matrix.msc2931.navigate']); + await loadIframe(["org.matrix.msc2931.navigate"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error handling navigation', + message: "Error handling navigation", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_UNKNOWN', - error: 'failed to navigate', - reason: 'Unknown error', + errcode: "M_UNKNOWN", + error: "failed to navigate", + reason: "Unknown error", }, } satisfies IMatrixApiError, }, @@ -311,10 +311,10 @@ describe('ClientWidgetApi', () => { }); }); - describe('send_event action', () => { - it('sends message events', async () => { - const roomId = '!room:example.org'; - const eventId = '$event:example.org'; + describe("send_event action", () => { + it("sends message events", async () => { + const roomId = "!room:example.org"; + const eventId = "$event:example.org"; driver.sendEvent.mockResolvedValue({ roomId, @@ -323,11 +323,11 @@ describe('ClientWidgetApi', () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', + type: "m.room.message", content: {}, room_id: roomId, }, @@ -338,7 +338,7 @@ describe('ClientWidgetApi', () => { `org.matrix.msc2762.send.event:${event.data.type}`, ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -350,9 +350,9 @@ describe('ClientWidgetApi', () => { expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, null, roomId); }); - it('sends state events', async () => { - const roomId = '!room:example.org'; - const eventId = '$event:example.org'; + it("sends state events", async () => { + const roomId = "!room:example.org"; + const eventId = "$event:example.org"; driver.sendEvent.mockResolvedValue({ roomId, @@ -361,13 +361,13 @@ describe('ClientWidgetApi', () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.topic', + type: "m.room.topic", content: {}, - state_key: '', + state_key: "", room_id: roomId, }, }; @@ -377,7 +377,7 @@ describe('ClientWidgetApi', () => { `org.matrix.msc2762.send.state_event:${event.data.type}`, ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -386,22 +386,22 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, '', roomId); + expect(driver.sendEvent).toHaveBeenCalledWith(event.data.type, event.data.content, "", roomId); }); - it('should reject requests when the driver throws an exception', async () => { - const roomId = '!room:example.org'; + it("should reject requests when the driver throws an exception", async () => { + const roomId = "!room:example.org"; - driver.sendEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); + driver.sendEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object")); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', - content: 'hello', + type: "m.room.message", + content: "hello", room_id: roomId, }, }; @@ -411,34 +411,34 @@ describe('ClientWidgetApi', () => { `org.matrix.msc2762.send.event:${event.data.type}`, ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error sending event' }, + error: { message: "Error sending event" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { - const roomId = '!room:example.org'; + it("should reject with Matrix API error response thrown by driver", async () => { + const roomId = "!room:example.org"; driver.processError.mockImplementation(processCustomMatrixError); driver.sendEvent.mockRejectedValue( - new CustomMatrixError('failed to send event', 400, 'M_NOT_JSON', { - reason: 'Content must be a JSON object.', + new CustomMatrixError("failed to send event", 400, "M_NOT_JSON", { + reason: "Content must be a JSON object.", }), ); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', - content: 'hello', + type: "m.room.message", + content: "hello", room_id: roomId, }, }; @@ -448,20 +448,20 @@ describe('ClientWidgetApi', () => { `org.matrix.msc2762.send.event:${event.data.type}`, ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error sending event', + message: "Error sending event", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_NOT_JSON', - error: 'failed to send event', - reason: 'Content must be a JSON object.', + errcode: "M_NOT_JSON", + error: "failed to send event", + reason: "Content must be a JSON object.", }, } satisfies IMatrixApiError, }, @@ -470,17 +470,17 @@ describe('ClientWidgetApi', () => { }); }); - describe('send_event action for delayed events', () => { - it('fails to send delayed events', async () => { - const roomId = '!room:example.org'; + describe("send_event action for delayed events", () => { + it("fails to send delayed events", async () => { + const roomId = "!room:example.org"; const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', + type: "m.room.message", content: {}, delay: 5000, room_id: roomId, @@ -493,7 +493,7 @@ describe('ClientWidgetApi', () => { // Without the required capability ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -504,10 +504,10 @@ describe('ClientWidgetApi', () => { expect(driver.sendDelayedEvent).not.toBeCalled(); }); - it('sends delayed message events', async () => { - const roomId = '!room:example.org'; - const parentDelayId = 'fp'; - const timeoutDelayId = 'ft'; + it("sends delayed message events", async () => { + const roomId = "!room:example.org"; + const parentDelayId = "fp"; + const timeoutDelayId = "ft"; driver.sendDelayedEvent.mockResolvedValue({ roomId, @@ -516,11 +516,11 @@ describe('ClientWidgetApi', () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', + type: "m.room.message", content: {}, room_id: roomId, delay: 5000, @@ -531,10 +531,10 @@ describe('ClientWidgetApi', () => { await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, - 'org.matrix.msc4157.send.delayed_event', + "org.matrix.msc4157.send.delayed_event", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -553,10 +553,10 @@ describe('ClientWidgetApi', () => { ); }); - it('sends delayed state events', async () => { - const roomId = '!room:example.org'; - const parentDelayId = 'fp'; - const timeoutDelayId = 'ft'; + it("sends delayed state events", async () => { + const roomId = "!room:example.org"; + const parentDelayId = "fp"; + const timeoutDelayId = "ft"; driver.sendDelayedEvent.mockResolvedValue({ roomId, @@ -565,13 +565,13 @@ describe('ClientWidgetApi', () => { const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.topic', + type: "m.room.topic", content: {}, - state_key: '', + state_key: "", room_id: roomId, delay: 5000, parent_delay_id: parentDelayId, @@ -581,10 +581,10 @@ describe('ClientWidgetApi', () => { await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.state_event:${event.data.type}`, - 'org.matrix.msc4157.send.delayed_event', + "org.matrix.msc4157.send.delayed_event", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -598,90 +598,90 @@ describe('ClientWidgetApi', () => { event.data.parent_delay_id, event.data.type, event.data.content, - '', + "", roomId, ); }); - it('should reject requests when the driver throws an exception', async () => { - const roomId = '!room:example.org'; + it("should reject requests when the driver throws an exception", async () => { + const roomId = "!room:example.org"; - driver.sendDelayedEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); + driver.sendDelayedEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object")); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', - content: 'hello', + type: "m.room.message", + content: "hello", room_id: roomId, delay: 5000, - parent_delay_id: 'fp', + parent_delay_id: "fp", }, }; await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, - 'org.matrix.msc4157.send.delayed_event', + "org.matrix.msc4157.send.delayed_event", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error sending event' }, + error: { message: "Error sending event" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { - const roomId = '!room:example.org'; + it("should reject with Matrix API error response thrown by driver", async () => { + const roomId = "!room:example.org"; driver.processError.mockImplementation(processCustomMatrixError); driver.sendDelayedEvent.mockRejectedValue( - new CustomMatrixError('failed to send event', 400, 'M_NOT_JSON', { - reason: 'Content must be a JSON object.', + new CustomMatrixError("failed to send event", 400, "M_NOT_JSON", { + reason: "Content must be a JSON object.", }), ); const event: ISendEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendEvent, data: { - type: 'm.room.message', - content: 'hello', + type: "m.room.message", + content: "hello", room_id: roomId, delay: 5000, - parent_delay_id: 'fp', + parent_delay_id: "fp", }, }; await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, - 'org.matrix.msc4157.send.delayed_event', + "org.matrix.msc4157.send.delayed_event", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error sending event', + message: "Error sending event", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_NOT_JSON', - error: 'failed to send event', - reason: 'Content must be a JSON object.', + errcode: "M_NOT_JSON", + error: "failed to send event", + reason: "Content must be a JSON object.", }, } satisfies IMatrixApiError, }, @@ -690,21 +690,21 @@ describe('ClientWidgetApi', () => { }); }); - describe('receiving events', () => { - const roomId = '!room:example.org'; - const otherRoomId = '!other-room:example.org'; - const event = createRoomEvent({ room_id: roomId, type: 'm.room.message', content: 'hello' }); + describe("receiving events", () => { + const roomId = "!room:example.org"; + const otherRoomId = "!other-room:example.org"; + const event = createRoomEvent({ room_id: roomId, type: "m.room.message", content: "hello" }); const eventFromOtherRoom = createRoomEvent({ room_id: otherRoomId, - type: 'm.room.message', - content: 'test', + type: "m.room.message", + content: "test", }); - it('forwards events to the widget from one room only', async () => { + it("forwards events to the widget from one room only", async () => { // Give the widget capabilities to receive from just one room await loadIframe([ `org.matrix.msc2762.timeline:${roomId}`, - 'org.matrix.msc2762.receive.event:m.room.message', + "org.matrix.msc2762.receive.event:m.room.message", ]); // Event from the matching room should be forwarded @@ -716,13 +716,13 @@ describe('ClientWidgetApi', () => { expect(transport.send).not.toHaveBeenCalledWith(WidgetApiToWidgetAction.SendEvent, eventFromOtherRoom); }); - it('forwards events to the widget from the currently viewed room', async () => { + it("forwards events to the widget from the currently viewed room", async () => { clientWidgetApi.setViewedRoomId(roomId); // Give the widget capabilities to receive events without specifying // any rooms that it can read await loadIframe([ `org.matrix.msc2762.timeline:${roomId}`, - 'org.matrix.msc2762.receive.event:m.room.message', + "org.matrix.msc2762.receive.event:m.room.message", ]); // Event from the viewed room should be forwarded @@ -739,11 +739,11 @@ describe('ClientWidgetApi', () => { expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.SendEvent, eventFromOtherRoom); }); - it('forwards events to the widget from all rooms', async () => { + it("forwards events to the widget from all rooms", async () => { // Give the widget capabilities to receive from any known room await loadIframe([ `org.matrix.msc2762.timeline:${Symbols.AnyRoom}`, - 'org.matrix.msc2762.receive.event:m.room.message', + "org.matrix.msc2762.receive.event:m.room.message", ]); // Events from both rooms should be forwarded @@ -754,13 +754,13 @@ describe('ClientWidgetApi', () => { }); }); - describe('receiving room state', () => { - it('syncs initial state and feeds updates', async () => { - const roomId = '!room:example.org'; - const otherRoomId = '!other-room:example.org'; + describe("receiving room state", () => { + it("syncs initial state and feeds updates", async () => { + const roomId = "!room:example.org"; + const otherRoomId = "!other-room:example.org"; clientWidgetApi.setViewedRoomId(roomId); - jest.spyOn(transport, 'send').mockImplementation((action, data) => { + jest.spyOn(transport, "send").mockImplementation((action, data) => { if (action === WidgetApiToWidgetAction.SupportedApiVersions) { return Promise.resolve({ supported_versions: CurrentApiVersions }); } @@ -769,27 +769,27 @@ describe('ClientWidgetApi', () => { const topicEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.topic', - state_key: '', - content: { topic: 'Hello world!' }, + type: "m.room.topic", + state_key: "", + content: { topic: "Hello world!" }, }); const nameEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.name', - state_key: '', - content: { name: 'Test room' }, + type: "m.room.name", + state_key: "", + content: { name: "Test room" }, }); const joinRulesEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.join_rules', - state_key: '', - content: { join_rule: 'public' }, + type: "m.room.join_rules", + state_key: "", + content: { join_rule: "public" }, }); const otherRoomNameEvent = createRoomEvent({ room_id: otherRoomId, - type: 'm.room.name', - state_key: '', - content: { name: 'Other room' }, + type: "m.room.name", + state_key: "", + content: { name: "Other room" }, }); // Artificially delay the delivery of the join rules event @@ -798,31 +798,31 @@ describe('ClientWidgetApi', () => { driver.readRoomState.mockImplementation(async (rId, eventType, stateKey) => { if (rId === roomId) { - if (eventType === 'm.room.topic' && stateKey === '') return [topicEvent]; - if (eventType === 'm.room.name' && stateKey === '') return [nameEvent]; - if (eventType === 'm.room.join_rules' && stateKey === '') { + if (eventType === "m.room.topic" && stateKey === "") return [topicEvent]; + if (eventType === "m.room.name" && stateKey === "") return [nameEvent]; + if (eventType === "m.room.join_rules" && stateKey === "") { await joinRules; return [joinRulesEvent]; } } else if (rId === otherRoomId) { - if (eventType === 'm.room.name' && stateKey === '') return [otherRoomNameEvent]; + if (eventType === "m.room.name" && stateKey === "") return [otherRoomNameEvent]; } return []; }); await loadIframe([ - 'org.matrix.msc2762.receive.state_event:m.room.topic#', - 'org.matrix.msc2762.receive.state_event:m.room.name#', - 'org.matrix.msc2762.receive.state_event:m.room.join_rules#', + "org.matrix.msc2762.receive.state_event:m.room.topic#", + "org.matrix.msc2762.receive.state_event:m.room.name#", + "org.matrix.msc2762.receive.state_event:m.room.join_rules#", ]); // Simulate a race between reading the original join rules event and // the join rules being updated at the same time const newJoinRulesEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.join_rules', - state_key: '', - content: { join_rule: 'invite' }, + type: "m.room.join_rules", + state_key: "", + content: { join_rule: "invite" }, }); clientWidgetApi.feedStateUpdate(newJoinRulesEvent); // What happens if the original join rules are delivered after the @@ -844,9 +844,9 @@ describe('ClientWidgetApi', () => { // as expected const newTopicEvent = createRoomEvent({ room_id: roomId, - type: 'm.room.topic', - state_key: '', - content: { topic: 'Our new topic' }, + type: "m.room.topic", + state_key: "", + content: { topic: "Our new topic" }, }); clientWidgetApi.feedStateUpdate(newTopicEvent); @@ -874,22 +874,22 @@ describe('ClientWidgetApi', () => { }); }); - describe('update_delayed_event action', () => { - it('fails to update delayed events', async () => { + describe("update_delayed_event action", () => { + it("fails to update delayed events", async () => { const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', + delay_id: "f", action: UpdateDelayedEventAction.Send, }, }; await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -900,21 +900,21 @@ describe('ClientWidgetApi', () => { expect(driver.updateDelayedEvent).not.toBeCalled(); }); - it('fails to update delayed events with unsupported action', async () => { + it("fails to update delayed events with unsupported action", async () => { const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', - action: 'unknown' as UpdateDelayedEventAction, + delay_id: "f", + action: "unknown" as UpdateDelayedEventAction, }, }; - await loadIframe(['org.matrix.msc4157.update_delayed_event']); + await loadIframe(["org.matrix.msc4157.update_delayed_event"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -925,7 +925,7 @@ describe('ClientWidgetApi', () => { expect(driver.updateDelayedEvent).not.toBeCalled(); }); - it('updates delayed events', async () => { + it("updates delayed events", async () => { driver.updateDelayedEvent.mockResolvedValue(undefined); for (const action of [ @@ -935,18 +935,18 @@ describe('ClientWidgetApi', () => { ]) { const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', + delay_id: "f", action, }, }; - await loadIframe(['org.matrix.msc4157.update_delayed_event']); + await loadIframe(["org.matrix.msc4157.update_delayed_event"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, {}); @@ -956,67 +956,67 @@ describe('ClientWidgetApi', () => { } }); - it('should reject requests when the driver throws an exception', async () => { - driver.updateDelayedEvent.mockRejectedValue(new Error('M_BAD_JSON: Content must be a JSON object')); + it("should reject requests when the driver throws an exception", async () => { + driver.updateDelayedEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object")); const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', + delay_id: "f", action: UpdateDelayedEventAction.Send, }, }; - await loadIframe(['org.matrix.msc4157.update_delayed_event']); + await loadIframe(["org.matrix.msc4157.update_delayed_event"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error updating delayed event' }, + error: { message: "Error updating delayed event" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.updateDelayedEvent.mockRejectedValue( - new CustomMatrixError('failed to update delayed event', 400, 'M_NOT_JSON', { - reason: 'Content must be a JSON object.', + new CustomMatrixError("failed to update delayed event", 400, "M_NOT_JSON", { + reason: "Content must be a JSON object.", }), ); const event: IUpdateDelayedEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { - delay_id: 'f', + delay_id: "f", action: UpdateDelayedEventAction.Send, }, }; - await loadIframe(['org.matrix.msc4157.update_delayed_event']); + await loadIframe(["org.matrix.msc4157.update_delayed_event"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error updating delayed event', + message: "Error updating delayed event", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_NOT_JSON', - error: 'failed to update delayed event', - reason: 'Content must be a JSON object.', + errcode: "M_NOT_JSON", + error: "failed to update delayed event", + reason: "Content must be a JSON object.", }, } satisfies IMatrixApiError, }, @@ -1025,20 +1025,20 @@ describe('ClientWidgetApi', () => { }); }); - describe('send_to_device action', () => { - it('sends unencrypted to-device events', async () => { + describe("send_to_device action", () => { + it("sends unencrypted to-device events", async () => { const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1047,7 +1047,7 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, {}); @@ -1060,18 +1060,18 @@ describe('ClientWidgetApi', () => { ); }); - it('fails to send to-device events without event type', async () => { + it("fails to send to-device events without event type", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1080,54 +1080,54 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing event type' }, + error: { message: "Invalid request - missing event type" }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it('fails to send to-device events without event contents', async () => { + it("fails to send to-device events without event contents", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, }, }; await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing event contents' }, + error: { message: "Invalid request - missing event contents" }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it('fails to send to-device events without encryption flag', async () => { + it("fails to send to-device events without encryption flag", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1136,30 +1136,30 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing encryption flag' }, + error: { message: "Invalid request - missing encryption flag" }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it('fails to send to-device events with any event type', async () => { + it("fails to send to-device events with any event type", async () => { const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1168,34 +1168,34 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}_different`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Cannot send to-device events of this type' }, + error: { message: "Cannot send to-device events of this type" }, }); }); expect(driver.sendToDevice).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { + it("should reject requests when the driver throws an exception", async () => { driver.sendToDevice.mockRejectedValue( new Error("M_FORBIDDEN: You don't have permission to send to-device events"), ); const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1204,36 +1204,36 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Error sending event' }, + error: { message: "Error sending event" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.sendToDevice.mockRejectedValue( - new CustomMatrixError('failed to send event', 400, 'M_FORBIDDEN', { + new CustomMatrixError("failed to send event", 400, "M_FORBIDDEN", { reason: "You don't have permission to send to-device events", }), ); const event: ISendToDeviceFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SendToDevice, data: { - type: 'net.example.test', + type: "net.example.test", encrypted: false, messages: { - '@foo:bar.com': { + "@foo:bar.com": { DEVICEID: { - example_content_key: 'value', + example_content_key: "value", }, }, }, @@ -1242,19 +1242,19 @@ describe('ClientWidgetApi', () => { await loadIframe([`org.matrix.msc3819.send.to_device:${event.data.type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Error sending event', + message: "Error sending event", matrix_api_error: { http_status: 400, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_FORBIDDEN', - error: 'failed to send event', + errcode: "M_FORBIDDEN", + error: "failed to send event", reason: "You don't have permission to send to-device events", }, } satisfies IMatrixApiError, @@ -1264,40 +1264,40 @@ describe('ClientWidgetApi', () => { }); }); - describe('get_openid action', () => { - it('gets info', async () => { + describe("get_openid action", () => { + it("gets info", async () => { driver.askOpenID.mockImplementation((observable) => { observable.update({ state: OpenIDRequestState.Allowed, token: { - access_token: 'access_token', + access_token: "access_token", }, }); }); const event: IGetOpenIDActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.GetOpenIDCredentials, data: {}, }; await loadIframe([]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { state: OpenIDRequestState.Allowed, - access_token: 'access_token', + access_token: "access_token", }); }); expect(driver.askOpenID).toHaveBeenCalledWith(expect.any(SimpleObservable)); }); - it('fails when client provided invalid token', async () => { + it("fails when client provided invalid token", async () => { driver.askOpenID.mockImplementation((observable) => { observable.update({ state: OpenIDRequestState.Allowed, @@ -1306,19 +1306,19 @@ describe('ClientWidgetApi', () => { const event: IGetOpenIDActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.GetOpenIDCredentials, data: {}, }; await loadIframe([]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - error: { message: 'client provided invalid OIDC token for an allowed request' }, + error: { message: "client provided invalid OIDC token for an allowed request" }, }); }); @@ -1326,10 +1326,10 @@ describe('ClientWidgetApi', () => { }); }); - describe('com.beeper.read_room_account_data action', () => { - it('reads room account data', async () => { - const type = 'net.example.test'; - const roomId = '!room:example.org'; + describe("com.beeper.read_room_account_data action", () => { + it("reads room account data", async () => { + const type = "net.example.test"; + const roomId = "!room:example.org"; driver.readRoomAccountData.mockResolvedValue([ { @@ -1341,8 +1341,8 @@ describe('ClientWidgetApi', () => { const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data: { room_ids: [roomId], @@ -1352,7 +1352,7 @@ describe('ClientWidgetApi', () => { await loadIframe([`com.beeper.capabilities.receive.room_account_data:${type}`]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { @@ -1369,9 +1369,9 @@ describe('ClientWidgetApi', () => { expect(driver.readRoomAccountData).toHaveBeenCalledWith(event.data.type); }); - it('does not read room account data', async () => { - const type = 'net.example.test'; - const roomId = '!room:example.org'; + it("does not read room account data", async () => { + const type = "net.example.test"; + const roomId = "!room:example.org"; driver.readRoomAccountData.mockResolvedValue([ { @@ -1383,8 +1383,8 @@ describe('ClientWidgetApi', () => { const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data: { room_ids: [roomId], @@ -1394,11 +1394,11 @@ describe('ClientWidgetApi', () => { await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - error: { message: 'Cannot read room account data of this type' }, + error: { message: "Cannot read room account data of this type" }, }); }); @@ -1406,10 +1406,10 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc2876.read_events action', () => { - it('reads events from a specific room', async () => { - const roomId = '!room:example.org'; - const event = createRoomEvent({ room_id: roomId, type: 'net.example.test', content: 'test' }); + describe("org.matrix.msc2876.read_events action", () => { + it("reads events from a specific room", async () => { + const roomId = "!room:example.org"; + const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: "test" }); driver.readRoomTimeline.mockImplementation(async (rId) => { if (rId === roomId) return [event]; return []; @@ -1417,22 +1417,22 @@ describe('ClientWidgetApi', () => { const request: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', + type: "net.example.test", room_ids: [roomId], }, }; await loadIframe([ `org.matrix.msc2762.timeline:${roomId}`, - 'org.matrix.msc2762.receive.event:net.example.test', + "org.matrix.msc2762.receive.event:net.example.test", ]); clientWidgetApi.setViewedRoomId(roomId); - emitEvent(new CustomEvent('', { detail: request })); + emitEvent(new CustomEvent("", { detail: request })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(request, { @@ -1442,7 +1442,7 @@ describe('ClientWidgetApi', () => { expect(driver.readRoomTimeline).toHaveBeenCalledWith( roomId, - 'net.example.test', + "net.example.test", undefined, undefined, 0, @@ -1450,11 +1450,11 @@ describe('ClientWidgetApi', () => { ); }); - it('reads events from all rooms', async () => { - const roomId = '!room:example.org'; - const otherRoomId = '!other-room:example.org'; - const event = createRoomEvent({ room_id: roomId, type: 'net.example.test', content: 'test' }); - const otherRoomEvent = createRoomEvent({ room_id: otherRoomId, type: 'net.example.test', content: 'hi' }); + it("reads events from all rooms", async () => { + const roomId = "!room:example.org"; + const otherRoomId = "!other-room:example.org"; + const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: "test" }); + const otherRoomEvent = createRoomEvent({ room_id: otherRoomId, type: "net.example.test", content: "hi" }); driver.getKnownRooms.mockReturnValue([roomId, otherRoomId]); driver.readRoomTimeline.mockImplementation(async (rId) => { if (rId === roomId) return [event]; @@ -1464,22 +1464,22 @@ describe('ClientWidgetApi', () => { const request: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', + type: "net.example.test", room_ids: Symbols.AnyRoom, }, }; await loadIframe([ `org.matrix.msc2762.timeline:${Symbols.AnyRoom}`, - 'org.matrix.msc2762.receive.event:net.example.test', + "org.matrix.msc2762.receive.event:net.example.test", ]); clientWidgetApi.setViewedRoomId(roomId); - emitEvent(new CustomEvent('', { detail: request })); + emitEvent(new CustomEvent("", { detail: request })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(request, { @@ -1489,7 +1489,7 @@ describe('ClientWidgetApi', () => { expect(driver.readRoomTimeline).toHaveBeenCalledWith( roomId, - 'net.example.test', + "net.example.test", undefined, undefined, 0, @@ -1497,7 +1497,7 @@ describe('ClientWidgetApi', () => { ); expect(driver.readRoomTimeline).toHaveBeenCalledWith( otherRoomId, - 'net.example.test', + "net.example.test", undefined, undefined, 0, @@ -1505,40 +1505,40 @@ describe('ClientWidgetApi', () => { ); }); - it('reads state events with any state key', async () => { + it("reads state events with any state key", async () => { driver.readRoomTimeline.mockResolvedValue([ - createRoomEvent({ type: 'net.example.test', state_key: 'A' }), - createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + createRoomEvent({ type: "net.example.test", state_key: "A" }), + createRoomEvent({ type: "net.example.test", state_key: "B" }), ]); const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', + type: "net.example.test", state_key: true, }, }; - await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test']); - clientWidgetApi.setViewedRoomId('!room-id'); + await loadIframe(["org.matrix.msc2762.receive.state_event:net.example.test"]); + clientWidgetApi.setViewedRoomId("!room-id"); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { events: [ - createRoomEvent({ type: 'net.example.test', state_key: 'A' }), - createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + createRoomEvent({ type: "net.example.test", state_key: "A" }), + createRoomEvent({ type: "net.example.test", state_key: "B" }), ], }); }); expect(driver.readRoomTimeline).toBeCalledWith( - '!room-id', - 'net.example.test', + "!room-id", + "net.example.test", undefined, undefined, 0, @@ -1546,21 +1546,21 @@ describe('ClientWidgetApi', () => { ); }); - it('fails to read state events with any state key', async () => { + it("fails to read state events with any state key", async () => { const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', + type: "net.example.test", state_key: true, }, }; await loadIframe([]); // Without the required capability - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1571,57 +1571,57 @@ describe('ClientWidgetApi', () => { expect(driver.readRoomTimeline).not.toBeCalled(); }); - it('reads state events with a specific state key', async () => { - driver.readRoomTimeline.mockResolvedValue([createRoomEvent({ type: 'net.example.test', state_key: 'B' })]); + it("reads state events with a specific state key", async () => { + driver.readRoomTimeline.mockResolvedValue([createRoomEvent({ type: "net.example.test", state_key: "B" })]); const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', - state_key: 'B', + type: "net.example.test", + state_key: "B", }, }; - await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test#B']); - clientWidgetApi.setViewedRoomId('!room-id'); + await loadIframe(["org.matrix.msc2762.receive.state_event:net.example.test#B"]); + clientWidgetApi.setViewedRoomId("!room-id"); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - events: [createRoomEvent({ type: 'net.example.test', state_key: 'B' })], + events: [createRoomEvent({ type: "net.example.test", state_key: "B" })], }); }); expect(driver.readRoomTimeline).toBeCalledWith( - '!room-id', - 'net.example.test', + "!room-id", + "net.example.test", undefined, - 'B', + "B", 0, undefined, ); }); - it('fails to read state events with a specific state key', async () => { + it("fails to read state events with a specific state key", async () => { const event: IReadEventFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC2876ReadEvents, data: { - type: 'net.example.test', - state_key: 'B', + type: "net.example.test", + state_key: "B", }, }; // Request the capability for the wrong state key - await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test#A']); + await loadIframe(["org.matrix.msc2762.receive.state_event:net.example.test#A"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1633,39 +1633,39 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc3869.read_relations action', () => { - it('should present as supported api version', () => { + describe("org.matrix.msc3869.read_relations action", () => { + it("should present as supported api version", () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC3869]), }); }); - it('should handle and process the request', async () => { + it("should handle and process the request", async () => { driver.readEventRelations.mockResolvedValue({ chunk: [createRoomEvent()], }); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: '$event' }, + data: { event_id: "$event" }, }; - await loadIframe(['org.matrix.msc2762.receive.event:m.room.message']); + await loadIframe(["org.matrix.msc2762.receive.event:m.room.message"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1674,7 +1674,7 @@ describe('ClientWidgetApi', () => { }); expect(driver.readEventRelations).toBeCalledWith( - '$event', + "$event", undefined, undefined, undefined, @@ -1685,39 +1685,39 @@ describe('ClientWidgetApi', () => { ); }); - it('should only return events that match requested capabilities', async () => { + it("should only return events that match requested capabilities", async () => { driver.readEventRelations.mockResolvedValue({ chunk: [ createRoomEvent(), - createRoomEvent({ type: 'm.reaction' }), - createRoomEvent({ type: 'net.example.test', state_key: 'A' }), - createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + createRoomEvent({ type: "m.reaction" }), + createRoomEvent({ type: "net.example.test", state_key: "A" }), + createRoomEvent({ type: "net.example.test", state_key: "B" }), ], }); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: '$event' }, + data: { event_id: "$event" }, }; await loadIframe([ - 'org.matrix.msc2762.receive.event:m.room.message', - 'org.matrix.msc2762.receive.state_event:net.example.test#A', + "org.matrix.msc2762.receive.event:m.room.message", + "org.matrix.msc2762.receive.state_event:net.example.test#A", ]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - chunk: [createRoomEvent(), createRoomEvent({ type: 'net.example.test', state_key: 'A' })], + chunk: [createRoomEvent(), createRoomEvent({ type: "net.example.test", state_key: "A" })], }); }); expect(driver.readEventRelations).toBeCalledWith( - '$event', + "$event", undefined, undefined, undefined, @@ -1728,31 +1728,31 @@ describe('ClientWidgetApi', () => { ); }); - it('should accept all options and pass it to the driver', async () => { + it("should accept all options and pass it to the driver", async () => { driver.readEventRelations.mockResolvedValue({ chunk: [], }); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: '$event', - room_id: '!room-id', - event_type: 'm.room.message', - rel_type: 'm.reference', + event_id: "$event", + room_id: "!room-id", + event_type: "m.room.message", + rel_type: "m.reference", limit: 25, - from: 'from-token', - to: 'to-token', - direction: 'f', + from: "from-token", + to: "to-token", + direction: "f", }, }; - await loadIframe(['org.matrix.msc2762.timeline:!room-id']); + await loadIframe(["org.matrix.msc2762.timeline:!room-id"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -1761,127 +1761,127 @@ describe('ClientWidgetApi', () => { }); expect(driver.readEventRelations).toBeCalledWith( - '$event', - '!room-id', - 'm.reference', - 'm.room.message', - 'from-token', - 'to-token', + "$event", + "!room-id", + "m.reference", + "m.room.message", + "from-token", + "to-token", 25, - 'f', + "f", ); }); - it('should reject requests without event_id', async () => { + it("should reject requests without event_id", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing event ID' }, + error: { message: "Invalid request - missing event ID" }, }); }); - it('should reject requests with a negative limit', async () => { + it("should reject requests with a negative limit", async () => { const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: '$event', + event_id: "$event", limit: -1, }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - limit out of range' }, + error: { message: "Invalid request - limit out of range" }, }); }); - it('should reject requests when the room timeline was not requested', async () => { + it("should reject requests when the room timeline was not requested", async () => { const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, data: { - event_id: '$event', - room_id: '!another-room-id', + event_id: "$event", + room_id: "!another-room-id", }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unable to access room timeline: !another-room-id' }, + error: { message: "Unable to access room timeline: !another-room-id" }, }); }); - it('should reject requests when the driver throws an exception', async () => { + it("should reject requests when the driver throws an exception", async () => { driver.readEventRelations.mockRejectedValue( new Error("M_FORBIDDEN: You don't have permission to access that event"), ); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: '$event' }, + data: { event_id: "$event" }, }; await loadIframe(); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while reading relations' }, + error: { message: "Unexpected error while reading relations" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.readEventRelations.mockRejectedValue( - new CustomMatrixError('failed to read relations', 403, 'M_FORBIDDEN', { + new CustomMatrixError("failed to read relations", 403, "M_FORBIDDEN", { reason: "You don't have permission to access that event", }), ); const event: IReadRelationsFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3869ReadRelations, - data: { event_id: '$event' }, + data: { event_id: "$event" }, }; await loadIframe(); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while reading relations', + message: "Unexpected error while reading relations", matrix_api_error: { http_status: 403, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_FORBIDDEN', - error: 'failed to read relations', + errcode: "M_FORBIDDEN", + error: "failed to read relations", reason: "You don't have permission to access that event", }, } satisfies IMatrixApiError, @@ -1891,51 +1891,51 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc3973.user_directory_search action', () => { - it('should present as supported api version', () => { + describe("org.matrix.msc3973.user_directory_search action", () => { + it("should present as supported api version", () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC3973]), }); }); - it('should handle and process the request', async () => { + it("should handle and process the request", async () => { driver.searchUserDirectory.mockResolvedValue({ limited: true, results: [ { - userId: '@foo:bar.com', + userId: "@foo:bar.com", }, ], }); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: 'foo' }, + data: { search_term: "foo" }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { limited: true, results: [ { - user_id: '@foo:bar.com', + user_id: "@foo:bar.com", display_name: undefined, avatar_url: undefined, }, @@ -1943,61 +1943,61 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.searchUserDirectory).toBeCalledWith('foo', undefined); + expect(driver.searchUserDirectory).toBeCalledWith("foo", undefined); }); - it('should accept all options and pass it to the driver', async () => { + it("should accept all options and pass it to the driver", async () => { driver.searchUserDirectory.mockResolvedValue({ limited: false, results: [ { - userId: '@foo:bar.com', + userId: "@foo:bar.com", }, { - userId: '@bar:foo.com', - displayName: 'Bar', - avatarUrl: 'mxc://...', + userId: "@bar:foo.com", + displayName: "Bar", + avatarUrl: "mxc://...", }, ], }); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: { - search_term: 'foo', + search_term: "foo", limit: 5, }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { limited: false, results: [ { - user_id: '@foo:bar.com', + user_id: "@foo:bar.com", display_name: undefined, avatar_url: undefined, }, { - user_id: '@bar:foo.com', - display_name: 'Bar', - avatar_url: 'mxc://...', + user_id: "@bar:foo.com", + display_name: "Bar", + avatar_url: "mxc://...", }, ], }); }); - expect(driver.searchUserDirectory).toBeCalledWith('foo', 5); + expect(driver.searchUserDirectory).toBeCalledWith("foo", 5); }); - it('should accept empty search_term', async () => { + it("should accept empty search_term", async () => { driver.searchUserDirectory.mockResolvedValue({ limited: false, results: [], @@ -2005,15 +2005,15 @@ describe('ClientWidgetApi', () => { const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: '' }, + data: { search_term: "" }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { @@ -2022,126 +2022,126 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.searchUserDirectory).toBeCalledWith('', undefined); + expect(driver.searchUserDirectory).toBeCalledWith("", undefined); }); - it('should reject requests when the capability was not requested', async () => { + it("should reject requests when the capability was not requested", async () => { const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: 'foo' }, + data: { search_term: "foo" }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); expect(driver.searchUserDirectory).not.toBeCalled(); }); - it('should reject requests without search_term', async () => { + it("should reject requests without search_term", async () => { const event: IWidgetApiRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: {}, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - missing search term' }, + error: { message: "Invalid request - missing search term" }, }); expect(driver.searchUserDirectory).not.toBeCalled(); }); - it('should reject requests with a negative limit', async () => { + it("should reject requests with a negative limit", async () => { const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data: { - search_term: 'foo', + search_term: "foo", limit: -1, }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Invalid request - limit out of range' }, + error: { message: "Invalid request - limit out of range" }, }); expect(driver.searchUserDirectory).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.searchUserDirectory.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); + it("should reject requests when the driver throws an exception", async () => { + driver.searchUserDirectory.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: 'foo' }, + data: { search_term: "foo" }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while searching in the user directory' }, + error: { message: "Unexpected error while searching in the user directory" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.searchUserDirectory.mockRejectedValue( - new CustomMatrixError('failed to search the user directory', 429, 'M_LIMIT_EXCEEDED', { - reason: 'Too many requests', + new CustomMatrixError("failed to search the user directory", 429, "M_LIMIT_EXCEEDED", { + reason: "Too many requests", retry_after_ms: 2000, }), ); const event: IUserDirectorySearchFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, - data: { search_term: 'foo' }, + data: { search_term: "foo" }, }; - await loadIframe(['org.matrix.msc3973.user_directory_search']); + await loadIframe(["org.matrix.msc3973.user_directory_search"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while searching in the user directory', + message: "Unexpected error while searching in the user directory", matrix_api_error: { http_status: 429, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_LIMIT_EXCEEDED', - error: 'failed to search the user directory', - reason: 'Too many requests', + errcode: "M_LIMIT_EXCEEDED", + error: "failed to search the user directory", + reason: "Too many requests", retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2151,123 +2151,123 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc4039.get_media_config action', () => { - it('should present as supported api version', () => { + describe("org.matrix.msc4039.get_media_config action", () => { + it("should present as supported api version", () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC4039]), }); }); - it('should handle and process the request', async () => { + it("should handle and process the request", async () => { driver.getMediaConfig.mockResolvedValue({ - 'm.upload.size': 1000, + "m.upload.size": 1000, }); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - 'm.upload.size': 1000, + "m.upload.size": 1000, }); }); expect(driver.getMediaConfig).toBeCalled(); }); - it('should reject requests when the capability was not requested', async () => { + it("should reject requests when the capability was not requested", async () => { const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); expect(driver.getMediaConfig).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.getMediaConfig.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); + it("should reject requests when the driver throws an exception", async () => { + driver.getMediaConfig.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while getting the media configuration' }, + error: { message: "Unexpected error while getting the media configuration" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.getMediaConfig.mockRejectedValue( - new CustomMatrixError('failed to get the media configuration', 429, 'M_LIMIT_EXCEEDED', { - reason: 'Too many requests', + new CustomMatrixError("failed to get the media configuration", 429, "M_LIMIT_EXCEEDED", { + reason: "Too many requests", retry_after_ms: 2000, }), ); const event: IGetMediaConfigActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction, data: {}, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while getting the media configuration', + message: "Unexpected error while getting the media configuration", matrix_api_error: { http_status: 429, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_LIMIT_EXCEEDED', - error: 'failed to get the media configuration', - reason: 'Too many requests', + errcode: "M_LIMIT_EXCEEDED", + error: "failed to get the media configuration", + reason: "Too many requests", retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2277,17 +2277,17 @@ describe('ClientWidgetApi', () => { }); }); - describe('MSC4039', () => { - it('should present as supported api version', () => { + describe("MSC4039", () => { + it("should present as supported api version", () => { const event: ISupportedVersionsActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.SupportedApiVersions, data: {}, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { supported_versions: expect.arrayContaining([UnstableApiVersion.MSC4039]), @@ -2295,115 +2295,115 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc4039.upload_file action', () => { - it('should handle and process the request', async () => { + describe("org.matrix.msc4039.upload_file action", () => { + it("should handle and process the request", async () => { driver.uploadFile.mockResolvedValue({ - contentUri: 'mxc://...', + contentUri: "mxc://...", }); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: 'data', + file: "data", }, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - content_uri: 'mxc://...', + content_uri: "mxc://...", }); }); expect(driver.uploadFile).toBeCalled(); }); - it('should reject requests when the capability was not requested', async () => { + it("should reject requests when the capability was not requested", async () => { const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: 'data', + file: "data", }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); expect(driver.uploadFile).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.uploadFile.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); + it("should reject requests when the driver throws an exception", async () => { + driver.uploadFile.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: 'data', + file: "data", }, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while uploading a file' }, + error: { message: "Unexpected error while uploading a file" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.uploadFile.mockRejectedValue( - new CustomMatrixError('failed to upload a file', 429, 'M_LIMIT_EXCEEDED', { - reason: 'Too many requests', + new CustomMatrixError("failed to upload a file", 429, "M_LIMIT_EXCEEDED", { + reason: "Too many requests", retry_after_ms: 2000, }), ); const event: IUploadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039UploadFileAction, data: { - file: 'data', + file: "data", }, }; - await loadIframe(['org.matrix.msc4039.upload_file']); + await loadIframe(["org.matrix.msc4039.upload_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while uploading a file', + message: "Unexpected error while uploading a file", matrix_api_error: { http_status: 429, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_LIMIT_EXCEEDED', - error: 'failed to upload a file', - reason: 'Too many requests', + errcode: "M_LIMIT_EXCEEDED", + error: "failed to upload a file", + reason: "Too many requests", retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2413,115 +2413,115 @@ describe('ClientWidgetApi', () => { }); }); - describe('org.matrix.msc4039.download_file action', () => { - it('should handle and process the request', async () => { + describe("org.matrix.msc4039.download_file action", () => { + it("should handle and process the request", async () => { driver.downloadFile.mockResolvedValue({ - file: 'test contents', + file: "test contents", }); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: 'mxc://example.com/test_file', + content_uri: "mxc://example.com/test_file", }, }; - await loadIframe(['org.matrix.msc4039.download_file']); + await loadIframe(["org.matrix.msc4039.download_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - file: 'test contents', + file: "test contents", }); }); - expect(driver.downloadFile).toHaveBeenCalledWith('mxc://example.com/test_file'); + expect(driver.downloadFile).toHaveBeenCalledWith("mxc://example.com/test_file"); }); - it('should reject requests when the capability was not requested', async () => { + it("should reject requests when the capability was not requested", async () => { const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: 'mxc://example.com/test_file', + content_uri: "mxc://example.com/test_file", }, }; - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Missing capability' }, + error: { message: "Missing capability" }, }); expect(driver.uploadFile).not.toBeCalled(); }); - it('should reject requests when the driver throws an exception', async () => { - driver.downloadFile.mockRejectedValue(new Error('M_LIMIT_EXCEEDED: Too many requests')); + it("should reject requests when the driver throws an exception", async () => { + driver.downloadFile.mockRejectedValue(new Error("M_LIMIT_EXCEEDED: Too many requests")); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: 'mxc://example.com/test_file', + content_uri: "mxc://example.com/test_file", }, }; - await loadIframe(['org.matrix.msc4039.download_file']); + await loadIframe(["org.matrix.msc4039.download_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { - error: { message: 'Unexpected error while downloading a file' }, + error: { message: "Unexpected error while downloading a file" }, }); }); }); - it('should reject with Matrix API error response thrown by driver', async () => { + it("should reject with Matrix API error response thrown by driver", async () => { driver.processError.mockImplementation(processCustomMatrixError); driver.downloadFile.mockRejectedValue( - new CustomMatrixError('failed to download a file', 429, 'M_LIMIT_EXCEEDED', { - reason: 'Too many requests', + new CustomMatrixError("failed to download a file", 429, "M_LIMIT_EXCEEDED", { + reason: "Too many requests", retry_after_ms: 2000, }), ); const event: IDownloadFileActionFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.MSC4039DownloadFileAction, data: { - content_uri: 'mxc://example.com/test_file', + content_uri: "mxc://example.com/test_file", }, }; - await loadIframe(['org.matrix.msc4039.download_file']); + await loadIframe(["org.matrix.msc4039.download_file"]); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toBeCalledWith(event, { error: { - message: 'Unexpected error while downloading a file', + message: "Unexpected error while downloading a file", matrix_api_error: { http_status: 429, http_headers: {}, - url: '', + url: "", response: { - errcode: 'M_LIMIT_EXCEEDED', - error: 'failed to download a file', - reason: 'Too many requests', + errcode: "M_LIMIT_EXCEEDED", + error: "failed to download a file", + reason: "Too many requests", retry_after_ms: 2000, }, } satisfies IMatrixApiError, @@ -2531,13 +2531,13 @@ describe('ClientWidgetApi', () => { }); }); - it('updates theme', () => { - clientWidgetApi.updateTheme({ name: 'dark' }); - expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.ThemeChange, { name: 'dark' }); + it("updates theme", () => { + clientWidgetApi.updateTheme({ name: "dark" }); + expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.ThemeChange, { name: "dark" }); }); - it('updates language', () => { - clientWidgetApi.updateLanguage('tlh'); - expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.LanguageChange, { lang: 'tlh' }); + it("updates language", () => { + clientWidgetApi.updateLanguage("tlh"); + expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.LanguageChange, { lang: "tlh" }); }); });