diff --git a/app/EmojiService.ts b/app/EmojiService.ts index cdc5ee728208..879efb090f7b 100644 --- a/app/EmojiService.ts +++ b/app/EmojiService.ts @@ -34,9 +34,9 @@ type EmojiEntryType = Readonly<{ type SheetCacheEntry = Map; export class EmojiService { - private readonly emojiMap = new Map(); + readonly #emojiMap = new Map(); - private readonly sheetCache = new LRUCache({ + readonly #sheetCache = new LRUCache({ // Each sheet is roughly 500kb max: 10, }); @@ -52,12 +52,12 @@ export class EmojiService { return new Response('invalid', { status: 400 }); } - return this.fetch(emoji); + return this.#fetch(emoji); }); for (const [sheet, emojiList] of Object.entries(manifest)) { for (const utf16 of emojiList) { - this.emojiMap.set(utf16ToEmoji(utf16), { sheet, utf16 }); + this.#emojiMap.set(utf16ToEmoji(utf16), { sheet, utf16 }); } } } @@ -71,15 +71,15 @@ export class EmojiService { return new EmojiService(resourceService, manifest); } - private async fetch(emoji: string): Promise { - const entry = this.emojiMap.get(emoji); + async #fetch(emoji: string): Promise { + const entry = this.#emojiMap.get(emoji); if (!entry) { return new Response('entry not found', { status: 404 }); } const { sheet, utf16 } = entry; - let imageMap = this.sheetCache.get(sheet); + let imageMap = this.#sheetCache.get(sheet); if (!imageMap) { const proto = await this.resourceService.getData( `emoji-sheet-${sheet}.proto` @@ -96,7 +96,7 @@ export class EmojiService { image || new Uint8Array(0), ]) ); - this.sheetCache.set(sheet, imageMap); + this.#sheetCache.set(sheet, imageMap); } const image = imageMap.get(utf16); diff --git a/app/OptionalResourceService.ts b/app/OptionalResourceService.ts index 9ed0ecb07495..072fd2bc47a7 100644 --- a/app/OptionalResourceService.ts +++ b/app/OptionalResourceService.ts @@ -29,22 +29,22 @@ const RESOURCES_DICT_PATH = join( const MAX_CACHE_SIZE = 50 * 1024 * 1024; export class OptionalResourceService { - private maybeDeclaration: OptionalResourcesDictType | undefined; + #maybeDeclaration: OptionalResourcesDictType | undefined; - private readonly cache = new LRUCache({ + readonly #cache = new LRUCache({ maxSize: MAX_CACHE_SIZE, sizeCalculation: buf => buf.length, }); - private readonly fileQueues = new Map(); + readonly #fileQueues = new Map(); private constructor(private readonly resourcesDir: string) { ipcMain.handle('OptionalResourceService:getData', (_event, name) => this.getData(name) ); - drop(this.lazyInit()); + drop(this.#lazyInit()); } public static create(resourcesDir: string): OptionalResourceService { @@ -52,20 +52,20 @@ export class OptionalResourceService { } public async getData(name: string): Promise { - await this.lazyInit(); + await this.#lazyInit(); - const decl = this.declaration[name]; + const decl = this.#declaration[name]; if (!decl) { return undefined; } - const inMemory = this.cache.get(name); + const inMemory = this.#cache.get(name); if (inMemory) { return inMemory; } const filePath = join(this.resourcesDir, name); - return this.queueFileWork(filePath, async () => { + return this.#queueFileWork(filePath, async () => { try { const onDisk = await readFile(filePath); const digest = createHash('sha512').update(onDisk).digest(); @@ -76,7 +76,7 @@ export class OptionalResourceService { onDisk.length === decl.size ) { log.warn(`OptionalResourceService: loaded ${name} from disk`); - this.cache.set(name, onDisk); + this.#cache.set(name, onDisk); return onDisk; } @@ -94,7 +94,7 @@ export class OptionalResourceService { // Just do our best effort and move forward } - return this.fetch(name, decl, filePath); + return this.#fetch(name, decl, filePath); }); } @@ -102,15 +102,15 @@ export class OptionalResourceService { // Private // - private async lazyInit(): Promise { - if (this.maybeDeclaration !== undefined) { + async #lazyInit(): Promise { + if (this.#maybeDeclaration !== undefined) { return; } const json: unknown = JSON.parse( await readFile(RESOURCES_DICT_PATH, 'utf8') ); - this.maybeDeclaration = parseUnknown(OptionalResourcesDictSchema, json); + this.#maybeDeclaration = parseUnknown(OptionalResourcesDictSchema, json); // Clean unknown resources let subPaths: Array; @@ -126,7 +126,7 @@ export class OptionalResourceService { await Promise.all( subPaths.map(async subPath => { - if (this.declaration[subPath]) { + if (this.#declaration[subPath]) { return; } @@ -144,39 +144,39 @@ export class OptionalResourceService { ); } - private get declaration(): OptionalResourcesDictType { - if (this.maybeDeclaration === undefined) { + get #declaration(): OptionalResourcesDictType { + if (this.#maybeDeclaration === undefined) { throw new Error('optional-resources.json not loaded yet'); } - return this.maybeDeclaration; + return this.#maybeDeclaration; } - private async queueFileWork( + async #queueFileWork( filePath: string, body: () => Promise ): Promise { - let queue = this.fileQueues.get(filePath); + let queue = this.#fileQueues.get(filePath); if (!queue) { queue = new PQueue({ concurrency: 1 }); - this.fileQueues.set(filePath, queue); + this.#fileQueues.set(filePath, queue); } try { return await queue.add(body); } finally { if (queue.size === 0) { - this.fileQueues.delete(filePath); + this.#fileQueues.delete(filePath); } } } - private async fetch( + async #fetch( name: string, decl: OptionalResourceType, destPath: string ): Promise { const result = await got(decl.url, await getGotOptions()).buffer(); - this.cache.set(name, result); + this.#cache.set(name, result); try { await mkdir(dirname(destPath), { recursive: true }); diff --git a/app/PreventDisplaySleepService.ts b/app/PreventDisplaySleepService.ts index f6ad41f1123d..39057682166b 100644 --- a/app/PreventDisplaySleepService.ts +++ b/app/PreventDisplaySleepService.ts @@ -17,20 +17,20 @@ export class PreventDisplaySleepService { ); if (isEnabled) { - this.enable(); + this.#enable(); } else { - this.disable(); + this.#disable(); } } - private enable(): void { + #enable(): void { if (this.blockerId !== undefined) { return; } this.blockerId = this.powerSaveBlocker.start('prevent-display-sleep'); } - private disable(): void { + #disable(): void { if (this.blockerId === undefined) { return; } diff --git a/app/SystemTrayService.ts b/app/SystemTrayService.ts index b072efa2e104..e9e6152b2fe8 100644 --- a/app/SystemTrayService.ts +++ b/app/SystemTrayService.ts @@ -24,29 +24,20 @@ export type SystemTrayServiceOptionsType = Readonly<{ * [0]: https://www.electronjs.org/docs/api/tray */ export class SystemTrayService { - private browserWindow?: BrowserWindow; - - private readonly i18n: LocalizerType; - - private tray?: Tray; - - private isEnabled = false; - - private isQuitting = false; - - private unreadCount = 0; - - private boundRender: typeof SystemTrayService.prototype.render; - - private createTrayInstance: (icon: NativeImage) => Tray; + #browserWindow?: BrowserWindow; + readonly #i18n: LocalizerType; + #tray?: Tray; + #isEnabled = false; + #isQuitting = false; + #unreadCount = 0; + #createTrayInstance: (icon: NativeImage) => Tray; constructor({ i18n, createTrayInstance }: SystemTrayServiceOptionsType) { log.info('System tray service: created'); - this.i18n = i18n; - this.boundRender = this.render.bind(this); - this.createTrayInstance = createTrayInstance || (icon => new Tray(icon)); + this.#i18n = i18n; + this.#createTrayInstance = createTrayInstance || (icon => new Tray(icon)); - nativeTheme.on('updated', this.boundRender); + nativeTheme.on('updated', this.#render); } /** @@ -55,7 +46,7 @@ export class SystemTrayService { * toggle in the tray's context menu. */ setMainWindow(newBrowserWindow: undefined | BrowserWindow): void { - const oldBrowserWindow = this.browserWindow; + const oldBrowserWindow = this.#browserWindow; if (oldBrowserWindow === newBrowserWindow) { return; } @@ -67,18 +58,18 @@ export class SystemTrayService { ); if (oldBrowserWindow) { - oldBrowserWindow.off('show', this.boundRender); - oldBrowserWindow.off('hide', this.boundRender); + oldBrowserWindow.off('show', this.#render); + oldBrowserWindow.off('hide', this.#render); } if (newBrowserWindow) { - newBrowserWindow.on('show', this.boundRender); - newBrowserWindow.on('hide', this.boundRender); + newBrowserWindow.on('show', this.#render); + newBrowserWindow.on('hide', this.#render); } - this.browserWindow = newBrowserWindow; + this.#browserWindow = newBrowserWindow; - this.render(); + this.#render(); } /** @@ -86,27 +77,27 @@ export class SystemTrayService { * `setMainWindow`), the tray icon will not be shown, even if enabled. */ setEnabled(isEnabled: boolean): void { - if (this.isEnabled === isEnabled) { + if (this.#isEnabled === isEnabled) { return; } log.info(`System tray service: ${isEnabled ? 'enabling' : 'disabling'}`); - this.isEnabled = isEnabled; + this.#isEnabled = isEnabled; - this.render(); + this.#render(); } /** * Update the unread count, which updates the tray icon if it's visible. */ setUnreadCount(unreadCount: number): void { - if (this.unreadCount === unreadCount) { + if (this.#unreadCount === unreadCount) { return; } log.info(`System tray service: setting unread count to ${unreadCount}`); - this.unreadCount = unreadCount; - this.render(); + this.#unreadCount = unreadCount; + this.#render(); } /** @@ -118,35 +109,36 @@ export class SystemTrayService { markShouldQuit(): void { log.info('System tray service: markShouldQuit'); - this.tray = undefined; - this.isQuitting = true; + this.#tray = undefined; + this.#isQuitting = true; } isVisible(): boolean { - return this.tray !== undefined; + return this.#tray !== undefined; } - private render(): void { - if (this.isEnabled && this.browserWindow) { - this.renderEnabled(); + #render = (): void => { + if (this.#isEnabled && this.#browserWindow) { + this.#renderEnabled(); return; } - this.renderDisabled(); - } + this.#renderDisabled(); + }; - private renderEnabled() { - if (this.isQuitting) { + #renderEnabled() { + if (this.#isQuitting) { log.info('System tray service: not rendering the tray, quitting'); return; } log.info('System tray service: rendering the tray'); - this.tray ??= this.createTray(); - const { browserWindow, tray } = this; + this.#tray ??= this.#createTray(); + const tray = this.#tray; + const browserWindow = this.#browserWindow; try { - tray.setImage(getIcon(this.unreadCount)); + tray.setImage(getIcon(this.#unreadCount)); } catch (err: unknown) { log.warn( 'System tray service: failed to set preferred image. Falling back...' @@ -164,7 +156,7 @@ export class SystemTrayService { id: 'toggleWindowVisibility', ...(browserWindow?.isVisible() ? { - label: this.i18n('icu:hide'), + label: this.#i18n('icu:hide'), click: () => { log.info( 'System tray service: hiding the window from the context menu' @@ -172,25 +164,25 @@ export class SystemTrayService { // We re-fetch `this.browserWindow` here just in case the browser window // has changed while the context menu was open. Same applies in the // "show" case below. - this.browserWindow?.hide(); + this.#browserWindow?.hide(); }, } : { - label: this.i18n('icu:show'), + label: this.#i18n('icu:show'), click: () => { log.info( 'System tray service: showing the window from the context menu' ); - if (this.browserWindow) { - this.browserWindow.show(); - focusAndForceToTop(this.browserWindow); + if (this.#browserWindow) { + this.#browserWindow.show(); + focusAndForceToTop(this.#browserWindow); } }, }), }, { id: 'quit', - label: this.i18n('icu:quit'), + label: this.#i18n('icu:quit'), click: () => { log.info( 'System tray service: quitting the app from the context menu' @@ -202,21 +194,21 @@ export class SystemTrayService { ); } - private renderDisabled() { + #renderDisabled() { log.info('System tray service: rendering no tray'); - if (!this.tray) { + if (!this.#tray) { return; } - this.tray.destroy(); - this.tray = undefined; + this.#tray.destroy(); + this.#tray = undefined; } - private createTray(): Tray { + #createTray(): Tray { log.info('System tray service: creating the tray'); // This icon may be swiftly overwritten. - const result = this.createTrayInstance(getDefaultIcon()); + const result = this.#createTrayInstance(getDefaultIcon()); // Note: "When app indicator is used on Linux, the click event is ignored." This // doesn't mean that the click event is always ignored on Linux; it depends on how @@ -224,7 +216,7 @@ export class SystemTrayService { // // See . result.on('click', () => { - const { browserWindow } = this; + const browserWindow = this.#browserWindow; if (!browserWindow) { return; } @@ -236,7 +228,7 @@ export class SystemTrayService { } }); - result.setToolTip(this.i18n('icu:signalDesktop')); + result.setToolTip(this.#i18n('icu:signalDesktop')); return result; } @@ -246,7 +238,7 @@ export class SystemTrayService { * into the existing tray instances. It should not be used by "real" code. */ _getTray(): undefined | Tray { - return this.tray; + return this.#tray; } } diff --git a/app/SystemTraySettingCache.ts b/app/SystemTraySettingCache.ts index e320c1ec5069..fe07622858c6 100644 --- a/app/SystemTraySettingCache.ts +++ b/app/SystemTraySettingCache.ts @@ -15,9 +15,8 @@ import type { ConfigType } from './base_config'; * process. */ export class SystemTraySettingCache { - private cachedValue: undefined | SystemTraySetting; - - private getPromise: undefined | Promise; + #cachedValue: undefined | SystemTraySetting; + #getPromise: undefined | Promise; constructor( private readonly ephemeralConfig: Pick, @@ -25,19 +24,19 @@ export class SystemTraySettingCache { ) {} async get(): Promise { - if (this.cachedValue !== undefined) { - return this.cachedValue; + if (this.#cachedValue !== undefined) { + return this.#cachedValue; } - this.getPromise = this.getPromise || this.doFirstGet(); - return this.getPromise; + this.#getPromise = this.#getPromise || this.#doFirstGet(); + return this.#getPromise; } set(value: SystemTraySetting): void { - this.cachedValue = value; + this.#cachedValue = value; } - private async doFirstGet(): Promise { + async #doFirstGet(): Promise { let result: SystemTraySetting; // These command line flags are not officially supported, but many users rely on them. @@ -76,15 +75,15 @@ export class SystemTraySettingCache { ); } - return this.updateCachedValue(result); + return this.#updateCachedValue(result); } - private updateCachedValue(value: SystemTraySetting): SystemTraySetting { + #updateCachedValue(value: SystemTraySetting): SystemTraySetting { // If there's a value in the cache, someone has updated the value "out from under us", // so we should return that because it's newer. - this.cachedValue = - this.cachedValue === undefined ? value : this.cachedValue; + this.#cachedValue = + this.#cachedValue === undefined ? value : this.#cachedValue; - return this.cachedValue; + return this.#cachedValue; } } diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index 5546d78183dd..39d3be9a572e 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -149,17 +149,14 @@ export function start(): void { } export class ConversationController { - private _initialFetchComplete = false; + #_initialFetchComplete = false; private _initialPromise: undefined | Promise; - private _conversationOpenStart = new Map(); - - private _hasQueueEmptied = false; - - private _combineConversationsQueue = new PQueue({ concurrency: 1 }); - - private _signalConversationId: undefined | string; + #_conversationOpenStart = new Map(); + #_hasQueueEmptied = false; + #_combineConversationsQueue = new PQueue({ concurrency: 1 }); + #_signalConversationId: undefined | string; constructor(private _conversations: ConversationModelCollectionType) { const debouncedUpdateUnreadCount = debounce( @@ -192,7 +189,7 @@ export class ConversationController { } updateUnreadCount(): void { - if (!this._hasQueueEmptied) { + if (!this.#_hasQueueEmptied) { return; } @@ -238,12 +235,12 @@ export class ConversationController { } onEmpty(): void { - this._hasQueueEmptied = true; + this.#_hasQueueEmptied = true; this.updateUnreadCount(); } get(id?: string | null): ConversationModel | undefined { - if (!this._initialFetchComplete) { + if (!this.#_initialFetchComplete) { throw new Error( 'ConversationController.get() needs complete initial fetch' ); @@ -283,7 +280,7 @@ export class ConversationController { ); } - if (!this._initialFetchComplete) { + if (!this.#_initialFetchComplete) { throw new Error( 'ConversationController.get() needs complete initial fetch' ); @@ -460,13 +457,13 @@ export class ConversationController { await updateConversation(conversation.attributes); } - this._signalConversationId = conversation.id; + this.#_signalConversationId = conversation.id; return conversation; } isSignalConversationId(conversationId: string): boolean { - return this._signalConversationId === conversationId; + return this.#_signalConversationId === conversationId; } areWePrimaryDevice(): boolean { @@ -841,14 +838,14 @@ export class ConversationController { } checkForConflicts(): Promise { - return this._combineConversationsQueue.add(() => - this.doCheckForConflicts() + return this.#_combineConversationsQueue.add(() => + this.#doCheckForConflicts() ); } // Note: `doCombineConversations` is directly used within this function since both // run on `_combineConversationsQueue` queue and we don't want deadlocks. - private async doCheckForConflicts(): Promise { + async #doCheckForConflicts(): Promise { log.info('ConversationController.checkForConflicts: starting...'); const byServiceId = Object.create(null); const byE164 = Object.create(null); @@ -884,7 +881,7 @@ export class ConversationController { if (conversation.get('e164')) { // Keep new one // eslint-disable-next-line no-await-in-loop - await this.doCombineConversations({ + await this.#doCombineConversations({ current: conversation, obsolete: existing, }); @@ -892,7 +889,7 @@ export class ConversationController { } else { // Keep existing - note that this applies if neither had an e164 // eslint-disable-next-line no-await-in-loop - await this.doCombineConversations({ + await this.#doCombineConversations({ current: existing, obsolete: conversation, }); @@ -918,7 +915,7 @@ export class ConversationController { if (conversation.get('e164') || conversation.getPni()) { // Keep new one // eslint-disable-next-line no-await-in-loop - await this.doCombineConversations({ + await this.#doCombineConversations({ current: conversation, obsolete: existing, }); @@ -926,7 +923,7 @@ export class ConversationController { } else { // Keep existing - note that this applies if neither had an e164 // eslint-disable-next-line no-await-in-loop - await this.doCombineConversations({ + await this.#doCombineConversations({ current: existing, obsolete: conversation, }); @@ -964,7 +961,7 @@ export class ConversationController { if (conversation.getServiceId()) { // Keep new one // eslint-disable-next-line no-await-in-loop - await this.doCombineConversations({ + await this.#doCombineConversations({ current: conversation, obsolete: existing, }); @@ -972,7 +969,7 @@ export class ConversationController { } else { // Keep existing - note that this applies if neither had a service id // eslint-disable-next-line no-await-in-loop - await this.doCombineConversations({ + await this.#doCombineConversations({ current: existing, obsolete: conversation, }); @@ -1010,14 +1007,14 @@ export class ConversationController { !isGroupV2(existing.attributes) ) { // eslint-disable-next-line no-await-in-loop - await this.doCombineConversations({ + await this.#doCombineConversations({ current: conversation, obsolete: existing, }); byGroupV2Id[groupV2Id] = conversation; } else { // eslint-disable-next-line no-await-in-loop - await this.doCombineConversations({ + await this.#doCombineConversations({ current: existing, obsolete: conversation, }); @@ -1032,12 +1029,12 @@ export class ConversationController { async combineConversations( options: CombineConversationsParams ): Promise { - return this._combineConversationsQueue.add(() => - this.doCombineConversations(options) + return this.#_combineConversationsQueue.add(() => + this.#doCombineConversations(options) ); } - private async doCombineConversations({ + async #doCombineConversations({ current, obsolete, obsoleteTitleInfo, @@ -1304,12 +1301,12 @@ export class ConversationController { reset(): void { delete this._initialPromise; - this._initialFetchComplete = false; + this.#_initialFetchComplete = false; this._conversations.reset([]); } load(): Promise { - this._initialPromise ||= this.doLoad(); + this._initialPromise ||= this.#doLoad(); return this._initialPromise; } @@ -1346,16 +1343,16 @@ export class ConversationController { } onConvoOpenStart(conversationId: string): void { - this._conversationOpenStart.set(conversationId, Date.now()); + this.#_conversationOpenStart.set(conversationId, Date.now()); } onConvoMessageMount(conversationId: string): void { - const loadStart = this._conversationOpenStart.get(conversationId); + const loadStart = this.#_conversationOpenStart.get(conversationId); if (loadStart === undefined) { return; } - this._conversationOpenStart.delete(conversationId); + this.#_conversationOpenStart.delete(conversationId); this.get(conversationId)?.onOpenComplete(loadStart); } @@ -1424,7 +1421,7 @@ export class ConversationController { } } - private async doLoad(): Promise { + async #doLoad(): Promise { log.info('ConversationController: starting initial fetch'); if (this._conversations.length) { @@ -1460,7 +1457,7 @@ export class ConversationController { // It is alright to call it first because the 'add'/'update' events are // triggered after updating the collection. - this._initialFetchComplete = true; + this.#_initialFetchComplete = true; // Hydrate the final set of conversations batchDispatch(() => { diff --git a/ts/IdleDetector.ts b/ts/IdleDetector.ts index f21b4959c735..8da55849e3a7 100644 --- a/ts/IdleDetector.ts +++ b/ts/IdleDetector.ts @@ -14,7 +14,7 @@ export class IdleDetector extends EventEmitter { public start(): void { log.info('Start idle detector'); - this.scheduleNextCallback(); + this.#scheduleNextCallback(); } public stop(): void { @@ -23,10 +23,10 @@ export class IdleDetector extends EventEmitter { } log.info('Stop idle detector'); - this.clearScheduledCallbacks(); + this.#clearScheduledCallbacks(); } - private clearScheduledCallbacks() { + #clearScheduledCallbacks() { if (this.handle) { cancelIdleCallback(this.handle); delete this.handle; @@ -36,14 +36,14 @@ export class IdleDetector extends EventEmitter { delete this.timeoutId; } - private scheduleNextCallback() { - this.clearScheduledCallbacks(); + #scheduleNextCallback() { + this.#clearScheduledCallbacks(); this.handle = window.requestIdleCallback(deadline => { const { didTimeout } = deadline; const timeRemaining = deadline.timeRemaining(); const isIdle = timeRemaining >= IDLE_THRESHOLD_MS; this.timeoutId = setTimeout( - () => this.scheduleNextCallback(), + () => this.#scheduleNextCallback(), POLL_INTERVAL_MS ); if (isIdle || didTimeout) { diff --git a/ts/LibSignalStores.ts b/ts/LibSignalStores.ts index 375841d2e5fd..19984cb15999 100644 --- a/ts/LibSignalStores.ts +++ b/ts/LibSignalStores.ts @@ -51,15 +51,14 @@ export type SessionsOptions = Readonly<{ }>; export class Sessions extends SessionStore { - private readonly ourServiceId: ServiceIdString; - - private readonly zone: Zone | undefined; + readonly #ourServiceId: ServiceIdString; + readonly #zone: Zone | undefined; constructor({ ourServiceId, zone }: SessionsOptions) { super(); - this.ourServiceId = ourServiceId; - this.zone = zone; + this.#ourServiceId = ourServiceId; + this.#zone = zone; } async saveSession( @@ -67,17 +66,17 @@ export class Sessions extends SessionStore { record: SessionRecord ): Promise { await window.textsecure.storage.protocol.storeSession( - toQualifiedAddress(this.ourServiceId, address), + toQualifiedAddress(this.#ourServiceId, address), record, - { zone: this.zone } + { zone: this.#zone } ); } async getSession(name: ProtocolAddress): Promise { - const encodedAddress = toQualifiedAddress(this.ourServiceId, name); + const encodedAddress = toQualifiedAddress(this.#ourServiceId, name); const record = await window.textsecure.storage.protocol.loadSession( encodedAddress, - { zone: this.zone } + { zone: this.#zone } ); return record || null; @@ -87,10 +86,10 @@ export class Sessions extends SessionStore { addresses: Array ): Promise> { const encodedAddresses = addresses.map(addr => - toQualifiedAddress(this.ourServiceId, addr) + toQualifiedAddress(this.#ourServiceId, addr) ); return window.textsecure.storage.protocol.loadSessions(encodedAddresses, { - zone: this.zone, + zone: this.#zone, }); } } @@ -101,20 +100,19 @@ export type IdentityKeysOptions = Readonly<{ }>; export class IdentityKeys extends IdentityKeyStore { - private readonly ourServiceId: ServiceIdString; - - private readonly zone: Zone | undefined; + readonly #ourServiceId: ServiceIdString; + readonly #zone: Zone | undefined; constructor({ ourServiceId, zone }: IdentityKeysOptions) { super(); - this.ourServiceId = ourServiceId; - this.zone = zone; + this.#ourServiceId = ourServiceId; + this.#zone = zone; } async getIdentityKey(): Promise { const keyPair = window.textsecure.storage.protocol.getIdentityKeyPair( - this.ourServiceId + this.#ourServiceId ); if (!keyPair) { throw new Error('IdentityKeyStore/getIdentityKey: No identity key!'); @@ -124,7 +122,7 @@ export class IdentityKeys extends IdentityKeyStore { async getLocalRegistrationId(): Promise { const id = await window.textsecure.storage.protocol.getLocalRegistrationId( - this.ourServiceId + this.#ourServiceId ); if (!isNumber(id)) { throw new Error( @@ -157,7 +155,7 @@ export class IdentityKeys extends IdentityKeyStore { encodedAddress, publicKey, false, - { zone: this.zone } + { zone: this.#zone } ); } @@ -182,11 +180,11 @@ export type PreKeysOptions = Readonly<{ }>; export class PreKeys extends PreKeyStore { - private readonly ourServiceId: ServiceIdString; + readonly #ourServiceId: ServiceIdString; constructor({ ourServiceId }: PreKeysOptions) { super(); - this.ourServiceId = ourServiceId; + this.#ourServiceId = ourServiceId; } async savePreKey(): Promise { @@ -195,7 +193,7 @@ export class PreKeys extends PreKeyStore { async getPreKey(id: number): Promise { const preKey = await window.textsecure.storage.protocol.loadPreKey( - this.ourServiceId, + this.#ourServiceId, id ); @@ -207,18 +205,18 @@ export class PreKeys extends PreKeyStore { } async removePreKey(id: number): Promise { - await window.textsecure.storage.protocol.removePreKeys(this.ourServiceId, [ + await window.textsecure.storage.protocol.removePreKeys(this.#ourServiceId, [ id, ]); } } export class KyberPreKeys extends KyberPreKeyStore { - private readonly ourServiceId: ServiceIdString; + readonly #ourServiceId: ServiceIdString; constructor({ ourServiceId }: PreKeysOptions) { super(); - this.ourServiceId = ourServiceId; + this.#ourServiceId = ourServiceId; } async saveKyberPreKey(): Promise { @@ -228,7 +226,7 @@ export class KyberPreKeys extends KyberPreKeyStore { async getKyberPreKey(id: number): Promise { const kyberPreKey = await window.textsecure.storage.protocol.loadKyberPreKey( - this.ourServiceId, + this.#ourServiceId, id ); @@ -241,7 +239,7 @@ export class KyberPreKeys extends KyberPreKeyStore { async markKyberPreKeyUsed(id: number): Promise { await window.textsecure.storage.protocol.maybeRemoveKyberPreKey( - this.ourServiceId, + this.#ourServiceId, id ); } @@ -253,13 +251,13 @@ export type SenderKeysOptions = Readonly<{ }>; export class SenderKeys extends SenderKeyStore { - private readonly ourServiceId: ServiceIdString; + readonly #ourServiceId: ServiceIdString; readonly zone: Zone | undefined; constructor({ ourServiceId, zone }: SenderKeysOptions) { super(); - this.ourServiceId = ourServiceId; + this.#ourServiceId = ourServiceId; this.zone = zone; } @@ -268,7 +266,7 @@ export class SenderKeys extends SenderKeyStore { distributionId: Uuid, record: SenderKeyRecord ): Promise { - const encodedAddress = toQualifiedAddress(this.ourServiceId, sender); + const encodedAddress = toQualifiedAddress(this.#ourServiceId, sender); await window.textsecure.storage.protocol.saveSenderKey( encodedAddress, @@ -282,7 +280,7 @@ export class SenderKeys extends SenderKeyStore { sender: ProtocolAddress, distributionId: Uuid ): Promise { - const encodedAddress = toQualifiedAddress(this.ourServiceId, sender); + const encodedAddress = toQualifiedAddress(this.#ourServiceId, sender); const senderKey = await window.textsecure.storage.protocol.getSenderKey( encodedAddress, @@ -299,11 +297,11 @@ export type SignedPreKeysOptions = Readonly<{ }>; export class SignedPreKeys extends SignedPreKeyStore { - private readonly ourServiceId: ServiceIdString; + readonly #ourServiceId: ServiceIdString; constructor({ ourServiceId }: SignedPreKeysOptions) { super(); - this.ourServiceId = ourServiceId; + this.#ourServiceId = ourServiceId; } async saveSignedPreKey(): Promise { @@ -313,7 +311,7 @@ export class SignedPreKeys extends SignedPreKeyStore { async getSignedPreKey(id: number): Promise { const signedPreKey = await window.textsecure.storage.protocol.loadSignedPreKey( - this.ourServiceId, + this.#ourServiceId, id ); diff --git a/ts/SignalProtocolStore.ts b/ts/SignalProtocolStore.ts index f73784024a2a..4b8182f2a934 100644 --- a/ts/SignalProtocolStore.ts +++ b/ts/SignalProtocolStore.ts @@ -240,11 +240,10 @@ export class SignalProtocolStore extends EventEmitter { // Cached values - private ourIdentityKeys = new Map(); + #ourIdentityKeys = new Map(); - private ourRegistrationIds = new Map(); - - private cachedPniSignatureMessage: PniSignatureMessageType | undefined; + #ourRegistrationIds = new Map(); + #cachedPniSignatureMessage: PniSignatureMessageType | undefined; identityKeys?: Map< IdentityKeyIdType, @@ -273,24 +272,18 @@ export class SignalProtocolStore extends EventEmitter { sessionQueueJobCounter = 0; - private readonly identityQueues = new Map(); - - private currentZone?: Zone; - - private currentZoneDepth = 0; - - private readonly zoneQueue: Array = []; - - private pendingSessions = new Map(); - - private pendingSenderKeys = new Map(); - - private pendingUnprocessed = new Map(); + readonly #identityQueues = new Map(); + #currentZone?: Zone; + #currentZoneDepth = 0; + readonly #zoneQueue: Array = []; + #pendingSessions = new Map(); + #pendingSenderKeys = new Map(); + #pendingUnprocessed = new Map(); async hydrateCaches(): Promise { await Promise.all([ (async () => { - this.ourIdentityKeys.clear(); + this.#ourIdentityKeys.clear(); const map = (await DataReader.getItemById( 'identityKeyMap' )) as unknown as ItemType<'identityKeyMap'>; @@ -304,14 +297,14 @@ export class SignalProtocolStore extends EventEmitter { 'Invalid identity key serviceId' ); const { privKey, pubKey } = map.value[serviceId]; - this.ourIdentityKeys.set(serviceId, { + this.#ourIdentityKeys.set(serviceId, { privKey, pubKey, }); } })(), (async () => { - this.ourRegistrationIds.clear(); + this.#ourRegistrationIds.clear(); const map = (await DataReader.getItemById( 'registrationIdMap' )) as unknown as ItemType<'registrationIdMap'>; @@ -324,7 +317,7 @@ export class SignalProtocolStore extends EventEmitter { isServiceIdString(serviceId), 'Invalid registration id serviceId' ); - this.ourRegistrationIds.set(serviceId, map.value[serviceId]); + this.#ourRegistrationIds.set(serviceId, map.value[serviceId]); } })(), _fillCaches( @@ -361,25 +354,22 @@ export class SignalProtocolStore extends EventEmitter { } getIdentityKeyPair(ourServiceId: ServiceIdString): KeyPairType | undefined { - return this.ourIdentityKeys.get(ourServiceId); + return this.#ourIdentityKeys.get(ourServiceId); } async getLocalRegistrationId( ourServiceId: ServiceIdString ): Promise { - return this.ourRegistrationIds.get(ourServiceId); + return this.#ourRegistrationIds.get(ourServiceId); } - private _getKeyId( - ourServiceId: ServiceIdString, - keyId: number - ): PreKeyIdType { + #_getKeyId(ourServiceId: ServiceIdString, keyId: number): PreKeyIdType { return `${ourServiceId}:${keyId}`; } // KyberPreKeys - private _getKyberPreKeyEntry( + #_getKyberPreKeyEntry( id: PreKeyIdType, logContext: string ): @@ -420,8 +410,8 @@ export class SignalProtocolStore extends EventEmitter { ourServiceId: ServiceIdString, keyId: number ): Promise { - const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); - const entry = this._getKyberPreKeyEntry(id, 'loadKyberPreKey'); + const id: PreKeyIdType = this.#_getKeyId(ourServiceId, keyId); + const entry = this.#_getKyberPreKeyEntry(id, 'loadKyberPreKey'); return entry?.item; } @@ -457,7 +447,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('storeKyberPreKey: this.kyberPreKeys not yet cached!'); } - const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); + const id: PreKeyIdType = this.#_getKeyId(ourServiceId, keyId); const item = kyberPreKeyCache.get(id); if (!item) { throw new Error(`confirmKyberPreKey: missing kyber prekey ${id}!`); @@ -487,7 +477,7 @@ export class SignalProtocolStore extends EventEmitter { const toSave: Array = []; keys.forEach(key => { - const id: PreKeyIdType = this._getKeyId(ourServiceId, key.keyId); + const id: PreKeyIdType = this.#_getKeyId(ourServiceId, key.keyId); if (kyberPreKeyCache.has(id)) { throw new Error(`storeKyberPreKey: kyber prekey ${id} already exists!`); } @@ -519,8 +509,8 @@ export class SignalProtocolStore extends EventEmitter { ourServiceId: ServiceIdString, keyId: number ): Promise { - const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); - const entry = this._getKyberPreKeyEntry(id, 'maybeRemoveKyberPreKey'); + const id: PreKeyIdType = this.#_getKeyId(ourServiceId, keyId); + const entry = this.#_getKyberPreKeyEntry(id, 'maybeRemoveKyberPreKey'); if (!entry) { return; @@ -544,7 +534,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('removeKyberPreKeys: this.kyberPreKeys not yet cached!'); } - const ids = keyIds.map(keyId => this._getKeyId(ourServiceId, keyId)); + const ids = keyIds.map(keyId => this.#_getKeyId(ourServiceId, keyId)); log.info('removeKyberPreKeys: Removing kyber prekeys:', formatKeys(keyIds)); const changes = await DataWriter.removeKyberPreKeyById(ids); @@ -554,7 +544,7 @@ export class SignalProtocolStore extends EventEmitter { }); if (kyberPreKeyCache.size < LOW_KEYS_THRESHOLD) { - this.emitLowKeys( + this.#emitLowKeys( ourServiceId, `removeKyberPreKeys@${kyberPreKeyCache.size}` ); @@ -579,7 +569,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('loadPreKey: this.preKeys not yet cached!'); } - const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); + const id: PreKeyIdType = this.#_getKeyId(ourServiceId, keyId); const entry = this.preKeys.get(id); if (!entry) { log.error('Failed to fetch prekey:', id); @@ -628,7 +618,7 @@ export class SignalProtocolStore extends EventEmitter { const now = Date.now(); const toSave: Array = []; keys.forEach(key => { - const id: PreKeyIdType = this._getKeyId(ourServiceId, key.keyId); + const id: PreKeyIdType = this.#_getKeyId(ourServiceId, key.keyId); if (preKeyCache.has(id)) { throw new Error(`storePreKeys: prekey ${id} already exists!`); @@ -665,7 +655,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('removePreKeys: this.preKeys not yet cached!'); } - const ids = keyIds.map(keyId => this._getKeyId(ourServiceId, keyId)); + const ids = keyIds.map(keyId => this.#_getKeyId(ourServiceId, keyId)); log.info('removePreKeys: Removing prekeys:', formatKeys(keyIds)); @@ -676,7 +666,7 @@ export class SignalProtocolStore extends EventEmitter { }); if (preKeyCache.size < LOW_KEYS_THRESHOLD) { - this.emitLowKeys(ourServiceId, `removePreKeys@${preKeyCache.size}`); + this.#emitLowKeys(ourServiceId, `removePreKeys@${preKeyCache.size}`); } } @@ -756,7 +746,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('storeKyberPreKey: this.signedPreKeys not yet cached!'); } - const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); + const id: PreKeyIdType = this.#_getKeyId(ourServiceId, keyId); const item = signedPreKeyCache.get(id); if (!item) { throw new Error(`confirmSignedPreKey: missing prekey ${id}!`); @@ -785,7 +775,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('storeSignedPreKey: this.signedPreKeys not yet cached!'); } - const id: SignedPreKeyIdType = this._getKeyId(ourServiceId, keyId); + const id: SignedPreKeyIdType = this.#_getKeyId(ourServiceId, keyId); const fromDB = { id, @@ -813,7 +803,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('removeSignedPreKey: this.signedPreKeys not yet cached!'); } - const ids = keyIds.map(keyId => this._getKeyId(ourServiceId, keyId)); + const ids = keyIds.map(keyId => this.#_getKeyId(ourServiceId, keyId)); log.info( 'removeSignedPreKeys: Removing signed prekeys:', @@ -855,13 +845,13 @@ export class SignalProtocolStore extends EventEmitter { zone = GLOBAL_ZONE ): Promise { return this.withZone(zone, 'enqueueSenderKeyJob', async () => { - const queue = this._getSenderKeyQueue(qualifiedAddress); + const queue = this.#_getSenderKeyQueue(qualifiedAddress); return queue.add(task); }); } - private _createSenderKeyQueue(): PQueue { + #_createSenderKeyQueue(): PQueue { return new PQueue({ concurrency: 1, timeout: MINUTE * 30, @@ -869,18 +859,18 @@ export class SignalProtocolStore extends EventEmitter { }); } - private _getSenderKeyQueue(senderId: QualifiedAddress): PQueue { + #_getSenderKeyQueue(senderId: QualifiedAddress): PQueue { const cachedQueue = this.senderKeyQueues.get(senderId.toString()); if (cachedQueue) { return cachedQueue; } - const freshQueue = this._createSenderKeyQueue(); + const freshQueue = this.#_createSenderKeyQueue(); this.senderKeyQueues.set(senderId.toString(), freshQueue); return freshQueue; } - private getSenderKeyId( + #getSenderKeyId( senderKeyId: QualifiedAddress, distributionId: string ): SenderKeyIdType { @@ -901,7 +891,7 @@ export class SignalProtocolStore extends EventEmitter { const senderId = qualifiedAddress.toString(); try { - const id = this.getSenderKeyId(qualifiedAddress, distributionId); + const id = this.#getSenderKeyId(qualifiedAddress, distributionId); const fromDB: SenderKeyType = { id, @@ -911,7 +901,7 @@ export class SignalProtocolStore extends EventEmitter { lastUpdatedDate: Date.now(), }; - this.pendingSenderKeys.set(id, { + this.#pendingSenderKeys.set(id, { hydrated: true, fromDB, item: record, @@ -919,7 +909,7 @@ export class SignalProtocolStore extends EventEmitter { // Current zone doesn't support pending sessions - commit immediately if (!zone.supportsPendingSenderKeys()) { - await this.commitZoneChanges('saveSenderKey'); + await this.#commitZoneChanges('saveSenderKey'); } } catch (error) { const errorString = Errors.toLogFormat(error); @@ -943,10 +933,10 @@ export class SignalProtocolStore extends EventEmitter { const senderId = qualifiedAddress.toString(); try { - const id = this.getSenderKeyId(qualifiedAddress, distributionId); + const id = this.#getSenderKeyId(qualifiedAddress, distributionId); - const map = this.pendingSenderKeys.has(id) - ? this.pendingSenderKeys + const map = this.#pendingSenderKeys.has(id) + ? this.#pendingSenderKeys : this.senderKeys; const entry = map.get(id); @@ -991,7 +981,7 @@ export class SignalProtocolStore extends EventEmitter { const senderId = qualifiedAddress.toString(); try { - const id = this.getSenderKeyId(qualifiedAddress, distributionId); + const id = this.#getSenderKeyId(qualifiedAddress, distributionId); await DataWriter.removeSenderKeyById(id); @@ -1009,8 +999,8 @@ export class SignalProtocolStore extends EventEmitter { if (this.senderKeys) { this.senderKeys.clear(); } - if (this.pendingSenderKeys) { - this.pendingSenderKeys.clear(); + if (this.#pendingSenderKeys) { + this.#pendingSenderKeys.clear(); } await DataWriter.removeAllSenderKeys(); }); @@ -1030,7 +1020,7 @@ export class SignalProtocolStore extends EventEmitter { const waitStart = Date.now(); return this.withZone(zone, 'enqueueSessionJob', async () => { - const queue = this._getSessionQueue(qualifiedAddress); + const queue = this.#_getSessionQueue(qualifiedAddress); const waitTime = Date.now() - waitStart; log.info( @@ -1048,7 +1038,7 @@ export class SignalProtocolStore extends EventEmitter { }); } - private _createSessionQueue(): PQueue { + #_createSessionQueue(): PQueue { return new PQueue({ concurrency: 1, timeout: MINUTE * 30, @@ -1056,20 +1046,20 @@ export class SignalProtocolStore extends EventEmitter { }); } - private _getSessionQueue(id: QualifiedAddress): PQueue { + #_getSessionQueue(id: QualifiedAddress): PQueue { const cachedQueue = this.sessionQueues.get(id.toString()); if (cachedQueue) { return cachedQueue; } - const freshQueue = this._createSessionQueue(); + const freshQueue = this.#_createSessionQueue(); this.sessionQueues.set(id.toString(), freshQueue); return freshQueue; } // Identity Queue - private _createIdentityQueue(): PQueue { + #_createIdentityQueue(): PQueue { return new PQueue({ concurrency: 1, timeout: MINUTE * 30, @@ -1077,7 +1067,7 @@ export class SignalProtocolStore extends EventEmitter { }); } - private _runOnIdentityQueue( + #_runOnIdentityQueue( serviceId: ServiceIdString, zone: Zone, name: string, @@ -1085,12 +1075,12 @@ export class SignalProtocolStore extends EventEmitter { ): Promise { let queue: PQueue; - const cachedQueue = this.identityQueues.get(serviceId); + const cachedQueue = this.#identityQueues.get(serviceId); if (cachedQueue) { queue = cachedQueue; } else { - queue = this._createIdentityQueue(); - this.identityQueues.set(serviceId, queue); + queue = this.#_createIdentityQueue(); + this.#identityQueues.set(serviceId, queue); } // We run the identity queue task in zone because `saveIdentity` needs to @@ -1124,10 +1114,10 @@ export class SignalProtocolStore extends EventEmitter { const debugName = `withZone(${zone.name}:${name})`; // Allow re-entering from LibSignalStores - if (this.currentZone && this.currentZone !== zone) { + if (this.#currentZone && this.#currentZone !== zone) { const start = Date.now(); - log.info(`${debugName}: locked by ${this.currentZone.name}, waiting`); + log.info(`${debugName}: locked by ${this.#currentZone.name}, waiting`); return new Promise((resolve, reject) => { const callback = async () => { @@ -1143,33 +1133,35 @@ export class SignalProtocolStore extends EventEmitter { } }; - this.zoneQueue.push({ zone, callback }); + this.#zoneQueue.push({ zone, callback }); }); } - this.enterZone(zone, name); + this.#enterZone(zone, name); let result: T; try { result = await body(); } catch (error) { - if (this.isInTopLevelZone()) { - await this.revertZoneChanges(name, error); + if (this.#isInTopLevelZone()) { + await this.#revertZoneChanges(name, error); } - this.leaveZone(zone); + this.#leaveZone(zone); throw error; } - if (this.isInTopLevelZone()) { - await this.commitZoneChanges(name); + if (this.#isInTopLevelZone()) { + await this.#commitZoneChanges(name); } - this.leaveZone(zone); + this.#leaveZone(zone); return result; } - private async commitZoneChanges(name: string): Promise { - const { pendingSenderKeys, pendingSessions, pendingUnprocessed } = this; + async #commitZoneChanges(name: string): Promise { + const pendingUnprocessed = this.#pendingUnprocessed; + const pendingSenderKeys = this.#pendingSenderKeys; + const pendingSessions = this.#pendingSessions; if ( pendingSenderKeys.size === 0 && @@ -1186,9 +1178,9 @@ export class SignalProtocolStore extends EventEmitter { `pending unprocessed ${pendingUnprocessed.size}` ); - this.pendingSenderKeys = new Map(); - this.pendingSessions = new Map(); - this.pendingUnprocessed = new Map(); + this.#pendingSenderKeys = new Map(); + this.#pendingSessions = new Map(); + this.#pendingUnprocessed = new Map(); // Commit both sender keys, sessions and unprocessed in the same database transaction // to unroll both on error. @@ -1223,28 +1215,28 @@ export class SignalProtocolStore extends EventEmitter { }); } - private async revertZoneChanges(name: string, error: Error): Promise { + async #revertZoneChanges(name: string, error: Error): Promise { log.info( `revertZoneChanges(${name}): ` + - `pending sender keys size ${this.pendingSenderKeys.size}, ` + - `pending sessions size ${this.pendingSessions.size}, ` + - `pending unprocessed size ${this.pendingUnprocessed.size}`, + `pending sender keys size ${this.#pendingSenderKeys.size}, ` + + `pending sessions size ${this.#pendingSessions.size}, ` + + `pending unprocessed size ${this.#pendingUnprocessed.size}`, Errors.toLogFormat(error) ); - this.pendingSenderKeys.clear(); - this.pendingSessions.clear(); - this.pendingUnprocessed.clear(); + this.#pendingSenderKeys.clear(); + this.#pendingSessions.clear(); + this.#pendingUnprocessed.clear(); } - private isInTopLevelZone(): boolean { - return this.currentZoneDepth === 1; + #isInTopLevelZone(): boolean { + return this.#currentZoneDepth === 1; } - private enterZone(zone: Zone, name: string): void { - this.currentZoneDepth += 1; - if (this.currentZoneDepth === 1) { - assertDev(this.currentZone === undefined, 'Should not be in the zone'); - this.currentZone = zone; + #enterZone(zone: Zone, name: string): void { + this.#currentZoneDepth += 1; + if (this.#currentZoneDepth === 1) { + assertDev(this.#currentZone === undefined, 'Should not be in the zone'); + this.#currentZone = zone; if (zone !== GLOBAL_ZONE) { log.info(`SignalProtocolStore.enterZone(${zone.name}:${name})`); @@ -1252,19 +1244,19 @@ export class SignalProtocolStore extends EventEmitter { } } - private leaveZone(zone: Zone): void { - assertDev(this.currentZone === zone, 'Should be in the correct zone'); + #leaveZone(zone: Zone): void { + assertDev(this.#currentZone === zone, 'Should be in the correct zone'); - this.currentZoneDepth -= 1; + this.#currentZoneDepth -= 1; assertDev( - this.currentZoneDepth >= 0, + this.#currentZoneDepth >= 0, 'Unmatched number of leaveZone calls' ); // Since we allow re-entering zones we might actually be in two overlapping // async calls. Leave the zone and yield to another one only if there are // no active zone users anymore. - if (this.currentZoneDepth !== 0) { + if (this.#currentZoneDepth !== 0) { return; } @@ -1272,17 +1264,17 @@ export class SignalProtocolStore extends EventEmitter { log.info(`SignalProtocolStore.leaveZone(${zone.name})`); } - this.currentZone = undefined; + this.#currentZone = undefined; - const next = this.zoneQueue.shift(); + const next = this.#zoneQueue.shift(); if (!next) { return; } const toEnter = [next]; - while (this.zoneQueue[0]?.zone === next.zone) { - const elem = this.zoneQueue.shift(); + while (this.#zoneQueue[0]?.zone === next.zone) { + const elem = this.#zoneQueue.shift(); assertDev(elem, 'Zone element should be present'); toEnter.push(elem); @@ -1313,8 +1305,8 @@ export class SignalProtocolStore extends EventEmitter { const id = qualifiedAddress.toString(); try { - const map = this.pendingSessions.has(id) - ? this.pendingSessions + const map = this.#pendingSessions.has(id) + ? this.#pendingSessions : this.sessions; const entry = map.get(id); @@ -1399,13 +1391,13 @@ export class SignalProtocolStore extends EventEmitter { item: record, }; - assertDev(this.currentZone, 'Must run in the zone'); + assertDev(this.#currentZone, 'Must run in the zone'); - this.pendingSessions.set(id, newSession); + this.#pendingSessions.set(id, newSession); // Current zone doesn't support pending sessions - commit immediately if (!zone.supportsPendingSessions()) { - await this.commitZoneChanges('storeSession'); + await this.#commitZoneChanges('storeSession'); } } catch (error) { const errorString = Errors.toLogFormat(error); @@ -1421,7 +1413,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('getOpenDevices: this.sessions not yet cached!'); } - return this._getAllSessions().some( + return this.#_getAllSessions().some( ({ fromDB }) => fromDB.serviceId === serviceId ); }); @@ -1446,7 +1438,7 @@ export class SignalProtocolStore extends EventEmitter { try { const serviceIdSet = new Set(serviceIds); - const allSessions = this._getAllSessions(); + const allSessions = this.#_getAllSessions(); const entries = allSessions.filter( ({ fromDB }) => fromDB.ourServiceId === ourServiceId && @@ -1537,7 +1529,7 @@ export class SignalProtocolStore extends EventEmitter { try { await DataWriter.removeSessionById(id); this.sessions.delete(id); - this.pendingSessions.delete(id); + this.#pendingSessions.delete(id); } catch (e) { log.error(`removeSession: Failed to delete session for ${id}`); } @@ -1578,7 +1570,7 @@ export class SignalProtocolStore extends EventEmitter { const entry = entries[i]; if (entry.fromDB.conversationId === id) { this.sessions.delete(entry.fromDB.id); - this.pendingSessions.delete(entry.fromDB.id); + this.#pendingSessions.delete(entry.fromDB.id); } } @@ -1603,7 +1595,7 @@ export class SignalProtocolStore extends EventEmitter { const entry = entries[i]; if (entry.fromDB.serviceId === serviceId) { this.sessions.delete(entry.fromDB.id); - this.pendingSessions.delete(entry.fromDB.id); + this.#pendingSessions.delete(entry.fromDB.id); } } @@ -1611,7 +1603,7 @@ export class SignalProtocolStore extends EventEmitter { }); } - private async _archiveSession(entry?: SessionCacheEntry, zone?: Zone) { + async #_archiveSession(entry?: SessionCacheEntry, zone?: Zone) { if (!entry) { return; } @@ -1646,9 +1638,9 @@ export class SignalProtocolStore extends EventEmitter { log.info(`archiveSession: session for ${id}`); - const entry = this.pendingSessions.get(id) || this.sessions.get(id); + const entry = this.#pendingSessions.get(id) || this.sessions.get(id); - await this._archiveSession(entry); + await this.#_archiveSession(entry); }); } @@ -1670,7 +1662,7 @@ export class SignalProtocolStore extends EventEmitter { const { serviceId, deviceId } = encodedAddress; - const allEntries = this._getAllSessions(); + const allEntries = this.#_getAllSessions(); const entries = allEntries.filter( entry => entry.fromDB.serviceId === serviceId && @@ -1679,7 +1671,7 @@ export class SignalProtocolStore extends EventEmitter { await Promise.all( entries.map(async entry => { - await this._archiveSession(entry, zone); + await this.#_archiveSession(entry, zone); }) ); }); @@ -1693,14 +1685,14 @@ export class SignalProtocolStore extends EventEmitter { log.info('archiveAllSessions: archiving all sessions for', serviceId); - const allEntries = this._getAllSessions(); + const allEntries = this.#_getAllSessions(); const entries = allEntries.filter( entry => entry.fromDB.serviceId === serviceId ); await Promise.all( entries.map(async entry => { - await this._archiveSession(entry); + await this.#_archiveSession(entry); }) ); }); @@ -1711,7 +1703,7 @@ export class SignalProtocolStore extends EventEmitter { if (this.sessions) { this.sessions.clear(); } - this.pendingSessions.clear(); + this.#pendingSessions.clear(); const changes = await DataWriter.removeAllSessions(); log.info(`clearSessionStore: Removed ${changes} sessions`); }); @@ -1830,7 +1822,7 @@ export class SignalProtocolStore extends EventEmitter { `to ${newRecord.id}` ); - await this._saveIdentityKey(newRecord); + await this.#_saveIdentityKey(newRecord); this.identityKeys.delete(record.fromDB.id); const changes = await DataWriter.removeIdentityKeyById(record.fromDB.id); @@ -1931,7 +1923,7 @@ export class SignalProtocolStore extends EventEmitter { log.error('isTrustedForSending: Needs unverified approval!'); return false; } - if (this.isNonBlockingApprovalRequired(identityRecord)) { + if (this.#isNonBlockingApprovalRequired(identityRecord)) { log.error('isTrustedForSending: Needs non-blocking approval!'); return false; } @@ -1973,7 +1965,7 @@ export class SignalProtocolStore extends EventEmitter { return Bytes.toBase64(fingerprint); } - private async _saveIdentityKey(data: IdentityKeyType): Promise { + async #_saveIdentityKey(data: IdentityKeyType): Promise { if (!this.identityKeys) { throw new Error('_saveIdentityKey: this.identityKeys not yet cached!'); } @@ -2010,7 +2002,7 @@ export class SignalProtocolStore extends EventEmitter { nonblockingApproval = false; } - return this._runOnIdentityQueue( + return this.#_runOnIdentityQueue( encodedAddress.serviceId, zone, 'saveIdentity', @@ -2025,7 +2017,7 @@ export class SignalProtocolStore extends EventEmitter { if (!identityRecord || !identityRecord.publicKey) { // Lookup failed, or the current key was removed, so save this one. log.info(`${logId}: Saving new identity...`); - await this._saveIdentityKey({ + await this.#_saveIdentityKey({ id, publicKey, firstUse: true, @@ -2074,7 +2066,7 @@ export class SignalProtocolStore extends EventEmitter { verifiedStatus = VerifiedStatus.DEFAULT; } - await this._saveIdentityKey({ + await this.#_saveIdentityKey({ id, publicKey, firstUse: false, @@ -2106,11 +2098,11 @@ export class SignalProtocolStore extends EventEmitter { return true; } - if (this.isNonBlockingApprovalRequired(identityRecord)) { + if (this.#isNonBlockingApprovalRequired(identityRecord)) { log.info(`${logId}: Setting approval status...`); identityRecord.nonblockingApproval = nonblockingApproval; - await this._saveIdentityKey(identityRecord); + await this.#_saveIdentityKey(identityRecord); return false; } @@ -2121,9 +2113,7 @@ export class SignalProtocolStore extends EventEmitter { } // https://github.com/signalapp/Signal-Android/blob/fc3db538bcaa38dc149712a483d3032c9c1f3998/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalBaseIdentityKeyStore.java#L257 - private isNonBlockingApprovalRequired( - identityRecord: IdentityKeyType - ): boolean { + #isNonBlockingApprovalRequired(identityRecord: IdentityKeyType): boolean { return ( !identityRecord.firstUse && isMoreRecentThan(identityRecord.timestamp, TIMESTAMP_THRESHOLD) && @@ -2135,17 +2125,17 @@ export class SignalProtocolStore extends EventEmitter { serviceId: ServiceIdString, attributes: Partial ): Promise { - return this._runOnIdentityQueue( + return this.#_runOnIdentityQueue( serviceId, GLOBAL_ZONE, 'saveIdentityWithAttributes', async () => { - return this.saveIdentityWithAttributesOnQueue(serviceId, attributes); + return this.#saveIdentityWithAttributesOnQueue(serviceId, attributes); } ); } - private async saveIdentityWithAttributesOnQueue( + async #saveIdentityWithAttributesOnQueue( serviceId: ServiceIdString, attributes: Partial ): Promise { @@ -2172,7 +2162,7 @@ export class SignalProtocolStore extends EventEmitter { }; if (validateIdentityKey(updates)) { - await this._saveIdentityKey(updates); + await this.#_saveIdentityKey(updates); } } @@ -2187,7 +2177,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('setApproval: Invalid approval status'); } - return this._runOnIdentityQueue( + return this.#_runOnIdentityQueue( serviceId, GLOBAL_ZONE, 'setApproval', @@ -2199,7 +2189,7 @@ export class SignalProtocolStore extends EventEmitter { } identityRecord.nonblockingApproval = nonblockingApproval; - await this._saveIdentityKey(identityRecord); + await this.#_saveIdentityKey(identityRecord); } ); } @@ -2218,7 +2208,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('setVerified: Invalid verified status'); } - return this._runOnIdentityQueue( + return this.#_runOnIdentityQueue( serviceId, GLOBAL_ZONE, 'setVerified', @@ -2230,7 +2220,7 @@ export class SignalProtocolStore extends EventEmitter { } if (validateIdentityKey(identityRecord)) { - await this._saveIdentityKey({ + await this.#_saveIdentityKey({ ...identityRecord, ...extra, verified: verifiedStatus, @@ -2304,7 +2294,7 @@ export class SignalProtocolStore extends EventEmitter { `Invalid verified status: ${verifiedStatus}` ); - return this._runOnIdentityQueue( + return this.#_runOnIdentityQueue( serviceId, GLOBAL_ZONE, 'updateIdentityAfterSync', @@ -2319,7 +2309,7 @@ export class SignalProtocolStore extends EventEmitter { keyMatches && verifiedStatus === identityRecord?.verified; if (!keyMatches || !statusMatches) { - await this.saveIdentityWithAttributesOnQueue(serviceId, { + await this.#saveIdentityWithAttributesOnQueue(serviceId, { publicKey, verified: verifiedStatus, firstUse: !hadEntry, @@ -2440,11 +2430,11 @@ export class SignalProtocolStore extends EventEmitter { { zone = GLOBAL_ZONE }: SessionTransactionOptions = {} ): Promise { return this.withZone(zone, 'addUnprocessed', async () => { - this.pendingUnprocessed.set(data.id, data); + this.#pendingUnprocessed.set(data.id, data); // Current zone doesn't support pending unprocessed - commit immediately if (!zone.supportsPendingUnprocessed()) { - await this.commitZoneChanges('addUnprocessed'); + await this.#commitZoneChanges('addUnprocessed'); } }); } @@ -2455,11 +2445,11 @@ export class SignalProtocolStore extends EventEmitter { ): Promise { return this.withZone(zone, 'addMultipleUnprocessed', async () => { for (const elem of array) { - this.pendingUnprocessed.set(elem.id, elem); + this.#pendingUnprocessed.set(elem.id, elem); } // Current zone doesn't support pending unprocessed - commit immediately if (!zone.supportsPendingUnprocessed()) { - await this.commitZoneChanges('addMultipleUnprocessed'); + await this.#commitZoneChanges('addMultipleUnprocessed'); } }); } @@ -2505,8 +2495,8 @@ export class SignalProtocolStore extends EventEmitter { log.info(`SignalProtocolStore.removeOurOldPni(${oldPni})`); // Update caches - this.ourIdentityKeys.delete(oldPni); - this.ourRegistrationIds.delete(oldPni); + this.#ourIdentityKeys.delete(oldPni); + this.#ourRegistrationIds.delete(oldPni); const preKeyPrefix = `${oldPni}:`; if (this.preKeys) { @@ -2575,11 +2565,11 @@ export class SignalProtocolStore extends EventEmitter { const pniPrivateKey = identityKeyPair.privateKey.serialize(); // Update caches - this.ourIdentityKeys.set(pni, { + this.#ourIdentityKeys.set(pni, { pubKey: pniPublicKey, privKey: pniPrivateKey, }); - this.ourRegistrationIds.set(pni, registrationId); + this.#ourRegistrationIds.set(pni, registrationId); // Update database await Promise.all([ @@ -2670,8 +2660,8 @@ export class SignalProtocolStore extends EventEmitter { return undefined; } - if (this.cachedPniSignatureMessage?.pni === ourPni) { - return this.cachedPniSignatureMessage; + if (this.#cachedPniSignatureMessage?.pni === ourPni) { + return this.#cachedPniSignatureMessage; } const aciKeyPair = this.getIdentityKeyPair(ourAci); @@ -2690,12 +2680,12 @@ export class SignalProtocolStore extends EventEmitter { PrivateKey.deserialize(Buffer.from(pniKeyPair.privKey)) ); const aciPubKey = PublicKey.deserialize(Buffer.from(aciKeyPair.pubKey)); - this.cachedPniSignatureMessage = { + this.#cachedPniSignatureMessage = { pni: ourPni, signature: pniIdentity.signAlternateIdentity(aciPubKey), }; - return this.cachedPniSignatureMessage; + return this.#cachedPniSignatureMessage; } async verifyAlternateIdentity({ @@ -2725,20 +2715,20 @@ export class SignalProtocolStore extends EventEmitter { ); } - private _getAllSessions(): Array { + #_getAllSessions(): Array { const union = new Map(); this.sessions?.forEach((value, key) => { union.set(key, value); }); - this.pendingSessions.forEach((value, key) => { + this.#pendingSessions.forEach((value, key) => { union.set(key, value); }); return Array.from(union.values()); } - private emitLowKeys(ourServiceId: ServiceIdString, source: string) { + #emitLowKeys(ourServiceId: ServiceIdString, source: string) { const logId = `SignalProtocolStore.emitLowKeys/${source}:`; try { log.info(`${logId}: Emitting event`); diff --git a/ts/WebAudioRecorder.ts b/ts/WebAudioRecorder.ts index 9bce3f19898e..55324ec1ac25 100644 --- a/ts/WebAudioRecorder.ts +++ b/ts/WebAudioRecorder.ts @@ -20,12 +20,12 @@ type OptionsType = { }; export class WebAudioRecorder { - private buffer: Array; - private options: OptionsType; - private context: BaseAudioContext; - private input: GainNode; - private onComplete: (recorder: WebAudioRecorder, blob: Blob) => unknown; - private onError: (recorder: WebAudioRecorder, error: string) => unknown; + #buffer: Array; + #options: OptionsType; + #context: BaseAudioContext; + #input: GainNode; + #onComplete: (recorder: WebAudioRecorder, blob: Blob) => unknown; + #onError: (recorder: WebAudioRecorder, error: string) => unknown; private processor?: ScriptProcessorNode; public worker?: Worker; @@ -37,19 +37,19 @@ export class WebAudioRecorder { onError: (recorder: WebAudioRecorder, error: string) => unknown; } ) { - this.options = { + this.#options = { ...DEFAULT_OPTIONS, ...options, }; - this.context = sourceNode.context; - this.input = this.context.createGain(); - sourceNode.connect(this.input); - this.buffer = []; - this.initWorker(); + this.#context = sourceNode.context; + this.#input = this.#context.createGain(); + sourceNode.connect(this.#input); + this.#buffer = []; + this.#initWorker(); - this.onComplete = callbacks.onComplete; - this.onError = callbacks.onError; + this.#onComplete = callbacks.onComplete; + this.#onError = callbacks.onError; } isRecording(): boolean { @@ -62,21 +62,22 @@ export class WebAudioRecorder { return; } - const { buffer, worker } = this; - const { bufferSize, numChannels } = this.options; + const { worker } = this; + const buffer = this.#buffer; + const { bufferSize, numChannels } = this.#options; if (!worker) { this.error('startRecording: worker not initialized'); return; } - this.processor = this.context.createScriptProcessor( + this.processor = this.#context.createScriptProcessor( bufferSize, numChannels, numChannels ); - this.input.connect(this.processor); - this.processor.connect(this.context.destination); + this.#input.connect(this.processor); + this.processor.connect(this.#context.destination); this.processor.onaudioprocess = event => { // eslint-disable-next-line no-plusplus for (let ch = 0; ch < numChannels; ++ch) { @@ -101,7 +102,7 @@ export class WebAudioRecorder { return; } - this.input.disconnect(); + this.#input.disconnect(); this.processor.disconnect(); delete this.processor; this.worker.postMessage({ command: 'cancel' }); @@ -118,13 +119,13 @@ export class WebAudioRecorder { return; } - this.input.disconnect(); + this.#input.disconnect(); this.processor.disconnect(); delete this.processor; this.worker.postMessage({ command: 'finish' }); } - private initWorker(): void { + #initWorker(): void { if (this.worker != null) { this.worker.terminate(); } @@ -134,7 +135,7 @@ export class WebAudioRecorder { const { data } = event; switch (data.command) { case 'complete': - this.onComplete(this, data.blob); + this.#onComplete(this, data.blob); break; case 'error': this.error(data.message); @@ -146,14 +147,14 @@ export class WebAudioRecorder { this.worker.postMessage({ command: 'init', config: { - sampleRate: this.context.sampleRate, - numChannels: this.options.numChannels, + sampleRate: this.#context.sampleRate, + numChannels: this.#options.numChannels, }, - options: this.options, + options: this.#options, }); } error(message: string): void { - this.onError(this, `WebAudioRecorder.js: ${message}`); + this.#onError(this, `WebAudioRecorder.js: ${message}`); } } diff --git a/ts/badges/badgeImageFileDownloader.ts b/ts/badges/badgeImageFileDownloader.ts index 073f440c403b..19599c2380ef 100644 --- a/ts/badges/badgeImageFileDownloader.ts +++ b/ts/badges/badgeImageFileDownloader.ts @@ -15,12 +15,11 @@ enum BadgeDownloaderState { } class BadgeImageFileDownloader { - private state = BadgeDownloaderState.Idle; - - private queue = new PQueue({ concurrency: 3 }); + #state = BadgeDownloaderState.Idle; + #queue = new PQueue({ concurrency: 3 }); public async checkForFilesToDownload(): Promise { - switch (this.state) { + switch (this.#state) { case BadgeDownloaderState.CheckingWithAnotherCheckEnqueued: log.info( 'BadgeDownloader#checkForFilesToDownload: not enqueuing another check' @@ -30,10 +29,10 @@ class BadgeImageFileDownloader { log.info( 'BadgeDownloader#checkForFilesToDownload: enqueuing another check' ); - this.state = BadgeDownloaderState.CheckingWithAnotherCheckEnqueued; + this.#state = BadgeDownloaderState.CheckingWithAnotherCheckEnqueued; return; case BadgeDownloaderState.Idle: { - this.state = BadgeDownloaderState.Checking; + this.#state = BadgeDownloaderState.Checking; const urlsToDownload = getUrlsToDownload(); log.info( @@ -41,7 +40,7 @@ class BadgeImageFileDownloader { ); try { - await this.queue.addAll( + await this.#queue.addAll( urlsToDownload.map(url => () => downloadBadgeImageFile(url)) ); } catch (err: unknown) { @@ -53,8 +52,8 @@ class BadgeImageFileDownloader { // issue][0]. // // [0]: https://github.com/microsoft/TypeScript/issues/9998 - const previousState = this.state as BadgeDownloaderState; - this.state = BadgeDownloaderState.Idle; + const previousState = this.#state as BadgeDownloaderState; + this.#state = BadgeDownloaderState.Idle; if ( previousState === BadgeDownloaderState.CheckingWithAnotherCheckEnqueued @@ -64,7 +63,7 @@ class BadgeImageFileDownloader { return; } default: - throw missingCaseError(this.state); + throw missingCaseError(this.#state); } } } diff --git a/ts/challenge.ts b/ts/challenge.ts index 8322328fa0fc..122d2d035c68 100644 --- a/ts/challenge.ts +++ b/ts/challenge.ts @@ -123,37 +123,30 @@ export function getChallengeURL(type: 'chat' | 'registration'): string { // `ChallengeHandler` should be in memory at the same time because they could // overwrite each others storage data. export class ChallengeHandler { - private solving = 0; + #solving = 0; + #isLoaded = false; + #challengeToken: string | undefined; + #seq = 0; + #isOnline = false; + #challengeRateLimitRetryAt: undefined | number; + readonly #responseHandlers = new Map(); - private isLoaded = false; - - private challengeToken: string | undefined; - - private seq = 0; - - private isOnline = false; - - private challengeRateLimitRetryAt: undefined | number; - - private readonly responseHandlers = new Map(); - - private readonly registeredConversations = new Map< + readonly #registeredConversations = new Map< string, RegisteredChallengeType >(); - private readonly startTimers = new Map(); - - private readonly pendingStarts = new Set(); + readonly #startTimers = new Map(); + readonly #pendingStarts = new Set(); constructor(private readonly options: Options) {} public async load(): Promise { - if (this.isLoaded) { + if (this.#isLoaded) { return; } - this.isLoaded = true; + this.#isLoaded = true; const challenges: ReadonlyArray = this.options.storage.get(STORAGE_KEY) || []; @@ -182,39 +175,39 @@ export class ChallengeHandler { } public async onOffline(): Promise { - this.isOnline = false; + this.#isOnline = false; log.info('challenge: offline'); } public async onOnline(): Promise { - this.isOnline = true; + this.#isOnline = true; - const pending = Array.from(this.pendingStarts.values()); - this.pendingStarts.clear(); + const pending = Array.from(this.#pendingStarts.values()); + this.#pendingStarts.clear(); log.info(`challenge: online, starting ${pending.length} queues`); // Start queues for challenges that matured while we were offline - await this.startAllQueues(); + await this.#startAllQueues(); } public maybeSolve({ conversationId, reason }: MaybeSolveOptionsType): void { - const challenge = this.registeredConversations.get(conversationId); + const challenge = this.#registeredConversations.get(conversationId); if (!challenge) { return; } - if (this.solving > 0) { + if (this.#solving > 0) { return; } - if (this.challengeRateLimitRetryAt) { + if (this.#challengeRateLimitRetryAt) { return; } if (challenge.token) { - drop(this.solve({ reason, token: challenge.token })); + drop(this.#solve({ reason, token: challenge.token })); } } @@ -224,18 +217,18 @@ export class ChallengeHandler { reason: string ): void { const waitTime = Math.max(0, retryAt - Date.now()); - const oldTimer = this.startTimers.get(conversationId); + const oldTimer = this.#startTimers.get(conversationId); if (oldTimer) { clearTimeoutIfNecessary(oldTimer); } - this.startTimers.set( + this.#startTimers.set( conversationId, setTimeout(() => { - this.startTimers.delete(conversationId); + this.#startTimers.delete(conversationId); - this.challengeRateLimitRetryAt = undefined; + this.#challengeRateLimitRetryAt = undefined; - drop(this.startQueue(conversationId)); + drop(this.#startQueue(conversationId)); }, waitTime) ); log.info( @@ -244,14 +237,14 @@ export class ChallengeHandler { } public forceWaitOnAll(retryAt: number): void { - this.challengeRateLimitRetryAt = retryAt; + this.#challengeRateLimitRetryAt = retryAt; - for (const conversationId of this.registeredConversations.keys()) { - const existing = this.registeredConversations.get(conversationId); + for (const conversationId of this.#registeredConversations.keys()) { + const existing = this.#registeredConversations.get(conversationId); if (!existing) { continue; } - this.registeredConversations.set(conversationId, { + this.#registeredConversations.set(conversationId, { ...existing, retryAt, }); @@ -271,20 +264,20 @@ export class ChallengeHandler { return; } - this.registeredConversations.set(conversationId, challenge); - await this.persist(); + this.#registeredConversations.set(conversationId, challenge); + await this.#persist(); // Challenge is already retryable - start the queue if (shouldStartQueue(challenge)) { log.info(`${logId}: starting conversation ${conversationId} immediately`); - await this.startQueue(conversationId); + await this.#startQueue(conversationId); return; } - if (this.challengeRateLimitRetryAt) { + if (this.#challengeRateLimitRetryAt) { this.scheduleRetry( conversationId, - this.challengeRateLimitRetryAt, + this.#challengeRateLimitRetryAt, 'register-challengeRateLimit' ); } else if (challenge.retryAt) { @@ -310,17 +303,17 @@ export class ChallengeHandler { } if (!challenge.silent) { - drop(this.solve({ token: challenge.token, reason })); + drop(this.#solve({ token: challenge.token, reason })); } } public onResponse(response: IPCResponse): void { - const handler = this.responseHandlers.get(response.seq); + const handler = this.#responseHandlers.get(response.seq); if (!handler) { return; } - this.responseHandlers.delete(response.seq); + this.#responseHandlers.delete(response.seq); handler.resolve(response.data); } @@ -331,72 +324,72 @@ export class ChallengeHandler { log.info( `challenge: unregistered conversation ${conversationId} via ${source}` ); - this.registeredConversations.delete(conversationId); - this.pendingStarts.delete(conversationId); + this.#registeredConversations.delete(conversationId); + this.#pendingStarts.delete(conversationId); - const timer = this.startTimers.get(conversationId); - this.startTimers.delete(conversationId); + const timer = this.#startTimers.get(conversationId); + this.#startTimers.delete(conversationId); clearTimeoutIfNecessary(timer); - await this.persist(); + await this.#persist(); } public async requestCaptcha({ reason, token = '', }: RequestCaptchaOptionsType): Promise { - const request: IPCRequest = { seq: this.seq, reason }; - this.seq += 1; + const request: IPCRequest = { seq: this.#seq, reason }; + this.#seq += 1; this.options.requestChallenge(request); const response = await new Promise((resolve, reject) => { - this.responseHandlers.set(request.seq, { token, resolve, reject }); + this.#responseHandlers.set(request.seq, { token, resolve, reject }); }); return response.captcha; } - private async persist(): Promise { + async #persist(): Promise { assertDev( - this.isLoaded, + this.#isLoaded, 'ChallengeHandler has to be loaded before persisting new data' ); await this.options.storage.put( STORAGE_KEY, - Array.from(this.registeredConversations.values()) + Array.from(this.#registeredConversations.values()) ); } public areAnyRegistered(): boolean { - return this.registeredConversations.size > 0; + return this.#registeredConversations.size > 0; } public isRegistered(conversationId: string): boolean { - return this.registeredConversations.has(conversationId); + return this.#registeredConversations.has(conversationId); } - private startAllQueues({ + #startAllQueues({ force = false, }: { force?: boolean; } = {}): void { log.info(`challenge: startAllQueues force=${force}`); - Array.from(this.registeredConversations.values()) + Array.from(this.#registeredConversations.values()) .filter(challenge => force || shouldStartQueue(challenge)) - .forEach(challenge => this.startQueue(challenge.conversationId)); + .forEach(challenge => this.#startQueue(challenge.conversationId)); } - private async startQueue(conversationId: string): Promise { - if (!this.isOnline) { - this.pendingStarts.add(conversationId); + async #startQueue(conversationId: string): Promise { + if (!this.#isOnline) { + this.#pendingStarts.add(conversationId); return; } await this.unregister(conversationId, 'startQueue'); - if (this.registeredConversations.size === 0) { + if (this.#registeredConversations.size === 0) { this.options.setChallengeStatus('idle'); } @@ -404,21 +397,21 @@ export class ChallengeHandler { this.options.startQueue(conversationId); } - private async solve({ reason, token }: SolveOptionsType): Promise { - this.solving += 1; + async #solve({ reason, token }: SolveOptionsType): Promise { + this.#solving += 1; this.options.setChallengeStatus('required'); - this.challengeToken = token; + this.#challengeToken = token; const captcha = await this.requestCaptcha({ reason, token }); // Another `.solve()` has completed earlier than us - if (this.challengeToken === undefined) { - this.solving -= 1; + if (this.#challengeToken === undefined) { + this.#solving -= 1; return; } - const lastToken = this.challengeToken; - this.challengeToken = undefined; + const lastToken = this.#challengeToken; + this.#challengeToken = undefined; this.options.setChallengeStatus('pending'); @@ -465,13 +458,13 @@ export class ChallengeHandler { this.forceWaitOnAll(retryAt); return; } finally { - this.solving -= 1; + this.#solving -= 1; } log.info(`challenge(${reason}): challenge success. force sending`); this.options.setChallengeStatus('idle'); this.options.onChallengeSolved(); - this.startAllQueues({ force: true }); + this.#startAllQueues({ force: true }); } } diff --git a/ts/components/conversation/ErrorBoundary.tsx b/ts/components/conversation/ErrorBoundary.tsx index dd4fe715dcd7..7e74d9a3dd5a 100644 --- a/ts/components/conversation/ErrorBoundary.tsx +++ b/ts/components/conversation/ErrorBoundary.tsx @@ -47,8 +47,8 @@ export class ErrorBoundary extends React.PureComponent { return (
@@ -62,24 +62,24 @@ export class ErrorBoundary extends React.PureComponent { ); } - private onClick(event: React.MouseEvent): void { + #onClick(event: React.MouseEvent): void { event.stopPropagation(); event.preventDefault(); - this.onAction(); + this.#onAction(); } - private onKeyDown(event: React.KeyboardEvent): void { + #onKeyDown(event: React.KeyboardEvent): void { if (event.key !== 'Enter' && event.key !== ' ') { return; } event.stopPropagation(); event.preventDefault(); - this.onAction(); + this.#onAction(); } - private onAction(): void { + #onAction(): void { const { showDebugLog } = this.props; showDebugLog(); } diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 4da5389774aa..5deb5ea7b8ed 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -410,11 +410,11 @@ export class Message extends React.PureComponent { public reactionsContainerRef: React.RefObject = React.createRef(); - private hasSelectedTextRef: React.MutableRefObject = { + #hasSelectedTextRef: React.MutableRefObject = { current: false, }; - private metadataRef: React.RefObject = React.createRef(); + #metadataRef: React.RefObject = React.createRef(); public reactionsContainerRefMerger = createRefMerger(); @@ -432,7 +432,7 @@ export class Message extends React.PureComponent { super(props); this.state = { - metadataWidth: this.guessMetadataWidth(), + metadataWidth: this.#guessMetadataWidth(), expiring: false, expired: false, @@ -447,7 +447,7 @@ export class Message extends React.PureComponent { showOutgoingGiftBadgeModal: false, hasDeleteForEveryoneTimerExpired: - this.getTimeRemainingForDeleteForEveryone() <= 0, + this.#getTimeRemainingForDeleteForEveryone() <= 0, }; } @@ -474,7 +474,7 @@ export class Message extends React.PureComponent { return state; } - private hasReactions(): boolean { + #hasReactions(): boolean { const { reactions } = this.props; return Boolean(reactions && reactions.length); } @@ -518,7 +518,7 @@ export class Message extends React.PureComponent { window.ConversationController?.onConvoMessageMount(conversationId); this.startTargetedTimer(); - this.startDeleteForEveryoneTimerIfApplicable(); + this.#startDeleteForEveryoneTimerIfApplicable(); this.startGiftBadgeInterval(); const { isTargeted } = this.props; @@ -543,7 +543,7 @@ export class Message extends React.PureComponent { checkForAccount(contact.firstNumber); } - document.addEventListener('selectionchange', this.handleSelectionChange); + document.addEventListener('selectionchange', this.#handleSelectionChange); } public override componentWillUnmount(): void { @@ -553,14 +553,17 @@ export class Message extends React.PureComponent { clearTimeoutIfNecessary(this.deleteForEveryoneTimeout); clearTimeoutIfNecessary(this.giftBadgeInterval); this.toggleReactionViewer(true); - document.removeEventListener('selectionchange', this.handleSelectionChange); + document.removeEventListener( + 'selectionchange', + this.#handleSelectionChange + ); } public override componentDidUpdate(prevProps: Readonly): void { const { isTargeted, status, timestamp } = this.props; this.startTargetedTimer(); - this.startDeleteForEveryoneTimerIfApplicable(); + this.#startDeleteForEveryoneTimerIfApplicable(); if (!prevProps.isTargeted && isTargeted) { this.setFocus(); @@ -586,7 +589,7 @@ export class Message extends React.PureComponent { } } - private getMetadataPlacement( + #getMetadataPlacement( { attachments, attachmentDroppedDueToSize, @@ -634,11 +637,11 @@ export class Message extends React.PureComponent { return MetadataPlacement.InlineWithText; } - if (this.canRenderStickerLikeEmoji()) { + if (this.#canRenderStickerLikeEmoji()) { return MetadataPlacement.Bottom; } - if (this.shouldShowJoinButton()) { + if (this.#shouldShowJoinButton()) { return MetadataPlacement.Bottom; } @@ -653,7 +656,7 @@ export class Message extends React.PureComponent { * This will probably guess wrong, but it's valuable to get close to the real value * because it can reduce layout jumpiness. */ - private guessMetadataWidth(): number { + #guessMetadataWidth(): number { const { direction, expirationLength, isSMS, status, isEditedMessage } = this.props; @@ -714,12 +717,12 @@ export class Message extends React.PureComponent { })); } - private getTimeRemainingForDeleteForEveryone(): number { + #getTimeRemainingForDeleteForEveryone(): number { const { timestamp } = this.props; return Math.max(timestamp - Date.now() + DAY, 0); } - private startDeleteForEveryoneTimerIfApplicable(): void { + #startDeleteForEveryoneTimerIfApplicable(): void { const { canDeleteForEveryone } = this.props; const { hasDeleteForEveryoneTimerExpired } = this.state; if ( @@ -733,7 +736,7 @@ export class Message extends React.PureComponent { this.deleteForEveryoneTimeout = setTimeout(() => { this.setState({ hasDeleteForEveryoneTimerExpired: true }); delete this.deleteForEveryoneTimeout; - }, this.getTimeRemainingForDeleteForEveryone()); + }, this.#getTimeRemainingForDeleteForEveryone()); } public checkExpired(): void { @@ -761,12 +764,12 @@ export class Message extends React.PureComponent { } } - private areLinksEnabled(): boolean { + #areLinksEnabled(): boolean { const { isMessageRequestAccepted, isBlocked } = this.props; return isMessageRequestAccepted && !isBlocked; } - private shouldRenderAuthor(): boolean { + #shouldRenderAuthor(): boolean { const { author, conversationType, direction, shouldCollapseAbove } = this.props; return Boolean( @@ -777,7 +780,7 @@ export class Message extends React.PureComponent { ); } - private canRenderStickerLikeEmoji(): boolean { + #canRenderStickerLikeEmoji(): boolean { const { attachments, bodyRanges, @@ -799,7 +802,7 @@ export class Message extends React.PureComponent { ); } - private updateMetadataWidth = (newMetadataWidth: number): void => { + #updateMetadataWidth = (newMetadataWidth: number): void => { this.setState(({ metadataWidth }) => ({ // We don't want text to jump around if the metadata shrinks, but we want to make // sure we have enough room. @@ -807,16 +810,16 @@ export class Message extends React.PureComponent { })); }; - private handleSelectionChange = () => { + #handleSelectionChange = () => { const selection = document.getSelection(); if (selection != null && !selection.isCollapsed) { - this.hasSelectedTextRef.current = true; + this.#hasSelectedTextRef.current = true; } }; - private renderMetadata(): ReactNode { + #renderMetadata(): ReactNode { let isInline: boolean; - const metadataPlacement = this.getMetadataPlacement(); + const metadataPlacement = this.#getMetadataPlacement(); switch (metadataPlacement) { case MetadataPlacement.NotRendered: case MetadataPlacement.RenderedByMessageAudioComponent: @@ -854,7 +857,7 @@ export class Message extends React.PureComponent { timestamp, } = this.props; - const isStickerLike = isSticker || this.canRenderStickerLikeEmoji(); + const isStickerLike = isSticker || this.#canRenderStickerLikeEmoji(); return ( { isShowingImage={this.isShowingImage()} isSticker={isStickerLike} isTapToViewExpired={isTapToViewExpired} - onWidthMeasured={isInline ? this.updateMetadataWidth : undefined} + onWidthMeasured={isInline ? this.#updateMetadataWidth : undefined} pushPanelForConversation={pushPanelForConversation} - ref={this.metadataRef} + ref={this.#metadataRef} retryMessageSend={retryMessageSend} showEditHistoryModal={showEditHistoryModal} status={status} @@ -886,7 +889,7 @@ export class Message extends React.PureComponent { ); } - private renderAuthor(): ReactNode { + #renderAuthor(): ReactNode { const { author, contactNameColor, @@ -896,7 +899,7 @@ export class Message extends React.PureComponent { isTapToViewExpired, } = this.props; - if (!this.shouldRenderAuthor()) { + if (!this.#shouldRenderAuthor()) { return null; } @@ -951,7 +954,7 @@ export class Message extends React.PureComponent { const { imageBroken } = this.state; const collapseMetadata = - this.getMetadataPlacement() === MetadataPlacement.NotRendered; + this.#getMetadataPlacement() === MetadataPlacement.NotRendered; if (!attachments || !attachments[0]) { return null; @@ -960,7 +963,7 @@ export class Message extends React.PureComponent { // For attachments which aren't full-frame const withContentBelow = Boolean(text || attachmentDroppedDueToSize); - const withContentAbove = Boolean(quote) || this.shouldRenderAuthor(); + const withContentAbove = Boolean(quote) || this.#shouldRenderAuthor(); const displayImage = canDisplayImage(attachments); if (displayImage && !imageBroken) { @@ -1203,7 +1206,7 @@ export class Message extends React.PureComponent { ? i18n('icu:message--call-link-description') : undefined); - const isClickable = this.areLinksEnabled(); + const isClickable = this.#areLinksEnabled(); const className = classNames( 'module-message__link-preview', @@ -1371,7 +1374,7 @@ export class Message extends React.PureComponent { const maybeSpacer = text ? undefined - : this.getMetadataPlacement() === MetadataPlacement.InlineWithText && ( + : this.#getMetadataPlacement() === MetadataPlacement.InlineWithText && ( ); @@ -1456,12 +1459,12 @@ export class Message extends React.PureComponent { )} > {description} - {this.getMetadataPlacement() === + {this.#getMetadataPlacement() === MetadataPlacement.InlineWithText && ( )}
- {this.renderMetadata()} + {this.#renderMetadata()} ); @@ -1569,7 +1572,7 @@ export class Message extends React.PureComponent { {buttonContents} - {this.renderMetadata()} + {this.#renderMetadata()} {showOutgoingGiftBadgeModal ? ( { conversationType === 'group' && direction === 'incoming'; const withContentBelow = withCaption || - this.getMetadataPlacement() !== MetadataPlacement.NotRendered; + this.#getMetadataPlacement() !== MetadataPlacement.NotRendered; const otherContent = (contact && contact.firstNumber && contact.serviceId) || withCaption; @@ -1833,7 +1836,7 @@ export class Message extends React.PureComponent { ); } - private renderAvatar(): ReactNode { + #renderAvatar(): ReactNode { const { author, conversationId, @@ -1854,7 +1857,7 @@ export class Message extends React.PureComponent {
{shouldCollapseBelow ? ( @@ -1887,7 +1890,7 @@ export class Message extends React.PureComponent { ); } - private getContents(): string | undefined { + #getContents(): string | undefined { const { deletedForEveryone, direction, i18n, status, text } = this.props; if (deletedForEveryone) { @@ -1920,7 +1923,7 @@ export class Message extends React.PureComponent { } = this.props; const { metadataWidth } = this.state; - const contents = this.getContents(); + const contents = this.#getContents(); if (!contents) { return null; @@ -1950,10 +1953,10 @@ export class Message extends React.PureComponent { const range = window.getSelection()?.getRangeAt(0); if ( clickCount === 3 && - this.metadataRef.current && - range?.intersectsNode(this.metadataRef.current) + this.#metadataRef.current && + range?.intersectsNode(this.#metadataRef.current) ) { - range.setEndBefore(this.metadataRef.current); + range.setEndBefore(this.#metadataRef.current); } }} onDoubleClick={(event: React.MouseEvent) => { @@ -1965,7 +1968,7 @@ export class Message extends React.PureComponent { { text={contents || ''} textAttachment={textAttachment} /> - {this.getMetadataPlacement() === MetadataPlacement.InlineWithText && ( + {this.#getMetadataPlacement() === MetadataPlacement.InlineWithText && ( )}
); } - private shouldShowJoinButton(): boolean { + #shouldShowJoinButton(): boolean { const { previews } = this.props; if (previews?.length !== 1) { @@ -2006,10 +2009,10 @@ export class Message extends React.PureComponent { return Boolean(onlyPreview.isCallLink); } - private renderAction(): JSX.Element | null { + #renderAction(): JSX.Element | null { const { direction, activeCallConversationId, i18n, previews } = this.props; - if (this.shouldShowJoinButton()) { + if (this.#shouldShowJoinButton()) { const firstPreview = previews[0]; const inAnotherCall = Boolean( activeCallConversationId && @@ -2044,7 +2047,7 @@ export class Message extends React.PureComponent { return null; } - private renderError(): ReactNode { + #renderError(): ReactNode { const { status, direction } = this.props; if ( @@ -2205,7 +2208,7 @@ export class Message extends React.PureComponent { } = this.props; const collapseMetadata = - this.getMetadataPlacement() === MetadataPlacement.NotRendered; + this.#getMetadataPlacement() === MetadataPlacement.NotRendered; const withContentBelow = !collapseMetadata; const withContentAbove = !collapseMetadata && @@ -2243,7 +2246,7 @@ export class Message extends React.PureComponent { ); } - private popperPreventOverflowModifier(): Partial { + #popperPreventOverflowModifier(): Partial { const { containerElementRef } = this.props; return { name: 'preventOverflow', @@ -2302,7 +2305,7 @@ export class Message extends React.PureComponent { public renderReactions(outgoing: boolean): JSX.Element | null { const { getPreferredBadge, reactions = [], i18n, theme } = this.props; - if (!this.hasReactions()) { + if (!this.#hasReactions()) { return null; } @@ -2465,7 +2468,7 @@ export class Message extends React.PureComponent { {({ ref, style }) => ( { return ( <> {this.renderText()} - {this.renderMetadata()} + {this.#renderMetadata()} ); } @@ -2508,7 +2511,7 @@ export class Message extends React.PureComponent { return ( <> {this.renderTapToView()} - {this.renderMetadata()} + {this.#renderMetadata()} ); } @@ -2523,8 +2526,8 @@ export class Message extends React.PureComponent { {this.renderPayment()} {this.renderEmbeddedContact()} {this.renderText()} - {this.renderAction()} - {this.renderMetadata()} + {this.#renderAction()} + {this.#renderMetadata()} {this.renderSendMessageButton()} ); @@ -2740,7 +2743,7 @@ export class Message extends React.PureComponent { const isAttachmentPending = this.isAttachmentPending(); const width = this.getWidth(); - const isEmojiOnly = this.canRenderStickerLikeEmoji(); + const isEmojiOnly = this.#canRenderStickerLikeEmoji(); const isStickerLike = isSticker || isEmojiOnly; // If it's a mostly-normal gray incoming text box, we don't want to darken it as much @@ -2773,7 +2776,7 @@ export class Message extends React.PureComponent { isTapToViewError ? 'module-message__container--with-tap-to-view-error' : null, - this.hasReactions() ? 'module-message__container--with-reactions' : null, + this.#hasReactions() ? 'module-message__container--with-reactions' : null, deletedForEveryone ? 'module-message__container--deleted-for-everyone' : null @@ -2806,7 +2809,7 @@ export class Message extends React.PureComponent { }} tabIndex={-1} > - {this.renderAuthor()} + {this.#renderAuthor()}
{this.renderContents()}
@@ -2890,13 +2893,13 @@ export class Message extends React.PureComponent { } else { wrapperProps = { onMouseDown: () => { - this.hasSelectedTextRef.current = false; + this.#hasSelectedTextRef.current = false; }, // We use `onClickCapture` here and preven default/stop propagation to // prevent other click handlers from firing. onClickCapture: event => { if (isMacOS ? event.metaKey : event.ctrlKey) { - if (this.hasSelectedTextRef.current) { + if (this.#hasSelectedTextRef.current) { return; } @@ -2964,8 +2967,8 @@ export class Message extends React.PureComponent { // eslint-disable-next-line react/no-unknown-property inert={isSelectMode ? '' : undefined} > - {this.renderError()} - {this.renderAvatar()} + {this.#renderError()} + {this.#renderAvatar()} {this.renderContainer()} {renderMenu?.()} diff --git a/ts/components/conversation/Timeline.tsx b/ts/components/conversation/Timeline.tsx index 7efef17f9ff4..0ab49beebaf4 100644 --- a/ts/components/conversation/Timeline.tsx +++ b/ts/components/conversation/Timeline.tsx @@ -189,18 +189,18 @@ export class Timeline extends React.Component< StateType, SnapshotType > { - private readonly containerRef = React.createRef(); - private readonly messagesRef = React.createRef(); - private readonly atBottomDetectorRef = React.createRef(); - private readonly lastSeenIndicatorRef = React.createRef(); - private intersectionObserver?: IntersectionObserver; + readonly #containerRef = React.createRef(); + readonly #messagesRef = React.createRef(); + readonly #atBottomDetectorRef = React.createRef(); + readonly #lastSeenIndicatorRef = React.createRef(); + #intersectionObserver?: IntersectionObserver; // This is a best guess. It will likely be overridden when the timeline is measured. - private maxVisibleRows = Math.ceil(window.innerHeight / MIN_ROW_HEIGHT); + #maxVisibleRows = Math.ceil(window.innerHeight / MIN_ROW_HEIGHT); - private hasRecentlyScrolledTimeout?: NodeJS.Timeout; - private delayedPeekTimeout?: NodeJS.Timeout; - private peekInterval?: NodeJS.Timeout; + #hasRecentlyScrolledTimeout?: NodeJS.Timeout; + #delayedPeekTimeout?: NodeJS.Timeout; + #peekInterval?: NodeJS.Timeout; // eslint-disable-next-line react/state-in-constructor override state: StateType = { @@ -213,31 +213,28 @@ export class Timeline extends React.Component< widthBreakpoint: WidthBreakpoint.Wide, }; - private onScrollLockChange = (): void => { + #onScrollLockChange = (): void => { this.setState({ - scrollLocked: this.scrollerLock.isLocked(), + scrollLocked: this.#scrollerLock.isLocked(), }); }; - private scrollerLock = createScrollerLock( - 'Timeline', - this.onScrollLockChange - ); + #scrollerLock = createScrollerLock('Timeline', this.#onScrollLockChange); - private onScroll = (event: UIEvent): void => { + #onScroll = (event: UIEvent): void => { // When content is removed from the viewport, such as typing indicators leaving // or messages being edited smaller or deleted, scroll events are generated and // they are marked as user-generated (isTrusted === true). Actual user generated // scroll events with movement must scroll a nonbottom state at some point. - const isAtBottom = this.isAtBottom(); + const isAtBottom = this.#isAtBottom(); if (event.isTrusted && !isAtBottom) { - this.scrollerLock.onUserInterrupt('onScroll'); + this.#scrollerLock.onUserInterrupt('onScroll'); } // hasRecentlyScrolled is used to show the floating date header, which we only // want to show when scrolling through history or on conversation first open. // Checking bottom prevents new messages and typing from showing the header. - if (!this.state.hasRecentlyScrolled && this.isAtBottom()) { + if (!this.state.hasRecentlyScrolled && this.#isAtBottom()) { return; } @@ -248,24 +245,24 @@ export class Timeline extends React.Component< // [0]: https://github.com/facebook/react/blob/29b7b775f2ecf878eaf605be959d959030598b07/packages/react-reconciler/src/ReactUpdateQueue.js#L401-L404 oldState.hasRecentlyScrolled ? null : { hasRecentlyScrolled: true } ); - clearTimeoutIfNecessary(this.hasRecentlyScrolledTimeout); - this.hasRecentlyScrolledTimeout = setTimeout(() => { + clearTimeoutIfNecessary(this.#hasRecentlyScrolledTimeout); + this.#hasRecentlyScrolledTimeout = setTimeout(() => { this.setState({ hasRecentlyScrolled: false }); }, 3000); }; - private scrollToItemIndex(itemIndex: number): void { - if (this.scrollerLock.isLocked()) { + #scrollToItemIndex(itemIndex: number): void { + if (this.#scrollerLock.isLocked()) { return; } - this.messagesRef.current + this.#messagesRef.current ?.querySelector(`[data-item-index="${itemIndex}"]`) ?.scrollIntoViewIfNeeded(); } - private scrollToBottom = (setFocus?: boolean): void => { - if (this.scrollerLock.isLocked()) { + #scrollToBottom = (setFocus?: boolean): void => { + if (this.#scrollerLock.isLocked()) { return; } @@ -276,20 +273,20 @@ export class Timeline extends React.Component< const lastMessageId = items[lastIndex]; targetMessage(lastMessageId, id); } else { - const containerEl = this.containerRef.current; + const containerEl = this.#containerRef.current; if (containerEl) { scrollToBottom(containerEl); } } }; - private onClickScrollDownButton = (): void => { - this.scrollerLock.onUserInterrupt('onClickScrollDownButton'); - this.scrollDown(false); + #onClickScrollDownButton = (): void => { + this.#scrollerLock.onUserInterrupt('onClickScrollDownButton'); + this.#scrollDown(false); }; - private scrollDown = (setFocus?: boolean): void => { - if (this.scrollerLock.isLocked()) { + #scrollDown = (setFocus?: boolean): void => { + if (this.#scrollerLock.isLocked()) { return; } @@ -309,7 +306,7 @@ export class Timeline extends React.Component< } if (messageLoadingState) { - this.scrollToBottom(setFocus); + this.#scrollToBottom(setFocus); return; } @@ -323,10 +320,10 @@ export class Timeline extends React.Component< const messageId = items[oldestUnseenIndex]; targetMessage(messageId, id); } else { - this.scrollToItemIndex(oldestUnseenIndex); + this.#scrollToItemIndex(oldestUnseenIndex); } } else if (haveNewest) { - this.scrollToBottom(setFocus); + this.#scrollToBottom(setFocus); } else { const lastId = last(items); if (lastId) { @@ -335,8 +332,8 @@ export class Timeline extends React.Component< } }; - private isAtBottom(): boolean { - const containerEl = this.containerRef.current; + #isAtBottom(): boolean { + const containerEl = this.#containerRef.current; if (!containerEl) { return false; } @@ -346,10 +343,10 @@ export class Timeline extends React.Component< return isScrolledNearBottom || !hasScrollbars; } - private updateIntersectionObserver(): void { - const containerEl = this.containerRef.current; - const messagesEl = this.messagesRef.current; - const atBottomDetectorEl = this.atBottomDetectorRef.current; + #updateIntersectionObserver(): void { + const containerEl = this.#containerRef.current; + const messagesEl = this.#messagesRef.current; + const atBottomDetectorEl = this.#atBottomDetectorRef.current; if (!containerEl || !messagesEl || !atBottomDetectorEl) { return; } @@ -368,7 +365,7 @@ export class Timeline extends React.Component< // We re-initialize the `IntersectionObserver`. We don't want stale references to old // props, and we care about the order of `IntersectionObserverEntry`s. (We could do // this another way, but this approach works.) - this.intersectionObserver?.disconnect(); + this.#intersectionObserver?.disconnect(); const intersectionRatios = new Map(); @@ -445,7 +442,7 @@ export class Timeline extends React.Component< setIsNearBottom(id, newIsNearBottom); if (newestBottomVisibleMessageId) { - this.markNewestBottomVisibleMessageRead(); + this.#markNewestBottomVisibleMessageRead(); const rowIndex = getRowIndexFromElement(newestBottomVisible); const maxRowIndex = items.length - 1; @@ -471,15 +468,15 @@ export class Timeline extends React.Component< } }; - this.intersectionObserver = new IntersectionObserver( + this.#intersectionObserver = new IntersectionObserver( (entries, observer) => { assertDev( - this.intersectionObserver === observer, + this.#intersectionObserver === observer, 'observer.disconnect() should prevent callbacks from firing' ); // Observer was updated from under us - if (this.intersectionObserver !== observer) { + if (this.#intersectionObserver !== observer) { return; } @@ -493,13 +490,13 @@ export class Timeline extends React.Component< for (const child of messagesEl.children) { if ((child as HTMLElement).dataset.messageId) { - this.intersectionObserver.observe(child); + this.#intersectionObserver.observe(child); } } - this.intersectionObserver.observe(atBottomDetectorEl); + this.#intersectionObserver.observe(atBottomDetectorEl); } - private markNewestBottomVisibleMessageRead = throttle((): void => { + #markNewestBottomVisibleMessageRead = throttle((): void => { const { id, markMessageRead } = this.props; const { newestBottomVisibleMessageId } = this.state; if (newestBottomVisibleMessageId) { @@ -507,36 +504,37 @@ export class Timeline extends React.Component< } }, 500); - private setupGroupCallPeekTimeouts(): void { - this.cleanupGroupCallPeekTimeouts(); + #setupGroupCallPeekTimeouts(): void { + this.#cleanupGroupCallPeekTimeouts(); - this.delayedPeekTimeout = setTimeout(() => { + this.#delayedPeekTimeout = setTimeout(() => { const { id, peekGroupCallForTheFirstTime } = this.props; - this.delayedPeekTimeout = undefined; + this.#delayedPeekTimeout = undefined; peekGroupCallForTheFirstTime(id); }, 500); - this.peekInterval = setInterval(() => { + this.#peekInterval = setInterval(() => { const { id, peekGroupCallIfItHasMembers } = this.props; peekGroupCallIfItHasMembers(id); }, MINUTE); } - private cleanupGroupCallPeekTimeouts(): void { - const { delayedPeekTimeout, peekInterval } = this; + #cleanupGroupCallPeekTimeouts(): void { + const peekInterval = this.#peekInterval; + const delayedPeekTimeout = this.#delayedPeekTimeout; clearTimeoutIfNecessary(delayedPeekTimeout); - this.delayedPeekTimeout = undefined; + this.#delayedPeekTimeout = undefined; if (peekInterval) { clearInterval(peekInterval); - this.peekInterval = undefined; + this.#peekInterval = undefined; } } public override componentDidMount(): void { - const containerEl = this.containerRef.current; - const messagesEl = this.messagesRef.current; + const containerEl = this.#containerRef.current; + const messagesEl = this.#messagesRef.current; const { conversationType, isConversationSelected } = this.props; strictAssert( // We don't render anything unless the conversation is selected @@ -544,31 +542,31 @@ export class Timeline extends React.Component< ' mounted without some refs' ); - this.updateIntersectionObserver(); + this.#updateIntersectionObserver(); window.SignalContext.activeWindowService.registerForActive( - this.markNewestBottomVisibleMessageRead + this.#markNewestBottomVisibleMessageRead ); if (conversationType === 'group') { - this.setupGroupCallPeekTimeouts(); + this.#setupGroupCallPeekTimeouts(); } } public override componentWillUnmount(): void { window.SignalContext.activeWindowService.unregisterForActive( - this.markNewestBottomVisibleMessageRead + this.#markNewestBottomVisibleMessageRead ); - this.intersectionObserver?.disconnect(); - this.cleanupGroupCallPeekTimeouts(); + this.#intersectionObserver?.disconnect(); + this.#cleanupGroupCallPeekTimeouts(); this.props.updateVisibleMessages?.([]); } public override getSnapshotBeforeUpdate( prevProps: Readonly ): SnapshotType { - const containerEl = this.containerRef.current; + const containerEl = this.#containerRef.current; if (!containerEl) { return null; } @@ -579,7 +577,7 @@ export class Timeline extends React.Component< const scrollAnchor = getScrollAnchorBeforeUpdate( prevProps, props, - this.isAtBottom() + this.#isAtBottom() ); switch (scrollAnchor) { @@ -627,10 +625,10 @@ export class Timeline extends React.Component< messageLoadingState, } = this.props; - const containerEl = this.containerRef.current; - if (!this.scrollerLock.isLocked() && containerEl && snapshot) { + const containerEl = this.#containerRef.current; + if (!this.#scrollerLock.isLocked() && containerEl && snapshot) { if (snapshot === scrollToUnreadIndicator) { - const lastSeenIndicatorEl = this.lastSeenIndicatorRef.current; + const lastSeenIndicatorEl = this.#lastSeenIndicatorRef.current; if (lastSeenIndicatorEl) { lastSeenIndicatorEl.scrollIntoView(); } else { @@ -641,7 +639,7 @@ export class Timeline extends React.Component< ); } } else if ('scrollToIndex' in snapshot) { - this.scrollToItemIndex(snapshot.scrollToIndex); + this.#scrollToItemIndex(snapshot.scrollToIndex); } else if ('scrollTop' in snapshot) { containerEl.scrollTop = snapshot.scrollTop; } else { @@ -657,12 +655,12 @@ export class Timeline extends React.Component< oldItems.at(-1) !== newItems.at(-1); if (haveItemsChanged) { - this.updateIntersectionObserver(); + this.#updateIntersectionObserver(); // This condition is somewhat arbitrary. - const numberToKeepAtBottom = this.maxVisibleRows * 2; + const numberToKeepAtBottom = this.#maxVisibleRows * 2; const shouldDiscardOlderMessages: boolean = - this.isAtBottom() && newItems.length > numberToKeepAtBottom; + this.#isAtBottom() && newItems.length > numberToKeepAtBottom; if (shouldDiscardOlderMessages) { discardMessages({ conversationId: id, @@ -676,9 +674,9 @@ export class Timeline extends React.Component< !messageLoadingState && previousMessageLoadingState ? previousMessageLoadingState : undefined; - const numberToKeepAtTop = this.maxVisibleRows * 5; + const numberToKeepAtTop = this.#maxVisibleRows * 5; const shouldDiscardNewerMessages: boolean = - !this.isAtBottom() && + !this.#isAtBottom() && loadingStateThatJustFinished === TimelineMessageLoadingState.LoadingOlderMessages && newItems.length > numberToKeepAtTop; @@ -691,18 +689,18 @@ export class Timeline extends React.Component< } } if (previousMessageChangeCounter !== messageChangeCounter) { - this.markNewestBottomVisibleMessageRead(); + this.#markNewestBottomVisibleMessageRead(); } if (previousConversationType !== conversationType) { - this.cleanupGroupCallPeekTimeouts(); + this.#cleanupGroupCallPeekTimeouts(); if (conversationType === 'group') { - this.setupGroupCallPeekTimeouts(); + this.#setupGroupCallPeekTimeouts(); } } } - private handleBlur = (event: React.FocusEvent): void => { + #handleBlur = (event: React.FocusEvent): void => { const { clearTargetedMessage } = this.props; const { currentTarget } = event; @@ -726,9 +724,7 @@ export class Timeline extends React.Component< }, 0); }; - private handleKeyDown = ( - event: React.KeyboardEvent - ): void => { + #handleKeyDown = (event: React.KeyboardEvent): void => { const { targetMessage, targetedMessageId, items, id } = this.props; const commandKey = get(window, 'platform') === 'darwin' && event.metaKey; const controlKey = get(window, 'platform') !== 'darwin' && event.ctrlKey; @@ -803,7 +799,7 @@ export class Timeline extends React.Component< } if (event.key === 'End' || (commandOrCtrl && event.key === 'ArrowDown')) { - this.scrollDown(true); + this.#scrollDown(true); event.preventDefault(); event.stopPropagation(); } @@ -939,7 +935,7 @@ export class Timeline extends React.Component< key="last seen indicator" count={totalUnseen} i18n={i18n} - ref={this.lastSeenIndicatorRef} + ref={this.#lastSeenIndicatorRef} /> ); } else if (oldestUnseenIndex === nextItemIndex) { @@ -964,7 +960,7 @@ export class Timeline extends React.Component< > {renderItem({ - containerElementRef: this.containerRef, + containerElementRef: this.#containerRef, containerWidthBreakpoint: widthBreakpoint, conversationId: id, isBlocked, @@ -1098,7 +1094,7 @@ export class Timeline extends React.Component< } return ( - + { const { isNearBottom } = this.props; @@ -1107,9 +1103,9 @@ export class Timeline extends React.Component< widthBreakpoint: getWidthBreakpoint(size.width), }); - this.maxVisibleRows = Math.ceil(size.height / MIN_ROW_HEIGHT); + this.#maxVisibleRows = Math.ceil(size.height / MIN_ROW_HEIGHT); - const containerEl = this.containerRef.current; + const containerEl = this.#containerRef.current; if (containerEl && isNearBottom) { scrollToBottom(containerEl); } @@ -1124,8 +1120,8 @@ export class Timeline extends React.Component< )} role="presentation" tabIndex={-1} - onBlur={this.handleBlur} - onKeyDown={this.handleKeyDown} + onBlur={this.#handleBlur} + onKeyDown={this.#handleKeyDown} ref={ref} > {headerElements} @@ -1134,8 +1130,8 @@ export class Timeline extends React.Component<
{haveOldest && ( @@ -1162,7 +1158,7 @@ export class Timeline extends React.Component<
@@ -1181,7 +1177,7 @@ export class Timeline extends React.Component<
diff --git a/ts/components/leftPane/LeftPaneArchiveHelper.tsx b/ts/components/leftPane/LeftPaneArchiveHelper.tsx index 32e5e06b78a1..c414da81991c 100644 --- a/ts/components/leftPane/LeftPaneArchiveHelper.tsx +++ b/ts/components/leftPane/LeftPaneArchiveHelper.tsx @@ -34,29 +34,24 @@ export type LeftPaneArchivePropsType = | (LeftPaneArchiveBasePropsType & LeftPaneSearchPropsType); export class LeftPaneArchiveHelper extends LeftPaneHelper { - private readonly archivedConversations: ReadonlyArray; - - private readonly isSearchingGlobally: boolean; - - private readonly searchConversation: undefined | ConversationType; - - private readonly searchTerm: string; - - private readonly searchHelper: undefined | LeftPaneSearchHelper; - - private readonly startSearchCounter: number; + readonly #archivedConversations: ReadonlyArray; + readonly #isSearchingGlobally: boolean; + readonly #searchConversation: undefined | ConversationType; + readonly #searchTerm: string; + readonly #searchHelper: undefined | LeftPaneSearchHelper; + readonly #startSearchCounter: number; constructor(props: Readonly) { super(); - this.archivedConversations = props.archivedConversations; - this.isSearchingGlobally = props.isSearchingGlobally; - this.searchConversation = props.searchConversation; - this.searchTerm = props.searchTerm; - this.startSearchCounter = props.startSearchCounter; + this.#archivedConversations = props.archivedConversations; + this.#isSearchingGlobally = props.isSearchingGlobally; + this.#searchConversation = props.searchConversation; + this.#searchTerm = props.searchTerm; + this.#startSearchCounter = props.startSearchCounter; if ('conversationResults' in props) { - this.searchHelper = new LeftPaneSearchHelper(props); + this.#searchHelper = new LeftPaneSearchHelper(props); } } @@ -100,7 +95,7 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper unknown; showConversation: ShowConversationType; }>): ReactChild | null { - if (!this.searchConversation) { + if (!this.#searchConversation) { return null; } @@ -111,11 +106,11 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper ); @@ -128,8 +123,8 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper): ReactChild | null { - if (this.searchHelper) { - return this.searchHelper.getPreRowsNode({ i18n }); + if (this.#searchHelper) { + return this.#searchHelper.getPreRowsNode({ i18n }); } return ( @@ -143,16 +138,16 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper conversation.id === selectedConversationId ); return result === -1 ? undefined : result; @@ -180,7 +175,8 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper): boolean { const hasSearchingChanged = - 'conversationResults' in old !== Boolean(this.searchHelper); + 'conversationResults' in old !== Boolean(this.#searchHelper); if (hasSearchingChanged) { return true; } - if ('conversationResults' in old && this.searchHelper) { - return this.searchHelper.shouldRecomputeRowHeights(old); + if ('conversationResults' in old && this.#searchHelper) { + return this.#searchHelper.shouldRecomputeRowHeights(old); } return false; @@ -251,7 +247,9 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper id === selectedConversationId) + this.#archivedConversations.some( + ({ id }) => id === selectedConversationId + ) ) { searchInConversation(selectedConversationId); diff --git a/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx b/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx index 24b7b8863550..9747db6d3d03 100644 --- a/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx +++ b/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx @@ -42,31 +42,19 @@ export type LeftPaneChooseGroupMembersPropsType = { }; export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper { - private readonly candidateContacts: ReadonlyArray; - - private readonly isPhoneNumberChecked: boolean; - - private readonly isUsernameChecked: boolean; - - private readonly isShowingMaximumGroupSizeModal: boolean; - - private readonly isShowingRecommendedGroupSizeModal: boolean; - - private readonly groupSizeRecommendedLimit: number; - - private readonly groupSizeHardLimit: number; - - private readonly searchTerm: string; - - private readonly phoneNumber: ParsedE164Type | undefined; - - private readonly username: string | undefined; - - private readonly selectedContacts: Array; - - private readonly selectedConversationIdsSet: Set; - - private readonly uuidFetchState: UUIDFetchStateType; + readonly #candidateContacts: ReadonlyArray; + readonly #isPhoneNumberChecked: boolean; + readonly #isUsernameChecked: boolean; + readonly #isShowingMaximumGroupSizeModal: boolean; + readonly #isShowingRecommendedGroupSizeModal: boolean; + readonly #groupSizeRecommendedLimit: number; + readonly #groupSizeHardLimit: number; + readonly #searchTerm: string; + readonly #phoneNumber: ParsedE164Type | undefined; + readonly #username: string | undefined; + readonly #selectedContacts: Array; + readonly #selectedConversationIdsSet: Set; + readonly #uuidFetchState: UUIDFetchStateType; constructor({ candidateContacts, @@ -84,27 +72,27 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper) { super(); - this.uuidFetchState = uuidFetchState; - this.groupSizeRecommendedLimit = groupSizeRecommendedLimit - 1; - this.groupSizeHardLimit = groupSizeHardLimit - 1; + this.#uuidFetchState = uuidFetchState; + this.#groupSizeRecommendedLimit = groupSizeRecommendedLimit - 1; + this.#groupSizeHardLimit = groupSizeHardLimit - 1; - this.candidateContacts = candidateContacts; - this.isShowingMaximumGroupSizeModal = isShowingMaximumGroupSizeModal; - this.isShowingRecommendedGroupSizeModal = + this.#candidateContacts = candidateContacts; + this.#isShowingMaximumGroupSizeModal = isShowingMaximumGroupSizeModal; + this.#isShowingRecommendedGroupSizeModal = isShowingRecommendedGroupSizeModal; - this.searchTerm = searchTerm; + this.#searchTerm = searchTerm; const isUsernameVisible = username !== undefined && username !== ourUsername && - this.candidateContacts.every(contact => contact.username !== username); + this.#candidateContacts.every(contact => contact.username !== username); if (isUsernameVisible) { - this.username = username; + this.#username = username; } - this.isUsernameChecked = selectedContacts.some( - contact => contact.username === this.username + this.#isUsernameChecked = selectedContacts.some( + contact => contact.username === this.#username ); const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode); @@ -114,22 +102,22 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper contact.e164 === e164); const isVisible = e164 !== ourE164 && - this.candidateContacts.every(contact => contact.e164 !== e164); + this.#candidateContacts.every(contact => contact.e164 !== e164); if (isVisible) { - this.phoneNumber = phoneNumber; + this.#phoneNumber = phoneNumber; } } else { - this.isPhoneNumberChecked = false; + this.#isPhoneNumberChecked = false; } - this.selectedContacts = selectedContacts; + this.#selectedContacts = selectedContacts; - this.selectedConversationIdsSet = new Set( + this.#selectedConversationIdsSet = new Set( selectedContacts.map(contact => contact.id) ); } @@ -183,7 +171,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper ); } @@ -200,20 +188,20 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper unknown; }>): ReactChild { let modalNode: undefined | ReactChild; - if (this.isShowingMaximumGroupSizeModal) { + if (this.#isShowingMaximumGroupSizeModal) { modalNode = ( ); - } else if (this.isShowingRecommendedGroupSizeModal) { + } else if (this.#isShowingRecommendedGroupSizeModal) { modalNode = ( @@ -222,9 +210,9 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper - {Boolean(this.selectedContacts.length) && ( + {Boolean(this.#selectedContacts.length) && ( - {this.selectedContacts.map(contact => ( + {this.#selectedContacts.map(contact => ( ): ReactChild { return ( @@ -278,18 +266,18 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper= this.groupSizeHardLimit; + #hasSelectedMaximumNumberOfContacts(): boolean { + return this.#selectedContacts.length >= this.#groupSizeHardLimit; } - private hasExceededMaximumNumberOfContacts(): boolean { + #hasExceededMaximumNumberOfContacts(): boolean { // It should be impossible to reach this state. This is here as a failsafe. - return this.selectedContacts.length > this.groupSizeHardLimit; + return this.#selectedContacts.length > this.#groupSizeHardLimit; } } diff --git a/ts/components/leftPane/LeftPaneComposeHelper.tsx b/ts/components/leftPane/LeftPaneComposeHelper.tsx index 561d2185fc8f..931264d8db30 100644 --- a/ts/components/leftPane/LeftPaneComposeHelper.tsx +++ b/ts/components/leftPane/LeftPaneComposeHelper.tsx @@ -35,21 +35,14 @@ enum TopButtons { } export class LeftPaneComposeHelper extends LeftPaneHelper { - private readonly composeContacts: ReadonlyArray; - - private readonly composeGroups: ReadonlyArray; - - private readonly uuidFetchState: UUIDFetchStateType; - - private readonly searchTerm: string; - - private readonly phoneNumber: ParsedE164Type | undefined; - - private readonly isPhoneNumberVisible: boolean; - - private readonly username: string | undefined; - - private readonly isUsernameVisible: boolean; + readonly #composeContacts: ReadonlyArray; + readonly #composeGroups: ReadonlyArray; + readonly #uuidFetchState: UUIDFetchStateType; + readonly #searchTerm: string; + readonly #phoneNumber: ParsedE164Type | undefined; + readonly #isPhoneNumberVisible: boolean; + readonly #username: string | undefined; + readonly #isUsernameVisible: boolean; constructor({ composeContacts, @@ -61,24 +54,24 @@ export class LeftPaneComposeHelper extends LeftPaneHelper) { super(); - this.composeContacts = composeContacts; - this.composeGroups = composeGroups; - this.searchTerm = searchTerm; - this.uuidFetchState = uuidFetchState; + this.#composeContacts = composeContacts; + this.#composeGroups = composeGroups; + this.#searchTerm = searchTerm; + this.#uuidFetchState = uuidFetchState; - this.username = username; - this.isUsernameVisible = + this.#username = username; + this.#isUsernameVisible = Boolean(username) && - this.composeContacts.every(contact => contact.username !== username); + this.#composeContacts.every(contact => contact.username !== username); const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode); if (!username && phoneNumber) { - this.phoneNumber = phoneNumber; - this.isPhoneNumberVisible = this.composeContacts.every( + this.#phoneNumber = phoneNumber; + this.#isPhoneNumberVisible = this.#composeContacts.every( contact => contact.e164 !== phoneNumber.e164 ); } else { - this.isPhoneNumberVisible = false; + this.#isPhoneNumberVisible = false; } } @@ -125,7 +118,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper ); } @@ -143,20 +136,20 @@ export class LeftPaneComposeHelper extends LeftPaneHelper ): boolean { const prev = new LeftPaneComposeHelper(exProps); - const currHeaderIndices = this.getHeaderIndices(); - const prevHeaderIndices = prev.getHeaderIndices(); + const currHeaderIndices = this.#getHeaderIndices(); + const prevHeaderIndices = prev.#getHeaderIndices(); return ( currHeaderIndices.top !== prevHeaderIndices.top || @@ -299,26 +292,26 @@ export class LeftPaneComposeHelper extends LeftPaneHelper { - private readonly searchTerm: string; - - private readonly phoneNumber: ParsedE164Type | undefined; - - private readonly regionCode: string | undefined; - - private readonly uuidFetchState: UUIDFetchStateType; - - private readonly countries: ReadonlyArray; - - private readonly selectedRegion: string; + readonly #searchTerm: string; + readonly #phoneNumber: ParsedE164Type | undefined; + readonly #regionCode: string | undefined; + readonly #uuidFetchState: UUIDFetchStateType; + readonly #countries: ReadonlyArray; + readonly #selectedRegion: string; constructor({ searchTerm, @@ -57,14 +52,14 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper) { super(); - this.searchTerm = searchTerm; - this.uuidFetchState = uuidFetchState; - this.regionCode = regionCode; - this.countries = countries; - this.selectedRegion = selectedRegion; + this.#searchTerm = searchTerm; + this.#uuidFetchState = uuidFetchState; + this.#regionCode = regionCode; + this.#countries = countries; + this.#selectedRegion = selectedRegion; - this.phoneNumber = parseAndFormatPhoneNumber( - this.searchTerm, + this.#phoneNumber = parseAndFormatPhoneNumber( + this.#searchTerm, selectedRegion || regionCode ); } @@ -83,7 +78,7 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper void; }): undefined | (() => void) { - return this.isFetching() ? undefined : startComposing; + return this.#isFetching() ? undefined : startComposing; } override getSearchInput({ @@ -122,25 +117,25 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper { if (ev.key === 'Enter') { - drop(this.doLookup(lookupActions)); + drop(this.#doLookup(lookupActions)); } }} /> @@ -157,10 +152,10 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper drop(this.doLookup(lookupActions))} + disabled={this.#isLookupDisabled()} + onClick={() => drop(this.#doLookup(lookupActions))} > - {this.isFetching() ? ( + {this.#isFetching() ? ( @@ -198,14 +193,14 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper { - if (!this.phoneNumber || this.isLookupDisabled()) { + if (!this.#phoneNumber || this.#isLookupDisabled()) { return; } @@ -213,8 +208,8 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper { - private readonly searchTerm: string; - - private readonly username: string | undefined; - - private readonly uuidFetchState: UUIDFetchStateType; + readonly #searchTerm: string; + readonly #username: string | undefined; + readonly #uuidFetchState: UUIDFetchStateType; constructor({ searchTerm, @@ -43,10 +41,10 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper) { super(); - this.searchTerm = searchTerm; - this.uuidFetchState = uuidFetchState; + this.#searchTerm = searchTerm; + this.#uuidFetchState = uuidFetchState; - this.username = username; + this.#username = username; } override getHeaderContents({ @@ -63,7 +61,7 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper void; }): undefined | (() => void) { - return this.isFetching() ? undefined : startComposing; + return this.#isFetching() ? undefined : startComposing; } override getSearchInput({ @@ -103,17 +101,17 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper { if (ev.key === 'Enter') { - drop(this.doLookup(lookupActions)); + drop(this.#doLookup(lookupActions)); } }} /> @@ -129,10 +127,10 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper drop(this.doLookup(lookupActions))} + disabled={this.#isLookupDisabled()} + onClick={() => drop(this.#doLookup(lookupActions))} > - {this.isFetching() ? ( + {this.#isFetching() ? ( @@ -170,14 +168,14 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper { - if (!this.username || this.isLookupDisabled()) { + if (!this.#username || this.#isLookupDisabled()) { return; } @@ -185,7 +183,7 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper { - private readonly conversations: ReadonlyArray; - - private readonly archivedConversations: ReadonlyArray; - - private readonly pinnedConversations: ReadonlyArray; - - private readonly isAboutToSearch: boolean; - - private readonly isSearchingGlobally: boolean; - - private readonly startSearchCounter: number; - - private readonly searchDisabled: boolean; - - private readonly searchTerm: string; - - private readonly searchConversation: undefined | ConversationType; - - private readonly filterByUnread: boolean; + readonly #conversations: ReadonlyArray; + readonly #archivedConversations: ReadonlyArray; + readonly #pinnedConversations: ReadonlyArray; + readonly #isAboutToSearch: boolean; + readonly #isSearchingGlobally: boolean; + readonly #startSearchCounter: number; + readonly #searchDisabled: boolean; + readonly #searchTerm: string; + readonly #searchConversation: undefined | ConversationType; + readonly #filterByUnread: boolean; constructor({ conversations, @@ -68,25 +59,25 @@ export class LeftPaneInboxHelper extends LeftPaneHelper }: Readonly) { super(); - this.conversations = conversations; - this.archivedConversations = archivedConversations; - this.pinnedConversations = pinnedConversations; - this.isAboutToSearch = isAboutToSearch; - this.isSearchingGlobally = isSearchingGlobally; - this.startSearchCounter = startSearchCounter; - this.searchDisabled = searchDisabled; - this.searchTerm = searchTerm; - this.searchConversation = searchConversation; - this.filterByUnread = filterByUnread; + this.#conversations = conversations; + this.#archivedConversations = archivedConversations; + this.#pinnedConversations = pinnedConversations; + this.#isAboutToSearch = isAboutToSearch; + this.#isSearchingGlobally = isSearchingGlobally; + this.#startSearchCounter = startSearchCounter; + this.#searchDisabled = searchDisabled; + this.#searchTerm = searchTerm; + this.#searchConversation = searchConversation; + this.#filterByUnread = filterByUnread; } getRowCount(): number { - const headerCount = this.hasPinnedAndNonpinned() ? 2 : 0; - const buttonCount = this.archivedConversations.length ? 1 : 0; + const headerCount = this.#hasPinnedAndNonpinned() ? 2 : 0; + const buttonCount = this.#archivedConversations.length ? 1 : 0; return ( headerCount + - this.pinnedConversations.length + - this.conversations.length + + this.#pinnedConversations.length + + this.#conversations.length + buttonCount ); } @@ -116,17 +107,17 @@ export class LeftPaneInboxHelper extends LeftPaneHelper clearSearchQuery={clearSearchQuery} endConversationSearch={endConversationSearch} endSearch={endSearch} - disabled={this.searchDisabled} + disabled={this.#searchDisabled} i18n={i18n} - isSearchingGlobally={this.isSearchingGlobally} - searchConversation={this.searchConversation} - searchTerm={this.searchTerm} + isSearchingGlobally={this.#isSearchingGlobally} + searchConversation={this.#searchConversation} + searchTerm={this.#searchTerm} showConversation={showConversation} - startSearchCounter={this.startSearchCounter} + startSearchCounter={this.#startSearchCounter} updateSearchTerm={updateSearchTerm} onFilterClick={updateFilterByUnread} - filterButtonEnabled={!this.searchConversation} - filterPressed={this.filterByUnread} + filterButtonEnabled={!this.#searchConversation} + filterPressed={this.#filterByUnread} /> ); } @@ -149,11 +140,13 @@ export class LeftPaneInboxHelper extends LeftPaneHelper } getRow(rowIndex: number): undefined | Row { - const { conversations, archivedConversations, pinnedConversations } = this; + const pinnedConversations = this.#pinnedConversations; + const archivedConversations = this.#archivedConversations; + const conversations = this.#conversations; const archivedConversationsCount = archivedConversations.length; - if (this.hasPinnedAndNonpinned()) { + if (this.#hasPinnedAndNonpinned()) { switch (rowIndex) { case 0: return { @@ -226,9 +219,9 @@ export class LeftPaneInboxHelper extends LeftPaneHelper const isConversationSelected = ( conversation: Readonly ) => conversation.id === selectedConversationId; - const hasHeaders = this.hasPinnedAndNonpinned(); + const hasHeaders = this.#hasPinnedAndNonpinned(); - const pinnedConversationIndex = this.pinnedConversations.findIndex( + const pinnedConversationIndex = this.#pinnedConversations.findIndex( isConversationSelected ); if (pinnedConversationIndex !== -1) { @@ -236,11 +229,11 @@ export class LeftPaneInboxHelper extends LeftPaneHelper return pinnedConversationIndex + headerOffset; } - const conversationIndex = this.conversations.findIndex( + const conversationIndex = this.#conversations.findIndex( isConversationSelected ); if (conversationIndex !== -1) { - const pinnedOffset = this.pinnedConversations.length; + const pinnedOffset = this.#pinnedConversations.length; const headerOffset = hasHeaders ? 2 : 0; return conversationIndex + pinnedOffset + headerOffset; } @@ -250,20 +243,21 @@ export class LeftPaneInboxHelper extends LeftPaneHelper override requiresFullWidth(): boolean { const hasNoConversations = - !this.conversations.length && - !this.pinnedConversations.length && - !this.archivedConversations.length; - return hasNoConversations || this.isAboutToSearch; + !this.#conversations.length && + !this.#pinnedConversations.length && + !this.#archivedConversations.length; + return hasNoConversations || this.#isAboutToSearch; } shouldRecomputeRowHeights(old: Readonly): boolean { - return old.pinnedConversations.length !== this.pinnedConversations.length; + return old.pinnedConversations.length !== this.#pinnedConversations.length; } getConversationAndMessageAtIndex( conversationIndex: number ): undefined | { conversationId: string } { - const { conversations, pinnedConversations } = this; + const pinnedConversations = this.#pinnedConversations; + const conversations = this.#conversations; const conversation = pinnedConversations[conversationIndex] || conversations[conversationIndex - pinnedConversations.length] || @@ -278,7 +272,7 @@ export class LeftPaneInboxHelper extends LeftPaneHelper _targetedMessageId: unknown ): undefined | { conversationId: string } { return getConversationInDirection( - [...this.pinnedConversations, ...this.conversations], + [...this.#pinnedConversations, ...this.#conversations], toFind, selectedConversationId ); @@ -295,9 +289,9 @@ export class LeftPaneInboxHelper extends LeftPaneHelper handleKeydownForSearch(event, options); } - private hasPinnedAndNonpinned(): boolean { + #hasPinnedAndNonpinned(): boolean { return Boolean( - this.pinnedConversations.length && this.conversations.length + this.#pinnedConversations.length && this.#conversations.length ); } } diff --git a/ts/components/leftPane/LeftPaneSearchHelper.tsx b/ts/components/leftPane/LeftPaneSearchHelper.tsx index b4fd1f85b1c2..3e9b762861c7 100644 --- a/ts/components/leftPane/LeftPaneSearchHelper.tsx +++ b/ts/components/leftPane/LeftPaneSearchHelper.tsx @@ -50,36 +50,24 @@ export type LeftPaneSearchPropsType = { searchConversation: undefined | ConversationType; }; -const searchResultKeys: Array< - 'conversationResults' | 'contactResults' | 'messageResults' -> = ['conversationResults', 'contactResults', 'messageResults']; - export class LeftPaneSearchHelper extends LeftPaneHelper { - private readonly conversationResults: MaybeLoadedSearchResultsType; + readonly #conversationResults: MaybeLoadedSearchResultsType; + readonly #contactResults: MaybeLoadedSearchResultsType; + readonly #isSearchingGlobally: boolean; - private readonly contactResults: MaybeLoadedSearchResultsType; - - private readonly isSearchingGlobally: boolean; - - private readonly messageResults: MaybeLoadedSearchResultsType<{ + readonly #messageResults: MaybeLoadedSearchResultsType<{ id: string; conversationId: string; type: string; }>; - private readonly searchConversationName?: string; - - private readonly primarySendsSms: boolean; - - private readonly searchTerm: string; - - private readonly startSearchCounter: number; - - private readonly searchDisabled: boolean; - - private readonly searchConversation: undefined | ConversationType; - - private readonly filterByUnread: boolean; + readonly #searchConversationName?: string; + readonly #primarySendsSms: boolean; + readonly #searchTerm: string; + readonly #startSearchCounter: number; + readonly #searchDisabled: boolean; + readonly #searchConversation: undefined | ConversationType; + readonly #filterByUnread: boolean; constructor({ contactResults, @@ -96,18 +84,17 @@ export class LeftPaneSearchHelper extends LeftPaneHelper) { super(); - this.contactResults = contactResults; - this.conversationResults = conversationResults; - this.isSearchingGlobally = isSearchingGlobally; - this.messageResults = messageResults; - this.primarySendsSms = primarySendsSms; - this.searchConversation = searchConversation; - this.searchConversationName = searchConversationName; - this.searchDisabled = searchDisabled; - this.searchTerm = searchTerm; - this.startSearchCounter = startSearchCounter; - this.filterByUnread = filterByUnread; - this.onEnterKeyDown = this.onEnterKeyDown.bind(this); + this.#contactResults = contactResults; + this.#conversationResults = conversationResults; + this.#isSearchingGlobally = isSearchingGlobally; + this.#messageResults = messageResults; + this.#primarySendsSms = primarySendsSms; + this.#searchConversation = searchConversation; + this.#searchConversationName = searchConversationName; + this.#searchDisabled = searchDisabled; + this.#searchTerm = searchTerm; + this.#startSearchCounter = startSearchCounter; + this.#filterByUnread = filterByUnread; } override getSearchInput({ @@ -135,17 +122,17 @@ export class LeftPaneSearchHelper extends LeftPaneHelper ); @@ -156,7 +143,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper): ReactChild | null { - const mightHaveSearchResults = this.allResults().some( + const mightHaveSearchResults = this.#allResults().some( searchResult => searchResult.isLoading || searchResult.results.length ); @@ -164,7 +151,9 @@ export class LeftPaneSearchHelper extends LeftPaneHelper 0) { + if (this.#filterByUnread && this.#searchTerm.length > 0) { noResultsMessage = i18n('icu:noSearchResultsWithUnreadFilter', { searchTerm, }); - } else if (this.filterByUnread) { + } else if (this.#filterByUnread) { noResultsMessage = i18n('icu:noSearchResultsOnlyUnreadFilter'); } else { noResultsMessage = i18n('icu:noSearchResults', { @@ -195,7 +184,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper - {this.filterByUnread && ( + {this.#filterByUnread && (
result + getRowCountForLoadedSearchResults(searchResults), 0 ); // The clear unread filter button adds an extra row - if (this.filterByUnread) { + if (this.#filterByUnread) { count += 1; } @@ -257,9 +246,11 @@ export class LeftPaneSearchHelper extends LeftPaneHelper - this.filterByUnread + this.#filterByUnread ? i18n('icu:conversationsUnreadHeader') : i18n('icu:conversationsHeader'), }; @@ -350,7 +341,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper searchResult.isLoading || searchResult.results.length === 0 ), @@ -361,24 +352,30 @@ export class LeftPaneSearchHelper extends LeftPaneHelper): boolean { const oldSearchPaneHelper = new LeftPaneSearchHelper(old); - const oldIsLoading = oldSearchPaneHelper.isLoading(); - const newIsLoading = this.isLoading(); + const oldIsLoading = oldSearchPaneHelper.#isLoading(); + const newIsLoading = this.#isLoading(); if (oldIsLoading && newIsLoading) { return false; } if (oldIsLoading !== newIsLoading) { return true; } - return searchResultKeys.some( - key => - getRowCountForLoadedSearchResults(old[key]) !== - getRowCountForLoadedSearchResults(this[key]) - ); + const searchResultsByKey = [ + { current: this.#conversationResults, prev: old.conversationResults }, + { current: this.#contactResults, prev: old.contactResults }, + { current: this.#messageResults, prev: old.messageResults }, + ]; + return searchResultsByKey.some(item => { + return ( + getRowCountForLoadedSearchResults(item.prev) !== + getRowCountForLoadedSearchResults(item.current) + ); + }); } getConversationAndMessageAtIndex( @@ -388,7 +385,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper results.isLoading); + #isLoading(): boolean { + return this.#allResults().some(results => results.isLoading); } - private onEnterKeyDown( + #onEnterKeyDown = ( clearSearchQuery: () => unknown, showConversation: ShowConversationType - ): void { + ): void => { const conversation = this.getConversationAndMessageAtIndex(0); if (!conversation) { return; } showConversation(conversation); clearSearchQuery(); - } + }; } function getRowCountForLoadedSearchResults( diff --git a/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx b/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx index 169e8512dab1..0273474f1aaa 100644 --- a/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx +++ b/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx @@ -38,21 +38,14 @@ export type LeftPaneSetGroupMetadataPropsType = { }; export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper { - private readonly groupAvatar: undefined | Uint8Array; - - private readonly groupName: string; - - private readonly groupExpireTimer: DurationInSeconds; - - private readonly hasError: boolean; - - private readonly isCreating: boolean; - - private readonly isEditingAvatar: boolean; - - private readonly selectedContacts: ReadonlyArray; - - private readonly userAvatarData: ReadonlyArray; + readonly #groupAvatar: undefined | Uint8Array; + readonly #groupName: string; + readonly #groupExpireTimer: DurationInSeconds; + readonly #hasError: boolean; + readonly #isCreating: boolean; + readonly #isEditingAvatar: boolean; + readonly #selectedContacts: ReadonlyArray; + readonly #userAvatarData: ReadonlyArray; constructor({ groupAvatar, @@ -66,14 +59,14 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper) { super(); - this.groupAvatar = groupAvatar; - this.groupName = groupName; - this.groupExpireTimer = groupExpireTimer; - this.hasError = hasError; - this.isCreating = isCreating; - this.isEditingAvatar = isEditingAvatar; - this.selectedContacts = selectedContacts; - this.userAvatarData = userAvatarData; + this.#groupAvatar = groupAvatar; + this.#groupName = groupName; + this.#groupExpireTimer = groupExpireTimer; + this.#hasError = hasError; + this.#isCreating = isCreating; + this.#isEditingAvatar = isEditingAvatar; + this.#selectedContacts = selectedContacts; + this.#userAvatarData = userAvatarData; } override getHeaderContents({ @@ -90,7 +83,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper void; }): undefined | (() => void) { - return this.isCreating ? undefined : showChooseGroupMembers; + return this.#isCreating ? undefined : showChooseGroupMembers; } override getPreRowsNode({ @@ -134,7 +127,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper unknown; }>): ReactChild { const [avatarColor] = AvatarColors; - const disabled = this.isCreating; + const disabled = this.#isCreating; return (
- {this.isEditingAvatar && ( + {this.#isEditingAvatar && ( @@ -179,7 +172,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper
@@ -206,12 +199,12 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper - {this.hasError && ( + {this.#hasError && ( ): ReactChild { return (