matrix-js-sdk 37.1.0 → 37.2.0-rc.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/git-revision.txt +1 -1
- package/lib/client.d.ts +11 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +24 -5
- package/lib/client.js.map +1 -1
- package/lib/content-helpers.d.ts.map +1 -1
- package/lib/crypto-api/index.d.ts +9 -0
- package/lib/crypto-api/index.d.ts.map +1 -1
- package/lib/crypto-api/index.js.map +1 -1
- package/lib/embedded.d.ts.map +1 -1
- package/lib/embedded.js +27 -14
- package/lib/embedded.js.map +1 -1
- package/lib/errors.d.ts +7 -0
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +11 -0
- package/lib/errors.js.map +1 -1
- package/lib/http-api/fetch.d.ts +4 -2
- package/lib/http-api/fetch.d.ts.map +1 -1
- package/lib/http-api/fetch.js.map +1 -1
- package/lib/matrixrtc/EncryptionManager.d.ts.map +1 -1
- package/lib/matrixrtc/{MembershipManager.d.ts → LegacyMembershipManager.d.ts} +4 -40
- package/lib/matrixrtc/LegacyMembershipManager.d.ts.map +1 -0
- package/lib/matrixrtc/{MembershipManager.js → LegacyMembershipManager.js} +19 -11
- package/lib/matrixrtc/LegacyMembershipManager.js.map +1 -0
- package/lib/matrixrtc/LivekitFocus.d.ts.map +1 -1
- package/lib/matrixrtc/MatrixRTCSession.d.ts +28 -1
- package/lib/matrixrtc/MatrixRTCSession.d.ts.map +1 -1
- package/lib/matrixrtc/MatrixRTCSession.js +19 -5
- package/lib/matrixrtc/MatrixRTCSession.js.map +1 -1
- package/lib/matrixrtc/NewMembershipManager.d.ts +210 -0
- package/lib/matrixrtc/NewMembershipManager.d.ts.map +1 -0
- package/lib/matrixrtc/NewMembershipManager.js +784 -0
- package/lib/matrixrtc/NewMembershipManager.js.map +1 -0
- package/lib/matrixrtc/NewMembershipManagerActionScheduler.d.ts +57 -0
- package/lib/matrixrtc/NewMembershipManagerActionScheduler.d.ts.map +1 -0
- package/lib/matrixrtc/NewMembershipManagerActionScheduler.js +124 -0
- package/lib/matrixrtc/NewMembershipManagerActionScheduler.js.map +1 -0
- package/lib/models/beacon.d.ts.map +1 -1
- package/lib/models/poll.d.ts.map +1 -1
- package/lib/oidc/authorize.d.ts.map +1 -1
- package/lib/oidc/discovery.d.ts.map +1 -1
- package/lib/oidc/register.d.ts +2 -1
- package/lib/oidc/register.d.ts.map +1 -1
- package/lib/oidc/register.js +15 -4
- package/lib/oidc/register.js.map +1 -1
- package/lib/oidc/validate.d.ts.map +1 -1
- package/lib/rust-crypto/rust-crypto.d.ts +9 -0
- package/lib/rust-crypto/rust-crypto.d.ts.map +1 -1
- package/lib/rust-crypto/rust-crypto.js +136 -106
- package/lib/rust-crypto/rust-crypto.js.map +1 -1
- package/lib/store/indexeddb-store-worker.d.ts.map +1 -1
- package/lib/testing.d.ts.map +1 -1
- package/lib/utils.d.ts +8 -0
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +20 -3
- package/lib/utils.js.map +1 -1
- package/lib/webrtc/call.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/client.ts +35 -5
- package/src/crypto-api/index.ts +10 -0
- package/src/embedded.ts +62 -27
- package/src/errors.ts +13 -0
- package/src/http-api/fetch.ts +4 -4
- package/src/matrixrtc/{MembershipManager.ts → LegacyMembershipManager.ts} +19 -39
- package/src/matrixrtc/MatrixRTCSession.ts +52 -7
- package/src/matrixrtc/NewMembershipManager.ts +943 -0
- package/src/matrixrtc/NewMembershipManagerActionScheduler.ts +133 -0
- package/src/oidc/register.ts +17 -4
- package/src/rust-crypto/rust-crypto.ts +37 -11
- package/src/utils.ts +17 -0
- package/lib/matrixrtc/MembershipManager.d.ts.map +0 -1
- package/lib/matrixrtc/MembershipManager.js.map +0 -1
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
|
+
/*
|
|
4
|
+
Copyright 2025 The Matrix.org Foundation C.I.C.
|
|
5
|
+
|
|
6
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
you may not use this file except in compliance with the License.
|
|
8
|
+
You may obtain a copy of the License at
|
|
9
|
+
|
|
10
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
|
|
12
|
+
Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
See the License for the specific language governing permissions and
|
|
16
|
+
limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { EventType } from "../@types/event.js";
|
|
20
|
+
import { UpdateDelayedEventAction } from "../@types/requests.js";
|
|
21
|
+
import { UnsupportedDelayedEventsEndpointError } from "../errors.js";
|
|
22
|
+
import { ConnectionError, HTTPError, MatrixError } from "../http-api/errors.js";
|
|
23
|
+
import { logger as rootLogger } from "../logger.js";
|
|
24
|
+
import { defer } from "../utils.js";
|
|
25
|
+
import { DEFAULT_EXPIRE_DURATION } from "./CallMembership.js";
|
|
26
|
+
import { isLivekitFocusActive } from "./LivekitFocus.js";
|
|
27
|
+
import { ActionScheduler } from "./NewMembershipManagerActionScheduler.js";
|
|
28
|
+
var logger = rootLogger.getChild("MatrixRTCSession");
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* This interface defines what a MembershipManager uses and exposes.
|
|
32
|
+
* This interface is what we use to write tests and allows changing the actual implementation
|
|
33
|
+
* without breaking tests because of some internal method renaming.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/* MembershipActionTypes:
|
|
39
|
+
▼
|
|
40
|
+
┌─────────────────────┐
|
|
41
|
+
│SendFirstDelayedEvent│
|
|
42
|
+
└─────────────────────┘
|
|
43
|
+
│
|
|
44
|
+
▼
|
|
45
|
+
┌─────────────┐
|
|
46
|
+
┌────────────│SendJoinEvent│────────────┐
|
|
47
|
+
│ └─────────────┘ │
|
|
48
|
+
│ ┌─────┐ ┌──────┐ │ ┌──────┐
|
|
49
|
+
▼ ▼ │ │ ▼ ▼ ▼ │
|
|
50
|
+
┌────────────┐ │ │ ┌───────────────────┐ │
|
|
51
|
+
│UpdateExpiry│ │ │ │RestartDelayedEvent│ │
|
|
52
|
+
└────────────┘ │ │ └───────────────────┘ │
|
|
53
|
+
│ │ │ │ │ │
|
|
54
|
+
└─────┘ └──────┘ │ │
|
|
55
|
+
│ │
|
|
56
|
+
┌────────────────────┐ │ │
|
|
57
|
+
│SendMainDelayedEvent│◄───────┘ │
|
|
58
|
+
└───────────────────┬┘ │
|
|
59
|
+
│ │
|
|
60
|
+
└─────────────────────┘
|
|
61
|
+
STOP ALL ABOVE
|
|
62
|
+
▼
|
|
63
|
+
┌───────────────────────────────┐
|
|
64
|
+
│ SendScheduledDelayedLeaveEvent│
|
|
65
|
+
└───────────────────────────────┘
|
|
66
|
+
│
|
|
67
|
+
▼
|
|
68
|
+
┌──────────────┐
|
|
69
|
+
│SendLeaveEvent│
|
|
70
|
+
└──────────────┘
|
|
71
|
+
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* The different types of actions the MembershipManager can take.
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
export var MembershipActionType = /*#__PURE__*/function (MembershipActionType) {
|
|
78
|
+
MembershipActionType["SendFirstDelayedEvent"] = "SendFirstDelayedEvent";
|
|
79
|
+
MembershipActionType["SendJoinEvent"] = "SendJoinEvent";
|
|
80
|
+
MembershipActionType["RestartDelayedEvent"] = "RestartDelayedEvent";
|
|
81
|
+
MembershipActionType["UpdateExpiry"] = "UpdateExpiry";
|
|
82
|
+
MembershipActionType["SendMainDelayedEvent"] = "SendMainDelayedEvent";
|
|
83
|
+
MembershipActionType["SendScheduledDelayedLeaveEvent"] = "SendScheduledDelayedLeaveEvent";
|
|
84
|
+
MembershipActionType["SendLeaveEvent"] = "SendLeaveEvent";
|
|
85
|
+
return MembershipActionType;
|
|
86
|
+
}({});
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
var Status = /*#__PURE__*/function (Status) {
|
|
92
|
+
Status["Disconnected"] = "Disconnected";
|
|
93
|
+
Status["Connecting"] = "Connecting";
|
|
94
|
+
Status["ConnectingFailed"] = "ConnectingFailed";
|
|
95
|
+
Status["Connected"] = "Connected";
|
|
96
|
+
Status["Reconnecting"] = "Reconnecting";
|
|
97
|
+
Status["Disconnecting"] = "Disconnecting";
|
|
98
|
+
Status["Stuck"] = "Stuck";
|
|
99
|
+
Status["Unknown"] = "Unknown";
|
|
100
|
+
return Status;
|
|
101
|
+
}(Status || {});
|
|
102
|
+
/**
|
|
103
|
+
* This class is responsible for sending all events relating to the own membership of a matrixRTC call.
|
|
104
|
+
* It has the following tasks:
|
|
105
|
+
* - Send the users leave delayed event before sending the membership
|
|
106
|
+
* - Send the users membership if the state machine is started
|
|
107
|
+
* - Check if the delayed event was canceled due to sending the membership
|
|
108
|
+
* - update the delayed event (`restart`)
|
|
109
|
+
* - Update the state event every ~5h = `DEFAULT_EXPIRE_DURATION` (so it does not get treated as expired)
|
|
110
|
+
* - When the state machine is stopped:
|
|
111
|
+
* - Disconnect the member
|
|
112
|
+
* - Stop the timer for the delay refresh
|
|
113
|
+
* - Stop the timer for updating the state event
|
|
114
|
+
*/
|
|
115
|
+
export class MembershipManager {
|
|
116
|
+
isJoined() {
|
|
117
|
+
return this.activated;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Puts the MembershipManager in a state where it tries to be joined.
|
|
122
|
+
* It will send delayed events and membership events
|
|
123
|
+
* @param fociPreferred
|
|
124
|
+
* @param focusActive
|
|
125
|
+
* @param onError This will be called once the membership manager encounters an unrecoverable error.
|
|
126
|
+
* This should bubble up the the frontend to communicate that the call does not work in the current environment.
|
|
127
|
+
*/
|
|
128
|
+
join(fociPreferred, focusActive, onError) {
|
|
129
|
+
if (this.scheduler.running) {
|
|
130
|
+
logger.error("MembershipManager is already running. Ignoring join request.");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.fociPreferred = fociPreferred;
|
|
134
|
+
this.focusActive = focusActive;
|
|
135
|
+
this.leavePromiseDefer = undefined;
|
|
136
|
+
this.activated = true;
|
|
137
|
+
this.state = MembershipManager.defaultState;
|
|
138
|
+
this.scheduler.startWithJoin().then(() => {
|
|
139
|
+
if (!this.scheduler.running) {
|
|
140
|
+
var _this$leavePromiseDef;
|
|
141
|
+
(_this$leavePromiseDef = this.leavePromiseDefer) === null || _this$leavePromiseDef === void 0 || _this$leavePromiseDef.resolve(true);
|
|
142
|
+
this.leavePromiseDefer = undefined;
|
|
143
|
+
}
|
|
144
|
+
}).catch(e => {
|
|
145
|
+
logger.error("MembershipManager stopped because: ", e);
|
|
146
|
+
onError === null || onError === void 0 || onError(e);
|
|
147
|
+
})
|
|
148
|
+
// Should already be set to false when calling `leave` in non error cases.
|
|
149
|
+
.finally(() => this.activated = false);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Leave from the call (Send an rtc session event with content: `{}`)
|
|
154
|
+
* @param timeout the maximum duration this promise will take to resolve
|
|
155
|
+
* @returns true if it managed to leave and false if the timeout condition happened.
|
|
156
|
+
*/
|
|
157
|
+
leave(timeout) {
|
|
158
|
+
if (!this.scheduler.running) {
|
|
159
|
+
logger.warn("Called MembershipManager.leave() even though the MembershipManager is not running");
|
|
160
|
+
return Promise.resolve(true);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// We use the promise to track if we already scheduled a leave event
|
|
164
|
+
// So we do not check scheduler.actions/scheduler.insertions
|
|
165
|
+
if (!this.leavePromiseDefer) {
|
|
166
|
+
// reset scheduled actions so we will not do any new actions.
|
|
167
|
+
this.leavePromiseDefer = defer();
|
|
168
|
+
this.activated = false;
|
|
169
|
+
this.scheduler.initiateLeave();
|
|
170
|
+
if (timeout) setTimeout(() => {
|
|
171
|
+
var _this$leavePromiseDef2;
|
|
172
|
+
return (_this$leavePromiseDef2 = this.leavePromiseDefer) === null || _this$leavePromiseDef2 === void 0 ? void 0 : _this$leavePromiseDef2.resolve(false);
|
|
173
|
+
}, timeout);
|
|
174
|
+
}
|
|
175
|
+
return this.leavePromiseDefer.promise;
|
|
176
|
+
}
|
|
177
|
+
onRTCSessionMemberUpdate(memberships) {
|
|
178
|
+
var _this = this;
|
|
179
|
+
return _asyncToGenerator(function* () {
|
|
180
|
+
var isMyMembership = m => m.sender === _this.client.getUserId() && m.deviceId === _this.client.getDeviceId();
|
|
181
|
+
if (_this.isJoined() && !memberships.some(isMyMembership)) {
|
|
182
|
+
// If one of these actions are scheduled or are getting inserted in the next iteration, we should already
|
|
183
|
+
// take care of our missing membership.
|
|
184
|
+
var sendingMembershipActions = [MembershipActionType.SendFirstDelayedEvent, MembershipActionType.SendJoinEvent];
|
|
185
|
+
logger.warn("Missing own membership: force re-join");
|
|
186
|
+
if (_this.scheduler.actions.find(a => sendingMembershipActions.includes(a.type))) {
|
|
187
|
+
logger.error("NewMembershipManger tried adding another `SendFirstDelayedEvent` actions even though we already have one in the Queue\nActionQueueOnMemberUpdate:", _this.scheduler.actions);
|
|
188
|
+
} else {
|
|
189
|
+
// Only react to our own membership missing if we have not already scheduled sending a new membership DirectMembershipManagerAction.Join
|
|
190
|
+
_this.state.hasMemberStateEvent = false;
|
|
191
|
+
_this.scheduler.initiateJoin();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return Promise.resolve();
|
|
195
|
+
})();
|
|
196
|
+
}
|
|
197
|
+
getActiveFocus() {
|
|
198
|
+
if (this.focusActive) {
|
|
199
|
+
// A livekit active focus
|
|
200
|
+
if (isLivekitFocusActive(this.focusActive)) {
|
|
201
|
+
if (this.focusActive.focus_selection === "oldest_membership") {
|
|
202
|
+
var oldestMembership = this.getOldestMembership();
|
|
203
|
+
return oldestMembership === null || oldestMembership === void 0 ? void 0 : oldestMembership.getPreferredFoci()[0];
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
logger.warn("Unknown own ActiveFocus type. This makes it impossible to connect to an SFU.");
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
// We do not understand the membership format (could be legacy). We default to oldestMembership
|
|
210
|
+
// Once there are other methods this is a hard error!
|
|
211
|
+
var _oldestMembership = this.getOldestMembership();
|
|
212
|
+
return _oldestMembership === null || _oldestMembership === void 0 ? void 0 : _oldestMembership.getPreferredFoci()[0];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @throws if the client does not return user or device id.
|
|
218
|
+
* @param joinConfig
|
|
219
|
+
* @param room
|
|
220
|
+
* @param client
|
|
221
|
+
* @param getOldestMembership
|
|
222
|
+
*/
|
|
223
|
+
constructor(joinConfig, room, client, getOldestMembership) {
|
|
224
|
+
this.joinConfig = joinConfig;
|
|
225
|
+
this.room = room;
|
|
226
|
+
this.client = client;
|
|
227
|
+
this.getOldestMembership = getOldestMembership;
|
|
228
|
+
_defineProperty(this, "activated", false);
|
|
229
|
+
_defineProperty(this, "leavePromiseDefer", void 0);
|
|
230
|
+
// MembershipManager mutable state.
|
|
231
|
+
_defineProperty(this, "state", void 0);
|
|
232
|
+
// Membership Event static parameters:
|
|
233
|
+
_defineProperty(this, "deviceId", void 0);
|
|
234
|
+
_defineProperty(this, "stateKey", void 0);
|
|
235
|
+
_defineProperty(this, "fociPreferred", void 0);
|
|
236
|
+
_defineProperty(this, "focusActive", void 0);
|
|
237
|
+
// Config:
|
|
238
|
+
_defineProperty(this, "membershipServerSideExpiryTimeoutOverride", void 0);
|
|
239
|
+
// Scheduler:
|
|
240
|
+
_defineProperty(this, "oldStatus", void 0);
|
|
241
|
+
_defineProperty(this, "scheduler", new ActionScheduler(type => {
|
|
242
|
+
if (this.oldStatus) {
|
|
243
|
+
// we put this at the beginning of the actions scheduler loop handle callback since it is a loop this
|
|
244
|
+
// is equivalent to running it at the end of the loop. (just after applying the status/action list changes)
|
|
245
|
+
logger.debug("MembershipManager applied action changes. Status: ".concat(this.oldStatus, " -> ").concat(this.status));
|
|
246
|
+
}
|
|
247
|
+
this.oldStatus = this.status;
|
|
248
|
+
logger.debug("MembershipManager before processing action. status=".concat(this.oldStatus));
|
|
249
|
+
return this.membershipLoopHandler(type);
|
|
250
|
+
}));
|
|
251
|
+
var [userId, deviceId] = [this.client.getUserId(), this.client.getDeviceId()];
|
|
252
|
+
if (userId === null) throw Error("Missing userId in client");
|
|
253
|
+
if (deviceId === null) throw Error("Missing deviceId in client");
|
|
254
|
+
this.deviceId = deviceId;
|
|
255
|
+
this.stateKey = this.makeMembershipStateKey(userId, deviceId);
|
|
256
|
+
this.state = MembershipManager.defaultState;
|
|
257
|
+
}
|
|
258
|
+
static get defaultState() {
|
|
259
|
+
return {
|
|
260
|
+
hasMemberStateEvent: false,
|
|
261
|
+
delayId: undefined,
|
|
262
|
+
startTime: 0,
|
|
263
|
+
rateLimitRetries: new Map(),
|
|
264
|
+
networkErrorRetries: new Map(),
|
|
265
|
+
expireUpdateIterations: 1
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
get callMemberEventRetryDelayMinimum() {
|
|
269
|
+
var _this$joinConfig$call, _this$joinConfig;
|
|
270
|
+
return (_this$joinConfig$call = (_this$joinConfig = this.joinConfig) === null || _this$joinConfig === void 0 ? void 0 : _this$joinConfig.callMemberEventRetryDelayMinimum) !== null && _this$joinConfig$call !== void 0 ? _this$joinConfig$call : 3000;
|
|
271
|
+
}
|
|
272
|
+
get membershipEventExpiryTimeout() {
|
|
273
|
+
var _this$joinConfig$memb, _this$joinConfig2;
|
|
274
|
+
return (_this$joinConfig$memb = (_this$joinConfig2 = this.joinConfig) === null || _this$joinConfig2 === void 0 ? void 0 : _this$joinConfig2.membershipExpiryTimeout) !== null && _this$joinConfig$memb !== void 0 ? _this$joinConfig$memb : DEFAULT_EXPIRE_DURATION;
|
|
275
|
+
}
|
|
276
|
+
get membershipEventExpiryTimeoutHeadroom() {
|
|
277
|
+
var _this$joinConfig$memb2, _this$joinConfig3;
|
|
278
|
+
return (_this$joinConfig$memb2 = (_this$joinConfig3 = this.joinConfig) === null || _this$joinConfig3 === void 0 ? void 0 : _this$joinConfig3.membershipExpiryTimeoutHeadroom) !== null && _this$joinConfig$memb2 !== void 0 ? _this$joinConfig$memb2 : 5000;
|
|
279
|
+
}
|
|
280
|
+
computeNextExpiryActionTs(iteration) {
|
|
281
|
+
return this.state.startTime + this.membershipEventExpiryTimeout * iteration - this.membershipEventExpiryTimeoutHeadroom;
|
|
282
|
+
}
|
|
283
|
+
get membershipServerSideExpiryTimeout() {
|
|
284
|
+
var _ref, _this$membershipServe, _this$joinConfig4;
|
|
285
|
+
return (_ref = (_this$membershipServe = this.membershipServerSideExpiryTimeoutOverride) !== null && _this$membershipServe !== void 0 ? _this$membershipServe : (_this$joinConfig4 = this.joinConfig) === null || _this$joinConfig4 === void 0 ? void 0 : _this$joinConfig4.membershipServerSideExpiryTimeout) !== null && _ref !== void 0 ? _ref : 8000;
|
|
286
|
+
}
|
|
287
|
+
get membershipKeepAlivePeriod() {
|
|
288
|
+
var _this$joinConfig$memb3, _this$joinConfig5;
|
|
289
|
+
return (_this$joinConfig$memb3 = (_this$joinConfig5 = this.joinConfig) === null || _this$joinConfig5 === void 0 ? void 0 : _this$joinConfig5.membershipKeepAlivePeriod) !== null && _this$joinConfig$memb3 !== void 0 ? _this$joinConfig$memb3 : 5000;
|
|
290
|
+
}
|
|
291
|
+
get maximumRateLimitRetryCount() {
|
|
292
|
+
var _this$joinConfig$maxi, _this$joinConfig6;
|
|
293
|
+
return (_this$joinConfig$maxi = (_this$joinConfig6 = this.joinConfig) === null || _this$joinConfig6 === void 0 ? void 0 : _this$joinConfig6.maximumRateLimitRetryCount) !== null && _this$joinConfig$maxi !== void 0 ? _this$joinConfig$maxi : 10;
|
|
294
|
+
}
|
|
295
|
+
get maximumNetworkErrorRetryCount() {
|
|
296
|
+
var _this$joinConfig$maxi2, _this$joinConfig7;
|
|
297
|
+
return (_this$joinConfig$maxi2 = (_this$joinConfig7 = this.joinConfig) === null || _this$joinConfig7 === void 0 ? void 0 : _this$joinConfig7.maximumNetworkErrorRetryCount) !== null && _this$joinConfig$maxi2 !== void 0 ? _this$joinConfig$maxi2 : 10;
|
|
298
|
+
}
|
|
299
|
+
// LOOP HANDLER:
|
|
300
|
+
membershipLoopHandler(type) {
|
|
301
|
+
var _this2 = this;
|
|
302
|
+
return _asyncToGenerator(function* () {
|
|
303
|
+
_this2.oldStatus = _this2.status;
|
|
304
|
+
switch (type) {
|
|
305
|
+
case MembershipActionType.SendFirstDelayedEvent:
|
|
306
|
+
{
|
|
307
|
+
// Before we start we check if we come from a state where we have a delay id.
|
|
308
|
+
if (!_this2.state.delayId) {
|
|
309
|
+
return _this2.sendFirstDelayedLeaveEvent(); // Normal case without any previous delayed id.
|
|
310
|
+
} else {
|
|
311
|
+
// This can happen if someone else (or another client) removes our own membership event.
|
|
312
|
+
// It will trigger `onRTCSessionMemberUpdate` queue `MembershipActionType.SendFirstDelayedEvent`.
|
|
313
|
+
// We might still have our delayed event from the previous participation and dependent on the server this might not
|
|
314
|
+
// get automatically removed if the state changes. Hence It would remove our membership unexpectedly shortly after the rejoin.
|
|
315
|
+
//
|
|
316
|
+
// In this block we will try to cancel this delayed event before setting up a new one.
|
|
317
|
+
|
|
318
|
+
return _this2.cancelKnownDelayIdBeforeSendFirstDelayedEvent(_this2.state.delayId);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
case MembershipActionType.RestartDelayedEvent:
|
|
322
|
+
{
|
|
323
|
+
if (!_this2.state.delayId) {
|
|
324
|
+
// Delay id got reset. This action was used to check if the hs canceled the delayed event when the join state got sent.
|
|
325
|
+
return createInsertActionUpdate(_this2.state.hasMemberStateEvent ? MembershipActionType.SendMainDelayedEvent : MembershipActionType.SendFirstDelayedEvent);
|
|
326
|
+
}
|
|
327
|
+
return _this2.restartDelayedEvent(_this2.state.delayId);
|
|
328
|
+
}
|
|
329
|
+
case MembershipActionType.SendMainDelayedEvent:
|
|
330
|
+
{
|
|
331
|
+
return _this2.sendMainDelayedEvent();
|
|
332
|
+
}
|
|
333
|
+
case MembershipActionType.SendScheduledDelayedLeaveEvent:
|
|
334
|
+
{
|
|
335
|
+
// We are already good
|
|
336
|
+
if (!_this2.state.hasMemberStateEvent) {
|
|
337
|
+
return {
|
|
338
|
+
replace: []
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (_this2.state.delayId) {
|
|
342
|
+
return _this2.sendScheduledDelayedLeaveEventOrFallbackToSendLeaveEvent(_this2.state.delayId);
|
|
343
|
+
} else {
|
|
344
|
+
return createInsertActionUpdate(MembershipActionType.SendLeaveEvent);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
case MembershipActionType.SendJoinEvent:
|
|
348
|
+
{
|
|
349
|
+
return _this2.sendJoinEvent();
|
|
350
|
+
}
|
|
351
|
+
case MembershipActionType.UpdateExpiry:
|
|
352
|
+
{
|
|
353
|
+
return _this2.updateExpiryOnJoinedEvent();
|
|
354
|
+
}
|
|
355
|
+
case MembershipActionType.SendLeaveEvent:
|
|
356
|
+
{
|
|
357
|
+
// We are good already
|
|
358
|
+
if (!_this2.state.hasMemberStateEvent) {
|
|
359
|
+
return {
|
|
360
|
+
replace: []
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// This is only a fallback in case we do not have working delayed events support.
|
|
364
|
+
// first we should try to just send the scheduled leave event
|
|
365
|
+
return _this2.sendFallbackLeaveEvent();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
})();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// HANDLERS (used in the membershipLoopHandler)
|
|
372
|
+
sendFirstDelayedLeaveEvent() {
|
|
373
|
+
var _this3 = this;
|
|
374
|
+
return _asyncToGenerator(function* () {
|
|
375
|
+
return yield _this3.client._unstable_sendDelayedStateEvent(_this3.room.roomId, {
|
|
376
|
+
delay: _this3.membershipServerSideExpiryTimeout
|
|
377
|
+
}, EventType.GroupCallMemberPrefix, {},
|
|
378
|
+
// leave event
|
|
379
|
+
_this3.stateKey).then(response => {
|
|
380
|
+
// On success we reset retries and set delayId.
|
|
381
|
+
_this3.state.rateLimitRetries.set(MembershipActionType.SendFirstDelayedEvent, 0);
|
|
382
|
+
_this3.state.networkErrorRetries.set(MembershipActionType.SendFirstDelayedEvent, 0);
|
|
383
|
+
_this3.state.delayId = response.delay_id;
|
|
384
|
+
return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
|
|
385
|
+
}).catch(e => {
|
|
386
|
+
var repeatActionType = MembershipActionType.SendFirstDelayedEvent;
|
|
387
|
+
if (_this3.manageMaxDelayExceededSituation(e)) {
|
|
388
|
+
return createInsertActionUpdate(repeatActionType);
|
|
389
|
+
}
|
|
390
|
+
var update = _this3.actionUpdateFromErrors(e, repeatActionType, "sendDelayedStateEvent");
|
|
391
|
+
if (update) return update;
|
|
392
|
+
|
|
393
|
+
// log and fall through
|
|
394
|
+
if (_this3.isUnsupportedDelayedEndpoint(e)) {
|
|
395
|
+
logger.info("Not using delayed event because the endpoint is not supported");
|
|
396
|
+
} else {
|
|
397
|
+
logger.info("Not using delayed event because: " + e);
|
|
398
|
+
}
|
|
399
|
+
// On any other error we fall back to not using delayed events and send the join state event immediately
|
|
400
|
+
return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
|
|
401
|
+
});
|
|
402
|
+
})();
|
|
403
|
+
}
|
|
404
|
+
cancelKnownDelayIdBeforeSendFirstDelayedEvent(delayId) {
|
|
405
|
+
var _this4 = this;
|
|
406
|
+
return _asyncToGenerator(function* () {
|
|
407
|
+
// Remove all running updates and restarts
|
|
408
|
+
return yield _this4.client._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Cancel).then(() => {
|
|
409
|
+
_this4.state.delayId = undefined;
|
|
410
|
+
_this4.resetRateLimitCounter(MembershipActionType.SendFirstDelayedEvent);
|
|
411
|
+
return createReplaceActionUpdate(MembershipActionType.SendFirstDelayedEvent);
|
|
412
|
+
}).catch(e => {
|
|
413
|
+
var repeatActionType = MembershipActionType.SendFirstDelayedEvent;
|
|
414
|
+
var update = _this4.actionUpdateFromErrors(e, repeatActionType, "updateDelayedEvent");
|
|
415
|
+
if (update) return update;
|
|
416
|
+
if (_this4.isNotFoundError(e)) {
|
|
417
|
+
// If we get a M_NOT_FOUND we know that the delayed event got already removed.
|
|
418
|
+
// This means we are good and can set it to undefined and run this again.
|
|
419
|
+
_this4.state.delayId = undefined;
|
|
420
|
+
return createReplaceActionUpdate(repeatActionType);
|
|
421
|
+
}
|
|
422
|
+
if (_this4.isUnsupportedDelayedEndpoint(e)) {
|
|
423
|
+
return createReplaceActionUpdate(MembershipActionType.SendJoinEvent);
|
|
424
|
+
}
|
|
425
|
+
// We do not just ignore and log this error since we would also need to reset the delayId.
|
|
426
|
+
|
|
427
|
+
// This becomes an unrecoverable error case since something is significantly off if we don't hit any of the above cases
|
|
428
|
+
// when state.delayId !== undefined
|
|
429
|
+
// We do not just ignore and log this error since we would also need to reset the delayId.
|
|
430
|
+
// It is cleaner if we, the frontend, rejoins instead of resetting the delayId here and behaving like in the success case.
|
|
431
|
+
throw Error("We failed to cancel a delayed event where we already had a delay id with an error we cannot automatically handle");
|
|
432
|
+
});
|
|
433
|
+
})();
|
|
434
|
+
}
|
|
435
|
+
restartDelayedEvent(delayId) {
|
|
436
|
+
var _this5 = this;
|
|
437
|
+
return _asyncToGenerator(function* () {
|
|
438
|
+
return yield _this5.client._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Restart).then(() => {
|
|
439
|
+
_this5.resetRateLimitCounter(MembershipActionType.RestartDelayedEvent);
|
|
440
|
+
return createInsertActionUpdate(MembershipActionType.RestartDelayedEvent, _this5.membershipKeepAlivePeriod);
|
|
441
|
+
}).catch(e => {
|
|
442
|
+
var repeatActionType = MembershipActionType.RestartDelayedEvent;
|
|
443
|
+
if (_this5.isNotFoundError(e)) {
|
|
444
|
+
_this5.state.delayId = undefined;
|
|
445
|
+
return createInsertActionUpdate(MembershipActionType.SendMainDelayedEvent);
|
|
446
|
+
}
|
|
447
|
+
// If the HS does not support delayed events we wont reschedule.
|
|
448
|
+
if (_this5.isUnsupportedDelayedEndpoint(e)) return {};
|
|
449
|
+
|
|
450
|
+
// TODO this also needs a test: get rate limit while checking id delayed event is scheduled
|
|
451
|
+
var update = _this5.actionUpdateFromErrors(e, repeatActionType, "updateDelayedEvent");
|
|
452
|
+
if (update) return update;
|
|
453
|
+
|
|
454
|
+
// In other error cases we have no idea what is happening
|
|
455
|
+
throw Error("Could not restart delayed event, even though delayed events are supported. " + e);
|
|
456
|
+
});
|
|
457
|
+
})();
|
|
458
|
+
}
|
|
459
|
+
sendMainDelayedEvent() {
|
|
460
|
+
var _this6 = this;
|
|
461
|
+
return _asyncToGenerator(function* () {
|
|
462
|
+
return yield _this6.client._unstable_sendDelayedStateEvent(_this6.room.roomId, {
|
|
463
|
+
delay: _this6.membershipServerSideExpiryTimeout
|
|
464
|
+
}, EventType.GroupCallMemberPrefix, {},
|
|
465
|
+
// leave event
|
|
466
|
+
_this6.stateKey).then(response => {
|
|
467
|
+
_this6.state.delayId = response.delay_id;
|
|
468
|
+
_this6.resetRateLimitCounter(MembershipActionType.SendMainDelayedEvent);
|
|
469
|
+
return createInsertActionUpdate(MembershipActionType.RestartDelayedEvent, _this6.membershipKeepAlivePeriod);
|
|
470
|
+
}).catch(e => {
|
|
471
|
+
var repeatActionType = MembershipActionType.SendMainDelayedEvent;
|
|
472
|
+
// Don't do any other delayed event work if its not supported.
|
|
473
|
+
if (_this6.isUnsupportedDelayedEndpoint(e)) return {};
|
|
474
|
+
if (_this6.manageMaxDelayExceededSituation(e)) {
|
|
475
|
+
return createInsertActionUpdate(repeatActionType);
|
|
476
|
+
}
|
|
477
|
+
var update = _this6.actionUpdateFromErrors(e, repeatActionType, "updateDelayedEvent");
|
|
478
|
+
if (update) return update;
|
|
479
|
+
throw Error("Could not send delayed event, even though delayed events are supported. " + e);
|
|
480
|
+
});
|
|
481
|
+
})();
|
|
482
|
+
}
|
|
483
|
+
sendScheduledDelayedLeaveEventOrFallbackToSendLeaveEvent(delayId) {
|
|
484
|
+
var _this7 = this;
|
|
485
|
+
return _asyncToGenerator(function* () {
|
|
486
|
+
return yield _this7.client._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Send).then(() => {
|
|
487
|
+
_this7.state.hasMemberStateEvent = false;
|
|
488
|
+
_this7.resetRateLimitCounter(MembershipActionType.SendScheduledDelayedLeaveEvent);
|
|
489
|
+
return {
|
|
490
|
+
replace: []
|
|
491
|
+
};
|
|
492
|
+
}).catch(e => {
|
|
493
|
+
var repeatActionType = MembershipActionType.SendLeaveEvent;
|
|
494
|
+
if (_this7.isUnsupportedDelayedEndpoint(e)) return {};
|
|
495
|
+
if (_this7.isNotFoundError(e)) {
|
|
496
|
+
_this7.state.delayId = undefined;
|
|
497
|
+
return createInsertActionUpdate(repeatActionType);
|
|
498
|
+
}
|
|
499
|
+
var update = _this7.actionUpdateFromErrors(e, repeatActionType, "updateDelayedEvent");
|
|
500
|
+
if (update) return update;
|
|
501
|
+
|
|
502
|
+
// On any other error we fall back to SendLeaveEvent (this includes hard errors from rate limiting)
|
|
503
|
+
logger.warn("Encountered unexpected error during SendScheduledDelayedLeaveEvent. Falling back to SendLeaveEvent", e);
|
|
504
|
+
return createInsertActionUpdate(repeatActionType);
|
|
505
|
+
});
|
|
506
|
+
})();
|
|
507
|
+
}
|
|
508
|
+
sendJoinEvent() {
|
|
509
|
+
var _this8 = this;
|
|
510
|
+
return _asyncToGenerator(function* () {
|
|
511
|
+
return yield _this8.client.sendStateEvent(_this8.room.roomId, EventType.GroupCallMemberPrefix, _this8.makeMyMembership(_this8.membershipEventExpiryTimeout), _this8.stateKey).then(() => {
|
|
512
|
+
_this8.state.startTime = Date.now();
|
|
513
|
+
// The next update should already use twice the membershipEventExpiryTimeout
|
|
514
|
+
_this8.state.expireUpdateIterations = 1;
|
|
515
|
+
_this8.state.hasMemberStateEvent = true;
|
|
516
|
+
_this8.resetRateLimitCounter(MembershipActionType.SendJoinEvent);
|
|
517
|
+
return {
|
|
518
|
+
insert: [{
|
|
519
|
+
ts: Date.now(),
|
|
520
|
+
type: MembershipActionType.RestartDelayedEvent
|
|
521
|
+
}, {
|
|
522
|
+
ts: _this8.computeNextExpiryActionTs(_this8.state.expireUpdateIterations),
|
|
523
|
+
type: MembershipActionType.UpdateExpiry
|
|
524
|
+
}]
|
|
525
|
+
};
|
|
526
|
+
}).catch(e => {
|
|
527
|
+
var update = _this8.actionUpdateFromErrors(e, MembershipActionType.SendJoinEvent, "sendStateEvent");
|
|
528
|
+
if (update) return update;
|
|
529
|
+
throw e;
|
|
530
|
+
});
|
|
531
|
+
})();
|
|
532
|
+
}
|
|
533
|
+
updateExpiryOnJoinedEvent() {
|
|
534
|
+
var _this9 = this;
|
|
535
|
+
return _asyncToGenerator(function* () {
|
|
536
|
+
var nextExpireUpdateIteration = _this9.state.expireUpdateIterations + 1;
|
|
537
|
+
return yield _this9.client.sendStateEvent(_this9.room.roomId, EventType.GroupCallMemberPrefix, _this9.makeMyMembership(_this9.membershipEventExpiryTimeout * nextExpireUpdateIteration), _this9.stateKey).then(() => {
|
|
538
|
+
// Success, we reset retries and schedule update.
|
|
539
|
+
_this9.resetRateLimitCounter(MembershipActionType.UpdateExpiry);
|
|
540
|
+
_this9.state.expireUpdateIterations = nextExpireUpdateIteration;
|
|
541
|
+
return {
|
|
542
|
+
insert: [{
|
|
543
|
+
ts: _this9.computeNextExpiryActionTs(nextExpireUpdateIteration),
|
|
544
|
+
type: MembershipActionType.UpdateExpiry
|
|
545
|
+
}]
|
|
546
|
+
};
|
|
547
|
+
}).catch(e => {
|
|
548
|
+
var update = _this9.actionUpdateFromErrors(e, MembershipActionType.UpdateExpiry, "sendStateEvent");
|
|
549
|
+
if (update) return update;
|
|
550
|
+
throw e;
|
|
551
|
+
});
|
|
552
|
+
})();
|
|
553
|
+
}
|
|
554
|
+
sendFallbackLeaveEvent() {
|
|
555
|
+
var _this10 = this;
|
|
556
|
+
return _asyncToGenerator(function* () {
|
|
557
|
+
return yield _this10.client.sendStateEvent(_this10.room.roomId, EventType.GroupCallMemberPrefix, {}, _this10.stateKey).then(() => {
|
|
558
|
+
_this10.resetRateLimitCounter(MembershipActionType.SendLeaveEvent);
|
|
559
|
+
_this10.state.hasMemberStateEvent = false;
|
|
560
|
+
return {
|
|
561
|
+
replace: []
|
|
562
|
+
};
|
|
563
|
+
}).catch(e => {
|
|
564
|
+
var update = _this10.actionUpdateFromErrors(e, MembershipActionType.SendLeaveEvent, "sendStateEvent");
|
|
565
|
+
if (update) return update;
|
|
566
|
+
throw e;
|
|
567
|
+
});
|
|
568
|
+
})();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// HELPERS
|
|
572
|
+
makeMembershipStateKey(localUserId, localDeviceId) {
|
|
573
|
+
var stateKey = "".concat(localUserId, "_").concat(localDeviceId);
|
|
574
|
+
if (/^org\.matrix\.msc(3757|3779)\b/.exec(this.room.getVersion())) {
|
|
575
|
+
return stateKey;
|
|
576
|
+
} else {
|
|
577
|
+
return "_".concat(stateKey);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Constructs our own membership
|
|
583
|
+
*/
|
|
584
|
+
makeMyMembership(expires) {
|
|
585
|
+
var _this$fociPreferred;
|
|
586
|
+
return {
|
|
587
|
+
call_id: "",
|
|
588
|
+
scope: "m.room",
|
|
589
|
+
application: "m.call",
|
|
590
|
+
device_id: this.deviceId,
|
|
591
|
+
expires,
|
|
592
|
+
focus_active: {
|
|
593
|
+
type: "livekit",
|
|
594
|
+
focus_selection: "oldest_membership"
|
|
595
|
+
},
|
|
596
|
+
foci_preferred: (_this$fociPreferred = this.fociPreferred) !== null && _this$fociPreferred !== void 0 ? _this$fociPreferred : []
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Error checks and handlers
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Check if its a NOT_FOUND error
|
|
604
|
+
* @param error the error causing this handler check/execution
|
|
605
|
+
* @returns true if its a not found error
|
|
606
|
+
*/
|
|
607
|
+
isNotFoundError(error) {
|
|
608
|
+
return error instanceof MatrixError && error.errcode === "M_NOT_FOUND";
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Check if this is a DelayExceeded timeout and update the TimeoutOverride for the next try
|
|
613
|
+
* @param error the error causing this handler check/execution
|
|
614
|
+
* @returns true if its a delay exceeded error and we updated the local TimeoutOverride
|
|
615
|
+
*/
|
|
616
|
+
manageMaxDelayExceededSituation(error) {
|
|
617
|
+
if (error instanceof MatrixError && error.errcode === "M_UNKNOWN" && error.data["org.matrix.msc4140.errcode"] === "M_MAX_DELAY_EXCEEDED") {
|
|
618
|
+
var maxDelayAllowed = error.data["org.matrix.msc4140.max_delay"];
|
|
619
|
+
if (typeof maxDelayAllowed === "number" && this.membershipServerSideExpiryTimeout > maxDelayAllowed) {
|
|
620
|
+
this.membershipServerSideExpiryTimeoutOverride = maxDelayAllowed;
|
|
621
|
+
}
|
|
622
|
+
logger.warn("Retry sending delayed disconnection event due to server timeout limitations:", error);
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
actionUpdateFromErrors(error, type, method) {
|
|
628
|
+
var updateLimit = this.actionUpdateFromRateLimitError(error, method, type);
|
|
629
|
+
if (updateLimit) return updateLimit;
|
|
630
|
+
var updateNetwork = this.actionUpdateFromNetworkErrorRetry(error, type);
|
|
631
|
+
if (updateNetwork) return updateNetwork;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Check if we have a rate limit error and schedule the same action again if we dont exceed the rate limit retry count yet.
|
|
635
|
+
* @param error the error causing this handler check/execution
|
|
636
|
+
* @param method the method used for the throw message
|
|
637
|
+
* @param type which MembershipActionType we reschedule because of a rate limit.
|
|
638
|
+
* @throws If it is a rate limit error and the retry count got exceeded
|
|
639
|
+
* @returns Returns true if we handled the error by rescheduling the correct next action.
|
|
640
|
+
* Returns false if it is not a network error.
|
|
641
|
+
*/
|
|
642
|
+
actionUpdateFromRateLimitError(error, method, type) {
|
|
643
|
+
var _this$state$rateLimit;
|
|
644
|
+
// "Is rate limit"-boundary
|
|
645
|
+
if (!((error instanceof HTTPError || error instanceof MatrixError) && error.isRateLimitError())) {
|
|
646
|
+
return undefined;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// retry boundary
|
|
650
|
+
var rateLimitRetries = (_this$state$rateLimit = this.state.rateLimitRetries.get(type)) !== null && _this$state$rateLimit !== void 0 ? _this$state$rateLimit : 0;
|
|
651
|
+
if (rateLimitRetries < this.maximumRateLimitRetryCount) {
|
|
652
|
+
var resendDelay;
|
|
653
|
+
var defaultMs = 5000;
|
|
654
|
+
try {
|
|
655
|
+
var _error$getRetryAfterM;
|
|
656
|
+
resendDelay = (_error$getRetryAfterM = error.getRetryAfterMs()) !== null && _error$getRetryAfterM !== void 0 ? _error$getRetryAfterM : defaultMs;
|
|
657
|
+
logger.info("Rate limited by server, retrying in ".concat(resendDelay, "ms"));
|
|
658
|
+
} catch (e) {
|
|
659
|
+
logger.warn("Error while retrieving a rate-limit retry delay, retrying after default delay of ".concat(defaultMs), e);
|
|
660
|
+
resendDelay = defaultMs;
|
|
661
|
+
}
|
|
662
|
+
this.state.rateLimitRetries.set(type, rateLimitRetries + 1);
|
|
663
|
+
return createInsertActionUpdate(type, resendDelay);
|
|
664
|
+
}
|
|
665
|
+
throw Error("Exceeded maximum retries for " + type + " attempts (client." + method + "): " + error);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* FIXME Don't Check the error and retry the same MembershipAction again in the configured time and for the configured retry count.
|
|
670
|
+
* @param error the error causing this handler check/execution
|
|
671
|
+
* @param type the action type that we need to repeat because of the error
|
|
672
|
+
* @throws If it is a network error and the retry count got exceeded
|
|
673
|
+
* @returns
|
|
674
|
+
* Returns true if we handled the error by rescheduling the correct next action.
|
|
675
|
+
* Returns false if it is not a network error.
|
|
676
|
+
*/
|
|
677
|
+
actionUpdateFromNetworkErrorRetry(error, type) {
|
|
678
|
+
var _this$state$networkEr;
|
|
679
|
+
// "Is a network error"-boundary
|
|
680
|
+
var retries = (_this$state$networkEr = this.state.networkErrorRetries.get(type)) !== null && _this$state$networkEr !== void 0 ? _this$state$networkEr : 0;
|
|
681
|
+
var retryDurationString = this.callMemberEventRetryDelayMinimum / 1000 + "s";
|
|
682
|
+
var retryCounterString = "(" + retries + "/" + this.maximumNetworkErrorRetryCount + ")";
|
|
683
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
684
|
+
logger.warn("Network local timeout error while sending event, retrying in " + retryDurationString + " " + retryCounterString, error);
|
|
685
|
+
} else if (error instanceof Error && error.message.includes("updating delayed event")) {
|
|
686
|
+
// TODO: We do not want error message matching here but instead the error should be a typed HTTPError
|
|
687
|
+
// and be handled below automatically (the same as in the SPA case).
|
|
688
|
+
//
|
|
689
|
+
// The error originates because of https://github.com/matrix-org/matrix-widget-api/blob/5d81d4a26ff69e4bd3ddc79a884c9527999fb2f4/src/ClientWidgetApi.ts#L698-L701
|
|
690
|
+
// uses `e` instance of HttpError (and not MatrixError)
|
|
691
|
+
// The element web widget driver (only checks for MatrixError) is then failing to process (`processError`) it as a typed error: https://github.com/element-hq/element-web/blob/471712cbf06a067e5499bd5d2d7a75f693d9a12d/src/stores/widgets/StopGapWidgetDriver.ts#L711-L715
|
|
692
|
+
// So it will not call: `error.asWidgetApiErrorData()` which is also missing for `HttpError`
|
|
693
|
+
//
|
|
694
|
+
// A proper fix would be to either find a place to convert the `HttpError` into a `MatrixError` and the `processError`
|
|
695
|
+
// method to handle it as expected or to adjust `processError` to also process `HttpError`'s.
|
|
696
|
+
logger.warn("delayed event update timeout error, retrying in " + retryDurationString + " " + retryCounterString, error);
|
|
697
|
+
} else if (error instanceof ConnectionError) {
|
|
698
|
+
logger.warn("Network connection error while sending event, retrying in " + retryDurationString + " " + retryCounterString, error);
|
|
699
|
+
} else if ((error instanceof HTTPError || error instanceof MatrixError) && typeof error.httpStatus === "number" && error.httpStatus >= 500 && error.httpStatus < 600) {
|
|
700
|
+
logger.warn("Server error while sending event, retrying in " + retryDurationString + " " + retryCounterString, error);
|
|
701
|
+
} else {
|
|
702
|
+
return undefined;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// retry boundary
|
|
706
|
+
if (retries < this.maximumNetworkErrorRetryCount) {
|
|
707
|
+
this.state.networkErrorRetries.set(type, retries + 1);
|
|
708
|
+
return createInsertActionUpdate(type, this.callMemberEventRetryDelayMinimum);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Failure
|
|
712
|
+
throw Error("Reached maximum (" + this.maximumNetworkErrorRetryCount + ") retries cause by: " + error);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Check if its an UnsupportedDelayedEventsEndpointError and which implies that we cannot do any delayed event logic
|
|
717
|
+
* @param error The error to check
|
|
718
|
+
* @returns true it its an UnsupportedDelayedEventsEndpointError
|
|
719
|
+
*/
|
|
720
|
+
isUnsupportedDelayedEndpoint(error) {
|
|
721
|
+
return error instanceof UnsupportedDelayedEventsEndpointError;
|
|
722
|
+
}
|
|
723
|
+
resetRateLimitCounter(type) {
|
|
724
|
+
this.state.rateLimitRetries.set(type, 0);
|
|
725
|
+
this.state.networkErrorRetries.set(type, 0);
|
|
726
|
+
}
|
|
727
|
+
get status() {
|
|
728
|
+
var actions = this.scheduler.actions;
|
|
729
|
+
if (actions.length === 1) {
|
|
730
|
+
var {
|
|
731
|
+
type
|
|
732
|
+
} = actions[0];
|
|
733
|
+
switch (type) {
|
|
734
|
+
case MembershipActionType.SendFirstDelayedEvent:
|
|
735
|
+
case MembershipActionType.SendJoinEvent:
|
|
736
|
+
case MembershipActionType.SendMainDelayedEvent:
|
|
737
|
+
return Status.Connecting;
|
|
738
|
+
case MembershipActionType.UpdateExpiry:
|
|
739
|
+
// where no delayed events
|
|
740
|
+
return Status.Connected;
|
|
741
|
+
case MembershipActionType.SendScheduledDelayedLeaveEvent:
|
|
742
|
+
case MembershipActionType.SendLeaveEvent:
|
|
743
|
+
return Status.Disconnecting;
|
|
744
|
+
default:
|
|
745
|
+
// pass through as not expected
|
|
746
|
+
}
|
|
747
|
+
} else if (actions.length === 2) {
|
|
748
|
+
var types = actions.map(a => a.type);
|
|
749
|
+
// normal state for connected with delayed events
|
|
750
|
+
if ((types.includes(MembershipActionType.RestartDelayedEvent) || types.includes(MembershipActionType.SendMainDelayedEvent)) && types.includes(MembershipActionType.UpdateExpiry)) {
|
|
751
|
+
return Status.Connected;
|
|
752
|
+
}
|
|
753
|
+
} else if (actions.length === 3) {
|
|
754
|
+
var _types = actions.map(a => a.type);
|
|
755
|
+
// It is a correct connected state if we already schedule the next Restart but have not yet cleaned up
|
|
756
|
+
// the current restart.
|
|
757
|
+
if (_types.filter(t => t === MembershipActionType.RestartDelayedEvent).length === 2 && _types.includes(MembershipActionType.UpdateExpiry)) {
|
|
758
|
+
return Status.Connected;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (!this.scheduler.running) {
|
|
762
|
+
return Status.Disconnected;
|
|
763
|
+
}
|
|
764
|
+
logger.error("MembershipManager has an unknown state. Actions: ", actions);
|
|
765
|
+
return Status.Unknown;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function createInsertActionUpdate(type, offset) {
|
|
769
|
+
return {
|
|
770
|
+
insert: [{
|
|
771
|
+
ts: Date.now() + (offset !== null && offset !== void 0 ? offset : 0),
|
|
772
|
+
type
|
|
773
|
+
}]
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function createReplaceActionUpdate(type, offset) {
|
|
777
|
+
return {
|
|
778
|
+
replace: [{
|
|
779
|
+
ts: Date.now() + (offset !== null && offset !== void 0 ? offset : 0),
|
|
780
|
+
type
|
|
781
|
+
}]
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
//# sourceMappingURL=NewMembershipManager.js.map
|