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

Factor SyncApi options out of IStoredClientOptions (#3009)

There are a couple of callback interfaces which are currently stuffed into
`IStoredClientOpts` to make it easier to pass them into the `SyncApi`
constructor.

Before we add more fields to this, let's separate it out to a separate object.
This commit is contained in:
Richard van der Hoff
2023-01-03 13:38:21 +00:00
committed by GitHub
parent e9fef19c8f
commit 9b372d23ca
6 changed files with 116 additions and 82 deletions

View File

@@ -35,9 +35,7 @@ describe("MatrixClient", function () {
let store: MemoryStore | undefined; let store: MemoryStore | undefined;
const defaultClientOpts: IStoredClientOpts = { const defaultClientOpts: IStoredClientOpts = {
canResetEntireTimeline: (roomId) => false,
experimentalThreadSupport: false, experimentalThreadSupport: false,
crypto: {} as unknown as IStoredClientOpts["crypto"],
}; };
const setupTests = (): [MatrixClient, HttpBackend, MemoryStore] => { const setupTests = (): [MatrixClient, HttpBackend, MemoryStore] => {
const store = new MemoryStore(); const store = new MemoryStore();

View File

@@ -38,7 +38,7 @@ import {
IRoomTimelineData, IRoomTimelineData,
} from "../../src"; } from "../../src";
import { SlidingSyncSdk } from "../../src/sliding-sync-sdk"; import { SlidingSyncSdk } from "../../src/sliding-sync-sdk";
import { SyncState } from "../../src/sync"; import { SyncApiOptions, SyncState } from "../../src/sync";
import { IStoredClientOpts } from "../../src/client"; import { IStoredClientOpts } from "../../src/client";
import { logger } from "../../src/logger"; import { logger } from "../../src/logger";
import { emitPromise } from "../test-utils/test-utils"; import { emitPromise } from "../test-utils/test-utils";
@@ -111,6 +111,7 @@ describe("SlidingSyncSdk", () => {
// assign client/httpBackend globals // assign client/httpBackend globals
const setupClient = async (testOpts?: Partial<IStoredClientOpts & { withCrypto: boolean }>) => { const setupClient = async (testOpts?: Partial<IStoredClientOpts & { withCrypto: boolean }>) => {
testOpts = testOpts || {}; testOpts = testOpts || {};
const syncOpts: SyncApiOptions = {};
const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken); const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
httpBackend = testClient.httpBackend; httpBackend = testClient.httpBackend;
client = testClient.client; client = testClient.client;
@@ -118,10 +119,10 @@ describe("SlidingSyncSdk", () => {
if (testOpts.withCrypto) { if (testOpts.withCrypto) {
httpBackend!.when("GET", "/room_keys/version").respond(404, {}); httpBackend!.when("GET", "/room_keys/version").respond(404, {});
await client!.initCrypto(); await client!.initCrypto();
testOpts.crypto = client!.crypto; syncOpts.crypto = client!.crypto;
} }
httpBackend!.when("GET", "/_matrix/client/r0/pushrules").respond(200, {}); httpBackend!.when("GET", "/_matrix/client/r0/pushrules").respond(200, {});
sdk = new SlidingSyncSdk(mockSlidingSync, client, testOpts); sdk = new SlidingSyncSdk(mockSlidingSync, client, testOpts, syncOpts);
}; };
// tear down client/httpBackend globals // tear down client/httpBackend globals

View File

@@ -21,7 +21,7 @@ limitations under the License.
import { EmoteEvent, IPartialEvent, MessageEvent, NoticeEvent, Optional } from "matrix-events-sdk"; import { EmoteEvent, IPartialEvent, MessageEvent, NoticeEvent, Optional } from "matrix-events-sdk";
import type { IMegolmSessionData } from "./@types/crypto"; import type { IMegolmSessionData } from "./@types/crypto";
import { ISyncStateData, SyncApi, SyncState } from "./sync"; import { ISyncStateData, SyncApi, SyncApiOptions, SyncState } from "./sync";
import { import {
EventStatus, EventStatus,
IContent, IContent,
@@ -456,17 +456,7 @@ export interface IStartClientOpts {
slidingSync?: SlidingSync; slidingSync?: SlidingSync;
} }
export interface IStoredClientOpts extends IStartClientOpts { export interface IStoredClientOpts extends IStartClientOpts {}
// Crypto manager
crypto?: Crypto;
/**
* A function which is called
* with a room ID and returns a boolean. It should return 'true' if the SDK can
* SAFELY remove events from this room. It may not be safe to remove events if
* there are other references to the timelines for this room.
*/
canResetEntireTimeline: ResetTimelineCallback;
}
export enum RoomVersionStability { export enum RoomVersionStability {
Stable = "stable", Stable = "stable",
@@ -1433,20 +1423,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
logger.error("Can't fetch server versions, continuing to initialise sync, this will be retried later", e); logger.error("Can't fetch server versions, continuing to initialise sync, this will be retried later", e);
} }
// shallow-copy the opts dict before modifying and storing it this.clientOpts = opts ?? {};
this.clientOpts = Object.assign({}, opts) as IStoredClientOpts;
this.clientOpts.crypto = this.crypto;
this.clientOpts.canResetEntireTimeline = (roomId): boolean => {
if (!this.canResetTimelineCallback) {
return false;
}
return this.canResetTimelineCallback(roomId);
};
if (this.clientOpts.slidingSync) { if (this.clientOpts.slidingSync) {
this.syncApi = new SlidingSyncSdk(this.clientOpts.slidingSync, this, this.clientOpts); this.syncApi = new SlidingSyncSdk(
this.clientOpts.slidingSync,
this,
this.clientOpts,
this.buildSyncApiOptions(),
);
} else { } else {
this.syncApi = new SyncApi(this, this.clientOpts); this.syncApi = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions());
} }
this.syncApi.sync(); this.syncApi.sync();
if (this.clientOpts.clientWellKnownPollPeriod !== undefined) { if (this.clientOpts.clientWellKnownPollPeriod !== undefined) {
@@ -1459,6 +1447,21 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
this.toDeviceMessageQueue.start(); this.toDeviceMessageQueue.start();
} }
/**
* Construct a SyncApiOptions for this client, suitable for passing into the SyncApi constructor
*/
protected buildSyncApiOptions(): SyncApiOptions {
return {
crypto: this.crypto,
canResetEntireTimeline: (roomId: string): boolean => {
if (!this.canResetTimelineCallback) {
return false;
}
return this.canResetTimelineCallback(roomId);
},
};
}
/** /**
* High level helper method to stop the client from polling and allow a * High level helper method to stop the client from polling and allow a
* clean shutdown. * clean shutdown.
@@ -3954,7 +3957,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const res = await this.http.authedRequest<{ room_id: string }>(Method.Post, path, queryString, data); const res = await this.http.authedRequest<{ room_id: string }>(Method.Post, path, queryString, data);
const roomId = res.room_id; const roomId = res.room_id;
const syncApi = new SyncApi(this, this.clientOpts); const syncApi = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions());
const room = syncApi.createRoom(roomId); const room = syncApi.createRoom(roomId);
if (opts.syncRoom) { if (opts.syncRoom) {
// v2 will do this for us // v2 will do this for us
@@ -6154,7 +6157,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/ */
public peekInRoom(roomId: string): Promise<Room> { public peekInRoom(roomId: string): Promise<Room> {
this.peekSync?.stopPeeking(); this.peekSync?.stopPeeking();
this.peekSync = new SyncApi(this, this.clientOpts); this.peekSync = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions());
return this.peekSync.peek(roomId); return this.peekSync.peek(roomId);
} }
@@ -6661,7 +6664,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
if (this.syncLeftRoomsPromise) { if (this.syncLeftRoomsPromise) {
return this.syncLeftRoomsPromise; // return the ongoing request return this.syncLeftRoomsPromise; // return the ongoing request
} }
const syncApi = new SyncApi(this, this.clientOpts); const syncApi = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions());
this.syncLeftRoomsPromise = syncApi.syncLeftRooms(); this.syncLeftRoomsPromise = syncApi.syncLeftRooms();
// cleanup locks // cleanup locks

View File

@@ -167,9 +167,9 @@ export class RoomWidgetClient extends MatrixClient {
// still has some valuable helper methods that we make use of, so we // still has some valuable helper methods that we make use of, so we
// instantiate it anyways // instantiate it anyways
if (opts.slidingSync) { if (opts.slidingSync) {
this.syncApi = new SlidingSyncSdk(opts.slidingSync, this, opts); this.syncApi = new SlidingSyncSdk(opts.slidingSync, this, opts, this.buildSyncApiOptions());
} else { } else {
this.syncApi = new SyncApi(this, opts); this.syncApi = new SyncApi(this, opts, this.buildSyncApiOptions());
} }
this.room = this.syncApi.createRoom(this.roomId); this.room = this.syncApi.createRoom(this.roomId);

View File

@@ -18,8 +18,15 @@ import { NotificationCountType, Room, RoomEvent } from "./models/room";
import { logger } from "./logger"; import { logger } from "./logger";
import * as utils from "./utils"; import * as utils from "./utils";
import { EventTimeline } from "./models/event-timeline"; import { EventTimeline } from "./models/event-timeline";
import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client"; import { ClientEvent, IStoredClientOpts, MatrixClient } from "./client";
import { ISyncStateData, SyncState, _createAndReEmitRoom } from "./sync"; import {
ISyncStateData,
SyncState,
_createAndReEmitRoom,
SyncApiOptions,
defaultClientOpts,
defaultSyncApiOpts,
} from "./sync";
import { MatrixEvent } from "./models/event"; import { MatrixEvent } from "./models/event";
import { Crypto } from "./crypto"; import { Crypto } from "./crypto";
import { IMinimalEvent, IRoomEvent, IStateEvent, IStrippedState, ISyncResponse } from "./sync-accumulator"; import { IMinimalEvent, IRoomEvent, IStateEvent, IStrippedState, ISyncResponse } from "./sync-accumulator";
@@ -342,6 +349,8 @@ class ExtensionReceipts implements Extension<ExtensionReceiptsRequest, Extension
* sliding sync API, see sliding-sync.ts or the class SlidingSync. * sliding sync API, see sliding-sync.ts or the class SlidingSync.
*/ */
export class SlidingSyncSdk { export class SlidingSyncSdk {
private readonly opts: IStoredClientOpts;
private readonly syncOpts: SyncApiOptions;
private syncState: SyncState | null = null; private syncState: SyncState | null = null;
private syncStateData?: ISyncStateData; private syncStateData?: ISyncStateData;
private lastPos: string | null = null; private lastPos: string | null = null;
@@ -351,19 +360,11 @@ export class SlidingSyncSdk {
public constructor( public constructor(
private readonly slidingSync: SlidingSync, private readonly slidingSync: SlidingSync,
private readonly client: MatrixClient, private readonly client: MatrixClient,
private readonly opts: Partial<IStoredClientOpts> = {}, opts?: IStoredClientOpts,
syncOpts?: SyncApiOptions,
) { ) {
this.opts.initialSyncLimit = this.opts.initialSyncLimit ?? 8; this.opts = defaultClientOpts(opts);
this.opts.resolveInvitesToProfiles = this.opts.resolveInvitesToProfiles || false; this.syncOpts = defaultSyncApiOpts(syncOpts);
this.opts.pollTimeout = this.opts.pollTimeout || 30 * 1000;
this.opts.pendingEventOrdering = this.opts.pendingEventOrdering || PendingEventOrdering.Chronological;
this.opts.experimentalThreadSupport = this.opts.experimentalThreadSupport === true;
if (!opts.canResetEntireTimeline) {
opts.canResetEntireTimeline = (_roomId: string): boolean => {
return false;
};
}
if (client.getNotifTimelineSet()) { if (client.getNotifTimelineSet()) {
client.reEmitter.reEmit(client.getNotifTimelineSet()!, [RoomEvent.Timeline, RoomEvent.TimelineReset]); client.reEmitter.reEmit(client.getNotifTimelineSet()!, [RoomEvent.Timeline, RoomEvent.TimelineReset]);
@@ -377,8 +378,8 @@ export class SlidingSyncSdk {
new ExtensionTyping(this.client), new ExtensionTyping(this.client),
new ExtensionReceipts(this.client), new ExtensionReceipts(this.client),
]; ];
if (this.opts.crypto) { if (this.syncOpts.crypto) {
extensions.push(new ExtensionE2EE(this.opts.crypto)); extensions.push(new ExtensionE2EE(this.syncOpts.crypto));
} }
extensions.forEach((ext) => { extensions.forEach((ext) => {
this.slidingSync.registerExtension(ext); this.slidingSync.registerExtension(ext);
@@ -698,7 +699,7 @@ export class SlidingSyncSdk {
if (limited) { if (limited) {
room.resetLiveTimeline( room.resetLiveTimeline(
roomData.prev_batch, roomData.prev_batch,
null, // TODO this.opts.canResetEntireTimeline(room.roomId) ? null : syncEventData.oldSyncToken, null, // TODO this.syncOpts.canResetEntireTimeline(room.roomId) ? null : syncEventData.oldSyncToken,
); );
// We have to assume any gap in any timeline is // We have to assume any gap in any timeline is
@@ -730,8 +731,8 @@ export class SlidingSyncSdk {
const processRoomEvent = async (e: MatrixEvent): Promise<void> => { const processRoomEvent = async (e: MatrixEvent): Promise<void> => {
client.emit(ClientEvent.Event, e); client.emit(ClientEvent.Event, e);
if (e.isState() && e.getType() == EventType.RoomEncryption && this.opts.crypto) { if (e.isState() && e.getType() == EventType.RoomEncryption && this.syncOpts.crypto) {
await this.opts.crypto.onCryptoEvent(room, e); await this.syncOpts.crypto.onCryptoEvent(room, e);
} }
}; };

View File

@@ -34,7 +34,7 @@ import { EventTimeline } from "./models/event-timeline";
import { PushProcessor } from "./pushprocessor"; import { PushProcessor } from "./pushprocessor";
import { logger } from "./logger"; import { logger } from "./logger";
import { InvalidStoreError, InvalidStoreState } from "./errors"; import { InvalidStoreError, InvalidStoreState } from "./errors";
import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client"; import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering, ResetTimelineCallback } from "./client";
import { import {
IEphemeral, IEphemeral,
IInvitedRoom, IInvitedRoom,
@@ -59,6 +59,7 @@ import { BeaconEvent } from "./models/beacon";
import { IEventsResponse } from "./@types/requests"; import { IEventsResponse } from "./@types/requests";
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync"; import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
import { Feature, ServerSupport } from "./feature"; import { Feature, ServerSupport } from "./feature";
import { Crypto } from "./crypto";
const DEBUG = true; const DEBUG = true;
@@ -110,6 +111,22 @@ function debuglog(...params: any[]): void {
logger.log(...params); logger.log(...params);
} }
/**
* Options passed into the constructor of SyncApi by MatrixClient
*/
export interface SyncApiOptions {
// Crypto manager
crypto?: Crypto;
/**
* A function which is called
* with a room ID and returns a boolean. It should return 'true' if the SDK can
* SAFELY remove events from this room. It may not be safe to remove events if
* there are other references to the timelines for this room.
*/
canResetEntireTimeline?: ResetTimelineCallback;
}
interface ISyncOptions { interface ISyncOptions {
filter?: string; filter?: string;
hasSyncedBefore?: boolean; hasSyncedBefore?: boolean;
@@ -163,7 +180,29 @@ type WrappedRoom<T> = T & {
isBrandNewRoom: boolean; isBrandNewRoom: boolean;
}; };
/** add default settings to an IStoredClientOpts */
export function defaultClientOpts(opts?: IStoredClientOpts): IStoredClientOpts {
return {
initialSyncLimit: 8,
resolveInvitesToProfiles: false,
pollTimeout: 30 * 1000,
pendingEventOrdering: PendingEventOrdering.Chronological,
experimentalThreadSupport: false,
...opts,
};
}
export function defaultSyncApiOpts(syncOpts?: SyncApiOptions): SyncApiOptions {
return {
canResetEntireTimeline: (_roomId): boolean => false,
...syncOpts,
};
}
export class SyncApi { export class SyncApi {
private readonly opts: IStoredClientOpts;
private readonly syncOpts: SyncApiOptions;
private _peekRoom: Optional<Room> = null; private _peekRoom: Optional<Room> = null;
private currentSyncRequest?: Promise<ISyncResponse>; private currentSyncRequest?: Promise<ISyncResponse>;
private abortController?: AbortController; private abortController?: AbortController;
@@ -180,21 +219,13 @@ export class SyncApi {
/** /**
* Construct an entity which is able to sync with a homeserver. * Construct an entity which is able to sync with a homeserver.
* @param client - The matrix client instance to use. * @param client - The matrix client instance to use.
* @param opts - Config options * @param opts - client config options
* @param syncOpts - sync-specific options passed by the client
* @internal * @internal
*/ */
public constructor(private readonly client: MatrixClient, private readonly opts: Partial<IStoredClientOpts> = {}) { public constructor(private readonly client: MatrixClient, opts?: IStoredClientOpts, syncOpts?: SyncApiOptions) {
this.opts.initialSyncLimit = this.opts.initialSyncLimit ?? 8; this.opts = defaultClientOpts(opts);
this.opts.resolveInvitesToProfiles = this.opts.resolveInvitesToProfiles || false; this.syncOpts = defaultSyncApiOpts(syncOpts);
this.opts.pollTimeout = this.opts.pollTimeout || 30 * 1000;
this.opts.pendingEventOrdering = this.opts.pendingEventOrdering || PendingEventOrdering.Chronological;
this.opts.experimentalThreadSupport = this.opts.experimentalThreadSupport === true;
if (!opts.canResetEntireTimeline) {
opts.canResetEntireTimeline = (roomId: string): boolean => {
return false;
};
}
if (client.getNotifTimelineSet()) { if (client.getNotifTimelineSet()) {
client.reEmitter.reEmit(client.getNotifTimelineSet()!, [RoomEvent.Timeline, RoomEvent.TimelineReset]); client.reEmitter.reEmit(client.getNotifTimelineSet()!, [RoomEvent.Timeline, RoomEvent.TimelineReset]);
@@ -632,7 +663,7 @@ export class SyncApi {
return; return;
} }
if (this.opts.lazyLoadMembers) { if (this.opts.lazyLoadMembers) {
this.opts.crypto?.enableLazyLoading(); this.syncOpts.crypto?.enableLazyLoading();
} }
try { try {
debuglog("Storing client options..."); debuglog("Storing client options...");
@@ -866,10 +897,10 @@ export class SyncApi {
catchingUp: this.catchingUp, catchingUp: this.catchingUp,
}; };
if (this.opts.crypto) { if (this.syncOpts.crypto) {
// tell the crypto module we're about to process a sync // tell the crypto module we're about to process a sync
// response // response
await this.opts.crypto.onSyncWillProcess(syncEventData); await this.syncOpts.crypto.onSyncWillProcess(syncEventData);
} }
try { try {
@@ -894,8 +925,8 @@ export class SyncApi {
// tell the crypto module to do its processing. It may block (to do a // tell the crypto module to do its processing. It may block (to do a
// /keys/changes request). // /keys/changes request).
if (this.opts.crypto) { if (this.syncOpts.crypto) {
await this.opts.crypto.onSyncCompleted(syncEventData); await this.syncOpts.crypto.onSyncCompleted(syncEventData);
} }
// keep emitting SYNCING -> SYNCING for clients who want to do bulk updates // keep emitting SYNCING -> SYNCING for clients who want to do bulk updates
@@ -907,8 +938,8 @@ export class SyncApi {
// stored sync data which means we don't have to worry that we may have missed // stored sync data which means we don't have to worry that we may have missed
// device changes. We can also skip the delay since we're not calling this very // device changes. We can also skip the delay since we're not calling this very
// frequently (and we don't really want to delay the sync for it). // frequently (and we don't really want to delay the sync for it).
if (this.opts.crypto) { if (this.syncOpts.crypto) {
await this.opts.crypto.saveDeviceList(0); await this.syncOpts.crypto.saveDeviceList(0);
} }
// tell databases that everything is now in a consistent state and can be saved. // tell databases that everything is now in a consistent state and can be saved.
@@ -1356,7 +1387,7 @@ export class SyncApi {
if (limited) { if (limited) {
room.resetLiveTimeline( room.resetLiveTimeline(
joinObj.timeline.prev_batch, joinObj.timeline.prev_batch,
this.opts.canResetEntireTimeline!(room.roomId) ? null : syncEventData.oldSyncToken ?? null, this.syncOpts.canResetEntireTimeline!(room.roomId) ? null : syncEventData.oldSyncToken ?? null,
); );
// We have to assume any gap in any timeline is // We have to assume any gap in any timeline is
@@ -1370,10 +1401,10 @@ export class SyncApi {
// avoids a race condition if the application tries to send a message after the // avoids a race condition if the application tries to send a message after the
// state event is processed, but before crypto is enabled, which then causes the // state event is processed, but before crypto is enabled, which then causes the
// crypto layer to complain. // crypto layer to complain.
if (this.opts.crypto) { if (this.syncOpts.crypto) {
for (const e of stateEvents.concat(events)) { for (const e of stateEvents.concat(events)) {
if (e.isState() && e.getType() === EventType.RoomEncryption && e.getStateKey() === "") { if (e.isState() && e.getType() === EventType.RoomEncryption && e.getStateKey() === "") {
await this.opts.crypto.onCryptoEvent(room, e); await this.syncOpts.crypto.onCryptoEvent(room, e);
} }
} }
} }
@@ -1462,8 +1493,8 @@ export class SyncApi {
// Handle device list updates // Handle device list updates
if (data.device_lists) { if (data.device_lists) {
if (this.opts.crypto) { if (this.syncOpts.crypto) {
await this.opts.crypto.handleDeviceListChanges(syncEventData, data.device_lists); await this.syncOpts.crypto.handleDeviceListChanges(syncEventData, data.device_lists);
} else { } else {
// FIXME if we *don't* have a crypto module, we still need to // FIXME if we *don't* have a crypto module, we still need to
// invalidate the device lists. But that would require a // invalidate the device lists. But that would require a
@@ -1472,12 +1503,12 @@ export class SyncApi {
} }
// Handle one_time_keys_count // Handle one_time_keys_count
if (this.opts.crypto && data.device_one_time_keys_count) { if (this.syncOpts.crypto && data.device_one_time_keys_count) {
const currentCount = data.device_one_time_keys_count.signed_curve25519 || 0; const currentCount = data.device_one_time_keys_count.signed_curve25519 || 0;
this.opts.crypto.updateOneTimeKeyCount(currentCount); this.syncOpts.crypto.updateOneTimeKeyCount(currentCount);
} }
if ( if (
this.opts.crypto && this.syncOpts.crypto &&
(data.device_unused_fallback_key_types || data["org.matrix.msc2732.device_unused_fallback_key_types"]) (data.device_unused_fallback_key_types || data["org.matrix.msc2732.device_unused_fallback_key_types"])
) { ) {
// The presence of device_unused_fallback_key_types indicates that the // The presence of device_unused_fallback_key_types indicates that the
@@ -1485,7 +1516,7 @@ export class SyncApi {
// signed_curve25519 fallback key we need a new one. // signed_curve25519 fallback key we need a new one.
const unusedFallbackKeys = const unusedFallbackKeys =
data.device_unused_fallback_key_types || data["org.matrix.msc2732.device_unused_fallback_key_types"]; data.device_unused_fallback_key_types || data["org.matrix.msc2732.device_unused_fallback_key_types"];
this.opts.crypto.setNeedsNewFallback( this.syncOpts.crypto.setNeedsNewFallback(
Array.isArray(unusedFallbackKeys) && !unusedFallbackKeys.includes("signed_curve25519"), Array.isArray(unusedFallbackKeys) && !unusedFallbackKeys.includes("signed_curve25519"),
); );
} }