diff --git a/.babelrc b/.babelrc index 028dc10..199c68a 100644 --- a/.babelrc +++ b/.babelrc @@ -1,10 +1,5 @@ { "sourceMaps": true, - "presets": [ - "@babel/preset-env", - "@babel/preset-typescript" - ], - "plugins": [ - "@babel/plugin-proposal-class-properties" - ] + "presets": ["@babel/preset-env", "@babel/preset-typescript"], + "plugins": ["@babel/plugin-proposal-class-properties"] } diff --git a/.eslintrc.js b/.eslintrc.js index 2ce44f3..96ca83a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,10 +1,6 @@ module.exports = { - plugins: [ - "matrix-org", - ], - extends: [ - "plugin:matrix-org/babel", - ], + plugins: ["matrix-org"], + extends: ["plugin:matrix-org/babel"], parserOptions: { project: ["./tsconfig-dev.json"], }, @@ -19,12 +15,15 @@ module.exports = { "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", - }], + "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", @@ -32,19 +31,22 @@ module.exports = { "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", + 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", + "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..4e25fe4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,38 +1,41 @@ name: Build and test on: - push: - branches: - - master - pull_request: + push: + branches: + - master + pull_request: jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - cache: 'yarn' - - - name: Install NPM packages - run: yarn install --frozen-lockfile - - - name: Check Linting Rules and Types - run: yarn lint - - - name: test - run: yarn test --coverage - - - name: Upload coverage - uses: actions/upload-artifact@v4 - with: - name: coverage - path: | - coverage - !coverage/lcov-report - - - name: build - run: yarn build + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + cache: "yarn" + + - name: Install NPM packages + run: yarn install --frozen-lockfile + + - name: Check Linting Rules and Types + run: yarn lint + + - name: Check Formatting + run: yarn prettier:check + + - name: test + run: yarn test --coverage + + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage + path: | + coverage + !coverage/lcov-report + + - name: build + run: yarn build diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fbc5277..7f13a4d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,59 +1,59 @@ name: Release Automation on: - workflow_dispatch: - inputs: - version-bump: - description: The scale of the version bump required for semver compatibility - required: true - default: patch - type: choice - options: - - patch - - minor - - major + workflow_dispatch: + inputs: + version-bump: + description: The scale of the version bump required for semver compatibility + required: true + default: patch + type: choice + options: + - patch + - minor + - major concurrency: release permissions: - contents: write + contents: write jobs: - release: - name: "Release & Publish" - runs-on: ubuntu-latest - steps: - - name: 🧮 Checkout code - uses: actions/checkout@v4 - with: - token: ${{ secrets.ELEMENT_BOT_TOKEN }} + release: + name: "Release & Publish" + runs-on: ubuntu-latest + steps: + - name: 🧮 Checkout code + uses: actions/checkout@v4 + with: + token: ${{ secrets.ELEMENT_BOT_TOKEN }} - - name: 🔧 Set up node environment - uses: actions/setup-node@v4 - with: - cache: 'yarn' + - name: 🔧 Set up node environment + uses: actions/setup-node@v4 + with: + cache: "yarn" - - name: 🛠️ Setup - run: yarn install --pure-lockfile + - name: 🛠️ Setup + run: yarn install --pure-lockfile - - name: 👊 Bump version - run: | - yarn version --no-git-tag-version --${{ github.event.inputs.version-bump }} - git config --global user.name 'ElementRobot' - git config --global user.email 'releases@riot.im' - git commit -am "${{ github.event.inputs.version-bump }} version bump" - git push + - name: 👊 Bump version + run: | + yarn version --no-git-tag-version --${{ github.event.inputs.version-bump }} + git config --global user.name 'ElementRobot' + git config --global user.email 'releases@riot.im' + git commit -am "${{ github.event.inputs.version-bump }} version bump" + git push - - name: 📖 Build lib - run: yarn build + - name: 📖 Build lib + run: yarn build - - name: 🚀 Publish to npm - id: npm-publish - uses: JS-DevTools/npm-publish@v3 - with: - token: ${{ secrets.NPM_TOKEN }} - access: public + - name: 🚀 Publish to npm + id: npm-publish + uses: JS-DevTools/npm-publish@v3 + with: + token: ${{ secrets.NPM_TOKEN }} + access: public - - name: 🧬 Create release - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ steps.npm-publish.outputs.version }} - body: ${{ steps.npm-publish.outputs.version }} Release - draft: false - prerelease: false + - name: 🧬 Create release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.npm-publish.outputs.version }} + body: ${{ steps.npm-publish.outputs.version }} Release + draft: false + prerelease: false diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..6a17910 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1 @@ +module.exports = require("eslint-plugin-matrix-org/.prettierrc.js"); diff --git a/README.md b/README.md index 56bef5f..a29fac5 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 @@ -52,7 +52,7 @@ 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, {custom: "reply"}); + 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. @@ -83,7 +83,7 @@ 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.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..18f9565 100644 --- a/examples/widget/index.css +++ b/examples/widget/index.css @@ -14,10 +14,11 @@ * limitations under the License. */ -html, body { +html, +body { background-color: #ffffff; color: #000000; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; } body { diff --git a/examples/widget/index.html b/examples/widget/index.html index 6e1c682..92fa58f 100644 --- a/examples/widget/index.html +++ b/examples/widget/index.html @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. --> - + - - Example Widget + + Example Widget - - - - - - -
Loading...
- - - - - - - - - + + + + + + - + } + + diff --git a/examples/widget/utils.js b/examples/widget/utils.js index 2d8076b..705a6f0 100644 --- a/examples/widget/utils.js +++ b/examples/widget/utils.js @@ -15,8 +15,8 @@ */ 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) { diff --git a/package.json b/package.json index f2d0619..f3f7719 100644 --- a/package.json +++ b/package.json @@ -1,76 +1,79 @@ { - "name": "matrix-widget-api", - "version": "1.13.0", - "description": "Matrix Widget API SDK", - "main": "./lib/index.js", - "types": "./lib/index.d.ts", - "repository": "https://github.com/matrix-org/matrix-widget-api", - "author": "The Matrix.org Foundation C.I.C.", - "license": "Apache-2.0", - "scripts": { - "start": "tsc -w", - "clean": "rimraf lib dist", - "build": "yarn clean && yarn build:compile && yarn build:types && yarn build:browser", - "build:compile": "babel -d lib --verbose --extensions \".ts\" src", - "build:types": "tsc --emitDeclarationOnly", - "build:browser": "yarn build:browser:dev && yarn build:browser:prod", - "build:browser:dev": "browserify lib/index.js --debug --s mxwidgets -o dist/api.js", - "build:browser:prod": "browserify lib/index.js --s mxwidgets -p tinyify -o dist/api.min.js", - "lint": "yarn lint:types && yarn lint:ts && yarn lint:workflows", - "lint:ts": "eslint src test", - "lint:types": "tsc --noEmit", - "lint:fix": "eslint src test --fix", - "lint:workflows": "find .github/workflows -type f \\( -iname '*.yaml' -o -iname '*.yml' \\) | xargs -I {} sh -c 'echo \"Linting {}\"; action-validator \"{}\"'", - "test": "jest" - }, - "files": [ - "src", - "lib", - "dist", - "package.json", - "README.md", - "LICENSE", - "CONTRIBUTING.rst" - ], - "devDependencies": { - "@action-validator/cli": "^0.6.0", - "@action-validator/core": "^0.5.3", - "@babel/cli": "^7.11.6", - "@babel/core": "^7.11.6", - "@babel/eslint-parser": "^7.25.9", - "@babel/eslint-plugin": "^7.25.9", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/preset-env": "^7.11.5", - "@babel/preset-typescript": "^7.10.4", - "@casualbot/jest-sonar-reporter": "^2.2.7", - "@stylistic/eslint-plugin": "^2.10.1", - "@testing-library/dom": "^8.0.0", - "@types/jest": "^29.5.12", - "@types/node": "^18.16.0", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "browserify": "^17.0.0", - "eslint": "^8.0.0", - "eslint-config-google": "^0.14.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-babel": "^5.3.1", - "eslint-plugin-import": "^2.31.0", - "eslint-plugin-matrix-org": "^2.0.0", - "eslint-plugin-unicorn": "^56.0.0", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", - "rimraf": "^3.0.2", - "tinyify": "^3.0.0", - "ts-node": "^10.9.1", - "typescript": "^5.0.4" - }, - "dependencies": { - "@types/events": "^3.0.0", - "events": "^3.2.0" - }, - "@casualbot/jest-sonar-reporter": { - "outputDirectory": "coverage", - "outputName": "jest-sonar-report.xml", - "relativePaths": true - } + "name": "matrix-widget-api", + "version": "1.13.0", + "description": "Matrix Widget API SDK", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "repository": "https://github.com/matrix-org/matrix-widget-api", + "author": "The Matrix.org Foundation C.I.C.", + "license": "Apache-2.0", + "scripts": { + "start": "tsc -w", + "clean": "rimraf lib dist", + "build": "yarn clean && yarn build:compile && yarn build:types && yarn build:browser", + "build:compile": "babel -d lib --verbose --extensions \".ts\" src", + "build:types": "tsc --emitDeclarationOnly", + "build:browser": "yarn build:browser:dev && yarn build:browser:prod", + "build:browser:dev": "browserify lib/index.js --debug --s mxwidgets -o dist/api.js", + "build:browser:prod": "browserify lib/index.js --s mxwidgets -p tinyify -o dist/api.min.js", + "lint": "yarn lint:types && yarn lint:ts && yarn lint:workflows", + "lint:ts": "eslint src test", + "lint:types": "tsc --noEmit", + "lint:fix": "eslint src test --fix", + "lint:workflows": "find .github/workflows -type f \\( -iname '*.yaml' -o -iname '*.yml' \\) | xargs -I {} sh -c 'echo \"Linting {}\"; action-validator \"{}\"'", + "prettier:check": "prettier -c .", + "prettier:format": "prettier -w .", + "test": "jest" + }, + "files": [ + "src", + "lib", + "dist", + "package.json", + "README.md", + "LICENSE", + "CONTRIBUTING.rst" + ], + "devDependencies": { + "@action-validator/cli": "^0.6.0", + "@action-validator/core": "^0.5.3", + "@babel/cli": "^7.11.6", + "@babel/core": "^7.11.6", + "@babel/eslint-parser": "^7.25.9", + "@babel/eslint-plugin": "^7.25.9", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/preset-env": "^7.11.5", + "@babel/preset-typescript": "^7.10.4", + "@casualbot/jest-sonar-reporter": "^2.2.7", + "@stylistic/eslint-plugin": "^2.10.1", + "@testing-library/dom": "^8.0.0", + "@types/jest": "^29.5.12", + "@types/node": "^18.16.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "browserify": "^17.0.0", + "eslint": "^8.0.0", + "eslint-config-google": "^0.14.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-babel": "^5.3.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-matrix-org": "^2.0.0", + "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", + "typescript": "^5.0.4" + }, + "dependencies": { + "@types/events": "^3.0.0", + "events": "^3.2.0" + }, + "@casualbot/jest-sonar-reporter": { + "outputDirectory": "coverage", + "outputName": "jest-sonar-report.xml", + "relativePaths": true + } } diff --git a/renovate.json b/renovate.json index 5db72dd..d383718 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,4 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ] + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"] } diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index 4449d10..9c949b4 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -175,12 +175,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)); @@ -194,36 +189,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 { @@ -254,17 +251,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 { @@ -274,14 +273,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) { @@ -326,14 +328,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 { @@ -354,26 +359,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" }, }); } @@ -383,9 +389,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); } @@ -394,7 +403,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( @@ -421,12 +433,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"); @@ -457,24 +469,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" }, }); } @@ -482,13 +494,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}` }, }); } } @@ -503,14 +515,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" }, }); } } @@ -518,45 +530,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" }, }); } @@ -578,11 +591,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" }, }); } @@ -605,31 +618,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" }, }); } @@ -637,16 +654,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" }, }); } } @@ -654,19 +674,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 { @@ -701,7 +721,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 @@ -722,7 +742,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" }, }); } } @@ -731,7 +751,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 @@ -765,28 +785,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"); @@ -800,7 +822,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" }, }); @@ -813,21 +835,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"); @@ -844,10 +861,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"); @@ -864,10 +878,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"); @@ -884,10 +897,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"); @@ -999,15 +1009,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(); } /** @@ -1126,38 +1134,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); + 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); } - 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); - } - }, - 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)); } } } @@ -1175,8 +1187,8 @@ export class ClientWidgetApi extends EventEmitter { public async feedStateUpdate(rawEvent: IRoomEvent): Promise { 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) { 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 6743ad6..a9ae879 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { waitFor } from '@testing-library/dom'; +import { waitFor } from "@testing-library/dom"; import { ClientWidgetApi } from "../src/ClientWidgetApi"; import { WidgetDriver } from "../src/driver/WidgetDriver"; @@ -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, }; @@ -80,21 +80,23 @@ 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', () => { +describe("ClientWidgetApi", () => { let capabilities: Capability[]; let iframe: HTMLIFrameElement; let driver: jest.Mocked; @@ -105,18 +107,18 @@ describe('ClientWidgetApi', () => { async function loadIframe(caps: Capability[] = []): Promise { capabilities = caps; - const ready = new Promise(resolve => { - clientWidgetApi.once('ready', resolve); + const ready = new Promise((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 = { @@ -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(() => { @@ -164,154 +164,145 @@ 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, {}); }); - expect(driver.navigate).toHaveBeenCalledWith( - event.data.uri, - ); + 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, }, @@ -320,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, @@ -332,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, }, @@ -347,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, { @@ -356,17 +347,12 @@ 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 () => { - 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, @@ -375,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, }, }; @@ -391,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, { @@ -400,29 +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, }, }; @@ -432,39 +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, }, }; @@ -474,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, }, @@ -496,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, @@ -519,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, { @@ -530,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, @@ -542,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, @@ -557,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, { @@ -579,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, @@ -591,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, @@ -607,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, { @@ -624,97 +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, }, @@ -723,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 @@ -749,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 @@ -772,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 @@ -787,10 +754,10 @@ 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) => { @@ -802,60 +769,60 @@ 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 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) { - 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 @@ -864,57 +831,52 @@ 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 // 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); 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]), + }); }); }); }); - describe('dont receive UpdateState if version not supported', () => { - it('syncs initial state and feeds updates', async () => { - const roomId = '!room:example.org'; + describe("dont receive UpdateState if version not supported", () => { + it("syncs initial state and feeds updates", async () => { + const roomId = "!room:example.org"; clientWidgetApi.setViewedRoomId(roomId); jest.spyOn(transport, "send").mockImplementation((action, data) => { if (action === WidgetApiToWidgetAction.SupportedApiVersions) { @@ -923,42 +885,39 @@ describe('ClientWidgetApi', () => { return Promise.resolve({}); }); - await loadIframe([ - 'org.matrix.msc2762.receive.state_event:m.room.join_rules#', - ]); + await loadIframe(["org.matrix.msc2762.receive.state_event:m.room.join_rules#"]); 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); await waitFor(() => { - // Only the updated join rules should have been delivered expect(transport.send).not.toHaveBeenCalledWith(WidgetApiToWidgetAction.UpdateState); }); }); }); - 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, { @@ -969,21 +928,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, { @@ -994,7 +953,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 [ @@ -1004,98 +963,88 @@ 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, {}); }); - 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"), - ); + 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, }, @@ -1104,20 +1053,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': { - 'DEVICEID': { - 'example_content_key': 'value', + "@foo:bar.com": { + DEVICEID: { + example_content_key: "value", }, }, }, @@ -1126,7 +1075,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, {}); @@ -1139,18 +1088,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': { - 'DEVICEID': { - 'example_content_key': 'value', + "@foo:bar.com": { + DEVICEID: { + example_content_key: "value", }, }, }, @@ -1159,54 +1108,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': { - 'DEVICEID': { - 'example_content_key': 'value', + "@foo:bar.com": { + DEVICEID: { + example_content_key: "value", }, }, }, @@ -1215,30 +1164,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': { - 'DEVICEID': { - 'example_content_key': 'value', + "@foo:bar.com": { + DEVICEID: { + example_content_key: "value", }, }, }, @@ -1247,34 +1196,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': { - 'DEVICEID': { - 'example_content_key': 'value', + "@foo:bar.com": { + DEVICEID: { + example_content_key: "value", }, }, }, @@ -1283,41 +1232,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', - { - 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 = { 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': { - 'DEVICEID': { - 'example_content_key': 'value', + "@foo:bar.com": { + DEVICEID: { + example_content_key: "value", }, }, }, @@ -1326,19 +1270,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, @@ -1348,8 +1292,8 @@ 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, @@ -1361,15 +1305,15 @@ 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, { @@ -1381,7 +1325,7 @@ describe('ClientWidgetApi', () => { 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, @@ -1390,19 +1334,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" }, }); }); @@ -1410,21 +1354,23 @@ 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([{ - type, - room_id: roomId, - content: {}, - }]); + driver.readRoomAccountData.mockResolvedValue([ + { + type, + room_id: roomId, + content: {}, + }, + ]); const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data: { room_ids: [roomId], @@ -1432,39 +1378,41 @@ 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 })); + emitEvent(new CustomEvent("", { detail: event })); await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { - events: [{ - type, - room_id: roomId, - content: {}, - }], + events: [ + { + type, + room_id: roomId, + content: {}, + }, + ], }); }); 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([{ - type, - room_id: roomId, - content: {}, - }]); + driver.readRoomAccountData.mockResolvedValue([ + { + type, + room_id: roomId, + content: {}, + }, + ]); const event: IReadRoomAccountDataFromWidgetActionRequest = { api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', + widgetId: "test", + requestId: "0", action: WidgetApiFromWidgetAction.BeeperReadRoomAccountData, data: { room_ids: [roomId], @@ -1474,11 +1422,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" }, }); }); @@ -1486,10 +1434,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 []; @@ -1497,22 +1445,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, { @@ -1521,15 +1469,20 @@ describe('ClientWidgetApi', () => { }); expect(driver.readRoomTimeline).toHaveBeenCalledWith( - roomId, 'net.example.test', undefined, undefined, 0, undefined, + roomId, + "net.example.test", + undefined, + undefined, + 0, + undefined, ); }); - 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]; @@ -1539,22 +1492,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, { @@ -1563,64 +1516,79 @@ 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, ); }); - 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', undefined, undefined, 0, undefined, + "!room-id", + "net.example.test", + undefined, + undefined, + 0, + undefined, ); }); - 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, { @@ -1631,56 +1599,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', undefined, 'B', 0, undefined, + "!room-id", + "net.example.test", + undefined, + "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, { @@ -1692,43 +1661,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, - ]), + 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, { @@ -1737,78 +1702,85 @@ describe('ClientWidgetApi', () => { }); expect(driver.readEventRelations).toBeCalledWith( - '$event', undefined, undefined, undefined, undefined, undefined, - undefined, undefined, + "$event", + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, ); }); - 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', undefined, undefined, undefined, undefined, undefined, - undefined, undefined, + "$event", + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + 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.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, { @@ -1817,126 +1789,127 @@ 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", ); }); - 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', - { - 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 = { 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, @@ -1946,115 +1919,113 @@ 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, - ]), + 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', - }], + results: [ + { + 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', - display_name: undefined, - avatar_url: undefined, - }], + results: [ + { + user_id: "@foo:bar.com", + display_name: undefined, + avatar_url: undefined, + }, + ], }); }); - 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: [], @@ -2062,17 +2033,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, { @@ -2081,141 +2050,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', - 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 = { 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, @@ -2225,138 +2179,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, - ]), + 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', - 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 = { 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, @@ -2366,148 +2305,133 @@ 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, - ]), + supported_versions: expect.arrayContaining([UnstableApiVersion.MSC4039]), }); }); }); - 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', - 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 = { 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, @@ -2517,128 +2441,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', - 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 = { 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, @@ -2648,13 +2559,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 d95dcb8..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; @@ -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 { @@ -81,7 +81,7 @@ class ClientTransportHelper { } } -describe('WidgetApi', () => { +describe("WidgetApi", () => { let widgetApi: WidgetApi; let widgetTransportHelper: WidgetTransportHelper; let clientListener: (e: MessageEvent) => void; @@ -96,10 +96,7 @@ describe('WidgetApi', () => { 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) { @@ -122,21 +119,27 @@ describe('WidgetApi', () => { 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({ + 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({ chunk: [], }); @@ -144,27 +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 () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); + 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."); + 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(); @@ -174,24 +183,32 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - 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'); - }); - - it('should handle an error with details', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC3869] } as ISupportedVersionsActionResponseData, - ); + 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"); + }); + + it("should handle an error with details", async () => { + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC3869], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { @@ -200,78 +217,69 @@ describe('WidgetApi', () => { url: "", response: { errcode: "M_UNKNOWN", - error: 'Unknown error', + 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, - ); + describe("sendEvent", () => { + it("sends message events", async () => { + widgetTransportHelper.queueResponse({ + 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 () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - event_id: '$event', - } as ISendEventFromWidgetResponseData, - ); + it("sends state events", async () => { + widgetTransportHelper.queueResponse({ + 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 () => { - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } } as IWidgetApiErrorResponseData, - ); + it("should handle an error", async () => { + 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 () => { + it("should handle an error with details", async () => { const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, @@ -279,124 +287,86 @@ describe('WidgetApi', () => { url: "", response: { errcode: "M_UNKNOWN", - error: 'Unknown error', + 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, - ); + describe("delayed sendEvent", () => { + it("sends delayed message events", async () => { + widgetTransportHelper.queueResponse({ + 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 () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - delay_id: 'id', - } as ISendEventFromWidgetResponseData, - ); + it("sends delayed state events", async () => { + widgetTransportHelper.queueResponse({ + 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 () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - delay_id: 'id', - } as ISendEventFromWidgetResponseData, - ); + it("sends delayed child action message events", async () => { + widgetTransportHelper.queueResponse({ + 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 () => { - widgetTransportHelper.queueResponse( - { - room_id: '!room-id', - delay_id: 'id', - } as ISendEventFromWidgetResponseData, - ); + it("sends delayed child action state events", async () => { + widgetTransportHelper.queueResponse({ + room_id: "!room-id", + delay_id: "id", + } as ISendEventFromWidgetResponseData); - await expect(widgetApi.sendStateEvent( - 'm.room.topic', - "", - {}, - '!room-id', - 1000, - undefined, - )).resolves.toEqual({ - room_id: '!room-id', - delay_id: 'id', + 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, - ); + it("should handle an error", async () => { + 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 () => { + it("should handle an error with details", async () => { const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, @@ -404,51 +374,41 @@ describe('WidgetApi', () => { url: "", response: { errcode: "M_UNKNOWN", - error: 'Unknown error', + 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 () => { + 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, - ); + it("should handle an error", async () => { + 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 () => { + it("should handle an error with details", async () => { const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { http_status: 400, @@ -456,73 +416,56 @@ describe('WidgetApi', () => { url: "", response: { errcode: "M_UNKNOWN", - error: 'Unknown error', + 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', () => { + 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', - ]); + 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 () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC3973] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { - limited: false, - results: [], - } as IUserDirectorySearchFromWidgetResponseData, - ); + 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); - await expect(widgetApi.searchUserDirectory( - 'foo', 10, - )).resolves.toEqual({ + await expect(widgetApi.searchUserDirectory("foo", 10)).resolves.toEqual({ limited: false, results: [], }); @@ -531,20 +474,18 @@ 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 () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); + 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(); expect(request).not.toBeUndefined(); @@ -554,23 +495,19 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC3973] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } }, - ); + it("should handle an error", async () => { + 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, - ); + it("should handle an error with details", async () => { + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC3973], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { @@ -579,37 +516,35 @@ describe('WidgetApi', () => { url: "", response: { errcode: "M_UNKNOWN", - error: 'Unknown error', + 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, - ); + 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); await expect(widgetApi.getMediaConfig()).resolves.toEqual({ - 'm.upload.size': 1000, + "m.upload.size": 1000, }); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); @@ -619,10 +554,8 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should reject the request if the api is not supported', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); + 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.", @@ -636,23 +569,19 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } }, - ); + it("should handle an error", async () => { + 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, - ); + it("should handle an error with details", async () => { + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { @@ -661,37 +590,35 @@ describe('WidgetApi', () => { url: "", response: { errcode: "M_UNKNOWN", - error: 'Unknown error', + 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), + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); - 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, - ); + 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); await expect(widgetApi.uploadFile("data")).resolves.toEqual({ - content_uri: 'mxc://...', + content_uri: "mxc://...", }); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); @@ -701,10 +628,8 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should reject the request if the api is not supported', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); + 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.", @@ -718,23 +643,19 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } }, - ); + it("should handle an error", async () => { + 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, - ); + it("should handle an error with details", async () => { + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { @@ -743,37 +664,33 @@ describe('WidgetApi', () => { url: "", response: { errcode: "M_UNKNOWN", - error: 'Unknown error', + 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( - new WidgetApiResponseError('An error occurred', errorDetails), + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); - 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, - ); + 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); await expect(widgetApi.downloadFile("mxc://example.com/test_file")).resolves.toEqual({ - file: 'test contents', + file: "test contents", }); expect(widgetTransportHelper.nextTrackedRequest()).not.toBeUndefined(); @@ -783,10 +700,8 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should reject the request if the api is not supported', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [] } as ISupportedVersionsActionResponseData, - ); + 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.", @@ -800,23 +715,19 @@ describe('WidgetApi', () => { } satisfies SendRequestArgs); }); - it('should handle an error', async () => { - widgetTransportHelper.queueResponse( - { supported_versions: [UnstableApiVersion.MSC4039] } as ISupportedVersionsActionResponseData, - ); - widgetTransportHelper.queueResponse( - { error: { message: 'An error occurred' } }, - ); + it("should handle an error", async () => { + 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, - ); + it("should handle an error with details", async () => { + widgetTransportHelper.queueResponse({ + supported_versions: [UnstableApiVersion.MSC4039], + } as ISupportedVersionsActionResponseData); const errorDetails: IWidgetApiErrorResponseDataDetails = { matrix_api_error: { @@ -825,22 +736,20 @@ describe('WidgetApi', () => { url: "", response: { errcode: "M_UNKNOWN", - error: 'Unknown error', + 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( - new WidgetApiResponseError('An error occurred', errorDetails), + new WidgetApiResponseError("An error occurred", errorDetails), ); }); }); diff --git a/test/url-template-test.ts b/test/url-template-test.ts index b1db0fe..3f28df8 100644 --- a/test/url-template-test.ts +++ b/test/url-template-test.ts @@ -23,13 +23,13 @@ describe("runTemplate", () => { url, { id: "widget-id", - creatorUserId: '@user-id', - type: 'type', + creatorUserId: "@user-id", + type: "type", url, }, { deviceId: "my-device-id", - currentUserId: '@user-id', + currentUserId: "@user-id", }, ); @@ -42,13 +42,13 @@ describe("runTemplate", () => { url, { id: "widget-id", - creatorUserId: '@user-id', - type: 'type', + creatorUserId: "@user-id", + type: "type", url, }, { - currentUserId: '@user-id', - baseUrl: 'https://localhost/api', + currentUserId: "@user-id", + baseUrl: "https://localhost/api", }, ); diff --git a/tsconfig-dev.json b/tsconfig-dev.json index 4ca2a2a..1415bcb 100644 --- a/tsconfig-dev.json +++ b/tsconfig-dev.json @@ -1,6 +1,4 @@ { - "extends": "./tsconfig.json", - "include": [ - "./test/**/*.ts" - ] + "extends": "./tsconfig.json", + "include": ["./test/**/*.ts"] } diff --git a/tsconfig.json b/tsconfig.json index 5abd7b9..e5261eb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,22 +1,17 @@ { - "compilerOptions": { - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "esModuleInterop": true, - "module": "commonjs", - "moduleResolution": "node", - "target": "es2016", - "sourceMap": true, - "outDir": "./lib", - "declaration": true, - "types": ["jest"], - "lib": [ - "es2020", - "dom" - ], - "strict": true - }, - "include": [ - "./src/**/*.ts" - ] + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "module": "commonjs", + "moduleResolution": "node", + "target": "es2016", + "sourceMap": true, + "outDir": "./lib", + "declaration": true, + "types": ["jest"], + "lib": ["es2020", "dom"], + "strict": true + }, + "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"