1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-06 12:02:40 +03:00

Improve typing around event emitter handlers (#2180)

This commit is contained in:
Michael Telatynski
2022-02-22 12:18:07 +00:00
committed by GitHub
parent 1ac4cc4b11
commit 12e525b664
41 changed files with 906 additions and 416 deletions

View File

@@ -31,6 +31,9 @@ module.exports = {
"no-async-promise-executor": "off",
// We use a `logger` intermediary module
"no-console": "error",
// restrict EventEmitters to force callers to use TypedEventEmitter
"no-restricted-imports": ["error", "events"],
},
overrides: [{
files: [

View File

@@ -1,4 +1,4 @@
import { EventStatus } from "../../src/matrix";
import { EventStatus, RoomEvent } from "../../src/matrix";
import { MatrixScheduler } from "../../src/scheduler";
import { Room } from "../../src/models/room";
import { TestClient } from "../TestClient";
@@ -95,7 +95,7 @@ describe("MatrixClient retrying", function() {
// wait for the localecho of ev1 to be updated
const p3 = new Promise<void>((resolve, reject) => {
room.on("Room.localEchoUpdated", (ev0) => {
room.on(RoomEvent.LocalEchoUpdated, (ev0) => {
if (ev0 === ev1) {
resolve();
}

View File

@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import { ReEmitter } from "../../src/ReEmitter";

View File

@@ -1,4 +1,5 @@
import '../olm-loader';
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import { Crypto } from "../../src/crypto";

View File

@@ -26,7 +26,7 @@ export async function resetCrossSigningKeys(client, {
crypto.crossSigningInfo.keys = oldKeys;
throw e;
}
crypto.baseApis.emit("crossSigning.keysChanged", {});
crypto.emit("crossSigning.keysChanged", {});
await crypto.afterCrossSigningLocalKeyChange();
}

View File

@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { VerificationBase } from '../../../../src/crypto/verification/Base';
import { CrossSigningInfo } from '../../../../src/crypto/CrossSigning';
import { encodeBase64 } from "../../../../src/crypto/olmlib";
import { setupWebcrypto, teardownWebcrypto } from './util';
import { VerificationBase } from '../../../../src/crypto/verification/Base';
jest.useFakeTimers();

View File

@@ -15,7 +15,7 @@ limitations under the License.
*/
import { EventTimelineSet } from "../../src/models/event-timeline-set";
import { MatrixEvent } from "../../src/models/event";
import { MatrixEvent, MatrixEventEvent } from "../../src/models/event";
import { Room } from "../../src/models/room";
import { Relations } from "../../src/models/relations";
@@ -103,7 +103,7 @@ describe("Relations", function() {
// Add the target event first, then the relation event
{
const relationsCreated = new Promise(resolve => {
targetEvent.once("Event.relationsCreated", resolve);
targetEvent.once(MatrixEventEvent.RelationsCreated, resolve);
});
const timelineSet = new EventTimelineSet(room, {
@@ -118,7 +118,7 @@ describe("Relations", function() {
// Add the relation event first, then the target event
{
const relationsCreated = new Promise(resolve => {
targetEvent.once("Event.relationsCreated", resolve);
targetEvent.once(MatrixEventEvent.RelationsCreated, resolve);
});
const timelineSet = new EventTimelineSet(room, {

View File

@@ -16,16 +16,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter";
export class ReEmitter {
private target: EventEmitter;
constructor(private readonly target: EventEmitter) {}
constructor(target: EventEmitter) {
this.target = target;
}
reEmit(source: EventEmitter, eventNames: string[]) {
public reEmit(source: EventEmitter, eventNames: string[]): void {
for (const eventName of eventNames) {
// We include the source as the last argument for event handlers which may need it,
// such as read receipt listeners on the client class which won't have the context
@@ -48,3 +47,19 @@ export class ReEmitter {
}
}
}
export class TypedReEmitter<
Events extends string,
Arguments extends ListenerMap<Events>,
> extends ReEmitter {
constructor(target: TypedEventEmitter<Events, Arguments>) {
super(target);
}
public reEmit<ReEmittedEvents extends string, T extends Events & ReEmittedEvents>(
source: TypedEventEmitter<ReEmittedEvents, any>,
eventNames: T[],
): void {
super.reEmit(source, eventNames);
}
}

View File

@@ -19,15 +19,22 @@ limitations under the License.
* @module client
*/
import { EventEmitter } from "events";
import { EmoteEvent, IPartialEvent, MessageEvent, NoticeEvent } from "matrix-events-sdk";
import { ISyncStateData, SyncApi, SyncState } from "./sync";
import { EventStatus, IContent, IDecryptOptions, IEvent, MatrixEvent } from "./models/event";
import {
EventStatus,
IContent,
IDecryptOptions,
IEvent,
MatrixEvent,
MatrixEventEvent,
MatrixEventHandlerMap,
} from "./models/event";
import { StubStore } from "./store/stub";
import { createNewMatrixCall, MatrixCall } from "./webrtc/call";
import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall } from "./webrtc/call";
import { Filter, IFilterDefinition } from "./filter";
import { CallEventHandler } from './webrtc/callEventHandler';
import { CallEventHandlerEvent, CallEventHandler, CallEventHandlerEventHandlerMap } from './webrtc/callEventHandler';
import * as utils from './utils';
import { sleep } from './utils';
import { Group } from "./models/group";
@@ -37,12 +44,12 @@ import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery";
import * as olmlib from "./crypto/olmlib";
import { decodeBase64, encodeBase64 } from "./crypto/olmlib";
import { IExportedDevice as IOlmDevice } from "./crypto/OlmDevice";
import { ReEmitter } from './ReEmitter';
import { TypedReEmitter } from './ReEmitter';
import { IRoomEncryption, RoomList } from './crypto/RoomList';
import { logger } from './logger';
import { SERVICE_TYPES } from './service-types';
import {
FileType,
FileType, HttpApiEvent, HttpApiEventHandlerMap,
IHttpOpts,
IUpload,
MatrixError,
@@ -58,6 +65,8 @@ import {
} from "./http-api";
import {
Crypto,
CryptoEvent,
CryptoEventHandlerMap,
fixBackupKey,
IBootstrapCrossSigningOpts,
ICheckOwnCrossSigningTrustOpts,
@@ -68,7 +77,7 @@ import {
import { DeviceInfo, IDevice } from "./crypto/deviceinfo";
import { decodeRecoveryKey } from './crypto/recoverykey';
import { keyFromAuthData } from './crypto/key_passphrase';
import { User } from "./models/user";
import { User, UserEvent, UserEventHandlerMap } from "./models/user";
import { getHttpUriForMxc } from "./content-repo";
import { SearchResult } from "./models/search-result";
import {
@@ -88,7 +97,20 @@ import {
} from "./crypto/keybackup";
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider";
import { MatrixScheduler } from "./scheduler";
import { IAuthData, ICryptoCallbacks, IMinimalEvent, IRoomEvent, IStateEvent, NotificationCountType } from "./matrix";
import {
IAuthData,
ICryptoCallbacks,
IMinimalEvent,
IRoomEvent,
IStateEvent,
NotificationCountType,
RoomEvent,
RoomEventHandlerMap,
RoomMemberEvent,
RoomMemberEventHandlerMap,
RoomStateEvent,
RoomStateEventHandlerMap,
} from "./matrix";
import {
CrossSigningKey,
IAddSecretStorageKeyOpts,
@@ -155,6 +177,7 @@ import { IThreepid } from "./@types/threepids";
import { CryptoStore } from "./crypto/store/base";
import { MediaHandler } from "./webrtc/mediaHandler";
import { IRefreshTokenResponse } from "./@types/auth";
import { TypedEventEmitter } from "./models/typed-event-emitter";
export type Store = IStore;
export type SessionStore = WebStorageSessionStore;
@@ -453,7 +476,7 @@ export interface ISignedKey {
}
export type KeySignatures = Record<string, Record<string, ICrossSigningKey | ISignedKey>>;
interface IUploadKeySignaturesResponse {
export interface IUploadKeySignaturesResponse {
failures: Record<string, Record<string, {
errcode: string;
error: string;
@@ -747,15 +770,107 @@ interface ITimestampToEventResponse {
// 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",
/* @deprecated */
Group = "Group",
// The following enum members are both deprecated and in the wrong place, Groups haven't been TSified
GroupProfile = "Group.profile",
GroupMyMembership = "Group.myMembership",
}
type RoomEvents = RoomEvent.Name
| RoomEvent.Redaction
| RoomEvent.RedactionCancelled
| RoomEvent.Receipt
| RoomEvent.Tags
| RoomEvent.LocalEchoUpdated
| RoomEvent.AccountData
| RoomEvent.MyMembership
| RoomEvent.Timeline
| RoomEvent.TimelineReset;
type RoomStateEvents = RoomStateEvent.Events
| RoomStateEvent.Members
| RoomStateEvent.NewMember;
type CryptoEvents = CryptoEvent.KeySignatureUploadFailure
| CryptoEvent.KeyBackupStatus
| CryptoEvent.KeyBackupFailed
| CryptoEvent.KeyBackupSessionsRemaining
| CryptoEvent.RoomKeyRequest
| CryptoEvent.RoomKeyRequestCancellation
| CryptoEvent.VerificationRequest
| CryptoEvent.DeviceVerificationChanged
| CryptoEvent.UserTrustStatusChanged
| CryptoEvent.KeysChanged
| CryptoEvent.Warning
| CryptoEvent.DevicesUpdated
| CryptoEvent.WillUpdateDevices;
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;
type EmittedEvents = ClientEvent
| RoomEvents
| RoomStateEvents
| CryptoEvents
| MatrixEventEvents
| RoomMemberEvents
| UserEvents
| CallEvent // re-emitted by call.ts using Object.values
| CallEventHandlerEvent.Incoming
| HttpApiEvent.SessionLoggedOut
| HttpApiEvent.NoConsent;
export type ClientEventHandlerMap = {
[ClientEvent.Sync]: (state: SyncState, lastState?: SyncState, data?: ISyncStateData) => void;
[ClientEvent.Event]: (event: MatrixEvent) => void;
[ClientEvent.ToDeviceEvent]: (event: MatrixEvent) => void;
[ClientEvent.AccountData]: (event: MatrixEvent, lastEvent?: MatrixEvent) => void;
[ClientEvent.Room]: (room: Room) => void;
[ClientEvent.DeleteRoom]: (roomId: string) => void;
[ClientEvent.SyncUnexpectedError]: (error: Error) => void;
[ClientEvent.ClientWellKnown]: (data: IClientWellKnown) => void;
[ClientEvent.Group]: (group: Group) => void;
[ClientEvent.GroupProfile]: (group: Group) => void;
[ClientEvent.GroupMyMembership]: (group: Group) => void;
} & RoomEventHandlerMap
& RoomStateEventHandlerMap
& CryptoEventHandlerMap
& MatrixEventHandlerMap
& RoomMemberEventHandlerMap
& UserEventHandlerMap
& CallEventHandlerEventHandlerMap
& CallEventHandlerMap
& HttpApiEventHandlerMap;
/**
* 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 EventEmitter {
export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHandlerMap> {
public static readonly RESTORE_BACKUP_ERROR_BAD_KEY = 'RESTORE_BACKUP_ERROR_BAD_KEY';
public reEmitter = new ReEmitter(this);
public reEmitter = new TypedReEmitter<EmittedEvents, ClientEventHandlerMap>(this);
public olmVersion: [number, number, number] = null; // populated after initCrypto
public usingExternalCrypto = false;
public store: Store;
@@ -836,7 +951,7 @@ export class MatrixClient extends EventEmitter {
const userId = opts.userId || null;
this.credentials = { userId };
this.http = new MatrixHttpApi(this, {
this.http = new MatrixHttpApi(this as ConstructorParameters<typeof MatrixHttpApi>[0], {
baseUrl: opts.baseUrl,
idBaseUrl: opts.idBaseUrl,
accessToken: opts.accessToken,
@@ -897,7 +1012,7 @@ export class MatrixClient extends EventEmitter {
// 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("sync", this.startCallEventHandler);
this.on(ClientEvent.Sync, this.startCallEventHandler);
}
this.timelineSupport = Boolean(opts.timelineSupport);
@@ -922,7 +1037,7 @@ export class MatrixClient extends EventEmitter {
// 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("Event.decrypted", (event) => {
this.on(MatrixEventEvent.Decrypted, (event) => {
const oldActions = event.getPushActions();
const actions = this.getPushActionsForEvent(event, true);
@@ -957,7 +1072,7 @@ export class MatrixClient extends EventEmitter {
// 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/9421
this.on("Room.receipt", (event, room) => {
this.on(RoomEvent.Receipt, (event, room) => {
if (room && this.isRoomEncrypted(room.roomId)) {
// Figure out if we've read something or if it's just informational
const content = event.getContent();
@@ -992,7 +1107,7 @@ export class MatrixClient extends EventEmitter {
// Note: we don't need to handle 'total' notifications because the counts
// will come from the server.
room.setUnreadNotificationCount("highlight", highlightCount);
room.setUnreadNotificationCount(NotificationCountType.Highlight, highlightCount);
}
});
}
@@ -1557,16 +1672,16 @@ export class MatrixClient extends EventEmitter {
);
this.reEmitter.reEmit(crypto, [
"crypto.keyBackupFailed",
"crypto.keyBackupSessionsRemaining",
"crypto.roomKeyRequest",
"crypto.roomKeyRequestCancellation",
"crypto.warning",
"crypto.devicesUpdated",
"crypto.willUpdateDevices",
"deviceVerificationChanged",
"userTrustStatusChanged",
"crossSigning.keysChanged",
CryptoEvent.KeyBackupFailed,
CryptoEvent.KeyBackupSessionsRemaining,
CryptoEvent.RoomKeyRequest,
CryptoEvent.RoomKeyRequestCancellation,
CryptoEvent.Warning,
CryptoEvent.DevicesUpdated,
CryptoEvent.WillUpdateDevices,
CryptoEvent.DeviceVerificationChanged,
CryptoEvent.UserTrustStatusChanged,
CryptoEvent.KeysChanged,
]);
logger.log("Crypto: initialising crypto object...");
@@ -1578,9 +1693,8 @@ export class MatrixClient extends EventEmitter {
this.olmVersion = Crypto.getOlmVersion();
// if crypto initialisation was successful, tell it to attach its event
// handlers.
crypto.registerEventHandlers(this);
// if crypto initialisation was successful, tell it to attach its event handlers.
crypto.registerEventHandlers(this as Parameters<Crypto["registerEventHandlers"]>[0]);
this.crypto = crypto;
}
@@ -1820,7 +1934,7 @@ export class MatrixClient extends EventEmitter {
* @returns {Verification} a verification object
* @deprecated Use `requestVerification` instead.
*/
public beginKeyVerification(method: string, userId: string, deviceId: string): Verification {
public beginKeyVerification(method: string, userId: string, deviceId: string): Verification<any, any> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
@@ -3660,7 +3774,7 @@ export class MatrixClient extends EventEmitter {
const targetId = localEvent.getAssociatedId();
if (targetId && targetId.startsWith("~")) {
const target = room.getPendingEvents().find(e => e.getId() === targetId);
target.once("Event.localEventIdReplaced", () => {
target.once(MatrixEventEvent.LocalEventIdReplaced, () => {
localEvent.updateAssociatedId(target.getId());
});
}
@@ -4758,7 +4872,7 @@ export class MatrixClient extends EventEmitter {
}
return promise.then((response) => {
this.store.removeRoom(roomId);
this.emit("deleteRoom", roomId);
this.emit(ClientEvent.DeleteRoom, roomId);
return response;
});
}
@@ -4911,7 +5025,7 @@ export class MatrixClient extends EventEmitter {
const user = this.getUser(this.getUserId());
if (user) {
user.displayName = name;
user.emit("User.displayName", user.events.presence, user);
user.emit(UserEvent.DisplayName, user.events.presence, user);
}
return prom;
}
@@ -4928,7 +5042,7 @@ export class MatrixClient extends EventEmitter {
const user = this.getUser(this.getUserId());
if (user) {
user.avatarUrl = url;
user.emit("User.avatarUrl", user.events.presence, user);
user.emit(UserEvent.AvatarUrl, user.events.presence, user);
}
return prom;
}
@@ -6098,7 +6212,7 @@ export class MatrixClient extends EventEmitter {
private startCallEventHandler = (): void => {
if (this.isInitialSyncComplete()) {
this.callEventHandler.start();
this.off("sync", this.startCallEventHandler);
this.off(ClientEvent.Sync, this.startCallEventHandler);
}
};
@@ -6246,7 +6360,7 @@ export class MatrixClient extends EventEmitter {
// it absorbs errors and returns `{}`.
this.clientWellKnownPromise = AutoDiscovery.getRawClientConfig(this.getDomain());
this.clientWellKnown = await this.clientWellKnownPromise;
this.emit("WellKnown.client", this.clientWellKnown);
this.emit(ClientEvent.ClientWellKnown, this.clientWellKnown);
}
public getClientWellKnown(): IClientWellKnown {
@@ -6510,7 +6624,7 @@ export class MatrixClient extends EventEmitter {
const allEvents = originalEvent ? events.concat(originalEvent) : events;
await Promise.all(allEvents.map(e => {
if (e.isEncrypted()) {
return new Promise(resolve => e.once("Event.decrypted", resolve));
return new Promise(resolve => e.once(MatrixEventEvent.Decrypted, resolve));
}
}));
events = events.filter(e => e.getType() === eventType);

View File

@@ -19,7 +19,6 @@ limitations under the License.
* @module crypto/CrossSigning
*/
import { EventEmitter } from 'events';
import { PkSigning } from "@matrix-org/olm";
import { decodeBase64, encodeBase64, pkSign, pkVerify } from './olmlib';
@@ -55,7 +54,7 @@ export interface ICrossSigningInfo {
crossSigningVerifiedBefore: boolean;
}
export class CrossSigningInfo extends EventEmitter {
export class CrossSigningInfo {
public keys: Record<string, ICrossSigningKey> = {};
public firstUse = true;
// This tracks whether we've ever verified this user with any identity.
@@ -79,9 +78,7 @@ export class CrossSigningInfo extends EventEmitter {
public readonly userId: string,
private callbacks: ICryptoCallbacks = {},
private cacheCallbacks: ICacheCallbacks = {},
) {
super();
}
) {}
public static fromStorage(obj: ICrossSigningInfo, userId: string): CrossSigningInfo {
const res = new CrossSigningInfo(userId);

View File

@@ -20,8 +20,6 @@ limitations under the License.
* Manages the list of other users' devices
*/
import { EventEmitter } from 'events';
import { logger } from '../logger';
import { DeviceInfo, IDevice } from './deviceinfo';
import { CrossSigningInfo, ICrossSigningInfo } from './CrossSigning';
@@ -31,6 +29,8 @@ import { chunkPromises, defer, IDeferred, sleep } from '../utils';
import { IDownloadKeyResult, MatrixClient } from "../client";
import { OlmDevice } from "./OlmDevice";
import { CryptoStore } from "./store/base";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { CryptoEvent, CryptoEventHandlerMap } from "./index";
/* State transition diagram for DeviceList.deviceTrackingStatus
*
@@ -62,10 +62,12 @@ export enum TrackingStatus {
export type DeviceInfoMap = Record<string, Record<string, DeviceInfo>>;
type EmittedEvents = CryptoEvent.WillUpdateDevices | CryptoEvent.DevicesUpdated | CryptoEvent.UserCrossSigningUpdated;
/**
* @alias module:crypto/DeviceList
*/
export class DeviceList extends EventEmitter {
export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHandlerMap> {
private devices: { [userId: string]: { [deviceId: string]: IDevice } } = {};
public crossSigningInfo: { [userId: string]: ICrossSigningInfo } = {};
@@ -634,7 +636,7 @@ export class DeviceList extends EventEmitter {
});
const finished = (success: boolean): void => {
this.emit("crypto.willUpdateDevices", users, !this.hasFetched);
this.emit(CryptoEvent.WillUpdateDevices, users, !this.hasFetched);
users.forEach((u) => {
this.dirty = true;
@@ -659,7 +661,7 @@ export class DeviceList extends EventEmitter {
}
});
this.saveIfDirty();
this.emit("crypto.devicesUpdated", users, !this.hasFetched);
this.emit(CryptoEvent.DevicesUpdated, users, !this.hasFetched);
this.hasFetched = true;
};
@@ -867,7 +869,7 @@ class DeviceListUpdateSerialiser {
// NB. Unlike most events in the js-sdk, this one is internal to the
// js-sdk and is not re-emitted
this.deviceList.emit('userCrossSigningUpdated', userId);
this.deviceList.emit(CryptoEvent.UserCrossSigningUpdated, userId);
}
}
}

View File

@@ -14,17 +14,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventEmitter } from "events";
import { logger } from "../logger";
import { MatrixEvent } from "../models/event";
import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { Method, PREFIX_UNSTABLE } from "../http-api";
import { Crypto, IBootstrapCrossSigningOpts } from "./index";
import { CrossSigningKeys, ICrossSigningKey, ICryptoCallbacks, ISignedKey, KeySignatures } from "../matrix";
import {
ClientEvent,
CrossSigningKeys,
ClientEventHandlerMap,
ICrossSigningKey,
ICryptoCallbacks,
ISignedKey,
KeySignatures,
} from "../matrix";
import { ISecretStorageKeyInfo } from "./api";
import { IKeyBackupInfo } from "./keybackup";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { IAccountDataClient } from "./SecretStorage";
interface ICrossSigningKeys {
authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
@@ -256,7 +264,10 @@ export class EncryptionSetupOperation {
* Catches account data set by SecretStorage during bootstrapping by
* implementing the methods related to account data in MatrixClient
*/
class AccountDataClientAdapter extends EventEmitter {
class AccountDataClientAdapter
extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap>
implements IAccountDataClient {
//
public readonly values = new Map<string, MatrixEvent>();
/**
@@ -303,7 +314,7 @@ class AccountDataClientAdapter extends EventEmitter {
// and it seems to rely on this.
return Promise.resolve().then(() => {
const event = new MatrixEvent({ type, content });
this.emit("accountData", event, lastEvent);
this.emit(ClientEvent.AccountData, event, lastEvent);
return {};
});
}

View File

@@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventEmitter } from 'stream';
import { logger } from '../logger';
import * as olmlib from './olmlib';
import { encodeBase64 } from './olmlib';
import { randomString } from '../randomstring';
import { encryptAES, decryptAES, IEncryptedPayload, calculateKeyCheck } from './aes';
import { encodeBase64 } from "./olmlib";
import { ICryptoCallbacks, MatrixClient, MatrixEvent } from '../matrix';
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes';
import { ClientEvent, ICryptoCallbacks, MatrixEvent } from '../matrix';
import { ClientEventHandlerMap, MatrixClient } from "../client";
import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api';
import { TypedEventEmitter } from '../models/typed-event-emitter';
export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";
@@ -36,7 +36,7 @@ export interface ISecretRequest {
cancel: (reason: string) => void;
}
export interface IAccountDataClient extends EventEmitter {
export interface IAccountDataClient extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap> {
// Subset of MatrixClient (which also uses any for the event content)
getAccountDataFromServer: <T extends {[k: string]: any}>(eventType: string) => Promise<T>;
getAccountData: (eventType: string) => MatrixEvent;
@@ -98,17 +98,17 @@ export class SecretStorage {
ev.getType() === 'm.secret_storage.default_key' &&
ev.getContent().key === keyId
) {
this.accountDataAdapter.removeListener('accountData', listener);
this.accountDataAdapter.removeListener(ClientEvent.AccountData, listener);
resolve();
}
};
this.accountDataAdapter.on('accountData', listener);
this.accountDataAdapter.on(ClientEvent.AccountData, listener);
this.accountDataAdapter.setAccountData(
'm.secret_storage.default_key',
{ key: keyId },
).catch(e => {
this.accountDataAdapter.removeListener('accountData', listener);
this.accountDataAdapter.removeListener(ClientEvent.AccountData, listener);
reject(e);
});
});

View File

@@ -26,14 +26,13 @@ import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib";
import { DeviceInfo } from "./deviceinfo";
import { DeviceTrustLevel } from './CrossSigning';
import { keyFromPassphrase } from './key_passphrase';
import { sleep } from "../utils";
import { getCrypto, sleep } from "../utils";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { encodeRecoveryKey } from './recoverykey';
import { encryptAES, decryptAES, calculateKeyCheck } from './aes';
import { getCrypto } from '../utils';
import { ICurve25519AuthData, IAes256AuthData, IKeyBackupInfo, IKeyBackupSession } from "./keybackup";
import { calculateKeyCheck, decryptAES, encryptAES } from './aes';
import { IAes256AuthData, ICurve25519AuthData, IKeyBackupInfo, IKeyBackupSession } from "./keybackup";
import { UnstableValue } from "../NamespacedValue";
import { IMegolmSessionData } from "./index";
import { CryptoEvent, IMegolmSessionData } from "./index";
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
@@ -155,7 +154,7 @@ export class BackupManager {
this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey);
this.baseApis.emit('crypto.keyBackupStatus', true);
this.baseApis.emit(CryptoEvent.KeyBackupStatus, true);
// There may be keys left over from a partially completed backup, so
// schedule a send to check.
@@ -173,7 +172,7 @@ export class BackupManager {
this.backupInfo = undefined;
this.baseApis.emit('crypto.keyBackupStatus', false);
this.baseApis.emit(CryptoEvent.KeyBackupStatus, false);
}
public getKeyBackupEnabled(): boolean | null {
@@ -458,7 +457,7 @@ export class BackupManager {
await this.checkKeyBackup();
// Backup version has changed or this backup version
// has been deleted
this.baseApis.crypto.emit("crypto.keyBackupFailed", err.data.errcode);
this.baseApis.crypto.emit(CryptoEvent.KeyBackupFailed, err.data.errcode);
throw err;
}
}
@@ -487,7 +486,7 @@ export class BackupManager {
}
let remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
this.baseApis.crypto.emit("crypto.keyBackupSessionsRemaining", remaining);
this.baseApis.crypto.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
const rooms: IKeyBackup["rooms"] = {};
for (const session of sessions) {
@@ -524,7 +523,7 @@ export class BackupManager {
await this.baseApis.crypto.cryptoStore.unmarkSessionsNeedingBackup(sessions);
remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
this.baseApis.crypto.emit("crypto.keyBackupSessionsRemaining", remaining);
this.baseApis.crypto.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
return sessions.length;
}
@@ -580,7 +579,7 @@ export class BackupManager {
);
const remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
this.baseApis.emit("crypto.keyBackupSessionsRemaining", remaining);
this.baseApis.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
return remaining;
}

View File

@@ -22,27 +22,36 @@ limitations under the License.
*/
import anotherjson from "another-json";
import { EventEmitter } from 'events';
import { ReEmitter } from '../ReEmitter';
import { TypedReEmitter } from '../ReEmitter';
import { logger } from '../logger';
import { IExportedDevice, OlmDevice } from "./OlmDevice";
import * as olmlib from "./olmlib";
import { DeviceInfoMap, DeviceList } from "./DeviceList";
import { DeviceInfo, IDevice } from "./deviceinfo";
import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms";
import * as algorithms from "./algorithms";
import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from './CrossSigning';
import { EncryptionSetupBuilder } from "./EncryptionSetup";
import {
IAccountDataClient,
ISecretRequest,
SECRET_STORAGE_ALGORITHM_V1_AES,
SecretStorage,
SecretStorageKeyTuple,
ISecretRequest,
SecretStorageKeyObject,
SecretStorageKeyTuple,
} from './SecretStorage';
import { IAddSecretStorageKeyOpts, ICreateSecretStorageOpts, IImportRoomKeysOpts, ISecretStorageKeyInfo } from "./api";
import {
IAddSecretStorageKeyOpts,
ICreateSecretStorageOpts,
IEncryptedEventInfo,
IImportRoomKeysOpts,
IRecoveryKey,
ISecretStorageKeyInfo,
} from "./api";
import { OutgoingRoomKeyRequestManager } from './OutgoingRoomKeyRequestManager';
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { VerificationBase } from "./verification/Base";
import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from './verification/QRCode';
import { SAS as SASVerification } from './verification/SAS';
import { keyFromPassphrase } from './key_passphrase';
@@ -52,21 +61,28 @@ import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChan
import { ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel";
import { IllegalMethod } from "./verification/IllegalMethod";
import { KeySignatureUploadError } from "../errors";
import { decryptAES, encryptAES, calculateKeyCheck } from './aes';
import { calculateKeyCheck, decryptAES, encryptAES } from './aes';
import { DehydrationManager, IDeviceKeys, IOneTimeKey } from './dehydration';
import { BackupManager } from "./backup";
import { IStore } from "../store";
import { Room } from "../models/room";
import { RoomMember } from "../models/room-member";
import { MatrixEvent, EventStatus, IClearEvent, IEvent } from "../models/event";
import { MatrixClient, IKeysUploadResponse, SessionStore, ISignedKey, ICrossSigningKey } from "../client";
import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base";
import { Room, RoomEvent } from "../models/room";
import { RoomMember, RoomMemberEvent } from "../models/room-member";
import { EventStatus, IClearEvent, IEvent, MatrixEvent, MatrixEventEvent } from "../models/event";
import {
ClientEvent,
ICrossSigningKey,
IKeysUploadResponse,
ISignedKey,
IUploadKeySignaturesResponse,
MatrixClient,
SessionStore,
} from "../client";
import type { IRoomEncryption, RoomList } from "./RoomList";
import { IRecoveryKey, IEncryptedEventInfo } from "./api";
import { IKeyBackupInfo } from "./keybackup";
import { ISyncStateData } from "../sync";
import { CryptoStore } from "./store/base";
import { IVerificationChannel } from "./verification/request/Channel";
import { TypedEventEmitter } from "../models/typed-event-emitter";
const DeviceVerification = DeviceInfo.DeviceVerification;
@@ -186,7 +202,45 @@ export interface IRequestsMap {
setRequestByChannel(channel: IVerificationChannel, request: VerificationRequest): void;
}
export class Crypto extends EventEmitter {
export enum CryptoEvent {
DeviceVerificationChanged = "deviceVerificationChanged",
UserTrustStatusChanged = "userTrustStatusChanged",
UserCrossSigningUpdated = "userCrossSigningUpdated",
RoomKeyRequest = "crypto.roomKeyRequest",
RoomKeyRequestCancellation = "crypto.roomKeyRequestCancellation",
KeyBackupStatus = "crypto.keyBackupStatus",
KeyBackupFailed = "crypto.keyBackupFailed",
KeyBackupSessionsRemaining = "crypto.keyBackupSessionsRemaining",
KeySignatureUploadFailure = "crypto.keySignatureUploadFailure",
VerificationRequest = "crypto.verification.request",
Warning = "crypto.warning",
WillUpdateDevices = "crypto.willUpdateDevices",
DevicesUpdated = "crypto.devicesUpdated",
KeysChanged = "crossSigning.keysChanged",
}
export type CryptoEventHandlerMap = {
[CryptoEvent.DeviceVerificationChanged]: (userId: string, deviceId: string, device: DeviceInfo) => void;
[CryptoEvent.UserTrustStatusChanged]: (userId: string, trustLevel: UserTrustLevel) => void;
[CryptoEvent.RoomKeyRequest]: (request: IncomingRoomKeyRequest) => void;
[CryptoEvent.RoomKeyRequestCancellation]: (request: IncomingRoomKeyRequestCancellation) => void;
[CryptoEvent.KeyBackupStatus]: (enabled: boolean) => void;
[CryptoEvent.KeyBackupFailed]: (errcode: string) => void;
[CryptoEvent.KeyBackupSessionsRemaining]: (remaining: number) => void;
[CryptoEvent.KeySignatureUploadFailure]: (
failures: IUploadKeySignaturesResponse["failures"],
source: "checkOwnCrossSigningTrust" | "afterCrossSigningLocalKeyChange" | "setDeviceVerification",
upload: (opts: { shouldEmit: boolean }) => Promise<void>
) => void;
[CryptoEvent.VerificationRequest]: (request: VerificationRequest<any>) => void;
[CryptoEvent.Warning]: (type: string) => void;
[CryptoEvent.KeysChanged]: (data: {}) => void;
[CryptoEvent.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void;
[CryptoEvent.DevicesUpdated]: (users: string[], initialFetch: boolean) => void;
[CryptoEvent.UserCrossSigningUpdated]: (userId: string) => void;
};
export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap> {
/**
* @return {string} The version of Olm.
*/
@@ -201,8 +255,8 @@ export class Crypto extends EventEmitter {
public readonly dehydrationManager: DehydrationManager;
public readonly secretStorage: SecretStorage;
private readonly reEmitter: ReEmitter;
private readonly verificationMethods: any; // TODO types
private readonly reEmitter: TypedReEmitter<CryptoEvent, CryptoEventHandlerMap>;
private readonly verificationMethods: Map<VerificationMethod, typeof VerificationBase>;
public readonly supportedAlgorithms: string[];
private readonly outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager;
private readonly toDeviceVerificationRequests: ToDeviceRequests;
@@ -295,10 +349,10 @@ export class Crypto extends EventEmitter {
private readonly clientStore: IStore,
public readonly cryptoStore: CryptoStore,
private readonly roomList: RoomList,
verificationMethods: any[], // TODO types
verificationMethods: Array<keyof typeof defaultVerificationMethods | typeof VerificationBase>,
) {
super();
this.reEmitter = new ReEmitter(this);
this.reEmitter = new TypedReEmitter(this);
if (verificationMethods) {
this.verificationMethods = new Map();
@@ -307,20 +361,21 @@ export class Crypto extends EventEmitter {
if (defaultVerificationMethods[method]) {
this.verificationMethods.set(
method,
defaultVerificationMethods[method],
<typeof VerificationBase>defaultVerificationMethods[method],
);
}
} else if (method.NAME) {
} else if (method["NAME"]) {
this.verificationMethods.set(
method.NAME,
method,
method["NAME"],
method as typeof VerificationBase,
);
} else {
logger.warn(`Excluding unknown verification method ${method}`);
}
}
} else {
this.verificationMethods = defaultVerificationMethods;
this.verificationMethods =
new Map(Object.entries(defaultVerificationMethods)) as Map<VerificationMethod, typeof VerificationBase>;
}
this.backupManager = new BackupManager(baseApis, async () => {
@@ -358,8 +413,8 @@ export class Crypto extends EventEmitter {
// XXX: This isn't removed at any point, but then none of the event listeners
// this class sets seem to be removed at any point... :/
this.deviceList.on('userCrossSigningUpdated', this.onDeviceListUserCrossSigningUpdated);
this.reEmitter.reEmit(this.deviceList, ["crypto.devicesUpdated", "crypto.willUpdateDevices"]);
this.deviceList.on(CryptoEvent.UserCrossSigningUpdated, this.onDeviceListUserCrossSigningUpdated);
this.reEmitter.reEmit(this.deviceList, [CryptoEvent.DevicesUpdated, CryptoEvent.WillUpdateDevices]);
this.supportedAlgorithms = Object.keys(algorithms.DECRYPTION_CLASSES);
@@ -375,7 +430,7 @@ export class Crypto extends EventEmitter {
this.crossSigningInfo = new CrossSigningInfo(userId, cryptoCallbacks, cacheCallbacks);
// Yes, we pass the client twice here: see SecretStorage
this.secretStorage = new SecretStorage(baseApis, cryptoCallbacks, baseApis);
this.secretStorage = new SecretStorage(baseApis as IAccountDataClient, cryptoCallbacks, baseApis);
this.dehydrationManager = new DehydrationManager(this);
// Assuming no app-supplied callback, default to getting from SSSS.
@@ -487,7 +542,7 @@ export class Crypto extends EventEmitter {
deviceTrust.isCrossSigningVerified()
) {
const deviceObj = this.deviceList.getStoredDevice(userId, deviceId);
this.emit("deviceVerificationChanged", userId, deviceId, deviceObj);
this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj);
}
}
}
@@ -1165,7 +1220,7 @@ export class Crypto extends EventEmitter {
if (Object.keys(failures || []).length > 0) {
if (shouldEmit) {
this.baseApis.emit(
"crypto.keySignatureUploadFailure",
CryptoEvent.KeySignatureUploadFailure,
failures,
"afterCrossSigningLocalKeyChange",
upload, // continuation
@@ -1391,11 +1446,10 @@ export class Crypto extends EventEmitter {
// that reset the keys
this.storeTrustedSelfKeys(null);
// emit cross-signing has been disabled
this.emit("crossSigning.keysChanged", {});
this.emit(CryptoEvent.KeysChanged, {});
// as the trust for our own user has changed,
// also emit an event for this
this.emit("userTrustStatusChanged",
this.userId, this.checkUserTrust(userId));
this.emit(CryptoEvent.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId));
}
} else {
await this.checkDeviceVerifications(userId);
@@ -1410,7 +1464,7 @@ export class Crypto extends EventEmitter {
this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage());
}
this.emit("userTrustStatusChanged", userId, this.checkUserTrust(userId));
this.emit(CryptoEvent.UserTrustStatusChanged, userId, this.checkUserTrust(userId));
}
};
@@ -1567,7 +1621,7 @@ export class Crypto extends EventEmitter {
if (Object.keys(failures || []).length > 0) {
if (shouldEmit) {
this.baseApis.emit(
"crypto.keySignatureUploadFailure",
CryptoEvent.KeySignatureUploadFailure,
failures,
"checkOwnCrossSigningTrust",
upload,
@@ -1585,10 +1639,10 @@ export class Crypto extends EventEmitter {
upload({ shouldEmit: true });
}
this.emit("userTrustStatusChanged", userId, this.checkUserTrust(userId));
this.emit(CryptoEvent.UserTrustStatusChanged, userId, this.checkUserTrust(userId));
if (masterChanged) {
this.baseApis.emit("crossSigning.keysChanged", {});
this.emit(CryptoEvent.KeysChanged, {});
await this.afterCrossSigningLocalKeyChange();
}
@@ -1675,18 +1729,14 @@ export class Crypto extends EventEmitter {
* @param {external:EventEmitter} eventEmitter event source where we can register
* for event notifications
*/
public registerEventHandlers(eventEmitter: EventEmitter): void {
eventEmitter.on("RoomMember.membership", (event: MatrixEvent, member: RoomMember, oldMembership?: string) => {
try {
this.onRoomMembership(event, member, oldMembership);
} catch (e) {
logger.error("Error handling membership change:", e);
}
});
eventEmitter.on("toDeviceEvent", this.onToDeviceEvent);
eventEmitter.on("Room.timeline", this.onTimelineEvent);
eventEmitter.on("Event.decrypted", this.onTimelineEvent);
public registerEventHandlers(eventEmitter: TypedEventEmitter<
RoomMemberEvent.Membership | ClientEvent.ToDeviceEvent | RoomEvent.Timeline | MatrixEventEvent.Decrypted,
any
>): void {
eventEmitter.on(RoomMemberEvent.Membership, this.onMembership);
eventEmitter.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
eventEmitter.on(RoomEvent.Timeline, this.onTimelineEvent);
eventEmitter.on(MatrixEventEvent.Decrypted, this.onTimelineEvent);
}
/** Start background processes related to crypto */
@@ -2070,9 +2120,7 @@ export class Crypto extends EventEmitter {
if (!this.crossSigningInfo.getId() && userId === this.crossSigningInfo.userId) {
this.storeTrustedSelfKeys(xsk.keys);
// This will cause our own user trust to change, so emit the event
this.emit(
"userTrustStatusChanged", this.userId, this.checkUserTrust(userId),
);
this.emit(CryptoEvent.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId));
}
// Now sign the master key with our user signing key (unless it's ourself)
@@ -2094,7 +2142,7 @@ export class Crypto extends EventEmitter {
if (Object.keys(failures || []).length > 0) {
if (shouldEmit) {
this.baseApis.emit(
"crypto.keySignatureUploadFailure",
CryptoEvent.KeySignatureUploadFailure,
failures,
"setDeviceVerification",
upload,
@@ -2178,7 +2226,7 @@ export class Crypto extends EventEmitter {
if (Object.keys(failures || []).length > 0) {
if (shouldEmit) {
this.baseApis.emit(
"crypto.keySignatureUploadFailure",
CryptoEvent.KeySignatureUploadFailure,
failures,
"setDeviceVerification",
upload, // continuation
@@ -2193,7 +2241,7 @@ export class Crypto extends EventEmitter {
}
const deviceObj = DeviceInfo.fromStorage(dev, deviceId);
this.emit("deviceVerificationChanged", userId, deviceId, deviceObj);
this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj);
return deviceObj;
}
@@ -3045,6 +3093,14 @@ export class Crypto extends EventEmitter {
});
}
private onMembership = (event: MatrixEvent, member: RoomMember, oldMembership?: string) => {
try {
this.onRoomMembership(event, member, oldMembership);
} catch (e) {
logger.error("Error handling membership change:", e);
}
};
private onToDeviceEvent = (event: MatrixEvent): void => {
try {
logger.log(`received to_device ${event.getType()} from: ` +
@@ -3070,7 +3126,7 @@ export class Crypto extends EventEmitter {
event.attemptDecryption(this);
}
// once the event has been decrypted, try again
event.once('Event.decrypted', (ev) => {
event.once(MatrixEventEvent.Decrypted, (ev) => {
this.onToDeviceEvent(ev);
});
}
@@ -3219,15 +3275,15 @@ export class Crypto extends EventEmitter {
reject(new Error("Event status set to CANCELLED."));
}
};
event.once("Event.localEventIdReplaced", eventIdListener);
event.on("Event.status", statusListener);
event.once(MatrixEventEvent.LocalEventIdReplaced, eventIdListener);
event.on(MatrixEventEvent.Status, statusListener);
});
} catch (err) {
logger.error("error while waiting for the verification event to be sent: " + err.message);
return;
} finally {
event.removeListener("Event.localEventIdReplaced", eventIdListener);
event.removeListener("Event.status", statusListener);
event.removeListener(MatrixEventEvent.LocalEventIdReplaced, eventIdListener);
event.removeListener(MatrixEventEvent.Status, statusListener);
}
}
let request = requestsMap.getRequest(event);
@@ -3254,7 +3310,7 @@ export class Crypto extends EventEmitter {
!request.invalid && // check it has enough events to pass the UNSENT stage
!request.observeOnly;
if (shouldEmit) {
this.baseApis.emit("crypto.verification.request", request);
this.baseApis.emit(CryptoEvent.VerificationRequest, request);
}
}
@@ -3555,7 +3611,7 @@ export class Crypto extends EventEmitter {
return;
}
this.emit("crypto.roomKeyRequest", req);
this.emit(CryptoEvent.RoomKeyRequest, req);
}
/**
@@ -3574,7 +3630,7 @@ export class Crypto extends EventEmitter {
// we should probably only notify the app of cancellations we told it
// about, but we don't currently have a record of that, so we just pass
// everything through.
this.emit("crypto.roomKeyRequestCancellation", cancellation);
this.emit(CryptoEvent.RoomKeyRequestCancellation, cancellation);
}
/**

View File

@@ -20,8 +20,6 @@ limitations under the License.
* @module crypto/verification/Base
*/
import { EventEmitter } from 'events';
import { MatrixEvent } from '../../models/event';
import { logger } from '../../logger';
import { DeviceInfo } from '../deviceinfo';
@@ -30,6 +28,7 @@ import { KeysDuringVerification, requestKeysDuringVerification } from "../CrossS
import { IVerificationChannel } from "./request/Channel";
import { MatrixClient } from "../../client";
import { VerificationRequest } from "./request/VerificationRequest";
import { ListenerMap, TypedEventEmitter } from "../../models/typed-event-emitter";
const timeoutException = new Error("Verification timed out");
@@ -41,7 +40,18 @@ export class SwitchStartEventError extends Error {
export type KeyVerifier = (keyId: string, device: DeviceInfo, keyInfo: string) => void;
export class VerificationBase extends EventEmitter {
export enum VerificationEvent {
Cancel = "cancel",
}
export type VerificationEventHandlerMap = {
[VerificationEvent.Cancel]: (e: Error | MatrixEvent) => void;
};
export class VerificationBase<
Events extends string,
Arguments extends ListenerMap<Events | VerificationEvent>,
> extends TypedEventEmitter<Events | VerificationEvent, Arguments, VerificationEventHandlerMap> {
private cancelled = false;
private _done = false;
private promise: Promise<void> = null;
@@ -261,7 +271,7 @@ export class VerificationBase extends EventEmitter {
}
// Also emit a 'cancel' event that the app can listen for to detect cancellation
// before calling verify()
this.emit('cancel', e);
this.emit(VerificationEvent.Cancel, e);
}
}

View File

@@ -20,7 +20,7 @@ limitations under the License.
* @module crypto/verification/IllegalMethod
*/
import { VerificationBase as Base } from "./Base";
import { VerificationBase as Base, VerificationEvent, VerificationEventHandlerMap } from "./Base";
import { IVerificationChannel } from "./request/Channel";
import { MatrixClient } from "../../client";
import { MatrixEvent } from "../../models/event";
@@ -30,7 +30,7 @@ import { VerificationRequest } from "./request/VerificationRequest";
* @class crypto/verification/IllegalMethod/IllegalMethod
* @extends {module:crypto/verification/Base}
*/
export class IllegalMethod extends Base {
export class IllegalMethod extends Base<VerificationEvent, VerificationEventHandlerMap> {
public static factory(
channel: IVerificationChannel,
baseApis: MatrixClient,

View File

@@ -19,7 +19,7 @@ limitations under the License.
* @module crypto/verification/QRCode
*/
import { VerificationBase as Base } from "./Base";
import { VerificationBase as Base, VerificationEventHandlerMap } from "./Base";
import { newKeyMismatchError, newUserCancelledError } from './Error';
import { decodeBase64, encodeUnpaddedBase64 } from "../olmlib";
import { logger } from '../../logger';
@@ -31,15 +31,25 @@ import { MatrixEvent } from "../../models/event";
export const SHOW_QR_CODE_METHOD = "m.qr_code.show.v1";
export const SCAN_QR_CODE_METHOD = "m.qr_code.scan.v1";
interface IReciprocateQr {
confirm(): void;
cancel(): void;
}
export enum QrCodeEvent {
ShowReciprocateQr = "show_reciprocate_qr",
}
type EventHandlerMap = {
[QrCodeEvent.ShowReciprocateQr]: (qr: IReciprocateQr) => void;
} & VerificationEventHandlerMap;
/**
* @class crypto/verification/QRCode/ReciprocateQRCode
* @extends {module:crypto/verification/Base}
*/
export class ReciprocateQRCode extends Base {
public reciprocateQREvent: {
confirm(): void;
cancel(): void;
};
export class ReciprocateQRCode extends Base<QrCodeEvent, EventHandlerMap> {
public reciprocateQREvent: IReciprocateQr;
public static factory(
channel: IVerificationChannel,
@@ -76,7 +86,7 @@ export class ReciprocateQRCode extends Base {
confirm: resolve,
cancel: () => reject(newUserCancelledError()),
};
this.emit("show_reciprocate_qr", this.reciprocateQREvent);
this.emit(QrCodeEvent.ShowReciprocateQr, this.reciprocateQREvent);
});
// 3. determine key to sign / mark as trusted

View File

@@ -22,7 +22,7 @@ limitations under the License.
import anotherjson from 'another-json';
import { Utility, SAS as OlmSAS } from "@matrix-org/olm";
import { VerificationBase as Base, SwitchStartEventError } from "./Base";
import { VerificationBase as Base, SwitchStartEventError, VerificationEventHandlerMap } from "./Base";
import {
errorFactory,
newInvalidMessageError,
@@ -232,11 +232,19 @@ function intersection<T>(anArray: T[], aSet: Set<T>): T[] {
return anArray instanceof Array ? anArray.filter(x => aSet.has(x)) : [];
}
export enum SasEvent {
ShowSas = "show_sas",
}
type EventHandlerMap = {
[SasEvent.ShowSas]: (sas: ISasEvent) => void;
} & VerificationEventHandlerMap;
/**
* @alias module:crypto/verification/SAS
* @extends {module:crypto/verification/Base}
*/
export class SAS extends Base {
export class SAS extends Base<SasEvent, EventHandlerMap> {
private waitingForAccept: boolean;
public ourSASPubKey: string;
public theirSASPubKey: string;
@@ -371,7 +379,7 @@ export class SAS extends Base {
cancel: () => reject(newUserCancelledError()),
mismatch: () => reject(newMismatchedSASError()),
};
this.emit("show_sas", this.sasEvent);
this.emit(SasEvent.ShowSas, this.sasEvent);
});
[e] = await Promise.all([
@@ -447,7 +455,7 @@ export class SAS extends Base {
cancel: () => reject(newUserCancelledError()),
mismatch: () => reject(newMismatchedSASError()),
};
this.emit("show_sas", this.sasEvent);
this.emit(SasEvent.ShowSas, this.sasEvent);
});
[e] = await Promise.all([

View File

@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventEmitter } from 'events';
import { logger } from '../../../logger';
import {
errorFactory,
@@ -29,6 +27,7 @@ import { MatrixClient } from "../../../client";
import { MatrixEvent } from "../../../models/event";
import { VerificationBase } from "../Base";
import { VerificationMethod } from "../../index";
import { TypedEventEmitter } from "../../../models/typed-event-emitter";
// How long after the event's timestamp that the request times out
const TIMEOUT_FROM_EVENT_TS = 10 * 60 * 1000; // 10 minutes
@@ -76,13 +75,23 @@ interface ITransition {
event?: MatrixEvent;
}
export enum VerificationRequestEvent {
Change = "change",
}
type EventHandlerMap = {
[VerificationRequestEvent.Change]: () => void;
};
/**
* State machine for verification requests.
* Things that differ based on what channel is used to
* send and receive verification events are put in `InRoomChannel` or `ToDeviceChannel`.
* @event "change" whenever the state of the request object has changed.
*/
export class VerificationRequest<C extends IVerificationChannel = IVerificationChannel> extends EventEmitter {
export class VerificationRequest<
C extends IVerificationChannel = IVerificationChannel,
> extends TypedEventEmitter<VerificationRequestEvent, EventHandlerMap> {
private eventsByUs = new Map<string, MatrixEvent>();
private eventsByThem = new Map<string, MatrixEvent>();
private _observeOnly = false;
@@ -104,7 +113,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
private commonMethods: VerificationMethod[] = [];
private _phase: Phase;
private _cancellingUserId: string;
private _verifier: VerificationBase;
private _verifier: VerificationBase<any, any>;
constructor(
public readonly channel: C,
@@ -236,7 +245,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
}
/** The verifier to do the actual verification, once the method has been established. Only defined when the `phase` is PHASE_STARTED. */
public get verifier(): VerificationBase {
public get verifier(): VerificationBase<any, any> {
return this._verifier;
}
@@ -410,7 +419,10 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
* @param {string?} targetDevice.deviceId the id of the device to direct this request to
* @returns {VerifierBase} the verifier of the given method
*/
public beginKeyVerification(method: VerificationMethod, targetDevice: ITargetDevice = null): VerificationBase {
public beginKeyVerification(
method: VerificationMethod,
targetDevice: ITargetDevice = null,
): VerificationBase<any, any> {
// need to allow also when unsent in case of to_device
if (!this.observeOnly && !this._verifier) {
const validStartPhase =
@@ -453,7 +465,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
public async cancel({ reason = "User declined", code = "m.user" } = {}): Promise<void> {
if (!this.observeOnly && this._phase !== PHASE_CANCELLED) {
this._declining = true;
this.emit("change");
this.emit(VerificationRequestEvent.Change);
if (this._verifier) {
return this._verifier.cancel(errorFactory(code, reason)());
} else {
@@ -471,7 +483,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
if (!this.observeOnly && this.phase === PHASE_REQUESTED && !this.initiatedByMe) {
const methods = [...this.verificationMethods.keys()];
this._accepting = true;
this.emit("change");
this.emit(VerificationRequestEvent.Change);
await this.channel.send(READY_TYPE, { methods });
}
}
@@ -495,12 +507,12 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
handled = true;
}
if (handled) {
this.off("change", check);
this.off(VerificationRequestEvent.Change, check);
}
return handled;
};
if (!check()) {
this.on("change", check);
this.on(VerificationRequestEvent.Change, check);
}
});
}
@@ -508,7 +520,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
private setPhase(phase: Phase, notify = true): void {
this._phase = phase;
if (notify) {
this.emit("change");
this.emit(VerificationRequestEvent.Change);
}
}
@@ -768,7 +780,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
// set phase as last thing as this emits the "change" event
this.setPhase(phase);
} else if (this._observeOnly !== wasObserveOnly) {
this.emit("change");
this.emit(VerificationRequestEvent.Change);
}
} finally {
// log events we processed so we can see from rageshakes what events were added to a request
@@ -880,7 +892,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
method: VerificationMethod,
startEvent: MatrixEvent = null,
targetDevice: ITargetDevice = null,
): VerificationBase {
): VerificationBase<any, any> {
if (!targetDevice) {
targetDevice = this.targetDevice;
}

View File

@@ -15,7 +15,7 @@ limitations under the License.
*/
import { MatrixClient } from "./client";
import { IEvent, MatrixEvent } from "./models/event";
import { IEvent, MatrixEvent, MatrixEventEvent } from "./models/event";
export type EventMapper = (obj: Partial<IEvent>) => MatrixEvent;
@@ -33,7 +33,7 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event
if (event.isEncrypted()) {
if (!preventReEmit) {
client.reEmitter.reEmit(event, [
"Event.decrypted",
MatrixEventEvent.Decrypted,
]);
}
if (decrypt) {
@@ -41,7 +41,10 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event
}
}
if (!preventReEmit) {
client.reEmitter.reEmit(event, ["Event.replaced", "Event.visibilityChange"]);
client.reEmitter.reEmit(event, [
MatrixEventEvent.Replaced,
MatrixEventEvent.VisibilityChange,
]);
}
return event;
}

View File

@@ -21,7 +21,6 @@ limitations under the License.
*/
import { parse as parseContentType, ParsedMediaType } from "content-type";
import EventEmitter from "events";
import type { IncomingHttpHeaders, IncomingMessage } from "http";
import type { Request as _Request, CoreOptions } from "request";
@@ -35,6 +34,7 @@ import { IDeferred } from "./utils";
import { Callback } from "./client";
import * as utils from "./utils";
import { logger } from './logger';
import { TypedEventEmitter } from "./models/typed-event-emitter";
/*
TODO:
@@ -164,6 +164,16 @@ export enum Method {
export type FileType = Document | XMLHttpRequestBodyInit;
export enum HttpApiEvent {
SessionLoggedOut = "Session.logged_out",
NoConsent = "no_consent",
}
export type HttpApiEventHandlerMap = {
[HttpApiEvent.SessionLoggedOut]: (err: MatrixError) => void;
[HttpApiEvent.NoConsent]: (message: string, consentUri: string) => void;
};
/**
* Construct a MatrixHttpApi.
* @constructor
@@ -192,7 +202,10 @@ export type FileType = Document | XMLHttpRequestBodyInit;
export class MatrixHttpApi {
private uploads: IUpload[] = [];
constructor(private eventEmitter: EventEmitter, public readonly opts: IHttpOpts) {
constructor(
private eventEmitter: TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>,
public readonly opts: IHttpOpts,
) {
utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]);
opts.onlyData = !!opts.onlyData;
opts.useAuthorizationHeader = !!opts.useAuthorizationHeader;
@@ -603,13 +616,9 @@ export class MatrixHttpApi {
requestPromise.catch((err: MatrixError) => {
if (err.errcode == 'M_UNKNOWN_TOKEN' && !requestOpts?.inhibitLogoutEmit) {
this.eventEmitter.emit("Session.logged_out", err);
this.eventEmitter.emit(HttpApiEvent.SessionLoggedOut, err);
} else if (err.errcode == 'M_CONSENT_NOT_GIVEN') {
this.eventEmitter.emit(
"no_consent",
err.message,
err.data.consent_uri,
);
this.eventEmitter.emit(HttpApiEvent.NoConsent, err.message, err.data.consent_uri);
}
});

View File

@@ -0,0 +1,40 @@
/*
Copyright 2015 - 2022 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.
*/
/**
* Enum for event statuses.
* @readonly
* @enum {string}
*/
export enum EventStatus {
/** The event was not sent and will no longer be retried. */
NOT_SENT = "not_sent",
/** The message is being encrypted */
ENCRYPTING = "encrypting",
/** The event is in the process of being sent. */
SENDING = "sending",
/** The event is in a queue waiting to be sent. */
QUEUED = "queued",
/** The event has been sent to the server, but we have not yet received the echo. */
SENT = "sent",
/** The event was cancelled before it was successfully sent. */
CANCELLED = "cancelled",
}

View File

@@ -18,16 +18,15 @@ limitations under the License.
* @module models/event-timeline-set
*/
import { EventEmitter } from "events";
import { EventTimeline } from "./event-timeline";
import { EventStatus, MatrixEvent } from "./event";
import { EventStatus, MatrixEvent, MatrixEventEvent } from "./event";
import { logger } from '../logger';
import { Relations } from './relations';
import { Room } from "./room";
import { Room, RoomEvent } from "./room";
import { Filter } from "../filter";
import { EventType, RelationType } from "../@types/event";
import { RoomState } from "./room-state";
import { TypedEventEmitter } from "./typed-event-emitter";
// var DEBUG = false;
const DEBUG = true;
@@ -57,7 +56,15 @@ export interface IRoomTimelineData {
liveEvent?: boolean;
}
export class EventTimelineSet extends EventEmitter {
type EmittedEvents = RoomEvent.Timeline | RoomEvent.TimelineReset;
export type EventTimelineSetHandlerMap = {
[RoomEvent.Timeline]:
(event: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData) => void;
[RoomEvent.TimelineReset]: (room: Room, eventTimelineSet: EventTimelineSet, resetAllTimelines: boolean) => void;
};
export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTimelineSetHandlerMap> {
private readonly timelineSupport: boolean;
private unstableClientRelationAggregation: boolean;
private displayPendingEvents: boolean;
@@ -247,7 +254,7 @@ export class EventTimelineSet extends EventEmitter {
// Now we can swap the live timeline to the new one.
this.liveTimeline = newTimeline;
this.emit("Room.timelineReset", this.room, this, resetAllTimelines);
this.emit(RoomEvent.TimelineReset, this.room, this, resetAllTimelines);
}
/**
@@ -597,8 +604,7 @@ export class EventTimelineSet extends EventEmitter {
timeline: timeline,
liveEvent: !toStartOfTimeline && timeline == this.liveTimeline && !fromCache,
};
this.emit("Room.timeline", event, this.room,
Boolean(toStartOfTimeline), false, data);
this.emit(RoomEvent.Timeline, event, this.room, Boolean(toStartOfTimeline), false, data);
}
/**
@@ -652,7 +658,7 @@ export class EventTimelineSet extends EventEmitter {
const data = {
timeline: timeline,
};
this.emit("Room.timeline", removed, this.room, undefined, true, data);
this.emit(RoomEvent.Timeline, removed, this.room, undefined, true, data);
}
return removed;
}
@@ -819,7 +825,7 @@ export class EventTimelineSet extends EventEmitter {
// If the event is currently encrypted, wait until it has been decrypted.
if (event.isBeingDecrypted() || event.shouldAttemptDecryption()) {
event.once("Event.decrypted", () => {
event.once(MatrixEventEvent.Decrypted, () => {
this.aggregateRelations(event);
});
return;

View File

@@ -20,49 +20,22 @@ limitations under the License.
* @module models/event
*/
import { EventEmitter } from 'events';
import { ExtensibleEvent, ExtensibleEvents, Optional } from "matrix-events-sdk";
import { logger } from '../logger';
import { VerificationRequest } from "../crypto/verification/request/VerificationRequest";
import {
EventType,
MsgType,
RelationType,
EVENT_VISIBILITY_CHANGE_TYPE,
} from "../@types/event";
import { EVENT_VISIBILITY_CHANGE_TYPE, EventType, MsgType, RelationType } from "../@types/event";
import { Crypto, IEventDecryptionResult } from "../crypto";
import { deepSortedObjectEntries } from "../utils";
import { RoomMember } from "./room-member";
import { Thread, ThreadEvent } from "./thread";
import { Thread, ThreadEvent, EventHandlerMap as ThreadEventHandlerMap } from "./thread";
import { IActionsObject } from '../pushprocessor';
import { ReEmitter } from '../ReEmitter';
import { TypedReEmitter } from '../ReEmitter';
import { MatrixError } from "../http-api";
import { TypedEventEmitter } from "./typed-event-emitter";
import { EventStatus } from "./event-status";
/**
* Enum for event statuses.
* @readonly
* @enum {string}
*/
export enum EventStatus {
/** The event was not sent and will no longer be retried. */
NOT_SENT = "not_sent",
/** The message is being encrypted */
ENCRYPTING = "encrypting",
/** The event is in the process of being sent. */
SENDING = "sending",
/** The event is in a queue waiting to be sent. */
QUEUED = "queued",
/** The event has been sent to the server, but we have not yet received the echo. */
SENT = "sent",
/** The event was cancelled before it was successfully sent. */
CANCELLED = "cancelled",
}
export { EventStatus } from "./event-status";
const interns: Record<string, string> = {};
function intern(str: string): string {
@@ -209,7 +182,29 @@ export interface IMessageVisibilityHidden {
// A singleton implementing `IMessageVisibilityVisible`.
const MESSAGE_VISIBLE: IMessageVisibilityVisible = Object.freeze({ visible: true });
export class MatrixEvent extends EventEmitter {
export enum MatrixEventEvent {
Decrypted = "Event.decrypted",
BeforeRedaction = "Event.beforeRedaction",
VisibilityChange = "Event.visibilityChange",
LocalEventIdReplaced = "Event.localEventIdReplaced",
Status = "Event.status",
Replaced = "Event.replaced",
RelationsCreated = "Event.relationsCreated",
}
type EmittedEvents = MatrixEventEvent | ThreadEvent.Update;
export type MatrixEventHandlerMap = {
[MatrixEventEvent.Decrypted]: (event: MatrixEvent, err?: Error) => void;
[MatrixEventEvent.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void;
[MatrixEventEvent.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void;
[MatrixEventEvent.LocalEventIdReplaced]: (event: MatrixEvent) => void;
[MatrixEventEvent.Status]: (event: MatrixEvent, status: EventStatus) => void;
[MatrixEventEvent.Replaced]: (event: MatrixEvent) => void;
[MatrixEventEvent.RelationsCreated]: (relationType: string, eventType: string) => void;
} & ThreadEventHandlerMap;
export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHandlerMap> {
private pushActions: IActionsObject = null;
private _replacingEvent: MatrixEvent = null;
private _localRedactionEvent: MatrixEvent = null;
@@ -292,7 +287,7 @@ export class MatrixEvent extends EventEmitter {
*/
public verificationRequest: VerificationRequest = null;
private readonly reEmitter: ReEmitter;
private readonly reEmitter: TypedReEmitter<EmittedEvents, MatrixEventHandlerMap>;
/**
* Construct a Matrix Event object
@@ -343,7 +338,7 @@ export class MatrixEvent extends EventEmitter {
this.txnId = event.txn_id || null;
this.localTimestamp = Date.now() - (this.getAge() ?? 0);
this.reEmitter = new ReEmitter(this);
this.reEmitter = new TypedReEmitter(this);
}
/**
@@ -871,7 +866,7 @@ export class MatrixEvent extends EventEmitter {
this.setPushActions(null);
if (options.emit !== false) {
this.emit("Event.decrypted", this, err);
this.emit(MatrixEventEvent.Decrypted, this, err);
}
return;
@@ -1030,7 +1025,7 @@ export class MatrixEvent extends EventEmitter {
public markLocallyRedacted(redactionEvent: MatrixEvent): void {
if (this._localRedactionEvent) return;
this.emit("Event.beforeRedaction", this, redactionEvent);
this.emit(MatrixEventEvent.BeforeRedaction, this, redactionEvent);
this._localRedactionEvent = redactionEvent;
if (!this.event.unsigned) {
this.event.unsigned = {};
@@ -1068,7 +1063,7 @@ export class MatrixEvent extends EventEmitter {
});
}
if (change) {
this.emit("Event.visibilityChange", this, visible);
this.emit(MatrixEventEvent.VisibilityChange, this, visible);
}
}
}
@@ -1100,7 +1095,7 @@ export class MatrixEvent extends EventEmitter {
this._localRedactionEvent = null;
this.emit("Event.beforeRedaction", this, redactionEvent);
this.emit(MatrixEventEvent.BeforeRedaction, this, redactionEvent);
this._replacingEvent = null;
// we attempt to replicate what we would see from the server if
@@ -1263,7 +1258,7 @@ export class MatrixEvent extends EventEmitter {
this.setStatus(null);
if (this.getId() !== oldId) {
// emit the event if it changed
this.emit("Event.localEventIdReplaced", this);
this.emit(MatrixEventEvent.LocalEventIdReplaced, this);
}
this.localTimestamp = Date.now() - this.getAge();
@@ -1286,12 +1281,12 @@ export class MatrixEvent extends EventEmitter {
*/
public setStatus(status: EventStatus): void {
this.status = status;
this.emit("Event.status", this, status);
this.emit(MatrixEventEvent.Status, this, status);
}
public replaceLocalEventId(eventId: string): void {
this.event.event_id = eventId;
this.emit("Event.localEventIdReplaced", this);
this.emit(MatrixEventEvent.LocalEventIdReplaced, this);
}
/**
@@ -1340,7 +1335,7 @@ export class MatrixEvent extends EventEmitter {
}
if (this._replacingEvent !== newEvent) {
this._replacingEvent = newEvent;
this.emit("Event.replaced", this);
this.emit(MatrixEventEvent.Replaced, this);
this.invalidateExtensibleEvent();
}
}
@@ -1559,7 +1554,7 @@ export class MatrixEvent extends EventEmitter {
public setThread(thread: Thread): void {
this.thread = thread;
this.setThreadId(thread.id);
this.reEmitter.reEmit(thread, [ThreadEvent.Ready, ThreadEvent.Update]);
this.reEmitter.reEmit(thread, [ThreadEvent.Update]);
}
/**

View File

@@ -20,6 +20,7 @@ limitations under the License.
* @deprecated groups/communities never made it to the spec and support for them is being discontinued.
*/
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import * as utils from "../utils";

View File

@@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Relations } from "./relations";
import { Relations, RelationsEvent, EventHandlerMap } from "./relations";
import { MatrixEvent } from "./event";
import { Listener } from "./typed-event-emitter";
export class RelatedRelations {
private relations: Relations[];
@@ -28,11 +29,11 @@ export class RelatedRelations {
return this.relations.reduce((c, p) => [...c, ...p.getRelations()], []);
}
public on(ev: string, fn: (...params) => void) {
public on<T extends RelationsEvent>(ev: T, fn: Listener<RelationsEvent, EventHandlerMap, T>) {
this.relations.forEach(r => r.on(ev, fn));
}
public off(ev: string, fn: (...params) => void) {
public off<T extends RelationsEvent>(ev: T, fn: Listener<RelationsEvent, EventHandlerMap, T>) {
this.relations.forEach(r => r.off(ev, fn));
}
}

View File

@@ -14,12 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventEmitter } from 'events';
import { EventStatus, MatrixEvent, IAggregatedRelation } from './event';
import { EventStatus, IAggregatedRelation, MatrixEvent, MatrixEventEvent } from './event';
import { Room } from './room';
import { logger } from '../logger';
import { RelationType } from "../@types/event";
import { TypedEventEmitter } from "./typed-event-emitter";
export enum RelationsEvent {
Add = "Relations.add",
Remove = "Relations.remove",
Redaction = "Relations.redaction",
}
export type EventHandlerMap = {
[RelationsEvent.Add]: (event: MatrixEvent) => void;
[RelationsEvent.Remove]: (event: MatrixEvent) => void;
[RelationsEvent.Redaction]: (event: MatrixEvent) => void;
};
/**
* A container for relation events that supports easy access to common ways of
@@ -29,7 +40,7 @@ import { RelationType } from "../@types/event";
* The typical way to get one of these containers is via
* EventTimelineSet#getRelationsForEvent.
*/
export class Relations extends EventEmitter {
export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap> {
private relationEventIds = new Set<string>();
private relations = new Set<MatrixEvent>();
private annotationsByKey: Record<string, Set<MatrixEvent>> = {};
@@ -84,7 +95,7 @@ export class Relations extends EventEmitter {
// If the event is in the process of being sent, listen for cancellation
// so we can remove the event from the collection.
if (event.isSending()) {
event.on("Event.status", this.onEventStatus);
event.on(MatrixEventEvent.Status, this.onEventStatus);
}
this.relations.add(event);
@@ -97,9 +108,9 @@ export class Relations extends EventEmitter {
this.targetEvent.makeReplaced(lastReplacement);
}
event.on("Event.beforeRedaction", this.onBeforeRedaction);
event.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
this.emit("Relations.add", event);
this.emit(RelationsEvent.Add, event);
this.maybeEmitCreated();
}
@@ -138,7 +149,7 @@ export class Relations extends EventEmitter {
this.targetEvent.makeReplaced(lastReplacement);
}
this.emit("Relations.remove", event);
this.emit(RelationsEvent.Remove, event);
}
/**
@@ -150,14 +161,14 @@ export class Relations extends EventEmitter {
private onEventStatus = (event: MatrixEvent, status: EventStatus) => {
if (!event.isSending()) {
// Sending is done, so we don't need to listen anymore
event.removeListener("Event.status", this.onEventStatus);
event.removeListener(MatrixEventEvent.Status, this.onEventStatus);
return;
}
if (status !== EventStatus.CANCELLED) {
return;
}
// Event was cancelled, remove from the collection
event.removeListener("Event.status", this.onEventStatus);
event.removeListener(MatrixEventEvent.Status, this.onEventStatus);
this.removeEvent(event);
};
@@ -255,9 +266,9 @@ export class Relations extends EventEmitter {
this.targetEvent.makeReplaced(lastReplacement);
}
redactedEvent.removeListener("Event.beforeRedaction", this.onBeforeRedaction);
redactedEvent.removeListener(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
this.emit("Relations.redaction", redactedEvent);
this.emit(RelationsEvent.Redaction, redactedEvent);
};
/**
@@ -375,6 +386,6 @@ export class Relations extends EventEmitter {
return;
}
this.creationEmitted = true;
this.targetEvent.emit("Event.relationsCreated", this.relationType, this.eventType);
this.targetEvent.emit(MatrixEventEvent.RelationsCreated, this.relationType, this.eventType);
}
}

View File

@@ -18,16 +18,30 @@ limitations under the License.
* @module models/room-member
*/
import { EventEmitter } from "events";
import { getHttpUriForMxc } from "../content-repo";
import * as utils from "../utils";
import { User } from "./user";
import { MatrixEvent } from "./event";
import { RoomState } from "./room-state";
import { logger } from "../logger";
import { TypedEventEmitter } from "./typed-event-emitter";
import { EventType } from "../@types/event";
export class RoomMember extends EventEmitter {
export enum RoomMemberEvent {
Membership = "RoomMember.membership",
Name = "RoomMember.name",
PowerLevel = "RoomMember.powerLevel",
Typing = "RoomMember.typing",
}
export type RoomMemberEventHandlerMap = {
[RoomMemberEvent.Membership]: (event: MatrixEvent, member: RoomMember, oldMembership: string | null) => void;
[RoomMemberEvent.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void;
[RoomMemberEvent.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void;
[RoomMemberEvent.Typing]: (event: MatrixEvent, member: RoomMember) => void;
};
export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEventHandlerMap> {
private _isOutOfBand = false;
private _modified: number;
public _requestedProfileInfo: boolean; // used by sync.ts
@@ -107,7 +121,7 @@ export class RoomMember extends EventEmitter {
public setMembershipEvent(event: MatrixEvent, roomState?: RoomState): void {
const displayName = event.getDirectionalContent().displayname;
if (event.getType() !== "m.room.member") {
if (event.getType() !== EventType.RoomMember) {
return;
}
@@ -150,11 +164,11 @@ export class RoomMember extends EventEmitter {
if (oldMembership !== this.membership) {
this.updateModifiedTime();
this.emit("RoomMember.membership", event, this, oldMembership);
this.emit(RoomMemberEvent.Membership, event, this, oldMembership);
}
if (oldName !== this.name) {
this.updateModifiedTime();
this.emit("RoomMember.name", event, this, oldName);
this.emit(RoomMemberEvent.Name, event, this, oldName);
}
}
@@ -196,7 +210,7 @@ export class RoomMember extends EventEmitter {
// redraw everyone's level if the max has changed)
if (oldPowerLevel !== this.powerLevel || oldPowerLevelNorm !== this.powerLevelNorm) {
this.updateModifiedTime();
this.emit("RoomMember.powerLevel", powerLevelEvent, this);
this.emit(RoomMemberEvent.PowerLevel, powerLevelEvent, this);
}
}
@@ -222,7 +236,7 @@ export class RoomMember extends EventEmitter {
}
if (oldTyping !== this.typing) {
this.updateModifiedTime();
this.emit("RoomMember.typing", event, this);
this.emit(RoomMemberEvent.Typing, event, this);
}
}

View File

@@ -18,8 +18,6 @@ limitations under the License.
* @module models/room-state
*/
import { EventEmitter } from "events";
import { RoomMember } from "./room-member";
import { logger } from '../logger';
import * as utils from "../utils";
@@ -27,6 +25,7 @@ import { EventType } from "../@types/event";
import { MatrixEvent } from "./event";
import { MatrixClient } from "../client";
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
import { TypedEventEmitter } from "./typed-event-emitter";
// possible statuses for out-of-band member loading
enum OobStatus {
@@ -35,7 +34,19 @@ enum OobStatus {
Finished,
}
export class RoomState extends EventEmitter {
export enum RoomStateEvent {
Events = "RoomState.events",
Members = "RoomState.members",
NewMember = "RoomState.newMember",
}
export type RoomStateEventHandlerMap = {
[RoomStateEvent.Events]: (event: MatrixEvent, state: RoomState, lastStateEvent: MatrixEvent | null) => void;
[RoomStateEvent.Members]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
[RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
};
export class RoomState extends TypedEventEmitter<RoomStateEvent, RoomStateEventHandlerMap> {
private sentinels: Record<string, RoomMember> = {}; // userId: RoomMember
// stores fuzzy matches to a list of userIDs (applies utils.removeHiddenChars to keys)
private displayNameToUserIds: Record<string, string[]> = {};
@@ -307,7 +318,7 @@ export class RoomState extends EventEmitter {
this.updateDisplayNameCache(event.getStateKey(), event.getContent().displayname);
this.updateThirdPartyTokenCache(event);
}
this.emit("RoomState.events", event, this, lastStateEvent);
this.emit(RoomStateEvent.Events, event, this, lastStateEvent);
});
// update higher level data structures. This needs to be done AFTER the
@@ -342,7 +353,7 @@ export class RoomState extends EventEmitter {
member.setMembershipEvent(event, this);
this.updateMember(member);
this.emit("RoomState.members", event, this, member);
this.emit(RoomStateEvent.Members, event, this, member);
} else if (event.getType() === EventType.RoomPowerLevels) {
// events with unknown state keys should be ignored
// and should not aggregate onto members power levels
@@ -357,7 +368,7 @@ export class RoomState extends EventEmitter {
const oldLastModified = member.getLastModifiedTime();
member.setPowerLevelEvent(event);
if (oldLastModified !== member.getLastModifiedTime()) {
this.emit("RoomState.members", event, this, member);
this.emit(RoomStateEvent.Members, event, this, member);
}
});
@@ -384,7 +395,7 @@ export class RoomState extends EventEmitter {
// add member to members before emitting any events,
// as event handlers often lookup the member
this.members[userId] = member;
this.emit("RoomState.newMember", event, this, member);
this.emit(RoomStateEvent.NewMember, event, this, member);
}
return member;
}
@@ -397,8 +408,7 @@ export class RoomState extends EventEmitter {
}
private getStateEventMatching(event: MatrixEvent): MatrixEvent | null {
if (!this.events.has(event.getType())) return null;
return this.events.get(event.getType()).get(event.getStateKey());
return this.events.get(event.getType())?.get(event.getStateKey()) ?? null;
}
private updateMember(member: RoomMember): void {
@@ -503,7 +513,7 @@ export class RoomState extends EventEmitter {
this.setStateEvent(stateEvent);
this.updateMember(member);
this.emit("RoomState.members", stateEvent, this, member);
this.emit(RoomStateEvent.Members, stateEvent, this, member);
}
/**

View File

@@ -18,18 +18,17 @@ limitations under the License.
* @module models/room
*/
import { EventEmitter } from "events";
import { EventTimelineSet, DuplicateStrategy } from "./event-timeline-set";
import { Direction, EventTimeline } from "./event-timeline";
import { getHttpUriForMxc } from "../content-repo";
import * as utils from "../utils";
import { normalize } from "../utils";
import { EventStatus, IEvent, MatrixEvent } from "./event";
import { IEvent, MatrixEvent } from "./event";
import { EventStatus } from "./event-status";
import { RoomMember } from "./room-member";
import { IRoomSummary, RoomSummary } from "./room-summary";
import { logger } from '../logger';
import { ReEmitter } from '../ReEmitter';
import { TypedReEmitter } from '../ReEmitter';
import {
EventType, RoomCreateTypeField, RoomType, UNSTABLE_ELEMENT_FUNCTIONAL_USERS,
EVENT_VISIBILITY_CHANGE_TYPE,
@@ -38,8 +37,9 @@ import { IRoomVersionsCapability, MatrixClient, PendingEventOrdering, RoomVersio
import { GuestAccess, HistoryVisibility, JoinRule, ResizeMethod } from "../@types/partials";
import { Filter } from "../filter";
import { RoomState } from "./room-state";
import { Thread, ThreadEvent } from "./thread";
import { Thread, ThreadEvent, EventHandlerMap as ThreadHandlerMap } from "./thread";
import { Method } from "../http-api";
import { TypedEventEmitter } from "./typed-event-emitter";
// These constants are used as sane defaults when the homeserver doesn't support
// the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be
@@ -143,8 +143,44 @@ export interface ICreateFilterOpts {
prepopulateTimeline?: boolean;
}
export class Room extends EventEmitter {
private readonly reEmitter: ReEmitter;
export enum RoomEvent {
MyMembership = "Room.myMembership",
Tags = "Room.tags",
AccountData = "Room.accountData",
Receipt = "Room.receipt",
Name = "Room.name",
Redaction = "Room.redaction",
RedactionCancelled = "Room.redactionCancelled",
LocalEchoUpdated = "Room.localEchoUpdated",
Timeline = "Room.timeline",
TimelineReset = "Room.timelineReset",
}
type EmittedEvents = RoomEvent
| ThreadEvent.New
| ThreadEvent.Update
| RoomEvent.Timeline
| RoomEvent.TimelineReset;
export type RoomEventHandlerMap = {
[RoomEvent.MyMembership]: (room: Room, membership: string, prevMembership?: string) => void;
[RoomEvent.Tags]: (event: MatrixEvent, room: Room) => void;
[RoomEvent.AccountData]: (event: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => void;
[RoomEvent.Receipt]: (event: MatrixEvent, room: Room) => void;
[RoomEvent.Name]: (room: Room) => void;
[RoomEvent.Redaction]: (event: MatrixEvent, room: Room) => void;
[RoomEvent.RedactionCancelled]: (event: MatrixEvent, room: Room) => void;
[RoomEvent.LocalEchoUpdated]: (
event: MatrixEvent,
room: Room,
oldEventId?: string,
oldStatus?: EventStatus,
) => void;
[ThreadEvent.New]: (thread: Thread) => void;
} & ThreadHandlerMap;
export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap> {
private readonly reEmitter: TypedReEmitter<EmittedEvents, RoomEventHandlerMap>;
private txnToEvent: Record<string, MatrixEvent> = {}; // Pending in-flight requests { string: MatrixEvent }
// receipts should clobber based on receipt_type and user_id pairs hence
// the form of this structure. This is sub-optimal for the exposed APIs
@@ -287,7 +323,7 @@ export class Room extends EventEmitter {
// In some cases, we add listeners for every displayed Matrix event, so it's
// common to have quite a few more than the default limit.
this.setMaxListeners(100);
this.reEmitter = new ReEmitter(this);
this.reEmitter = new TypedReEmitter(this);
opts.pendingEventOrdering = opts.pendingEventOrdering || PendingEventOrdering.Chronological;
@@ -297,7 +333,8 @@ export class Room extends EventEmitter {
// the subsequent ones are the filtered ones in no particular order.
this.timelineSets = [new EventTimelineSet(this, opts)];
this.reEmitter.reEmit(this.getUnfilteredTimelineSet(), [
"Room.timeline", "Room.timelineReset",
RoomEvent.Timeline,
RoomEvent.TimelineReset,
]);
this.fixUpLegacyTimelineFields();
@@ -712,7 +749,7 @@ export class Room extends EventEmitter {
if (membership === "leave") {
this.cleanupAfterLeaving();
}
this.emit("Room.myMembership", this, membership, prevMembership);
this.emit(RoomEvent.MyMembership, this, membership, prevMembership);
}
}
@@ -1285,7 +1322,10 @@ export class Room extends EventEmitter {
}
const opts = Object.assign({ filter: filter }, this.opts);
const timelineSet = new EventTimelineSet(this, opts);
this.reEmitter.reEmit(timelineSet, ["Room.timeline", "Room.timelineReset"]);
this.reEmitter.reEmit(timelineSet, [
RoomEvent.Timeline,
RoomEvent.TimelineReset,
]);
this.filteredTimelineSets[filter.filterId] = timelineSet;
this.timelineSets.push(timelineSet);
@@ -1418,9 +1458,8 @@ export class Room extends EventEmitter {
this.threads.set(thread.id, thread);
this.reEmitter.reEmit(thread, [
ThreadEvent.Update,
ThreadEvent.Ready,
"Room.timeline",
"Room.timelineReset",
RoomEvent.Timeline,
RoomEvent.TimelineReset,
]);
if (!this.lastThread || this.lastThread.rootEvent.localTimestamp < rootEvent.localTimestamp) {
@@ -1462,7 +1501,7 @@ export class Room extends EventEmitter {
}
}
this.emit("Room.redaction", event, this);
this.emit(RoomEvent.Redaction, event, this);
// TODO: we stash user displaynames (among other things) in
// RoomMember objects which are then attached to other events
@@ -1584,7 +1623,7 @@ export class Room extends EventEmitter {
}
if (redactedEvent) {
redactedEvent.markLocallyRedacted(event);
this.emit("Room.redaction", event, this);
this.emit(RoomEvent.Redaction, event, this);
}
}
} else {
@@ -1602,7 +1641,7 @@ export class Room extends EventEmitter {
}
}
this.emit("Room.localEchoUpdated", event, this, null, null);
this.emit(RoomEvent.LocalEchoUpdated, event, this, null, null);
}
/**
@@ -1730,8 +1769,7 @@ export class Room extends EventEmitter {
}
}
this.emit("Room.localEchoUpdated", localEvent, this,
oldEventId, oldStatus);
this.emit(RoomEvent.LocalEchoUpdated, localEvent, this, oldEventId, oldStatus);
}
/**
@@ -1815,7 +1853,7 @@ export class Room extends EventEmitter {
}
this.savePendingEvents();
this.emit("Room.localEchoUpdated", event, this, oldEventId, oldStatus);
this.emit(RoomEvent.LocalEchoUpdated, event, this, oldEventId, oldStatus);
}
private revertRedactionLocalEcho(redactionEvent: MatrixEvent): void {
@@ -1828,7 +1866,7 @@ export class Room extends EventEmitter {
if (redactedEvent) {
redactedEvent.unmarkLocallyRedacted();
// re-render after undoing redaction
this.emit("Room.redactionCancelled", redactionEvent, this);
this.emit(RoomEvent.RedactionCancelled, redactionEvent, this);
// reapply relation now redaction failed
if (redactedEvent.isRelation()) {
this.aggregateNonLiveRelation(redactedEvent);
@@ -1968,7 +2006,7 @@ export class Room extends EventEmitter {
});
if (oldName !== this.name) {
this.emit("Room.name", this);
this.emit(RoomEvent.Name, this);
}
}
@@ -2061,7 +2099,7 @@ export class Room extends EventEmitter {
this.addReceiptsToStructure(event, synthetic);
// send events after we've regenerated the structure & cache, otherwise things that
// listened for the event would read stale data.
this.emit("Room.receipt", event, this);
this.emit(RoomEvent.Receipt, event, this);
}
/**
@@ -2195,7 +2233,7 @@ export class Room extends EventEmitter {
// XXX: we could do a deep-comparison to see if the tags have really
// changed - but do we want to bother?
this.emit("Room.tags", event, this);
this.emit(RoomEvent.Tags, event, this);
}
/**
@@ -2210,7 +2248,7 @@ export class Room extends EventEmitter {
}
const lastEvent = this.accountData[event.getType()];
this.accountData[event.getType()] = event;
this.emit("Room.accountData", event, this, lastEvent);
this.emit(RoomEvent.AccountData, event, this, lastEvent);
}
}

View File

@@ -14,25 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient } from "../matrix";
import { ReEmitter } from "../ReEmitter";
import { MatrixClient, RoomEvent } from "../matrix";
import { TypedReEmitter } from "../ReEmitter";
import { RelationType } from "../@types/event";
import { IRelationsRequestOpts } from "../@types/requests";
import { MatrixEvent, IThreadBundledRelationship } from "./event";
import { IThreadBundledRelationship, MatrixEvent } from "./event";
import { Direction, EventTimeline } from "./event-timeline";
import { EventTimelineSet } from './event-timeline-set';
import { EventTimelineSet, EventTimelineSetHandlerMap } from './event-timeline-set';
import { Room } from './room';
import { TypedEventEmitter } from "./typed-event-emitter";
import { RoomState } from "./room-state";
export enum ThreadEvent {
New = "Thread.new",
Ready = "Thread.ready",
Update = "Thread.update",
NewReply = "Thread.newReply",
ViewThread = "Thred.viewThread",
ViewThread = "Thread.viewThread",
}
type EmittedEvents = Exclude<ThreadEvent, ThreadEvent.New>
| RoomEvent.Timeline
| RoomEvent.TimelineReset;
export type EventHandlerMap = {
[ThreadEvent.Update]: (thread: Thread) => void;
[ThreadEvent.NewReply]: (thread: Thread, event: MatrixEvent) => void;
[ThreadEvent.ViewThread]: () => void;
} & EventTimelineSetHandlerMap;
interface IThreadOpts {
initialEvents?: MatrixEvent[];
room: Room;
@@ -42,15 +51,15 @@ interface IThreadOpts {
/**
* @experimental
*/
export class Thread extends TypedEventEmitter<ThreadEvent> {
export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
/**
* A reference to all the events ID at the bottom of the threads
*/
public readonly timelineSet;
public readonly timelineSet: EventTimelineSet;
private _currentUserParticipated = false;
private reEmitter: ReEmitter;
private reEmitter: TypedReEmitter<EmittedEvents, EventHandlerMap>;
private lastEvent: MatrixEvent;
private replyCount = 0;
@@ -75,11 +84,11 @@ export class Thread extends TypedEventEmitter<ThreadEvent> {
timelineSupport: true,
pendingEvents: true,
});
this.reEmitter = new ReEmitter(this);
this.reEmitter = new TypedReEmitter(this);
this.reEmitter.reEmit(this.timelineSet, [
"Room.timeline",
"Room.timelineReset",
RoomEvent.Timeline,
RoomEvent.TimelineReset,
]);
// If we weren't able to find the root event, it's probably missing
@@ -94,8 +103,8 @@ export class Thread extends TypedEventEmitter<ThreadEvent> {
opts?.initialEvents?.forEach(event => this.addEvent(event));
this.room.on("Room.localEchoUpdated", this.onEcho);
this.room.on("Room.timeline", this.onEcho);
this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho);
this.room.on(RoomEvent.Timeline, this.onEcho);
}
public get hasServerSideSupport(): boolean {
@@ -103,7 +112,7 @@ export class Thread extends TypedEventEmitter<ThreadEvent> {
?.capabilities?.[RelationType.Thread]?.enabled;
}
onEcho = (event: MatrixEvent) => {
private onEcho = (event: MatrixEvent) => {
if (this.timelineSet.eventIdToTimeline(event.getId())) {
this.emit(ThreadEvent.Update, this);
}
@@ -139,10 +148,11 @@ export class Thread extends TypedEventEmitter<ThreadEvent> {
* the tail/root references if needed
* Will fire "Thread.update"
* @param event The event to add
* @param {boolean} toStartOfTimeline whether the event is being added
* to the start (and not the end) of the timeline.
*/
public async addEvent(event: MatrixEvent, toStartOfTimeline = false): Promise<void> {
// Add all incoming events to the thread's timeline set when there's
// no server support
// Add all incoming events to the thread's timeline set when there's no server support
if (!this.hasServerSideSupport) {
// all the relevant membership info to hydrate events with a sender
// is held in the main room timeline

View File

@@ -14,13 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
enum EventEmitterEvents {
export enum EventEmitterEvents {
NewListener = "newListener",
RemoveListener = "removeListener",
Error = "error",
}
type AnyListener = (...args: any) => any;
export type ListenerMap<E extends string> = { [eventName in E]: AnyListener };
type EventEmitterEventListener = (eventName: string, listener: AnyListener) => void;
type EventEmitterErrorListener = (error: Error) => void;
export type Listener<
E extends string,
A extends ListenerMap<E>,
T extends E | EventEmitterEvents,
> = T extends E ? A[T]
: T extends EventEmitterEvents ? EventEmitterErrorListener
: EventEmitterEventListener;
/**
* Typed Event Emitter class which can act as a Base Model for all our model
* and communication events.
@@ -28,17 +43,26 @@ enum EventEmitterEvents {
* to properly type this, so that our events are not stringly-based and prone
* to silly typos.
*/
export abstract class TypedEventEmitter<Events extends string> extends EventEmitter {
public addListener(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this {
export class TypedEventEmitter<
Events extends string,
Arguments extends ListenerMap<Events>,
SuperclassArguments extends ListenerMap<any> = Arguments,
> extends EventEmitter {
public addListener<T extends Events | EventEmitterEvents>(
event: T,
listener: Listener<Events, Arguments, T>,
): this {
return super.addListener(event, listener);
}
public emit(event: Events | EventEmitterEvents, ...args: any[]): boolean {
public emit<T extends Events>(event: T, ...args: Parameters<SuperclassArguments[T]>): boolean;
public emit<T extends Events>(event: T, ...args: Parameters<Arguments[T]>): boolean;
public emit<T extends Events>(event: T, ...args: any[]): boolean {
return super.emit(event, ...args);
}
public eventNames(): (Events | EventEmitterEvents)[] {
return super.eventNames() as Events[];
return super.eventNames() as Array<Events | EventEmitterEvents>;
}
public listenerCount(event: Events | EventEmitterEvents): number {
@@ -49,23 +73,38 @@ export abstract class TypedEventEmitter<Events extends string> extends EventEmit
return super.listeners(event);
}
public off(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this {
public off<T extends Events | EventEmitterEvents>(
event: T,
listener: Listener<Events, Arguments, T>,
): this {
return super.off(event, listener);
}
public on(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this {
public on<T extends Events | EventEmitterEvents>(
event: T,
listener: Listener<Events, Arguments, T>,
): this {
return super.on(event, listener);
}
public once(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this {
public once<T extends Events | EventEmitterEvents>(
event: T,
listener: Listener<Events, Arguments, T>,
): this {
return super.once(event, listener);
}
public prependListener(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this {
public prependListener<T extends Events | EventEmitterEvents>(
event: T,
listener: Listener<Events, Arguments, T>,
): this {
return super.prependListener(event, listener);
}
public prependOnceListener(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this {
public prependOnceListener<T extends Events | EventEmitterEvents>(
event: T,
listener: Listener<Events, Arguments, T>,
): this {
return super.prependOnceListener(event, listener);
}
@@ -73,7 +112,10 @@ export abstract class TypedEventEmitter<Events extends string> extends EventEmit
return super.removeAllListeners(event);
}
public removeListener(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this {
public removeListener<T extends Events | EventEmitterEvents>(
event: T,
listener: Listener<Events, Arguments, T>,
): this {
return super.removeListener(event, listener);
}

View File

@@ -18,12 +18,29 @@ limitations under the License.
* @module models/user
*/
import { EventEmitter } from "events";
import { MatrixEvent } from "./event";
import { TypedEventEmitter } from "./typed-event-emitter";
export class User extends EventEmitter {
// eslint-disable-next-line camelcase
export enum UserEvent {
DisplayName = "User.displayName",
AvatarUrl = "User.avatarUrl",
Presence = "User.presence",
CurrentlyActive = "User.currentlyActive",
LastPresenceTs = "User.lastPresenceTs",
/* @deprecated */
_UnstableStatusMessage = "User.unstable_statusMessage",
}
export type UserEventHandlerMap = {
[UserEvent.DisplayName]: (event: MatrixEvent | undefined, user: User) => void;
[UserEvent.AvatarUrl]: (event: MatrixEvent | undefined, user: User) => void;
[UserEvent.Presence]: (event: MatrixEvent | undefined, user: User) => void;
[UserEvent.CurrentlyActive]: (event: MatrixEvent | undefined, user: User) => void;
[UserEvent.LastPresenceTs]: (event: MatrixEvent | undefined, user: User) => void;
[UserEvent._UnstableStatusMessage]: (user: User) => void;
};
export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
private modified: number;
// XXX these should be read-only
@@ -94,25 +111,25 @@ export class User extends EventEmitter {
const firstFire = this.events.presence === null;
this.events.presence = event;
const eventsToFire = [];
const eventsToFire: UserEvent[] = [];
if (event.getContent().presence !== this.presence || firstFire) {
eventsToFire.push("User.presence");
eventsToFire.push(UserEvent.Presence);
}
if (event.getContent().avatar_url &&
event.getContent().avatar_url !== this.avatarUrl) {
eventsToFire.push("User.avatarUrl");
eventsToFire.push(UserEvent.AvatarUrl);
}
if (event.getContent().displayname &&
event.getContent().displayname !== this.displayName) {
eventsToFire.push("User.displayName");
eventsToFire.push(UserEvent.DisplayName);
}
if (event.getContent().currently_active !== undefined &&
event.getContent().currently_active !== this.currentlyActive) {
eventsToFire.push("User.currentlyActive");
eventsToFire.push(UserEvent.CurrentlyActive);
}
this.presence = event.getContent().presence;
eventsToFire.push("User.lastPresenceTs");
eventsToFire.push(UserEvent.LastPresenceTs);
if (event.getContent().status_msg) {
this.presenceStatusMsg = event.getContent().status_msg;
@@ -213,7 +230,7 @@ export class User extends EventEmitter {
if (!event.getContent()) this.unstable_statusMessage = "";
else this.unstable_statusMessage = event.getContent()["status"];
this.updateModifiedTime();
this.emit("User.unstable_statusMessage", this);
this.emit(UserEvent._UnstableStatusMessage, this);
}
}

View File

@@ -16,8 +16,6 @@ limitations under the License.
/* eslint-disable @babel/no-invalid-this */
import { EventEmitter } from 'events';
import { MemoryStore, IOpts as IBaseOpts } from "./memory";
import { LocalIndexedDBStoreBackend } from "./indexeddb-local-backend";
import { RemoteIndexedDBStoreBackend } from "./indexeddb-remote-backend";
@@ -27,6 +25,7 @@ import { logger } from '../logger';
import { ISavedSync } from "./index";
import { IIndexedDBBackend } from "./indexeddb-backend";
import { ISyncResponse } from "../sync-accumulator";
import { TypedEventEmitter } from "../models/typed-event-emitter";
/**
* This is an internal module. See {@link IndexedDBStore} for the public class.
@@ -46,6 +45,10 @@ interface IOpts extends IBaseOpts {
workerFactory?: () => Worker;
}
type EventHandlerMap = {
"degraded": (e: Error) => void;
};
export class IndexedDBStore extends MemoryStore {
static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
return LocalIndexedDBStoreBackend.exists(indexedDB, dbName);
@@ -59,7 +62,7 @@ export class IndexedDBStore extends MemoryStore {
// the database, such that we can derive the set if users that have been
// modified since we last saved.
private userModifiedMap: Record<string, number> = {}; // user_id : timestamp
private emitter = new EventEmitter();
private emitter = new TypedEventEmitter<keyof EventHandlerMap, EventHandlerMap>();
/**
* Construct a new Indexed Database store, which extends MemoryStore.

View File

@@ -25,6 +25,15 @@ export enum LocalStorageErrors {
QuotaExceededError = 'QuotaExceededError'
}
type EventHandlerMap = {
[LocalStorageErrors.Global]: (error: Error) => void;
[LocalStorageErrors.SetItemError]: (error: Error) => void;
[LocalStorageErrors.GetItemError]: (error: Error) => void;
[LocalStorageErrors.RemoveItemError]: (error: Error) => void;
[LocalStorageErrors.ClearError]: (error: Error) => void;
[LocalStorageErrors.QuotaExceededError]: (error: Error) => void;
};
/**
* Used in element-web as a temporary hack to handle all the localStorage errors on the highest level possible
* As of 15.11.2021 (DD/MM/YYYY) we're not properly handling local storage exceptions anywhere.
@@ -33,5 +42,5 @@ export enum LocalStorageErrors {
* maybe you should check out your disk, as it's probably dying and your session may die with it.
* See: https://github.com/vector-im/element-web/issues/18423
*/
class LocalStorageErrorsEventsEmitter extends TypedEventEmitter<LocalStorageErrors> {}
class LocalStorageErrorsEventsEmitter extends TypedEventEmitter<LocalStorageErrors, EventHandlerMap> {}
export const localStorageErrorsEventsEmitter = new LocalStorageErrorsEventsEmitter();

View File

@@ -24,7 +24,7 @@ import { Group } from "../models/group";
import { Room } from "../models/room";
import { User } from "../models/user";
import { IEvent, MatrixEvent } from "../models/event";
import { RoomState } from "../models/room-state";
import { RoomState, RoomStateEvent } from "../models/room-state";
import { RoomMember } from "../models/room-member";
import { Filter } from "../filter";
import { ISavedSync, IStore } from "./index";
@@ -126,7 +126,7 @@ export class MemoryStore implements IStore {
this.rooms[room.roomId] = room;
// add listeners for room member changes so we can keep the room member
// map up-to-date.
room.currentState.on("RoomState.members", this.onRoomMember);
room.currentState.on(RoomStateEvent.Members, this.onRoomMember);
// add existing members
room.currentState.getMembers().forEach((m) => {
this.onRoomMember(null, room.currentState, m);
@@ -185,7 +185,7 @@ export class MemoryStore implements IStore {
*/
public removeRoom(roomId: string): void {
if (this.rooms[roomId]) {
this.rooms[roomId].removeListener("RoomState.members", this.onRoomMember);
this.rooms[roomId].currentState.removeListener(RoomStateEvent.Members, this.onRoomMember);
}
delete this.rooms[roomId];
}

View File

@@ -23,8 +23,8 @@ limitations under the License.
* for HTTP and WS at some point.
*/
import { User } from "./models/user";
import { NotificationCountType, Room } from "./models/room";
import { User, UserEvent } from "./models/user";
import { NotificationCountType, Room, RoomEvent } from "./models/room";
import { Group } from "./models/group";
import * as utils from "./utils";
import { IDeferred } from "./utils";
@@ -33,7 +33,7 @@ import { EventTimeline } from "./models/event-timeline";
import { PushProcessor } from "./pushprocessor";
import { logger } from './logger';
import { InvalidStoreError } from './errors';
import { IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client";
import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client";
import {
Category,
IEphemeral,
@@ -53,6 +53,8 @@ import { MatrixError, Method } from "./http-api";
import { ISavedSync } from "./store";
import { EventType } from "./@types/event";
import { IPushRules } from "./@types/PushRules";
import { RoomStateEvent } from "./models/room-state";
import { RoomMemberEvent } from "./models/room-member";
const DEBUG = true;
@@ -171,8 +173,10 @@ export class SyncApi {
}
if (client.getNotifTimelineSet()) {
client.reEmitter.reEmit(client.getNotifTimelineSet(),
["Room.timeline", "Room.timelineReset"]);
client.reEmitter.reEmit(client.getNotifTimelineSet(), [
RoomEvent.Timeline,
RoomEvent.TimelineReset,
]);
}
}
@@ -192,16 +196,17 @@ export class SyncApi {
timelineSupport,
unstableClientRelationAggregation,
});
client.reEmitter.reEmit(room, ["Room.name", "Room.timeline",
"Room.redaction",
"Room.redactionCancelled",
"Room.receipt", "Room.tags",
"Room.timelineReset",
"Room.localEchoUpdated",
"Room.accountData",
"Room.myMembership",
"Room.replaceEvent",
"Room.visibilityChange",
client.reEmitter.reEmit(room, [
RoomEvent.Name,
RoomEvent.Redaction,
RoomEvent.RedactionCancelled,
RoomEvent.Receipt,
RoomEvent.Tags,
RoomEvent.LocalEchoUpdated,
RoomEvent.AccountData,
RoomEvent.MyMembership,
RoomEvent.Timeline,
RoomEvent.TimelineReset,
]);
this.registerStateListeners(room);
return room;
@@ -214,7 +219,10 @@ export class SyncApi {
public createGroup(groupId: string): Group {
const client = this.client;
const group = new Group(groupId);
client.reEmitter.reEmit(group, ["Group.profile", "Group.myMembership"]);
client.reEmitter.reEmit(group, [
ClientEvent.GroupProfile,
ClientEvent.GroupMyMembership,
]);
client.store.storeGroup(group);
return group;
}
@@ -229,17 +237,18 @@ export class SyncApi {
// to the client now. We need to add a listener for RoomState.members in
// order to hook them correctly. (TODO: find a better way?)
client.reEmitter.reEmit(room.currentState, [
"RoomState.events", "RoomState.members", "RoomState.newMember",
RoomStateEvent.Events,
RoomStateEvent.Members,
RoomStateEvent.NewMember,
]);
room.currentState.on("RoomState.newMember", function(event, state, member) {
room.currentState.on(RoomStateEvent.NewMember, function(event, state, member) {
member.user = client.getUser(member.userId);
client.reEmitter.reEmit(
member,
[
"RoomMember.name", "RoomMember.typing", "RoomMember.powerLevel",
"RoomMember.membership",
],
);
client.reEmitter.reEmit(member, [
RoomMemberEvent.Name,
RoomMemberEvent.Typing,
RoomMemberEvent.PowerLevel,
RoomMemberEvent.Membership,
]);
});
}
@@ -249,9 +258,9 @@ export class SyncApi {
*/
private deregisterStateListeners(room: Room): void {
// could do with a better way of achieving this.
room.currentState.removeAllListeners("RoomState.events");
room.currentState.removeAllListeners("RoomState.members");
room.currentState.removeAllListeners("RoomState.newMember");
room.currentState.removeAllListeners(RoomStateEvent.Events);
room.currentState.removeAllListeners(RoomStateEvent.Members);
room.currentState.removeAllListeners(RoomStateEvent.NewMember);
}
/**
@@ -314,7 +323,7 @@ export class SyncApi {
room.recalculate();
client.store.storeRoom(room);
client.emit("Room", room);
client.emit(ClientEvent.Room, room);
this.processEventsForNotifs(room, events);
});
@@ -362,7 +371,7 @@ export class SyncApi {
user.setPresenceEvent(presenceEvent);
client.store.storeUser(user);
}
client.emit("event", presenceEvent);
client.emit(ClientEvent.Event, presenceEvent);
});
}
@@ -388,7 +397,7 @@ export class SyncApi {
response.messages.start);
client.store.storeRoom(this._peekRoom);
client.emit("Room", this._peekRoom);
client.emit(ClientEvent.Room, this._peekRoom);
this.peekPoll(this._peekRoom);
return this._peekRoom;
@@ -445,7 +454,7 @@ export class SyncApi {
user.setPresenceEvent(presenceEvent);
this.client.store.storeUser(user);
}
this.client.emit("event", presenceEvent);
this.client.emit(ClientEvent.Event, presenceEvent);
});
// strip out events which aren't for the given room_id (e.g presence)
@@ -840,7 +849,7 @@ export class SyncApi {
logger.error("Caught /sync error", e.stack || e);
// Emit the exception for client handling
this.client.emit("sync.unexpectedError", e);
this.client.emit(ClientEvent.SyncUnexpectedError, e);
}
// update this as it may have changed
@@ -1073,7 +1082,7 @@ export class SyncApi {
user.setPresenceEvent(presenceEvent);
client.store.storeUser(user);
}
client.emit("event", presenceEvent);
client.emit(ClientEvent.Event, presenceEvent);
});
}
@@ -1096,7 +1105,7 @@ export class SyncApi {
client.pushRules = PushProcessor.rewriteDefaultRules(rules);
}
const prevEvent = prevEventsMap[accountDataEvent.getId()];
client.emit("accountData", accountDataEvent, prevEvent);
client.emit(ClientEvent.AccountData, accountDataEvent, prevEvent);
return accountDataEvent;
},
);
@@ -1149,7 +1158,7 @@ export class SyncApi {
}
}
client.emit("toDeviceEvent", toDeviceEvent);
client.emit(ClientEvent.ToDeviceEvent, toDeviceEvent);
},
);
} else {
@@ -1201,10 +1210,10 @@ export class SyncApi {
if (inviteObj.isBrandNewRoom) {
room.recalculate();
client.store.storeRoom(room);
client.emit("Room", room);
client.emit(ClientEvent.Room, room);
}
stateEvents.forEach(function(e) {
client.emit("event", e);
client.emit(ClientEvent.Event, e);
});
room.updateMyMembership("invite");
});
@@ -1325,13 +1334,13 @@ export class SyncApi {
room.recalculate();
if (joinObj.isBrandNewRoom) {
client.store.storeRoom(room);
client.emit("Room", room);
client.emit(ClientEvent.Room, room);
}
this.processEventsForNotifs(room, events);
const processRoomEvent = async (e) => {
client.emit("event", e);
client.emit(ClientEvent.Event, e);
if (e.isState() && e.getType() == "m.room.encryption" && this.opts.crypto) {
await this.opts.crypto.onCryptoEvent(e);
}
@@ -1351,10 +1360,10 @@ export class SyncApi {
await utils.promiseMapSeries(timelineEvents, processRoomEvent);
await utils.promiseMapSeries(threadedEvents, processRoomEvent);
ephemeralEvents.forEach(function(e) {
client.emit("event", e);
client.emit(ClientEvent.Event, e);
});
accountDataEvents.forEach(function(e) {
client.emit("event", e);
client.emit(ClientEvent.Event, e);
});
room.updateMyMembership("join");
@@ -1381,22 +1390,22 @@ export class SyncApi {
room.recalculate();
if (leaveObj.isBrandNewRoom) {
client.store.storeRoom(room);
client.emit("Room", room);
client.emit(ClientEvent.Room, room);
}
this.processEventsForNotifs(room, events);
stateEvents.forEach(function(e) {
client.emit("event", e);
client.emit(ClientEvent.Event, e);
});
timelineEvents.forEach(function(e) {
client.emit("event", e);
client.emit(ClientEvent.Event, e);
});
threadedEvents.forEach(function(e) {
client.emit("event", e);
client.emit(ClientEvent.Event, e);
});
accountDataEvents.forEach(function(e) {
client.emit("event", e);
client.emit(ClientEvent.Event, e);
});
room.updateMyMembership("leave");
@@ -1551,7 +1560,7 @@ export class SyncApi {
group.setMyMembership(sectionName);
if (isBrandNew) {
// Now we've filled in all the fields, emit the Group event
this.client.emit("Group", group);
this.client.emit(ClientEvent.Group, group);
}
}
}
@@ -1778,7 +1787,7 @@ export class SyncApi {
const old = this.syncState;
this.syncState = newState;
this.syncStateData = data;
this.client.emit("sync", this.syncState, old, data);
this.client.emit(ClientEvent.Sync, this.syncState, old, data);
}
/**
@@ -1796,8 +1805,11 @@ export class SyncApi {
function createNewUser(client: MatrixClient, userId: string): User {
const user = new User(userId);
client.reEmitter.reEmit(user, [
"User.avatarUrl", "User.displayName", "User.presence",
"User.currentlyActive", "User.lastPresenceTs",
UserEvent.AvatarUrl,
UserEvent.DisplayName,
UserEvent.Presence,
UserEvent.CurrentlyActive,
UserEvent.LastPresenceTs,
]);
return user;
}

View File

@@ -21,8 +21,6 @@ limitations under the License.
* @module webrtc/call
*/
import { EventEmitter } from 'events';
import { logger } from '../logger';
import * as utils from '../utils';
import { MatrixEvent } from '../models/event';
@@ -47,6 +45,7 @@ import {
import { CallFeed } from './callFeed';
import { MatrixClient } from "../client";
import { ISendEventResponse } from "../@types/requests";
import { EventEmitterEvents, TypedEventEmitter } from "../models/typed-event-emitter";
// events: hangup, error(err), replaced(call), state(state, oldState)
@@ -241,6 +240,21 @@ function genCallID(): string {
return Date.now().toString() + randomString(16);
}
export type CallEventHandlerMap = {
[CallEvent.DataChannel]: (channel: RTCDataChannel) => void;
[CallEvent.FeedsChanged]: (feeds: CallFeed[]) => void;
[CallEvent.Replaced]: (newCall: MatrixCall) => void;
[CallEvent.Error]: (error: CallError) => void;
[CallEvent.RemoteHoldUnhold]: (onHold: boolean) => void;
[CallEvent.LocalHoldUnhold]: (onHold: boolean) => void;
[CallEvent.LengthChanged]: (length: number) => void;
[CallEvent.State]: (state: CallState, oldState?: CallState) => void;
[CallEvent.Hangup]: () => void;
[CallEvent.AssertedIdentityChanged]: () => void;
/* @deprecated */
[CallEvent.HoldUnhold]: (onHold: boolean) => void;
};
/**
* Construct a new Matrix Call.
* @constructor
@@ -252,7 +266,7 @@ function genCallID(): string {
* @param {Array<Object>} opts.turnServers Optional. A list of TURN servers.
* @param {MatrixClient} opts.client The Matrix Client instance to send events to.
*/
export class MatrixCall extends EventEmitter {
export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap> {
public roomId: string;
public callId: string;
public state = CallState.Fledgling;
@@ -1973,7 +1987,7 @@ export class MatrixCall extends EventEmitter {
this.peerConn.close();
}
if (shouldEmit) {
this.emit(CallEvent.Hangup, this);
this.emit(CallEvent.Hangup);
}
}
@@ -1995,7 +2009,7 @@ export class MatrixCall extends EventEmitter {
}
private checkForErrorListener(): void {
if (this.listeners("error").length === 0) {
if (this.listeners(EventEmitterEvents.Error).length === 0) {
throw new Error(
"You MUST attach an error listener using call.on('error', function() {})",
);

View File

@@ -14,17 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from '../models/event';
import { MatrixEvent, MatrixEventEvent } from '../models/event';
import { logger } from '../logger';
import { createNewMatrixCall, MatrixCall, CallErrorCode, CallState, CallDirection } from './call';
import { CallDirection, CallErrorCode, CallState, createNewMatrixCall, MatrixCall } from './call';
import { EventType } from '../@types/event';
import { MatrixClient } from '../client';
import { ClientEvent, MatrixClient } from '../client';
import { MCallAnswer, MCallHangupReject } from "./callEventTypes";
import { SyncState } from "../sync";
import { RoomEvent } from "../models/room";
// Don't ring unless we'd be ringing for at least 3 seconds: the user needs some
// time to press the 'accept' button
const RING_GRACE_PERIOD = 3000;
export enum CallEventHandlerEvent {
Incoming = "Call.incoming",
}
export type CallEventHandlerEventHandlerMap = {
[CallEventHandlerEvent.Incoming]: (call: MatrixCall) => void;
};
export class CallEventHandler {
client: MatrixClient;
calls: Map<string, MatrixCall>;
@@ -47,17 +57,17 @@ export class CallEventHandler {
}
public start() {
this.client.on("sync", this.evaluateEventBuffer);
this.client.on("Room.timeline", this.onRoomTimeline);
this.client.on(ClientEvent.Sync, this.evaluateEventBuffer);
this.client.on(RoomEvent.Timeline, this.onRoomTimeline);
}
public stop() {
this.client.removeListener("sync", this.evaluateEventBuffer);
this.client.removeListener("Room.timeline", this.onRoomTimeline);
this.client.removeListener(ClientEvent.Sync, this.evaluateEventBuffer);
this.client.removeListener(RoomEvent.Timeline, this.onRoomTimeline);
}
private evaluateEventBuffer = async () => {
if (this.client.getSyncState() === "SYNCING") {
if (this.client.getSyncState() === SyncState.Syncing) {
await Promise.all(this.callEventBuffer.map(event => {
this.client.decryptEventIfNeeded(event);
}));
@@ -101,7 +111,7 @@ export class CallEventHandler {
if (event.isBeingDecrypted() || event.isDecryptionFailure()) {
// add an event listener for once the event is decrypted.
event.once("Event.decrypted", async () => {
event.once(MatrixEventEvent.Decrypted, async () => {
if (!this.eventIsACall(event)) return;
if (this.callEventBuffer.includes(event)) {
@@ -221,7 +231,7 @@ export class CallEventHandler {
call.hangup(CallErrorCode.Replaced, true);
}
} else {
this.client.emit("Call.incoming", call);
this.client.emit(CallEventHandlerEvent.Incoming, call);
}
return;
} else if (type === EventType.CallCandidates) {

View File

@@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import EventEmitter from "events";
import { SDPStreamMetadataPurpose } from "./callEventTypes";
import { MatrixClient } from "../client";
import { RoomMember } from "../models/room-member";
import { TypedEventEmitter } from "../models/typed-event-emitter";
const POLLING_INTERVAL = 200; // ms
export const SPEAKING_THRESHOLD = -60; // dB
@@ -47,7 +46,14 @@ export enum CallFeedEvent {
Speaking = "speaking",
}
export class CallFeed extends EventEmitter {
type EventHandlerMap = {
[CallFeedEvent.NewStream]: (stream: MediaStream) => void;
[CallFeedEvent.MuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void;
[CallFeedEvent.VolumeChanged]: (volume: number) => void;
[CallFeedEvent.Speaking]: (speaking: boolean) => void;
};
export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap> {
public stream: MediaStream;
public userId: string;
public purpose: SDPStreamMetadataPurpose;