matrix-js-sdk 37.2.0 → 37.3.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 +8 -2
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +11 -3
- package/lib/client.js.map +1 -1
- package/lib/common-crypto/CryptoBackend.d.ts +8 -0
- package/lib/common-crypto/CryptoBackend.d.ts.map +1 -1
- package/lib/common-crypto/CryptoBackend.js.map +1 -1
- package/lib/http-api/errors.d.ts +8 -0
- package/lib/http-api/errors.d.ts.map +1 -1
- package/lib/http-api/errors.js +14 -0
- package/lib/http-api/errors.js.map +1 -1
- package/lib/http-api/fetch.d.ts.map +1 -1
- package/lib/http-api/fetch.js +3 -3
- package/lib/http-api/fetch.js.map +1 -1
- package/lib/matrixrtc/LegacyMembershipManager.d.ts +9 -1
- package/lib/matrixrtc/LegacyMembershipManager.d.ts.map +1 -1
- package/lib/matrixrtc/LegacyMembershipManager.js +20 -0
- package/lib/matrixrtc/LegacyMembershipManager.js.map +1 -1
- package/lib/matrixrtc/MatrixRTCSession.d.ts.map +1 -1
- package/lib/matrixrtc/MatrixRTCSession.js.map +1 -1
- package/lib/matrixrtc/NewMembershipManager.d.ts +7 -58
- package/lib/matrixrtc/NewMembershipManager.d.ts.map +1 -1
- package/lib/matrixrtc/NewMembershipManager.js +130 -137
- package/lib/matrixrtc/NewMembershipManager.js.map +1 -1
- package/lib/matrixrtc/NewMembershipManagerActionScheduler.js +3 -3
- package/lib/matrixrtc/NewMembershipManagerActionScheduler.js.map +1 -1
- package/lib/matrixrtc/index.d.ts +1 -0
- package/lib/matrixrtc/index.d.ts.map +1 -1
- package/lib/matrixrtc/index.js +1 -0
- package/lib/matrixrtc/index.js.map +1 -1
- package/lib/matrixrtc/types.d.ts +77 -1
- package/lib/matrixrtc/types.d.ts.map +1 -1
- package/lib/matrixrtc/types.js +40 -1
- package/lib/matrixrtc/types.js.map +1 -1
- package/lib/models/room-summary.d.ts +30 -0
- package/lib/models/room-summary.d.ts.map +1 -1
- package/lib/models/room-summary.js +11 -0
- package/lib/models/room-summary.js.map +1 -1
- package/lib/models/room.d.ts +37 -1
- package/lib/models/room.d.ts.map +1 -1
- package/lib/models/room.js +133 -28
- package/lib/models/room.js.map +1 -1
- package/lib/oidc/tokenRefresher.d.ts.map +1 -1
- package/lib/oidc/tokenRefresher.js +8 -1
- package/lib/oidc/tokenRefresher.js.map +1 -1
- package/lib/pushprocessor.d.ts +10 -1
- package/lib/pushprocessor.d.ts.map +1 -1
- package/lib/pushprocessor.js +24 -9
- package/lib/pushprocessor.js.map +1 -1
- package/lib/rust-crypto/OutgoingRequestProcessor.d.ts.map +1 -1
- package/lib/rust-crypto/OutgoingRequestProcessor.js +6 -1
- package/lib/rust-crypto/OutgoingRequestProcessor.js.map +1 -1
- package/lib/rust-crypto/rust-crypto.d.ts +4 -1
- package/lib/rust-crypto/rust-crypto.d.ts.map +1 -1
- package/lib/rust-crypto/rust-crypto.js +55 -46
- package/lib/rust-crypto/rust-crypto.js.map +1 -1
- package/lib/sliding-sync-sdk.d.ts.map +1 -1
- package/lib/sliding-sync-sdk.js +86 -75
- package/lib/sliding-sync-sdk.js.map +1 -1
- package/lib/sliding-sync.d.ts +23 -60
- package/lib/sliding-sync.d.ts.map +1 -1
- package/lib/sliding-sync.js +62 -321
- package/lib/sliding-sync.js.map +1 -1
- package/package.json +6 -6
- package/src/client.ts +12 -5
- package/src/common-crypto/CryptoBackend.ts +9 -0
- package/src/http-api/errors.ts +14 -0
- package/src/http-api/fetch.ts +3 -4
- package/src/matrixrtc/LegacyMembershipManager.ts +27 -1
- package/src/matrixrtc/MatrixRTCSession.ts +2 -1
- package/src/matrixrtc/NewMembershipManager.ts +120 -165
- package/src/matrixrtc/NewMembershipManagerActionScheduler.ts +3 -3
- package/src/matrixrtc/index.ts +1 -0
- package/src/matrixrtc/types.ts +84 -1
- package/src/models/room-summary.ts +31 -0
- package/src/models/room.ts +133 -23
- package/src/oidc/tokenRefresher.ts +8 -2
- package/src/pushprocessor.ts +25 -16
- package/src/rust-crypto/OutgoingRequestProcessor.ts +6 -0
- package/src/rust-crypto/rust-crypto.ts +7 -1
- package/src/sliding-sync-sdk.ts +28 -28
- package/src/sliding-sync.ts +38 -329
package/src/sliding-sync.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright 2022 The Matrix.org Foundation C.I.C.
|
|
2
|
+
Copyright 2022-2024 The Matrix.org Foundation C.I.C.
|
|
3
3
|
|
|
4
4
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
you may not use this file except in compliance with the License.
|
|
@@ -18,7 +18,7 @@ import { logger } from "./logger.ts";
|
|
|
18
18
|
import { type MatrixClient } from "./client.ts";
|
|
19
19
|
import { type IRoomEvent, type IStateEvent } from "./sync-accumulator.ts";
|
|
20
20
|
import { TypedEventEmitter } from "./models/typed-event-emitter.ts";
|
|
21
|
-
import { sleep
|
|
21
|
+
import { sleep } from "./utils.ts";
|
|
22
22
|
import { type HTTPError } from "./http-api/index.ts";
|
|
23
23
|
|
|
24
24
|
// /sync requests allow you to set a timeout= but the request may continue
|
|
@@ -82,10 +82,23 @@ export interface MSC3575SlidingSyncRequest {
|
|
|
82
82
|
clientTimeout?: number;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* New format of hero introduced in MSC4186 with display name and avatar URL
|
|
87
|
+
* in addition to just user_id (as it is on the wire, with underscores)
|
|
88
|
+
* as opposed to Hero in room-summary.ts which has fields in camelCase
|
|
89
|
+
* (and also a flag to note what format the hero came from).
|
|
90
|
+
*/
|
|
91
|
+
export interface MSC4186Hero {
|
|
92
|
+
user_id: string;
|
|
93
|
+
displayname?: string;
|
|
94
|
+
avatar_url?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
export interface MSC3575RoomData {
|
|
86
98
|
name: string;
|
|
87
99
|
required_state: IStateEvent[];
|
|
88
100
|
timeline: (IRoomEvent | IStateEvent)[];
|
|
101
|
+
heroes?: MSC4186Hero[];
|
|
89
102
|
notification_count?: number;
|
|
90
103
|
highlight_count?: number;
|
|
91
104
|
joined_count?: number;
|
|
@@ -96,41 +109,13 @@ export interface MSC3575RoomData {
|
|
|
96
109
|
is_dm?: boolean;
|
|
97
110
|
prev_batch?: string;
|
|
98
111
|
num_live?: number;
|
|
112
|
+
bump_stamp?: number;
|
|
99
113
|
}
|
|
100
114
|
|
|
101
115
|
interface ListResponse {
|
|
102
116
|
count: number;
|
|
103
|
-
ops: Operation[];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
interface BaseOperation {
|
|
107
|
-
op: string;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
interface DeleteOperation extends BaseOperation {
|
|
111
|
-
op: "DELETE";
|
|
112
|
-
index: number;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
interface InsertOperation extends BaseOperation {
|
|
116
|
-
op: "INSERT";
|
|
117
|
-
index: number;
|
|
118
|
-
room_id: string;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
interface InvalidateOperation extends BaseOperation {
|
|
122
|
-
op: "INVALIDATE";
|
|
123
|
-
range: [number, number];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
interface SyncOperation extends BaseOperation {
|
|
127
|
-
op: "SYNC";
|
|
128
|
-
range: [number, number];
|
|
129
|
-
room_ids: string[];
|
|
130
117
|
}
|
|
131
118
|
|
|
132
|
-
type Operation = DeleteOperation | InsertOperation | InvalidateOperation | SyncOperation;
|
|
133
|
-
|
|
134
119
|
/**
|
|
135
120
|
* A complete Sliding Sync response
|
|
136
121
|
*/
|
|
@@ -163,7 +148,6 @@ class SlidingList {
|
|
|
163
148
|
private isModified?: boolean;
|
|
164
149
|
|
|
165
150
|
// returned data
|
|
166
|
-
public roomIndexToRoomId: Record<number, string> = {};
|
|
167
151
|
public joinedCount = 0;
|
|
168
152
|
|
|
169
153
|
/**
|
|
@@ -204,9 +188,6 @@ class SlidingList {
|
|
|
204
188
|
// reset values as the join count may be very different (if filters changed) including the rooms
|
|
205
189
|
// (e.g. sort orders or sliding window ranges changed)
|
|
206
190
|
|
|
207
|
-
// the constantly changing sliding window ranges. Not an array for performance reasons
|
|
208
|
-
// E.g. tracking ranges 0-99, 500-599, we don't want to have a 600 element array
|
|
209
|
-
this.roomIndexToRoomId = {};
|
|
210
191
|
// the total number of joined rooms according to the server, always >= len(roomIndexToRoomId)
|
|
211
192
|
this.joinedCount = 0;
|
|
212
193
|
}
|
|
@@ -226,26 +207,6 @@ class SlidingList {
|
|
|
226
207
|
}
|
|
227
208
|
return list;
|
|
228
209
|
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Check if a given index is within the list range. This is required even though the /sync API
|
|
232
|
-
* provides explicit updates with index positions because of the following situation:
|
|
233
|
-
* 0 1 2 3 4 5 6 7 8 indexes
|
|
234
|
-
* a b c d e f COMMANDS: SYNC 0 2 a b c; SYNC 6 8 d e f;
|
|
235
|
-
* a b c d _ f COMMAND: DELETE 7;
|
|
236
|
-
* e a b c d f COMMAND: INSERT 0 e;
|
|
237
|
-
* c=3 is wrong as we are not tracking it, ergo we need to see if `i` is in range else drop it
|
|
238
|
-
* @param i - The index to check
|
|
239
|
-
* @returns True if the index is within a sliding window
|
|
240
|
-
*/
|
|
241
|
-
public isIndexInRange(i: number): boolean {
|
|
242
|
-
for (const r of this.list.ranges) {
|
|
243
|
-
if (r[0] <= i && i <= r[1]) {
|
|
244
|
-
return true;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
210
|
}
|
|
250
211
|
|
|
251
212
|
/**
|
|
@@ -274,10 +235,10 @@ export interface Extension<Req extends object, Res extends object> {
|
|
|
274
235
|
/**
|
|
275
236
|
* A function which is called when the request JSON is being formed.
|
|
276
237
|
* Returns the data to insert under this key.
|
|
277
|
-
* @param isInitial - True when this is part of the initial request
|
|
238
|
+
* @param isInitial - True when this is part of the initial request.
|
|
278
239
|
* @returns The request JSON to send.
|
|
279
240
|
*/
|
|
280
|
-
onRequest(isInitial: boolean): Req
|
|
241
|
+
onRequest(isInitial: boolean): Promise<Req>;
|
|
281
242
|
/**
|
|
282
243
|
* A function which is called when there is response JSON under this extension.
|
|
283
244
|
* @param data - The response JSON under the extension name.
|
|
@@ -295,12 +256,10 @@ export interface Extension<Req extends object, Res extends object> {
|
|
|
295
256
|
* of information when processing sync responses.
|
|
296
257
|
* - RoomData: concerns rooms, useful for SlidingSyncSdk to update its knowledge of rooms.
|
|
297
258
|
* - Lifecycle: concerns callbacks at various well-defined points in the sync process.
|
|
298
|
-
* - List: concerns lists, useful for UI layers to re-render room lists.
|
|
299
259
|
* Specifically, the order of event invocation is:
|
|
300
260
|
* - Lifecycle (state=RequestFinished)
|
|
301
261
|
* - RoomData (N times)
|
|
302
262
|
* - Lifecycle (state=Complete)
|
|
303
|
-
* - List (at most once per list)
|
|
304
263
|
*/
|
|
305
264
|
export enum SlidingSyncEvent {
|
|
306
265
|
/**
|
|
@@ -313,16 +272,9 @@ export enum SlidingSyncEvent {
|
|
|
313
272
|
* - SlidingSyncState.RequestFinished: Fires after we receive a valid response but before the
|
|
314
273
|
* response has been processed. Perform any pre-process steps here. If there was a problem syncing,
|
|
315
274
|
* `err` will be set (e.g network errors).
|
|
316
|
-
* - SlidingSyncState.Complete: Fires after
|
|
317
|
-
* SlidingSyncEvent.List.
|
|
275
|
+
* - SlidingSyncState.Complete: Fires after the response has been processed.
|
|
318
276
|
*/
|
|
319
277
|
Lifecycle = "SlidingSync.Lifecycle",
|
|
320
|
-
/**
|
|
321
|
-
* This event fires whenever there has been a change to this list index. It fires exactly once
|
|
322
|
-
* per list, even if there were multiple operations for the list.
|
|
323
|
-
* It fires AFTER Lifecycle and RoomData events.
|
|
324
|
-
*/
|
|
325
|
-
List = "SlidingSync.List",
|
|
326
278
|
}
|
|
327
279
|
|
|
328
280
|
export type SlidingSyncEventHandlerMap = {
|
|
@@ -332,7 +284,6 @@ export type SlidingSyncEventHandlerMap = {
|
|
|
332
284
|
resp: MSC3575SlidingSyncResponse | null,
|
|
333
285
|
err?: Error,
|
|
334
286
|
) => void;
|
|
335
|
-
[SlidingSyncEvent.List]: (listKey: string, joinedCount: number, roomIndexToRoomId: Record<number, string>) => void;
|
|
336
287
|
};
|
|
337
288
|
|
|
338
289
|
/**
|
|
@@ -347,11 +298,6 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
347
298
|
private terminated = false;
|
|
348
299
|
// flag set when resend() is called because we cannot rely on detecting AbortError in JS SDK :(
|
|
349
300
|
private needsResend = false;
|
|
350
|
-
// the txn_id to send with the next request.
|
|
351
|
-
private txnId: string | null = null;
|
|
352
|
-
// a list (in chronological order of when they were sent) of objects containing the txn ID and
|
|
353
|
-
// a defer to resolve/reject depending on whether they were successfully sent or not.
|
|
354
|
-
private txnIdDefers: (IDeferred<string> & { txnId: string })[] = [];
|
|
355
301
|
// map of extension name to req/resp handler
|
|
356
302
|
private extensions: Record<string, Extension<any, any>> = {};
|
|
357
303
|
|
|
@@ -426,14 +372,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
426
372
|
* @param key - The list key
|
|
427
373
|
* @returns The list data which contains the rooms in this list
|
|
428
374
|
*/
|
|
429
|
-
public getListData(key: string): { joinedCount: number
|
|
375
|
+
public getListData(key: string): { joinedCount: number } | null {
|
|
430
376
|
const data = this.lists.get(key);
|
|
431
377
|
if (!data) {
|
|
432
378
|
return null;
|
|
433
379
|
}
|
|
434
380
|
return {
|
|
435
381
|
joinedCount: data.joinedCount,
|
|
436
|
-
roomIndexToRoomId: Object.assign({}, data.roomIndexToRoomId),
|
|
437
382
|
};
|
|
438
383
|
}
|
|
439
384
|
|
|
@@ -461,13 +406,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
461
406
|
* (or rejects with the transaction ID if the action was not applied e.g the request was cancelled
|
|
462
407
|
* immediately after sending, in which case the action will be applied in the subsequent request)
|
|
463
408
|
*/
|
|
464
|
-
public setListRanges(key: string, ranges: number[][]):
|
|
409
|
+
public setListRanges(key: string, ranges: number[][]): void {
|
|
465
410
|
const list = this.lists.get(key);
|
|
466
411
|
if (!list) {
|
|
467
|
-
|
|
412
|
+
throw new Error("no list with key " + key);
|
|
468
413
|
}
|
|
469
414
|
list.updateListRange(ranges);
|
|
470
|
-
|
|
415
|
+
this.resend();
|
|
471
416
|
}
|
|
472
417
|
|
|
473
418
|
/**
|
|
@@ -479,7 +424,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
479
424
|
* (or rejects with the transaction ID if the action was not applied e.g the request was cancelled
|
|
480
425
|
* immediately after sending, in which case the action will be applied in the subsequent request)
|
|
481
426
|
*/
|
|
482
|
-
public setList(key: string, list: MSC3575List):
|
|
427
|
+
public setList(key: string, list: MSC3575List): void {
|
|
483
428
|
const existingList = this.lists.get(key);
|
|
484
429
|
if (existingList) {
|
|
485
430
|
existingList.replaceList(list);
|
|
@@ -488,7 +433,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
488
433
|
this.lists.set(key, new SlidingList(list));
|
|
489
434
|
}
|
|
490
435
|
this.listModifiedCount += 1;
|
|
491
|
-
|
|
436
|
+
this.resend();
|
|
492
437
|
}
|
|
493
438
|
|
|
494
439
|
/**
|
|
@@ -504,27 +449,21 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
504
449
|
* /sync request to resend new subscriptions. If the /sync stream has not started, this will
|
|
505
450
|
* prepare the room subscriptions for when start() is called.
|
|
506
451
|
* @param s - The new desired room subscriptions.
|
|
507
|
-
* @returns A promise which resolves to the transaction ID when it has been received down sync
|
|
508
|
-
* (or rejects with the transaction ID if the action was not applied e.g the request was cancelled
|
|
509
|
-
* immediately after sending, in which case the action will be applied in the subsequent request)
|
|
510
452
|
*/
|
|
511
|
-
public modifyRoomSubscriptions(s: Set<string>):
|
|
453
|
+
public modifyRoomSubscriptions(s: Set<string>): void {
|
|
512
454
|
this.desiredRoomSubscriptions = s;
|
|
513
|
-
|
|
455
|
+
this.resend();
|
|
514
456
|
}
|
|
515
457
|
|
|
516
458
|
/**
|
|
517
459
|
* Modify which events to retrieve for room subscriptions. Invalidates all room subscriptions
|
|
518
460
|
* such that they will be sent up afresh.
|
|
519
461
|
* @param rs - The new room subscription fields to fetch.
|
|
520
|
-
* @returns A promise which resolves to the transaction ID when it has been received down sync
|
|
521
|
-
* (or rejects with the transaction ID if the action was not applied e.g the request was cancelled
|
|
522
|
-
* immediately after sending, in which case the action will be applied in the subsequent request)
|
|
523
462
|
*/
|
|
524
|
-
public modifyRoomSubscriptionInfo(rs: MSC3575RoomSubscription):
|
|
463
|
+
public modifyRoomSubscriptionInfo(rs: MSC3575RoomSubscription): void {
|
|
525
464
|
this.roomSubscriptionInfo = rs;
|
|
526
465
|
this.confirmedRoomSubscriptions = new Set<string>();
|
|
527
|
-
|
|
466
|
+
this.resend();
|
|
528
467
|
}
|
|
529
468
|
|
|
530
469
|
/**
|
|
@@ -538,11 +477,11 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
538
477
|
this.extensions[ext.name()] = ext;
|
|
539
478
|
}
|
|
540
479
|
|
|
541
|
-
private getExtensionRequest(isInitial: boolean): Record<string, object | undefined
|
|
480
|
+
private async getExtensionRequest(isInitial: boolean): Promise<Record<string, object | undefined>> {
|
|
542
481
|
const ext: Record<string, object | undefined> = {};
|
|
543
|
-
|
|
544
|
-
ext[extName] = this.extensions[extName].onRequest(isInitial);
|
|
545
|
-
}
|
|
482
|
+
for (const extName in this.extensions) {
|
|
483
|
+
ext[extName] = await this.extensions[extName].onRequest(isInitial);
|
|
484
|
+
}
|
|
546
485
|
return ext;
|
|
547
486
|
}
|
|
548
487
|
|
|
@@ -595,203 +534,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
595
534
|
this.emit(SlidingSyncEvent.Lifecycle, state, resp, err);
|
|
596
535
|
}
|
|
597
536
|
|
|
598
|
-
private shiftRight(listKey: string, hi: number, low: number): void {
|
|
599
|
-
const list = this.lists.get(listKey);
|
|
600
|
-
if (!list) {
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
// l h
|
|
604
|
-
// 0,1,2,3,4 <- before
|
|
605
|
-
// 0,1,2,2,3 <- after, hi is deleted and low is duplicated
|
|
606
|
-
for (let i = hi; i > low; i--) {
|
|
607
|
-
if (list.isIndexInRange(i)) {
|
|
608
|
-
list.roomIndexToRoomId[i] = list.roomIndexToRoomId[i - 1];
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
private shiftLeft(listKey: string, hi: number, low: number): void {
|
|
614
|
-
const list = this.lists.get(listKey);
|
|
615
|
-
if (!list) {
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
// l h
|
|
619
|
-
// 0,1,2,3,4 <- before
|
|
620
|
-
// 0,1,3,4,4 <- after, low is deleted and hi is duplicated
|
|
621
|
-
for (let i = low; i < hi; i++) {
|
|
622
|
-
if (list.isIndexInRange(i)) {
|
|
623
|
-
list.roomIndexToRoomId[i] = list.roomIndexToRoomId[i + 1];
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
private removeEntry(listKey: string, index: number): void {
|
|
629
|
-
const list = this.lists.get(listKey);
|
|
630
|
-
if (!list) {
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
// work out the max index
|
|
634
|
-
let max = -1;
|
|
635
|
-
for (const n in list.roomIndexToRoomId) {
|
|
636
|
-
if (Number(n) > max) {
|
|
637
|
-
max = Number(n);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (max < 0 || index > max) {
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
// Everything higher than the gap needs to be shifted left.
|
|
644
|
-
this.shiftLeft(listKey, max, index);
|
|
645
|
-
delete list.roomIndexToRoomId[max];
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
private addEntry(listKey: string, index: number): void {
|
|
649
|
-
const list = this.lists.get(listKey);
|
|
650
|
-
if (!list) {
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
// work out the max index
|
|
654
|
-
let max = -1;
|
|
655
|
-
for (const n in list.roomIndexToRoomId) {
|
|
656
|
-
if (Number(n) > max) {
|
|
657
|
-
max = Number(n);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
if (max < 0 || index > max) {
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
// Everything higher than the gap needs to be shifted right, +1 so we don't delete the highest element
|
|
664
|
-
this.shiftRight(listKey, max + 1, index);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
private processListOps(list: ListResponse, listKey: string): void {
|
|
668
|
-
let gapIndex = -1;
|
|
669
|
-
const listData = this.lists.get(listKey);
|
|
670
|
-
if (!listData) {
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
list.ops.forEach((op: Operation) => {
|
|
674
|
-
if (!listData) {
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
switch (op.op) {
|
|
678
|
-
case "DELETE": {
|
|
679
|
-
logger.debug("DELETE", listKey, op.index, ";");
|
|
680
|
-
delete listData.roomIndexToRoomId[op.index];
|
|
681
|
-
if (gapIndex !== -1) {
|
|
682
|
-
// we already have a DELETE operation to process, so process it.
|
|
683
|
-
this.removeEntry(listKey, gapIndex);
|
|
684
|
-
}
|
|
685
|
-
gapIndex = op.index;
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
case "INSERT": {
|
|
689
|
-
logger.debug("INSERT", listKey, op.index, op.room_id, ";");
|
|
690
|
-
if (listData.roomIndexToRoomId[op.index]) {
|
|
691
|
-
// something is in this space, shift items out of the way
|
|
692
|
-
if (gapIndex < 0) {
|
|
693
|
-
// we haven't been told where to shift from, so make way for a new room entry.
|
|
694
|
-
this.addEntry(listKey, op.index);
|
|
695
|
-
} else if (gapIndex > op.index) {
|
|
696
|
-
// the gap is further down the list, shift every element to the right
|
|
697
|
-
// starting at the gap so we can just shift each element in turn:
|
|
698
|
-
// [A,B,C,_] gapIndex=3, op.index=0
|
|
699
|
-
// [A,B,C,C] i=3
|
|
700
|
-
// [A,B,B,C] i=2
|
|
701
|
-
// [A,A,B,C] i=1
|
|
702
|
-
// Terminate. We'll assign into op.index next.
|
|
703
|
-
this.shiftRight(listKey, gapIndex, op.index);
|
|
704
|
-
} else if (gapIndex < op.index) {
|
|
705
|
-
// the gap is further up the list, shift every element to the left
|
|
706
|
-
// starting at the gap so we can just shift each element in turn
|
|
707
|
-
this.shiftLeft(listKey, op.index, gapIndex);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
// forget the gap, we don't need it anymore. This is outside the check for
|
|
711
|
-
// a room being present in this index position because INSERTs always universally
|
|
712
|
-
// forget the gap, not conditionally based on the presence of a room in the INSERT
|
|
713
|
-
// position. Without this, DELETE 0; INSERT 0; would do the wrong thing.
|
|
714
|
-
gapIndex = -1;
|
|
715
|
-
listData.roomIndexToRoomId[op.index] = op.room_id;
|
|
716
|
-
break;
|
|
717
|
-
}
|
|
718
|
-
case "INVALIDATE": {
|
|
719
|
-
const startIndex = op.range[0];
|
|
720
|
-
for (let i = startIndex; i <= op.range[1]; i++) {
|
|
721
|
-
delete listData.roomIndexToRoomId[i];
|
|
722
|
-
}
|
|
723
|
-
logger.debug("INVALIDATE", listKey, op.range[0], op.range[1], ";");
|
|
724
|
-
break;
|
|
725
|
-
}
|
|
726
|
-
case "SYNC": {
|
|
727
|
-
const startIndex = op.range[0];
|
|
728
|
-
for (let i = startIndex; i <= op.range[1]; i++) {
|
|
729
|
-
const roomId = op.room_ids[i - startIndex];
|
|
730
|
-
if (!roomId) {
|
|
731
|
-
break; // we are at the end of list
|
|
732
|
-
}
|
|
733
|
-
listData.roomIndexToRoomId[i] = roomId;
|
|
734
|
-
}
|
|
735
|
-
logger.debug("SYNC", listKey, op.range[0], op.range[1], (op.room_ids || []).join(" "), ";");
|
|
736
|
-
break;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
if (gapIndex !== -1) {
|
|
741
|
-
// we already have a DELETE operation to process, so process it
|
|
742
|
-
// Everything higher than the gap needs to be shifted left.
|
|
743
|
-
this.removeEntry(listKey, gapIndex);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
537
|
/**
|
|
748
|
-
* Resend a Sliding Sync request. Used when something has changed in the request.
|
|
749
|
-
* the transaction ID of this request on success. Rejects with the transaction ID of this request
|
|
750
|
-
* on failure.
|
|
538
|
+
* Resend a Sliding Sync request. Used when something has changed in the request.
|
|
751
539
|
*/
|
|
752
|
-
public resend():
|
|
753
|
-
if (this.needsResend && this.txnIdDefers.length > 0) {
|
|
754
|
-
// we already have a resend queued, so just return the same promise
|
|
755
|
-
return this.txnIdDefers[this.txnIdDefers.length - 1].promise;
|
|
756
|
-
}
|
|
540
|
+
public resend(): void {
|
|
757
541
|
this.needsResend = true;
|
|
758
|
-
this.txnId = this.client.makeTxnId();
|
|
759
|
-
const d = defer<string>();
|
|
760
|
-
this.txnIdDefers.push({
|
|
761
|
-
...d,
|
|
762
|
-
txnId: this.txnId,
|
|
763
|
-
});
|
|
764
542
|
this.abortController?.abort();
|
|
765
543
|
this.abortController = new AbortController();
|
|
766
|
-
return d.promise;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
private resolveTransactionDefers(txnId?: string): void {
|
|
770
|
-
if (!txnId) {
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
// find the matching index
|
|
774
|
-
let txnIndex = -1;
|
|
775
|
-
for (let i = 0; i < this.txnIdDefers.length; i++) {
|
|
776
|
-
if (this.txnIdDefers[i].txnId === txnId) {
|
|
777
|
-
txnIndex = i;
|
|
778
|
-
break;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
if (txnIndex === -1) {
|
|
782
|
-
// this shouldn't happen; we shouldn't be seeing txn_ids for things we don't know about,
|
|
783
|
-
// whine about it.
|
|
784
|
-
logger.warn(`resolveTransactionDefers: seen ${txnId} but it isn't a pending txn, ignoring.`);
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
// This list is sorted in time, so if the input txnId ACKs in the middle of this array,
|
|
788
|
-
// then everything before it that hasn't been ACKed yet never will and we should reject them.
|
|
789
|
-
for (let i = 0; i < txnIndex; i++) {
|
|
790
|
-
this.txnIdDefers[i].reject(this.txnIdDefers[i].txnId);
|
|
791
|
-
}
|
|
792
|
-
this.txnIdDefers[txnIndex].resolve(txnId);
|
|
793
|
-
// clear out settled promises, including the one we resolved.
|
|
794
|
-
this.txnIdDefers = this.txnIdDefers.slice(txnIndex + 1);
|
|
795
544
|
}
|
|
796
545
|
|
|
797
546
|
/**
|
|
@@ -802,7 +551,6 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
802
551
|
this.abortController?.abort();
|
|
803
552
|
// remove all listeners so things can be GC'd
|
|
804
553
|
this.removeAllListeners(SlidingSyncEvent.Lifecycle);
|
|
805
|
-
this.removeAllListeners(SlidingSyncEvent.List);
|
|
806
554
|
this.removeAllListeners(SlidingSyncEvent.RoomData);
|
|
807
555
|
}
|
|
808
556
|
|
|
@@ -811,20 +559,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
811
559
|
*/
|
|
812
560
|
private resetup(): void {
|
|
813
561
|
logger.warn("SlidingSync: resetting connection info");
|
|
814
|
-
// any pending txn ID defers will be forgotten already by the server, so clear them out
|
|
815
|
-
this.txnIdDefers.forEach((d) => {
|
|
816
|
-
d.reject(d.txnId);
|
|
817
|
-
});
|
|
818
|
-
this.txnIdDefers = [];
|
|
819
562
|
// resend sticky params and de-confirm all subscriptions
|
|
820
563
|
this.lists.forEach((l) => {
|
|
821
564
|
l.setModified(true);
|
|
822
565
|
});
|
|
823
566
|
this.confirmedRoomSubscriptions = new Set<string>(); // leave desired ones alone though!
|
|
824
567
|
// reset the connection as we might be wedged
|
|
825
|
-
this.
|
|
826
|
-
this.abortController?.abort();
|
|
827
|
-
this.abortController = new AbortController();
|
|
568
|
+
this.resend();
|
|
828
569
|
}
|
|
829
570
|
|
|
830
571
|
/**
|
|
@@ -836,20 +577,18 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
836
577
|
let currentPos: string | undefined;
|
|
837
578
|
while (!this.terminated) {
|
|
838
579
|
this.needsResend = false;
|
|
839
|
-
let doNotUpdateList = false;
|
|
840
580
|
let resp: MSC3575SlidingSyncResponse | undefined;
|
|
841
581
|
try {
|
|
842
|
-
const listModifiedCount = this.listModifiedCount;
|
|
843
582
|
const reqLists: Record<string, MSC3575List> = {};
|
|
844
583
|
this.lists.forEach((l: SlidingList, key: string) => {
|
|
845
|
-
reqLists[key] = l.getList(
|
|
584
|
+
reqLists[key] = l.getList(true);
|
|
846
585
|
});
|
|
847
586
|
const reqBody: MSC3575SlidingSyncRequest = {
|
|
848
587
|
lists: reqLists,
|
|
849
588
|
pos: currentPos,
|
|
850
589
|
timeout: this.timeoutMS,
|
|
851
590
|
clientTimeout: this.timeoutMS + BUFFER_PERIOD_MS,
|
|
852
|
-
extensions: this.getExtensionRequest(currentPos === undefined),
|
|
591
|
+
extensions: await this.getExtensionRequest(currentPos === undefined),
|
|
853
592
|
};
|
|
854
593
|
// check if we are (un)subscribing to a room and modify request this one time for it
|
|
855
594
|
const newSubscriptions = difference(this.desiredRoomSubscriptions, this.confirmedRoomSubscriptions);
|
|
@@ -868,10 +607,6 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
868
607
|
reqBody.room_subscriptions[roomId] = sub;
|
|
869
608
|
}
|
|
870
609
|
}
|
|
871
|
-
if (this.txnId) {
|
|
872
|
-
reqBody.txn_id = this.txnId;
|
|
873
|
-
this.txnId = null;
|
|
874
|
-
}
|
|
875
610
|
this.pendingReq = this.client.slidingSync(reqBody, this.proxyBaseUrl, this.abortController.signal);
|
|
876
611
|
resp = await this.pendingReq;
|
|
877
612
|
currentPos = resp.pos;
|
|
@@ -882,13 +617,6 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
882
617
|
for (const roomId of unsubscriptions) {
|
|
883
618
|
this.confirmedRoomSubscriptions.delete(roomId);
|
|
884
619
|
}
|
|
885
|
-
if (listModifiedCount !== this.listModifiedCount) {
|
|
886
|
-
// the lists have been modified whilst we were waiting for 'await' to return, but the abort()
|
|
887
|
-
// call did nothing. It is NOT SAFE to modify the list array now. We'll process the response but
|
|
888
|
-
// not update list pointers.
|
|
889
|
-
logger.debug("list modified during await call, not updating list");
|
|
890
|
-
doNotUpdateList = true;
|
|
891
|
-
}
|
|
892
620
|
// mark all these lists as having been sent as sticky so we don't keep sending sticky params
|
|
893
621
|
this.lists.forEach((l) => {
|
|
894
622
|
l.setModified(false);
|
|
@@ -931,27 +659,8 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|
|
931
659
|
await this.invokeRoomDataListeners(roomId, resp!.rooms[roomId]);
|
|
932
660
|
}
|
|
933
661
|
|
|
934
|
-
const listKeysWithUpdates: Set<string> = new Set();
|
|
935
|
-
if (!doNotUpdateList) {
|
|
936
|
-
for (const [key, list] of Object.entries(resp.lists)) {
|
|
937
|
-
list.ops = list.ops ?? [];
|
|
938
|
-
if (list.ops.length > 0) {
|
|
939
|
-
listKeysWithUpdates.add(key);
|
|
940
|
-
}
|
|
941
|
-
this.processListOps(list, key);
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
662
|
this.invokeLifecycleListeners(SlidingSyncState.Complete, resp);
|
|
945
663
|
await this.onPostExtensionsResponse(resp.extensions);
|
|
946
|
-
listKeysWithUpdates.forEach((listKey: string) => {
|
|
947
|
-
const list = this.lists.get(listKey);
|
|
948
|
-
if (!list) {
|
|
949
|
-
return;
|
|
950
|
-
}
|
|
951
|
-
this.emit(SlidingSyncEvent.List, listKey, list.joinedCount, Object.assign({}, list.roomIndexToRoomId));
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
this.resolveTransactionDefers(resp.txn_id);
|
|
955
664
|
}
|
|
956
665
|
}
|
|
957
666
|
}
|