Showing preview only (1,300K chars total). Download the full file or copy to clipboard to get everything.
Repository: turt2live/matrix-bot-sdk
Branch: main
Commit: abfc53c5a040
Files: 184
Total size: 1.2 MB
Directory structure:
gitextract_mgmy1jcz/
├── .eslintrc.js
├── .github/
│ ├── CODEOWNERS
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yaml
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── SECURITY.md
│ └── workflows/
│ ├── docs.yml
│ └── static_analysis.yml
├── .gitignore
├── .npmignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│ ├── index.md
│ └── tutorials/
│ ├── appservice.md
│ ├── bot-to-appservice.md
│ ├── bot.md
│ ├── encryption-appservices.md
│ ├── encryption-bots.md
│ ├── encryption.md
│ ├── index.json
│ └── room-upgrades.md
├── eslint.config.js
├── examples/
│ ├── appservice.ts
│ ├── bot.ts
│ ├── encryption_appservice.ts
│ ├── encryption_bot.ts
│ └── login_register.ts
├── jsdoc.json
├── package.json
├── src/
│ ├── AdminApis.ts
│ ├── DMs.ts
│ ├── IFilter.ts
│ ├── MatrixAuth.ts
│ ├── MatrixClient.ts
│ ├── PantalaimonClient.ts
│ ├── SynapseAdminApis.ts
│ ├── SynchronousMatrixClient.ts
│ ├── UnstableApis.ts
│ ├── appservice/
│ │ ├── Appservice.ts
│ │ ├── Intent.ts
│ │ ├── MatrixBridge.ts
│ │ ├── UnstableAppserviceApis.ts
│ │ └── http_responses.ts
│ ├── b64.ts
│ ├── e2ee/
│ │ ├── CryptoClient.ts
│ │ ├── ICryptoRoomInformation.ts
│ │ ├── RoomTracker.ts
│ │ ├── RustEngine.ts
│ │ └── decorators.ts
│ ├── helpers/
│ │ ├── MatrixEntity.ts
│ │ ├── MatrixGlob.ts
│ │ ├── MentionPill.ts
│ │ ├── Permalinks.ts
│ │ ├── ProfileCache.ts
│ │ ├── RichReply.ts
│ │ └── UnpaddedBase64.ts
│ ├── http.ts
│ ├── identity/
│ │ └── IdentityClient.ts
│ ├── index.ts
│ ├── logging/
│ │ ├── ConsoleLogger.ts
│ │ ├── ILogger.ts
│ │ ├── LogService.ts
│ │ └── RichConsoleLogger.ts
│ ├── metrics/
│ │ ├── IMetricListener.ts
│ │ ├── Metrics.ts
│ │ ├── contexts.ts
│ │ ├── decorators.ts
│ │ └── names.ts
│ ├── mixins/
│ │ ├── AutojoinRoomsMixin.ts
│ │ └── AutojoinUpgradedRoomsMixin.ts
│ ├── models/
│ │ ├── Account.ts
│ │ ├── CreateRoom.ts
│ │ ├── Crypto.ts
│ │ ├── EventContext.ts
│ │ ├── IdentityServerModels.ts
│ │ ├── MSC2176.ts
│ │ ├── MatrixError.ts
│ │ ├── MatrixProfile.ts
│ │ ├── OpenIDConnect.ts
│ │ ├── Policies.ts
│ │ ├── PowerLevelAction.ts
│ │ ├── PowerLevelBounds.ts
│ │ ├── Presence.ts
│ │ ├── ServerVersions.ts
│ │ ├── Spaces.ts
│ │ ├── Threepid.ts
│ │ ├── events/
│ │ │ ├── AliasesEvent.ts
│ │ │ ├── CanonicalAliasEvent.ts
│ │ │ ├── CreateEvent.ts
│ │ │ ├── EncryptedRoomEvent.ts
│ │ │ ├── EncryptionEvent.ts
│ │ │ ├── Event.ts
│ │ │ ├── EventKind.ts
│ │ │ ├── InvalidEventError.ts
│ │ │ ├── JoinRulesEvent.ts
│ │ │ ├── MembershipEvent.ts
│ │ │ ├── MessageEvent.ts
│ │ │ ├── PinnedEventsEvent.ts
│ │ │ ├── PowerLevelsEvent.ts
│ │ │ ├── PresenceEvent.ts
│ │ │ ├── RedactionEvent.ts
│ │ │ ├── RoomAvatarEvent.ts
│ │ │ ├── RoomEvent.ts
│ │ │ ├── RoomNameEvent.ts
│ │ │ ├── RoomTopicEvent.ts
│ │ │ ├── SpaceChildEvent.ts
│ │ │ ├── _MissingEvents.md
│ │ │ └── converter.ts
│ │ └── unstable/
│ │ └── MediaInfo.ts
│ ├── preprocessors/
│ │ ├── IPreprocessor.ts
│ │ └── RichRepliesPreprocessor.ts
│ ├── request.ts
│ ├── simple-validation.ts
│ ├── storage/
│ │ ├── IAppserviceStorageProvider.ts
│ │ ├── ICryptoStorageProvider.ts
│ │ ├── IStorageProvider.ts
│ │ ├── MemoryStorageProvider.ts
│ │ ├── RustSdkCryptoStorageProvider.ts
│ │ ├── SimpleFsStorageProvider.ts
│ │ └── SimplePostgresStorageProvider.ts
│ └── strategies/
│ ├── AppserviceJoinRoomStrategy.ts
│ └── JoinRoomStrategy.ts
├── test/
│ ├── AdminApisTest.ts
│ ├── DMsTest.ts
│ ├── IdentityClientTest.ts
│ ├── MatrixAuthTest.ts
│ ├── MatrixClientTest.ts
│ ├── SynapseAdminApisTest.ts
│ ├── SynchronousMatrixClientTest.ts
│ ├── TestUtils.ts
│ ├── UnstableApisTest.ts
│ ├── appservice/
│ │ ├── AppserviceTest.ts
│ │ ├── IntentTest.ts
│ │ ├── MatrixBridgeTest.ts
│ │ └── UnstableAppserviceApisTest.ts
│ ├── b64Test.ts
│ ├── encryption/
│ │ ├── CryptoClientTest.ts
│ │ ├── RoomTrackerTest.ts
│ │ └── decoratorsTest.ts
│ ├── helpers/
│ │ ├── MatrixEntityTest.ts
│ │ ├── MatrixGlobTest.ts
│ │ ├── MentionPillTest.ts
│ │ ├── PermalinksTest.ts
│ │ ├── ProfileCacheTest.ts
│ │ ├── RichReplyTest.ts
│ │ └── UnpaddedBase64Test.ts
│ ├── logging/
│ │ └── LogServiceTest.ts
│ ├── metrics/
│ │ ├── MetricsTest.ts
│ │ └── decoratorsTest.ts
│ ├── mixins/
│ │ ├── AutojoinRoomsMixinTest.ts
│ │ └── AutojoinUpgradedRoomsMixinTest.ts
│ ├── models/
│ │ ├── MatrixProfileTest.ts
│ │ ├── PresenceTest.ts
│ │ ├── SpacesTest.ts
│ │ └── events/
│ │ ├── AliasesEventTest.ts
│ │ ├── CanonicalAliasEventTest.ts
│ │ ├── CreateEventTest.ts
│ │ ├── EncryptedRoomEventTest.ts
│ │ ├── EncryptionEventTest.ts
│ │ ├── EventTest.ts
│ │ ├── JoinRulesEventTest.ts
│ │ ├── MembershipEventTest.ts
│ │ ├── MessageEventTest.ts
│ │ ├── PinnedEventsEventTest.ts
│ │ ├── PowerLevelsEventTest.ts
│ │ ├── RedactionEventTest.ts
│ │ ├── RoomAvatarEventTest.ts
│ │ ├── RoomNameEventTest.ts
│ │ ├── RoomTopicEventTest.ts
│ │ ├── SpaceChildEventTest.ts
│ │ └── converterTest.ts
│ ├── preprocessors/
│ │ └── RichRepliesPreprocessorTest.ts
│ ├── requestTest.ts
│ ├── simple-validationTest.ts
│ ├── storage/
│ │ ├── MemoryStorageProviderTest.ts
│ │ ├── SimpleFsStorageProviderTest.ts
│ │ └── SimplePostgresStorageProviderTest.ts
│ └── strategies/
│ ├── AppserviceJoinRoomStrategyTest.ts
│ └── JoinRoomStrategyTest.ts
├── tsconfig-examples.json
├── tsconfig-release.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.js
================================================
module.exports = {
plugins: [
"matrix-org",
],
extends: [
"plugin:matrix-org/babel",
],
env: {
browser: true,
node: true,
},
rules: {
"no-var": ["warn"],
"prefer-rest-params": ["warn"],
"prefer-spread": ["warn"],
"one-var": ["warn"],
"padded-blocks": ["warn"],
"no-extend-native": ["warn"],
"camelcase": ["warn"],
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always",
}],
"arrow-parens": "off",
"prefer-promise-reject-errors": "off",
"quotes": "off",
"indent": "off",
"no-constant-condition": "off",
"no-async-promise-executor": "off",
// We use a `LogService` intermediary module
"no-console": "error",
},
overrides: [{
files: [
"**/*.ts",
],
extends: [
"plugin:matrix-org/typescript",
],
rules: {
// TypeScript has its own version of this
"@babel/no-invalid-this": "off",
// We're okay being explicit at the moment
"@typescript-eslint/no-empty-interface": "off",
// We disable this while we're transitioning
"@typescript-eslint/no-explicit-any": "off",
// We'd rather not do this but we do
"@typescript-eslint/ban-ts-comment": "off",
// We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off",
"quotes": "off",
// We use a `logger` intermediary module
"no-console": "error",
"max-len": ["error", { "code": 180 }],
"no-extra-boolean-cast": "off",
},
}],
};
================================================
FILE: .github/CODEOWNERS
================================================
* turt2live
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Log snippet**
```
If the bug can be seen in your logs, please paste them here (between the triple backticks).
```
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/config.yaml
================================================
blank_issues_enabled: true
contact_links:
- name: Community Support
url: "https://matrix.to/#/#bot-sdk:t2bot.io"
about: General support questions can be asked here.
- name: Security Policy
url: https://www.t2host.io/docs/legal/security-disclosure-policy-v1/
about: Learn more about our security disclosure policy.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the concern is. Ex. The [...] function doesn't work
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Thanks for submitting a PR! Please ensure the following requirements are met in order for us to review your PR -->
## Checklist
* [ ] Tests written for all new code
* [ ] Linter has been satisfied
* [ ] Sign-off given on the changes (see CONTRIBUTING.md)
================================================
FILE: .github/SECURITY.md
================================================
# Reporting a vulnerability
**If you've found a security vulnerability, please report it by
DMing [@travis:t2l.io](https://matrix.to/#/@travis:t2l.io) on Matrix**
This project is managed under t2bot.io's Security Disclosure Policy, which upstreams to the Matrix.org Foundation
as needed. For more information on t2bot.io's SDP, visit https://www.t2host.io/docs/legal/security-disclosure-policy-v1/
================================================
FILE: .github/workflows/docs.yml
================================================
name: Docs
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '24'
- run: yarn install
- run: yarn docs
- name: Build and deploy docs
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: .jsdoc/matrix-bot-sdk/develop
================================================
FILE: .github/workflows/static_analysis.yml
================================================
name: Static Analysis
on:
push:
branches:
- main
pull_request:
jobs:
# Global
# ================================================
eslint:
name: 'ESLint'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '24' # Target desired node version
- run: yarn install
- run: yarn lint
# Node-specific
# ================================================
build:
strategy:
matrix:
node: [ 22, 24 ]
name: 'Build Node ${{ matrix.node }}'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: yarn install
- run: yarn build
- run: yarn build:examples
build-docs:
strategy:
matrix:
node: [ 22, 24 ]
name: 'Build Docs Node ${{ matrix.node }}'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: yarn install
- run: yarn docs
tests:
strategy:
matrix:
node: [ 22, 24 ]
name: 'Tests Node ${{ matrix.node }}'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: yarn install
- uses: nick-invision/retry@v2
with:
max_attempts: 3
timeout_minutes: 15
command: yarn test
================================================
FILE: .gitignore
================================================
.idea/
lib/
examples/storage/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# jsdoc files
.jsdoc
================================================
FILE: .npmignore
================================================
src/
.travis.yml
.idea/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to matrix-bot-sdk
Woo! Good to see you're interested in contributing a change to the bot-sdk! Everyone is welcome to contribute, however
there are some requirements in order to do so. Namely, all contributors must be willing to license their changes under
the same license as the project itself. We follow a simple "inbound=outbound" model for contributions: the act of
submitting an "inbound" contribution means that the contributor agrees to license the code under the same terms as the
project's overall "outbound" license - in this case, MIT (per [LICENSE](./LICENSE)).
## How to contribute
The easiest way to contribute is by forking the project and opening a PR. We highly recommend using a branch for your
changes to make it easier to maintain your PRs and open several (if needed).
Unfortunately at this time it might take a little while to have your PR reviewed: one day it will be faster, but in
the meantime please be patient.
## Sign off
In order to have a concrete record that your contribution is intentional
and you agree to license it under the same terms as the project's license, we've
adopted the same lightweight approach that the Linux Kernel
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
projects use: the DCO (Developer Certificate of Origin:
http://developercertificate.org/). This is a simple declaration that you wrote
the contribution or otherwise have the right to contribute it to matrix-bot-sdk:
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
If you agree to this for your contribution, then all that's needed is to
include the line in your commit or pull request comment:
```
Signed-off-by: Your Name <your@email.example.org>
```
We accept contributions under a legally identifiable name, such as your name on
government documentation or common-law names (names claimed by legitimate usage
or repute). Unfortunately, we cannot accept anonymous contributions at this
time.
Git allows you to add this signoff automatically when using the `-s` flag to
`git commit`, which uses the name and email set in your `user.name` and
`user.email` git configs.
If you forgot to sign off your commits before making your pull request and are
on Git 2.17+ you can mass signoff using rebase:
```
git rebase --signoff origin/develop
```
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 - 2023 Travis Ralston
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# matrix-bot-sdk
[](https://www.npmjs.com/package/matrix-bot-sdk)
TypeScript/JavaScript SDK for Matrix bots. For help and support, visit [#matrix-bot-sdk:t2bot.io](https://matrix.to/#/#matrix-bot-sdk:t2bot.io)
# Documentation
Documentation for the project is available [here](https://turt2live.github.io/matrix-bot-sdk/index.html).
# Matrix version support
The Matrix protocol is [versioned](https://spec.matrix.org/latest/#specification-versions) to ensure endpoints and
functionality can safely rotate in and out of the ecosystem. The bot-sdk will assume it is connected to a homeserver
with support for at least one of the last 2 versions, at the time of the bot-sdk's release. This means that if you
connect the bot-sdk to a homeserver which is 3 or more Matrix versions out of date, things might not work for you.
It is recommended to update the bot-sdk as frequently as spec releases themselves (or faster) to avoid this situation,
and watch the repo for updates in the event a release is delayed.
**Note**: Currently the bot-sdk does not throw an error if the server appears to be incompatible, however this might
change in the future.
================================================
FILE: docs/index.md
================================================
[](https://www.npmjs.com/package/matrix-bot-sdk)
TypeScript/JavaScript SDK for Matrix bots. For help and support, visit [#matrix-bot-sdk:t2bot.io](https://matrix.to/#/#matrix-bot-sdk:t2bot.io)
## Templates and guides
* [Bot documentation](https://turt2live.github.io/matrix-bot-sdk/tutorial-bot.html)
* [Appservice/bridge documentation](https://turt2live.github.io/matrix-bot-sdk/tutorial-appservice.html)
* [matrix.org's guide on the basic functions of the bot](https://matrix.org/docs/guides/usage-of-matrix-bot-sdk)
* [GitHub bot template repository](https://github.com/turt2live/matrix-bot-sdk-bot-template)
## Installing
This package can be found on [npm](https://www.npmjs.com/package/matrix-bot-sdk):
```
npm install matrix-bot-sdk
```
================================================
FILE: docs/tutorials/appservice.md
================================================
Application services are essentially superpowered bots with a much more capable connection to the homeserver. While bots
can operate on nearly any homeserver, appservices need to be specifically installed and configured by the server admins.
Because of the direct connection nature, and the ability to reserve a whole namespace of user IDs, appservices typically
take the shape of bridges. They also typically take the shape of single-user bots which outgrew the performance of calling
`/sync` in a loop.
Appservices are added to homeservers using a registration file. Typically, these are YAML files which get added/listed
to the server config somewhere. Check your homeserver software's documentation for how to install an appservice.
The bot-sdk does not automatically generate a registration file, but it is trivial to generate one by hand. Implementations
typically request that the server admin also supply an exact copy of the registration file so it can be handed off to the
bot-sdk to handle. Advanced uses (ie: multiple namespaces for a single appservice) might require translating the registration
file into something the bot-sdk is willing to accept, however most cases will be perfectly fine to just read it in directly.
An example registration file is:
```yaml
# A general purpose identifier for the appservice. Typically, this is just a lowercase version of the application
# name. It should be unique among all other appservices installed on the homeserver.
id: mybridge
# These are the authentication secrets used to communicate between the homeserver and appservice. They should
# be secret, sufficiently complex, and different from each other and all other appservices installed on the
# homeserver.
as_token: <RANDOM STRING>
hs_token: <RANDOM STRING>
# These are the namespaces that the appservice would like to reserve or use. Typically, bridges will want to
# reserve an alias and user namespace.
namespaces:
aliases:
- exclusive: true
# It's good practice to limit the regex to just bridge users on the current homeserver to avoid confusing
# your bridge with other people who might be using it.
regex: "#bridge_.+:example.org"
users:
- exclusive: true
regex: "@bridge_.+:example.org"
rooms: [] # not commonly used, but required to be set
rate_limited: false # typical bridges don't want to be rate limited
# This is the localpart of the primary user for the appservice. For bridges, this is typically known as the
# "bridge bot" user.
sender_localpart: "bridge_bot"
# This is where the homeserver can reach your appservice at. The bot-sdk will automatically expose a webserver
# at the configured port to handle this traffic and turn it into useful events.
url: "http://localhost:9000"
# If you need ephemeral events (for crypto or other reasons), set this to true. Defaults to false to avoid flooding
# the appservice wtih traffic.
de.sorunome.msc2409.push_ephemeral: true
```
## Creating the appservice instance
The {@link Appservice} class wants a whole bunch of options, though the details are not much different from a regular
bot. Namely, it wants a storage mechanism, config options for the webserver, and an appservice registration to use as
reference.
```typescript
const registration: {@link IAppserviceRegistration} = {/* ... typically read from the YAML file ... */ };
const options: {@link IAppserviceOptions} = {
// Webserver options
port: 9000,
bindAddress: "0.0.0.0",
// Where the appservice can reach the homeserver at. This should be the same URL configured for clients and bots.
homeserverUrl: "https://example.org",
// The domain name of the homeserver. This is the part that is included in user IDs.
homeserverName: "example.org",
registration: registration,
storage: new SimpleFsStorageProvider("./path/to/appservice.json"), // or any other {@link IAppserviceStorageProvider}
joinStrategy: new SimpleRetryJoinStrategy(), // just to ensure reliable joins
};
const appservice = new Appservice(options);
// Attach listeners here
appservice.on("room.message", (roomId: string, event: any) => {
if (!event['content']?.['msgtype']) return;
// handle message
});
// Typically appservices will want to autojoin all rooms
AutojoinRoomsMixin.setupOnAppservice(appservice);
// Actually start the appservice
appservice.begin().then(() => console.log("Appservice started"));
```
The `appservice` instance will emit all the same stuff as a regular bot. Check out the bot tutorial for more information:
{@tutorial bot}.
## Intents
Intents are how the bot-sdk deals with the namespace of users declared in the registration. The bridge bot user is also
an intent, though with a special accessor.
To get the bridge bot intent, use `appservice.botIntent`. For all other intents, `appservice.getIntentForSuffix("suffix")`
is typically easiest.
The {@link Intent} class has all sorts of built-in functions, though if you need to do something more complex then you
may need to handle the intent differently:
```typescript
const intent = appservice.getIntentForSuffix("your_suffix"); // typically a suffix is an identifier from a third party platform
await intent.ensureRegistered(); // can be called multiple times safely
await intent.underlyingClient.setDisplayName("Name"); // or whatever function you'd like to call
```
Typically the bridge bot intent is used for anything which doesn't need to be impersonated by a specific user ID, such
as querying room state or inviting users. The bridge client is exposed as `appservice.botClient` for easy access.
================================================
FILE: docs/tutorials/bot-to-appservice.md
================================================
Once a bot has outgrown the performance of a `/sync` loop, it's typically time to convert it to an appservice. This involves
changing how the application receives events from the server, and possibly even migrating datastores if using custom storage
providers.
For the purposes of this tutorial, we'll assume the bot has been running as `@bot:example.org` with an access token.
First, you'll need to create a registration file. Check out the appservices tutorial for more information on what this
is: {@tutorial appservice}.
```yaml
id: examplebot
as_token: <RANDOM STRING>
hs_token: <RANDOM STRING>
url: "http://localhost:9000" # where your bot can be reached at the built-in webserver for the bot-sdk
sender_localpart: "bot"
# We don't need any namespaces, but they need to be declared
namespaces:
users: []
aliases: []
rooms: []
rate_limited: false
de.sorunome.msc2409.push_ephemeral: true # default false. Keep false if not using typing notifications, encryption, etc.
```
That registration will need to be installed on the homeserver. Consult your homeserver documentation for more information.
Next, you'll need to incorporate that into the bot-sdk's interface. Because the bot-sdk is more geared to bridges, we'll
have to lie to it a bit to ensure it stays happy.
```typescript
const registration: IAppserviceRegistration = {
id: "examplebot",
as_token: "<RANDOM STRING>",
hs_token: "<RANDOM STRING>",
url: "http://localhost:9000", // not used by bot-sdk, but good to define for documentation purposes
sender_localpart: "bot",
namespaces: {
users: [{exclusive: true, regex: "@bot.+"}], // we won't be using anything in the namespace, but need to define it
aliases: [],
rooms: [],
},
// For documentation purposes:
rate_limited: false,
"de.sorunome.msc2409.push_ephemeral": true,
};
```
Then, you'll need to define the appservice options. This will be things such as where the internal webserver will listen
at and where its data is stored. If you used a storage provider built into the bot-sdk, it can be reused here.
```typescript
const options: IAppserviceOptions = {
// Webserver options
port: 9000,
bindAddress: "0.0.0.0",
// This should be the same URL used by the bot.
homeserverUrl: "https://example.org",
// The domain name of the homeserver. This is the part that is included in user IDs.
homeserverName: "example.org",
registration: registration,
storage: new SimpleFsStorageProvider("./path/to/bot.json"),
joinStrategy: new SimpleRetryJoinStrategy(), // just to ensure reliable joins
};
```
Now, your listeners from your bot can be attached to the `appservice` instance instead:
```typescript
// Old code:
// bot.on("room.message", (roomId: string, event: any) => {
// if (!event['content']?.['msgtype']) return;
//
// // handle message
// });
// ---------------
// New code:
appservice.on("room.message", (roomId: string, event: any) => {
if (!event['content']?.['msgtype']) return;
// handle message
});
```
Finally, start the appservice and give it a go: `appservice.begin().then(() => console.log("Appservice started"));`
If you need to access a {@link MatrixClient} instance for calling functions, use `appservice.botClient`. Note that the
client instance will not emit events because it will not be syncing/will not be started. It should not have `start()`
called on it as this can cause data loss/duplication.
================================================
FILE: docs/tutorials/bot.md
================================================
Bots are typically simple creatures which act on commands and provide utility to rooms. They work very similar to how
normal Matrix clients work, with the added complexity of needing to be run on a server somewhere. Unlike appservices
(bridges), bots do not need to be added by a server admin and can be attached to any regular account.
For a guide starting from scratch, check out the [matrix.org guide](https://matrix.org/docs/guides/usage-of-matrix-bot-sdk).
## Creating the bot account
The bot-sdk can be used to script a simple registration or login script, depending on whether or not an account has
been made prior to deploying the bot. If you already have an access token, skip this section.
**Registration**:
```typescript
import { MatrixAuth } from "matrix-bot-sdk";
// This will be the URL where clients can reach your homeserver. Note that this might be different
// from where the web/chat interface is hosted. The server must support password registration without
// captcha or terms of service (public servers typically won't work).
const homeserverUrl = "https://example.org";
const auth = new MatrixAuth(homeserverUrl);
const client = await auth.passwordRegister("username", "password");
console.log("Copy this access token to your bot's config: ", client.accessToken);
```
**Login** (preferred):
```typescript
import { MatrixAuth } from "matrix-bot-sdk";
// This will be the URL where clients can reach your homeserver. Note that this might be different
// from where the web/chat interface is hosted. The server must support password registration without
// captcha or terms of service (public servers typically won't work).
const homeserverUrl = "https://example.org";
const auth = new MatrixAuth(homeserverUrl);
const client = await auth.passwordLogin("username", "password");
console.log("Copy this access token to your bot's config: ", client.accessToken);
```
In either case, the access token printed at the end will need copying to your bot's config.
## Quickstart bot
As an example, a bot which replies to `!hello` commands would be:
```typescript
import {
MatrixClient,
SimpleFsStorageProvider,
AutojoinRoomsMixin,
} from "matrix-bot-sdk";
// This will be the URL where clients can reach your homeserver. Note that this might be different
// from where the web/chat interface is hosted. The server must support password registration without
// captcha or terms of service (public servers typically won't work).
const homeserverUrl = "https://example.org";
// Use the access token you got from login or registration above.
const accessToken = "ACQUIRED_FROM_ABOVE";
// In order to make sure the bot doesn't lose its state between restarts, we'll give it a place to cache
// any information it needs to. You can implement your own storage provider if you like, but a JSON file
// will work fine for this example.
const storage = new SimpleFsStorageProvider("hello-bot.json");
// Finally, let's create the client and set it to autojoin rooms. Autojoining is typical of bots to ensure
// they can be easily added to any room.
const client = new MatrixClient(homeserverUrl, accessToken, storage);
AutojoinRoomsMixin.setupOnClient(client);
// Before we start the bot, register our command handler
client.on("room.message", handleCommand);
// Now that everything is set up, start the bot. This will start the sync loop and run until killed.
client.start().then(() => console.log("Bot started!"));
// This is the command handler we registered a few lines up
async function handleCommand(roomId: string, event: any) {
// Don't handle unhelpful events (ones that aren't text messages, are redacted, or sent by us)
if (event['content']?.['msgtype'] !== 'm.text') return;
if (event['sender'] === await client.getUserId()) return;
// Check to ensure that the `!hello` command is being run
const body = event['content']['body'];
if (!body?.startsWith("!hello")) return;
// Now that we've passed all the checks, we can actually act upon the command
await client.replyNotice(roomId, event, "Hello world!");
}
```
## Watching for events
A `MatrixClient` instance will call listeners for various different things that might happen after the bot has started.
### [Room messages](https://spec.matrix.org/latest/client-server-api/#instant-messaging) & [events](https://spec.matrix.org/latest/client-server-api/#events)
```typescript
client.on("room.message", (roomId: string, event: any) => {
// `event['type']` will always be an `m.room.message` and can be processed as such
// be sure to check if the event is redacted/invalid though:
if (!event['content']?.['msgtype']) return;
});
```
```typescript
client.on("room.event", (roomId: string, event: any) => {
// Check `event['type']` to see if it is an event you're interested in
if (event['type'] !== 'org.example.custom') return;
// Note that state events can also be sent down this listener too
if (event['state_key'] !== undefined) return; // state event
});
```
### [Account data](https://spec.matrix.org/latest/client-server-api/#client-config)
```typescript
client.on("account_data", (event: any) => {
// Handle the account data update
});
```
```typescript
client.on("room.account_data", (roomId: string, event: any) => {
// Handle the room account data update
});
```
### Room membership
```typescript
client.on("room.join", (roomId: string, event: any) => {
// The client has joined `roomId`
});
```
```typescript
client.on("room.leave", (roomId: string, event: any) => {
// The client has left `roomId` (either voluntarily, kicked, or banned)
});
```
```typescript
client.on("room.join", (roomId: string, event: any) => {
// The client has been invited to `roomId`
});
```
================================================
FILE: docs/tutorials/encryption-appservices.md
================================================
Encryption for appservices is just about as easy as bots, though involves using a storage mechanism which is capable of
handling the higher traffic. Eventually the SDK will support custom stores, however for now the crypto store must be
a {@link RustSdkAppserviceCryptoStorageProvider}.
```typescript
const storage = new SimpleFsStorageProvider("./path/to/appservice.json"); // or any other {@link IStorageProvider}
const cryptoStorage = new RustSdkAppserviceCryptoStorageProvider("./path/to/directory");
// ⚠⚠ Be sure to back up both `./path/to/appservice.json` and `./path/to/directory` when using this setup
const registration: IAppserviceRegistration = {
/* ... */
"de.sorunome.msc2409.push_ephemeral": true,
};
const options: IAppserviceOptions = {
/* ... */
storage: storage,
cryptoStorage: cryptoStorage,
intentOptions: {
// Enable encryption on all appservice users, including the `sender_localpart` user
encryption: true,
},
}
const appservice = new Appservice(options);
```
## Advanced usage
To monitor the encryption/decryption process, add the following listeners:
```typescript
appservice.on("room.encrypted_event", (roomId: string, event: any) => {
// handle `m.room.encrypted` event that was received from the server
});
```
```typescript
appservice.on("room.decrypted_event", (roomId: string, event: any) => {
// handle a decrypted `m.room.encrypted` event (`event` will be representative of the cleartext event).
// this is effectively the same as `on('room.event', ...)` though at a different point in the lifecycle.
});
```
```typescript
appservice.on("room.failed_decryption", (roomId: string, event: any, error: Error) => {
// handle `m.room.encrypted` event that could not be decrypted
});
```
To control when encryption is set up for {@link Intent}s, set `intentOptions.encryption = false` in the appservice options
and call `await intent.enableEncryption()` before encryption will be needed. It is safe to call multiple times.
================================================
FILE: docs/tutorials/encryption-bots.md
================================================
Setting up encryption for a bot is easy: simply provide a crypto storage provider in addition to your other storage
providers and it'll start working behind the scenes.
```typescript
const storageProvider = new SimpleFsStorageProvider("./path/to/bot.json"); // or any other {@link IStorageProvider}
const cryptoProvider = new RustSdkCryptoStorageProvider("./path/to/directory");
// ⚠⚠ Be sure to back up both `./path/to/bot.json` and `./path/to/directory` when using this setup
const homeserverUrl = "https://example.org"; // where the bot can reach the homeserver at
const accessToken = "..."; // acquired from login or registration.
// ℹ The access token for the bot should remain consistent. The crypto storage in particular will assume that the
// device ID (and thus access token) does not change between restarts. If the access token becomes invalid, or the
// crypto storage is lost, a new access token and new crypto storage will need to be created.
const client = new MatrixClient(homeserverUrl, accessToken, storageProvider, cryptoProvider);
// set up your listeners here
client.on("room.message", (roomId: string, event: any) => {
if (!event['content']?.['msgtype']) return;
// handle message here. It'll be decrypted already.
});
// This will set up crypto if needed and prepare the client for automatically decrypting and encrypting messages. Simply
// use the client like you would without encryption and it should just work.
client.start().then(() => console.log("Bot started!"));
```
## Advanced usage
To monitor the encryption/decryption process, add the following listeners:
```typescript
client.on("room.encrypted_event", (roomId: string, event: any) => {
// handle `m.room.encrypted` event that was received from the server
});
```
```typescript
client.on("room.decrypted_event", (roomId: string, event: any) => {
// handle a decrypted `m.room.encrypted` event (`event` will be representative of the cleartext event).
// this is effectively the same as `on('room.event', ...)` though at a different point in the lifecycle.
});
```
```typescript
client.on("room.failed_decryption", (roomId: string, event: any, error: Error) => {
// handle `m.room.encrypted` event that could not be decrypted
});
```
================================================
FILE: docs/tutorials/encryption.md
================================================
Matrix supports end-to-end encryption between users in encrypted rooms. Not all rooms are encrypted, and most bots and
bridges do not support encryption out of the gate. With the bot-sdk, encryption (or crypto) needs to be turned on
deliberately in the code.
The following guides go into detail on how to enable encryption for different use cases:
* {@tutorial encryption-bots}
* {@tutorial encryption-appservices}
## General principles
For both bots and appservices, an {@link ICryptoStorageProvider} will be needed to actually enable encryption. Eventually
this will be able to be your own implementation, but for now must be a {@link RustSdkCryptoStorageProvider} or derivative.
================================================
FILE: docs/tutorials/index.json
================================================
{
"bot": {
"title": "Bot usage",
"order": 1
},
"appservice": {
"title": "Appservice (bridge) usage",
"order": 2,
"children": {
"bot-to-appservice": {
"title": "Converting a bot to an appservice",
"order": 1
}
}
},
"room-upgrades": {
"title": "Room upgrades",
"order": 4
},
"encryption": {
"title": "Encryption",
"order": 3,
"children": {
"encryption-bots": {
"title": "Encryption for bots",
"order": 1
},
"encryption-appservices": {
"title": "Encryption for appservices",
"order": 2
}
}
}
}
================================================
FILE: docs/tutorials/room-upgrades.md
================================================
<!-- TODO: Improve section -->
When a room is upgraded, bots and bridges might have to relocate data to the new room. This SDK can handle the easier part of ensuring the bot/bridge is in the new room, and emits events to make the remainder of the process a little easier.
An upgrade happens in two phases: a `room.archived` phase where the old room is flagged as being replaced by another room and a `room.upgraded` phase once the bot/bridge is aware of the new room. Bots and appservices can be told to automatically try and join the new room by attaching a `AutojoinUpgradedRoomsMixin` to the client/appservice, much like the `AutojoinRoomsMixin`.
Bots and appservices should listen for `room.upgraded` to perform a data transfer as this is when there is referential integrity between the two rooms. Prior to an upgrade, there is no guarantee that the replacement room advertised is actually valid.
To get the full chain of rooms, use `getRoomUpgradeHistory(roomId)` on a `MatrixClient` (ie: the `botIntent.underlyingClient` or your own).
================================================
FILE: eslint.config.js
================================================
const {
defineConfig,
} = require("eslint/config");
const matrixOrg = require("eslint-plugin-matrix-org");
const globals = require("globals");
const js = require("@eslint/js");
const {
FlatCompat,
} = require("@eslint/eslintrc");
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
module.exports = defineConfig([{
plugins: {
"matrix-org": matrixOrg,
},
extends: compat.extends("plugin:matrix-org/babel"),
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
rules: {
"valid-jsdoc": ["off"],
"require-jsdoc": ["off"],
"unicorn/no-instanceof-array": "off",
"no-var": ["warn"],
"prefer-rest-params": ["warn"],
"prefer-spread": ["warn"],
"one-var": ["warn"],
"padded-blocks": ["warn"],
"no-extend-native": ["warn"],
"camelcase": ["warn"],
"no-multi-spaces": ["error", {
"ignoreEOLComments": true,
}],
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always",
}],
"arrow-parens": "off",
"prefer-promise-reject-errors": "off",
"quotes": "off",
"indent": "off",
"no-constant-condition": "off",
"no-async-promise-executor": "off",
"no-console": "error",
},
}, {
files: ["**/*.ts"],
extends: compat.extends("plugin:matrix-org/typescript"),
rules: {
"valid-jsdoc": ["off"],
"require-jsdoc": ["off"],
"unicorn/no-instanceof-array": "off",
"@babel/no-invalid-this": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/consistent-type-exports": "off",
"@typescript-eslint/consistent-type-imports": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/no-wrapper-object-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-empty-object-type": "off",
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-base-to-string": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@stylistic/member-delimiter-style": "off",
"quotes": "off",
"no-console": "error",
"max-len": ["error", {
"code": 180,
}],
"no-extra-boolean-cast": "off",
},
}]);
================================================
FILE: examples/appservice.ts
================================================
import {
Appservice,
AutojoinRoomsMixin,
IAppserviceOptions,
IAppserviceRegistration, LogService,
MemoryStorageProvider,
SimpleRetryJoinStrategy,
} from "../src";
const registration: IAppserviceRegistration = {
as_token: "change_me",
hs_token: "change_me",
sender_localpart: "_example_bot",
namespaces: {
users: [{
regex: "@_example_.*:localhost",
exclusive: true,
}],
rooms: [],
aliases: [],
},
};
LogService.info("index", "Setting up appservice with in-memory storage");
const storage = new MemoryStorageProvider();
const options: IAppserviceOptions = {
bindAddress: "0.0.0.0",
port: 9000,
homeserverName: "localhost",
homeserverUrl: "http://localhost:8008",
storage: storage,
registration: registration,
joinStrategy: new SimpleRetryJoinStrategy(),
};
const appservice = new Appservice(options);
AutojoinRoomsMixin.setupOnAppservice(appservice);
appservice.on("room.event", (roomId, event) => {
LogService.info("index", `Received event ${event["event_id"]} (${event["type"]}) from ${event["sender"]} in ${roomId}`);
});
appservice.on("room.message", (roomId, event) => {
if (!event["content"]) return;
if (event["content"]["msgtype"] !== "m.text") return;
const body = event["content"]["body"];
LogService.info("index", `Received message ${event["event_id"]} from ${event["sender"]} in ${roomId}: ${body}`);
// We'll create fake ghosts based on the event ID. Typically these users would be mapped
// by some other means and not arbitrarily. The ghost here also echos whatever the original
// user said.
const intent = appservice.getIntentForSuffix(event["event_id"].toLowerCase().replace(/[^a-z0-9]/g, '_'));
intent.sendText(roomId, body, "m.notice");
});
appservice.on("query.user", (userId, createUser) => {
// This is called when the homeserver queries a user's existence. At this point, a
// user should be created. To do that, give an object or Promise of an object in the
// form below to the createUser function (as shown). To prevent the creation of a user,
// pass false to createUser, like so: createUser(false);
LogService.info("index", `Received query for user ${userId}`);
createUser({
display_name: "Test User",
avatar_mxc: "mxc://localhost/somewhere",
});
});
appservice.on("query.room", (roomAlias, createRoom) => {
// This is called when the homeserver queries to find out if a room alias exists. At
// this point, a room should be created and associated with the room alias. To do
// that, given an object or Promise of an object in the form below to the createRoom
// function (as shown). To prevent creation of a room, pass false to createRoom like
// so: createRoom(false); The object (with minor modifications) will be passed to
// the /createRoom API.
LogService.info("index", `Received query for alias ${roomAlias}`);
createRoom({
name: "Hello World",
topic: "This is an example room",
invite: [appservice.botUserId],
visibility: "public",
preset: "public_chat",
});
});
// Note: The following 3 handlers only fire for appservice users! These will NOT be fired
// for everyone.
appservice.on("room.invite", (roomId, inviteEvent) => {
LogService.info("index", `Received invite for ${inviteEvent["state_key"]} to ${roomId}`);
});
appservice.on("room.join", (roomId, joinEvent) => {
LogService.info("index", `Joined ${roomId} as ${joinEvent["state_key"]}`);
});
appservice.on("room.leave", (roomId, leaveEvent) => {
LogService.info("index", `Left ${roomId} as ${leaveEvent["state_key"]}`);
});
appservice.begin().then(() => LogService.info("index", "Appservice started"));
================================================
FILE: examples/bot.ts
================================================
import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs";
import {
AutojoinRoomsMixin,
LogLevel,
LogService,
MatrixClient,
MessageEvent,
RichConsoleLogger,
RustSdkCryptoStorageProvider,
SimpleFsStorageProvider,
} from "../src";
LogService.setLogger(new RichConsoleLogger());
LogService.setLevel(LogLevel.TRACE);
LogService.muteModule("Metrics");
LogService.trace = LogService.debug;
let creds = null;
try {
creds = require("../../examples/storage/bot.creds.json");
} catch (e) {
// ignore
}
const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost";
const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008";
const accessToken = creds?.['accessToken'] ?? 'YOUR_TOKEN';
const storage = new SimpleFsStorageProvider("./examples/storage/bot.json");
const crypto = new RustSdkCryptoStorageProvider("./examples/storage/bot_sqlite", StoreType.Sqlite);
const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto);
AutojoinRoomsMixin.setupOnClient(client);
(async function() {
await client.dms.update(); // should update in `start()`, but we're earlier than that here
const targetRoomId = await client.dms.getOrCreateDm(dmTarget);
client.on("room.message", async (roomId: string, event: any) => {
if (roomId !== targetRoomId) return;
const message = new MessageEvent(event);
if (message.messageType !== "m.text") return;
if (message.textBody.startsWith("!ping")) {
await client.replyNotice(roomId, event, "Pong from DM");
}
});
LogService.info("index", "Starting bot...");
await client.start();
})();
================================================
FILE: examples/encryption_appservice.ts
================================================
import * as fs from "fs";
import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs";
import {
Appservice,
EncryptionAlgorithm,
FileMessageEventContent,
IAppserviceOptions,
IAppserviceRegistration,
LogLevel,
LogService,
MessageEvent,
RichConsoleLogger,
RustSdkAppserviceCryptoStorageProvider,
SimpleFsStorageProvider,
SimpleRetryJoinStrategy,
} from "../src";
LogService.setLogger(new RichConsoleLogger());
LogService.setLevel(LogLevel.TRACE);
LogService.muteModule("Metrics");
LogService.trace = LogService.debug;
let creds = null;
try {
creds = require("../../examples/storage/encryption_appservice.creds.json");
} catch (e) {
// ignore
}
const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost";
const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008";
const storage = new SimpleFsStorageProvider("./examples/storage/encryption_appservice.json");
const crypto = new RustSdkAppserviceCryptoStorageProvider("./examples/storage/encryption_appservice_sqlite", StoreType.Sqlite);
const worksImage = fs.readFileSync("./examples/static/it-works.png");
const registration: IAppserviceRegistration = {
"as_token": creds?.['asToken'] ?? "change_me",
"hs_token": creds?.['hsToken'] ?? "change_me",
"sender_localpart": "crypto_main_bot_user2",
"namespaces": {
users: [{
regex: "@crypto.*:localhost",
exclusive: true,
}],
rooms: [],
aliases: [],
},
"de.sorunome.msc2409.push_ephemeral": true,
};
const options: IAppserviceOptions = {
bindAddress: "0.0.0.0",
port: 9000,
homeserverName: "localhost",
homeserverUrl: homeserverUrl,
storage: storage,
registration: registration,
joinStrategy: new SimpleRetryJoinStrategy(),
cryptoStorage: crypto,
intentOptions: {
encryption: true,
},
};
const appservice = new Appservice(options);
const bot = appservice.botIntent;
// const bot = appservice.getIntentForUserId("@crypto_bot1:localhost");
(async function() {
await bot.enableEncryption();
let encryptedRoomId: string;
const joinedRooms = await bot.underlyingClient.getJoinedRooms();
for (const roomId of joinedRooms) {
if (await bot.underlyingClient.crypto.isRoomEncrypted(roomId)) {
const members = await bot.underlyingClient.getJoinedRoomMembers(roomId);
if (members.length >= 2) {
encryptedRoomId = roomId;
break;
}
}
}
if (!encryptedRoomId) {
encryptedRoomId = await bot.underlyingClient.createRoom({
invite: [dmTarget],
is_direct: true,
visibility: "private",
preset: "trusted_private_chat",
initial_state: [
{ type: "m.room.encryption", state_key: "", content: { algorithm: EncryptionAlgorithm.MegolmV1AesSha2 } },
{ type: "m.room.guest_access", state_key: "", content: { guest_access: "can_join" } },
],
});
}
appservice.on("query.key_claim", (req, done) => {
LogService.info("index", "Key claim request:", req);
done({});
});
appservice.on("query.key", (req, done) => {
LogService.info("index", "Key query request:", req);
done({});
});
appservice.on("room.failed_decryption", async (roomId: string, event: any, e: Error) => {
LogService.error("index", `Failed to decrypt ${roomId} ${event['event_id']} because `, e);
});
appservice.on("room.message", async (roomId: string, event: any) => {
if (roomId !== encryptedRoomId) return;
const message = new MessageEvent(event);
if (message.sender === bot.userId && message.messageType === "m.notice") {
// yay, we decrypted our own message. Communicate that back for testing purposes.
const encrypted = await bot.underlyingClient.crypto.encryptMedia(Buffer.from(worksImage));
const mxc = await bot.underlyingClient.uploadContent(encrypted.buffer);
await bot.underlyingClient.sendMessage(roomId, {
msgtype: "m.image",
body: "it-works.png",
info: {
// XXX: We know these details, so have hardcoded them.
w: 256,
h: 256,
mimetype: "image/png",
size: worksImage.length,
},
file: {
url: mxc,
...encrypted.file,
},
});
return;
}
if (message.messageType === "m.image") {
const fileEvent = new MessageEvent<FileMessageEventContent>(message.raw);
const decrypted = await bot.underlyingClient.crypto.decryptMedia(fileEvent.content.file);
fs.writeFileSync('./examples/storage/decrypted.png', decrypted);
await bot.underlyingClient.unstableApis.addReactionToEvent(roomId, fileEvent.eventId, 'Decrypted');
return;
}
if (message.messageType !== "m.text") return;
if (message.textBody.startsWith("!ping")) {
await bot.underlyingClient.replyNotice(roomId, event, "Pong");
}
});
LogService.info("index", "Starting appservice...");
await appservice.begin();
})();
================================================
FILE: examples/encryption_bot.ts
================================================
import * as fs from "fs";
import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs";
import {
EncryptionAlgorithm,
FileMessageEventContent,
LogLevel,
LogService,
MatrixClient,
MessageEvent,
RichConsoleLogger,
RustSdkCryptoStorageProvider,
SimpleFsStorageProvider,
} from "../src";
LogService.setLogger(new RichConsoleLogger());
LogService.setLevel(LogLevel.TRACE);
LogService.muteModule("Metrics");
LogService.trace = LogService.debug;
let creds = null;
try {
creds = require("../../examples/storage/encryption_bot.creds.json");
} catch (e) {
// ignore
}
const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost";
const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008";
const accessToken = creds?.['accessToken'] ?? 'YOUR_TOKEN';
const storage = new SimpleFsStorageProvider("./examples/storage/encryption_bot.json");
const crypto = new RustSdkCryptoStorageProvider("./examples/storage/encryption_bot_sqlite", StoreType.Sqlite);
const worksImage = fs.readFileSync("./examples/static/it-works.png");
const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto);
(async function() {
let encryptedRoomId: string;
const joinedRooms = await client.getJoinedRooms();
await client.crypto.prepare(joinedRooms); // init crypto because we're doing things before the client is started
for (const roomId of joinedRooms) {
if (await client.crypto.isRoomEncrypted(roomId)) {
encryptedRoomId = roomId;
break;
}
}
if (!encryptedRoomId) {
encryptedRoomId = await client.createRoom({
invite: [dmTarget],
is_direct: true,
visibility: "private",
preset: "trusted_private_chat",
initial_state: [
{ type: "m.room.encryption", state_key: "", content: { algorithm: EncryptionAlgorithm.MegolmV1AesSha2 } },
{ type: "m.room.guest_access", state_key: "", content: { guest_access: "can_join" } },
],
});
}
client.on("room.failed_decryption", async (roomId: string, event: any, e: Error) => {
LogService.error("index", `Failed to decrypt ${roomId} ${event['event_id']} because `, e);
});
client.on("room.message", async (roomId: string, event: any) => {
if (roomId !== encryptedRoomId) return;
const message = new MessageEvent(event);
if (message.sender === (await client.getUserId()) && message.messageType === "m.notice") {
// yay, we decrypted our own message. Communicate that back for testing purposes.
const encrypted = await client.crypto.encryptMedia(Buffer.from(worksImage));
const mxc = await client.uploadContent(encrypted.buffer);
await client.sendMessage(roomId, {
msgtype: "m.image",
body: "it-works.png",
info: {
// XXX: We know these details, so have hardcoded them.
w: 256,
h: 256,
mimetype: "image/png",
size: worksImage.length,
},
file: {
url: mxc,
...encrypted.file,
},
});
return;
}
if (message.messageType === "m.image") {
const fileEvent = new MessageEvent<FileMessageEventContent>(message.raw);
const decrypted = await client.crypto.decryptMedia(fileEvent.content.file);
fs.writeFileSync('./examples/storage/decrypted.png', decrypted);
await client.unstableApis.addReactionToEvent(roomId, fileEvent.eventId, 'Decrypted');
return;
}
if (message.messageType !== "m.text") return;
if (message.textBody.startsWith("!ping")) {
await client.replyNotice(roomId, event, "Pong");
}
});
LogService.info("index", "Starting bot...");
await client.start();
})();
================================================
FILE: examples/login_register.ts
================================================
import { LogService, MatrixAuth } from "../src";
// CAUTION: This logs a lot of secrets the console, including the password. Use with caution.
const homeserverUrl = "http://localhost:8008";
const password = "P@ssw0rd";
const username = `example_user_${new Date().getTime()}`;
const auth = new MatrixAuth(homeserverUrl);
auth.passwordRegister(username, password).then(client => {
return client.getUserId();
}).then(userId => {
LogService.info("index", "Registered as " + userId + " - Trying to log in now");
return auth.passwordLogin(username, password);
}).then(client => {
return client.getUserId();
}).then(userId => {
LogService.info("index", "Logged in as " + userId);
}).catch(err => {
LogService.error("index", err);
});
================================================
FILE: jsdoc.json
================================================
{
"tags": {
"allowUnknownTags": true
},
"plugins": [
"node_modules/better-docs/category",
"node_modules/better-docs/typescript"
],
"source": {
"include": [
"src"
],
"includePattern": ".ts$"
},
"opts": {
"encoding": "utf8",
"destination": ".jsdoc",
"readme": "docs/index.md",
"recurse": true,
"verbose": true,
"template": "node_modules/better-docs"
}
}
================================================
FILE: package.json
================================================
{
"name": "matrix-bot-sdk",
"version": "develop",
"description": "TypeScript/JavaScript SDK for Matrix bots and appservices",
"repository": {
"type": "git",
"url": "git+https://github.com/turt2live/matrix-bot-sdk.git"
},
"author": "turt2live",
"license": "MIT",
"bugs": {
"url": "https://github.com/turt2live/matrix-bot-sdk/issues"
},
"homepage": "https://github.com/turt2live/matrix-bot-sdk#readme",
"scripts": {
"prepublishOnly": "yarn build",
"docs": "jsdoc -c jsdoc.json -P package.json -u docs/tutorials",
"build": "tsc --listEmittedFiles -p tsconfig-release.json",
"lint": "eslint \"{src,test,examples}/**/*.ts\"",
"lint:fix": "eslint \"{src,test,examples}/**/*.ts\" --fix",
"test": "jest",
"build:examples": "tsc -p tsconfig-examples.json",
"example:bot": "yarn build:examples && node lib/examples/bot.js",
"example:appservice": "yarn build:examples && node lib/examples/appservice.js",
"example:login_register": "yarn build:examples && node lib/examples/login_register.js",
"example:encryption_bot": "yarn build:examples && node lib/examples/encryption_bot.js",
"example:encryption_appservice": "yarn build:examples && node lib/examples/encryption_appservice.js"
},
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"engines": {
"node": ">=22.0.0"
},
"keywords": [
"matrix",
"bot",
"sdk",
"js",
"ts",
"node",
"helpers",
"snippets",
"chat",
"modules",
"bot-sdk",
"appservices"
],
"files": [
"src/*",
"lib/*",
"scripts/*",
"tsconfig.json"
],
"dependencies": {
"@matrix-org/matrix-sdk-crypto-nodejs": "^0.4.0",
"@types/express": "^4.17.20",
"another-json": "^0.2.0",
"async-lock": "^1.4.0",
"chalk": "4",
"express": "^4.18.2",
"glob-to-regexp": "^0.4.1",
"hash.js": "^1.1.7",
"html-to-text": "^9.0.5",
"htmlencode": "^0.0.4",
"lowdb": "1",
"lru-cache": "^10.0.1",
"mkdirp": "^3.0.1",
"morgan": "^1.10.0",
"postgres": "^3.4.1",
"request": "^2.88.2",
"request-promise": "^4.2.6",
"sanitize-html": "^2.11.0"
},
"devDependencies": {
"@babel/core": "^7.28.6",
"@babel/eslint-parser": "^7.28.6",
"@babel/eslint-plugin": "^7.27.1",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@stylistic/eslint-plugin": "^5.7.0",
"@testcontainers/postgresql": "^10.2.2",
"@types/async-lock": "^1.4.1",
"@types/jest": "^30.0.0",
"@types/lowdb": "^1.0.14",
"@types/mocha": "^10.0.3",
"@types/node": "22",
"@types/request-promise": "^4.1.51",
"@types/sanitize-html": "^2.9.3",
"@types/simple-mock": "^0.8.4",
"@typescript-eslint/eslint-plugin": "^8.53.0",
"@typescript-eslint/parser": "^8.53.0",
"better-docs": "^2.7.3",
"eslint": "^9.39.2",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-deprecate": "^0.8.7",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-matrix-org": "^3.0.0",
"eslint-plugin-unicorn": "^62.0.0",
"get-port": "5",
"globals": "^17.0.0",
"jest": "^30.2.0",
"jsdoc": "^4.0.5",
"matrix-mock-request": "^2.6.0",
"simple-mock": "^0.8.0",
"taffydb": "^2.7.3",
"testcontainers": "^10.2.2",
"tmp": "^0.2.1",
"ts-jest": "^29.4.6",
"typescript": "^5.9.3"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"testMatch": [
"<rootDir>/test/**/*Test.ts"
]
}
}
================================================
FILE: src/AdminApis.ts
================================================
import { MatrixClient } from "./MatrixClient";
import { SynapseAdminApis } from "./SynapseAdminApis";
/**
* Whois information about a user.
* See https://matrix.org/docs/spec/client_server/r0.5.0#get-matrix-client-r0-admin-whois-userid for more information.
* @category Admin APIs
*/
export interface WhoisInfo {
user_id: string;
devices: {
[device_id: string]: {
sessions: [{
connections: WhoisConnectionInfo[];
}];
};
};
}
interface WhoisConnectionInfo {
/**
* Most recently seen IP address of the session.
*/
ip: string;
/**
* Unix timestamp that the session was last active.
*/
last_seen: number;
/**
* User agent string last seen in the session.
*/
user_agent: string;
}
/**
* Access to various administrative APIs.
* @category Admin APIs
*/
export class AdminApis {
constructor(private client: MatrixClient) {
}
/**
* Gets access to the Synapse administrative APIs object.
*/
public get synapse(): SynapseAdminApis {
return new SynapseAdminApis(this.client);
}
/**
* Gets information about a particular user.
* @param {string} userId the user ID to lookup
* @returns {Promise<WhoisInfo>} resolves to the whois information
*/
public whoisUser(userId: string): Promise<WhoisInfo> {
return this.client.doRequest("GET", "/_matrix/client/v3/admin/whois/" + encodeURIComponent(userId));
}
}
================================================
FILE: src/DMs.ts
================================================
import { MatrixClient } from "./MatrixClient";
import { EncryptionAlgorithm } from "./models/Crypto";
import { LogService } from "./logging/LogService";
/**
* Handles DM (direct messages) matching between users. Note that bots which
* existed prior to this might not have DM rooms populated correctly - the
* account data can be populated externally and that will be reflected here.
*
* Note that DM status is persisted across all access tokens for a user and
* is not persisted with the regular stores. The DM map is instead tracked
* on the homeserver as account data and thus survives the bot's own storage
* being wiped.
* @category Utilities
*/
export class DMs {
private cached = new Map<string, string[]>();
private ready: Promise<void>;
/**
* Creates a new DM map.
* @param {MatrixClient} client The client the DM map is for.
*/
public constructor(private client: MatrixClient) {
this.client.on("account_data", (ev) => {
if (ev['type'] !== 'm.direct') return;
// noinspection JSIgnoredPromiseFromCall
this.updateFromAccountData();
});
this.client.on("room.invite", (rid, ev) => this.handleInvite(rid, ev));
}
private async updateFromAccountData() {
// Don't trust the sync update
let map = {};
try {
map = await this.client.getAccountData("m.direct");
} catch (e) {
if (e.body?.errcode !== "M_NOT_FOUND" && e.statusCode !== 404) {
LogService.warn("DMs", "Error getting m.direct account data: ", e);
}
}
this.cached = new Map<string, string[]>();
for (const [userId, roomIds] of Object.entries(map)) {
this.cached.set(userId, roomIds as string[]);
}
}
private async handleInvite(roomId: string, ev: any) {
if (ev['content']?.['is_direct'] === true) {
const userId = ev['sender'];
if (!this.cached.has(userId)) this.cached.set(userId, []);
this.cached.set(userId, [roomId, ...this.cached.get(userId)]);
await this.persistCache();
}
}
private async persistCache() {
const obj: Record<string, string[]> = {};
for (const [uid, rids] of this.cached.entries()) {
obj[uid] = rids;
}
await this.client.setAccountData("m.direct", obj);
}
private async fixDms(userId: string) {
const currentRooms = this.cached.get(userId);
if (!currentRooms) return;
const toKeep: string[] = [];
for (const roomId of currentRooms) {
try {
const members = await this.client.getAllRoomMembers(roomId);
const joined = members.filter(m => m.effectiveMembership === "join" || m.effectiveMembership === "invite");
if (joined.some(m => m.membershipFor === userId)) {
toKeep.push(roomId);
}
} catch (e) {
LogService.warn("DMs", `Unable to check ${roomId} for room members - assuming invalid DM`);
}
}
if (toKeep.length === currentRooms.length) return; // no change
if (toKeep.length > 0) {
this.cached.set(userId, toKeep);
} else {
this.cached.delete(userId);
}
await this.persistCache();
}
/**
* Forces an update of the DM cache.
* @returns {Promise<void>} Resolves when complete.
*/
public async update(): Promise<void> {
await this.ready; // finish the existing call if present
this.ready = this.updateFromAccountData();
return this.ready;
}
/**
* Gets or creates a DM with a given user. If a DM needs to be created, it will
* be created as an encrypted DM (if both the MatrixClient and target user support
* crypto). Otherwise, the createFn can be used to override the call. Note that
* when creating a DM room the room should have `is_direct: true` set.
* @param {string} userId The user ID to get/create a DM for.
* @param {Function} createFn Optional function to use to create the room. Resolves
* to the created room ID.
* @returns {Promise<string>} Resolves to the DM room ID.
*/
public async getOrCreateDm(userId: string, createFn?: (targetUserId: string) => Promise<string>): Promise<string> {
await this.ready;
await this.fixDms(userId);
const rooms = this.cached.get(userId);
if (rooms?.length) return rooms[0];
let roomId: string;
if (createFn) {
roomId = await createFn(userId);
} else {
let hasKeys = false;
if (!!this.client.crypto) {
const keys = await this.client.getUserDevices([userId]);
const userKeys = keys?.device_keys?.[userId] ?? {};
hasKeys = Object.values(userKeys).filter(device => Object.values(device).length > 0).length > 0;
}
roomId = await this.client.createRoom({
invite: [userId],
is_direct: true,
preset: "trusted_private_chat",
initial_state: hasKeys ? [{
type: "m.room.encryption",
state_key: "",
content: { algorithm: EncryptionAlgorithm.MegolmV1AesSha2 },
}] : [],
});
}
if (!this.cached.has(userId)) this.cached.set(userId, []);
this.cached.set(userId, [roomId, ...this.cached.get(userId)]);
await this.persistCache();
return roomId;
}
/**
* Determines if a given room is a DM according to the cache.
* @param {string} roomId The room ID.
* @returns {boolean} True if the room ID is a cached DM room ID.
*/
public isDm(roomId: string): boolean {
for (const val of this.cached.values()) {
if (val.includes(roomId)) {
return true;
}
}
return false;
}
}
================================================
FILE: src/IFilter.ts
================================================
/**
* Sync filter information.
*/
export interface IFilterInfo {
id: number;
filter: any; // TODO: Define what a filter is
}
================================================
FILE: src/MatrixAuth.ts
================================================
import { MatrixClient } from "./MatrixClient";
/**
* Functions for interacting with Matrix prior to having an access token. Intended
* to be used for logging in/registering to get a MatrixClient instance.
*
* By design, this limits the options used to create the MatrixClient. To specify
* custom elements to the client, get the access token from the returned client
* and create a new MatrixClient instance. Due to the nature of Matrix, it is
* also recommended to use the homeserverUrl from the generated MatrixClient as
* it may be different from that given to the MatrixAuth class.
*/
export class MatrixAuth {
/**
* Creates a new MatrixAuth class for creating a MatrixClient
* @param {string} homeserverUrl The homeserver URL to authenticate against.
*/
public constructor(private homeserverUrl: string) {
// nothing to do
}
/**
* Generate a client with no access token so we can reuse the doRequest
* logic already written.
*/
private createTemplateClient(): MatrixClient {
return new MatrixClient(this.homeserverUrl, "");
}
/**
* Performs simple registration using a password for the account. This will
* assume the server supports the m.login.password flow for registration, and
* will attempt to complete only that stage. The caller is expected to determine
* if the homeserver supports registration prior to invocation.
* @param {string} localpart The localpart (username) to register
* @param {string} password The password to register with
* @param {string} deviceName The name of the newly created device. Optional.
* @returns {Promise<MatrixClient>} Resolves to a logged-in MatrixClient
*/
public async passwordRegister(localpart: string, password: string, deviceName?: string): Promise<MatrixClient> {
// First try and complete the stage without UIA in hopes the server is kind to us:
const body = {
username: localpart,
password: password,
initial_device_display_name: deviceName,
};
let response;
try {
response = await this.createTemplateClient().doRequest("POST", "/_matrix/client/v3/register", null, body);
} catch (e) {
if (e.statusCode === 401) {
if (typeof (e.body) === "string") e.body = JSON.parse(e.body);
if (!e.body) throw new Error(JSON.stringify(Object.keys(e)));
// 401 means we need to do UIA, so try and complete a stage
const sessionId = e.body['session'];
const expectedFlow = ["m.login.dummy"];
let hasFlow = false;
for (const flow of e.body['flows']) {
const stages = flow['stages'];
if (stages.length !== expectedFlow.length) continue;
let stagesMatch = true;
for (let i = 0; i < stages.length; i++) {
if (stages[i] !== expectedFlow[i]) {
stagesMatch = false;
break;
}
}
if (stagesMatch) {
hasFlow = true;
break;
}
}
if (!hasFlow) throw new Error("Failed to find appropriate login flow in User-Interactive Authentication");
body['auth'] = {
type: expectedFlow[0], // HACK: We assume we only have one entry here
session: sessionId,
};
response = await this.createTemplateClient().doRequest("POST", "/_matrix/client/v3/register", null, body);
}
}
if (!response) throw new Error("Failed to register");
const accessToken = response['access_token'];
if (!accessToken) throw new Error("No access token returned");
return new MatrixClient(this.homeserverUrl, accessToken);
}
/**
* Performs simple password login with the homeserver. The caller is
* expected to confirm if the homeserver supports this login flow prior
* to invocation.
* @param {string} username The username (localpart or user ID) to log in with
* @param {string} password The password for the account
* @param {string} deviceName The name of the newly created device. Optional.
* @returns {Promise<MatrixClient>} Resolves to a logged-in MatrixClient
*/
public async passwordLogin(username: string, password: string, deviceName?: string): Promise<MatrixClient> {
const body = {
type: "m.login.password",
identifier: {
type: "m.id.user",
user: username,
},
password: password,
initial_device_display_name: deviceName,
};
const response = await this.createTemplateClient().doRequest("POST", "/_matrix/client/v3/login", null, body);
const accessToken = response["access_token"];
if (!accessToken) throw new Error("Expected access token in response - got nothing");
let homeserverUrl = this.homeserverUrl;
if (response['well_known'] && response['well_known']['m.homeserver'] && response['well_known']['m.homeserver']['base_url']) {
homeserverUrl = response['well_known']['m.homeserver']['base_url'];
}
return new MatrixClient(homeserverUrl, accessToken);
}
}
================================================
FILE: src/MatrixClient.ts
================================================
import { EventEmitter } from "events";
import { htmlEncode } from "htmlencode";
import { htmlToText } from "html-to-text";
import { IStorageProvider } from "./storage/IStorageProvider";
import { MemoryStorageProvider } from "./storage/MemoryStorageProvider";
import { IJoinRoomStrategy } from "./strategies/JoinRoomStrategy";
import { UnstableApis } from "./UnstableApis";
import { IPreprocessor } from "./preprocessors/IPreprocessor";
import { getRequestFn } from "./request";
import { extractRequestError, LogService } from "./logging/LogService";
import { RichReply } from "./helpers/RichReply";
import { Metrics } from "./metrics/Metrics";
import { timedMatrixClientFunctionCall } from "./metrics/decorators";
import { AdminApis } from "./AdminApis";
import { Presence } from "./models/Presence";
import { Membership, MembershipEvent } from "./models/events/MembershipEvent";
import { RoomEvent, RoomEventContent, StateEvent } from "./models/events/RoomEvent";
import { EventContext } from "./models/EventContext";
import { PowerLevelBounds } from "./models/PowerLevelBounds";
import { EventKind } from "./models/events/EventKind";
import { IdentityClient } from "./identity/IdentityClient";
import { OpenIDConnectToken } from "./models/OpenIDConnect";
import { doHttpRequest } from "./http";
import { Space, SpaceCreateOptions } from "./models/Spaces";
import { PowerLevelAction } from "./models/PowerLevelAction";
import { CryptoClient } from "./e2ee/CryptoClient";
import {
FallbackKey,
IToDeviceMessage,
MultiUserDeviceListResponse,
OTKAlgorithm,
OTKClaimResponse,
OTKCounts,
OTKs,
OwnUserDevice,
} from "./models/Crypto";
import { requiresCrypto } from "./e2ee/decorators";
import { ICryptoStorageProvider } from "./storage/ICryptoStorageProvider";
import { EncryptedRoomEvent } from "./models/events/EncryptedRoomEvent";
import { IWhoAmI } from "./models/Account";
import { RustSdkCryptoStorageProvider } from "./storage/RustSdkCryptoStorageProvider";
import { DMs } from "./DMs";
import { ServerVersions } from "./models/ServerVersions";
import { RoomCreateOptions } from "./models/CreateRoom";
import { PresenceState } from './models/events/PresenceEvent';
const SYNC_BACKOFF_MIN_MS = 5000;
const SYNC_BACKOFF_MAX_MS = 15000;
const VERSIONS_CACHE_MS = 7200000; // 2 hours
/**
* A client that is capable of interacting with a matrix homeserver.
*/
export class MatrixClient extends EventEmitter {
/**
* The presence status to use while syncing. The valid values are "online" to set the account as online,
* "offline" to set the user as offline, "unavailable" for marking the user away, and null for not setting
* an explicit presence (the default).
*
* Has no effect if the client is not syncing. Does not apply until the next sync request.
*/
public syncingPresence: PresenceState | null = null;
/**
* The number of milliseconds to wait for new events for on the next sync.
*
* Has no effect if the client is not syncing. Does not apply until the next sync request.
*/
public syncingTimeout = 30000;
/**
* The crypto manager instance for this client. Generally speaking, this shouldn't
* need to be accessed but is made available.
*
* Will be null/undefined if crypto is not possible.
*/
public readonly crypto: CryptoClient;
/**
* The DM manager instance for this client.
*/
public readonly dms: DMs;
private userId: string;
private requestId = 0;
private lastJoinedRoomIds: string[] = [];
private impersonatedUserId: string;
private impersonatedDeviceId: string;
private joinStrategy: IJoinRoomStrategy = null;
private eventProcessors: { [eventType: string]: IPreprocessor[] } = {};
private filterId = 0;
private stopSyncing = false;
private metricsInstance: Metrics = new Metrics();
private unstableApisInstance = new UnstableApis(this);
private cachedVersions: ServerVersions;
private versionsLastFetched = 0;
/**
* Set this to true to have the client only persist the sync token after the sync
* has been processed successfully. Note that if this is true then when the sync
* loop throws an error the client will not persist a token.
*/
protected persistTokenAfterSync = false;
/**
* Creates a new matrix client
* @param {string} homeserverUrl The homeserver's client-server API URL
* @param {string} accessToken The access token for the homeserver
* @param {IStorageProvider} storage The storage provider to use. Defaults to MemoryStorageProvider.
* @param {ICryptoStorageProvider} cryptoStore Optional crypto storage provider to use. If not supplied,
* end-to-end encryption will not be functional in this client.
*/
constructor(
public readonly homeserverUrl: string,
public readonly accessToken: string,
private storage: IStorageProvider = null,
public readonly cryptoStore: ICryptoStorageProvider = null,
) {
super();
if (this.homeserverUrl.endsWith("/")) {
this.homeserverUrl = this.homeserverUrl.substring(0, this.homeserverUrl.length - 1);
}
if (this.cryptoStore) {
if (!this.storage || this.storage instanceof MemoryStorageProvider) {
LogService.warn("MatrixClientLite", "Starting an encryption-capable client with a memory store is not considered a good idea.");
}
if (!(this.cryptoStore instanceof RustSdkCryptoStorageProvider)) {
throw new Error("Cannot support custom encryption stores: Use a RustSdkCryptoStorageProvider");
}
this.crypto = new CryptoClient(this);
this.on("room.event", (roomId, event) => {
// noinspection JSIgnoredPromiseFromCall
this.crypto.onRoomEvent(roomId, event);
});
this.on("room.join", (roomId) => {
// noinspection JSIgnoredPromiseFromCall
this.crypto.onRoomJoin(roomId);
});
LogService.debug("MatrixClientLite", "End-to-end encryption client created");
} else {
// LogService.trace("MatrixClientLite", "Not setting up encryption");
}
if (!this.storage) this.storage = new MemoryStorageProvider();
this.dms = new DMs(this);
}
/**
* The storage provider for this client. Direct access is usually not required.
*/
public get storageProvider(): IStorageProvider {
return this.storage;
}
/**
* The metrics instance for this client
*/
public get metrics(): Metrics {
return this.metricsInstance;
}
/**
* Assigns a new metrics instance, overwriting the old one.
* @param {Metrics} metrics The new metrics instance.
*/
public set metrics(metrics: Metrics) {
if (!metrics) throw new Error("Metrics cannot be null/undefined");
this.metricsInstance = metrics;
}
/**
* Gets the unstable API access class. This is generally not recommended to be
* used by clients.
* @return {UnstableApis} The unstable API access class.
*/
public get unstableApis(): UnstableApis {
return this.unstableApisInstance;
}
/**
* Gets the admin API access class.
* @return {AdminApis} The admin API access class.
*/
public get adminApis(): AdminApis {
return new AdminApis(this);
}
/**
* Sets a user ID to impersonate as. This will assume that the access token for this client
* is for an application service, and that the userId given is within the reach of the
* application service. Setting this to null will stop future impersonation. The user ID is
* assumed to already be valid
* @param {string} userId The user ID to masquerade as, or `null` to clear masquerading.
* @param {string} deviceId Optional device ID to impersonate under the given user, if supported
* by the server. Check the whoami response after setting.
*/
public impersonateUserId(userId: string | null, deviceId?: string): void {
this.impersonatedUserId = userId;
this.userId = userId;
if (userId) {
this.impersonatedDeviceId = deviceId;
} else if (deviceId) {
throw new Error("Cannot impersonate just a device: need a user ID");
} else {
this.impersonatedDeviceId = null;
}
}
/**
* Acquires an identity server client for communicating with an identity server. Note that
* this will automatically do the login portion to establish a usable token with the identity
* server provided, but it will not automatically accept any terms of service.
*
* The identity server name provided will in future be resolved to a server address - for now
* that resolution is assumed to be prefixing the name with `https://`.
* @param {string} identityServerName The domain of the identity server to connect to.
* @returns {Promise<IdentityClient>} Resolves to a prepared identity client.
*/
public async getIdentityServerClient(identityServerName: string): Promise<IdentityClient> {
const oidcToken = await this.getOpenIDConnectToken();
return IdentityClient.acquire(oidcToken, `https://${identityServerName}`, this);
}
/**
* Sets the strategy to use for when joinRoom is called on this client
* @param {IJoinRoomStrategy} strategy The strategy to use, or null to use none
*/
public setJoinStrategy(strategy: IJoinRoomStrategy): void {
this.joinStrategy = strategy;
}
/**
* Adds a preprocessor to the event pipeline. When this client encounters an event, it
* will try to run it through the preprocessors it can in the order they were added.
* @param {IPreprocessor} preprocessor the preprocessor to add
*/
public addPreprocessor(preprocessor: IPreprocessor): void {
if (!preprocessor) throw new Error("Preprocessor cannot be null");
const eventTypes = preprocessor.getSupportedEventTypes();
if (!eventTypes) return; // Nothing to do
for (const eventType of eventTypes) {
if (!this.eventProcessors[eventType]) this.eventProcessors[eventType] = [];
this.eventProcessors[eventType].push(preprocessor);
}
}
private async processEvent(event: any): Promise<any> {
if (!event) return event;
if (!this.eventProcessors[event["type"]]) return event;
for (const processor of this.eventProcessors[event["type"]]) {
await processor.processEvent(event, this, EventKind.RoomEvent);
}
return event;
}
/**
* Retrieves the server's supported specification versions and unstable features.
* @returns {Promise<ServerVersions>} Resolves to the server's supported versions.
*/
@timedMatrixClientFunctionCall()
public async getServerVersions(): Promise<ServerVersions> {
if (!this.cachedVersions || (Date.now() - this.versionsLastFetched) >= VERSIONS_CACHE_MS) {
this.cachedVersions = await this.doRequest("GET", "/_matrix/client/versions");
this.versionsLastFetched = Date.now();
}
return this.cachedVersions;
}
/**
* Determines if the server supports a given unstable feature flag. Useful for determining
* if the server can support an unstable MSC.
* @param {string} feature The feature name to look for.
* @returns {Promise<boolean>} Resolves to true if the server supports the flag, false otherwise.
*/
public async doesServerSupportUnstableFeature(feature: string): Promise<boolean> {
return !!(await this.getServerVersions()).unstable_features?.[feature];
}
/**
* Determines if the server supports a given version of the specification or not.
* @param {string} version The version to look for. Eg: "v1.1"
* @returns {Promise<boolean>} Resolves to true if the server supports the version, false otherwise.
*/
public async doesServerSupportVersion(version: string): Promise<boolean> {
return (await this.getServerVersions()).versions.includes(version);
}
/**
* Determines if the server supports at least one of the given specification versions or not.
* @param {string[]} versions The versions to look for. Eg: ["v1.1"]
* @returns {Promise<boolean>} Resolves to true if the server supports any of the versions, false otherwise.
*/
public async doesServerSupportAnyOneVersion(versions: string[]): Promise<boolean> {
for (const version of versions) {
if (await this.doesServerSupportVersion(version)) {
return true;
}
}
return false;
}
/**
* Retrieves an OpenID Connect token from the homeserver for the current user.
* @returns {Promise<OpenIDConnectToken>} Resolves to the token.
*/
@timedMatrixClientFunctionCall()
public async getOpenIDConnectToken(): Promise<OpenIDConnectToken> {
const userId = encodeURIComponent(await this.getUserId());
return this.doRequest("POST", "/_matrix/client/v3/user/" + userId + "/openid/request_token", null, {});
}
/**
* Retrieves content from account data.
* @param {string} eventType The type of account data to retrieve.
* @returns {Promise<any>} Resolves to the content of that account data.
*/
@timedMatrixClientFunctionCall()
public async getAccountData<T>(eventType: string): Promise<T> {
const userId = encodeURIComponent(await this.getUserId());
eventType = encodeURIComponent(eventType);
return this.doRequest("GET", "/_matrix/client/v3/user/" + userId + "/account_data/" + eventType);
}
/**
* Retrieves content from room account data.
* @param {string} eventType The type of room account data to retrieve.
* @param {string} roomId The room to read the account data from.
* @returns {Promise<any>} Resolves to the content of that account data.
*/
@timedMatrixClientFunctionCall()
public async getRoomAccountData<T>(eventType: string, roomId: string): Promise<T> {
const userId = encodeURIComponent(await this.getUserId());
eventType = encodeURIComponent(eventType);
roomId = encodeURIComponent(roomId);
return this.doRequest("GET", "/_matrix/client/v3/user/" + userId + "/rooms/" + roomId + "/account_data/" + eventType);
}
/**
* Retrieves content from account data. If the account data request throws an error,
* this simply returns the default provided.
* @param {string} eventType The type of account data to retrieve.
* @param {any} defaultContent The default value. Defaults to null.
* @returns {Promise<any>} Resolves to the content of that account data, or the default.
*/
@timedMatrixClientFunctionCall()
public async getSafeAccountData<T>(eventType: string, defaultContent: T = null): Promise<T> {
try {
return await this.getAccountData(eventType);
} catch (e) {
LogService.warn("MatrixClient", `Error getting ${eventType} account data:`, extractRequestError(e));
return defaultContent;
}
}
/**
* Retrieves content from room account data. If the account data request throws an error,
* this simply returns the default provided.
* @param {string} eventType The type of room account data to retrieve.
* @param {string} roomId The room to read the account data from.
* @param {any} defaultContent The default value. Defaults to null.
* @returns {Promise<any>} Resolves to the content of that room account data, or the default.
*/
@timedMatrixClientFunctionCall()
public async getSafeRoomAccountData<T>(eventType: string, roomId: string, defaultContent: T = null): Promise<T> {
try {
return await this.getRoomAccountData(eventType, roomId);
} catch (e) {
LogService.warn("MatrixClient", `Error getting ${eventType} room account data in ${roomId}:`, extractRequestError(e));
return defaultContent;
}
}
/**
* Sets account data.
* @param {string} eventType The type of account data to set
* @param {any} content The content to set
* @returns {Promise<any>} Resolves when updated
*/
@timedMatrixClientFunctionCall()
public async setAccountData(eventType: string, content: any): Promise<any> {
const userId = encodeURIComponent(await this.getUserId());
eventType = encodeURIComponent(eventType);
return this.doRequest("PUT", "/_matrix/client/v3/user/" + userId + "/account_data/" + eventType, null, content);
}
/**
* Sets room account data.
* @param {string} eventType The type of room account data to set
* @param {string} roomId The room to set account data in
* @param {any} content The content to set
* @returns {Promise<any>} Resolves when updated
*/
@timedMatrixClientFunctionCall()
public async setRoomAccountData(eventType: string, roomId: string, content: any): Promise<any> {
const userId = encodeURIComponent(await this.getUserId());
eventType = encodeURIComponent(eventType);
roomId = encodeURIComponent(roomId);
return this.doRequest("PUT", "/_matrix/client/v3/user/" + userId + "/rooms/" + roomId + "/account_data/" + eventType, null, content);
}
/**
* Gets the presence information for the current user.
* @returns {Promise<Presence>} Resolves to the presence status of the user.
*/
@timedMatrixClientFunctionCall()
public async getPresenceStatus(): Promise<Presence> {
return this.getPresenceStatusFor(await this.getUserId());
}
/**
* Gets the presence information for a given user.
* @param {string} userId The user ID to look up the presence of.
* @returns {Promise<Presence>} Resolves to the presence status of the user.
*/
@timedMatrixClientFunctionCall()
public async getPresenceStatusFor(userId: string): Promise<Presence> {
return this.doRequest("GET", "/_matrix/client/v3/presence/" + encodeURIComponent(userId) + "/status").then(r => new Presence(r));
}
/**
* Sets the presence status for the current user.
* @param {PresenceState} presence The new presence state for the user.
* @param {string?} statusMessage Optional status message to include with the presence.
* @returns {Promise<any>} Resolves when complete.
*/
@timedMatrixClientFunctionCall()
public async setPresenceStatus(presence: PresenceState, statusMessage: string | undefined = undefined): Promise<any> {
return this.doRequest("PUT", "/_matrix/client/v3/presence/" + encodeURIComponent(await this.getUserId()) + "/status", null, {
presence: presence,
status_msg: statusMessage,
});
}
/**
* Gets a published alias for the given room. These are supplied by the room admins
* and should point to the room, but may not. This is primarily intended to be used
* in the context of rendering a mention (pill) for a room.
* @param {string} roomIdOrAlias The room ID or alias to get an alias for.
* @returns {Promise<string>} Resolves to a published room alias, or falsey if none found.
*/
@timedMatrixClientFunctionCall()
public async getPublishedAlias(roomIdOrAlias: string): Promise<string> {
try {
const roomId = await this.resolveRoom(roomIdOrAlias);
const event = await this.getRoomStateEvent(roomId, "m.room.canonical_alias", "");
if (!event) return null;
const canonical = event['alias'];
const alt = event['alt_aliases'] || [];
return canonical || alt[0];
} catch (e) {
// Assume none
return null;
}
}
/**
* Adds a new room alias to the room directory
* @param {string} alias The alias to add (eg: "#my-room:matrix.org")
* @param {string} roomId The room ID to add the alias to
* @returns {Promise} resolves when the alias has been added
*/
@timedMatrixClientFunctionCall()
public createRoomAlias(alias: string, roomId: string): Promise<any> {
alias = encodeURIComponent(alias);
return this.doRequest("PUT", "/_matrix/client/v3/directory/room/" + alias, null, {
"room_id": roomId,
});
}
/**
* Removes a room alias from the room directory
* @param {string} alias The alias to remove
* @returns {Promise} resolves when the alias has been deleted
*/
@timedMatrixClientFunctionCall()
public deleteRoomAlias(alias: string): Promise<any> {
alias = encodeURIComponent(alias);
return this.doRequest("DELETE", "/_matrix/client/v3/directory/room/" + alias);
}
/**
* Sets the visibility of a room in the directory.
* @param {string} roomId The room ID to manipulate the visibility of
* @param {"public" | "private"} visibility The visibility to set for the room
* @return {Promise} resolves when the visibility has been updated
*/
@timedMatrixClientFunctionCall()
public setDirectoryVisibility(roomId: string, visibility: "public" | "private"): Promise<any> {
roomId = encodeURIComponent(roomId);
return this.doRequest("PUT", "/_matrix/client/v3/directory/list/room/" + roomId, null, {
"visibility": visibility,
});
}
/**
* Gets the visibility of a room in the directory.
* @param {string} roomId The room ID to query the visibility of
* @return {Promise<"public"|"private">} The visibility of the room
*/
@timedMatrixClientFunctionCall()
public getDirectoryVisibility(roomId: string): Promise<"public" | "private"> {
roomId = encodeURIComponent(roomId);
return this.doRequest("GET", "/_matrix/client/v3/directory/list/room/" + roomId).then(response => {
return response["visibility"];
});
}
/**
* Resolves a room ID or alias to a room ID. If the given ID or alias looks like a room ID
* already, it will be returned as-is. If the room ID or alias looks like a room alias, it
* will be resolved to a room ID if possible. If the room ID or alias is neither, an error
* will be raised.
* @param {string} roomIdOrAlias the room ID or alias to resolve to a room ID
* @returns {Promise<string>} resolves to the room ID
*/
@timedMatrixClientFunctionCall()
public async resolveRoom(roomIdOrAlias: string): Promise<string> {
if (roomIdOrAlias.startsWith("!")) return roomIdOrAlias; // probably
if (roomIdOrAlias.startsWith("#")) return this.lookupRoomAlias(roomIdOrAlias).then(r => r.roomId);
throw new Error("Invalid room ID or alias");
}
/**
* Does a room directory lookup for a given room alias
* @param {string} roomAlias the room alias to look up in the room directory
* @returns {Promise<RoomDirectoryLookupResponse>} resolves to the room's information
*/
@timedMatrixClientFunctionCall()
public lookupRoomAlias(roomAlias: string): Promise<RoomDirectoryLookupResponse> {
return this.doRequest("GET", "/_matrix/client/v3/directory/room/" + encodeURIComponent(roomAlias)).then(response => {
return {
roomId: response["room_id"],
residentServers: response["servers"],
};
});
}
/**
* Invites a user to a room.
* @param {string} userId the user ID to invite
* @param {string} roomId the room ID to invite the user to
* @returns {Promise<any>} resolves when completed
*/
@timedMatrixClientFunctionCall()
public inviteUser(userId, roomId) {
return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/invite", null, {
user_id: userId,
});
}
/**
* Kicks a user from a room.
* @param {string} userId the user ID to kick
* @param {string} roomId the room ID to kick the user in
* @param {string?} reason optional reason for the kick
* @returns {Promise<any>} resolves when completed
*/
@timedMatrixClientFunctionCall()
public kickUser(userId, roomId, reason = null) {
return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/kick", null, {
user_id: userId,
reason: reason,
});
}
/**
* Bans a user from a room.
* @param {string} userId the user ID to ban
* @param {string} roomId the room ID to set the ban in
* @param {string?} reason optional reason for the ban
* @returns {Promise<any>} resolves when completed
*/
@timedMatrixClientFunctionCall()
public banUser(userId, roomId, reason = null) {
return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/ban", null, {
user_id: userId,
reason: reason,
});
}
/**
* Unbans a user in a room.
* @param {string} userId the user ID to unban
* @param {string} roomId the room ID to lift the ban in
* @returns {Promise<any>} resolves when completed
*/
@timedMatrixClientFunctionCall()
public unbanUser(userId, roomId) {
return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/unban", null, {
user_id: userId,
});
}
/**
* Gets the current user ID for this client
* @returns {Promise<string>} The user ID of this client
*/
@timedMatrixClientFunctionCall()
public async getUserId(): Promise<string> {
if (this.userId) return this.userId;
// getWhoAmI should populate `this.userId` for us
await this.getWhoAmI();
return this.userId;
}
/**
* Gets the user's information from the server directly.
* @returns {Promise<IWhoAmI>} The "who am I" response.
*/
public async getWhoAmI(): Promise<IWhoAmI> {
const whoami = await this.doRequest("GET", "/_matrix/client/v3/account/whoami");
this.userId = whoami["user_id"];
return whoami;
}
/**
* Stops the client from syncing.
*/
public stop() {
this.stopSyncing = true;
}
/**
* Starts syncing the client with an optional filter
* @param {any} filter The filter to use, or null for none
* @returns {Promise<any>} Resolves when the client has started syncing
*/
public async start(filter: any = null): Promise<any> {
await this.dms.update();
this.stopSyncing = false;
if (!filter || typeof (filter) !== "object") {
LogService.trace("MatrixClientLite", "No filter given or invalid object - using defaults.");
filter = null;
}
LogService.trace("MatrixClientLite", "Populating joined rooms to avoid excessive join emits");
this.lastJoinedRoomIds = await this.getJoinedRooms();
const userId = await this.getUserId();
if (this.crypto) {
LogService.debug("MatrixClientLite", "Preparing end-to-end encryption");
await this.crypto.prepare(this.lastJoinedRoomIds);
LogService.info("MatrixClientLite", "End-to-end encryption enabled");
}
let createFilter = false;
// noinspection ES6RedundantAwait
const existingFilter = await Promise.resolve(this.storage.getFilter());
if (existingFilter) {
LogService.trace("MatrixClientLite", "Found existing filter. Checking consistency with given filter");
if (JSON.stringify(existingFilter.filter) === JSON.stringify(filter)) {
LogService.trace("MatrixClientLite", "Filters match");
this.filterId = existingFilter.id;
} else {
createFilter = true;
}
} else {
createFilter = true;
}
if (createFilter && filter) {
LogService.trace("MatrixClientLite", "Creating new filter");
await this.doRequest("POST", "/_matrix/client/v3/user/" + encodeURIComponent(userId) + "/filter", null, filter).then(async response => {
this.filterId = response["filter_id"];
// noinspection ES6RedundantAwait
await Promise.resolve(this.storage.setSyncToken(null));
// noinspection ES6RedundantAwait
await Promise.resolve(this.storage.setFilter({
id: this.filterId,
filter: filter,
}));
});
}
LogService.trace("MatrixClientLite", "Starting sync with filter ID " + this.filterId);
return this.startSyncInternal();
}
protected startSyncInternal(): Promise<any> {
return this.startSync();
}
protected async startSync(emitFn: (emitEventType: string, ...payload: any[]) => Promise<any> = null) {
// noinspection ES6RedundantAwait
let token = await Promise.resolve(this.storage.getSyncToken());
const promiseWhile = async () => {
if (this.stopSyncing) {
LogService.info("MatrixClientLite", "Client stop requested - stopping sync");
return;
}
try {
const response = await this.doSync(token);
token = response["next_batch"];
if (!this.persistTokenAfterSync) {
await Promise.resolve(this.storage.setSyncToken(token));
}
LogService.debug("MatrixClientLite", "Received sync. Next token: " + token);
await this.processSync(response, emitFn);
if (this.persistTokenAfterSync) {
await Promise.resolve(this.storage.setSyncToken(token));
}
} catch (e) {
// If we've requested to stop syncing, don't bother checking the error.
if (this.stopSyncing) {
LogService.info("MatrixClientLite", "Client stop requested - cancelling sync");
return;
}
LogService.error("MatrixClientLite", "Error handling sync " + extractRequestError(e));
const backoffTime = SYNC_BACKOFF_MIN_MS + Math.random() * (SYNC_BACKOFF_MAX_MS - SYNC_BACKOFF_MIN_MS);
LogService.info("MatrixClientLite", `Backing off for ${backoffTime}ms`);
await new Promise((r) => setTimeout(r, backoffTime));
}
return promiseWhile();
};
promiseWhile(); // start the loop
}
@timedMatrixClientFunctionCall()
protected doSync(token: string): Promise<any> {
LogService.debug("MatrixClientLite", "Performing sync with token " + token);
const conf = {
full_state: false,
timeout: Math.max(0, this.syncingTimeout),
};
// synapse complains if the variables are null, so we have to have it unset instead
if (token) conf["since"] = token;
if (this.filterId) conf['filter'] = this.filterId;
if (this.syncingPresence) conf['presence'] = this.syncingPresence;
// timeout is 40s if we have a token, otherwise 10min
return this.doRequest("GET", "/_matrix/client/v3/sync", conf, null, (token ? 40000 : 600000));
}
@timedMatrixClientFunctionCall()
protected async processSync(raw: any, emitFn: (emitEventType: string, ...payload: any[]) => Promise<any> = null): Promise<any> {
if (!emitFn) emitFn = (e, ...p) => Promise.resolve<any>(this.emit(e, ...p));
if (!raw) return; // nothing to process
const inbox: IToDeviceMessage[] = [];
if (raw['to_device']?.['events']) {
inbox.push(...raw['to_device']['events']);
}
for (const message of inbox) {
this.emit("to-device", message);
}
if (this.crypto) {
let unusedFallbacks: OTKAlgorithm[] = [];
if (raw['org.matrix.msc2732.device_unused_fallback_key_types']) {
unusedFallbacks = raw['org.matrix.msc2732.device_unused_fallback_key_types'];
} else if (raw['device_unused_fallback_key_types']) {
unusedFallbacks = raw['device_unused_fallback_key_types'];
}
const counts = raw['device_one_time_keys_count'] ?? {};
const changed = raw['device_lists']?.['changed'] ?? [];
const left = raw['device_lists']?.['left'] ?? [];
await this.crypto.updateSyncData(inbox, counts, unusedFallbacks, changed, left);
}
// Always process device messages first to ensure there are decryption keys
if (raw['account_data'] && raw['account_data']['events']) {
for (const event of raw['account_data']['events']) {
await emitFn("account_data", event);
}
}
if (!raw['rooms']) return; // nothing more to process
const leftRooms = raw['rooms']['leave'] || {};
const inviteRooms = raw['rooms']['invite'] || {};
const joinedRooms = raw['rooms']['join'] || {};
// Process rooms we've left first
for (const roomId in leftRooms) {
const room = leftRooms[roomId];
if (room['account_data'] && room['account_data']['events']) {
for (const event of room['account_data']['events']) {
await emitFn("room.account_data", roomId, event);
}
}
if (!room['timeline'] || !room['timeline']['events']) continue;
let leaveEvent = null;
for (const event of room['timeline']['events']) {
if (event['type'] !== 'm.room.member') continue;
if (event['state_key'] !== await this.getUserId()) continue;
const membership = event["content"]?.["membership"];
if (membership !== "leave" && membership !== "ban") continue;
const oldAge = leaveEvent && leaveEvent['unsigned'] && leaveEvent['unsigned']['age'] ? leaveEvent['unsigned']['age'] : 0;
const newAge = event['unsigned'] && event['unsigned']['age'] ? event['unsigned']['age'] : 0;
if (leaveEvent && oldAge < newAge) continue;
leaveEvent = event;
}
if (!leaveEvent) {
LogService.warn("MatrixClientLite", "Left room " + roomId + " without receiving an event");
continue;
}
leaveEvent = await this.processEvent(leaveEvent);
await emitFn("room.leave", roomId, leaveEvent);
this.lastJoinedRoomIds = this.lastJoinedRoomIds.filter(r => r !== roomId);
}
// Process rooms we've been invited to
for (const roomId in inviteRooms) {
const room = inviteRooms[roomId];
if (!room['invite_state'] || !room['invite_state']['events']) continue;
let inviteEvent = null;
for (const event of room['invite_state']['events']) {
if (event['type'] !== 'm.room.member') continue;
if (event['state_key'] !== await this.getUserId()) continue;
if (!event['content']) continue;
if (event['content']['membership'] !== "invite") continue;
const oldAge = inviteEvent && inviteEvent['unsigned'] && inviteEvent['unsigned']['age'] ? inviteEvent['unsigned']['age'] : 0;
const newAge = event['unsigned'] && event['unsigned']['age'] ? event['unsigned']['age'] : 0;
if (inviteEvent && oldAge < newAge) continue;
inviteEvent = event;
}
if (!inviteEvent) {
LogService.warn("MatrixClientLite", "Invited to room " + roomId + " without receiving an event");
continue;
}
inviteEvent = await this.processEvent(inviteEvent);
await emitFn("room.invite", roomId, inviteEvent);
}
// Process rooms we've joined and their events
for (const roomId in joinedRooms) {
const room = joinedRooms[roomId];
if (room['account_data'] && room['account_data']['events']) {
for (const event of room['account_data']['events']) {
await emitFn("room.account_data", roomId, event);
}
}
if (!room['timeline'] || !room['timeline']['events']) continue;
for (let event of room['timeline']['events']) {
if (event['type'] === "m.room.member" && event['state_key'] === await this.getUserId()) {
if (event['content']?.['membership'] === "join" && this.lastJoinedRoomIds.indexOf(roomId) === -1) {
await emitFn("room.join", roomId, await this.processEvent(event));
this.lastJoinedRoomIds.push(roomId);
}
}
event = await this.processEvent(event);
if (event['type'] === 'm.room.encrypted' && await this.crypto?.isRoomEncrypted(roomId)) {
await emitFn("room.encrypted_event", roomId, event);
try {
event = (await this.crypto.decryptRoomEvent(new EncryptedRoomEvent(event), roomId)).raw;
event = await this.processEvent(event);
await emitFn("room.decrypted_event", roomId, event);
} catch (e) {
LogService.error("MatrixClientLite", `Decryption error on ${roomId} ${event['event_id']}`, e);
await emitFn("room.failed_decryption", roomId, event, e);
}
}
if (event['type'] === 'm.room.message') {
await emitFn("room.message", roomId, event);
}
if (event['type'] === 'm.room.tombstone' && event['state_key'] === '') {
await emitFn("room.archived", roomId, event);
}
if (event['type'] === 'm.room.create' && event['state_key'] === '' && event['content']
&& event['content']['predecessor'] && event['content']['predecessor']['room_id']) {
await emitFn("room.upgraded", roomId, event);
}
await emitFn("room.event", roomId, event);
}
}
}
/**
* Gets an event for a room. If the event is encrypted, and the client supports encryption,
* and the room is encrypted, then this will return a decrypted event.
* @param {string} roomId the room ID to get the event in
* @param {string} eventId the event ID to look up
* @returns {Promise<any>} resolves to the found event
*/
@timedMatrixClientFunctionCall()
public async getEvent(roomId: string, eventId: string): Promise<any> {
const event = await this.getRawEvent(roomId, eventId);
if (event['type'] === 'm.room.encrypted' && await this.crypto?.isRoomEncrypted(roomId)) {
return this.processEvent((await this.crypto.decryptRoomEvent(new EncryptedRoomEvent(event), roomId)).raw);
}
return event;
}
/**
* Gets an event for a room. Returned as a raw event.
* @param {string} roomId the room ID to get the event in
* @param {string} eventId the event ID to look up
* @returns {Promise<any>} resolves to the found event
*/
@timedMatrixClientFunctionCall()
public getRawEvent(roomId: string, eventId: string): Promise<any> {
return this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/event/" + encodeURIComponent(eventId))
.then(ev => this.processEvent(ev));
}
/**
* Gets the room state for the given room. Returned as raw events.
* @param {string} roomId the room ID to get state for
* @returns {Promise<any[]>} resolves to the room's state
*/
@timedMatrixClientFunctionCall()
public getRoomState(roomId: string): Promise<any[]> {
return this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/state")
.then(state => Promise.all(state.map(ev => this.processEvent(ev))));
}
/**
* Gets the state events for a given room of a given type under the given state key.
* @param {string} roomId the room ID
* @param {string} type the event type
* @param {String} stateKey the state key, falsey if not needed
* @returns {Promise<any|any[]>} resolves to the state event(s)
* @deprecated It is not possible to get an array of events - use getRoomStateEvent instead
*/
@timedMatrixClientFunctionCall()
public getRoomStateEvents(roomId, type, stateKey): Promise<any | any[]> {
return this.getRoomStateEvent(roomId, type, stateKey);
}
/**
* Gets a state event for a given room of a given type under the given state key.
* @param {string} roomId the room ID
* @param {string} type the event type
* @param {String} stateKey the state key
* @returns {Promise<any>} resolves to the state event
*/
@timedMatrixClientFunctionCall()
public getRoomStateEvent(roomId, type, stateKey): Promise<any> {
const path = "/_matrix/client/v3/rooms/"
+ encodeURIComponent(roomId) + "/state/"
+ encodeURIComponent(type) + "/"
+ encodeURIComponent(stateKey ? stateKey : '');
return this.doRequest("GET", path)
.then(ev => this.processEvent(ev));
}
/**
* Gets the context surrounding an event.
* @param {string} roomId The room ID to get the context in.
* @param {string} eventId The event ID to get the context of.
* @param {number} limit The maximum number of events to return on either side of the event.
* @returns {Promise<EventContext>} The context of the event
*/
@timedMatrixClientFunctionCall()
public async getEventContext(roomId: string, eventId: string, limit = 10): Promise<EventContext> {
const res = await this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/context/" + encodeURIComponent(eventId), { limit });
return {
event: new RoomEvent<RoomEventContent>(res['event']),
before: res['events_before'].map(e => new RoomEvent<RoomEventContent>(e)),
after: res['events_after'].map(e => new RoomEvent<RoomEventContent>(e)),
state: res['state'].map(e => new StateEvent<RoomEventContent>(e)),
};
}
/**
* Gets the profile for a given user
* @param {string} userId the user ID to lookup
* @returns {Promise<any>} the profile of the user
*/
@timedMatrixClientFunctionCall()
public getUserProfile(userId: string): Promise<any> {
return this.doRequest("GET", "/_matrix/client/v3/profile/" + encodeURIComponent(userId));
}
/**
* Sets a new display name for the user.
* @param {string} displayName the new display name for the user, or null to clear
* @returns {Promise<any>} resolves when complete
*/
@timedMatrixClientFunctionCall()
public async setDisplayName(displayName: string): Promise<any> {
const userId = encodeURIComponent(await this.getUserId());
return this.doRequest("PUT", "/_matrix/client/v3/profile/" + userId + "/displayname", null, {
displayname: displayName,
});
}
/**
* Sets a new avatar url for the user.
* @param {string} avatarUrl the new avatar URL for the user, in the form of a Matrix Content URI
* @returns {Promise<any>} resolves when complete
*/
@timedMatrixClientFunctionCall()
public async setAvatarUrl(avatarUrl: string): Promise<any> {
const userId = encodeURIComponent(await this.getUserId());
return this.doRequest("PUT", "/_matrix/client/v3/profile/" + userId + "/avatar_url", null, {
avatar_url: avatarUrl,
});
}
/**
* Joins the given room
* @param {string} roomIdOrAlias the room ID or alias to join
* @param {string[]} viaServers the server names to try and join through
* @returns {Promise<string>} resolves to the joined room ID
*/
@timedMatrixClientFunctionCall()
public async joinRoom(roomIdOrAlias: string, viaServers: string[] = []): Promise<string> {
const apiCall = (targetIdOrAlias: string) => {
targetIdOrAlias = encodeURIComponent(targetIdOrAlias);
const qs = {};
if (viaServers.length > 0) qs['server_name'] = viaServers;
return this.doRequest("POST", "/_matrix/client/v3/join/" + targetIdOrAlias, qs, {}).then(response => {
return response['room_id'];
});
};
const userId = await this.getUserId();
if (this.joinStrategy) return this.joinStrategy.joinRoom(roomIdOrAlias, userId, apiCall);
else return apiCall(roomIdOrAlias);
}
/**
* Gets a list of joined room IDs
* @returns {Promise<string[]>} resolves to a list of room IDs the client participates in
*/
@timedMatrixClientFunctionCall()
public getJoinedRooms(): Promise<string[]> {
return this.doRequest("GET", "/_matrix/client/v3/joined_rooms").then(response => response['joined_rooms']);
}
/**
* Gets the joined members in a room. The client must be in the room to make this request.
* @param {string} roomId The room ID to get the joined members of.
* @returns {Promise<string>} The joined user IDs in the room
*/
@timedMatrixClientFunctionCall()
public getJoinedRoomMembers(roomId: string): Promise<string[]> {
return this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/joined_members").then(response => {
return Object.keys(response['joined']);
});
}
/**
* Gets the joined members in a room, as an object mapping userIds to profiles. The client must be in the room to make this request.
* @param {string} roomId The room ID to get the joined members of.
* @returns {Object} The joined user IDs in the room as an object mapped to a set of profiles.
*/
@timedMatrixClientFunctionCall()
public async getJoinedRoomMembersWithProfiles(roomId: string): Promise<{ [userId: string]: { display_name?: string, avatar_url?: string } }> {
return (await this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/joined_members")).joined;
}
/**
* Gets the membership events of users in the room. Defaults to all membership
* types, though this can be controlled with the membership and notMembership
* arguments. To change the point in time, use the batchToken.
* @param {string} roomId The room ID to get members in.
* @param {string} batchToken The point in time to get members at (or null for 'now')
* @param {string[]} membership The membership kinds to search for.
* @param {string[]} notMembership The membership kinds to not search for.
* @returns {Promise<MembershipEvent[]>} Resolves to the membership events of the users in the room.
* @see getRoomMembersByMembership
* @see getRoomMembersWithoutMembership
* @see getAllRoomMembers
*/
@timedMatrixClientFunctionCall()
public getRoomMembers(roomId: string, batchToken: string = null, membership: Membership[] = null, notMembership: Membership[] = null): Promise<MembershipEvent[]> {
if (!membership && !notMembership) {
return this.getAllRoomMembers(roomId, batchToken);
}
return Promise.all([
...(membership ?? []).map(m => this.getRoomMembersAt(roomId, m, null, batchToken)),
...(notMembership ?? []).map(m => this.getRoomMembersAt(roomId, null, m, batchToken)),
]).then(r => r.reduce((p, c) => {
p.push(...c);
return p;
}, [])).then(r => {
// Shouldn't ever happen, but dedupe just in case.
const vals = new Map<string, MembershipEvent>();
for (const ev of r) {
if (!vals.has(ev.membershipFor)) {
vals.set(ev.membershipFor, ev);
}
}
return Array.from(vals.values());
});
}
/**
* Gets all room members in the room, optionally at a given point in time.
* @param {string} roomId The room ID to get members of.
* @param {string} atToken Optional batch token to get members at. Leave falsy for "now".
* @returns {Promise<MembershipEvent[]>} Resolves to the member events in the room.
*/
@timedMatrixClientFunctionCall()
public getAllRoomMembers(roomId: string, atToken?: string): Promise<MembershipEvent[]> {
return this.getRoomMembersAt(roomId, null, null, atToken);
}
/**
* Gets the membership events of users in the room which have a particular membership type. To change
* the point in time the server should return membership events at, use `atToken`.
* @param {string} roomId The room ID to get members in.
* @param {Membership} membership The membership to search for.
* @param {string?} atToken Optional batch token to use, or null for "now".
* @returns {Promise<MembershipEvent[]>} Resolves to the membership events of the users in the room.
*/
@timedMatrixClientFunctionCall()
public getRoomMembersByMembership(roomId: string, membership: Membership, atToken?: string): Promise<MembershipEvent[]> {
return this.getRoomMembersAt(roomId, membership, null, atToken);
}
/**
* Gets the membership events of users in the room which lack a particular membership type. To change
* the point in time the server should return membership events at, use `atToken`.
* @param {string} roomId The room ID to get members in.
* @param {Membership} notMembership The membership to NOT search for.
* @param {string?} atToken Optional batch token to use, or null for "now".
* @returns {Promise<MembershipEvent[]>} Resolves to the membership events of the users in the room.
*/
@timedMatrixClientFunctionCall()
public async getRoomMembersWithoutMembership(roomId: string, notMembership: Membership, atToken?: string): Promise<MembershipEvent[]> {
return this.getRoomMembersAt(roomId, null, notMembership, atToken);
}
private getRoomMembersAt(roomId: string, membership: Membership | null, notMembership: Membership | null, atToken: string | null): Promise<MembershipEvent[]> {
const qs = {};
if (atToken) qs["at"] = atToken;
if (membership) qs["membership"] = membership;
if (notMembership) qs["not_membership"] = notMembership;
return this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/members", qs).then(r => {
return r['chunk'].map(e => new MembershipEvent(e));
});
}
/**
* Leaves the given room
* @param {string} roomId the room ID to leave
* @param {string=} reason Optional reason to be included as the reason for leaving the room.
* @returns {Promise<any>} resolves when left
*/
@timedMatrixClientFunctionCall()
public leaveRoom(roomId: string, reason?: string): Promise<any> {
return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/leave", null, { reason });
}
/**
* Forgets the given room
* @param {string} roomId the room ID to forget
* @returns {Promise<{}>} Resolves when forgotten
*/
@timedMatrixClientFunctionCall()
public forgetRoom(roomId: string): Promise<{}> {
return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/forget");
}
/**
* Sends a read receipt for an event in a room
* @param {string} roomId the room ID to send the receipt to
* @param {string} eventId the event ID to set the receipt at
* @returns {Promise<any>} resolves when the receipt has been sent
*/
@timedMatrixClientFunctionCall()
public sendReadReceipt(roomId: string, eventId: string): Promise<any> {
return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/receipt/m.read/" + encodeURIComponent(eventId), null, {});
}
/**
* Sets the typing status of the current user in a room
* @param {string} roomId the room ID the user is typing in
* @param {boolean} typing is the user currently typing
* @param {number} timeout how long should the server preserve the typing state, in milliseconds
* @returns {Promise<any>} resolves when the typing state has been set
*/
@timedMatrixClientFunctionCall()
public async setTyping(roomId: string, typing: boolean, timeout = 30000): Promise<any> {
const userId = await this.getUserId();
return this.doRequest("PUT", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/typing/" + encodeURIComponent(userId), null, {
typing,
timeout,
});
}
/**
* Replies to a given event with the given text. The event is sent with a msgtype of m.text.
* The message will be encrypted if the client supports encryption and the room is encrypted.
* @param {string} roomId the room ID to reply in
* @param {any} event the event to reply to
* @param {string} text the text to reply with
* @param {string} html the HTML to reply with, or falsey to use the `text`
* @returns {Promise<string>} resolves to the event ID which was sent
*/
@timedMatrixClientFunctionCall()
public replyText(roomId: string, event: any, text: string, html: string = null): Promise<string> {
if (!html) html = htmlEncode(text);
const reply = RichReply.createFor(roomId, event, text, html);
return this.sendMessage(roomId, reply);
}
/**
* Replies to a given event with the given HTML. The event is sent with a msgtype of m.text.
* The message will be encrypted if the client supports encryption and the room is encrypted.
* @param {string} roomId the room ID to reply in
* @param {any} event the event to reply to
* @param {string} html the HTML to reply with.
* @returns {Promise<string>} resolves to the event ID which was sent
*/
@timedMatrixClientFunctionCall()
public replyHtmlText(roomId: string, event: any, html: string): Promise<string> {
const text = htmlToText(html, { wordwrap: false });
const reply = RichReply.createFor(roomId, event, text, html);
return this.sendMessage(roomId, reply);
}
/**
* Replies to a given event with the given text. The event is sent with a msgtype of m.notice.
* The message will be encrypted if the client supports encryption and the room is encrypted.
* @param {string} roomId the room ID to reply in
* @param {any} event the event to reply to
* @param {string} text the text to reply with
* @param {string} html the HTML to reply with, or falsey to use the `text`
* @returns {Promise<string>} resolves to the event ID which was sent
*/
@timedMatrixClientFunctionCall()
public replyNotice(roomId: string, event: any, text: string, html: string = null): Promise<string> {
if (!html) html = htmlEncode(text);
const reply = RichReply.createFor(roomId, event, text, html);
reply['msgtype'] = 'm.notice';
return this.sendMessage(roomId, reply);
}
/**
* Replies to a given event with the given HTML. The event is sent with a msgtype of m.notice.
* The message will be encrypted if the client supports encryption and the room is encrypted.
* @param {string} roomId the room ID to reply in
* @param {any} event the event to reply to
* @param {string} html the HTML to reply with.
* @returns {Promise<string>} resolves to the event ID which was sent
*/
@timedMatrixClientFunctionCall()
public replyHtmlNotice(roomId: string, event: any, html: string): Promise<string> {
const text = htmlToText(html, { wordwrap: false });
const reply = RichReply.createFor(roomId, event, text, html);
reply['msgtype'] = 'm.notice';
return this.sendMessage(roomId, reply);
}
/**
* Sends a notice to the given room. The message will be encrypted if the client supports
* encryption and the room is encrypted.
* @param {string} roomId the room ID to send the notice to
* @param {string} text the text to send
* @returns {Promise<string>} resolves to the event ID that represents the message
*/
@timedMatrixClientFunctionCall()
public sendNotice(roomId: string, text: string): Promise<string> {
return this.sendMessage(roomId, {
body: text,
msgtype: "m.notice",
});
}
/**
* Sends a notice to the given room with HTML content. The message will be encrypted if the client supports
* encryption and the room is encrypted.
* @param {string} roomId the room ID to send the notice to
* @param {string} html the HTML to send
* @returns {Promise<string>} resolves to the event ID that represents the message
*/
@timedMatrixClientFunctionCall()
public sendHtmlNotice(roomId: string, html: string): Promise<string> {
return this.sendMessage(roomId, {
body: htmlToText(html, { wordwrap: false }),
msgtype: "m.notice",
format: "org.matrix.custom.html",
formatted_body: html,
});
}
/**
* Sends a text message to the given room. The message will be encrypted if the client supports
* encryption and the room is encrypted.
* @param {string} roomId the room ID to send the text to
* @param {string} text the text to send
* @returns {Promise<string>} resolves to the event ID that represents the message
*/
@timedMatrixClientFunctionCall()
public sendText(roomId: string, text: string): Promise<string> {
return this.sendMessage(roomId, {
body: text,
msgtype: "m.text",
});
}
/**
* Sends a text message to the given room with HTML content. The message will be encrypted if the client supports
* encryption and the room is encrypted.
* @param {string} roomId the room ID to send the text to
* @param {string} html the HTML to send
* @returns {Promise<string>} resolves to the event ID that represents the message
*/
@timedMatrixClientFunctionCall()
public sendHtmlText(roomId: string, html: string): Promise<string> {
return this.sendMessage(roomId, {
body: htmlToText(html, { wordwrap: false }),
msgtype: "m.text",
format: "org.matrix.custom.html",
formatted_body: html,
});
}
/**
* Sends a message to the given room. The message will be encrypted if the client supports
* encryption and the room is encrypted.
* @param {string} roomId the room ID to send the message to
* @param {object} content the event content to send
* @returns {Promise<string>} resolves to the event ID that represents the message
*/
@timedMatrixClientFunctionCall()
public sendMessage(roomId: string, content: any): Promise<string> {
return this.sendEvent(roomId, "m.room.message", content);
}
/**
* Sends an event to the given room. This will encrypt the event before sending if the room is
* encrypted and the client supports encryption. Use sendRawEvent() to avoid this behaviour.
* @param {string} roomId the room ID to send the event to
* @param {string} eventType the type of event to send
* @param {string} content the event body to send
* @returns {Promise<string>} resolves to the event ID that represents the event
*/
@timedMatrixClientFunctionCall()
public async sendEvent(roomId: string, eventType: string, content: any): Promise<string> {
if (await this.crypto?.isRoomEncrypted(roomId)) {
content = await this.crypto.encryptRoomEvent(roomId, eventType, content);
eventType = "m.room.encrypted";
}
return this.sendRawEvent(roomId, eventType, content);
}
/**
* Sends an event to the given room.
* @param {string} roomId the room ID to send the event to
* @param {string} eventType the type of event to send
* @param {string} content the event body to send
* @returns {Promise<string>} resolves to the event ID that represents the event
*/
@timedMatrixClientFunctionCall()
public async sendRawEvent(roomId: string, eventType: string, content: any): Promise<string> {
const txnId = (new Date().getTime()) + "__inc" + (++this.requestId);
const path = "/_matrix/client/v3/rooms/"
+ encodeURIComponent(roomId) + "/send/"
+ encodeURIComponent(eventType) + "/"
+ encodeURIComponent(txnId);
return this.doRequest("PUT", path, null, content).then(response => {
return response['event_id'];
});
}
/**
* Sends a state event to the given room
* @param {string} roomId the room ID to send the event to
* @param {string} type the event type to send
* @param {string} stateKey the state key to send, should not be null
* @param {string} content the event body to send
* @returns {Promise<string>} resolves to the event ID that represents the message
*/
@timedMatrixClientFunctionCall()
public sendStateEvent(roomId: string, type: string, stateKey: string, content: any): Promise<string> {
const path = "/_matrix/client/v3/rooms/"
+ encodeURIComponent(roomId) + "/state/"
+ encodeURIComponent(type) + "/"
+ encodeURIComponent(stateKey);
return this.doRequest("PUT", path, null, content).then(response => {
return response['event_id'];
});
}
/**
* Redact an event in a given room
* @param {string} roomId the room ID to send the redaction to
* @param {string} eventId the event ID to redact
* @param {String} reason an optional reason for redacting the event
* @returns {Promise<string>} resolves to the event ID that represents the redaction
*/
@timedMatrixClientFunctionCall()
public redactEvent(roomId: string, eventId: string, reason: string | null = null): Promise<string> {
const txnId = (new Date().getTime()) + "__inc" + (++this.requestId);
const content = reason !== null ? { reason } : {};
return this.doRequest("PUT", `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`, null, content).then(response => {
return response['event_id'];
});
}
/**
* Creates a room. See the RoomCreateOptions interface
* for more information on what to provide for `properties`. Note that creating
* a room may cause the bot/appservice to raise a join event.
* @param {RoomCreateOptions} properties the properties of the room.
* @returns {Promise<string>} resolves to the room ID that represents the room
*/
@timedMatrixClientFunctionCall()
public createRoom(properties: RoomCreateOptions = {}): Promise<string> {
return this.doRequest("POST", "/_matrix/client/v3/createRoom", null, properties).then(response => {
return response['room_id'];
});
}
/**
* Checks if a given user has a required power level required to send the given event.
* @param {string} userId the user ID to check the power level of
* @param {string} roomId the room ID to check the power level in
* @param {string} eventType the event type to look for in the `events` property of the power levels
* @param {boolean} isState true to indicate the event is intended to be a state event
* @returns {Promise<boolean>} resolves to true if the user has the required power level, resolves to false otherwise
*/
@timedMatrixClientFunctionCall()
public async userHasPowerLevelFor(userId: string, roomId: string, eventType: string, isState: boolean): Promise<boolean> {
const powerLevelsEvent = await this.getRoomStateEvent(roomId, "m.room.power_levels", "");
if (!powerLevelsEvent) {
// This is technically supposed to be non-fatal, but it's pretty unreasonable for a room to be missing
// power levels.
throw new Error("No power level event found");
}
let requiredPower = isState ? 50 : 0;
if (isState && Number.isFinite(powerLevelsEvent["state_default"])) requiredPower = powerLevelsEvent["state_default"];
if (!isState && Number.isFinite(powerLevelsEvent["events_default"])) requiredPower = powerLevelsEvent["events_default"];
if (Number.isFinite(powerLevelsEvent["events"]?.[eventType])) requiredPower = powerLevelsEvent["events"][eventType];
let userPower = 0;
if (Number.isFinite(powerLevelsEvent["users_default"])) userPower = powerLevelsEvent["users_default"];
if (Number.isFinite(powerLevelsEvent["users"]?.[userId])) userPower = powerLevelsEvent["users"][userId];
return userPower >= requiredPower;
}
/**
* Checks if a given user has a required power level to perform the given action
* @param {string} userId the user ID to check the power level of
* @param {string} roomId the room ID to check the power level in
* @param {PowerLevelAction} action the action to check power level for
* @returns {Promise<boolean>} resolves to true if the user has the required power level, resolves to false otherwise
*/
@timedMatrixClientFunctionCall()
public async userHasPowerLevelForAction(userId: string, roomId: string, action: PowerLevelAction): Promise<boolean> {
const powerLevelsEvent = await this.getRoomStateEvent(roomId, "m.room.power_levels", "");
if (!powerLevelsEvent) {
// This is technically supposed to be non-fatal, but it's pretty unreasonable for a room to be missing
// power levels.
throw new Error("No power level event found");
}
const defaultForActions: { [A in PowerLevelAction]: number } = {
[PowerLevelAction.Ban]: 50,
[PowerLevelAction.Invite]: 50,
[PowerLevelAction.Kick]: 50,
[PowerLevelAction.RedactEvents]: 50,
[PowerLevelAction.NotifyRoom]: 50,
};
let requiredPower = defaultForActions[action];
let investigate = powerLevelsEvent;
action.split('.').forEach(k => (investigate = investigate?.[k]));
if (Number.isFinite(investigate)) requiredPower = investigate;
let userPower = 0;
if (Number.isFinite(powerLevelsEvent["users_default"])) userPower = powerLevelsEvent["users_default"];
if (Number.isFinite(powerLevelsEvent["users"]?.[userId])) userPower = powerLevelsEvent["users"][userId];
return userPower >= requiredPower;
}
/**
* Determines the boundary conditions for this client's ability to change another user's power level
* in a given room. This will identify the maximum possible level this client can change the user to,
* and if that change could even be possible. If the returned object indicates that the client can
* change the power level of the user, the client is able to set the power level to any value equal
* to or less than the maximum value.
* @param {string} targetUserId The user ID to compare against.
* @param {string} roomId The room ID to compare within.
* @returns {Promise<PowerLevelBounds>} The bounds of the client's ability to change the user's power level.
*/
@timedMatrixClientFunctionCall()
public async calculatePowerLevelChangeBoundsOn(targetUserId: string, roomId: string): Promise<PowerLevelBounds> {
const myUserId = await this.getUserId();
const canChangePower = await this.userHasPowerLevelFor(myUserId, roomId, "m.room.power_levels", true);
if (!canChangePower) return { canModify: false, maximumPossibleLevel: 0 };
const powerLevelsEvent = await this.getRoomStateEvent(roomId, "m.room.power_levels", "");
if (!powerLevelsEvent) {
throw new Error("No power level event found");
}
let targetUserPower = 0;
let myUserPower = 0;
if (powerLevelsEvent["users"] && powerLevelsEvent["users"][targetUserId]) targetUserPower = powerLevelsEvent["users"][targetUserId];
if (powerLevelsEvent["users"] && powerLevelsEvent["users"][myUserId]) myUserPower = powerLevelsEvent["users"][myUserId];
if (myUserId === targetUserId) {
return { canModify: true, maximumPossibleLevel: myUserPower };
}
if (targetUserPower >= myUserPower) {
return { canModify: false, maximumPossibleLevel: myUserPower };
}
return { canModify: true, maximumPossibleLevel: myUserPower };
}
/**
* Sets the power level for a given user ID in the given room. Note that this is not safe to
* call multiple times concurrently as changes are not atomic. This will throw an error if
* the user lacks enough permission to change the power level, or if a power level event is
* missing from the room.
* @param {string} userId The user ID to change
* @param {string} roomId The room ID to change the power level in
* @param {number} newLevel The integer power level to set the user to.
* @returns {Promise<any>} Resolves when complete.
*/
@timedMatrixClientFunctionCall()
public async setUserPowerLevel(userId: string, roomId: string, newLevel: number): Promise<any> {
const currentLevels = await this.getRoomStateEvent(roomId, "m.room.power_levels", "");
if (!currentLevels['users']) currentLevels['users'] = {};
currentLevels['users'][userId] = newLevel;
await this.sendStateEvent(roomId, "m.room.power_levels", "", currentLevels);
}
/**
* Converts a MXC URI to an HTTP URL.
* @param {string} mxc The MXC URI to convert
* @returns {string} The HTTP URL for the content.
*/
public mxcToHttp(mxc: string): string {
if (!mxc.startsWith("mxc://")) throw new Error("Not a MXC URI");
const parts = mxc.substring("mxc://".length).split('/');
const originHomeserver = parts[0];
const mediaId = parts.slice(1, parts.length).join('/');
return `${this.homeserverUrl}/_matrix/media/v3/download/${encodeURIComponent(originHomeserver)}/${encodeURIComponent(mediaId)}`;
}
/**
* Converts a MXC URI to an HTTP URL for downsizing the content.
* @param {string} mxc The MXC URI to convert and downsize.
* @param {number} width The width, as an integer, for the thumbnail.
* @param {number} height The height, as an intenger, for the thumbnail.
* @param {"crop"|"scale"} method Whether to crop or scale (preserve aspect ratio) the content.
* @returns {string} The HTTP URL for the downsized content.
*/
public mxcToHttpThumbnail(mxc: string, width: number, height: number, method: "crop" | "scale"): string {
const downloadUri = this.mxcToHttp(mxc);
return downloadUri.replace("/_matrix/media/v3/download", "/_matrix/media/v3/thumbnail")
+ `?width=${width}&height=${height}&method=${encodeURIComponent(method)}`;
}
/**
* Uploads data to the homeserver's media repository. Note that this will <b>not</b> automatically encrypt
* media as it cannot determine if the media should be encrypted.
* @param {Buffer} data the content to upload.
* @param {string} contentType the content type of the file. Defaults to application/octet-stream
* @param {string} filename the name of the file. Optional.
* @returns {Promise<string>} resolves to the MXC URI of the content
*/
@timedMatrixClientFunctionCall()
public uploadContent(data: Buffer, contentType = "application/octet-stream", filename: string = null): Promise<string> {
// TODO: Make doRequest take an object for options
return this.doRequest("POST", "/_matrix/media/v3/upload", { filename: filename }, data, 60000, false, contentType)
.then(response => response["content_uri"]);
}
/**
* Download content from the homeserver's media repository. Note that this will <b>not</b> automatically decrypt
* media as it cannot determine if the media is encrypted.
* @param {string} mxcUrl The MXC URI for the content.
* @param {string} allowRemote Indicates to the server that it should not attempt to fetch the
* media if it is deemed remote. This is to prevent routing loops where the server contacts itself.
* Defaults to true if not provided.
* @returns {Promise<{data: Buffer, contentType: string}>} Resolves to the downloaded content.
*/
public async downloadContent(mxcUrl: string, allowRemote = true): Promise<{ data: Buffer, contentType: string }> {
if (!mxcUrl.toLowerCase().startsWith("mxc://")) {
throw Error("'mxcUrl' does not begin with mxc://");
}
const urlParts = mxcUrl.substring("mxc://".length).split("/");
const domain = encodeURIComponent(urlParts[0]);
const mediaId = encodeURIComponent(urlParts[1].split("/")[0]);
const path = `/_matrix/client/v1/media/download/${domain}/${mediaId}`;
const res = await this.doRequest("GET", path, { allow_remote: allowRemote }, null, null, true, null, true);
return {
data: res.body,
contentType: res.headers["content-type"],
};
}
/**
* Uploads data to the homeserver's media repository after downloading it from the
* provided URL.
* @param {string} url The URL to download content from.
* @returns {Promise<string>} Resolves to the MXC URI of the content
*/
@timedMatrixClientFunctionCall()
public uploadContentFromUrl(url: string): Promise<string> {
return new Promise<{ body: Buffer, contentType: string }>((resolve, reject) => {
const requestId = ++this.requestId;
const params = {
uri: url,
method: "GET",
encoding: null,
};
getRequestFn()(params, (err, response, resBody) => {
if (err) {
LogService.error("MatrixClientLite", "(REQ-" + requestId + ")", extractRequestError(err));
reject(err);
} else {
const contentType = response.headers['content-type'] || "application/octet-stream";
LogService.trace("MatrixClientLite", "(REQ-" + requestId + " RESP-H" + response.statusCode + ")", "<data>");
if (response.statusCode < 200 || response.statusCode >= 300) {
LogService.error("MatrixClientLite", "(REQ-" + requestId + ")", "<data>");
reject(response);
} else resolve({ body: resBody, contentType: contentType });
}
});
}).then(obj => {
return this.uploadContent(obj.body, obj.contentType);
});
}
/**
* Determines the upgrade history for a given room as a doubly-linked list styled structure. Given
* a room ID in the history of upgrades, the resulting `previous` array will hold any rooms which
* are older than the given room. The resulting `newer` array will hold any rooms which are newer
* versions of the room. Both arrays will be defined, but may be empty individually. Element zero
* of each will always be the nearest to the given room ID and the last element will be the furthest
* from the room. The given room will never be in either array.
* @param {string} roomId the room ID to get the history of
* @returns {Promise<{previous: RoomReference[], newer: RoomReference[]}>} Resolves to the room's
* upgrade history
*/
@timedMatrixClientFunctionCall()
public async getRoomUpgradeHistory(roomId: string): Promise<{ previous: RoomReference[], newer: RoomReference[], current: RoomReference }> {
const result = { previous: [], newer: [], current: null };
const chaseCreates = async (findRoomId) => {
try {
const createEvent = await this.getRoomStateEvent(findRoomId, "m.room.create", "");
if (!createEvent) return;
if (findRoomId === roomId && !result.current) {
const version = createEvent['room_version'] || '1';
result.current = {
roomId: roomId,
version: version,
refEventId: null,
};
}
if (createEvent['predecessor'] && createEvent['predecessor']['room_id']) {
const prevRoomId = createEvent['predecessor']['room_id'];
if (prevRoomId === findRoomId) return; // Recursion is bad
if (result.previous.find(r => r.roomId === prevRoomId)) return; // Already found
let tombstoneEventId = null;
let prevVersion = "1";
try {
const roomState = await this.getRoomState(prevRoomId);
const tombstone = roomState.find(e => e['type'] === 'm.room.tombstone' && e['state_key'] === '');
const create = roomState.find(e => e['type'] === 'm.room.create' && e['state_key'] === '');
if (tombstone) {
if (!tombstone['content']) tombstone['content'] = {};
const tombstoneRefRoomId = tombstone['content']['replacement_room'];
if (tombstoneRefRoomId === findRoomId) tombstoneEventId = tombstone['event_id'];
}
if (create) {
if (!create['content']) create['content'] = {};
prevVersion = create['content']['room_version'] || "1";
}
} catch (e) {
// state not available
}
result.previous.push({
roomId: prevRoomId,
version: prevVersion,
refEventId: tombstoneEventId,
});
return chaseCreates(prevRoomId);
}
} catch (e) {
// no create event - that's fine
}
};
const chaseTombstones = async (findRoomId) => {
try {
const tombstoneEvent = await this.getRoomStateEvent(findRoomId, "m.room.tombstone", "");
if (!tombstoneEvent) return;
if (!tombstoneEvent['replacement_room']) return;
const newRoomId = tombstoneEvent['replacement_room'];
if (newRoomId === findRoomId) return; // Recursion is bad
if (result.newer.find(r => r.roomId === newRoomId)) return; // Already found
let newRoomVersion = "1";
let createEventId = null;
try {
const roomState = await this.getRoomState(newRoomId);
const create = roomState.find(e => e['type'] === 'm.room.create' && e['state_key'] === '');
if (create) {
if (!create['content']) create['content'] = {};
const predecessor = create['content']['predecessor'] || {};
const refPrevRoomId = predecessor['room_id'];
if (refPrevRoomId === findRoomId) {
createEventId = create['event_id'];
}
newRoomVersion = create['content']['room_version'] || "1";
}
} catch (e) {
// state not available
}
result.newer.push({
roomId: newRoomId,
version: newRoomVersion,
refEventId: createEventId,
});
return await chaseTombstones(newRoomId);
} catch (e) {
// no tombstone - that's fine
}
};
await chaseCreates(roomId);
await chaseTombstones(roomId);
return result;
}
/**
* Creates a Space room.
* @param {SpaceCreateOptions} opts The creation options.
* @returns {Promise<Space>} Resolves to the created space.
*/
@timedMatrixClientFunctionCall()
public async createSpace(opts: SpaceCreateOptions): Promise<Space> {
const roomCreateOpts: RoomCreateOptions = {
name: opts.name,
topic: opts.topic || "",
preset: opts.isPublic ? "public_chat" : "private_chat",
room_alias_name: opts.localpart,
initial_state: [
{
type: "m.room.history_visibility",
state_key: "",
content: {
history_visibility: opts.isPublic ? 'world_readable' : 'shared',
},
},
],
creation_content: {
type: "m.space",
},
invite: opts.invites || [],
power_level_content_override: {
ban: 100,
events_default: 50,
invite: 50,
kick: 100,
notifications: {
room: 100,
},
redact: 100,
state_default: 100,
users: {
[await this.getUserId()]: 100,
},
users_default: 0,
},
};
if (opts.avatarUrl) {
roomCreateOpts.initial_state.push({
type: 'm.room.avatar',
state_key: "",
content: {
url: opts.avatarUrl,
},
});
}
const roomId = await this.createRoom(roomCreateOpts);
return new Space(roomId, this);
}
/**
* Gets a Space.
* This API does not work with unstable spaces (e.g. org.matrix.msc.1772.space)
*
* @throws If the room is not a space or there was an error
* @returns {Promise<Space>} Resolves to the space.
*/
@timedMatrixClientFunctionCall()
public async getSpace(roomIdOrAlias: string): Promise<Space> {
const roomId = await this.resolveRoom(roomIdOrAlias);
const createEvent = await this.getRoomStateEvent(roomId, "m.room.create", "");
if (createEvent["type"] !== "m.space") {
throw new Error("Room is not a space");
}
return new Space(roomId, this);
}
/**
* Uploads One Time Keys for the current device.
* @param {OTKs} keys The keys to upload.
* @returns {Promise<OTKCounts>} Resolves to the current One Time Key counts when complete.
*/
@timedMatrixClientFunctionCall()
@requiresCrypto()
public async uploadDeviceOneTimeKeys(keys: OTKs): Promise<OTKCounts> {
return this.doRequest("POST", "/_matrix/client/v3/keys/upload", null, {
one_time_keys: keys,
}).then(r => r['one_time_key_counts']);
}
/**
* Gets the current One Time Key counts.
* @returns {Promise<OTKCounts>} Resolves to the One Time Key counts.
*/
@timedMatrixClientFunctionCall()
@requiresCrypto()
public async checkOneTimeKeyCounts(): Promise<OTKCounts> {
return this.doRequest("POST", "/_matrix/client/v3/keys/upload", null, {})
.then(r => r['one_time_key_counts']);
}
/**
* Uploads a fallback One Time Key to the server for usage. This will replace the existing fallback
* key.
* @param {FallbackKey} fallbackKey The fallback key.
* @returns {Promise<OTKCounts>} Resolves to the One Time Key counts.
*/
@timedMatrixClientFunctionCall()
@requiresCrypto()
public async uploadFallbackKey(fallbackKey: FallbackKey): Promise<OTKCounts> {
const keyObj = {
[`${OTKAlgorithm.Signed}:${fallbackKey.keyId}`]: fallbackKey.key,
};
return this.doRequest("POST", "/_matrix/client/v3/keys/upload", null, {
"org.matrix.msc2732.fallback_keys": keyObj,
"fallback_keys": keyObj,
}).then(r => r['one_time_key_counts']);
}
/**
* Gets <b>unverified</b> device lists for the given users. The caller is expected to validate
* and verify the device lists, including that the returned devices belong to the claimed users.
*
* Failures with federation are reported in the returned object. Users which did not fail a federation
* lookup but have no devices will not appear in either the failures or in the returned devices.
*
* See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-query for more
* information.
* @param {string[]} userIds The user IDs to query.
* @param {number} federationTimeoutMs The default timeout for requesting devices over federation. Defaults to
* 10 seconds.
* @returns {Promise<MultiUserDeviceListResponse>} Resolves to the device list/errors for the requested user IDs.
*/
@timedMatrixClientFunctionCall()
public async getUserDevices(userIds: string[], federationTimeoutMs = 10000): Promise<MultiUserDeviceListResponse> {
const req = {};
for (const userId of userIds) {
req[userId] = [];
}
return this.doRequest("POST", "/_matrix/client/v3/keys/query", {}, {
timeout: federationTimeoutMs,
device_keys: req,
});
}
/**
* Gets a device list for the client's own account, with metadata. The devices are not verified
* in this response, but should be active on the account.
* @returns {Promise<OwnUserDevice[]>} Resolves to the active devices on the account.
*/
@timedMatrixClientFunctionCall()
public async getOwnDevices(): Promise<OwnUserDevice[]> {
return this.doRequest("GET", "/_matrix/client/v3/devices").then(r => {
return r['devices'];
});
}
/**
* Claims One Time Keys for a set of user devices, returning those keys. The caller is expected to verify
* and validate the returned keys.
*
* Failures with federation are reported in the returned object.
* @param {Record<string, Record<string, OTKAlgorithm>>} userDeviceMap The map of user IDs to device IDs to
* OTKAlgorithm to request a claim for.
* @param {number} federationTimeoutMs The default timeout for claiming keys over federation. Defaults to
* 10 seconds.
*/
@timedMatrixClientFunctionCall()
@requiresCrypto()
public async claimOneTimeKeys(userDeviceMap: Record<string, Record<string, OTKAlgorithm>>, federationTimeoutMs = 10000): Promise<OTKClaimResponse> {
return this.doRequest("POST", "/_matrix/client/v3/keys/claim", {}, {
timeout: federationTimeoutMs,
one_time_keys: userDeviceMap,
});
}
/**
* Sends to-device messages to the respective users/devices.
* @param {string} type The message type being sent.
* @param {Record<string, Record<string, any>>} messages The messages to send, mapped as user ID to
* device ID (or "*" to denote all of the user's devices) to message payload (content).
* @returns {Promise<void>} Resolves when complete.
*/
@timedMatrixClientFunctionCall()
public async sendToDevices(type: string, messages: Record<string, Record<string, any>>): Promise<void> {
const txnId = (new Date().getTime()) + "_TDEV__inc" + (++this.requestId);
return this.doRequest("PUT", `/_matrix/client/v3/sendToDevice/${encodeURIComponent(type)}/${encodeURIComponent(txnId)}`, null, {
messages: messages,
});
}
/**
* Get relations for a given event.
* @param {string} roomId The room ID to for the given event.
* @param {string} eventId The event ID to list relations for.
* @param {string?} relationType The type of relations (e.g. `m.room.member`) to filter for. Optional.
* @param {string?} eventType The type of event to look for (e.g. `m.room.member`). Optional.
* @returns {Promise<{chunk: any[]}>} Resolves to an object containing the chunk of relations
*/
@timedMatrixClientFunctionCall()
public async getRelationsForEvent(roomId: string, eventId: string, relationType?: string, eventType?: string): Promise<{ chunk: any[] }> {
let url = `/_matrix/client/v1/rooms/${encodeURIComponent(roomId)}/relations/${encodeURIComponent(eventId)}`;
if (relationType) {
url += `/${relationType}`;
}
if (eventType) {
url += `/${eventType}`;
}
return this.doRequest("GET", url);
}
/**
* Performs a web request to the homeserver, applying appropriate authorization headers for
* this client.
* @param {"GET"|"POST"|"PUT"|"DELETE"} method The HTTP method to use in the request
* @param {string} endpoint The endpoint to call. For example: "/_matrix/client/v3/account/whoami"
* @param {any} qs The query string to send. Optional.
* @param {any} body The request body to send. Optional. Will be converted to JSON unless the type is a Buffer.
* @param {number} timeout The number of milliseconds to wait before timing out.
* @param {boolean} raw If true, the raw response will be returned instead of the response body.
* @param {string} contentType The content type to send. Only used if the `body` is a Buffer.
* @param {string} noEncoding Set to true to disable encoding, and return a Buffer. Defaults to false
* @returns {Promise<any>} Resolves to the response (body), rejected if a non-2xx status code was returned.
*/
@timedMatrixClientFunctionCall()
public doRequest(method, endpoint, qs = null, body = null, timeout = 60000, raw = false, contentType = "application/json", noEncoding = false): Promise<any> {
if (this.impersonatedUserId) {
if (!qs) qs = { "user_id": this.impersonatedUserId };
else qs["user_id"] = this.impersonatedUserId;
}
if (this.impersonatedDeviceId) {
if (!qs) qs = { "org.matrix.msc3202.device_id": this.impersonatedDeviceId };
else qs["org.matrix.msc3202.device_id"] = this.impersonatedDeviceId;
}
const headers = {};
if (this.accessToken) {
headers["Authorization"] = `Bearer ${this.accessToken}`;
}
return doHttpRequest(this.homeserverUrl, method, endpoint, qs, body, headers, timeout, raw, contentType, noEncoding);
}
}
export interface RoomDirectoryLookupResponse {
roomId: string;
residentServers: string[];
}
export interface RoomReference {
/**
* The room ID being referenced
*/
roomId: string;
/**
* The version of the room at the time
*/
version: string;
/**
* If going backwards, the tombstone event ID, otherwise the creation
* event. If the room can't be verified, this will be null. Will be
* null if this reference is to the current room.
*/
refEventId: string;
}
================================================
FILE: src/PantalaimonClient.ts
================================================
/**
* A client specifically designed to interact with Pantalaimon instead of
* a Matrix homeserver. The key part of this is managing the access token
* and username/password for interacting with Pantalaimon.
*
* If the storage provider given claims to have an access token for
* this client, it will be used even if Pantalaimon considers it invalid.
*
* Expected usage:
* <code>
* const storage = new SimpleFsStorageProvider("storage/bot.json");
* const pantalaimon = new PantalaimonClient("http://localhost:8008", storage);
*
* // Note that the credentials will only be used if there is no available access token.
* const client = await pantalaimon.createClientWithCredentials("username", "password");
* </code>
*/
import { IStorageProvider } from "./storage/IStorageProvider";
import { MatrixClient } from "./MatrixClient";
import { MatrixAuth } from "./MatrixAuth";
const ACCESS_TOKEN_STORAGE_KEY = "pantalaimon_access_token";
// TODO: Write a test for this (it's hard because of the many interactions with different parts)
/**
* Supporting functions for interacting with a Pantalaimon instance.
* @category Encryption
*/
export class PantalaimonClient {
/**
* Creates a new PantalaimonClient class for interacting with Pantalaimon. The storage
* provider given will also be used in the client.
* @param {string} homeserverUrl The homeserver (Pantalaimon) URL to interact with.
* @param {IStorageProvider} storageProvider The storage provider to back the client with.
*/
public constructor(private homeserverUrl: string, private storageProvider: IStorageProvider) {
// nothing to do
}
/**
* Authenticates and creates the Pantalaimon-capable client. The username and password given
* are only used if the storage provider does not reveal an access token.
* @param {string} username The username to log in with, if needed.
* @param {string} password The password to log in with, if needed.
* @returns {Promise<MatrixClient>} Resolves to a MatrixClient ready for interacting with Pantalaimon.
*/
public async createClientWithCredentials(username: string, password: string): Promise<MatrixClient> {
const accessToken = await Promise.resolve(this.storageProvider.readValue(ACCESS_TOKEN_STORAGE_KEY));
if (accessToken) {
return new MatrixClient(this.homeserverUrl, accessToken, this.storageProvider);
}
const auth = new MatrixAuth(this.homeserverUrl);
const authedClient = await auth.passwordLogin(username, password);
await Promise.resolve(this.storageProvider.storeValue(ACCESS_TOKEN_STORAGE_KEY, authedClient.accessToken));
// We recreate the client to ensure we set it up with the right storage provider.
return new MatrixClient(this.homeserverUrl, authedClient.accessToken, this.storageProvider);
}
}
================================================
FILE: src/SynapseAdminApis.ts
================================================
import { MatrixClient } from "./MatrixClient";
import { MatrixError } from "./models/MatrixError";
/**
* Information about a user on Synapse.
* @category Admin APIs
*/
export interface SynapseUser {
/***
* The display name of the user, if set.
*/
displayname?: string;
/**
* External IDs for the user.
*/
external_ids?: {
auth_provider: string;
external_id: string;
}[];
/**
* A set of 3PIDs for the user.
*/
threepids?: {
medium: string;
address: string;
}[];
/**
* The avatar URL (usually MXC URI) for the user, if set.
*/
avatar_url?: string;
/**
* Whether or not the user is a Synapse administrator.
*/
admin?: boolean;
/**
* Whether or not the user is deactivated.
*/
deactivated?: boolean;
}
/**
* Added information to include when updating/creating a user.
* @category Admin APIs
*/
export interface SynapseUserProperties extends SynapseUser {
/**
* The password for the user. Leave undefined to leave unchanged.
*/
password?: string;
}
/**
* Information about a user on Synapse.
* @category Admin APIs
*/
export interface SynapseUserListing {
/**
* User ID.
*/
name: string;
/**
* Whether or not the user is a guest. 1 is true, 0 is false.
*/
is_guest: number;
/**
* Whether or not the user is an admin. 1 is true, 0 is false.
*/
admin: number;
/**
* Whether or not the user is deactivated. 1 is true, 0 is false.
*/
deactivated: number;
/**
* The type of user, if relevant.
*/
user_type: string | null;
/**
* The hash of the user's password, if relevant.
*/
password_hash: string | null;
/**
* The display name of the user, if set.
*/
displayname: string | null;
/**
* The avatar for the user, if set.
*/
avatar_url: string | null;
}
/**
* A resulting list of users on Synapse.
* @category Admin APIs
*/
export interface SynapseUserList {
/**
* A set of users matching the criteria.
*/
users: SynapseUserListing[];
/**
* The token to use to get the next set of users.
*/
next_token: string;
/**
* The total number of users on the Synapse instance.
*/
total: number;
}
/**
* A registration token on Synapse
* @category Admin APIs
*/
export interface SynapseRegistrationToken {
token: string;
uses_allowed: null | number;
pending: number;
completed: number;
expiry_time: null | number;
}
export interface SynapseRegistrationTokenUpdateOptions {
/**
* The integer number of times the token can be used to complete a registration before it becomes invalid.
* If null the token will have an unlimited number of uses.
* Default: unlimited uses.
*/
uses_allowed?: number | null;
/**
* The latest time the token is valid. Given as the number of milliseconds since 1970-01-01 00:00:00 UTC (the start of the Unix epoch).
* If null the token will not expire.
* Default: token does not expire.
*/
expiry_time?: number | null;
}
export interface SynapseRegistrationTokenOptions extends SynapseRegistrationTokenUpdateOptions {
/**
* The registration token. A string of no more than 64 characters that consists only of characters matched by the regex [A-Za-z0-9._~-].
* Default: randomly generated.
*/
token?: string;
/**
* The length of the token randomly generated if token is not specified. Must be between 1 and 64 inclusive.
* Default: 16.
*/
length?: number;
}
/**
* Information about a room on Synapse.
* @category Admin APIs
*/
export interface SynapseRoomListing {
room_id: string;
name: string;
canonical_alias: string;
joined_members: number;
joined_local_members: number;
version: string;
creator: string;
encryption: string; // algorithm
federatable: boolean;
public: boolean;
join_rules: string;
guest_access: string;
history_visibility: string;
state_events: number;
}
/**
* A resulting list of rooms on Synapse.
* @category Admin APIs
*/
export interface SynapseRoomList {
rooms: SynapseRoomListing[];
offset: string;
total_rooms: number;
next_batch: string;
prev_batch: string;
}
/**
* Available properties on a Synapse room listing to order by.
* @category Admin APIs
*/
export enum SynapseRoomProperty {
Name = "name",
CanonicalAlias = "canonical_alias",
JoinedMembers = "joined_members",
JoinedLocalMembers = "joined_local_members",
Version = "version",
Creator = "creator",
Encryption = "encryption",
CanFederate = "federatable",
IsPublic = "public",
JoinRules = "join_rules",
GuestAccess = "guest_access",
HistoryVisibility = "history_visibility",
NumStateEvents = "state_events",
}
export interface SynapseListUserOptions {
/**
* Filters to only return users with user IDs that contain this value. This parameter is ignored when using the name parameter.
*/
user_id?: string;
/**
* Filters to only return users with user ID localparts or displaynames that contain this value.
*/
name?: string;
/**
* If false will exclude guest users. Defaults to true to include guest users.
*/
guests?: boolean;
/**
* If true will include deactivated users. Defaults to false to exclude deactivated users.
*/
deactivated?: boolean;
/**
* The method by which to sort the returned list of users. If the ordered field has duplicates, the
* second order is always by ascending name, which guarantees a stable ordering.
* **Caution**: The database only has indexes on the columns `name` and `creation_ts`. This means
* that if a different sort order is used, it can cause a large load on the database.
*/
order_by?: "name" | "is_guest" | "admin" | "user_type" | "deactivated" | "shadow_banned" | "displayname" | "avatar_url" | "creation_ts";
/**
* The number of results to return at a time.
*/
limit?: number;
}
/**
* Access to various administrative APIs specifically available in Synapse.
* @category Admin APIs
*/
export class SynapseAdminApis {
constructor(private client: MatrixClient) {
}
/**
* Get information about a user. The client making the request must be an admin user.
* @param {string} userId The user ID to check.
* @returns {Promise<SynapseUser>} The resulting Synapse user record
*/
public async getUser(userId: string): Promise<SynapseUser> {
return this.client.doRequest(
"GET", "/_synapse/admin/v2/users/" + encodeURIComponent(userId),
);
}
/**
* Create or update a given user on a Synapse server. The
* client making the request must be an admin user.
* @param {string} userId The user ID to check.
* @param {SynapseUserProperties} opts Options to set when creating or updating the user.
* @returns {Promise<SynapseUser>} The resulting Synapse user record
*/
public async upsertUser(userId: string, opts: SynapseUserProperties = {}): Promise<SynapseUser> {
return this.client.doRequest(
"PUT", "/_synapse/admin/v2/users/" + encodeURIComponent(userId), undefined, opts,
);
}
/**
* Get a list of users registered with Synapse, optionally filtered by some criteria. The
* client making the request must be an admin user.
* @param {string} from The token to continue listing users from.
* @param {number} limit The maximum number of users to request.
* @param {string} name Optional localpart or display name filter for results.
* @param {boolean} guests Whether or not to include guest accounts. Default true.
* @param {boolean} deactivated Whether or not to include deactivated accounts. Default false.
* @returns {Promise<SynapseUserList>} A batch of user results.
*/
public async listUsers(from?: string, limit?: number, name?: string, guests = true, deactivated = false): Promise<SynapseUserList> {
const qs = { guests, deactivated };
if (from) qs['from'] = from;
if (limit) qs['limit'] = limit;
if (name) qs['name'] = name;
return this.client.doRequest("GET", "/_synapse/admin/v2/users", qs);
}
/**
* Get a list of all users registered with Synapse, optionally filtered by some criteria. The
* client making the request must be an admin user.
*
* This method returns an async generator that can be used to filter results.
* @param options Options to pass to the user listing function
* @example
* for await (const user of synapseAdminApis.listAllUsers()) {
* if (user.name === '@alice:example.com') {
* return user;
* }
* }
*/
public async* listAllUsers(options: SynapseListUserOptions = {}): AsyncGenerator<SynapseUserListing> {
let from: string | undefined = undefined;
let response: SynapseUserList;
do {
const qs = {
...options,
...(from && { from }),
};
response = await this.client.doRequest("GET", "/_synapse/admin/v2/users", qs);
for (const user of response.users) {
yield user;
}
from = response.next_token;
} while (from);
}
/**
* Determines if the given user is a Synapse server administrator for this homeserver. The
* client making the request must be an admin user themselves (check with `isSelfAdmin`)
* @param {string} userId The user ID to check.
* @returns {Promise<boolean>} Resolves to true if the user is an admin, false otherwise.
* Throws if there's an error.
*/
public async isAdmin(userId: string): Promise<boolean> {
const response = await this.client.doRequest("GET", `/_synapse/admin/v1/users/${encodeURIComponent(userId)}/admin`);
return response['admin'] || false;
}
/**
* Determines if the current user is an admin for the Synapse homeserver.
* @returns {Promise<boolean>} Resolve to true if the user is an admin, false otherwise.
* Throws if there's an error.
*/
public async isSelfAdmin(): Promise<boolean> {
try {
return await this.isAdmin(await this.client.getUserId());
} catch (err) {
if (err instanceof MatrixError && err.errcode === 'M_FORBIDDEN') {
return false;
}
throw err;
}
}
/**
* Lists the rooms on the server.
* @param {string} searchTerm A term to search for in the room names
* @param {string} from A previous batch token to search from
* @param {number} limit The maximum number of rooms to return
* @param {SynapseRoomProperty} orderBy A property of rooms to order by
* @param {boolean} reverseOrder True to reverse the orderBy direction.
* @returns {Promise<SynapseRoomList>} Resolves to the server's rooms, ordered and filtered.
*/
public async listRooms(searchTerm?: string, from?: string, limit?: number, orderBy?: SynapseRoomProperty, reverseOrder = false): Promise<SynapseRoomList> {
const params = {};
if (from) params['from'] = from;
if (limit) params['limit'] = limit;
if (searchTerm) params['search_term'] = searchTerm;
if (orderBy) params['order_by'] = orderBy;
if (reverseOrder) {
params['dir'] = 'b';
} else {
params['dir'] = 'f';
}
return this.client.doRequest("GET", "/_synapse/admin/v1/rooms", params);
}
/**
* Gets a list of state events in a room.
* @param {string} roomId The room ID to get state for.
* @returns {Promise<any[]>} Resolves to the room's state events.
*/
public async getRoomState(roomId: string): Promise<any[]> {
const r = await this.client.doRequest("GET", `/_synapse/admin/v1/rooms/${encodeURIComponent(roomId)}/state`);
return r?.['state'] || [];
}
/**
* Deletes a room from the server, purging all record of it.
* @param {string} roomId The room to delete.
* @returns {Promise} Resolves when complete.
*/
public async deleteRoom(roomId: string): Promise<void> {
return this.client.doRequest("DELETE", `/_synapse/admin/v2/rooms/${encodeURIComponent(roomId)}`, {}, { purge: true });
}
/**
* Gets the status of all active deletion tasks, and all those completed in the last 24h, for the given room_id.
* @param {string} roomId The room ID to get deletion state for.
* @returns {Promise<any[]>} Resolves to the room's deletion status results.
*/
public async getDeleteRoomState(roomId: string): Promise<any[]> {
const r = await this.client.doRequest("GET", `/_synapse/admin/v2/rooms/${encodeURIComponent(roomId)}/delete_status`);
return r?.['results'] || [];
}
/**
* List all registration tokens on the homeserver.
* @param valid If true, only valid tokens are returned.
* If false, only tokens that have expired or have had all uses exhausted are returned.
* If omitted, all tokens are returned regardless of validity.
* @returns An array of registration tokens.
*/
public async listRegistrationTokens(valid?: boolean): Promise<SynapseRegistrationToken[]> {
const res = await this.client.doRequest("GET", `/_synapse/admin/v1/registration_tokens`, { valid });
return res.registration_tokens;
}
/**
* Get details about a single token.
* @param token The token to fetch.
* @returns A registration tokens, or null if not found.
*/
public async getRegistrationToken(token: string): Promise<SynapseRegistrationToken | null> {
try {
return await this.client.doRequest("GET", `/_synapse/admin/v1/registration_tokens/${encodeURIComponent(token)}`);
} catch (e) {
if (e?.statusCode === 404) {
return null;
}
throw e;
}
}
/**
* Create a new registration token.
* @param options Options to pass to the request.
* @returns The newly created token.
*/
public async createRegistrationToken(options: SynapseRegistrationTokenOptions = {}): Promise<SynapseRegistrationToken> {
return this.client.doRequest("POST", `/_synapse/admin/v1/registration_tokens/new`, undefined, options);
}
/**
* Update an existing registration token.
* @param token The token to update.
* @param options Options to pass to the request.
* @returns The newly created token.
*/
public async updateRegistrationToken(token: string, options: SynapseRegistrationTokenUpdateOptions): Promise<SynapseRegistrationToken> {
return this.client.doRequest("PUT", `/_synapse/admin/v1/registration_tokens/${encodeURIComponent(token)}`, undefined, options);
}
/**
* Delete a registration token
* @param token The token to update.
* @returns A promise that resolves upon success.
*/
public async deleteRegistrationToken(token: string): Promise<void> {
return this.client.doRequest("DELETE", `/_synapse/admin/v1/registration_tokens/${encodeURIComponent(token)}`, undefined, {});
}
/**
* Grants another user the highest power available to a local user who is in the room.
* If the user is not in the room, and it is not publicly joinable, then invite the user.
* @param roomId The room to make the user admin in.
* @param userId The user to make admin in the room. If undefined, it uses the authenticated user.
* @returns Resolves when complete.
*/
public async makeRoomAdmin(roomId: string, userId?: string): Promise<void> {
return this.client.doRequest("POST", `/_synapse/admin/v1/rooms/${encodeURIComponent(roomId)}/make_room_admin`, {}, { user_id: userId });
}
}
================================================
FILE: src/SynchronousMatrixClient.ts
================================================
import { MatrixClient } from "./MatrixClient";
/**
* A MatrixClient class that handles events in sync for the /sync loop, instead
* of trying to push its way through the /sync loop as fast as possible. It is
* intended that the consumer extend this class and override the onWhatever()
* functions it needs. All of the onWhatever() functions have a default behaviour
* of doing nothing.
*/
export abstract class SynchronousMatrixClient extends MatrixClient {
/**
* Creates a new SynchronousMatrixClient. Note that this accepts a MatrixClient, though
* much of the class's properties are not brought over. Always convert your MatrixClient
* instance to a SynchronousMatrixClient as soon as possible to avoid diversion in which
* properties are proxied over.
* @param {MatrixClient} baseClient The client to wrap.
*/
protected constructor(baseClient: MatrixClient) {
super(baseClient.homeserverUrl, baseClient.accessToken, baseClient.storageProvider);
}
private async handleEvent(emitType: string, arg1: any, arg2: any): Promise<any> {
if (emitType === 'account_data') await this.onAccountData(arg1);
if (emitType === 'room.account_data') await this.onRoomAccountData(arg1, arg2);
if (emitType === 'room.leave') await this.onRoomLeave(arg1, arg2);
if (emitType === 'room.invite') await this.onRoomInvite(arg1, arg2);
if (emitType === 'room.join') await this.onRoomJoin(arg1, arg2);
if (emitType === 'room.archived') await this.onRoomArchived(arg1, arg2);
if (emitType === 'room.upgraded') await this.onRoomUpgraded(arg1, arg2);
if (emitType === 'room.message') await this.onRoomMessage(arg1, arg2);
if (emitType === 'room.event') await this.onRoomEvent(arg1, arg2);
// Still emit though for easier support of plugins.
this.emit(emitType, arg1, arg2);
}
protected startSyncInternal(): Promise<any> {
return this.startSync(this.handleEvent.bind(this));
}
/**
* Handles the `account_data` event raised by the client.
* @param {any} event The account data event.
* @returns {Promise<any>} Resolves when complete.
*/
protected onAccountData(event: any): Promise<any> {
return;
}
/**
* Handles the `room.account_data` event raised by the client.
* @param {string} roomId The Room ID the account data applies to.
* @param {any} event The room account data event.
* @returns {Promise<any>} Resolves when complete.
*/
protected onRoomAccountData(roomId: string, event: any): Promise<any> {
return;
}
/**
* Handles the `room.leave` event raised by the client.
* @param {string} roomId The Room ID the event happened in.
* @param {any} event The event.
* @returns {Promise<any>} Resolves when complete.
*/
protected onRoomLeave(roomId: string, event: any): Promise<any> {
return;
}
/**
* Handles the `room.invite` event raised by the client.
* @param {string} roomId The Room ID the event happened in.
* @param {any} event The event.
* @returns {Promise<any>} Resolves when complete.
*/
protected onRoomInvite(roomId: string, event: any): Promise<any> {
return;
}
/**
* Handles the `room.join` event raised by the client.
* @param {string} roomId The Room ID the event happened in.
* @param {any} event The event.
* @returns {Promise<any>} Resolves when complete.
*/
protected onRoomJoin(roomId: string, event: any): Promise<any> {
return;
}
/**
* Handles the `room.message` event raised by the client.
* @param {string} roomId The Room ID the event happened in.
* @param {any} event The event.
* @returns {Promise<any>} Resolves when complete.
*/
protected onRoomMessage(roomId: string, event: any): Promise<any> {
return;
}
/**
* Handles the `room.archived` event raised by the client.
* @param {string} roomId The Room ID the event happened in.
* @param {any} event The event.
* @returns {Promise<any>} Resolves when complete.
*/
protected onRoomArchived(roomId: string, event: any): Promise<any> {
return;
}
/**
* Handles the `room.upgraded` event raised by the client.
* @param {string} roomId The Room ID the event happened in.
* @param {any} event The event.
* @returns {Promise<any>} Resolves when complete.
*/
protected onRoomUpgraded(roomId: string, event: any): Promise<any> {
return;
}
/**
* Handles the `room.event` event raised by the client.
* @param {string} roomId The Room ID the event happened in.
* @param {any} event The event.
* @returns {Promise<any>} Resolves when complete.
*/
protected onRoomEvent(roomId: string, event: any): Promise<any> {
return;
}
}
================================================
FILE: src/UnstableApis.ts
================================================
import { MatrixClient } from "./MatrixClient";
import { MSC2380MediaInfo } from "./models/unstable/MediaInfo";
/**
* Unstable APIs that shouldn't be used in most circumstances.
* @category Unstable APIs
*/
export class UnstableApis {
constructor(private client: MatrixClient) {
}
/**
* Gets the local room aliases that are published for a given room.
* @param {string} roomId The room ID to get local aliases for.
* @returns {Promise<string[]>} Resolves to the aliases on the room, or an empty array.
* @deprecated Relies on MSC2432 endpoint.
*/
public async getRoomAliases(roomId: string): Promise<string[]> {
const r = await this.client.doRequest("GET", "/_matrix/client/unstable/org.matrix.msc2432/rooms/" + encodeURIComponent(roomId) + "/aliases");
return r['aliases'] || [];
}
/**
* Adds a reaction to an event. The contract for this function may change in the future.
* @param {string} roomId The room ID to react in
* @param {string} eventId The event ID to react against, in the given room
* @param {string} emoji The emoji to react with
* @returns {Promise<string>} Resolves to the event ID of the reaction
*/
public async addReactionToEvent(roomId: string, eventId: string, emoji: string): Promise<string> {
return this.client.sendRawEvent(roomId, "m.reaction", {
"m.relates_to": {
event_id: eventId,
key: emoji,
rel_type: "m.annotation",
},
});
}
/**
* Get relations for a given event.
* @param {string} roomId The room ID to for the given event.
* @param {string} eventId The event ID to list relations for.
* @param {string?} relationType The type of relations (e.g. `m.room.member`) to filter for. Optional.
* @param {string?} eventType The type of event to look for (e.g. `m.room.member`). Optional.
* @returns {Promise<{chunk: any[]}>} Resolves to an object containing the chunk of relations
* @deprecated Please use the function of the same name in MatrixClient. This will be removed in a future release.
*/
public async getRelationsForEvent(roomId: string, eventId: string, relationType?: string, eventType?: string): Promise<{ chunk: any[] }> {
let url = `/_matrix/client/unstable/rooms/${encodeURIComponent(roomId)}/relations/${encodeURIComponent(eventId)}`;
if (relationType) {
url += `/${relationType}`;
}
if (eventType) {
url += `/${eventType}`;
}
return this.client.doRequest("GET", url);
}
/**
* Get information about a media item. Implements MSC2380
* @param {string} mxcUrl The MXC to get information about.
* @returns {Promise<MSC2380MediaInfo>} Resolves to an object containing the media information.
*/
public async getMediaInfo(mxcUrl: string): Promise<MSC2380MediaInfo> {
if (!mxcUrl.toLowerCase().startsWith("mxc://")) {
throw Error("'mxcUrl' does not begin with mxc://");
}
const [domain, mediaId] = mxcUrl.substring("mxc://".length).split("/");
if (!domain || !mediaId) {
throw Error('Missing domain or media ID');
}
return this.client.doRequest("GET", `/_matrix/media/unstable/info/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`);
}
}
================================================
FILE: src/appservice/Appservice.ts
================================================
import * as express from "express";
import { EventEmitter } from "events";
import * as morgan from "morgan";
import * as LRU from "lru-cache";
import { stringify } from "querystring";
import { Intent } from "./Intent";
import {
AppserviceJoinRoomStrategy,
EncryptedRoomEvent,
EventKind,
IAppserviceCryptoStorageProvider,
IAppserviceStorageProvider,
IJoinRoomStrategy,
IPreprocessor,
LogService,
MatrixClient,
MemoryStorageProvider,
Metrics,
MSC3983KeyClaimResponse,
MSC3984KeyQueryResponse,
OTKAlgorithm,
redactObjectForLogging,
UserID,
} from "..";
import { MatrixBridge } from "./MatrixBridge";
import { IApplicationServiceProtocol } from "./http_responses";
const EDU_ANNOTATION_KEY = "io.t2bot.sdk.bot.type";
enum EduAnnotation {
ToDevice = "to_device",
Ephemeral = "ephemeral",
}
/**
* Represents an application service's registration file. This is expected to be
* loaded from another source, such as a YAML file.
* @category Application services
*/
export interface IAppserviceRegistration {
/**
* Optional ID for the appplication service. Used by homeservers to track which application
* service registers what.
*/
id?: string;
/**
* Optional URL at which the application service can be contacted.
*/
url?: string;
/**
* The token the application service uses to communicate with the homeserver.
*/
as_token: string;
/**
* The token the homeserver uses to communicate with the application service.
*/
hs_token: string;
/**
* The application service's own localpart (eg: "_irc_bot" in the user ID "@_irc_bot:domain.com")
*/
sender_localpart: string;
/**
* The various namespaces the application service can support.
*/
namespaces: {
/**
* The user namespaces the application service is requesting.
*/
users: {
/**
* Whether or not the application service holds an exclusive lock on the namespace. This
* means that no other user on the homeserver may register users that match this namespace.
*/
exclusive: boolean;
/**
* The regular expression that the homeserver uses to determine if a user is in this namespace.
*/
regex: string;
}[];
/**
* The room namespaces the application service is requesting. This is not for alises.
*/
rooms: {
/**
* Whether or not the application service holds an exclusive lock on the namespace.
*/
exclusive: boolean;
/**
* The regular expression that the homeserver uses to determine if a user is in this namespace.
*/
regex: string;
}[];
/**
* The room alias namespaces the application service is requesting.
*/
aliases: {
/**
* Whether or not the application service holds an exclusive lock on the namespace. This means
* that no other user on the homeserver may register aliases that match this namespace.
*/
exclusive: boolean;
/**
* The regular expression that the homeserver uses to determine if an alias is in this namespace.
*/
regex: string;
}[];
};
/**
* The protocols the application service supports. Optional.
*/
protocols?: string[];
/**
* If the application service is rate limited by the homeserver. Optional.
*/
rate_limited?: boolean;
/**
* **Experimental**
*
* Should the application service receive ephemeral events from the homeserver. Optional.
* @see https://github.com/matrix-org/matrix-doc/pull/2409
*/
"de.sorunome.msc2409.push_ephemeral"?: boolean;
// not interested in other options
}
/**
* General options for the application service
* @category Application services
*/
export interface IAp
gitextract_mgmy1jcz/ ├── .eslintrc.js ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yaml │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── SECURITY.md │ └── workflows/ │ ├── docs.yml │ └── static_analysis.yml ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs/ │ ├── index.md │ └── tutorials/ │ ├── appservice.md │ ├── bot-to-appservice.md │ ├── bot.md │ ├── encryption-appservices.md │ ├── encryption-bots.md │ ├── encryption.md │ ├── index.json │ └── room-upgrades.md ├── eslint.config.js ├── examples/ │ ├── appservice.ts │ ├── bot.ts │ ├── encryption_appservice.ts │ ├── encryption_bot.ts │ └── login_register.ts ├── jsdoc.json ├── package.json ├── src/ │ ├── AdminApis.ts │ ├── DMs.ts │ ├── IFilter.ts │ ├── MatrixAuth.ts │ ├── MatrixClient.ts │ ├── PantalaimonClient.ts │ ├── SynapseAdminApis.ts │ ├── SynchronousMatrixClient.ts │ ├── UnstableApis.ts │ ├── appservice/ │ │ ├── Appservice.ts │ │ ├── Intent.ts │ │ ├── MatrixBridge.ts │ │ ├── UnstableAppserviceApis.ts │ │ └── http_responses.ts │ ├── b64.ts │ ├── e2ee/ │ │ ├── CryptoClient.ts │ │ ├── ICryptoRoomInformation.ts │ │ ├── RoomTracker.ts │ │ ├── RustEngine.ts │ │ └── decorators.ts │ ├── helpers/ │ │ ├── MatrixEntity.ts │ │ ├── MatrixGlob.ts │ │ ├── MentionPill.ts │ │ ├── Permalinks.ts │ │ ├── ProfileCache.ts │ │ ├── RichReply.ts │ │ └── UnpaddedBase64.ts │ ├── http.ts │ ├── identity/ │ │ └── IdentityClient.ts │ ├── index.ts │ ├── logging/ │ │ ├── ConsoleLogger.ts │ │ ├── ILogger.ts │ │ ├── LogService.ts │ │ └── RichConsoleLogger.ts │ ├── metrics/ │ │ ├── IMetricListener.ts │ │ ├── Metrics.ts │ │ ├── contexts.ts │ │ ├── decorators.ts │ │ └── names.ts │ ├── mixins/ │ │ ├── AutojoinRoomsMixin.ts │ │ └── AutojoinUpgradedRoomsMixin.ts │ ├── models/ │ │ ├── Account.ts │ │ ├── CreateRoom.ts │ │ ├── Crypto.ts │ │ ├── EventContext.ts │ │ ├── IdentityServerModels.ts │ │ ├── MSC2176.ts │ │ ├── MatrixError.ts │ │ ├── MatrixProfile.ts │ │ ├── OpenIDConnect.ts │ │ ├── Policies.ts │ │ ├── PowerLevelAction.ts │ │ ├── PowerLevelBounds.ts │ │ ├── Presence.ts │ │ ├── ServerVersions.ts │ │ ├── Spaces.ts │ │ ├── Threepid.ts │ │ ├── events/ │ │ │ ├── AliasesEvent.ts │ │ │ ├── CanonicalAliasEvent.ts │ │ │ ├── CreateEvent.ts │ │ │ ├── EncryptedRoomEvent.ts │ │ │ ├── EncryptionEvent.ts │ │ │ ├── Event.ts │ │ │ ├── EventKind.ts │ │ │ ├── InvalidEventError.ts │ │ │ ├── JoinRulesEvent.ts │ │ │ ├── MembershipEvent.ts │ │ │ ├── MessageEvent.ts │ │ │ ├── PinnedEventsEvent.ts │ │ │ ├── PowerLevelsEvent.ts │ │ │ ├── PresenceEvent.ts │ │ │ ├── RedactionEvent.ts │ │ │ ├── RoomAvatarEvent.ts │ │ │ ├── RoomEvent.ts │ │ │ ├── RoomNameEvent.ts │ │ │ ├── RoomTopicEvent.ts │ │ │ ├── SpaceChildEvent.ts │ │ │ ├── _MissingEvents.md │ │ │ └── converter.ts │ │ └── unstable/ │ │ └── MediaInfo.ts │ ├── preprocessors/ │ │ ├── IPreprocessor.ts │ │ └── RichRepliesPreprocessor.ts │ ├── request.ts │ ├── simple-validation.ts │ ├── storage/ │ │ ├── IAppserviceStorageProvider.ts │ │ ├── ICryptoStorageProvider.ts │ │ ├── IStorageProvider.ts │ │ ├── MemoryStorageProvider.ts │ │ ├── RustSdkCryptoStorageProvider.ts │ │ ├── SimpleFsStorageProvider.ts │ │ └── SimplePostgresStorageProvider.ts │ └── strategies/ │ ├── AppserviceJoinRoomStrategy.ts │ └── JoinRoomStrategy.ts ├── test/ │ ├── AdminApisTest.ts │ ├── DMsTest.ts │ ├── IdentityClientTest.ts │ ├── MatrixAuthTest.ts │ ├── MatrixClientTest.ts │ ├── SynapseAdminApisTest.ts │ ├── SynchronousMatrixClientTest.ts │ ├── TestUtils.ts │ ├── UnstableApisTest.ts │ ├── appservice/ │ │ ├── AppserviceTest.ts │ │ ├── IntentTest.ts │ │ ├── MatrixBridgeTest.ts │ │ └── UnstableAppserviceApisTest.ts │ ├── b64Test.ts │ ├── encryption/ │ │ ├── CryptoClientTest.ts │ │ ├── RoomTrackerTest.ts │ │ └── decoratorsTest.ts │ ├── helpers/ │ │ ├── MatrixEntityTest.ts │ │ ├── MatrixGlobTest.ts │ │ ├── MentionPillTest.ts │ │ ├── PermalinksTest.ts │ │ ├── ProfileCacheTest.ts │ │ ├── RichReplyTest.ts │ │ └── UnpaddedBase64Test.ts │ ├── logging/ │ │ └── LogServiceTest.ts │ ├── metrics/ │ │ ├── MetricsTest.ts │ │ └── decoratorsTest.ts │ ├── mixins/ │ │ ├── AutojoinRoomsMixinTest.ts │ │ └── AutojoinUpgradedRoomsMixinTest.ts │ ├── models/ │ │ ├── MatrixProfileTest.ts │ │ ├── PresenceTest.ts │ │ ├── SpacesTest.ts │ │ └── events/ │ │ ├── AliasesEventTest.ts │ │ ├── CanonicalAliasEventTest.ts │ │ ├── CreateEventTest.ts │ │ ├── EncryptedRoomEventTest.ts │ │ ├── EncryptionEventTest.ts │ │ ├── EventTest.ts │ │ ├── JoinRulesEventTest.ts │ │ ├── MembershipEventTest.ts │ │ ├── MessageEventTest.ts │ │ ├── PinnedEventsEventTest.ts │ │ ├── PowerLevelsEventTest.ts │ │ ├── RedactionEventTest.ts │ │ ├── RoomAvatarEventTest.ts │ │ ├── RoomNameEventTest.ts │ │ ├── RoomTopicEventTest.ts │ │ ├── SpaceChildEventTest.ts │ │ └── converterTest.ts │ ├── preprocessors/ │ │ └── RichRepliesPreprocessorTest.ts │ ├── requestTest.ts │ ├── simple-validationTest.ts │ ├── storage/ │ │ ├── MemoryStorageProviderTest.ts │ │ ├── SimpleFsStorageProviderTest.ts │ │ └── SimplePostgresStorageProviderTest.ts │ └── strategies/ │ ├── AppserviceJoinRoomStrategyTest.ts │ └── JoinRoomStrategyTest.ts ├── tsconfig-examples.json ├── tsconfig-release.json └── tsconfig.json
SYMBOL INDEX (771 symbols across 110 files)
FILE: src/AdminApis.ts
type WhoisInfo (line 9) | interface WhoisInfo {
type WhoisConnectionInfo (line 20) | interface WhoisConnectionInfo {
class AdminApis (line 41) | class AdminApis {
method constructor (line 42) | constructor(private client: MatrixClient) {
method synapse (line 48) | public get synapse(): SynapseAdminApis {
method whoisUser (line 57) | public whoisUser(userId: string): Promise<WhoisInfo> {
FILE: src/DMs.ts
class DMs (line 16) | class DMs {
method constructor (line 24) | public constructor(private client: MatrixClient) {
method updateFromAccountData (line 34) | private async updateFromAccountData() {
method handleInvite (line 52) | private async handleInvite(roomId: string, ev: any) {
method persistCache (line 61) | private async persistCache() {
method fixDms (line 69) | private async fixDms(userId: string) {
method update (line 100) | public async update(): Promise<void> {
method getOrCreateDm (line 116) | public async getOrCreateDm(userId: string, createFn?: (targetUserId: s...
method isDm (line 156) | public isDm(roomId: string): boolean {
FILE: src/IFilter.ts
type IFilterInfo (line 4) | interface IFilterInfo {
FILE: src/MatrixAuth.ts
class MatrixAuth (line 13) | class MatrixAuth {
method constructor (line 18) | public constructor(private homeserverUrl: string) {
method createTemplateClient (line 26) | private createTemplateClient(): MatrixClient {
method passwordRegister (line 40) | public async passwordRegister(localpart: string, password: string, dev...
method passwordLogin (line 107) | public async passwordLogin(username: string, password: string, deviceN...
FILE: src/MatrixClient.ts
constant SYNC_BACKOFF_MIN_MS (line 48) | const SYNC_BACKOFF_MIN_MS = 5000;
constant SYNC_BACKOFF_MAX_MS (line 49) | const SYNC_BACKOFF_MAX_MS = 15000;
constant VERSIONS_CACHE_MS (line 50) | const VERSIONS_CACHE_MS = 7200000;
class MatrixClient (line 55) | class MatrixClient extends EventEmitter {
method constructor (line 114) | constructor(
method storageProvider (line 155) | public get storageProvider(): IStorageProvider {
method metrics (line 162) | public get metrics(): Metrics {
method metrics (line 170) | public set metrics(metrics: Metrics) {
method unstableApis (line 180) | public get unstableApis(): UnstableApis {
method adminApis (line 188) | public get adminApis(): AdminApis {
method impersonateUserId (line 201) | public impersonateUserId(userId: string | null, deviceId?: string): vo...
method getIdentityServerClient (line 223) | public async getIdentityServerClient(identityServerName: string): Prom...
method setJoinStrategy (line 232) | public setJoinStrategy(strategy: IJoinRoomStrategy): void {
method addPreprocessor (line 241) | public addPreprocessor(preprocessor: IPreprocessor): void {
method processEvent (line 253) | private async processEvent(event: any): Promise<any> {
method getServerVersions (line 269) | public async getServerVersions(): Promise<ServerVersions> {
method doesServerSupportUnstableFeature (line 284) | public async doesServerSupportUnstableFeature(feature: string): Promis...
method doesServerSupportVersion (line 293) | public async doesServerSupportVersion(version: string): Promise<boolea...
method doesServerSupportAnyOneVersion (line 302) | public async doesServerSupportAnyOneVersion(versions: string[]): Promi...
method getOpenIDConnectToken (line 316) | public async getOpenIDConnectToken(): Promise<OpenIDConnectToken> {
method getAccountData (line 327) | public async getAccountData<T>(eventType: string): Promise<T> {
method getRoomAccountData (line 340) | public async getRoomAccountData<T>(eventType: string, roomId: string):...
method getSafeAccountData (line 355) | public async getSafeAccountData<T>(eventType: string, defaultContent: ...
method getSafeRoomAccountData (line 373) | public async getSafeRoomAccountData<T>(eventType: string, roomId: stri...
method setAccountData (line 389) | public async setAccountData(eventType: string, content: any): Promise<...
method setRoomAccountData (line 403) | public async setRoomAccountData(eventType: string, roomId: string, con...
method getPresenceStatus (line 415) | public async getPresenceStatus(): Promise<Presence> {
method getPresenceStatusFor (line 425) | public async getPresenceStatusFor(userId: string): Promise<Presence> {
method setPresenceStatus (line 436) | public async setPresenceStatus(presence: PresenceState, statusMessage:...
method getPublishedAlias (line 451) | public async getPublishedAlias(roomIdOrAlias: string): Promise<string> {
method createRoomAlias (line 474) | public createRoomAlias(alias: string, roomId: string): Promise<any> {
method deleteRoomAlias (line 487) | public deleteRoomAlias(alias: string): Promise<any> {
method setDirectoryVisibility (line 499) | public setDirectoryVisibility(roomId: string, visibility: "public" | "...
method getDirectoryVisibility (line 512) | public getDirectoryVisibility(roomId: string): Promise<"public" | "pri...
method resolveRoom (line 528) | public async resolveRoom(roomIdOrAlias: string): Promise<string> {
method lookupRoomAlias (line 540) | public lookupRoomAlias(roomAlias: string): Promise<RoomDirectoryLookup...
method inviteUser (line 556) | public inviteUser(userId, roomId) {
method kickUser (line 570) | public kickUser(userId, roomId, reason = null) {
method banUser (line 585) | public banUser(userId, roomId, reason = null) {
method unbanUser (line 599) | public unbanUser(userId, roomId) {
method getUserId (line 610) | public async getUserId(): Promise<string> {
method getWhoAmI (line 623) | public async getWhoAmI(): Promise<IWhoAmI> {
method stop (line 632) | public stop() {
method start (line 641) | public async start(filter: any = null): Promise<any> {
method startSyncInternal (line 695) | protected startSyncInternal(): Promise<any> {
method startSync (line 699) | protected async startSync(emitFn: (emitEventType: string, ...payload: ...
method doSync (line 743) | protected doSync(token: string): Promise<any> {
method processSync (line 759) | protected async processSync(raw: any, emitFn: (emitEventType: string, ...
method getEvent (line 922) | public async getEvent(roomId: string, eventId: string): Promise<any> {
method getRawEvent (line 937) | public getRawEvent(roomId: string, eventId: string): Promise<any> {
method getRoomState (line 948) | public getRoomState(roomId: string): Promise<any[]> {
method getRoomStateEvents (line 962) | public getRoomStateEvents(roomId, type, stateKey): Promise<any | any[]> {
method getRoomStateEvent (line 974) | public getRoomStateEvent(roomId, type, stateKey): Promise<any> {
method getEventContext (line 991) | public async getEventContext(roomId: string, eventId: string, limit = ...
method getUserProfile (line 1007) | public getUserProfile(userId: string): Promise<any> {
method setDisplayName (line 1017) | public async setDisplayName(displayName: string): Promise<any> {
method setAvatarUrl (line 1030) | public async setAvatarUrl(avatarUrl: string): Promise<any> {
method joinRoom (line 1044) | public async joinRoom(roomIdOrAlias: string, viaServers: string[] = []...
method getJoinedRooms (line 1064) | public getJoinedRooms(): Promise<string[]> {
method getJoinedRoomMembers (line 1074) | public getJoinedRoomMembers(roomId: string): Promise<string[]> {
method getJoinedRoomMembersWithProfiles (line 1086) | public async getJoinedRoomMembersWithProfiles(roomId: string): Promise...
method getRoomMembers (line 1104) | public getRoomMembers(roomId: string, batchToken: string = null, membe...
method getAllRoomMembers (line 1134) | public getAllRoomMembers(roomId: string, atToken?: string): Promise<Me...
method getRoomMembersByMembership (line 1147) | public getRoomMembersByMembership(roomId: string, membership: Membersh...
method getRoomMembersWithoutMembership (line 1160) | public async getRoomMembersWithoutMembership(roomId: string, notMember...
method getRoomMembersAt (line 1164) | private getRoomMembersAt(roomId: string, membership: Membership | null...
method leaveRoom (line 1182) | public leaveRoom(roomId: string, reason?: string): Promise<any> {
method forgetRoom (line 1192) | public forgetRoom(roomId: string): Promise<{}> {
method sendReadReceipt (line 1203) | public sendReadReceipt(roomId: string, eventId: string): Promise<any> {
method setTyping (line 1215) | public async setTyping(roomId: string, typing: boolean, timeout = 3000...
method replyText (line 1233) | public replyText(roomId: string, event: any, text: string, html: strin...
method replyHtmlText (line 1249) | public replyHtmlText(roomId: string, event: any, html: string): Promis...
method replyNotice (line 1265) | public replyNotice(roomId: string, event: any, text: string, html: str...
method replyHtmlNotice (line 1282) | public replyHtmlNotice(roomId: string, event: any, html: string): Prom...
method sendNotice (line 1297) | public sendNotice(roomId: string, text: string): Promise<string> {
method sendHtmlNotice (line 1312) | public sendHtmlNotice(roomId: string, html: string): Promise<string> {
method sendText (line 1329) | public sendText(roomId: string, text: string): Promise<string> {
method sendHtmlText (line 1344) | public sendHtmlText(roomId: string, html: string): Promise<string> {
method sendMessage (line 1361) | public sendMessage(roomId: string, content: any): Promise<string> {
method sendEvent (line 1374) | public async sendEvent(roomId: string, eventType: string, content: any...
method sendRawEvent (line 1390) | public async sendRawEvent(roomId: string, eventType: string, content: ...
method sendStateEvent (line 1410) | public sendStateEvent(roomId: string, type: string, stateKey: string, ...
method redactEvent (line 1428) | public redactEvent(roomId: string, eventId: string, reason: string | n...
method createRoom (line 1444) | public createRoom(properties: RoomCreateOptions = {}): Promise<string> {
method userHasPowerLevelFor (line 1459) | public async userHasPowerLevelFor(userId: string, roomId: string, even...
method userHasPowerLevelForAction (line 1487) | public async userHasPowerLevelForAction(userId: string, roomId: string...
method calculatePowerLevelChangeBoundsOn (line 1527) | public async calculatePowerLevelChangeBoundsOn(targetUserId: string, r...
method setUserPowerLevel (line 1565) | public async setUserPowerLevel(userId: string, roomId: string, newLeve...
method mxcToHttp (line 1577) | public mxcToHttp(mxc: string): string {
method mxcToHttpThumbnail (line 1593) | public mxcToHttpThumbnail(mxc: string, width: number, height: number, ...
method uploadContent (line 1608) | public uploadContent(data: Buffer, contentType = "application/octet-st...
method downloadContent (line 1623) | public async downloadContent(mxcUrl: string, allowRemote = true): Prom...
method uploadContentFromUrl (line 1645) | public uploadContentFromUrl(url: string): Promise<string> {
method getRoomUpgradeHistory (line 1684) | public async getRoomUpgradeHistory(roomId: string): Promise<{ previous...
method createSpace (line 1794) | public async createSpace(opts: SpaceCreateOptions): Promise<Space> {
method getSpace (line 1850) | public async getSpace(roomIdOrAlias: string): Promise<Space> {
method uploadDeviceOneTimeKeys (line 1866) | public async uploadDeviceOneTimeKeys(keys: OTKs): Promise<OTKCounts> {
method checkOneTimeKeyCounts (line 1878) | public async checkOneTimeKeyCounts(): Promise<OTKCounts> {
method uploadFallbackKey (line 1891) | public async uploadFallbackKey(fallbackKey: FallbackKey): Promise<OTKC...
method getUserDevices (line 1916) | public async getUserDevices(userIds: string[], federationTimeoutMs = 1...
method getOwnDevices (line 1933) | public async getOwnDevices(): Promise<OwnUserDevice[]> {
method claimOneTimeKeys (line 1951) | public async claimOneTimeKeys(userDeviceMap: Record<string, Record<str...
method sendToDevices (line 1966) | public async sendToDevices(type: string, messages: Record<string, Reco...
method getRelationsForEvent (line 1982) | public async getRelationsForEvent(roomId: string, eventId: string, rel...
method doRequest (line 2007) | public doRequest(method, endpoint, qs = null, body = null, timeout = 6...
type RoomDirectoryLookupResponse (line 2024) | interface RoomDirectoryLookupResponse {
type RoomReference (line 2029) | interface RoomReference {
FILE: src/PantalaimonClient.ts
constant ACCESS_TOKEN_STORAGE_KEY (line 22) | const ACCESS_TOKEN_STORAGE_KEY = "pantalaimon_access_token";
class PantalaimonClient (line 30) | class PantalaimonClient {
method constructor (line 37) | public constructor(private homeserverUrl: string, private storageProvi...
method createClientWithCredentials (line 48) | public async createClientWithCredentials(username: string, password: s...
FILE: src/SynapseAdminApis.ts
type SynapseUser (line 8) | interface SynapseUser {
type SynapseUserProperties (line 50) | interface SynapseUserProperties extends SynapseUser {
type SynapseUserListing (line 61) | interface SynapseUserListing {
type SynapseUserList (line 107) | interface SynapseUserList {
type SynapseRegistrationToken (line 128) | interface SynapseRegistrationToken {
type SynapseRegistrationTokenUpdateOptions (line 136) | interface SynapseRegistrationTokenUpdateOptions {
type SynapseRegistrationTokenOptions (line 153) | interface SynapseRegistrationTokenOptions extends SynapseRegistrationTok...
type SynapseRoomListing (line 171) | interface SynapseRoomListing {
type SynapseRoomList (line 192) | interface SynapseRoomList {
type SynapseRoomProperty (line 205) | enum SynapseRoomProperty {
type SynapseListUserOptions (line 221) | interface SynapseListUserOptions {
class SynapseAdminApis (line 260) | class SynapseAdminApis {
method constructor (line 261) | constructor(private client: MatrixClient) {
method getUser (line 269) | public async getUser(userId: string): Promise<SynapseUser> {
method upsertUser (line 282) | public async upsertUser(userId: string, opts: SynapseUserProperties = ...
method listUsers (line 298) | public async listUsers(from?: string, limit?: number, name?: string, g...
method listAllUsers (line 319) | public async* listAllUsers(options: SynapseListUserOptions = {}): Asyn...
method isAdmin (line 342) | public async isAdmin(userId: string): Promise<boolean> {
method isSelfAdmin (line 352) | public async isSelfAdmin(): Promise<boolean> {
method listRooms (line 372) | public async listRooms(searchTerm?: string, from?: string, limit?: num...
method getRoomState (line 391) | public async getRoomState(roomId: string): Promise<any[]> {
method deleteRoom (line 401) | public async deleteRoom(roomId: string): Promise<void> {
method getDeleteRoomState (line 410) | public async getDeleteRoomState(roomId: string): Promise<any[]> {
method listRegistrationTokens (line 423) | public async listRegistrationTokens(valid?: boolean): Promise<SynapseR...
method getRegistrationToken (line 433) | public async getRegistrationToken(token: string): Promise<SynapseRegis...
method createRegistrationToken (line 449) | public async createRegistrationToken(options: SynapseRegistrationToken...
method updateRegistrationToken (line 459) | public async updateRegistrationToken(token: string, options: SynapseRe...
method deleteRegistrationToken (line 468) | public async deleteRegistrationToken(token: string): Promise<void> {
method makeRoomAdmin (line 479) | public async makeRoomAdmin(roomId: string, userId?: string): Promise<v...
FILE: src/SynchronousMatrixClient.ts
method constructor (line 18) | protected constructor(baseClient: MatrixClient) {
method handleEvent (line 22) | private async handleEvent(emitType: string, arg1: any, arg2: any): Promi...
method startSyncInternal (line 37) | protected startSyncInternal(): Promise<any> {
method onAccountData (line 46) | protected onAccountData(event: any): Promise<any> {
method onRoomAccountData (line 56) | protected onRoomAccountData(roomId: string, event: any): Promise<any> {
method onRoomLeave (line 66) | protected onRoomLeave(roomId: string, event: any): Promise<any> {
method onRoomInvite (line 76) | protected onRoomInvite(roomId: string, event: any): Promise<any> {
method onRoomJoin (line 86) | protected onRoomJoin(roomId: string, event: any): Promise<any> {
method onRoomMessage (line 96) | protected onRoomMessage(roomId: string, event: any): Promise<any> {
method onRoomArchived (line 106) | protected onRoomArchived(roomId: string, event: any): Promise<any> {
method onRoomUpgraded (line 116) | protected onRoomUpgraded(roomId: string, event: any): Promise<any> {
method onRoomEvent (line 126) | protected onRoomEvent(roomId: string, event: any): Promise<any> {
FILE: src/UnstableApis.ts
class UnstableApis (line 8) | class UnstableApis {
method constructor (line 9) | constructor(private client: MatrixClient) {
method getRoomAliases (line 18) | public async getRoomAliases(roomId: string): Promise<string[]> {
method addReactionToEvent (line 30) | public async addReactionToEvent(roomId: string, eventId: string, emoji...
method getRelationsForEvent (line 49) | public async getRelationsForEvent(roomId: string, eventId: string, rel...
method getMediaInfo (line 65) | public async getMediaInfo(mxcUrl: string): Promise<MSC2380MediaInfo> {
FILE: src/appservice/Appservice.ts
constant EDU_ANNOTATION_KEY (line 29) | const EDU_ANNOTATION_KEY = "io.t2bot.sdk.bot.type";
type EduAnnotation (line 31) | enum EduAnnotation {
type IAppserviceRegistration (line 41) | interface IAppserviceRegistration {
type IAppserviceOptions (line 145) | interface IAppserviceOptions {
class Appservice (line 220) | class Appservice extends EventEmitter {
method constructor (line 245) | constructor(private options: IAppserviceOptions) {
method expressAppInstance (line 337) | public get expressAppInstance() {
method bridge (line 344) | public get bridge(): MatrixBridge {
method botUserId (line 351) | public get botUserId(): string {
method botIntent (line 359) | public get botIntent(): Intent {
method botClient (line 369) | public get botClient(): MatrixClient {
method begin (line 377) | public begin(): Promise<void> {
method stop (line 399) | public stop(): void {
method getIntent (line 410) | public getIntent(localpart: string): Intent {
method getUserId (line 420) | public getUserId(localpart: string): string {
method getIntentForSuffix (line 430) | public getIntentForSuffix(suffix: string): Intent {
method getUserIdForSuffix (line 440) | public getUserIdForSuffix(suffix: string): string {
method getIntentForUserId (line 452) | public getIntentForUserId(userId: string): Intent {
method getSuffixForUserId (line 473) | public getSuffixForUserId(userId: string): string {
method isNamespacedUser (line 496) | public isNamespacedUser(userId: string): boolean {
method getAlias (line 509) | public getAlias(localpart: string): string {
method getAliasForSuffix (line 519) | public getAliasForSuffix(suffix: string): string {
method getAliasLocalpartForSuffix (line 532) | public getAliasLocalpartForSuffix(suffix: string): string {
method getSuffixForAlias (line 545) | public getSuffixForAlias(alias: string): string {
method isNamespacedAlias (line 568) | public isNamespacedAlias(alias: string): boolean {
method addPreprocessor (line 580) | public addPreprocessor(preprocessor: IPreprocessor): void {
method setRoomDirectoryVisibility (line 599) | public setRoomDirectoryVisibility(networkId: string, roomId: string, v...
method processEphemeralEvent (line 607) | private async processEphemeralEvent(event: any): Promise<any> {
method processEvent (line 618) | private async processEvent(event: any): Promise<any> {
method processMembershipEvent (line 629) | private async processMembershipEvent(event: any): Promise<void> {
method isAuthed (line 651) | private isAuthed(req: any): boolean {
method onTransaction (line 662) | private async onTransaction(req: express.Request, res: express.Respons...
method onUser (line 907) | private async onUser(req: express.Request, res: express.Response): Pro...
method onRoomAlias (line 928) | private async onRoomAlias(req: express.Request, res: express.Response)...
method onKeysClaim (line 951) | private async onKeysClaim(req: express.Request, res: express.Response)...
method onKeysQuery (line 985) | private async onKeysQuery(req: express.Request, res: express.Response)...
method onThirdpartyProtocol (line 1021) | private onThirdpartyProtocol(req: express.Request, res: express.Respon...
method handleThirdpartyObject (line 1040) | private handleThirdpartyObject(req: express.Request, res: express.Resp...
method onThirdpartyUser (line 1082) | private onThirdpartyUser(req: express.Request, res: express.Response) {
method onThirdpartyLocation (line 1086) | private onThirdpartyLocation(req: express.Request, res: express.Respon...
FILE: src/appservice/Intent.ts
class Intent (line 24) | class Intent {
method constructor (line 46) | constructor(private options: IAppserviceOptions, private impersonateUs...
method makeClient (line 53) | private makeClient(withCrypto: boolean, accessToken?: string) {
method userId (line 79) | public get userId(): string {
method underlyingClient (line 86) | public get underlyingClient(): MatrixClient {
method unstableApis (line 95) | public get unstableApis(): UnstableAppserviceApis {
method enableEncryption (line 104) | public async enableEncryption(): Promise<void> {
method getJoinedRooms (line 195) | public async getJoinedRooms(): Promise<string[]> {
method leaveRoom (line 208) | public async leaveRoom(roomId: string, reason?: string): Promise<any> {
method joinRoom (line 222) | public async joinRoom(roomIdOrAlias: string): Promise<string> {
method sendText (line 239) | public async sendText(roomId: string, body: string, msgtype: "m.text" ...
method sendEvent (line 250) | public async sendEvent(roomId: string, content: any): Promise<string> {
method ensureRegisteredAndJoined (line 261) | public async ensureRegisteredAndJoined(roomId: string) {
method ensureJoined (line 272) | public async ensureJoined(roomId: string) {
method refreshJoinedRooms (line 296) | public async refreshJoinedRooms(): Promise<string[]> {
method ensureRegistered (line 307) | public async ensureRegistered(deviceId?: string) {
FILE: src/appservice/MatrixBridge.ts
constant REMOTE_USER_INFO_ACCOUNT_DATA_EVENT_TYPE (line 4) | const REMOTE_USER_INFO_ACCOUNT_DATA_EVENT_TYPE = "io.t2bot.sdk.bot.remot...
constant REMOTE_ROOM_INFO_ACCOUNT_DATA_EVENT_TYPE (line 5) | const REMOTE_ROOM_INFO_ACCOUNT_DATA_EVENT_TYPE = "io.t2bot.sdk.bot.remot...
constant REMOTE_USER_MAP_ACCOUNT_DATA_EVENT_TYPE_PREFIX (line 6) | const REMOTE_USER_MAP_ACCOUNT_DATA_EVENT_TYPE_PREFIX = "io.t2bot.sdk.bot...
constant REMOTE_ROOM_MAP_ACCOUNT_DATA_EVENT_TYPE_PREFIX (line 7) | const REMOTE_ROOM_MAP_ACCOUNT_DATA_EVENT_TYPE_PREFIX = "io.t2bot.sdk.bot...
type IRemoteRoomInfo (line 13) | interface IRemoteRoomInfo {
type IRemoteUserInfo (line 24) | interface IRemoteUserInfo {
class MatrixBridge (line 44) | class MatrixBridge {
method constructor (line 45) | constructor(private appservice: Appservice) {
method getRemoteUserInfo (line 53) | public async getRemoteUserInfo<T extends IRemoteUserInfo>(userIntent: ...
method setRemoteUserInfo (line 65) | public async setRemoteUserInfo<T extends IRemoteUserInfo>(userIntent: ...
method getRemoteRoomInfo (line 76) | public async getRemoteRoomInfo<T extends IRemoteRoomInfo>(matrixRoomId...
method setRemoteRoomInfo (line 91) | public async setRemoteRoomInfo<T extends IRemoteRoomInfo>(matrixRoomId...
method getMatrixRoomIdForRemote (line 105) | public async getMatrixRoomIdForRemote(remoteRoomId: string): Promise<s...
method getIntentForRemote (line 118) | public async getIntentForRemote(remoteUserId: string): Promise<Intent> {
method updateRemoteUserMapping (line 126) | private async updateRemoteUserMapping(matrixUserId: string, remoteUser...
method updateRemoteRoomMapping (line 133) | private async updateRemoteRoomMapping(matrixRoomId: string, remoteRoom...
FILE: src/appservice/UnstableAppserviceApis.ts
class UnstableAppserviceApis (line 8) | class UnstableAppserviceApis {
method constructor (line 11) | constructor(private client: MatrixClient) {
method sendHistoricalEventBatch (line 24) | public async sendHistoricalEventBatch(roomId: string, prevEventId: str...
method sendEventWithTimestamp (line 42) | public async sendEventWithTimestamp(roomId: string, eventType: string,...
method sendStateEventWithTimestamp (line 58) | public async sendStateEventWithTimestamp(roomId: string, type: string,...
FILE: src/appservice/http_responses.ts
type IApplicationServiceProtocol (line 6) | interface IApplicationServiceProtocol {
type IFieldType (line 14) | interface IFieldType {
type IProtocolInstance (line 19) | interface IProtocolInstance {
type MSC3983KeyClaimResponse (line 32) | interface MSC3983KeyClaimResponse {
type MSC3984KeyQueryResponse (line 54) | interface MSC3984KeyQueryResponse {
FILE: src/b64.ts
function encodeBase64 (line 7) | function encodeBase64(b: ArrayBuffer | Uint8Array): string {
function encodeUnpaddedBase64 (line 21) | function encodeUnpaddedBase64(b: ArrayBuffer | Uint8Array): string {
function encodeUnpaddedUrlSafeBase64 (line 31) | function encodeUnpaddedUrlSafeBase64(b: ArrayBuffer | Uint8Array): string {
function decodeBase64 (line 41) | function decodeBase64(s: string): Uint8Array {
function decodeUnpaddedBase64 (line 51) | function decodeUnpaddedBase64(s: string): Uint8Array {
function decodeUnpaddedUrlSafeBase64 (line 61) | function decodeUnpaddedUrlSafeBase64(s: string): Uint8Array {
FILE: src/e2ee/CryptoClient.ts
class CryptoClient (line 35) | class CryptoClient {
method constructor (line 43) | public constructor(private client: MatrixClient) {
method storage (line 47) | private get storage(): RustSdkCryptoStorageProvider {
method clientDeviceId (line 54) | public get clientDeviceId(): string {
method clientDeviceEd25519 (line 61) | public get clientDeviceEd25519(): string {
method isReady (line 69) | public get isReady(): boolean {
method prepare (line 77) | public async prepare(roomIds: string[]) {
method onRoomEvent (line 120) | public async onRoomEvent(roomId: string, event: any) {
method onRoomJoin (line 138) | public async onRoomJoin(roomId: string) {
method isRoomEncrypted (line 152) | public async isRoomEncrypted(roomId: string): Promise<boolean> {
method updateSyncData (line 167) | public async updateSyncData(
method sign (line 198) | public async sign(obj: object): Promise<Signatures> {
method encryptRoomEvent (line 233) | public async encryptRoomEvent(roomId: string, eventType: string, conte...
method decryptRoomEvent (line 253) | public async decryptRoomEvent(event: EncryptedRoomEvent, roomId: strin...
method encryptMedia (line 272) | public async encryptMedia(file: Buffer): Promise<{ buffer: Buffer, fil...
method decryptMedia (line 287) | public async decryptMedia(file: EncryptedFile): Promise<Buffer> {
FILE: src/e2ee/ICryptoRoomInformation.ts
type ICryptoRoomInformation (line 7) | interface ICryptoRoomInformation extends Partial<EncryptionEventContent> {
FILE: src/e2ee/RoomTracker.ts
class RoomTracker (line 10) | class RoomTracker {
method constructor (line 11) | public constructor(private client: MatrixClient) {
method onRoomJoin (line 19) | public async onRoomJoin(roomId: string) {
method onRoomEvent (line 29) | public async onRoomEvent(roomId: string, event: any) {
method prepare (line 40) | public async prepare(roomIds: string[]) {
method queueRoomCheck (line 51) | public async queueRoomCheck(roomId: string) {
method getRoomCryptoConfig (line 88) | public async getRoomCryptoConfig(roomId: string): Promise<ICryptoRoomI...
FILE: src/e2ee/RustEngine.ts
constant SYNC_LOCK_NAME (line 24) | const SYNC_LOCK_NAME = "sync";
class RustEngine (line 29) | class RustEngine {
method constructor (line 32) | public constructor(public readonly machine: OlmMachine, private client...
method run (line 35) | public async run() {
method runOnly (line 39) | private async runOnly(...types: RequestType[]) {
method addTrackedUsers (line 69) | public async addTrackedUsers(userIds: string[]) {
method prepareEncrypt (line 81) | public async prepareEncrypt(roomId: string, roomInfo: ICryptoRoomInfor...
method processKeysClaimRequest (line 131) | private async processKeysClaimRequest(request: KeysClaimRequest) {
method processKeysUploadRequest (line 136) | private async processKeysUploadRequest(request: KeysUploadRequest) {
method processKeysQueryRequest (line 143) | private async processKeysQueryRequest(request: KeysQueryRequest) {
method processToDeviceRequest (line 148) | private async processToDeviceRequest(request: ToDeviceRequest) {
method actuallyProcessToDeviceRequest (line 153) | private async actuallyProcessToDeviceRequest(id: string, type: string,...
FILE: src/e2ee/decorators.ts
function requiresCrypto (line 8) | function requiresCrypto() {
function requiresReady (line 26) | function requiresReady() {
FILE: src/helpers/MatrixEntity.ts
class MatrixEntity (line 5) | class MatrixEntity {
method constructor (line 13) | constructor(private fullId: string) {
method localpart (line 24) | public get localpart(): string {
method domain (line 31) | public get domain(): string {
method toString (line 36) | public toString(): string {
class UserID (line 45) | class UserID extends MatrixEntity {
method constructor (line 46) | constructor(userId: string) {
class RoomAlias (line 58) | class RoomAlias extends MatrixEntity {
method constructor (line 59) | constructor(alias: string) {
FILE: src/helpers/MatrixGlob.ts
class MatrixGlob (line 8) | class MatrixGlob {
method constructor (line 18) | constructor(glob: string) {
method test (line 35) | public test(val: string): boolean {
FILE: src/helpers/MentionPill.ts
class MentionPill (line 9) | class MentionPill {
method constructor (line 10) | private constructor(private entityPermalink: string, private displayNa...
method html (line 16) | public get html(): string {
method text (line 23) | public get text(): string {
method forUser (line 34) | public static async forUser(userId: string, inRoomId: string = null, c...
method forRoom (line 66) | public static async forRoom(roomIdOrAlias: string, client: MatrixClien...
method withDisplayName (line 92) | public static withDisplayName(userId: string, displayName: string): Me...
FILE: src/helpers/Permalinks.ts
type PermalinkParts (line 6) | interface PermalinkParts {
class Permalinks (line 32) | class Permalinks {
method constructor (line 33) | private constructor() {
method encodeViaArgs (line 38) | private static encodeViaArgs(servers: string[]): string {
method forRoom (line 50) | public static forRoom(roomIdOrAlias: string, viaServers: string[] = []...
method forUser (line 59) | public static forUser(userId: string): string {
method forEvent (line 70) | public static forEvent(roomIdOrAlias: string, eventId: string, viaServ...
method parseUrl (line 79) | public static parseUrl(matrixTo: string): PermalinkParts {
FILE: src/helpers/ProfileCache.ts
type CacheKey (line 7) | type CacheKey = `${string}@${string | '<none>'}`;
class ProfileCache (line 15) | class ProfileCache {
method constructor (line 24) | constructor(maxEntries: number, maxAgeMs: number, private client: Matr...
method getCacheKey (line 31) | private getCacheKey(userId: string, roomId: string | null): CacheKey {
method watchWithClient (line 40) | public watchWithClient(client: MatrixClient) {
method watchWithAppservice (line 59) | public watchWithAppservice(appservice: Appservice, clientFn: (userId: ...
method getUserProfile (line 78) | public async getUserProfile(userId: string, roomId: string = null): Pr...
method getUserProfileWith (line 88) | private async getUserProfileWith(userId: string, roomId: string, clien...
method tryUpdateProfile (line 104) | private async tryUpdateProfile(roomId: string, memberEvent: Membership...
FILE: src/helpers/RichReply.ts
class RichReply (line 7) | class RichReply {
method constructor (line 8) | private constructor() {
method createFor (line 20) | public static createFor(roomId: string, event: any, withText: string, ...
FILE: src/helpers/UnpaddedBase64.ts
class UnpaddedBase64 (line 5) | class UnpaddedBase64 {
method constructor (line 6) | private constructor() {
method encodeBuffer (line 14) | public static encodeBuffer(buf: Buffer): string {
method encodeString (line 23) | public static encodeString(str: string): string {
method encodeBufferUrlSafe (line 32) | public static encodeBufferUrlSafe(buf: Buffer): string {
method encodeStringUrlSafe (line 41) | public static encodeStringUrlSafe(str: string): string {
FILE: src/http.ts
function doHttpRequest (line 22) | async function doHttpRequest(
function redactObjectForLogging (line 127) | function redactObjectForLogging(input: any): any {
FILE: src/identity/IdentityClient.ts
class IdentityClient (line 18) | class IdentityClient {
method constructor (line 31) | private constructor(public readonly accessToken: string, public readon...
method getAccount (line 40) | public getAccount(): Promise<IdentityServerAccount> {
method getTermsOfService (line 49) | public getTermsOfService(): Promise<Policies> {
method acceptTerms (line 61) | public acceptTerms(termsUrls: string[]): Promise<void> {
method acceptAllTerms (line 74) | public async acceptAllTerms(): Promise<void> {
method lookup (line 98) | public async lookup(identifiers: Threepid[], allowPlaintext = false): ...
method makeEmailInvite (line 150) | public async makeEmailInvite(emailAddress: string, roomId: string): Pr...
method doRequest (line 208) | public doRequest(method, endpoint, qs = null, body = null, timeout = 6...
method acquire (line 221) | public static async acquire(oidc: OpenIDConnectToken, serverUrl: strin...
FILE: src/logging/ConsoleLogger.ts
class ConsoleLogger (line 9) | class ConsoleLogger implements ILogger {
method trace (line 10) | public trace(module: string, ...messageOrObject: any[]) {
method debug (line 14) | public debug(module: string, ...messageOrObject: any[]) {
method error (line 18) | public error(module: string, ...messageOrObject: any[]) {
method info (line 22) | public info(module: string, ...messageOrObject: any[]) {
method warn (line 26) | public warn(module: string, ...messageOrObject: any[]) {
FILE: src/logging/ILogger.ts
type ILogger (line 5) | interface ILogger {
FILE: src/logging/LogService.ts
class LogLevel (line 9) | class LogLevel {
method constructor (line 35) | private constructor(private level: string, private sequence: number) {
method includes (line 38) | public includes(level: LogLevel): boolean {
method toString (line 42) | public toString(): string {
method fromString (line 46) | public static fromString(level: string, defaultLevel = LogLevel.DEBUG)...
class LogService (line 61) | class LogService {
method constructor (line 66) | private constructor() {
method level (line 72) | public static get level(): LogLevel {
method setLevel (line 80) | public static setLevel(level: LogLevel) {
method setLogger (line 88) | public static setLogger(logger: ILogger) {
method muteModule (line 96) | public static muteModule(name: string) {
method trace (line 105) | public static trace(module: string, ...messageOrObject: any[]) {
method debug (line 116) | public static debug(module: string, ...messageOrObject: any[]) {
method error (line 127) | public static error(module: string, ...messageOrObject: any[]) {
method info (line 138) | public static info(module: string, ...messageOrObject: any[]) {
method warn (line 149) | public static warn(module: string, ...messageOrObject: any[]) {
function extractRequestError (line 162) | function extractRequestError(err: Error | MatrixError): any {
FILE: src/logging/RichConsoleLogger.ts
class RichConsoleLogger (line 11) | class RichConsoleLogger implements ILogger {
method getTimestamp (line 19) | protected getTimestamp(): string {
method trace (line 24) | public trace(module: string, ...messageOrObject: any[]) {
method debug (line 33) | public debug(module: string, ...messageOrObject: any[]) {
method error (line 42) | public error(module: string, ...messageOrObject: any[]) {
method info (line 51) | public info(module: string, ...messageOrObject: any[]) {
method warn (line 60) | public warn(module: string, ...messageOrObject: any[]) {
FILE: src/metrics/IMetricListener.ts
type IMetricListener (line 13) | interface IMetricListener {
FILE: src/metrics/Metrics.ts
class Metrics (line 9) | class Metrics {
method constructor (line 20) | constructor(parent: Metrics = null) {
method registerListener (line 46) | public registerListener(listener: IMetricListener) {
method unregisterListener (line 54) | public unregisterListener(listener: IMetricListener) {
method start (line 64) | public start(metricName: string, context: IMetricContext) {
method end (line 74) | public end(metricName: string, context: IMetricContext) {
method increment (line 102) | public increment(metricName: string, context: IMetricContext, amount: ...
method decrement (line 112) | public decrement(metricName: string, context: IMetricContext, amount: ...
method reset (line 121) | public reset(metricName: string, context: IMetricContext) {
method assignUniqueContextId (line 130) | public assignUniqueContextId(context: IMetricContext): IMetricContext {
FILE: src/metrics/contexts.ts
type IMetricContext (line 9) | interface IMetricContext {
type FunctionCallContext (line 21) | interface FunctionCallContext extends IMetricContext {
type MatrixClientCallContext (line 32) | interface MatrixClientCallContext extends FunctionCallContext {
type IdentityClientCallContext (line 43) | interface IdentityClientCallContext extends FunctionCallContext {
type IntentCallContext (line 51) | interface IntentCallContext extends MatrixClientCallContext {
FILE: src/metrics/decorators.ts
function timedMatrixClientFunctionCall (line 18) | function timedMatrixClientFunctionCall() {
function timedIdentityClientFunctionCall (line 45) | function timedIdentityClientFunctionCall() {
function timedIntentFunctionCall (line 72) | function timedIntentFunctionCall() {
FILE: src/metrics/names.ts
constant METRIC_MATRIX_CLIENT_FUNCTION_CALL (line 5) | const METRIC_MATRIX_CLIENT_FUNCTION_CALL = "matrix_client_function_call";
constant METRIC_MATRIX_CLIENT_FAILED_FUNCTION_CALL (line 11) | const METRIC_MATRIX_CLIENT_FAILED_FUNCTION_CALL = "matrix_client_failed_...
constant METRIC_MATRIX_CLIENT_SUCCESSFUL_FUNCTION_CALL (line 17) | const METRIC_MATRIX_CLIENT_SUCCESSFUL_FUNCTION_CALL = "matrix_client_suc...
constant METRIC_IDENTITY_CLIENT_FUNCTION_CALL (line 23) | const METRIC_IDENTITY_CLIENT_FUNCTION_CALL = "identity_client_function_c...
constant METRIC_IDENTITY_CLIENT_FAILED_FUNCTION_CALL (line 29) | const METRIC_IDENTITY_CLIENT_FAILED_FUNCTION_CALL = "identity_client_fai...
constant METRIC_IDENTITY_CLIENT_SUCCESSFUL_FUNCTION_CALL (line 35) | const METRIC_IDENTITY_CLIENT_SUCCESSFUL_FUNCTION_CALL = "identity_client...
constant METRIC_INTENT_FUNCTION_CALL (line 41) | const METRIC_INTENT_FUNCTION_CALL = "intent_function_call";
constant METRIC_INTENT_FAILED_FUNCTION_CALL (line 47) | const METRIC_INTENT_FAILED_FUNCTION_CALL = "intent_failed_function_call";
constant METRIC_INTENT_SUCCESSFUL_FUNCTION_CALL (line 53) | const METRIC_INTENT_SUCCESSFUL_FUNCTION_CALL = "intent_successful_functi...
FILE: src/mixins/AutojoinRoomsMixin.ts
class AutojoinRoomsMixin (line 8) | class AutojoinRoomsMixin {
method setupOnClient (line 9) | public static setupOnClient(client: MatrixClient): void {
method setupOnAppservice (line 15) | public static setupOnAppservice(appservice: Appservice, conditional: (...
FILE: src/mixins/AutojoinUpgradedRoomsMixin.ts
class AutojoinUpgradedRoomsMixin (line 8) | class AutojoinUpgradedRoomsMixin {
method setupOnClient (line 9) | public static setupOnClient(client: MatrixClient): void {
method setupOnAppservice (line 20) | public static setupOnAppservice(appservice: Appservice): void {
FILE: src/models/Account.ts
type IWhoAmI (line 5) | interface IWhoAmI {
FILE: src/models/CreateRoom.ts
type RoomPreset (line 22) | type RoomPreset = "private_chat" | "trusted_private_chat" | "public_chat";
type RoomVisibility (line 30) | type RoomVisibility = "public" | "private";
type RoomCreateOptions (line 36) | interface RoomCreateOptions {
FILE: src/models/Crypto.ts
type OTKAlgorithm (line 5) | enum OTKAlgorithm {
type OTKLabel (line 14) | type OTKLabel<Algorithm extends OTKAlgorithm, ID extends string> = `${Al...
type Signatures (line 20) | interface Signatures {
type SignedCurve25519OTK (line 30) | interface SignedCurve25519OTK {
type FallbackKey (line 40) | interface FallbackKey {
type OTKs (line 49) | type OTKs =
type OTKCounts (line 57) | type OTKCounts = {
type EncryptionAlgorithm (line 68) | enum EncryptionAlgorithm {
type DeviceKeyAlgorithm (line 77) | enum DeviceKeyAlgorithm {
type DeviceKeyLabel (line 86) | type DeviceKeyLabel<Algorithm extends DeviceKeyAlgorithm, ID extends str...
type UserDevice (line 92) | interface UserDevice {
type OwnUserDevice (line 108) | interface OwnUserDevice {
type MultiUserDeviceListResponse (line 119) | interface MultiUserDeviceListResponse {
type OTKClaimResponse (line 138) | interface OTKClaimResponse {
type IOlmEncrypted (line 157) | interface IOlmEncrypted {
type IToDeviceMessage (line 172) | interface IToDeviceMessage<T = any> {
type IMegolmEncrypted (line 182) | interface IMegolmEncrypted {
FILE: src/models/EventContext.ts
type EventContext (line 3) | interface EventContext {
FILE: src/models/IdentityServerModels.ts
type IdentityServerAccount (line 5) | interface IdentityServerAccount {
type IdentityServerInvite (line 13) | interface IdentityServerInvite {
FILE: src/models/MSC2176.ts
type MSC2716BatchSendResponse (line 5) | interface MSC2716BatchSendResponse {
type MSC2716InsertionEventContent (line 26) | interface MSC2716InsertionEventContent {
type MSC2716ChunkEventContent (line 35) | interface MSC2716ChunkEventContent {
type MSC2716MarkerEventContent (line 44) | interface MSC2716MarkerEventContent {
FILE: src/models/MatrixError.ts
class MatrixError (line 5) | class MatrixError extends Error {
method constructor (line 26) | constructor(public readonly body: { errcode: string, error: string, re...
method message (line 36) | public get message() {
FILE: src/models/MatrixProfile.ts
type MatrixProfileInfo (line 7) | interface MatrixProfileInfo {
class MatrixProfile (line 23) | class MatrixProfile {
method constructor (line 29) | constructor(private userId: string, private profile: MatrixProfileInfo) {
method displayName (line 36) | public get displayName(): string {
method avatarUrl (line 45) | public get avatarUrl(): string {
method mention (line 52) | public get mention(): MentionPill {
FILE: src/models/OpenIDConnect.ts
type OpenIDConnectToken (line 5) | interface OpenIDConnectToken {
FILE: src/models/Policies.ts
type Policies (line 5) | interface Policies {
type Policy (line 15) | interface Policy {
type TranslatedPolicy (line 25) | interface TranslatedPolicy {
FILE: src/models/PowerLevelAction.ts
type PowerLevelAction (line 4) | enum PowerLevelAction {
FILE: src/models/PowerLevelBounds.ts
type PowerLevelBounds (line 4) | interface PowerLevelBounds {
FILE: src/models/Presence.ts
class Presence (line 7) | class Presence {
method constructor (line 8) | constructor(protected presence: PresenceEventContent) {
method state (line 14) | public get state(): PresenceState {
method statusMessage (line 21) | public get statusMessage(): string {
method lastActiveAgo (line 28) | public get lastActiveAgo(): number {
method currentlyActive (line 35) | public get currentlyActive(): boolean {
FILE: src/models/ServerVersions.ts
type ServerVersions (line 5) | type ServerVersions = {
FILE: src/models/Spaces.ts
type SpaceCreateOptions (line 10) | interface SpaceCreateOptions {
type SpaceChildEntityOptions (line 46) | interface SpaceChildEntityOptions {
type NewChildOpts (line 69) | type NewChildOpts = SpaceCreateOptions & SpaceChildEntityOptions;
type SpaceEntityMap (line 75) | interface SpaceEntityMap {
class Space (line 83) | class Space {
method constructor (line 84) | public constructor(public readonly roomId: string, public readonly cli...
method createChildSpace (line 92) | public async createChildSpace(opts: NewChildOpts): Promise<Space> {
method addChildSpace (line 104) | public async addChildSpace(space: Space, childOpts: SpaceChildEntityOp...
method removeChildSpace (line 113) | public async removeChildSpace(space: Space): Promise<void> {
method addChildRoom (line 123) | public async addChildRoom(roomId: string, childOpts: SpaceChildEntityO...
method removeChildRoom (line 141) | public async removeChildRoom(roomId: string): Promise<void> {
method getChildEntities (line 149) | public async getChildEntities(): Promise<SpaceEntityMap> {
method inviteUser (line 164) | public async inviteUser(userId: string) {
FILE: src/models/Threepid.ts
type Threepid (line 5) | interface Threepid {
FILE: src/models/events/AliasesEvent.ts
type AliasesEventContent (line 8) | interface AliasesEventContent {
class AliasesEvent (line 19) | class AliasesEvent extends StateEvent<AliasesEventContent> {
method constructor (line 20) | constructor(event: any) {
method forDomain (line 27) | public get forDomain(): string {
method aliases (line 34) | public get aliases(): string[] {
FILE: src/models/events/CanonicalAliasEvent.ts
type CanonicalAliasEventContent (line 8) | interface CanonicalAliasEventContent {
class CanonicalAliasEvent (line 19) | class CanonicalAliasEvent extends StateEvent<CanonicalAliasEventContent> {
method constructor (line 20) | constructor(event: any) {
method aliases (line 27) | public get aliases(): string {
FILE: src/models/events/CreateEvent.ts
type PreviousRoomInfo (line 8) | interface PreviousRoomInfo {
type CreateEventContent (line 26) | interface CreateEventContent extends Record<string, unknown> {
class CreateEvent (line 57) | class CreateEvent extends StateEvent<CreateEventContent> {
method constructor (line 58) | constructor(event: any) {
method creator (line 65) | public get creator(): string {
method version (line 72) | public get version(): string {
method federated (line 79) | public get federated(): boolean {
FILE: src/models/events/EncryptedRoomEvent.ts
type EncryptedRoomEventContent (line 9) | interface EncryptedRoomEventContent {
class EncryptedRoomEvent (line 39) | class EncryptedRoomEvent extends RoomEvent<EncryptedRoomEventContent> {
method constructor (line 40) | constructor(event: any) {
method algorithm (line 48) | public get algorithm(): EncryptionAlgorithm {
method megolmProperties (line 55) | public get megolmProperties(): IMegolmEncrypted {
FILE: src/models/events/EncryptionEvent.ts
type RoomEncryptionAlgorithm (line 8) | enum RoomEncryptionAlgorithm {
type EncryptionEventContent (line 17) | interface EncryptionEventContent {
class EncryptionEvent (line 38) | class EncryptionEvent extends StateEvent<EncryptionEventContent> {
method constructor (line 39) | constructor(event: any) {
method algorithm (line 46) | public get algorithm(): string | RoomEncryptionAlgorithm {
method rotationPeriodMs (line 53) | public get rotationPeriodMs(): number {
method rotationPeriodMessages (line 60) | public get rotationPeriodMessages(): number {
FILE: src/models/events/Event.ts
class MatrixEvent (line 5) | class MatrixEvent<T extends (Object | unknown) = unknown> {
method constructor (line 6) | constructor(protected event: any) {
method sender (line 12) | public get sender(): string {
method type (line 19) | public get type(): string {
method content (line 26) | public get content(): T {
method raw (line 35) | public get raw(): any {
FILE: src/models/events/EventKind.ts
type EventKind (line 5) | enum EventKind {
FILE: src/models/events/InvalidEventError.ts
class InvalidEventError (line 5) | class InvalidEventError extends Error {
method constructor (line 6) | constructor(message: string = null) {
class EventRedactedError (line 15) | class EventRedactedError extends InvalidEventError {
method constructor (line 16) | constructor(message: string = null) {
FILE: src/models/events/JoinRulesEvent.ts
type JoinRule (line 8) | type JoinRule = "public" | "knock" | "invite" | "private";
type JoinRulesEventContent (line 15) | interface JoinRulesEventContent {
class JoinRulesEvent (line 26) | class JoinRulesEvent extends StateEvent<JoinRulesEventContent> {
method constructor (line 27) | constructor(event: any) {
method rule (line 34) | public get rule(): JoinRule {
FILE: src/models/events/MembershipEvent.ts
type Membership (line 9) | type Membership = "join" | "leave" | "ban" | "invite";
type EffectiveMembership (line 16) | type EffectiveMembership = "join" | "leave" | "invite";
type MembershipEventContent (line 23) | interface MembershipEventContent {
class MembershipEvent (line 39) | class MembershipEvent extends StateEvent<MembershipEventContent> {
method constructor (line 40) | constructor(event: any) {
method ownMembership (line 49) | public get ownMembership(): boolean {
method membershipFor (line 56) | public get membershipFor(): string {
method membership (line 63) | public get membership(): Membership {
method effectiveMembership (line 72) | public get effectiveMembership(): EffectiveMembership {
FILE: src/models/events/MessageEvent.ts
type MessageType (line 9) | type MessageType =
type FileInfo (line 25) | interface FileInfo {
type ThumbnailInfo (line 42) | interface ThumbnailInfo {
type ThumbnailedFileInfo (line 69) | interface ThumbnailedFileInfo {
type FileWithThumbnailInfo (line 91) | interface FileWithThumbnailInfo extends FileInfo, ThumbnailedFileInfo {
type DimensionalFileInfo (line 99) | interface DimensionalFileInfo extends FileWithThumbnailInfo {
type TimedFileInfo (line 116) | interface TimedFileInfo extends FileInfo {
type VideoFileInfo (line 128) | interface VideoFileInfo extends DimensionalFileInfo, TimedFileInfo {
type AudioMessageEventContent (line 137) | interface AudioMessageEventContent extends FileMessageEventContent {
type VideoMessageEventContent (line 149) | interface VideoMessageEventContent extends FileMessageEventContent {
type ImageMessageEventContent (line 161) | interface ImageMessageEventContent extends FileMessageEventContent {
type FileMessageEventContent (line 173) | interface FileMessageEventContent extends MessageEventContent {
type EncryptedFile (line 195) | interface EncryptedFile {
type LocationMessageEventContent (line 216) | interface LocationMessageEventContent extends MessageEventContent {
type TextualMessageEventContent (line 233) | interface TextualMessageEventContent extends MessageEventContent {
type MessageEventContent (line 243) | interface MessageEventContent {
class MessageEvent (line 253) | class MessageEvent<T extends MessageEventContent> extends RoomEvent<T> {
method constructor (line 254) | constructor(event: any) {
method isRedacted (line 261) | public get isRedacted(): boolean {
method messageType (line 271) | public get messageType(): MessageType {
method textBody (line 280) | public get textBody(): string {
method externalUrl (line 289) | public get externalUrl(): string | undefined {
FILE: src/models/events/PinnedEventsEvent.ts
type PinnedEventsEventContent (line 8) | interface PinnedEventsEventContent {
class PinnedEventsEvent (line 19) | class PinnedEventsEvent extends StateEvent<PinnedEventsEventContent> {
method constructor (line 20) | constructor(event: any) {
method pinnedEventIds (line 27) | public get pinnedEventIds(): string[] {
FILE: src/models/events/PowerLevelsEvent.ts
type PowerLevelsEventContent (line 8) | interface PowerLevelsEventContent {
function defaultNum (line 65) | function defaultNum(val: number | undefined, def: number): number {
class PowerLevelsEvent (line 74) | class PowerLevelsEvent extends StateEvent<PowerLevelsEventContent> {
method constructor (line 75) | constructor(event: any) {
method banLevel (line 82) | public get banLevel(): number {
method inviteLevel (line 89) | public get inviteLevel(): number {
method kickLevel (line 96) | public get kickLevel(): number {
method redactLevel (line 103) | public get redactLevel(): number {
method notifyWholeRoomLevel (line 110) | public get notifyWholeRoomLevel(): number {
method defaultUserLevel (line 118) | public get defaultUserLevel(): number {
method defaultStateEventLevel (line 125) | public get defaultStateEventLevel(): number {
method defaultEventLevel (line 132) | public get defaultEventLevel(): number {
FILE: src/models/events/PresenceEvent.ts
type PresenceState (line 13) | type PresenceState = "online" | "offline" | "unavailable";
type PresenceEventContent (line 20) | interface PresenceEventContent {
class PresenceEvent (line 58) | class PresenceEvent extends MatrixEvent<PresenceEventContent> {
method constructor (line 59) | constructor(event: any) {
method presence (line 66) | public get presence(): PresenceState {
FILE: src/models/events/RedactionEvent.ts
type RedactionEventContent (line 8) | interface RedactionEventContent {
class RedactionEvent (line 19) | class RedactionEvent extends RoomEvent<RedactionEventContent> {
method constructor (line 20) | constructor(event: any) {
method redactsEventId (line 28) | public get redactsEventId(): string {
method redactsEventIds (line 35) | public get redactsEventIds(): string[] {
FILE: src/models/events/RoomAvatarEvent.ts
type RoomAvatarEventContent (line 9) | interface RoomAvatarEventContent {
class RoomAvatarEvent (line 25) | class RoomAvatarEvent extends StateEvent<RoomAvatarEventContent> {
method constructor (line 26) | constructor(event: any) {
method avatarUrl (line 33) | public get avatarUrl(): string {
FILE: src/models/events/RoomEvent.ts
type TypicalUnsigned (line 8) | interface TypicalUnsigned {
type RoomEventContent (line 24) | interface RoomEventContent {
class RoomEvent (line 32) | class RoomEvent<T extends (Object | unknown) = unknown> extends MatrixEv...
method constructor (line 33) | constructor(protected event: any) {
method eventId (line 40) | public get eventId(): string {
method timestamp (line 47) | public get timestamp(): number {
method unsigned (line 54) | public get unsigned(): TypicalUnsigned {
class StateEvent (line 63) | class StateEvent<T extends (Object | unknown) = unknown> extends RoomEve...
method constructor (line 64) | constructor(event: any) {
method stateKey (line 71) | public get stateKey(): string {
method previousContent (line 79) | public get previousContent(): T {
FILE: src/models/events/RoomNameEvent.ts
type RoomNameEventContent (line 8) | interface RoomNameEventContent {
class RoomNameEvent (line 16) | class RoomNameEvent extends StateEvent<RoomNameEventContent> {
method constructor (line 17) | constructor(event: any) {
method name (line 24) | public get name(): string {
FILE: src/models/events/RoomTopicEvent.ts
type RoomTopicEventContent (line 8) | interface RoomTopicEventContent {
class RoomTopicEvent (line 16) | class RoomTopicEvent extends StateEvent<RoomTopicEventContent> {
method constructor (line 17) | constructor(event: any) {
method topic (line 24) | public get topic(): string {
FILE: src/models/events/SpaceChildEvent.ts
type SpaceChildEventContent (line 8) | interface SpaceChildEventContent {
class SpaceChildEvent (line 31) | class SpaceChildEvent extends StateEvent<SpaceChildEventContent> {
method constructor (line 32) | constructor(event: any) {
method entityRoomId (line 39) | public get entityRoomId(): string {
method isActiveChild (line 47) | public get isActiveChild(): boolean {
method viaServers (line 56) | public get viaServers(): string[] {
method order (line 64) | public get order(): string {
method suggested (line 71) | public get suggested(): boolean {
FILE: src/models/events/converter.ts
function wrapRoomEvent (line 19) | function wrapRoomEvent(event: any): RoomEvent<any> {
FILE: src/models/unstable/MediaInfo.ts
type MSC2380MediaInfo (line 5) | interface MSC2380MediaInfo {
FILE: src/preprocessors/IPreprocessor.ts
type IPreprocessor (line 8) | interface IPreprocessor {
FILE: src/preprocessors/RichRepliesPreprocessor.ts
type IRichReplyMetadata (line 11) | interface IRichReplyMetadata {
class RichRepliesPreprocessor (line 63) | class RichRepliesPreprocessor implements IPreprocessor {
method constructor (line 70) | public constructor(private fetchRealEventContents = false) {
method getSupportedEventTypes (line 73) | public getSupportedEventTypes(): string[] {
method processEvent (line 77) | public async processEvent(event: any, client: MatrixClient, kind?: Eve...
FILE: src/request.ts
function setRequestFn (line 10) | function setRequestFn(fn) {
function getRequestFn (line 19) | function getRequestFn(): typeof origRequestFn {
FILE: src/simple-validation.ts
function validateSpaceOrderString (line 10) | function validateSpaceOrderString(order: string): true {
FILE: src/storage/IAppserviceStorageProvider.ts
type IAppserviceStorageProvider (line 8) | interface IAppserviceStorageProvider {
type IAppserviceCryptoStorageProvider (line 47) | interface IAppserviceCryptoStorageProvider {
FILE: src/storage/ICryptoStorageProvider.ts
type ICryptoStorageProvider (line 7) | interface ICryptoStorageProvider {
FILE: src/storage/IStorageProvider.ts
type IStorageProvider (line 7) | interface IStorageProvider {
FILE: src/storage/MemoryStorageProvider.ts
class MemoryStorageProvider (line 9) | class MemoryStorageProvider implements IStorageProvider, IAppserviceStor...
method setSyncToken (line 17) | setSyncToken(token: string | null): void {
method getSyncToken (line 21) | getSyncToken(): string | null {
method setFilter (line 25) | setFilter(filter: IFilterInfo): void {
method getFilter (line 29) | getFilter(): IFilterInfo {
method addRegisteredUser (line 33) | addRegisteredUser(userId: string) {
method isUserRegistered (line 39) | isUserRegistered(userId: string): boolean {
method isTransactionCompleted (line 43) | isTransactionCompleted(transactionId: string): boolean {
method setTransactionCompleted (line 47) | setTransactionCompleted(transactionId: string) {
method readValue (line 51) | readValue(key: string): string | null | undefined {
method storeValue (line 55) | storeValue(key: string, value: string): void {
method storageForUser (line 59) | storageForUser(userId: string): IStorageProvider {
FILE: src/storage/RustSdkCryptoStorageProvider.ts
class RustSdkCryptoStorageProvider (line 19) | class RustSdkCryptoStorageProvider implements ICryptoStorageProvider {
method constructor (line 27) | public constructor(
method getDeviceId (line 43) | public async getDeviceId(): Promise<string> {
method setDeviceId (line 47) | public async setDeviceId(deviceId: string): Promise<void> {
method getRoom (line 51) | public async getRoom(roomId: string): Promise<ICryptoRoomInformation> {
method storeRoom (line 56) | public async storeRoom(roomId: string, config: ICryptoRoomInformation)...
class RustSdkAppserviceCryptoStorageProvider (line 66) | class RustSdkAppserviceCryptoStorageProvider extends RustSdkCryptoStorag...
method constructor (line 72) | public constructor(private baseStoragePath: string, storageType: RustS...
method storageForUser (line 76) | public storageForUser(userId: string): ICryptoStorageProvider {
FILE: src/storage/SimpleFsStorageProvider.ts
class SimpleFsStorageProvider (line 15) | class SimpleFsStorageProvider implements IStorageProvider, IAppserviceSt...
method constructor (line 25) | constructor(filename: string, private trackTransactionsInMemory = true...
method setSyncToken (line 40) | setSyncToken(token: string | null): void {
method getSyncToken (line 44) | getSyncToken(): string | null {
method setFilter (line 48) | setFilter(filter: IFilterInfo): void {
method getFilter (line 52) | getFilter(): IFilterInfo {
method addRegisteredUser (line 56) | addRegisteredUser(userId: string) {
method isUserRegistered (line 64) | isUserRegistered(userId: string): boolean {
method isTransactionCompleted (line 69) | isTransactionCompleted(transactionId: string): boolean {
method setTransactionCompleted (line 78) | setTransactionCompleted(transactionId: string) {
method readValue (line 96) | readValue(key: string): string | null | undefined {
method storeValue (line 100) | storeValue(key: string, value: string): void {
method storageForUser (line 106) | storageForUser(userId: string): IStorageProvider {
class NamespacedFsProvider (line 115) | class NamespacedFsProvider implements IStorageProvider {
method constructor (line 116) | constructor(private prefix: string, private parent: SimpleFsStoragePro...
method setFilter (line 119) | setFilter(filter: IFilterInfo): Promise<any> | void {
method getFilter (line 123) | getFilter(): IFilterInfo | Promise<IFilterInfo> {
method setSyncToken (line 127) | setSyncToken(token: string | null): Promise<any> | void {
method getSyncToken (line 131) | getSyncToken(): string | Promise<string | null> | null {
method readValue (line 135) | readValue(key: string): string | Promise<string | null | undefined> | ...
method storeValue (line 139) | storeValue(key: string, value: string): Promise<any> | void {
FILE: src/storage/SimplePostgresStorageProvider.ts
class SimplePostgresStorageProvider (line 11) | class SimplePostgresStorageProvider implements IStorageProvider, IAppser...
method constructor (line 22) | constructor(connectionString: string, private trackTransactionsInMemor...
method setSyncToken (line 41) | public async setSyncToken(token: string | null): Promise<any> {
method getSyncToken (line 49) | public async getSyncToken(): Promise<string | null> {
method setFilter (line 56) | public async setFilter(filter: IFilterInfo): Promise<any> {
method getFilter (line 65) | public async getFilter(): Promise<IFilterInfo> {
method addRegisteredUser (line 73) | public async addRegisteredUser(userId: string): Promise<any> {
method isUserRegistered (line 81) | public async isUserRegistered(userId: string): Promise<boolean> {
method setTransactionCompleted (line 88) | public async setTransactionCompleted(transactionId: string): Promise<a...
method isTransactionCompleted (line 106) | public async isTransactionCompleted(transactionId: string): Promise<bo...
method readValue (line 117) | public async readValue(key: string): Promise<string | null | undefined> {
method storeValue (line 124) | public async storeValue(key: string, value: string): Promise<void> {
method storageForUser (line 132) | public storageForUser(userId: string): IStorageProvider {
class NamespacedPostgresProvider (line 141) | class NamespacedPostgresProvider implements IStorageProvider {
method constructor (line 142) | constructor(private prefix: string, private parent: SimplePostgresStor...
method setFilter (line 145) | public setFilter(filter: IFilterInfo): Promise<any> | void {
method getFilter (line 149) | public async getFilter(): Promise<IFilterInfo> {
method setSyncToken (line 153) | public setSyncToken(token: string | null): Promise<any> | void {
method getSyncToken (line 157) | public async getSyncToken(): Promise<string> {
method storeValue (line 161) | public storeValue(key: string, value: string): Promise<any> | void {
method readValue (line 165) | public readValue(key: string): string | Promise<string | null | undefi...
FILE: src/strategies/AppserviceJoinRoomStrategy.ts
class AppserviceJoinRoomStrategy (line 10) | class AppserviceJoinRoomStrategy implements IJoinRoomStrategy {
method constructor (line 11) | constructor(private underlyingStrategy: IJoinRoomStrategy, private app...
method joinRoom (line 14) | public async joinRoom(roomIdOrAlias: string, userId: string, apiCall: ...
FILE: src/strategies/JoinRoomStrategy.ts
type IJoinRoomStrategy (line 3) | interface IJoinRoomStrategy {
class SimpleRetryJoinStrategy (line 11) | class SimpleRetryJoinStrategy implements IJoinRoomStrategy {
method joinRoom (line 21) | public joinRoom(roomIdOrAlias: string, userId: string, apiCall: (targe...
function waitPromise (line 41) | function waitPromise(interval: number): Promise<any> {
FILE: test/AdminApisTest.ts
function createTestAdminClient (line 6) | function createTestAdminClient(storage: IStorageProvider = null): { clie...
FILE: test/IdentityClientTest.ts
function createTestIdentityClient (line 7) | async function createTestIdentityClient(): Promise<{ client: IdentityCli...
FILE: test/MatrixAuthTest.ts
function createTestAuth (line 6) | function createTestAuth(): { auth: MatrixAuth, http: HttpBackend, hsUrl:...
FILE: test/MatrixClientTest.ts
type ProcessSyncClient (line 1403) | interface ProcessSyncClient {
FILE: test/SynapseAdminApisTest.ts
function createTestSynapseAdminClient (line 18) | function createTestSynapseAdminClient(
FILE: test/SynchronousMatrixClientTest.ts
class TestSyncMatrixClient (line 6) | class TestSyncMatrixClient extends SynchronousMatrixClient {
method constructor (line 7) | constructor(client: MatrixClient) {
method doProcessSync (line 11) | public async doProcessSync(raw: any) {
function createSyncTestClient (line 18) | function createSyncTestClient(storage: IStorageProvider = null): { clien...
type ProcessSyncClient (line 30) | interface ProcessSyncClient {
FILE: test/TestUtils.ts
constant TEST_DEVICE_ID (line 7) | const TEST_DEVICE_ID = "TEST_DEVICE";
function expectArrayEquals (line 9) | function expectArrayEquals(expected: any[], actual: any[]) {
type Constructor (line 18) | type Constructor<T> = { new(...args: any[]): T };
function expectInstanceOf (line 20) | function expectInstanceOf<T>(expected: Constructor<T>, actual: any): boo...
function testDelay (line 24) | function testDelay(ms: number): Promise<any> {
function createTestClient (line 30) | function createTestClient(
constant CRYPTO_STORE_TYPES (line 50) | const CRYPTO_STORE_TYPES = [StoreType.Sqlite];
function testCryptoStores (line 52) | async function testCryptoStores(fn: (StoreType) => Promise<void>): Promi...
FILE: test/UnstableApisTest.ts
function createTestUnstableClient (line 6) | function createTestUnstableClient(
FILE: test/appservice/AppserviceTest.ts
function beginAppserviceWithProtocols (line 8) | async function beginAppserviceWithProtocols(protocols: string[]) {
function verifyAuth (line 816) | async function verifyAuth(method: string, route: string) {
function doCall (line 948) | async function doCall(route: string, opts: any = {}, err: any) {
function doCall (line 1023) | async function doCall(route: string, opts: any = {}) {
function doCall (line 1089) | async function doCall(route: string, opts: any = {}) {
function doCall (line 1164) | async function doCall(route: string, spyCallback: () => void) {
function checkBothPaths (line 1180) | async function checkBothPaths(spyCallback: () => void) {
function doCall (line 1362) | async function doCall(route: string, opts: any = {}) {
function doCall (line 1437) | async function doCall(route: string, opts: any = {}) {
function doCall (line 1512) | async function doCall(route: string, opts: any = {}) {
function doCall (line 1608) | async function doCall(route: string, opts: any = {}) {
function doCall (line 1696) | async function doCall(route: string, opts: any = {}) {
function doCall (line 1824) | async function doCall(route: string, opts: any = {}) {
function doCall (line 1919) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2004) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2357) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2433) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2516) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2595) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2677) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2759) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2829) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2899) | async function doCall(route: string, opts: any = {}) {
function doCall (line 2964) | async function doCall(route: string, opts: any = {}) {
function doCall (line 3039) | async function doCall(route: string, opts: any = {}) {
FILE: test/appservice/UnstableAppserviceApisTest.ts
function createTestUnstableClient (line 6) | function createTestUnstableClient(
FILE: test/b64Test.ts
function sb (line 10) | function sb(s: string): Buffer<ArrayBuffer> {
FILE: test/encryption/CryptoClientTest.ts
function bindNullEngine (line 7) | function bindNullEngine(http: HttpBackend) {
function copyOfTestFile (line 408) | function copyOfTestFile(): EncryptedFile {
FILE: test/encryption/RoomTrackerTest.ts
function prepareQueueSpies (line 7) | function prepareQueueSpies(
FILE: test/encryption/decoratorsTest.ts
class InterceptedClass (line 5) | class InterceptedClass {
method constructor (line 6) | constructor(private interceptedFn: (i: number) => number, public crypt...
method isReady (line 9) | public get isReady() {
method reqCryptoIntercepted (line 14) | async reqCryptoIntercepted(i: number): Promise<number> {
method reqReadyIntercepted (line 19) | async reqReadyIntercepted(i: number): Promise<number> {
FILE: test/metrics/MetricsTest.ts
function createTestMetricListener (line 5) | function createTestMetricListener(expectedName: string, expectedContext:...
FILE: test/metrics/decoratorsTest.ts
class InterceptedClass (line 10) | class InterceptedClass {
method constructor (line 13) | constructor(private metrics: Metrics, private interceptedFn: (i: numbe...
method matrixClientIntercepted (line 18) | async matrixClientIntercepted(i: number): Promise<number> {
method identityClientIntercepted (line 23) | async identityClientIntercepted(i: number): Promise<number> {
method intentIntercepted (line 28) | async intentIntercepted(i: number): Promise<number> {
FILE: test/models/events/EventTest.ts
function createMinimalEvent (line 3) | function createMinimalEvent(content: any = { hello: "world" }) {
FILE: test/storage/SimpleFsStorageProviderTest.ts
function createSimpleFsStorageProvider (line 7) | function createSimpleFsStorageProvider(inMemory = false, maxMemTransacti...
FILE: test/storage/SimplePostgresStorageProviderTest.ts
function createSimplePostgresStorageProvider (line 5) | function createSimplePostgresStorageProvider(connectionString: string, i...
Condensed preview — 184 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,325K chars).
[
{
"path": ".eslintrc.js",
"chars": 1927,
"preview": "module.exports = {\n plugins: [\n \"matrix-org\",\n ],\n extends: [\n \"plugin:matrix-org/babel\",\n ],\n"
},
{
"path": ".github/CODEOWNERS",
"chars": 12,
"preview": "* turt2live\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 577,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: 'bug'\nassignees: ''\n\n---\n\n**Describe th"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yaml",
"chars": 334,
"preview": "blank_issues_enabled: true\ncontact_links:\n - name: Community Support\n url: \"https://matrix.to/#/#bot-sdk:t2bot.io\"\n "
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 605,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: 'enhancement'\nassignees: ''\n\n---\n\n**"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 262,
"preview": "<!-- Thanks for submitting a PR! Please ensure the following requirements are met in order for us to review your PR -->\n"
},
{
"path": ".github/SECURITY.md",
"chars": 401,
"preview": "# Reporting a vulnerability\n\n**If you've found a security vulnerability, please report it by \nDMing [@travis:t2l.io](htt"
},
{
"path": ".github/workflows/docs.yml",
"chars": 433,
"preview": "name: Docs\non:\n push:\n branches:\n - main\njobs:\n build-and-deploy:\n runs-on: ubuntu-latest\n steps:\n - "
},
{
"path": ".github/workflows/static_analysis.yml",
"chars": 1592,
"preview": "name: Static Analysis\non:\n push:\n branches:\n - main\n pull_request:\njobs:\n # Global\n # ======================"
},
{
"path": ".gitignore",
"chars": 936,
"preview": ".idea/\nlib/\nexamples/storage/\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.p"
},
{
"path": ".npmignore",
"chars": 24,
"preview": "src/\n.travis.yml\n.idea/\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 3762,
"preview": "# Contributing to matrix-bot-sdk\n\nWoo! Good to see you're interested in contributing a change to the bot-sdk! Everyone i"
},
{
"path": "LICENSE",
"chars": 1078,
"preview": "MIT License\n\nCopyright (c) 2018 - 2023 Travis Ralston\n\nPermission is hereby granted, free of charge, to any person obtai"
},
{
"path": "README.md",
"chars": 1213,
"preview": "# matrix-bot-sdk\n\n[](https://www.npmjs.com/package/matrix-bot"
},
{
"path": "docs/index.md",
"chars": 807,
"preview": "[](https://www.npmjs.com/package/matrix-bot-sdk)\n\nTypeScript/"
},
{
"path": "docs/tutorials/appservice.md",
"chars": 5598,
"preview": "Application services are essentially superpowered bots with a much more capable connection to the homeserver. While bots"
},
{
"path": "docs/tutorials/bot-to-appservice.md",
"chars": 3492,
"preview": "Once a bot has outgrown the performance of a `/sync` loop, it's typically time to convert it to an appservice. This invo"
},
{
"path": "docs/tutorials/bot.md",
"chars": 5792,
"preview": "Bots are typically simple creatures which act on commands and provide utility to rooms. They work very similar to how\nno"
},
{
"path": "docs/tutorials/encryption-appservices.md",
"chars": 2014,
"preview": "Encryption for appservices is just about as easy as bots, though involves using a storage mechanism which is capable of\n"
},
{
"path": "docs/tutorials/encryption-bots.md",
"chars": 2262,
"preview": "Setting up encryption for a bot is easy: simply provide a crypto storage provider in addition to your other storage\nprov"
},
{
"path": "docs/tutorials/encryption.md",
"chars": 687,
"preview": "Matrix supports end-to-end encryption between users in encrypted rooms. Not all rooms are encrypted, and most bots and\nb"
},
{
"path": "docs/tutorials/index.json",
"chars": 639,
"preview": "{\n \"bot\": {\n \"title\": \"Bot usage\",\n \"order\": 1\n },\n \"appservice\": {\n \"title\": \"Appservice (bridge) usage\",\n "
},
{
"path": "docs/tutorials/room-upgrades.md",
"chars": 1045,
"preview": "<!-- TODO: Improve section -->\n\nWhen a room is upgraded, bots and bridges might have to relocate data to the new room. T"
},
{
"path": "eslint.config.js",
"chars": 2765,
"preview": "const {\n defineConfig,\n} = require(\"eslint/config\");\n\nconst matrixOrg = require(\"eslint-plugin-matrix-org\");\nconst gl"
},
{
"path": "examples/appservice.ts",
"chars": 3814,
"preview": "import {\n Appservice,\n AutojoinRoomsMixin,\n IAppserviceOptions,\n IAppserviceRegistration, LogService,\n Me"
},
{
"path": "examples/bot.ts",
"chars": 1660,
"preview": "import { StoreType } from \"@matrix-org/matrix-sdk-crypto-nodejs\";\n\nimport {\n AutojoinRoomsMixin,\n LogLevel,\n Lo"
},
{
"path": "examples/encryption_appservice.ts",
"chars": 5378,
"preview": "import * as fs from \"fs\";\nimport { StoreType } from \"@matrix-org/matrix-sdk-crypto-nodejs\";\n\nimport {\n Appservice,\n "
},
{
"path": "examples/encryption_bot.ts",
"chars": 4016,
"preview": "import * as fs from \"fs\";\nimport { StoreType } from \"@matrix-org/matrix-sdk-crypto-nodejs\";\n\nimport {\n EncryptionAlgo"
},
{
"path": "examples/login_register.ts",
"chars": 755,
"preview": "import { LogService, MatrixAuth } from \"../src\";\n\n// CAUTION: This logs a lot of secrets the console, including the pass"
},
{
"path": "jsdoc.json",
"chars": 422,
"preview": "{\n \"tags\": {\n \"allowUnknownTags\": true\n },\n \"plugins\": [\n \"node_modules/better-docs/category\",\n \"node_module"
},
{
"path": "package.json",
"chars": 3533,
"preview": "{\n \"name\": \"matrix-bot-sdk\",\n \"version\": \"develop\",\n \"description\": \"TypeScript/JavaScript SDK for Matrix bots and ap"
},
{
"path": "src/AdminApis.ts",
"chars": 1505,
"preview": "import { MatrixClient } from \"./MatrixClient\";\nimport { SynapseAdminApis } from \"./SynapseAdminApis\";\n\n/**\n * Whois info"
},
{
"path": "src/DMs.ts",
"chars": 6058,
"preview": "import { MatrixClient } from \"./MatrixClient\";\nimport { EncryptionAlgorithm } from \"./models/Crypto\";\nimport { LogServic"
},
{
"path": "src/IFilter.ts",
"chars": 135,
"preview": "/**\n * Sync filter information.\n */\nexport interface IFilterInfo {\n id: number;\n filter: any; // TODO: Define what"
},
{
"path": "src/MatrixAuth.ts",
"chars": 5519,
"preview": "import { MatrixClient } from \"./MatrixClient\";\n\n/**\n * Functions for interacting with Matrix prior to having an access t"
},
{
"path": "src/MatrixClient.ts",
"chars": 92160,
"preview": "import { EventEmitter } from \"events\";\nimport { htmlEncode } from \"htmlencode\";\nimport { htmlToText } from \"html-to-text"
},
{
"path": "src/PantalaimonClient.ts",
"chars": 2908,
"preview": "/**\n * A client specifically designed to interact with Pantalaimon instead of\n * a Matrix homeserver. The key part of th"
},
{
"path": "src/SynapseAdminApis.ts",
"chars": 16117,
"preview": "import { MatrixClient } from \"./MatrixClient\";\nimport { MatrixError } from \"./models/MatrixError\";\n\n/**\n * Information a"
},
{
"path": "src/SynchronousMatrixClient.ts",
"chars": 4947,
"preview": "import { MatrixClient } from \"./MatrixClient\";\n\n/**\n * A MatrixClient class that handles events in sync for the /sync lo"
},
{
"path": "src/UnstableApis.ts",
"chars": 3413,
"preview": "import { MatrixClient } from \"./MatrixClient\";\nimport { MSC2380MediaInfo } from \"./models/unstable/MediaInfo\";\n\n/**\n * U"
},
{
"path": "src/appservice/Appservice.ts",
"chars": 44166,
"preview": "import * as express from \"express\";\nimport { EventEmitter } from \"events\";\nimport * as morgan from \"morgan\";\nimport * as"
},
{
"path": "src/appservice/Intent.ts",
"chars": 14555,
"preview": "import {\n DeviceKeyAlgorithm,\n extractRequestError,\n IAppserviceCryptoStorageProvider,\n IAppserviceStoragePr"
},
{
"path": "src/appservice/MatrixBridge.ts",
"chars": 6381,
"preview": "import { Appservice } from \"./Appservice\";\nimport { Intent } from \"./Intent\";\n\nexport const REMOTE_USER_INFO_ACCOUNT_DAT"
},
{
"path": "src/appservice/UnstableAppserviceApis.ts",
"chars": 3229,
"preview": "import { MatrixClient } from \"../MatrixClient\";\nimport { MSC2716BatchSendResponse } from \"../models/MSC2176\";\n\n/**\n * Un"
},
{
"path": "src/appservice/http_responses.ts",
"chars": 2216,
"preview": "/**\n * This is the response format documented in\n * https://matrix.org/docs/spec/application_service/r0.1.2#get-matrix-a"
},
{
"path": "src/b64.ts",
"chars": 1845,
"preview": "/**\n * Encodes Base64.\n * @category Utilities\n * @param {ArrayBuffer | Uint8Array} b The buffer to encode.\n * @returns {"
},
{
"path": "src/e2ee/CryptoClient.ts",
"chars": 11541,
"preview": "import {\n DeviceId,\n OlmMachine,\n UserId,\n DeviceLists,\n RoomId,\n Attachment,\n EncryptedAttachment,"
},
{
"path": "src/e2ee/ICryptoRoomInformation.ts",
"chars": 280,
"preview": "import { EncryptionEventContent } from \"../models/events/EncryptionEvent\";\n\n/**\n * Information about a room for the purp"
},
{
"path": "src/e2ee/RoomTracker.ts",
"chars": 3330,
"preview": "import { MatrixClient } from \"../MatrixClient\";\nimport { EncryptionEventContent } from \"../models/events/EncryptionEvent"
},
{
"path": "src/e2ee/RustEngine.ts",
"chars": 6453,
"preview": "import {\n EncryptionSettings,\n KeysClaimRequest,\n OlmMachine,\n RequestType,\n RoomId,\n UserId,\n Encr"
},
{
"path": "src/e2ee/decorators.ts",
"chars": 1363,
"preview": "import { MatrixClient } from \"../MatrixClient\";\nimport { CryptoClient } from \"./CryptoClient\";\n\n/**\n * Flags a MatrixCli"
},
{
"path": "src/helpers/MatrixEntity.ts",
"chars": 1486,
"preview": "/**\n * Represents a Matrix entity\n * @category Utilities\n */\nexport class MatrixEntity {\n private entityLocalpart: st"
},
{
"path": "src/helpers/MatrixGlob.ts",
"chars": 1153,
"preview": "import * as globToRegexp from \"glob-to-regexp\";\n\n/**\n * Represents a common Matrix glob. This is commonly used\n * for se"
},
{
"path": "src/helpers/MentionPill.ts",
"chars": 3555,
"preview": "import { MatrixClient } from \"../MatrixClient\";\nimport { Permalinks } from \"./Permalinks\";\nimport { extractRequestError,"
},
{
"path": "src/helpers/Permalinks.ts",
"chars": 3263,
"preview": "/**\n * The parts of a permalink.\n * @see Permalinks\n * @category Utilities\n */\nexport interface PermalinkParts {\n /**"
},
{
"path": "src/helpers/ProfileCache.ts",
"chars": 5568,
"preview": "import * as LRU from \"lru-cache\";\n\nimport { extractRequestError, LogService, MatrixClient, MatrixProfile } from \"..\";\nim"
},
{
"path": "src/helpers/RichReply.ts",
"chars": 1843,
"preview": "import * as sanitizeHtml from \"sanitize-html\";\n\n/**\n * Helper for creating rich replies.\n * @category Utilities\n */\nexpo"
},
{
"path": "src/helpers/UnpaddedBase64.ts",
"chars": 1355,
"preview": "/**\n * Unpadded Base64 utilities for Matrix.\n * @category Utilities\n */\nexport class UnpaddedBase64 {\n private constr"
},
{
"path": "src/http.ts",
"chars": 6077,
"preview": "import { LogLevel, LogService } from \"./logging/LogService\";\nimport { getRequestFn } from \"./request\";\nimport { MatrixEr"
},
{
"path": "src/identity/IdentityClient.ts",
"chars": 10652,
"preview": "import * as crypto from \"crypto\";\n\nimport { OpenIDConnectToken } from \"../models/OpenIDConnect\";\nimport { doHttpRequest "
},
{
"path": "src/index.ts",
"chars": 4003,
"preview": "// Appservices\nexport * from \"./appservice/Appservice\";\nexport * from \"./appservice/Intent\";\nexport * from \"./appservice"
},
{
"path": "src/logging/ConsoleLogger.ts",
"chars": 846,
"preview": "import { ILogger } from \"./ILogger\";\n\n/* eslint-disable no-console */\n\n/**\n * Logs to the console in a plain format. Thi"
},
{
"path": "src/logging/ILogger.ts",
"chars": 1136,
"preview": "/**\n * Represents a logger\n * @category Logging\n */\nexport interface ILogger {\n /**\n * Logs to the INFO channel\n "
},
{
"path": "src/logging/LogService.ts",
"chars": 5104,
"preview": "import { MatrixError } from \"../models/MatrixError\";\nimport { ConsoleLogger } from \"./ConsoleLogger\";\nimport { ILogger }"
},
{
"path": "src/logging/RichConsoleLogger.ts",
"chars": 1919,
"preview": "import * as chalk from \"chalk\";\n\nimport { ILogger } from \"./ILogger\";\n\n/* eslint-disable no-console */\n\n/**\n * Prints to"
},
{
"path": "src/metrics/IMetricListener.ts",
"chars": 2379,
"preview": "import { IMetricContext } from \"./contexts\";\n\n/**\n * A simple interface for listening for Metric updates. Should be plug"
},
{
"path": "src/metrics/Metrics.ts",
"chars": 5274,
"preview": "import { IMetricListener } from \"./IMetricListener\";\nimport { IMetricContext } from \"./contexts\";\nimport { LogService } "
},
{
"path": "src/metrics/contexts.ts",
"chars": 1277,
"preview": "/**\n * Default context for all metrics.\n * @category Metrics\n */\nimport { MatrixClient } from \"../MatrixClient\";\nimport "
},
{
"path": "src/metrics/decorators.ts",
"chars": 3763,
"preview": "import {\n METRIC_IDENTITY_CLIENT_FAILED_FUNCTION_CALL,\n METRIC_IDENTITY_CLIENT_FUNCTION_CALL,\n METRIC_IDENTITY_"
},
{
"path": "src/metrics/names.ts",
"chars": 1965,
"preview": "/**\n * Time-series metric for how long a function call takes on MatrixClient. Uses a MatrixClientCallContext.\n * @catego"
},
{
"path": "src/mixins/AutojoinRoomsMixin.ts",
"chars": 910,
"preview": "import { MatrixClient } from \"../MatrixClient\";\nimport { Appservice } from \"../appservice/Appservice\";\n\n/**\n * Automatic"
},
{
"path": "src/mixins/AutojoinUpgradedRoomsMixin.ts",
"chars": 1694,
"preview": "import { MatrixClient } from \"../MatrixClient\";\nimport { Appservice } from \"../appservice/Appservice\";\n\n/**\n * Automatic"
},
{
"path": "src/models/Account.ts",
"chars": 138,
"preview": "/**\n * Response to a `/whoami` request.\n * @category Models\n */\nexport interface IWhoAmI {\n user_id: string;\n devi"
},
{
"path": "src/models/CreateRoom.ts",
"chars": 4849,
"preview": "import { PowerLevelsEventContent } from \"./events/PowerLevelsEvent\";\nimport { CreateEventContent } from \"./events/Create"
},
{
"path": "src/models/Crypto.ts",
"chars": 3975,
"preview": "/**\n * One time key algorithms.\n * @category Models\n */\nexport enum OTKAlgorithm {\n Signed = \"signed_curve25519\",\n "
},
{
"path": "src/models/EventContext.ts",
"chars": 593,
"preview": "import { RoomEvent, RoomEventContent, StateEvent } from \"./events/RoomEvent\";\n\nexport interface EventContext {\n /**\n "
},
{
"path": "src/models/IdentityServerModels.ts",
"chars": 440,
"preview": "/**\n * Information about a user on an identity server.\n * @category Models\n */\nexport interface IdentityServerAccount {\n"
},
{
"path": "src/models/MSC2176.ts",
"chars": 1210,
"preview": "/**\n * Response object for a batch send operation.\n * @category Models\n */\nexport interface MSC2716BatchSendResponse {\n "
},
{
"path": "src/models/MatrixError.ts",
"chars": 1008,
"preview": "/**\n * Represents an HTTP error from the Matrix server.\n * @category Error handling\n */\nexport class MatrixError extends"
},
{
"path": "src/models/MatrixProfile.ts",
"chars": 1497,
"preview": "import { MentionPill, UserID } from \"..\";\n\n/**\n * Profile information commonly associated with Matrix profiles\n * @categ"
},
{
"path": "src/models/OpenIDConnect.ts",
"chars": 224,
"preview": "/**\n * An OpenID Connect token from the homeserver.\n * @category Models\n */\nexport interface OpenIDConnectToken {\n ac"
},
{
"path": "src/models/Policies.ts",
"chars": 593,
"preview": "/**\n * Information about the policies (terms of service) a server may have.\n * @category Models\n */\nexport interface Pol"
},
{
"path": "src/models/PowerLevelAction.ts",
"chars": 745,
"preview": "/**\n * Actions that can be guarded by power levels.\n */\nexport enum PowerLevelAction {\n /**\n * Power level requir"
},
{
"path": "src/models/PowerLevelBounds.ts",
"chars": 480,
"preview": "/**\n * Information on the bounds of a power level change a user can apply.\n */\nexport interface PowerLevelBounds {\n /"
},
{
"path": "src/models/Presence.ts",
"chars": 913,
"preview": "import { PresenceEventContent, PresenceState } from \"./events/PresenceEvent\";\n\n/**\n * Presence information for a user.\n "
},
{
"path": "src/models/ServerVersions.ts",
"chars": 230,
"preview": "/**\n * Representation of the server's supported specification versions and unstable feature flags.\n * @category Models\n "
},
{
"path": "src/models/Spaces.ts",
"chars": 5360,
"preview": "import { MatrixClient } from \"../MatrixClient\";\nimport { UserID } from \"../helpers/MatrixEntity\";\nimport { validateSpace"
},
{
"path": "src/models/Threepid.ts",
"chars": 165,
"preview": "/**\n * A Third Party Identifier (3PID or threepid)\n * @category Models\n */\nexport interface Threepid {\n kind: \"email\""
},
{
"path": "src/models/events/AliasesEvent.ts",
"chars": 796,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * The content definition for m.room.aliases events\n * @category Matrix e"
},
{
"path": "src/models/events/CanonicalAliasEvent.ts",
"chars": 672,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * The content definition for m.room.canonical_alias events\n * @category "
},
{
"path": "src/models/events/CreateEvent.ts",
"chars": 1733,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * Information about the previous room.\n * @category Matrix event info\n *"
},
{
"path": "src/models/events/EncryptedRoomEvent.ts",
"chars": 1436,
"preview": "import { RoomEvent } from \"./RoomEvent\";\nimport { EncryptionAlgorithm, IMegolmEncrypted } from \"../Crypto\";\n\n/**\n * The "
},
{
"path": "src/models/events/EncryptionEvent.ts",
"chars": 1575,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * The kinds of room encryption algorithms allowed by the spec.\n * @categ"
},
{
"path": "src/models/events/Event.ts",
"chars": 827,
"preview": "/**\n * A Matrix event.\n * @category Matrix events\n */\nexport class MatrixEvent<T extends (Object | unknown) = unknown> {"
},
{
"path": "src/models/events/EventKind.ts",
"chars": 400,
"preview": "/**\n * Represents the different kinds of events a bot/appservice might see.\n * @category Matrix events\n */\nexport enum E"
},
{
"path": "src/models/events/InvalidEventError.ts",
"chars": 399,
"preview": "/**\n * Thrown when an event is invalid.\n * @category Matrix events\n */\nexport class InvalidEventError extends Error {\n "
},
{
"path": "src/models/events/JoinRulesEvent.ts",
"chars": 818,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * The types of join rules that are valid in Matrix.\n * @category Matrix "
},
{
"path": "src/models/events/MembershipEvent.ts",
"chars": 1969,
"preview": "import { StateEvent } from \"./RoomEvent\";\nimport { InvalidEventError } from \"./InvalidEventError\";\n\n/**\n * The types of "
},
{
"path": "src/models/events/MessageEvent.ts",
"chars": 6527,
"preview": "import { RoomEvent } from \"./RoomEvent\";\nimport { EventRedactedError } from \"./InvalidEventError\";\n\n/**\n * The types of "
},
{
"path": "src/models/events/PinnedEventsEvent.ts",
"chars": 687,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * The content definition for m.room.pinned_events events\n * @category Ma"
},
{
"path": "src/models/events/PowerLevelsEvent.ts",
"chars": 3280,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * The content definition for m.room.power_levels events\n * @category Mat"
},
{
"path": "src/models/events/PresenceEvent.ts",
"chars": 1680,
"preview": "import { MatrixEvent } from \"./Event\";\n\n/**\n * The allowed states of presence in Matrix.\n *\n * * `online`: The default s"
},
{
"path": "src/models/events/RedactionEvent.ts",
"chars": 1137,
"preview": "import { RoomEvent } from \"./RoomEvent\";\n\n/**\n * The content definition for m.room.redaction events\n * @category Matrix "
},
{
"path": "src/models/events/RoomAvatarEvent.ts",
"chars": 792,
"preview": "import { StateEvent } from \"./RoomEvent\";\nimport { DimensionalFileInfo } from \"./MessageEvent\";\n\n/**\n * The content defi"
},
{
"path": "src/models/events/RoomEvent.ts",
"chars": 1859,
"preview": "import { MatrixEvent } from \"./Event\";\n\n/**\n * The typical unsigned data found on an event.\n * @category Matrix event in"
},
{
"path": "src/models/events/RoomNameEvent.ts",
"chars": 542,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * The content definition for m.room.name events\n * @category Matrix even"
},
{
"path": "src/models/events/RoomTopicEvent.ts",
"chars": 552,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * The content definition for m.room.topic events\n * @category Matrix eve"
},
{
"path": "src/models/events/SpaceChildEvent.ts",
"chars": 1922,
"preview": "import { StateEvent } from \"./RoomEvent\";\n\n/**\n * The content definition for m.space.child events\n * @category Matrix ev"
},
{
"path": "src/models/events/_MissingEvents.md",
"chars": 740,
"preview": "## Missing event types\n\nThese are the event types that need definitions.\n\n### r0.6.0\n\n* `m.call.invite`\n* `m.call.candid"
},
{
"path": "src/models/events/converter.ts",
"chars": 1807,
"preview": "import { MembershipEvent } from \"./MembershipEvent\";\nimport { RoomEvent, StateEvent } from \"./RoomEvent\";\nimport {\n A"
},
{
"path": "src/models/unstable/MediaInfo.ts",
"chars": 394,
"preview": "/**\n * This interface implements the schema defined in [MSC2380](https://github.com/matrix-org/matrix-doc/pull/2380).\n *"
},
{
"path": "src/preprocessors/IPreprocessor.ts",
"chars": 869,
"preview": "import { MatrixClient } from \"../MatrixClient\";\nimport { EventKind } from \"..\";\n\n/**\n * Represents a preprocessor.\n * @c"
},
{
"path": "src/preprocessors/RichRepliesPreprocessor.ts",
"chars": 5928,
"preview": "import { MatrixClient } from \"../MatrixClient\";\nimport { IPreprocessor } from \"./IPreprocessor\";\nimport { EventKind, ext"
},
{
"path": "src/request.ts",
"chars": 518,
"preview": "import * as origRequestFn from \"request\";\n\nlet requestFn = origRequestFn;\n\n/**\n * Sets the function to use for performin"
},
{
"path": "src/simple-validation.ts",
"chars": 954,
"preview": "/**\n * Validate the 'order' parameter of a child space entry. It must be a\n * string between the range of \\x20 - \\x7F an"
},
{
"path": "src/storage/IAppserviceStorageProvider.ts",
"chars": 1898,
"preview": "import { ICryptoStorageProvider } from \"./ICryptoStorageProvider\";\nimport { IStorageProvider } from \"./IStorageProvider\""
},
{
"path": "src/storage/ICryptoStorageProvider.ts",
"chars": 1339,
"preview": "import { ICryptoRoomInformation } from \"../e2ee/ICryptoRoomInformation\";\n\n/**\n * A storage provider capable of only prov"
},
{
"path": "src/storage/IStorageProvider.ts",
"chars": 1958,
"preview": "import { IFilterInfo } from \"../IFilter\";\n\n/**\n * Represents a storage provider for the matrix client\n * @category Stora"
},
{
"path": "src/storage/MemoryStorageProvider.ts",
"chars": 1969,
"preview": "import { IStorageProvider } from \"./IStorageProvider\";\nimport { IFilterInfo } from \"../IFilter\";\nimport { IAppserviceSto"
},
{
"path": "src/storage/RustSdkCryptoStorageProvider.ts",
"chars": 3085,
"preview": "import * as lowdb from \"lowdb\";\nimport * as FileSync from \"lowdb/adapters/FileSync\";\nimport * as mkdirp from \"mkdirp\";\ni"
},
{
"path": "src/storage/SimpleFsStorageProvider.ts",
"chars": 5198,
"preview": "import * as lowdb from \"lowdb\";\nimport * as FileSync from \"lowdb/adapters/FileSync\";\nimport * as sha512 from \"hash.js/li"
},
{
"path": "src/storage/SimplePostgresStorageProvider.ts",
"chars": 6615,
"preview": "import * as postgres from \"postgres\";\n\nimport { IStorageProvider } from \"./IStorageProvider\";\nimport { IAppserviceStorag"
},
{
"path": "src/strategies/AppserviceJoinRoomStrategy.ts",
"chars": 2409,
"preview": "import { IJoinRoomStrategy } from \"./JoinRoomStrategy\";\nimport { Appservice } from \"../appservice/Appservice\";\n\n/**\n * A"
},
{
"path": "src/strategies/JoinRoomStrategy.ts",
"chars": 1736,
"preview": "import { extractRequestError, LogService } from \"..\";\n\nexport interface IJoinRoomStrategy {\n joinRoom(roomIdOrAlias: "
},
{
"path": "test/AdminApisTest.ts",
"chars": 1730,
"preview": "import HttpBackend from 'matrix-mock-request';\n\nimport { AdminApis, IStorageProvider, MatrixClient, WhoisInfo } from \".."
},
{
"path": "test/DMsTest.ts",
"chars": 14287,
"preview": "import * as simple from \"simple-mock\";\n\nimport { EncryptionAlgorithm } from \"../src\";\nimport { createTestClient, testCry"
},
{
"path": "test/IdentityClientTest.ts",
"chars": 32608,
"preview": "import HttpBackend from 'matrix-mock-request';\nimport * as simple from \"simple-mock\";\n\nimport { IdentityClient, MatrixCl"
},
{
"path": "test/MatrixAuthTest.ts",
"chars": 3981,
"preview": "import HttpBackend from 'matrix-mock-request';\n\nimport { MatrixAuth } from \"../src\";\nimport { createTestClient } from \"."
},
{
"path": "test/MatrixClientTest.ts",
"chars": 303258,
"preview": "import * as tmp from \"tmp\";\nimport * as simple from \"simple-mock\";\nimport { StoreType } from \"@matrix-org/matrix-sdk-cry"
},
{
"path": "test/SynapseAdminApisTest.ts",
"chars": 21318,
"preview": "import HttpBackend from 'matrix-mock-request';\n\nimport {\n IStorageProvider,\n MatrixClient,\n SynapseAdminApis,\n "
},
{
"path": "test/SynchronousMatrixClientTest.ts",
"chars": 34155,
"preview": "import * as simple from \"simple-mock\";\nimport HttpBackend from 'matrix-mock-request';\n\nimport { IStorageProvider, Matrix"
},
{
"path": "test/TestUtils.ts",
"chars": 1777,
"preview": "import * as tmp from \"tmp\";\nimport HttpBackend from \"matrix-mock-request\";\nimport { StoreType } from \"@matrix-org/matrix"
},
{
"path": "test/UnstableApisTest.ts",
"chars": 5542,
"preview": "import HttpBackend from 'matrix-mock-request';\n\nimport { IStorageProvider, MatrixClient, MSC2380MediaInfo, UnstableApis "
},
{
"path": "test/appservice/AppserviceTest.ts",
"chars": 125516,
"preview": "import * as getPort from \"get-port\";\nimport * as requestPromise from \"request-promise\";\nimport * as simple from \"simple-"
},
{
"path": "test/appservice/IntentTest.ts",
"chars": 52435,
"preview": "import * as simple from \"simple-mock\";\nimport HttpBackend from 'matrix-mock-request';\nimport * as tmp from \"tmp\";\nimport"
},
{
"path": "test/appservice/MatrixBridgeTest.ts",
"chars": 11240,
"preview": "import * as simple from \"simple-mock\";\n\nimport {\n Appservice,\n IAppserviceOptions,\n Intent,\n MatrixBridge,\n "
},
{
"path": "test/appservice/UnstableAppserviceApisTest.ts",
"chars": 4787,
"preview": "import HttpBackend from 'matrix-mock-request';\n\nimport { IStorageProvider, MatrixClient, MSC2716BatchSendResponse, Unsta"
},
{
"path": "test/b64Test.ts",
"chars": 1303,
"preview": "import {\n decodeBase64,\n decodeUnpaddedBase64,\n decodeUnpaddedUrlSafeBase64,\n encodeBase64,\n encodeUnpadd"
},
{
"path": "test/encryption/CryptoClientTest.ts",
"chars": 23653,
"preview": "import * as simple from \"simple-mock\";\nimport HttpBackend from 'matrix-mock-request';\n\nimport { EncryptedFile, MatrixCli"
},
{
"path": "test/encryption/RoomTrackerTest.ts",
"chars": 10004,
"preview": "import * as simple from \"simple-mock\";\n\nimport { EncryptionEventContent, MatrixClient, RoomEncryptionAlgorithm, RoomTrac"
},
{
"path": "test/encryption/decoratorsTest.ts",
"chars": 4008,
"preview": "import * as simple from \"simple-mock\";\n\nimport { requiresCrypto, requiresReady } from \"../../src\";\n\nclass InterceptedCla"
},
{
"path": "test/helpers/MatrixEntityTest.ts",
"chars": 1367,
"preview": "import { MatrixEntity, RoomAlias, UserID } from \"../../src\";\n\ndescribe('MatrixEntity', () => {\n it('should parse arbi"
},
{
"path": "test/helpers/MatrixGlobTest.ts",
"chars": 2548,
"preview": "import { MatrixGlob } from \"../../src\";\n\ndescribe('MatrixGlob', () => {\n it('should work with no glob characters', ()"
},
{
"path": "test/helpers/MentionPillTest.ts",
"chars": 8039,
"preview": "import * as simple from \"simple-mock\";\n\nimport { MentionPill } from \"../../src\";\nimport { createTestClient } from \"../Te"
},
{
"path": "test/helpers/PermalinksTest.ts",
"chars": 7812,
"preview": "import { PermalinkParts, Permalinks } from \"../../src\";\n\ndescribe('Permalinks', () => {\n describe('forRoom', () => {\n"
},
{
"path": "test/helpers/ProfileCacheTest.ts",
"chars": 12923,
"preview": "import * as simple from \"simple-mock\";\n\nimport { Appservice, ProfileCache } from \"../../src\";\nimport { createTestClient,"
},
{
"path": "test/helpers/RichReplyTest.ts",
"chars": 4058,
"preview": "import { RichReply } from \"../../src\";\n\ndescribe('RichReply', () => {\n it('should return a well-formatted reply', () "
},
{
"path": "test/helpers/UnpaddedBase64Test.ts",
"chars": 1602,
"preview": "import { UnpaddedBase64 } from \"../../src\";\n\ndescribe('UnpaddedBase64', () => {\n it('should encode buffers', () => {\n"
},
{
"path": "test/logging/LogServiceTest.ts",
"chars": 6576,
"preview": "import * as simple from \"simple-mock\";\n\nimport { ConsoleLogger, LogLevel, LogService } from \"../../src\";\n\ndescribe('LogS"
},
{
"path": "test/metrics/MetricsTest.ts",
"chars": 12082,
"preview": "import * as simple from \"simple-mock\";\n\nimport { IMetricContext, IMetricListener, Metrics } from \"../../src\";\n\nfunction "
},
{
"path": "test/metrics/decoratorsTest.ts",
"chars": 14384,
"preview": "import * as simple from \"simple-mock\";\n\nimport {\n Metrics,\n timedIdentityClientFunctionCall,\n timedIntentFuncti"
},
{
"path": "test/mixins/AutojoinRoomsMixinTest.ts",
"chars": 6604,
"preview": "import * as simple from \"simple-mock\";\n\nimport { Appservice, AutojoinRoomsMixin, Intent } from \"../../src\";\nimport { cre"
},
{
"path": "test/mixins/AutojoinUpgradedRoomsMixinTest.ts",
"chars": 3712,
"preview": "import * as simple from \"simple-mock\";\n\nimport { Appservice, AutojoinUpgradedRoomsMixin, Intent } from \"../../src\";\nimpo"
},
{
"path": "test/models/MatrixProfileTest.ts",
"chars": 1567,
"preview": "import { MatrixProfile, MatrixProfileInfo, UserID } from \"../../src\";\n\ndescribe(\"MatrixProfile\", () => {\n it(\"should "
},
{
"path": "test/models/PresenceTest.ts",
"chars": 654,
"preview": "import { Presence, PresenceEventContent } from \"../../src\";\n\ndescribe(\"Presence\", () => {\n it(\"should return the righ"
},
{
"path": "test/models/SpacesTest.ts",
"chars": 10639,
"preview": "import * as simple from \"simple-mock\";\n\nimport { Space } from \"../../src\";\nimport { createTestClient } from \"../TestUtil"
},
{
"path": "test/models/events/AliasesEventTest.ts",
"chars": 561,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { AliasesEvent } from \"../../../src\";\nimport { expectArrayEqual"
},
{
"path": "test/models/events/CanonicalAliasEventTest.ts",
"chars": 403,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { CanonicalAliasEvent } from \"../../../src\";\n\ndescribe(\"Canonic"
},
{
"path": "test/models/events/CreateEventTest.ts",
"chars": 1192,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { CreateEvent } from \"../../../src\";\n\ndescribe(\"CreateEvent\", ("
},
{
"path": "test/models/events/EncryptedRoomEventTest.ts",
"chars": 656,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { EncryptedRoomEvent, RoomEncryptionAlgorithm } from \"../../../"
},
{
"path": "test/models/events/EncryptionEventTest.ts",
"chars": 1161,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { EncryptionEvent, RoomEncryptionAlgorithm } from \"../../../src"
},
{
"path": "test/models/events/EventTest.ts",
"chars": 605,
"preview": "import { MatrixEvent } from \"../../../src\";\n\nexport function createMinimalEvent(content: any = { hello: \"world\" }) {\n "
},
{
"path": "test/models/events/JoinRulesEventTest.ts",
"chars": 384,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { JoinRulesEvent } from \"../../../src\";\n\ndescribe(\"JoinRulesEve"
},
{
"path": "test/models/events/MembershipEventTest.ts",
"chars": 2628,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { MembershipEvent } from \"../../../src\";\n\ndescribe(\"MembershipE"
},
{
"path": "test/models/events/MessageEventTest.ts",
"chars": 1741,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { EventRedactedError, MessageEvent, MessageEventContent } from "
},
{
"path": "test/models/events/PinnedEventsEventTest.ts",
"chars": 484,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { expectArrayEquals } from \"../../TestUtils\";\nimport { PinnedEv"
},
{
"path": "test/models/events/PowerLevelsEventTest.ts",
"chars": 1720,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { PowerLevelsEvent } from \"../../../src\";\n\ndescribe(\"PowerLevel"
},
{
"path": "test/models/events/RedactionEventTest.ts",
"chars": 1192,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { RedactionEvent } from \"../../../src\";\nimport { expectArrayEqu"
},
{
"path": "test/models/events/RoomAvatarEventTest.ts",
"chars": 397,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { RoomAvatarEvent } from \"../../../src\";\n\ndescribe(\"RoomAvatarE"
},
{
"path": "test/models/events/RoomNameEventTest.ts",
"chars": 380,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { RoomNameEvent } from \"../../../src\";\n\ndescribe(\"RoomNameEvent"
},
{
"path": "test/models/events/RoomTopicEventTest.ts",
"chars": 386,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { RoomTopicEvent } from \"../../../src\";\n\ndescribe(\"RoomTopicEve"
},
{
"path": "test/models/events/SpaceChildEventTest.ts",
"chars": 1606,
"preview": "import { createMinimalEvent } from \"./EventTest\";\nimport { SpaceChildEvent } from \"../../../src\";\n\ndescribe(\"SpaceChildE"
},
{
"path": "test/models/events/converterTest.ts",
"chars": 1550,
"preview": "import {\n MembershipEvent,\n MessageEvent,\n MessageEventContent,\n RoomEvent,\n RoomEventContent,\n StateE"
},
{
"path": "test/preprocessors/RichRepliesPreprocessorTest.ts",
"chars": 11739,
"preview": "import * as simple from \"simple-mock\";\n\nimport { EventKind, RichRepliesPreprocessor } from \"../../src\";\nimport { createT"
},
{
"path": "test/requestTest.ts",
"chars": 272,
"preview": "import { getRequestFn, setRequestFn } from \"../src\";\n\ndescribe('request', () => {\n it('should return whatever request"
},
{
"path": "test/simple-validationTest.ts",
"chars": 2625,
"preview": "import { validateSpaceOrderString } from \"../src\";\n\ndescribe('validateSpaceOrderString', () => {\n it('should return t"
},
{
"path": "test/storage/MemoryStorageProviderTest.ts",
"chars": 5206,
"preview": "import { IFilterInfo, MemoryStorageProvider } from \"../../src\";\n\ndescribe('MemoryStorageProvider', () => {\n it('shoul"
},
{
"path": "test/storage/SimpleFsStorageProviderTest.ts",
"chars": 10745,
"preview": "import * as tmp from \"tmp\";\n\nimport { IFilterInfo, SimpleFsStorageProvider } from \"../../src\";\n\ntmp.setGracefulCleanup()"
},
{
"path": "test/storage/SimplePostgresStorageProviderTest.ts",
"chars": 12872,
"preview": "import { PostgreSqlContainer, StartedPostgreSqlContainer } from \"@testcontainers/postgresql\";\n\nimport { IFilterInfo, Sim"
},
{
"path": "test/strategies/AppserviceJoinRoomStrategyTest.ts",
"chars": 15627,
"preview": "import * as simple from \"simple-mock\";\n\nimport { Appservice, AppserviceJoinRoomStrategy, IJoinRoomStrategy } from \"../.."
},
{
"path": "test/strategies/JoinRoomStrategyTest.ts",
"chars": 2926,
"preview": "import * as simple from \"simple-mock\";\n\nimport { SimpleRetryJoinStrategy } from \"../../src\";\n\ndescribe('SimpleRetryJoinS"
},
{
"path": "tsconfig-examples.json",
"chars": 359,
"preview": "{\n \"compilerOptions\": {\n \"experimentalDecorators\": true,\n \"emitDecoratorMetadata\": true,\n \"module\": \"commonjs\""
},
{
"path": "tsconfig-release.json",
"chars": 385,
"preview": "{\n \"compilerOptions\": {\n \"experimentalDecorators\": true,\n \"emitDecoratorMetadata\": true,\n \"module\": \"commonjs\""
},
{
"path": "tsconfig.json",
"chars": 404,
"preview": "{\n \"compilerOptions\": {\n \"experimentalDecorators\": true,\n \"emitDecoratorMetadata\": true,\n \"module\": \"commonjs\""
}
]
About this extraction
This page contains the full source code of the turt2live/matrix-bot-sdk GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 184 files (1.2 MB), approximately 277.6k tokens, and a symbol index with 771 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.