matrix-js-sdk 42.4.0 → 43.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/lib/@types/PushRules.d.ts +3 -1
- package/lib/@types/PushRules.d.ts.map +1 -1
- package/lib/@types/PushRules.js +2 -0
- package/lib/@types/PushRules.js.map +1 -1
- package/lib/@types/event.d.ts +1 -0
- package/lib/@types/event.d.ts.map +1 -1
- package/lib/@types/event.js.map +1 -1
- package/lib/@types/matrix-sdk-crypto-wasm.d.js.map +1 -1
- package/lib/client.d.ts +88 -37
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +72 -47
- package/lib/client.js.map +1 -1
- package/lib/crypto-api/index.d.ts +3 -3
- package/lib/crypto-api/index.d.ts.map +1 -1
- package/lib/crypto-api/index.js.map +1 -1
- package/lib/embedded.d.ts +17 -1
- package/lib/embedded.d.ts.map +1 -1
- package/lib/embedded.js +46 -1
- package/lib/embedded.js.map +1 -1
- package/lib/errors.d.ts +1 -1
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +2 -2
- package/lib/errors.js.map +1 -1
- package/lib/http-api/fetch.d.ts +7 -1
- package/lib/http-api/fetch.d.ts.map +1 -1
- package/lib/http-api/fetch.js +14 -5
- package/lib/http-api/fetch.js.map +1 -1
- package/lib/http-api/interface.d.ts +34 -8
- package/lib/http-api/interface.d.ts.map +1 -1
- package/lib/http-api/interface.js +10 -3
- package/lib/http-api/interface.js.map +1 -1
- package/lib/http-api/refresh.d.ts +30 -11
- package/lib/http-api/refresh.d.ts.map +1 -1
- package/lib/http-api/refresh.js +72 -5
- package/lib/http-api/refresh.js.map +1 -1
- package/lib/matrix.d.ts +1 -1
- package/lib/matrix.d.ts.map +1 -1
- package/lib/matrix.js.map +1 -1
- package/lib/matrixrtc/CallMembership.d.ts.map +1 -1
- package/lib/matrixrtc/CallMembership.js +20 -6
- package/lib/matrixrtc/CallMembership.js.map +1 -1
- package/lib/matrixrtc/IMembershipManager.d.ts +5 -0
- package/lib/matrixrtc/IMembershipManager.d.ts.map +1 -1
- package/lib/matrixrtc/IMembershipManager.js.map +1 -1
- package/lib/matrixrtc/MatrixRTCSession.d.ts +27 -1
- package/lib/matrixrtc/MatrixRTCSession.d.ts.map +1 -1
- package/lib/matrixrtc/MatrixRTCSession.js +58 -36
- package/lib/matrixrtc/MatrixRTCSession.js.map +1 -1
- package/lib/matrixrtc/MatrixRTCSessionManager.js +1 -1
- package/lib/matrixrtc/MatrixRTCSessionManager.js.map +1 -1
- package/lib/matrixrtc/MembershipManager.d.ts +76 -0
- package/lib/matrixrtc/MembershipManager.d.ts.map +1 -1
- package/lib/matrixrtc/MembershipManager.js +264 -47
- package/lib/matrixrtc/MembershipManager.js.map +1 -1
- package/lib/matrixrtc/index.d.ts +1 -1
- package/lib/matrixrtc/index.d.ts.map +1 -1
- package/lib/matrixrtc/index.js +1 -1
- package/lib/matrixrtc/index.js.map +1 -1
- package/lib/matrixrtc/membershipData/common.d.ts +8 -0
- package/lib/matrixrtc/membershipData/common.d.ts.map +1 -1
- package/lib/matrixrtc/membershipData/common.js +10 -0
- package/lib/matrixrtc/membershipData/common.js.map +1 -1
- package/lib/matrixrtc/membershipData/index.d.ts +1 -1
- package/lib/matrixrtc/membershipData/index.d.ts.map +1 -1
- package/lib/matrixrtc/membershipData/index.js +1 -1
- package/lib/matrixrtc/membershipData/index.js.map +1 -1
- package/lib/matrixrtc/types.d.ts +104 -11
- package/lib/matrixrtc/types.d.ts.map +1 -1
- package/lib/matrixrtc/types.js +263 -19
- package/lib/matrixrtc/types.js.map +1 -1
- package/lib/matrixrtc/utils.d.ts +22 -1
- package/lib/matrixrtc/utils.d.ts.map +1 -1
- package/lib/matrixrtc/utils.js +38 -0
- package/lib/matrixrtc/utils.js.map +1 -1
- package/lib/models/room-sticky-events.d.ts +7 -0
- package/lib/models/room-sticky-events.d.ts.map +1 -1
- package/lib/models/room-sticky-events.js +27 -0
- package/lib/models/room-sticky-events.js.map +1 -1
- package/lib/models/thread.d.ts.map +1 -1
- package/lib/models/thread.js +8 -0
- package/lib/models/thread.js.map +1 -1
- package/lib/oauth/authorize.js +1 -1
- package/lib/oauth/authorize.js.map +1 -1
- package/lib/oauth/index.d.ts +3 -5
- package/lib/oauth/index.d.ts.map +1 -1
- package/lib/oauth/index.js +7 -7
- package/lib/oauth/index.js.map +1 -1
- package/lib/oauth/tokenRefresher.d.ts +10 -4
- package/lib/oauth/tokenRefresher.d.ts.map +1 -1
- package/lib/oauth/tokenRefresher.js +34 -24
- package/lib/oauth/tokenRefresher.js.map +1 -1
- package/lib/receipt-accumulator.d.ts +10 -11
- package/lib/receipt-accumulator.d.ts.map +1 -1
- package/lib/receipt-accumulator.js +26 -25
- package/lib/receipt-accumulator.js.map +1 -1
- package/lib/room-summary-api.d.ts +71 -0
- package/lib/room-summary-api.d.ts.map +1 -0
- package/lib/room-summary-api.js +82 -0
- package/lib/room-summary-api.js.map +1 -0
- package/lib/rust-crypto/backup.d.ts +7 -0
- package/lib/rust-crypto/backup.d.ts.map +1 -1
- package/lib/rust-crypto/backup.js +41 -25
- package/lib/rust-crypto/backup.js.map +1 -1
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +6 -1
- package/lib/sync.js.map +1 -1
- package/package.json +6 -6
- package/src/@types/PushRules.ts +2 -0
- package/src/@types/event.ts +1 -1
- package/src/@types/matrix-sdk-crypto-wasm.d.ts +15 -12
- package/src/client.ts +105 -62
- package/src/crypto-api/index.ts +3 -3
- package/src/embedded.ts +61 -2
- package/src/errors.ts +2 -1
- package/src/http-api/fetch.ts +14 -5
- package/src/http-api/interface.ts +36 -8
- package/src/http-api/refresh.ts +101 -17
- package/src/matrix.ts +1 -1
- package/src/matrixrtc/CallMembership.ts +22 -4
- package/src/matrixrtc/IMembershipManager.ts +6 -0
- package/src/matrixrtc/MatrixRTCSession.ts +72 -49
- package/src/matrixrtc/MatrixRTCSessionManager.ts +1 -1
- package/src/matrixrtc/MembershipManager.ts +284 -61
- package/src/matrixrtc/index.ts +7 -1
- package/src/matrixrtc/membershipData/common.ts +12 -0
- package/src/matrixrtc/membershipData/index.ts +1 -1
- package/src/matrixrtc/types.ts +351 -23
- package/src/matrixrtc/utils.ts +54 -1
- package/src/models/room-sticky-events.ts +26 -1
- package/src/models/thread.ts +9 -0
- package/src/oauth/authorize.ts +1 -1
- package/src/oauth/index.ts +7 -8
- package/src/oauth/tokenRefresher.ts +15 -5
- package/src/receipt-accumulator.ts +30 -26
- package/src/room-summary-api.ts +114 -0
- package/src/rust-crypto/backup.ts +46 -33
- package/src/sync.ts +9 -1
package/src/http-api/refresh.ts
CHANGED
|
@@ -15,8 +15,10 @@ limitations under the License.
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { MatrixError, TokenRefreshLogoutError } from "./errors.ts";
|
|
18
|
-
import { type IHttpOpts } from "./interface.ts";
|
|
18
|
+
import { type AccessTokens, type IHttpOpts, type OAuth2ClientConfig } from "./interface.ts";
|
|
19
19
|
import { sleep } from "../utils.ts";
|
|
20
|
+
import { TokenRefresher } from "../oauth/tokenRefresher.ts";
|
|
21
|
+
import { OAuth2, type ValidatedAuthMetadata } from "../oauth/index.ts";
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* This is an internal module. See {@link MatrixHttpApi} for the public class.
|
|
@@ -28,25 +30,25 @@ export const enum TokenRefreshOutcome {
|
|
|
28
30
|
Logout = "logout",
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
interface Snapshot {
|
|
32
|
-
accessToken: string;
|
|
33
|
-
refreshToken?: string;
|
|
34
|
-
expiry?: Date;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
33
|
// If the token expires in less than this time amount of time, we will eagerly refresh it before making the intended request.
|
|
38
34
|
const REFRESH_IF_TOKEN_EXPIRES_WITHIN_MS = 500;
|
|
39
35
|
// If we get an unknown token error and the token expires in less than this time amount of time, we will refresh it before making the intended request.
|
|
40
36
|
// Otherwise, we will error as the token should not have expired yet and we need to avoid retrying indefinitely.
|
|
41
37
|
const REFRESH_ON_ERROR_IF_TOKEN_EXPIRES_WITHIN_MS = 60 * 1000;
|
|
42
38
|
|
|
43
|
-
type Opts = Pick<
|
|
39
|
+
type Opts = Pick<
|
|
40
|
+
IHttpOpts,
|
|
41
|
+
"onTokenRefresh" | "logger" | "refreshToken" | "accessToken" | "oauth2ClientConfig" | "authMetadataCallback"
|
|
42
|
+
>;
|
|
44
43
|
|
|
45
44
|
/**
|
|
46
45
|
* This class is responsible for managing the access token and refresh token for authenticated requests.
|
|
47
|
-
* It will automatically refresh the access token
|
|
46
|
+
* It will automatically refresh the tokens when the access token is about to expire and can handle
|
|
47
|
+
* Unknown Token errors (via {@link TokenManager.handleUnknownToken})
|
|
48
|
+
*
|
|
49
|
+
* It will update the {@link Opts} object with new tokens as they are refreshed, and also call the {@link IHttpOpts.onTokenRefresh} callback if provided.
|
|
48
50
|
*/
|
|
49
|
-
export class
|
|
51
|
+
export class TokenManager {
|
|
50
52
|
public constructor(private readonly opts: Opts) {}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -57,12 +59,22 @@ export class TokenRefresher {
|
|
|
57
59
|
|
|
58
60
|
private latestTokenRefreshExpiry?: Date;
|
|
59
61
|
|
|
62
|
+
// The object at the OAuth API layer that actually does the refreshing
|
|
63
|
+
// This needs OAuth params to be fetched before it can be constructed so it starts
|
|
64
|
+
// off as undefined and is constructed once we are able to do so.
|
|
65
|
+
private tokenRefresher?: TokenRefresher;
|
|
66
|
+
|
|
67
|
+
// Tracks an in-progress attempt to discover the OAuth2 auth server metadata and construct
|
|
68
|
+
// `tokenRefresher`, so that concurrent callers wait for (and share the result of) the same attempt
|
|
69
|
+
// rather than racing to discover it independently.
|
|
70
|
+
private tokenRefresherPromise?: Promise<TokenRefresher>;
|
|
71
|
+
|
|
60
72
|
/**
|
|
61
73
|
* This function is called before every request to ensure that the access token is valid.
|
|
62
74
|
* @returns a snapshot containing the access token and other properties which must be passed to the handleUnknownToken
|
|
63
75
|
* handler if an M_UNKNOWN_TOKEN error is encountered.
|
|
64
76
|
*/
|
|
65
|
-
public async prepareForRequest(): Promise<
|
|
77
|
+
public async prepareForRequest(): Promise<AccessTokens> {
|
|
66
78
|
// Ensure our token is refreshed before we build the headers/params
|
|
67
79
|
await this.refreshIfNeeded();
|
|
68
80
|
|
|
@@ -93,13 +105,13 @@ export class TokenRefresher {
|
|
|
93
105
|
* @param attempt - the number of attempts made for this request so far
|
|
94
106
|
* @returns a TokenRefreshOutcome indicating the result of the refresh attempt
|
|
95
107
|
*/
|
|
96
|
-
public async handleUnknownToken(snapshot:
|
|
108
|
+
public async handleUnknownToken(snapshot: AccessTokens, attempt: number): Promise<TokenRefreshOutcome> {
|
|
97
109
|
return this._handleUnknownToken(snapshot, attempt);
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
private async _handleUnknownToken(): Promise<TokenRefreshOutcome>;
|
|
101
|
-
private async _handleUnknownToken(snapshot:
|
|
102
|
-
private async _handleUnknownToken(snapshot?:
|
|
113
|
+
private async _handleUnknownToken(snapshot: AccessTokens, attempt: number): Promise<TokenRefreshOutcome>;
|
|
114
|
+
private async _handleUnknownToken(snapshot?: AccessTokens, attempt?: number): Promise<TokenRefreshOutcome> {
|
|
103
115
|
if (snapshot?.expiry) {
|
|
104
116
|
// If our token is unknown, but it should not have expired yet, then we should not refresh
|
|
105
117
|
const expiresIn = snapshot.expiry.getTime() - Date.now();
|
|
@@ -132,8 +144,8 @@ export class TokenRefresher {
|
|
|
132
144
|
* @returns Promise that resolves to a boolean - true when token was refreshed successfully
|
|
133
145
|
*/
|
|
134
146
|
private async doTokenRefresh(attempt?: number): Promise<TokenRefreshOutcome> {
|
|
135
|
-
if (!this.opts.refreshToken || !this.opts.
|
|
136
|
-
this.opts.logger?.error("Unable to refresh token - no refresh token or
|
|
147
|
+
if (!this.opts.refreshToken || !this.opts.oauth2ClientConfig) {
|
|
148
|
+
this.opts.logger?.error("Unable to refresh token - no refresh token or OAuth2 client config");
|
|
137
149
|
return TokenRefreshOutcome.Logout;
|
|
138
150
|
}
|
|
139
151
|
|
|
@@ -144,7 +156,13 @@ export class TokenRefresher {
|
|
|
144
156
|
|
|
145
157
|
try {
|
|
146
158
|
this.opts.logger?.debug("Attempting to refresh token");
|
|
147
|
-
const
|
|
159
|
+
const tokenRefresher = await this.ensureTokenRefresher();
|
|
160
|
+
if (!tokenRefresher) {
|
|
161
|
+
// We already checked `oauth2ClientConfig` is set above, so this should be unreachable.
|
|
162
|
+
return TokenRefreshOutcome.Logout;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { accessToken, refreshToken, expiry } = await tokenRefresher.refreshTokens(this.opts.refreshToken);
|
|
148
166
|
this.opts.accessToken = accessToken;
|
|
149
167
|
this.opts.refreshToken = refreshToken;
|
|
150
168
|
this.latestTokenRefreshExpiry = expiry;
|
|
@@ -163,4 +181,70 @@ export class TokenRefresher {
|
|
|
163
181
|
return TokenRefreshOutcome.Failure;
|
|
164
182
|
}
|
|
165
183
|
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Attempt to revoke the current access and refresh tokens with the OAuth2 authorization server, e.g. as
|
|
187
|
+
* part of logging out. Does nothing if this is not an OAuth2-native session (ie. no `oauth2ClientConfig`
|
|
188
|
+
* was supplied).
|
|
189
|
+
* @throws when discovery of the auth server metadata, or revocation of either token, fails
|
|
190
|
+
*/
|
|
191
|
+
public async revokeTokens(): Promise<void> {
|
|
192
|
+
const tokenRefresher = await this.ensureTokenRefresher();
|
|
193
|
+
if (!tokenRefresher) return;
|
|
194
|
+
|
|
195
|
+
await Promise.all(
|
|
196
|
+
[
|
|
197
|
+
this.opts.accessToken ? tokenRefresher.revokeToken(this.opts.accessToken, "access_token") : undefined,
|
|
198
|
+
this.opts.refreshToken
|
|
199
|
+
? tokenRefresher.revokeToken(this.opts.refreshToken, "refresh_token")
|
|
200
|
+
: undefined,
|
|
201
|
+
].filter((p): p is Promise<void> => p !== undefined),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Lazily constructs (and caches) the {@link TokenRefresher} used to talk to the OAuth2 authorization
|
|
207
|
+
* server, discovering its metadata first if this is the first time it's needed.
|
|
208
|
+
* @returns the token refresher, or undefined if no `oauth2ClientConfig` was supplied
|
|
209
|
+
* @throws when discovery of the auth server metadata fails in a way that isn't worth retrying
|
|
210
|
+
*/
|
|
211
|
+
private async ensureTokenRefresher(): Promise<TokenRefresher | undefined> {
|
|
212
|
+
if (this.tokenRefresher) return this.tokenRefresher;
|
|
213
|
+
if (!this.opts.oauth2ClientConfig) return undefined;
|
|
214
|
+
if (!this.opts.authMetadataCallback) return undefined;
|
|
215
|
+
|
|
216
|
+
if (!this.tokenRefresherPromise) {
|
|
217
|
+
this.tokenRefresherPromise = this.discoverTokenRefresher(
|
|
218
|
+
this.opts.oauth2ClientConfig,
|
|
219
|
+
this.opts.authMetadataCallback,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
this.tokenRefresher = await this.tokenRefresherPromise;
|
|
225
|
+
return this.tokenRefresher;
|
|
226
|
+
} finally {
|
|
227
|
+
// Either we now have `tokenRefresher` cached and won't need this again, or discovery
|
|
228
|
+
// failed and a future call should be free to retry from scratch.
|
|
229
|
+
this.tokenRefresherPromise = undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Discovers the OAuth2 auth server metadata and constructs the OAuth2 client and token refresher from it.
|
|
235
|
+
* Throws if the request fails.
|
|
236
|
+
*/
|
|
237
|
+
private async discoverTokenRefresher(
|
|
238
|
+
config: OAuth2ClientConfig,
|
|
239
|
+
authMetaDataCallback: () => Promise<ValidatedAuthMetadata>,
|
|
240
|
+
): Promise<TokenRefresher> {
|
|
241
|
+
const metadata = await authMetaDataCallback();
|
|
242
|
+
|
|
243
|
+
const oauth2 = new OAuth2(metadata, {
|
|
244
|
+
clientId: config.clientId,
|
|
245
|
+
deviceId: config.deviceId,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return new TokenRefresher(oauth2, (tokens) => this.opts.onTokenRefresh?.(tokens));
|
|
249
|
+
}
|
|
166
250
|
}
|
package/src/matrix.ts
CHANGED
|
@@ -84,7 +84,7 @@ export * from "./models/event-status.ts";
|
|
|
84
84
|
export * from "./models/profile-keys.ts";
|
|
85
85
|
export * from "./models/related-relations.ts";
|
|
86
86
|
export { type StickyMatrixEvent, RoomStickyEventsEvent } from "./models/room-sticky-events.ts";
|
|
87
|
-
export type { RoomSummary } from "./
|
|
87
|
+
export type { RoomSummary } from "./room-summary-api.ts";
|
|
88
88
|
export * as ContentHelpers from "./content-helpers.ts";
|
|
89
89
|
export * as SecretStorage from "./secret-storage.ts";
|
|
90
90
|
export { createNewMatrixCall, CallEvent } from "./webrtc/call.ts";
|
|
@@ -36,6 +36,20 @@ import { EventType } from "../@types/event.ts";
|
|
|
36
36
|
*/
|
|
37
37
|
export const DEFAULT_EXPIRE_DURATION = 1000 * 60 * 60 * 4;
|
|
38
38
|
|
|
39
|
+
/** The fields of a legacy session membership that belong to the session, not the application */
|
|
40
|
+
const sessionFields: (keyof SessionMembershipData)[] = [
|
|
41
|
+
"application",
|
|
42
|
+
"call_id",
|
|
43
|
+
"device_id",
|
|
44
|
+
"focus_active",
|
|
45
|
+
"foci_preferred",
|
|
46
|
+
"created_ts",
|
|
47
|
+
"scope",
|
|
48
|
+
"expires",
|
|
49
|
+
"m.call.intent",
|
|
50
|
+
"membershipID",
|
|
51
|
+
];
|
|
52
|
+
|
|
39
53
|
/**
|
|
40
54
|
* Describes the source event type that provided the membership data.
|
|
41
55
|
*/
|
|
@@ -261,10 +275,14 @@ export class CallMembership {
|
|
|
261
275
|
case MembershipKind.RTC:
|
|
262
276
|
return data.application;
|
|
263
277
|
case MembershipKind.Session:
|
|
264
|
-
default:
|
|
265
|
-
// SessionData
|
|
266
|
-
//
|
|
267
|
-
|
|
278
|
+
default: {
|
|
279
|
+
// SessionData has no application object: the application's own
|
|
280
|
+
// data sits at the top level beside the session fields, so
|
|
281
|
+
// everything but those is the application's
|
|
282
|
+
const { application, ...rest } = data;
|
|
283
|
+
for (const key of sessionFields) delete rest[key as keyof typeof rest];
|
|
284
|
+
return { ...rest, "type": application, "m.call.intent": data["m.call.intent"] };
|
|
285
|
+
}
|
|
268
286
|
}
|
|
269
287
|
}
|
|
270
288
|
|
|
@@ -117,4 +117,10 @@ export interface IMembershipManager extends TypedEventEmitter<
|
|
|
117
117
|
* @param callIntent The new intent to set.
|
|
118
118
|
*/
|
|
119
119
|
updateCallIntent(callIntent: RTCCallIntent): Promise<void>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Replace the application-specific data of our membership (see
|
|
123
|
+
* `SessionConfig.applicationData`), re-sending the membership if it changed.
|
|
124
|
+
*/
|
|
125
|
+
updateApplicationData(applicationData: Record<string, unknown>): Promise<void>;
|
|
120
126
|
}
|
|
@@ -38,6 +38,9 @@ import type {
|
|
|
38
38
|
RtcSlotEventContent,
|
|
39
39
|
RtcSlotEncryptionContent,
|
|
40
40
|
} from "./types.ts";
|
|
41
|
+
import { RTC_NOTIFICATION_MAX_SENDER_TS_AHEAD_MS } from "./types.ts";
|
|
42
|
+
import { isLeftMembershipContent } from "./membershipData/index.ts";
|
|
43
|
+
import { UnsupportedStickyEventsEndpointError } from "../errors.ts";
|
|
41
44
|
import {
|
|
42
45
|
MembershipManagerEvent,
|
|
43
46
|
type MembershipManagerEventHandlerMap,
|
|
@@ -48,7 +51,7 @@ import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts";
|
|
|
48
51
|
import { TypedReEmitter } from "../ReEmitter.ts";
|
|
49
52
|
import { type IContent, type MatrixEvent } from "../models/event.ts";
|
|
50
53
|
import { RoomStickyEventsEvent, type RoomStickyEventsMap } from "../models/room-sticky-events.ts";
|
|
51
|
-
import { computeSlotId } from "./utils.ts";
|
|
54
|
+
import { computeSlotId, getSlotEventContent, isSlotClosed, isSlotOpen } from "./utils.ts";
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* Events emitted by MatrixRTCSession
|
|
@@ -101,6 +104,15 @@ export interface SessionConfig {
|
|
|
101
104
|
*/
|
|
102
105
|
callIntent?: RTCCallIntent;
|
|
103
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Application-specific data to publish in our membership alongside the
|
|
109
|
+
* application `type` and `m.call.intent`: in the `application` object of
|
|
110
|
+
* an `m.rtc.member` event, or at the top level of a legacy `m.call.member`
|
|
111
|
+
* one. Keys should be namespaced. Read back through
|
|
112
|
+
* {@link CallMembership.applicationData}.
|
|
113
|
+
*/
|
|
114
|
+
applicationData?: Record<string, unknown>;
|
|
115
|
+
|
|
104
116
|
/**
|
|
105
117
|
* How long (in milliseconds) the callee's client should keep ringing/waiting for an
|
|
106
118
|
* answer before the sender gives up and the call notification is considered timed out.
|
|
@@ -358,6 +370,16 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|
|
358
370
|
return isSlotClosed(this.roomSubset, this.slotDescription);
|
|
359
371
|
}
|
|
360
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Whether this session's slot is open.
|
|
375
|
+
*
|
|
376
|
+
* @returns `true` if the slot is open, `false` if the slot is closed or `undefined`
|
|
377
|
+
* if no slot exists.
|
|
378
|
+
*/
|
|
379
|
+
public isSlotOpen(): boolean | undefined {
|
|
380
|
+
return isSlotOpen(this.roomSubset, this.slotDescription);
|
|
381
|
+
}
|
|
382
|
+
|
|
361
383
|
/**
|
|
362
384
|
* Ensures this session's slot is open, sending a slot state event to open (or create) it if needed.
|
|
363
385
|
*
|
|
@@ -721,6 +743,18 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|
|
721
743
|
await this.membershipManager?.updateCallIntent(callIntent);
|
|
722
744
|
}
|
|
723
745
|
|
|
746
|
+
/**
|
|
747
|
+
* Replace the application-specific data in our membership (see
|
|
748
|
+
* {@link SessionConfig.applicationData}), re-sending it if it changed.
|
|
749
|
+
*/
|
|
750
|
+
public async updateApplicationData(applicationData: Record<string, unknown>): Promise<void> {
|
|
751
|
+
const myMembership = this.membershipManager?.ownMembership;
|
|
752
|
+
if (!myMembership) {
|
|
753
|
+
throw Error("Not connected yet");
|
|
754
|
+
}
|
|
755
|
+
await this.membershipManager?.updateApplicationData(applicationData);
|
|
756
|
+
}
|
|
757
|
+
|
|
724
758
|
/**
|
|
725
759
|
* Re-emit an EncryptionKeyChanged event for each tracked encryption key. This can be used to export
|
|
726
760
|
* the keys.
|
|
@@ -776,11 +810,13 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|
|
776
810
|
callIntent?: RTCCallIntent,
|
|
777
811
|
): void {
|
|
778
812
|
const lifetime = this.joinConfig?.notificationLifetimeMs ?? 90_000;
|
|
813
|
+
const slotId = computeSlotId(this.slotDescription);
|
|
779
814
|
const sendNotificationEvent = async (): Promise<{
|
|
780
815
|
response: ISendEventResponse;
|
|
781
816
|
content: IRTCNotificationContent;
|
|
782
817
|
}> => {
|
|
783
818
|
const content: IRTCNotificationContent = {
|
|
819
|
+
"slot_id": slotId,
|
|
784
820
|
"m.mentions": { user_ids: [], room: true },
|
|
785
821
|
"notification_type": notificationType,
|
|
786
822
|
"m.relates_to": {
|
|
@@ -789,12 +825,12 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|
|
789
825
|
},
|
|
790
826
|
"sender_ts": Date.now(),
|
|
791
827
|
"lifetime": lifetime,
|
|
828
|
+
"msc4354_sticky_key": slotId,
|
|
792
829
|
};
|
|
793
830
|
if (callIntent) {
|
|
794
831
|
content["m.call.intent"] = callIntent;
|
|
795
832
|
}
|
|
796
|
-
|
|
797
|
-
return { response, content };
|
|
833
|
+
return { response: await this.sendNotificationEvent(content), content };
|
|
798
834
|
};
|
|
799
835
|
|
|
800
836
|
void sendNotificationEvent()
|
|
@@ -803,9 +839,30 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|
|
803
839
|
const newResult = { ...notification.response, ...notification.content };
|
|
804
840
|
this.emit(MatrixRTCSessionEvent.DidSendCallNotification, newResult);
|
|
805
841
|
})
|
|
806
|
-
.catch((
|
|
807
|
-
|
|
842
|
+
.catch((error) => this.logger.error("Failed to send call notification", error));
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Sends a notification event, as a sticky event (MSC4354) where the server supports it.
|
|
847
|
+
*/
|
|
848
|
+
private async sendNotificationEvent(content: IRTCNotificationContent): Promise<ISendEventResponse> {
|
|
849
|
+
const roomId = this.roomSubset.roomId;
|
|
850
|
+
try {
|
|
851
|
+
// Stay sticky slightly longer than the lifetime, since the server measures the sticky duration
|
|
852
|
+
// from `origin_server_ts` while receivers measure the lifetime from `sender_ts`.
|
|
853
|
+
const stickyDurationMs = content.lifetime + RTC_NOTIFICATION_MAX_SENDER_TS_AHEAD_MS;
|
|
854
|
+
return await this.client._unstable_sendStickyEvent(
|
|
855
|
+
roomId,
|
|
856
|
+
stickyDurationMs,
|
|
857
|
+
null,
|
|
858
|
+
EventType.RTCNotification,
|
|
859
|
+
content,
|
|
808
860
|
);
|
|
861
|
+
} catch (error) {
|
|
862
|
+
if (!(error instanceof UnsupportedStickyEventsEndpointError)) throw error;
|
|
863
|
+
this.logger.debug("Server does not support sticky events, sending notification as a regular event");
|
|
864
|
+
return await this.client.sendEvent(roomId, EventType.RTCNotification, content);
|
|
865
|
+
}
|
|
809
866
|
}
|
|
810
867
|
|
|
811
868
|
/**
|
|
@@ -843,13 +900,14 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|
|
843
900
|
|
|
844
901
|
/**
|
|
845
902
|
* Call this when something changed that may impacts the current MatrixRTC members in this session.
|
|
903
|
+
*
|
|
904
|
+
* @deprecated use {@link ensureRecalculateSessionMembers} instead.
|
|
846
905
|
*/
|
|
847
|
-
// We allow this name schema since this function should only be used for testing purposes.
|
|
848
906
|
public _onRTCSessionMemberUpdate = async (): Promise<void> => {
|
|
849
|
-
await this.
|
|
907
|
+
await this.ensureRecalculateSessionMembers();
|
|
850
908
|
};
|
|
851
909
|
|
|
852
|
-
//
|
|
910
|
+
// Recalculations are chained onto this promise, so they never run in parallel.
|
|
853
911
|
private recalculateSessionMembersPromise: Promise<void> = Promise.resolve();
|
|
854
912
|
|
|
855
913
|
/**
|
|
@@ -857,7 +915,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|
|
857
915
|
* Also ensures that only one recalculation is made at a time.
|
|
858
916
|
* @returns A promise resolving when the state has been recalculated.
|
|
859
917
|
*/
|
|
860
|
-
|
|
918
|
+
public ensureRecalculateSessionMembers(): Promise<void> {
|
|
861
919
|
if (this.membershipNeedsRecalculation) {
|
|
862
920
|
// We have already requested recalcuation, don't attempt a new one.
|
|
863
921
|
return this.recalculateSessionMembersPromise;
|
|
@@ -952,7 +1010,7 @@ async function computeBackendIdentityAndVerifyMemberEvents(
|
|
|
952
1010
|
const content = memberEvent.getContent();
|
|
953
1011
|
|
|
954
1012
|
// Quick filter to avoid unneeded processing of invalid events or left events.
|
|
955
|
-
if (!quickFilterNonRelevantContents(content
|
|
1013
|
+
if (!quickFilterNonRelevantContents(content)) {
|
|
956
1014
|
continue;
|
|
957
1015
|
}
|
|
958
1016
|
|
|
@@ -970,11 +1028,12 @@ async function computeBackendIdentityAndVerifyMemberEvents(
|
|
|
970
1028
|
return callMemberships;
|
|
971
1029
|
}
|
|
972
1030
|
|
|
973
|
-
function quickFilterNonRelevantContents(content: IContent
|
|
1031
|
+
function quickFilterNonRelevantContents(content: IContent): boolean {
|
|
1032
|
+
// Don't even bother about left memberships (saves us from costly type/"key in" checks in bigger rooms)
|
|
1033
|
+
if (isLeftMembershipContent(content)) return false;
|
|
1034
|
+
|
|
974
1035
|
// Ignore sticky keys for the count
|
|
975
1036
|
const eventKeysCount = Object.keys(content).filter((k) => k !== "msc4354_sticky_key").length;
|
|
976
|
-
// Don't even bother about empty events (saves us from costly type/"key in" checks in bigger rooms)
|
|
977
|
-
if (eventKeysCount === 0) return false;
|
|
978
1037
|
|
|
979
1038
|
// We first decide if it's a MSC4143 event (per device state key)
|
|
980
1039
|
if (eventKeysCount > 1 && "application" in content) {
|
|
@@ -1016,42 +1075,6 @@ function isValidMembership(
|
|
|
1016
1075
|
return true;
|
|
1017
1076
|
}
|
|
1018
1077
|
|
|
1019
|
-
/**
|
|
1020
|
-
* Reads the slot state event's content for the given slot description.
|
|
1021
|
-
*
|
|
1022
|
-
* @returns The slot event's content, or `undefined` if no slot event exists for the given description.
|
|
1023
|
-
*/
|
|
1024
|
-
function getSlotEventContent(
|
|
1025
|
-
room: Pick<Room, "getLiveTimeline">,
|
|
1026
|
-
slotDescription: SlotDescription,
|
|
1027
|
-
): RtcSlotEventContent | undefined {
|
|
1028
|
-
const slotId = computeSlotId(slotDescription);
|
|
1029
|
-
const slotEvent = room
|
|
1030
|
-
.getLiveTimeline()
|
|
1031
|
-
.getState(EventTimeline.FORWARDS)
|
|
1032
|
-
?.getStateEvents(EventType.RTCSlot, slotId);
|
|
1033
|
-
if (!slotEvent) return undefined;
|
|
1034
|
-
|
|
1035
|
-
return slotEvent.getContent<RtcSlotEventContent>();
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
/**
|
|
1039
|
-
* Whether the given slot is closed.
|
|
1040
|
-
*
|
|
1041
|
-
* @returns `true` if the slot is closed, `false` if the slot is open or `undefined`
|
|
1042
|
-
* if no slot exists.
|
|
1043
|
-
*/
|
|
1044
|
-
function isSlotClosed(room: Pick<Room, "getLiveTimeline">, slotDescription: SlotDescription): boolean | undefined {
|
|
1045
|
-
const content = getSlotEventContent(room, slotDescription) as Partial<RtcSlotEventContent> | undefined;
|
|
1046
|
-
if (content === undefined) return undefined;
|
|
1047
|
-
|
|
1048
|
-
return (
|
|
1049
|
-
content.status !== "open" ||
|
|
1050
|
-
typeof content.application !== "object" ||
|
|
1051
|
-
content.application?.type !== slotDescription.application
|
|
1052
|
-
);
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
1078
|
/**
|
|
1056
1079
|
* Collects the raw member events from room state and sticky store.
|
|
1057
1080
|
*/
|
|
@@ -172,7 +172,7 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
|
|
|
172
172
|
// Alternatively we would need to setup some event emission when the RTC session ended.
|
|
173
173
|
// TODO we want to add the emission en session end. This makes the responsibility of the session manager more clear.
|
|
174
174
|
|
|
175
|
-
await session.
|
|
175
|
+
await session.ensureRecalculateSessionMembers().catch((error) => {
|
|
176
176
|
this.logger.error(`Error updating RTC session members for ${room.roomId}: ${error}`);
|
|
177
177
|
});
|
|
178
178
|
|