matrix-js-sdk
Version:
Matrix Client-Server SDK for Javascript
1,452 lines (1,284 loc) • 390 kB
text/typescript
/*
Copyright 2015-2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* This is an internal module. See {@link MatrixClient} for the public class.
*/
import { Optional } from "matrix-events-sdk";
import type { IDeviceKeys, IMegolmSessionData, IOneTimeKey } from "./@types/crypto";
import { ISyncStateData, SetPresence, SyncApi, SyncApiOptions, SyncState } from "./sync";
import {
EventStatus,
IContent,
IDecryptOptions,
IEvent,
MatrixEvent,
MatrixEventEvent,
MatrixEventHandlerMap,
PushDetails,
} from "./models/event";
import { StubStore } from "./store/stub";
import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall, supportsMatrixCall } from "./webrtc/call";
import { Filter, IFilterDefinition, IRoomEventFilter } from "./filter";
import { CallEventHandlerEvent, CallEventHandler, CallEventHandlerEventHandlerMap } from "./webrtc/callEventHandler";
import {
GroupCallEventHandler,
GroupCallEventHandlerEvent,
GroupCallEventHandlerEventHandlerMap,
} from "./webrtc/groupCallEventHandler";
import * as utils from "./utils";
import { replaceParam, QueryDict, sleep, noUnsafeEventProps, safeSet } from "./utils";
import { Direction, EventTimeline } from "./models/event-timeline";
import { IActionsObject, PushProcessor } from "./pushprocessor";
import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery";
import * as olmlib from "./crypto/olmlib";
import { decodeBase64, encodeBase64 } from "./base64";
import { IExportedDevice as IExportedOlmDevice } from "./crypto/OlmDevice";
import { IOlmDevice } from "./crypto/algorithms/megolm";
import { TypedReEmitter } from "./ReEmitter";
import { IRoomEncryption } from "./crypto/RoomList";
import { logger, Logger } from "./logger";
import { SERVICE_TYPES } from "./service-types";
import {
Body,
ClientPrefix,
FileType,
HttpApiEvent,
HttpApiEventHandlerMap,
HTTPError,
IdentityPrefix,
IHttpOpts,
IRequestOpts,
TokenRefreshFunction,
MatrixError,
MatrixHttpApi,
MediaPrefix,
Method,
retryNetworkOperation,
Upload,
UploadOpts,
UploadResponse,
} from "./http-api";
import {
Crypto,
CryptoEvent,
CryptoEventHandlerMap,
fixBackupKey,
ICheckOwnCrossSigningTrustOpts,
ICryptoCallbacks,
IRoomKeyRequestBody,
isCryptoAvailable,
VerificationMethod,
} from "./crypto";
import { DeviceInfo } from "./crypto/deviceinfo";
import { decodeRecoveryKey } from "./crypto/recoverykey";
import { keyFromAuthData } from "./crypto/key_passphrase";
import { User, UserEvent, UserEventHandlerMap } from "./models/user";
import { getHttpUriForMxc } from "./content-repo";
import { SearchResult } from "./models/search-result";
import { DEHYDRATION_ALGORITHM, IDehydratedDevice, IDehydratedDeviceKeyInfo } from "./crypto/dehydration";
import {
IKeyBackupInfo,
IKeyBackupPrepareOpts,
IKeyBackupRestoreOpts,
IKeyBackupRestoreResult,
IKeyBackupRoomSessions,
IKeyBackupSession,
} from "./crypto/keybackup";
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider";
import { MatrixScheduler } from "./scheduler";
import { BeaconEvent, BeaconEventHandlerMap } from "./models/beacon";
import { AuthDict } from "./interactive-auth";
import { IMinimalEvent, IRoomEvent, IStateEvent } from "./sync-accumulator";
import { CrossSigningKey, ICreateSecretStorageOpts, IEncryptedEventInfo, IRecoveryKey } from "./crypto/api";
import { EventTimelineSet } from "./models/event-timeline-set";
import { VerificationRequest } from "./crypto/verification/request/VerificationRequest";
import { VerificationBase as Verification } from "./crypto/verification/Base";
import * as ContentHelpers from "./content-helpers";
import { CrossSigningInfo, DeviceTrustLevel, ICacheCallbacks, UserTrustLevel } from "./crypto/CrossSigning";
import { NotificationCountType, Room, RoomEvent, RoomEventHandlerMap, RoomNameState } from "./models/room";
import { RoomMemberEvent, RoomMemberEventHandlerMap } from "./models/room-member";
import { IPowerLevelsContent, RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state";
import {
IAddThreePidOnlyBody,
IBindThreePidBody,
IContextResponse,
ICreateRoomOpts,
IEventSearchOpts,
IFilterResponse,
IGuestAccessOpts,
IJoinRoomOpts,
INotificationsResponse,
IPaginateOpts,
IPresenceOpts,
IRedactOpts,
IRelationsRequestOpts,
IRelationsResponse,
IRoomDirectoryOptions,
ISearchOpts,
ISendEventResponse,
IStatusResponse,
ITagsResponse,
KnockRoomOpts,
} from "./@types/requests";
import {
EventType,
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
MSC3912_RELATION_BASED_REDACTIONS_PROP,
MsgType,
PUSHER_ENABLED,
RelationType,
RoomCreateTypeField,
RoomType,
UNSTABLE_MSC3088_ENABLED,
UNSTABLE_MSC3088_PURPOSE,
UNSTABLE_MSC3089_TREE_SUBTYPE,
} from "./@types/event";
import { IdServerUnbindResult, IImageInfo, JoinRule, Preset, Visibility } from "./@types/partials";
import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper";
import { randomString } from "./randomstring";
import { BackupManager, IKeyBackup, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup";
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace";
import { ISignatures } from "./@types/signed";
import { IStore } from "./store";
import { ISecretRequest } from "./crypto/SecretStorage";
import {
IEventWithRoomId,
ISearchRequestBody,
ISearchResponse,
ISearchResults,
IStateEventWithRoomId,
SearchOrderBy,
} from "./@types/search";
import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@types/synapse";
import { IHierarchyRoom } from "./@types/spaces";
import {
IPusher,
IPusherRequest,
IPushRule,
IPushRules,
PushRuleAction,
PushRuleActionName,
PushRuleKind,
RuleId,
} from "./@types/PushRules";
import { IThreepid } from "./@types/threepids";
import { CryptoStore, OutgoingRoomKeyRequest } from "./crypto/store/base";
import { GroupCall, GroupCallIntent, GroupCallType, IGroupCallDataChannelOptions } from "./webrtc/groupCall";
import { MediaHandler } from "./webrtc/mediaHandler";
import {
ILoginFlowsResponse,
IRefreshTokenResponse,
LoginRequest,
LoginResponse,
LoginTokenPostResponse,
SSOAction,
} from "./@types/auth";
import { TypedEventEmitter } from "./models/typed-event-emitter";
import { MAIN_ROOM_TIMELINE, ReceiptType } from "./@types/read_receipts";
import { MSC3575SlidingSyncRequest, MSC3575SlidingSyncResponse, SlidingSync } from "./sliding-sync";
import { SlidingSyncSdk } from "./sliding-sync-sdk";
import {
determineFeatureSupport,
FeatureSupport,
Thread,
THREAD_RELATION_TYPE,
ThreadFilterType,
threadFilterTypeToFilter,
} from "./models/thread";
import { M_BEACON_INFO, MBeaconInfoEventContent } from "./@types/beacon";
import { NamespacedValue, UnstableValue } from "./NamespacedValue";
import { ToDeviceMessageQueue } from "./ToDeviceMessageQueue";
import { ToDeviceBatch } from "./models/ToDeviceMessage";
import { IgnoredInvites } from "./models/invites-ignorer";
import { UIARequest, UIAResponse } from "./@types/uia";
import { LocalNotificationSettings } from "./@types/local_notifications";
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature";
import { BackupDecryptor, CryptoBackend } from "./common-crypto/CryptoBackend";
import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants";
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo, CryptoApi, ImportRoomKeysOpts } from "./crypto-api";
import { DeviceInfoMap } from "./crypto/DeviceList";
import {
AddSecretStorageKeyOpts,
SecretStorageKeyDescription,
ServerSideSecretStorage,
ServerSideSecretStorageImpl,
} from "./secret-storage";
import { RegisterRequest, RegisterResponse } from "./@types/registration";
import { MatrixRTCSessionManager } from "./matrixrtc/MatrixRTCSessionManager";
import { getRelationsThreadFilter } from "./thread-utils";
export type Store = IStore;
export type ResetTimelineCallback = (roomId: string) => boolean;
const SCROLLBACK_DELAY_MS = 3000;
export const CRYPTO_ENABLED: boolean = isCryptoAvailable();
const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value
const TURN_CHECK_INTERVAL = 10 * 60 * 1000; // poll for turn credentials every 10 minutes
export const UNSTABLE_MSC3852_LAST_SEEN_UA = new UnstableValue(
"last_seen_user_agent",
"org.matrix.msc3852.last_seen_user_agent",
);
interface IExportedDevice {
olmDevice: IExportedOlmDevice;
userId: string;
deviceId: string;
}
export interface IKeysUploadResponse {
one_time_key_counts: {
// eslint-disable-line camelcase
[algorithm: string]: number;
};
}
export interface ICreateClientOpts {
baseUrl: string;
idBaseUrl?: string;
/**
* The data store used for sync data from the homeserver. If not specified,
* this client will not store any HTTP responses. The `createClient` helper
* will create a default store if needed.
*/
store?: Store;
/**
* A store to be used for end-to-end crypto session data. If not specified,
* end-to-end crypto will be disabled. The `createClient` helper will create
* a default store if needed. Calls the factory supplied to
* {@link setCryptoStoreFactory} if unspecified; or if no factory has been
* specified, uses a default implementation (indexeddb in the browser,
* in-memory otherwise).
*/
cryptoStore?: CryptoStore;
/**
* The scheduler to use. If not
* specified, this client will not retry requests on failure. This client
* will supply its own processing function to
* {@link MatrixScheduler#setProcessFunction}.
*/
scheduler?: MatrixScheduler;
/**
* The function to invoke for HTTP requests.
* Most supported environments have a global `fetch` registered to which this will fall back.
*/
fetchFn?: typeof global.fetch;
userId?: string;
/**
* A unique identifier for this device; used for tracking things like crypto
* keys and access tokens. If not specified, end-to-end encryption will be
* disabled.
*/
deviceId?: string;
accessToken?: string;
refreshToken?: string;
/**
* Function used to attempt refreshing access and refresh tokens
* Called by http-api when a possibly expired token is encountered
* and a refreshToken is found
*/
tokenRefreshFunction?: TokenRefreshFunction;
/**
* Identity server provider to retrieve the user's access token when accessing
* the identity server. See also https://github.com/vector-im/element-web/issues/10615
* which seeks to replace the previous approach of manual access tokens params
* with this callback throughout the SDK.
*/
identityServer?: IIdentityServerProvider;
/**
* The default maximum amount of
* time to wait before timing out HTTP requests. If not specified, there is no timeout.
*/
localTimeoutMs?: number;
/**
* Set to true to use
* Authorization header instead of query param to send the access token to the server.
*
* Default false.
*/
useAuthorizationHeader?: boolean;
/**
* Set to true to enable
* improved timeline support, see {@link MatrixClient#getEventTimeline}.
* It is disabled by default for compatibility with older clients - in particular to
* maintain support for back-paginating the live timeline after a '/sync'
* result with a gap.
*/
timelineSupport?: boolean;
/**
* Extra query parameters to append
* to all requests with this client. Useful for application services which require
* `?user_id=`.
*/
queryParams?: Record<string, string>;
/**
* Device data exported with
* "exportDevice" method that must be imported to recreate this device.
* Should only be useful for devices with end-to-end crypto enabled.
* If provided, deviceId and userId should **NOT** be provided at the top
* level (they are present in the exported data).
*/
deviceToImport?: IExportedDevice;
/**
* Encryption key used for encrypting sensitive data (such as e2ee keys) in storage.
*
* This must be set to the same value every time the client is initialised for the same device.
*
* If unset, either a hardcoded key or no encryption at all is used, depending on the Crypto implementation.
*
* No particular requirement is placed on the key data (it is fed into an HKDF to generate the actual encryption
* keys).
*/
pickleKey?: string;
/**
* Verification methods we should offer to the other side when performing an interactive verification.
* If unset, we will offer all known methods. Currently these are: showing a QR code, scanning a QR code, and SAS
* (aka "emojis").
*/
verificationMethods?: Array<VerificationMethod>;
/**
* Whether relaying calls through a TURN server should be forced. Default false.
*/
forceTURN?: boolean;
/**
* Up to this many ICE candidates will be gathered when an incoming call arrives.
* Gathering does not send data to the caller, but will communicate with the configured TURN
* server. Default 0.
*/
iceCandidatePoolSize?: number;
/**
* True to advertise support for call transfers to other parties on Matrix calls. Default false.
*/
supportsCallTransfer?: boolean;
/**
* Whether to allow a fallback ICE server should be used for negotiating a
* WebRTC connection if the homeserver doesn't provide any servers. Defaults to false.
*/
fallbackICEServerAllowed?: boolean;
/**
* If true, to-device signalling for group calls will be encrypted
* with Olm. Default: true.
*/
useE2eForGroupCall?: boolean;
livekitServiceURL?: string;
/**
* Crypto callbacks provided by the application
*/
cryptoCallbacks?: ICryptoCallbacks;
/**
* Method to generate room names for empty rooms and rooms names based on membership.
* Defaults to a built-in English handler with basic pluralisation.
*/
roomNameGenerator?: (roomId: string, state: RoomNameState) => string | null;
/**
* If true, participant can join group call without video and audio this has to be allowed. By default, a local
* media stream is needed to establish a group call.
* Default: false.
*/
isVoipWithNoMediaAllowed?: boolean;
/**
* If true, group calls will not establish media connectivity and only create the signaling events,
* so that livekit media can be used in the application layert (js-sdk contains no livekit code).
*/
useLivekitForGroupCalls?: boolean;
/**
* A logger to associate with this MatrixClient.
* Defaults to the built-in global logger.
*/
logger?: Logger;
}
export interface IMatrixClientCreateOpts extends ICreateClientOpts {
/**
* Whether to allow sending messages to encrypted rooms when encryption
* is not available internally within this SDK. This is useful if you are using an external
* E2E proxy, for example. Defaults to false.
*/
usingExternalCrypto?: boolean;
}
export enum PendingEventOrdering {
Chronological = "chronological",
Detached = "detached",
}
export interface IStartClientOpts {
/**
* The event `limit=` to apply to initial sync. Default: 8.
*/
initialSyncLimit?: number;
/**
* True to put `archived=true</code> on the <code>/initialSync` request. Default: false.
*/
includeArchivedRooms?: boolean;
/**
* True to do /profile requests on every invite event if the displayname/avatar_url is not known for this user ID. Default: false.
*/
resolveInvitesToProfiles?: boolean;
/**
* Controls where pending messages appear in a room's timeline. If "<b>chronological</b>", messages will
* appear in the timeline when the call to `sendEvent` was made. If "<b>detached</b>",
* pending messages will appear in a separate list, accessbile via {@link Room#getPendingEvents}.
* Default: "chronological".
*/
pendingEventOrdering?: PendingEventOrdering;
/**
* The number of milliseconds to wait on /sync. Default: 30000 (30 seconds).
*/
pollTimeout?: number;
/**
* The filter to apply to /sync calls.
*/
filter?: Filter;
/**
* True to perform syncing without automatically updating presence.
*/
disablePresence?: boolean;
/**
* True to not load all membership events during initial sync but fetch them when needed by calling
* `loadOutOfBandMembers` This will override the filter option at this moment.
*/
lazyLoadMembers?: boolean;
/**
* The number of seconds between polls to /.well-known/matrix/client, undefined to disable.
* This should be in the order of hours. Default: undefined.
*/
clientWellKnownPollPeriod?: number;
/**
* @deprecated use `threadSupport` instead
*/
experimentalThreadSupport?: boolean;
/**
* Will organises events in threaded conversations when
* a thread relation is encountered
*/
threadSupport?: boolean;
/**
* @experimental
*/
slidingSync?: SlidingSync;
}
export interface IStoredClientOpts extends IStartClientOpts {}
export enum RoomVersionStability {
Stable = "stable",
Unstable = "unstable",
}
export interface IRoomVersionsCapability {
default: string;
available: Record<string, RoomVersionStability>;
}
export interface ICapability {
enabled: boolean;
}
export interface IChangePasswordCapability extends ICapability {}
export interface IThreadsCapability extends ICapability {}
export interface IGetLoginTokenCapability extends ICapability {}
export const GET_LOGIN_TOKEN_CAPABILITY = new NamespacedValue(
"m.get_login_token",
"org.matrix.msc3882.get_login_token",
);
export const UNSTABLE_MSC2666_SHARED_ROOMS = "uk.half-shot.msc2666";
export const UNSTABLE_MSC2666_MUTUAL_ROOMS = "uk.half-shot.msc2666.mutual_rooms";
export const UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = "uk.half-shot.msc2666.query_mutual_rooms";
/**
* A representation of the capabilities advertised by a homeserver as defined by
* [Capabilities negotiation](https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3capabilities).
*/
export interface Capabilities {
[key: string]: any;
"m.change_password"?: IChangePasswordCapability;
"m.room_versions"?: IRoomVersionsCapability;
"io.element.thread"?: IThreadsCapability;
"m.get_login_token"?: IGetLoginTokenCapability;
"org.matrix.msc3882.get_login_token"?: IGetLoginTokenCapability;
}
/** @deprecated prefer {@link CrossSigningKeyInfo}. */
export type ICrossSigningKey = CrossSigningKeyInfo;
enum CrossSigningKeyType {
MasterKey = "master_key",
SelfSigningKey = "self_signing_key",
UserSigningKey = "user_signing_key",
}
export type CrossSigningKeys = Record<CrossSigningKeyType, ICrossSigningKey>;
export type SendToDeviceContentMap = Map<string, Map<string, Record<string, any>>>;
export interface ISignedKey {
keys: Record<string, string>;
signatures: ISignatures;
user_id: string;
algorithms: string[];
device_id: string;
}
export type KeySignatures = Record<string, Record<string, ICrossSigningKey | ISignedKey>>;
export interface IUploadKeySignaturesResponse {
failures: Record<
string,
Record<
string,
{
errcode: string;
error: string;
}
>
>;
}
export interface IPreviewUrlResponse {
[key: string]: undefined | string | number;
"og:title": string;
"og:type": string;
"og:url": string;
"og:image"?: string;
"og:image:type"?: string;
"og:image:height"?: number;
"og:image:width"?: number;
"og:description"?: string;
"matrix:image:size"?: number;
}
export interface ITurnServerResponse {
uris: string[];
username: string;
password: string;
ttl: number;
}
export interface ITurnServer {
urls: string[];
username: string;
credential: string;
}
export interface IServerVersions {
versions: string[];
unstable_features: Record<string, boolean>;
}
export const M_AUTHENTICATION = new UnstableValue("m.authentication", "org.matrix.msc2965.authentication");
export interface IClientWellKnown {
[key: string]: any;
"m.homeserver"?: IWellKnownConfig;
"m.identity_server"?: IWellKnownConfig;
[M_AUTHENTICATION.name]?: IDelegatedAuthConfig; // MSC2965
}
export interface IWellKnownConfig<T = IClientWellKnown> {
raw?: T;
action?: AutoDiscoveryAction;
reason?: string;
error?: Error | string;
// eslint-disable-next-line
base_url?: string | null;
// XXX: this is undocumented
server_name?: string;
}
export interface IDelegatedAuthConfig {
// MSC2965
/** The OIDC Provider/issuer the client should use */
issuer: string;
/** The optional URL of the web UI where the user can manage their account */
account?: string;
}
interface IKeyBackupPath {
path: string;
queryData?: {
version: string;
};
}
interface IMediaConfig {
[key: string]: any; // extensible
"m.upload.size"?: number;
}
interface IThirdPartySigned {
sender: string;
mxid: string;
token: string;
signatures: ISignatures;
}
interface IJoinRequestBody {
third_party_signed?: IThirdPartySigned;
}
interface ITagMetadata {
[key: string]: any;
order?: number;
}
interface IMessagesResponse {
start?: string;
end?: string;
chunk: IRoomEvent[];
state?: IStateEvent[];
}
interface IThreadedMessagesResponse {
prev_batch: string;
next_batch: string;
chunk: IRoomEvent[];
state: IStateEvent[];
}
export interface IRequestTokenResponse {
sid: string;
submit_url?: string;
}
export interface IRequestMsisdnTokenResponse extends IRequestTokenResponse {
msisdn: string;
success: boolean;
intl_fmt: string;
}
export interface IUploadKeysRequest {
"device_keys"?: Required<IDeviceKeys>;
"one_time_keys"?: Record<string, IOneTimeKey>;
"org.matrix.msc2732.fallback_keys"?: Record<string, IOneTimeKey>;
}
export interface IQueryKeysRequest {
device_keys: { [userId: string]: string[] };
timeout?: number;
token?: string;
}
export interface IClaimKeysRequest {
one_time_keys: { [userId: string]: { [deviceId: string]: string } };
timeout?: number;
}
export interface IOpenIDToken {
access_token: string;
token_type: "Bearer" | string;
matrix_server_name: string;
expires_in: number;
}
interface IRoomInitialSyncResponse {
room_id: string;
membership: "invite" | "join" | "leave" | "ban";
messages?: {
start?: string;
end?: string;
chunk: IEventWithRoomId[];
};
state?: IStateEventWithRoomId[];
visibility: Visibility;
account_data?: IMinimalEvent[];
presence: Partial<IEvent>; // legacy and undocumented, api is deprecated so this won't get attention
}
interface IJoinedRoomsResponse {
joined_rooms: string[];
}
interface IJoinedMembersResponse {
joined: {
[userId: string]: {
display_name: string;
avatar_url: string;
};
};
}
// Re-export for backwards compatibility
export type IRegisterRequestParams = RegisterRequest;
export interface IPublicRoomsChunkRoom {
room_id: string;
name?: string;
avatar_url?: string;
topic?: string;
canonical_alias?: string;
aliases?: string[];
world_readable: boolean;
guest_can_join: boolean;
num_joined_members: number;
room_type?: RoomType | string; // Added by MSC3827
join_rule?: JoinRule.Knock | JoinRule.Public; // Added by MSC2403
}
interface IPublicRoomsResponse {
chunk: IPublicRoomsChunkRoom[];
next_batch?: string;
prev_batch?: string;
total_room_count_estimate?: number;
}
interface IUserDirectoryResponse {
results: {
user_id: string;
display_name?: string;
avatar_url?: string;
}[];
limited: boolean;
}
export interface IMyDevice {
"device_id": string;
"display_name"?: string;
"last_seen_ip"?: string;
"last_seen_ts"?: number;
// UNSTABLE_MSC3852_LAST_SEEN_UA
"last_seen_user_agent"?: string;
"org.matrix.msc3852.last_seen_user_agent"?: string;
}
export interface Keys {
keys: { [keyId: string]: string };
usage: string[];
user_id: string;
}
export interface SigningKeys extends Keys {
signatures: ISignatures;
}
export interface DeviceKeys {
[deviceId: string]: IDeviceKeys & {
unsigned?: {
device_display_name: string;
};
};
}
export interface IDownloadKeyResult {
failures: { [serverName: string]: object };
device_keys: { [userId: string]: DeviceKeys };
// the following three fields were added in 1.1
master_keys?: { [userId: string]: Keys };
self_signing_keys?: { [userId: string]: SigningKeys };
user_signing_keys?: { [userId: string]: SigningKeys };
}
export interface IClaimOTKsResult {
failures: { [serverName: string]: object };
one_time_keys: {
[userId: string]: {
[deviceId: string]: {
[keyId: string]: {
key: string;
signatures: ISignatures;
};
};
};
};
}
export interface IFieldType {
regexp: string;
placeholder: string;
}
export interface IInstance {
desc: string;
icon?: string;
fields: object;
network_id: string;
// XXX: this is undocumented but we rely on it: https://github.com/matrix-org/matrix-doc/issues/3203
instance_id: string;
}
export interface IProtocol {
user_fields: string[];
location_fields: string[];
icon: string;
field_types: Record<string, IFieldType>;
instances: IInstance[];
}
interface IThirdPartyLocation {
alias: string;
protocol: string;
fields: object;
}
interface IThirdPartyUser {
userid: string;
protocol: string;
fields: object;
}
interface IRoomSummary extends Omit<IPublicRoomsChunkRoom, "canonical_alias" | "aliases"> {
room_type?: RoomType;
membership?: string;
is_encrypted: boolean;
}
interface IRoomKeysResponse {
sessions: IKeyBackupRoomSessions;
}
interface IRoomsKeysResponse {
rooms: Record<string, IRoomKeysResponse>;
}
interface IRoomHierarchy {
rooms: IHierarchyRoom[];
next_batch?: string;
}
export interface TimestampToEventResponse {
event_id: string;
origin_server_ts: number;
}
interface IWhoamiResponse {
user_id: string;
device_id?: string;
is_guest?: boolean;
}
/* eslint-enable camelcase */
// We're using this constant for methods overloading and inspect whether a variable
// contains an eventId or not. This was required to ensure backwards compatibility
// of methods for threads
// Probably not the most graceful solution but does a good enough job for now
const EVENT_ID_PREFIX = "$";
export enum ClientEvent {
Sync = "sync",
Event = "event",
ToDeviceEvent = "toDeviceEvent",
AccountData = "accountData",
Room = "Room",
DeleteRoom = "deleteRoom",
SyncUnexpectedError = "sync.unexpectedError",
ClientWellKnown = "WellKnown.client",
ReceivedVoipEvent = "received_voip_event",
UndecryptableToDeviceEvent = "toDeviceEvent.undecryptable",
TurnServers = "turnServers",
TurnServersError = "turnServers.error",
}
type RoomEvents =
| RoomEvent.Name
| RoomEvent.Redaction
| RoomEvent.RedactionCancelled
| RoomEvent.Receipt
| RoomEvent.Tags
| RoomEvent.LocalEchoUpdated
| RoomEvent.HistoryImportedWithinTimeline
| RoomEvent.AccountData
| RoomEvent.MyMembership
| RoomEvent.Timeline
| RoomEvent.TimelineReset;
type RoomStateEvents =
| RoomStateEvent.Events
| RoomStateEvent.Members
| RoomStateEvent.NewMember
| RoomStateEvent.Update
| RoomStateEvent.Marker;
type CryptoEvents =
| CryptoEvent.KeySignatureUploadFailure
| CryptoEvent.KeyBackupStatus
| CryptoEvent.KeyBackupFailed
| CryptoEvent.KeyBackupSessionsRemaining
| CryptoEvent.KeyBackupDecryptionKeyCached
| CryptoEvent.RoomKeyRequest
| CryptoEvent.RoomKeyRequestCancellation
| CryptoEvent.VerificationRequest
| CryptoEvent.VerificationRequestReceived
| CryptoEvent.DeviceVerificationChanged
| CryptoEvent.UserTrustStatusChanged
| CryptoEvent.KeysChanged
| CryptoEvent.Warning
| CryptoEvent.DevicesUpdated
| CryptoEvent.WillUpdateDevices
| CryptoEvent.LegacyCryptoStoreMigrationProgress;
type MatrixEventEvents = MatrixEventEvent.Decrypted | MatrixEventEvent.Replaced | MatrixEventEvent.VisibilityChange;
type RoomMemberEvents =
| RoomMemberEvent.Name
| RoomMemberEvent.Typing
| RoomMemberEvent.PowerLevel
| RoomMemberEvent.Membership;
type UserEvents =
| UserEvent.AvatarUrl
| UserEvent.DisplayName
| UserEvent.Presence
| UserEvent.CurrentlyActive
| UserEvent.LastPresenceTs;
export type EmittedEvents =
| ClientEvent
| RoomEvents
| RoomStateEvents
| CryptoEvents
| MatrixEventEvents
| RoomMemberEvents
| UserEvents
| CallEvent // re-emitted by call.ts using Object.values
| CallEventHandlerEvent.Incoming
| GroupCallEventHandlerEvent.Incoming
| GroupCallEventHandlerEvent.Outgoing
| GroupCallEventHandlerEvent.Ended
| GroupCallEventHandlerEvent.Participants
| HttpApiEvent.SessionLoggedOut
| HttpApiEvent.NoConsent
| BeaconEvent;
export type ClientEventHandlerMap = {
/**
* Fires whenever the SDK's syncing state is updated. The state can be one of:
* <ul>
*
* <li>PREPARED: The client has synced with the server at least once and is
* ready for methods to be called on it. This will be immediately followed by
* a state of SYNCING. <i>This is the equivalent of "syncComplete" in the
* previous API.</i></li>
*
* <li>CATCHUP: The client has detected the connection to the server might be
* available again and will now try to do a sync again. As this sync might take
* a long time (depending how long ago was last synced, and general server
* performance) the client is put in this mode so the UI can reflect trying
* to catch up with the server after losing connection.</li>
*
* <li>SYNCING : The client is currently polling for new events from the server.
* This will be called <i>after</i> processing latest events from a sync.</li>
*
* <li>ERROR : The client has had a problem syncing with the server. If this is
* called <i>before</i> PREPARED then there was a problem performing the initial
* sync. If this is called <i>after</i> PREPARED then there was a problem polling
* the server for updates. This may be called multiple times even if the state is
* already ERROR. <i>This is the equivalent of "syncError" in the previous
* API.</i></li>
*
* <li>RECONNECTING: The sync connection has dropped, but not (yet) in a way that
* should be considered erroneous.
* </li>
*
* <li>STOPPED: The client has stopped syncing with server due to stopClient
* being called.
* </li>
* </ul>
* State transition diagram:
* ```
* +---->STOPPED
* |
* +----->PREPARED -------> SYNCING <--+
* | ^ | ^ |
* | CATCHUP ----------+ | | |
* | ^ V | |
* null ------+ | +------- RECONNECTING |
* | V V |
* +------->ERROR ---------------------+
*
* NB: 'null' will never be emitted by this event.
*
* ```
* Transitions:
* <ul>
*
* <li>`null -> PREPARED` : Occurs when the initial sync is completed
* first time. This involves setting up filters and obtaining push rules.
*
* <li>`null -> ERROR` : Occurs when the initial sync failed first time.
*
* <li>`ERROR -> PREPARED` : Occurs when the initial sync succeeds
* after previously failing.
*
* <li>`PREPARED -> SYNCING` : Occurs immediately after transitioning
* to PREPARED. Starts listening for live updates rather than catching up.
*
* <li>`SYNCING -> RECONNECTING` : Occurs when the live update fails.
*
* <li>`RECONNECTING -> RECONNECTING` : Can occur if the update calls
* continue to fail, but the keepalive calls (to /versions) succeed.
*
* <li>`RECONNECTING -> ERROR` : Occurs when the keepalive call also fails
*
* <li>`ERROR -> SYNCING` : Occurs when the client has performed a
* live update after having previously failed.
*
* <li>`ERROR -> ERROR` : Occurs when the client has failed to keepalive
* for a second time or more.</li>
*
* <li>`SYNCING -> SYNCING` : Occurs when the client has performed a live
* update. This is called <i>after</i> processing.</li>
*
* <li>`* -> STOPPED` : Occurs once the client has stopped syncing or
* trying to sync after stopClient has been called.</li>
* </ul>
*
* @param state - An enum representing the syncing state. One of "PREPARED",
* "SYNCING", "ERROR", "STOPPED".
*
* @param prevState - An enum representing the previous syncing state.
* One of "PREPARED", "SYNCING", "ERROR", "STOPPED" <b>or null</b>.
*
* @param data - Data about this transition.
*
* @example
* ```
* matrixClient.on("sync", function(state, prevState, data) {
* switch (state) {
* case "ERROR":
* // update UI to say "Connection Lost"
* break;
* case "SYNCING":
* // update UI to remove any "Connection Lost" message
* break;
* case "PREPARED":
* // the client instance is ready to be queried.
* var rooms = matrixClient.getRooms();
* break;
* }
* });
* ```
*/
[ClientEvent.Sync]: (state: SyncState, lastState: SyncState | null, data?: ISyncStateData) => void;
/**
* Fires whenever the SDK receives a new event.
* <p>
* This is only fired for live events received via /sync - it is not fired for
* events received over context, search, or pagination APIs.
*
* @param event - The matrix event which caused this event to fire.
* @example
* ```
* matrixClient.on("event", function(event){
* var sender = event.getSender();
* });
* ```
*/
[ClientEvent.Event]: (event: MatrixEvent) => void;
/**
* Fires whenever the SDK receives a new to-device event.
* @param event - The matrix event which caused this event to fire.
* @example
* ```
* matrixClient.on("toDeviceEvent", function(event){
* var sender = event.getSender();
* });
* ```
*/
[ClientEvent.ToDeviceEvent]: (event: MatrixEvent) => void;
/**
* Fires if a to-device event is received that cannot be decrypted.
* Encrypted to-device events will (generally) use plain Olm encryption,
* in which case decryption failures are fatal: the event will never be
* decryptable, unlike Megolm encrypted events where the key may simply
* arrive later.
*
* An undecryptable to-device event is therefore likley to indicate problems.
*
* @param event - The undecyptable to-device event
*/
[ClientEvent.UndecryptableToDeviceEvent]: (event: MatrixEvent) => void;
/**
* Fires whenever new user-scoped account_data is added.
* @param event - The event describing the account_data just added
* @param event - The previous account data, if known.
* @example
* ```
* matrixClient.on("accountData", function(event, oldEvent){
* myAccountData[event.type] = event.content;
* });
* ```
*/
[ClientEvent.AccountData]: (event: MatrixEvent, lastEvent?: MatrixEvent) => void;
/**
* Fires whenever a new Room is added. This will fire when you are invited to a
* room, as well as when you join a room. <strong>This event is experimental and
* may change.</strong>
* @param room - The newly created, fully populated room.
* @example
* ```
* matrixClient.on("Room", function(room){
* var roomId = room.roomId;
* });
* ```
*/
[ClientEvent.Room]: (room: Room) => void;
/**
* Fires whenever a Room is removed. This will fire when you forget a room.
* <strong>This event is experimental and may change.</strong>
* @param roomId - The deleted room ID.
* @example
* ```
* matrixClient.on("deleteRoom", function(roomId){
* // update UI from getRooms()
* });
* ```
*/
[ClientEvent.DeleteRoom]: (roomId: string) => void;
[ClientEvent.SyncUnexpectedError]: (error: Error) => void;
/**
* Fires when the client .well-known info is fetched.
*
* @param data - The JSON object returned by the server
*/
[ClientEvent.ClientWellKnown]: (data: IClientWellKnown) => void;
[ClientEvent.ReceivedVoipEvent]: (event: MatrixEvent) => void;
[ClientEvent.TurnServers]: (servers: ITurnServer[]) => void;
[ClientEvent.TurnServersError]: (error: Error, fatal: boolean) => void;
} & RoomEventHandlerMap &
RoomStateEventHandlerMap &
CryptoEventHandlerMap &
MatrixEventHandlerMap &
RoomMemberEventHandlerMap &
UserEventHandlerMap &
CallEventHandlerEventHandlerMap &
GroupCallEventHandlerEventHandlerMap &
CallEventHandlerMap &
HttpApiEventHandlerMap &
BeaconEventHandlerMap;
const SSO_ACTION_PARAM = new UnstableValue("action", "org.matrix.msc3824.action");
/**
* Represents a Matrix Client. Only directly construct this if you want to use
* custom modules. Normally, {@link createClient} should be used
* as it specifies 'sensible' defaults for these modules.
*/
export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHandlerMap> {
public static readonly RESTORE_BACKUP_ERROR_BAD_KEY = "RESTORE_BACKUP_ERROR_BAD_KEY";
private readonly logger: Logger;
public reEmitter = new TypedReEmitter<EmittedEvents, ClientEventHandlerMap>(this);
public olmVersion: [number, number, number] | null = null; // populated after initCrypto
public usingExternalCrypto = false;
private _store!: Store;
public deviceId: string | null;
public credentials: { userId: string | null };
/**
* Encryption key used for encrypting sensitive data (such as e2ee keys) in storage.
*
* As supplied in the constructor via {@link IMatrixClientCreateOpts#pickleKey}.
*
* If unset, either a hardcoded key or no encryption at all is used, depending on the Crypto implementation.
*
* @deprecated this should be a private property.
*/
public pickleKey?: string;
public scheduler?: MatrixScheduler;
public clientRunning = false;
public timelineSupport = false;
public urlPreviewCache: { [key: string]: Promise<IPreviewUrlResponse> } = {};
public identityServer?: IIdentityServerProvider;
public http: MatrixHttpApi<IHttpOpts & { onlyData: true }>; // XXX: Intended private, used in code.
/**
* The libolm crypto implementation, if it is in use.
*
* @deprecated This should not be used. Instead, use the methods exposed directly on this class or
* (where they are available) via {@link getCrypto}.
*/
public crypto?: Crypto; // XXX: Intended private, used in code. Being replaced by cryptoBackend
private cryptoBackend?: CryptoBackend; // one of crypto or rustCrypto
public cryptoCallbacks: ICryptoCallbacks; // XXX: Intended private, used in code.
public callEventHandler?: CallEventHandler; // XXX: Intended private, used in code.
public groupCallEventHandler?: GroupCallEventHandler;
public supportsCallTransfer = false; // XXX: Intended private, used in code.
public forceTURN = false; // XXX: Intended private, used in code.
public iceCandidatePoolSize = 0; // XXX: Intended private, used in code.
public idBaseUrl?: string;
public baseUrl: string;
public readonly isVoipWithNoMediaAllowed;
public useLivekitForGroupCalls: boolean;
// Note: these are all `protected` to let downstream consumers make mistakes if they want to.
// We don't technically support this usage, but have reasons to do this.
protected canSupportVoip = false;
protected peekSync: SyncApi | null = null;
protected isGuestAccount = false;
protected ongoingScrollbacks: { [roomId: string]: { promise?: Promise<Room>; errorTs?: number } } = {};
protected notifTimelineSet: EventTimelineSet | null = null;
protected cryptoStore?: CryptoStore;
protected verificationMethods?: VerificationMethod[];
protected fallbackICEServerAllowed = false;
protected syncApi?: SlidingSyncSdk | SyncApi;
public roomNameGenerator?: ICreateClientOpts["roomNameGenerator"];
public pushRules?: IPushRules;
protected syncLeftRoomsPromise?: Promise<Room[]>;
protected syncedLeftRooms = false;
protected clientOpts?: IStoredClientOpts;
protected clientWellKnownIntervalID?: ReturnType<typeof setInterval>;
protected canResetTimelineCallback?: ResetTimelineCallback;
public canSupport = new Map<Feature, ServerSupport>();
// The pushprocessor caches useful things, so keep one and re-use it
protected pushProcessor = new PushProcessor(this);
// Promise to a response of the server's /versions response
// TODO: This should expire: https://github.com/matrix-org/matrix-js-sdk/issues/1020
protected serverVersionsPromise?: Promise<IServerVersions>;
public cachedCapabilities?: {
capabilities: Capabilities;
expiration: number;
};
protected clientWellKnown?: IClientWellKnown;
protected clientWellKnownPromise?: Promise<IClientWellKnown>;
protected turnServers: ITurnServer[] = [];
protected turnServersExpiry = 0;
protected checkTurnServersIntervalID?: ReturnType<typeof setInterval>;
protected exportedOlmDeviceToImport?: IExportedOlmDevice;
protected txnCtr = 0;
protected mediaHandler = new MediaHandler(this);
protected sessionId: string;
/** IDs of events which are currently being encrypted.
*
* This is part of the cancellation mechanism: if the event is no longer listed here when encryption completes,
* that tells us that it has been cancelled, and we should not send it.
*/
private eventsBeingEncrypted = new Set<string>();
private useE2eForGroupCall = true;
private toDeviceMessageQueue: ToDeviceMessageQueue;
public livekitServiceURL?: string;
private _secretStorage: ServerSideSecretStorageImpl;
// A manager for determining which invites should be ignored.
public readonly ignoredInvites: IgnoredInvites;
public readonly matrixRTC: MatrixRTCSessionManager;
public constructor(opts: IMatrixClientCreateOpts) {
super();
// If a custom logger is provided, use it. Otherwise, default to the global
// one in logger.ts.
this.logger = opts.logger ?? logger;
opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl);
opts.idBaseUrl = utils.ensureNoTrailingSlash(opts.idBaseUrl);
this.baseUrl = opts.baseUrl;
this.idBaseUrl = opts.idBaseUrl;
this.identityServer = opts.identityServer;
this.usingExternalCrypto = opts.usingExternalCrypto ?? false;
this.store = opts.store || new StubStore();
this.deviceId = opts.deviceId || null;
this.sessionId = randomString(10);
const userId = opts.userId || null;
this.credentials = { userId };
this.http = new MatrixHttpApi(this as ConstructorParameters<typeof MatrixHttpApi>[0], {
fetchFn: opts.fetchFn,
baseUrl: opts.baseUrl,
idBaseUrl: opts.idBaseUrl,
accessToken: opts.accessToken,
refreshToken: opts.refreshToken,
tokenRefreshFunction: opts.tokenRefreshFunction,
prefix: ClientPrefix.V3,
onlyData: true,
extraParams: opts.queryParams,
localTimeoutMs: opts.localTimeoutMs,
useAuthorizationHeader: opts.useAuthorizationHeader,
logger: this.logger,
});
if (opts.deviceToImport) {
if (this.deviceId) {
this.logger.warn(
"not importing device because device ID is provided to " +
"constructor independently of exported data",
);
} else if (this.credentials.userId) {
this.logger.warn(
"not importing device because user ID is provided to " +
"constructor independently of exported data",
);
} else if (!opts.deviceToImport.deviceId) {
this.logger.warn("not importing device because no device ID in exported data");
} else {
this.deviceId = opts.deviceToImport.deviceId;
this.credentials.userId = opts.deviceToImport.userId;
// will be used during async initialization of the crypto
this.exportedOlmDeviceToImport = opts.deviceToImport.olmDevice;
}
} else if (opts.pickleKey) {
this.pickleKey = opts.pickleKey;
}
this.useLivekitForGroupCalls = Boolean(opts.useLivekitForGroupCalls);
this.scheduler = opts.scheduler;
if (this.scheduler) {
this.scheduler.setProcessFunction(async (eventToSend: MatrixEvent) => {
const room = this.getRoom(eventToSend.getRoomId());
if (eventToSend.status !== EventStatus.SENDING) {
this.updatePendingEventStatus(room, eventToSend, EventStatus.SENDING);
}
const res = await this.sendEventHttpRequest(eventToSend);
if (room) {
// ensure we update pending event before the next scheduler run so that any listeners to event id
// updates on the synchronous event emitter get a chance to run first.
room.updatePendingEvent(eventToSend, EventStatus.SENT, res.event_id);
}
return res;
});
}
if (supportsMatrixCall()) {
this.callEventHandler = new CallEventHandler(this);
this.groupCallEventHandler = new GroupCallEventHandler(this);
this.canSupportVoip = true;
// Start listening for calls after the initial sync is done
// We do not need to backfill the call event buffer
// with encrypted events that might never get decrypted
this.on(ClientEvent.Sync, this.startCallEventHandler);
}
// NB. We initialise MatrixRTC whether we have call support or not: this is just
// the underlying session management and doesn't use any actual media capabilities
this.matrixRTC = new MatrixRTCSessionManager(this);
this.on(ClientEvent.Sync, this.fixupRoomNotifications);
this.timelineSupport = Boolean(opts.timelineSupport);
this.cryptoStore = opts.cryptoStore;
this.verificationMethods = opts.verificationMethods;
this.cryptoCallbacks = opts.cryptoCallbacks || {};
this.forceTURN = opts.forceTURN || false;
this.iceCandidatePoolSize = opts.iceCandidatePoolSize === undefined ? 0 : opts.iceCandidatePoolSize;
this.supportsCallTransfer = opts.supportsCallTransfer || false;
this.fallbackICEServerAllowed = opts.fallbackICEServerAllowed || false;
this.isVoipWithNoMediaAllowed = opts.isVoipWithNoMediaAllowed || false;
if (opts.useE2eForGroupCall !== undefined) this.useE2eForGroupCall = opts.useE2eForGroupCall;
this.livekitServiceURL = opts.livekitServiceURL;
this.roomNameGenerator = opts.roomNameGenerator;
this.toDeviceMessageQueue = new ToDeviceMessageQueue(this);
// The SDK doesn't really provide a clean way for events to recalculate the push
// actions for themselves, so we have to kinda help them out when they are encrypted.
// We do this so that push rules are correctly executed on events in their decrypted
// state, such as highlights when the user's name is mentioned.
this.on(MatrixEventEvent.Decrypted, (event) => {
fixNotificationCountOnDecryption(this, event);
});
// Like above, we have to listen for read receipts from ourselves in order to
// correctly handle notification counts on encrypted rooms.
// This fixes https://github.com/vector-im/element-web/issues/9