diff --git a/.oxlint/rules/noExtraneousDependencies.mjs b/.oxlint/rules/noExtraneousDependencies.mjs index 3bae0dbc24..4132bf5a4a 100644 --- a/.oxlint/rules/noExtraneousDependencies.mjs +++ b/.oxlint/rules/noExtraneousDependencies.mjs @@ -79,7 +79,7 @@ function getPackageNameFromSource(source) { return `${scope}/${name}`; } const [name] = source.split('/', 1); - return `${name}`; + return name; } /** diff --git a/.oxlintrc.json b/.oxlintrc.json index 8d08ff7216..89285b2ac4 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -422,15 +422,15 @@ // [correctness] Disallow void DOM elements (e.g. ,
) from receiving children "react/void-dom-elements-no-children": "error", // [correctness] (βœ… recommended) (πŸ’‘ suggestion) (πŸ’­ needs type info) - "typescript/await-thenable": "off", // FIXME (errors: 126) + "typescript/await-thenable": "error", // [correctness] (βœ… recommended) (πŸ’‘ suggestion) (πŸ’­ needs type info) Disallow using the delete operator on array values "typescript/no-array-delete": "error", // [correctness] (βœ… recommended) (πŸ’­ needs type info) Require .toString() and .toLocaleString() to only be called on objects which provide useful information when stringified - "typescript/no-base-to-string": "off", // FIXME [reason: FILLMEIN] (errors: 14) + "typescript/no-base-to-string": "error", // [correctness] (βœ… recommended) Disallow duplicate enum member values "typescript/no-duplicate-enum-values": "error", // [correctness] (βœ… recommended) (πŸ› οΈ autofix) (πŸ’­ needs type info) Disallow duplicate constituents of union or intersection types - "typescript/no-duplicate-type-constituents": "off", // FIXME [reason: FILLMEIN] (errors: 2) + "typescript/no-duplicate-type-constituents": "error", // [correctness] (βœ… recommended) (🚧 planned autofix) Disallow extra non-null assertions "typescript/no-extra-non-null-assertion": "error", // [correctness] (βœ… recommended) (πŸ’‘ suggestion) (πŸ’­ needs type info) Require Promise-like statements to be handled appropriately @@ -440,15 +440,15 @@ // [correctness] (βœ… recommended) (πŸ’­ needs type info) Disallow the use of eval()-like functions "typescript/no-implied-eval": "error", // [correctness] (βœ… recommended) (πŸ› οΈ autofix) (πŸ’‘ suggestion) (πŸ’­ needs type info) Disallow the void operator except when used to discard a value - "typescript/no-meaningless-void-operator": "off", // DECIDEME (errors: 4) + "typescript/no-meaningless-void-operator": "error", // [correctness] (βœ… recommended) Enforce valid definition of new and constructor "typescript/no-misused-new": "error", // [correctness] (βœ… recommended) (πŸ’‘ suggestion) (πŸ’­ needs type info) Disallow using the spread operator when it might cause unexpected behavior - "typescript/no-misused-spread": "off", // DECIDEME (errors: 12) + "typescript/no-misused-spread": "error", // [correctness] (βœ… recommended) (πŸ’‘ suggestion) Disallow non-null assertions after an optional chain expression "typescript/no-non-null-asserted-optional-chain": "error", // [correctness] (βœ… recommended) (πŸ’­ needs type info) Disallow members of unions and intersections that do nothing or override type information - "typescript/no-redundant-type-constituents": "off", // DECIDEME (errors: 10) + "typescript/no-redundant-type-constituents": "error", // [correctness] (βœ… recommended) Disallow aliasing this "typescript/no-this-alias": "error", // [correctness] (βœ… recommended) (πŸ’‘ suggestion) Disallow unnecessary assignment of constructor property parameter @@ -466,9 +466,14 @@ // [correctness] (βœ… recommended) (πŸ› οΈ autofix) Require using namespace keyword over module keyword to declare custom TypeScript modules "typescript/prefer-namespace-keyword": "error", // [correctness] (βœ… recommended) (πŸ’­ needs type info) Require Array#sort and Array#toSorted calls to always provide a compareFunction - "typescript/require-array-sort-compare": "off", // DECIDEME (errors: 2) + "typescript/require-array-sort-compare": "error", // [correctness] (βœ… recommended) (πŸ’­ needs type info) Enforce template literal expressions to be of string type - "typescript/restrict-template-expressions": "off", // DECIDEME (errors: 76) + "typescript/restrict-template-expressions": [ + "error", + { + "allowNever": true // FIXME + } + ], // [correctness] (βœ… recommended) Disallow certain triple slash directives in favor of ES6-style import declarations "typescript/triple-slash-reference": "error", // [correctness] (βœ… recommended) (πŸ’­ needs type info) Enforce unbound methods are called with their expected scope @@ -547,31 +552,31 @@ // [nursery] Require return statements to either always or never specify values "typescript/consistent-return": "off", // [reason: Doesn't seem like it would catch many bugs] // [nursery] Enforce consistent usage of type exports - "typescript/consistent-type-exports": "off", // DECIDEME (errors: 2) + "typescript/consistent-type-exports": "error", // [nursery] Enforce dot notation whenever possible - "typescript/dot-notation": "off", // DECIDEME (errors: 2) + "typescript/dot-notation": "error", // [nursery] Disallow conditionals where the type is always truthy or always falsy "typescript/no-unnecessary-condition": "off", // FIXME (errors: 1560) [reason: This is a big project, and we should make sure we're not removing conditional checks that are dealing with bad data (improved types)] // [nursery] Disallow unnecessary namespace qualifiers - "typescript/no-unnecessary-qualifier": "off", // DECIDEME (errors: 5) + "typescript/no-unnecessary-qualifier": "error", // [nursery] Disallow conversion idioms when they do not change the type or value of the expression - "typescript/no-unnecessary-type-conversion": "off", // DECIDEME (errors: 127) + "typescript/no-unnecessary-type-conversion": "error", // [nursery] Disallow type parameters that aren't used multiple times "typescript/no-unnecessary-type-parameters": "off", // TODO: Has issues with disable comments // [nursery] Disallow default values that will never be used - "typescript/no-useless-default-assignment": "off", // DECIDEME (errors: 12) + "typescript/no-useless-default-assignment": "error", // [nursery] Enforce the use of Array.prototype.find() over Array.prototype.filter() followed by [0] when looking for a single result "typescript/prefer-find": "error", // [nursery] ⚠️ πŸ›  (πŸ’‘ suggestion) Enforce using concise optional chain expressions instead of chained logical ands, negated logical ors, or empty objects "typescript/prefer-optional-chain": "off", // DECIDEME (errors: 251) // [nursery] Require private members to be marked as readonly if they're never modified outside of the constructor - "typescript/prefer-readonly": "off", // DECIDEME (errors: 138) + "typescript/prefer-readonly": "error", // [nursery] Require function parameters to be typed as readonly to prevent accidental mutation of inputs "typescript/prefer-readonly-parameter-types": "off", // DECIDEME (errors: 10694) // [nursery] Enforce RegExp#exec over String#match if no global flag is provided "typescript/prefer-regexp-exec": "off", // DECIDEME (errors: 19) // [nursery] (βœ… recommended) Enforce using String#startsWith and String#endsWith over other equivalent methods of checking substrings - "typescript/prefer-string-starts-ends-with": "off", // DECIDEME (errors: 5) + "typescript/prefer-string-starts-ends-with": "error", // [nursery] Enforce ES5 or ES6 class for returning value in render function "react/require-render-return": "error", // [nursery] (πŸ’­ needs type info) Disallow passing a value-returning function in a position accepting a void function @@ -686,43 +691,43 @@ // [pedantic] (πŸ› οΈ autofix) (πŸ’‘ suggestion) Require expressions of type void to appear in statement position "typescript/no-confusing-void-expression": "off", // DECIDEME (errors: 621) // [pedantic] Disallow using code marked as @deprecated - "typescript/no-deprecated": "off", // DECIDEME (errors: 187) + "typescript/no-deprecated": "off", // FIXME (errors: 187) // [pedantic] Disallow Promises in places not designed to handle them "typescript/no-misused-promises": ["error", { "checksVoidReturn": false }], // [reason: FILLMEIN] (errors: 352) // [pedantic] Disallow enums from having both number and string members "typescript/no-mixed-enums": "error", // [pedantic] Disallow calling a function with a value with type any - "typescript/no-unsafe-argument": "off", // DECIDEME (errors: 531) + "typescript/no-unsafe-argument": "off", // LATER (errors: 531) // [pedantic] Disallow assigning a value with type any to variables and properties - "typescript/no-unsafe-assignment": "off", // DECIDEME (errors: 817) + "typescript/no-unsafe-assignment": "off", // LATER (errors: 817) // [pedantic] Disallow calling a value with type any - "typescript/no-unsafe-call": "off", // DECIDEME (errors: 364) + "typescript/no-unsafe-call": "off", // LATER (errors: 364) // [pedantic] Disallow using the unsafe built-in Function type - "typescript/no-unsafe-function-type": "off", // DECIDEME (errors: 1) + "typescript/no-unsafe-function-type": "error", // [pedantic] Disallow member access on a value with type any - "typescript/no-unsafe-member-access": "off", // DECIDEME (errors: 1087) + "typescript/no-unsafe-member-access": "off", // LATER (errors: 1087) // [pedantic] Disallow returning a value with type any from a function - "typescript/no-unsafe-return": "off", // DECIDEME (errors: 123) + "typescript/no-unsafe-return": "off", // LATER (errors: 123) // [pedantic] Disallow throwing non-Error values as exceptions - "typescript/only-throw-error": "off", // DECIDEME (errors: 6) + "typescript/only-throw-error": "error", // [pedantic] (πŸ’‘ suggestion) Require each enum member value to be explicitly initialized "typescript/prefer-enum-initializers": "off", // DECIDEME (errors: 180) // [pedantic] (πŸ› οΈ autofix) Enforce includes method over indexOf method - "typescript/prefer-includes": "off", // DECIDEME (errors: 3) + "typescript/prefer-includes": "error", // [pedantic] (πŸ› οΈ autofix) Enforce using the nullish coalescing operator instead of logical assignments or chaining "typescript/prefer-nullish-coalescing": "off", // DECIDEME (errors: 1199) // [pedantic] Require using Error objects as Promise rejection reasons "typescript/prefer-promise-reject-errors": "off", // DECIDEME (errors: 20) // [pedantic] (πŸ› οΈ autofix) Enforce using @ts-expect-error over @ts-ignore - "typescript/prefer-ts-expect-error": "off", // DECIDEME (errors: 6) + "typescript/prefer-ts-expect-error": "error", // [pedantic] Enforce that get() types should be assignable to their equivalent set() type "typescript/related-getter-setter-pairs": "error", // [pedantic] (🚧 planned autofix) Disallow async functions which do not return promises and have no await expression "typescript/require-await": "off", // DECIDEME (errors: 373) // [pedantic] Require both operands of addition to be the same type and be bigint, number, or string - "typescript/restrict-plus-operands": "off", // DECIDEME (errors: 3) + "typescript/restrict-plus-operands": "error", // [pedantic] (πŸ› οΈ autofix) (πŸ’‘ suggestion) Enforce consistent awaiting of returned promises - "typescript/return-await": "off", // DECIDEME (errors: 9) + "typescript/return-await": "error", // [pedantic] (🚧 planned autofix) Disallow certain types in boolean expressions "typescript/strict-boolean-expressions": "off", // DECIDEME (errors: 4219) // [pedantic] (πŸ’‘ suggestion) Require switch-case statements to be exhaustive @@ -1019,7 +1024,7 @@ // [restriction] Disallow require statements except in import statements "typescript/no-var-requires": "error", // [restriction] (πŸ› οΈ autofix) (πŸ’­ needcs type info) Enforce non-null assertions over explicit type assertions - "typescript/non-nullable-type-assertion-style": "off", // DECIDEME (errors: 12) + "typescript/non-nullable-type-assertion-style": "error", // [restriction] Require all enum members to be literal values "typescript/prefer-literal-enum-member": "off", // DECIDEME (errors: 21) // [restriction] (πŸ› οΈ autofix) (πŸ’­ needs type info) Require any function or method that returns a Promise to be marked async @@ -1312,7 +1317,7 @@ // [style] (🚧 planned autofix) Enforce that literals on classes are exposed in a consistent style "typescript/class-literal-property-style": "error", // [style] (πŸ› οΈ autofix) Enforce specifying generic type arguments on type annotation or constructor name of a constructor call - "typescript/consistent-generic-constructors": "off", // DECIDEME (errors: 38) + "typescript/consistent-generic-constructors": "error", // [style] (πŸ› οΈ autofix) Require or disallow the Record type "typescript/consistent-indexed-object-style": "off", // DECIDEME (errors: 40) // [style] (πŸ› οΈ autofix) (πŸ’‘ suggestion) Enforce consistent usage of type assertions @@ -1324,15 +1329,15 @@ // [style] (🚧 planned autofix) Disallow the declaration of empty interfaces "typescript/no-empty-interface": "error", // [reason: FILLMEIN] // [style] (πŸ’‘ suggestion) Disallow explicit type declarations for variables or parameters initialized to a number, string, or boolean - "typescript/no-inferrable-types": "off", // DECIDEME (errors: 12) + "typescript/no-inferrable-types": "error", // [style] Require or disallow parameter properties in class constructors - "typescript/parameter-properties": "off", // DECIDEME (errors: 128) + "typescript/parameter-properties": "error", // [style] (🚧 planned autofix) Enforce the use of for-of loop over the standard for loop where possible "typescript/prefer-for-of": "error", // [style] (πŸ› οΈ autofix) Enforce using function types instead of interfaces with call signatures - "typescript/prefer-function-type": "off", // DECIDEME (errors: 1) + "typescript/prefer-function-type": "error", // [style] (πŸ› οΈ autofix) (πŸ’­ needs type info) Enforce using type parameter when calling Array#reduce instead of using a type assertion - "typescript/prefer-reduce-type-parameter": "off", // DECIDEME (errors: 4) + "typescript/prefer-reduce-type-parameter": "error", // [style] (πŸ› οΈ autofix) (πŸ’­ needs type info) Enforce that this is used when only this type is returned "typescript/prefer-return-this-type": "error", // [style] Require that function overload signatures be consecutive @@ -1526,17 +1531,17 @@ // [suspicious] Enforce style prop value is an object "react/style-prop-object": "error", // [suspicious] (🚧 planned autofix) Disallow non-null assertion in locations that may be confusing - "typescript/no-confusing-non-null-assertion": "off", // DECIDEME (errors: 3) + "typescript/no-confusing-non-null-assertion": "error", // [suspicious] (⚠️ πŸ’‘ dangerous suggestion) Disallow classes used as namespaces - "typescript/no-extraneous-class": "off", // DECIDEME (errors: 3) + "typescript/no-extraneous-class": "error", // [suspicious] (🚧 planned autofix) (πŸ’­ needs type info) Disallow unnecessary equality comparisons against boolean literals - "typescript/no-unnecessary-boolean-literal-compare": "off", // DECIDEME (errors: 38) + "typescript/no-unnecessary-boolean-literal-compare": "error", // DECIDEME (errors: 38) // [suspicious] (🚧 planned autofix) (πŸ’­ needs type info) Disallow unnecessary template expressions - "typescript/no-unnecessary-template-expression": "off", // DECIDEME (errors: 20) + "typescript/no-unnecessary-template-expression": "error", // [suspicious] (πŸ› οΈ autofix) (πŸ’­ needs type info) Disallow type arguments that are equal to the default - "typescript/no-unnecessary-type-arguments": "off", // DECIDEME (errors: 24) + "typescript/no-unnecessary-type-arguments": "error", // [suspicious] (πŸ› οΈ autofix) (πŸ’­ needs type info) Disallow type assertions that do not change the type of an expression - "typescript/no-unnecessary-type-assertion": "off", // DECIDEME (errors: 11) + "typescript/no-unnecessary-type-assertion": "error", // [suspicious] (🚧 planned autofix) Disallow unnecessary constraints on generic types "typescript/no-unnecessary-type-constraint": "error", // [suspicious] (🚧 planned autofix) (πŸ’­ needs type info) Disallow comparing an enum value with a non-enum value @@ -1693,6 +1698,15 @@ "eslint/no-inner-declarations": "off" } }, + { + "files": [ + "ts/test-node/state/ducks/**", + "ts/test-electron/state/ducks/**" + ], + "rules": { + "typescript/await-thenable": "off" + } + }, { "files": [ ".oxlint/**", diff --git a/.storybook/main.ts b/.storybook/main.ts index 6a19c3c916..891acbe09c 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -106,7 +106,7 @@ const storybookConfig: StorybookConfig = { // oxlint-disable-next-line no-param-reassign webpackConfig.externals = ({ request }, callback) => { if ( - (/^node:/.test(request) && request !== 'node:buffer') || + (request.startsWith('node:') && request !== 'node:buffer') || EXTERNALS.has(request) ) { // Keep Node.js imports unchanged diff --git a/app/EmojiService.main.ts b/app/EmojiService.main.ts index 3f3c02ef4c..e9f9583022 100644 --- a/app/EmojiService.main.ts +++ b/app/EmojiService.main.ts @@ -27,6 +27,7 @@ type EmojiEntryType = Readonly<{ type SheetCacheEntry = Map>; export class EmojiService { + readonly #resourceService: OptionalResourceService; readonly #emojiMap = new Map(); readonly #sheetCache = new LRUCache({ @@ -35,9 +36,11 @@ export class EmojiService { }); private constructor( - private readonly resourceService: OptionalResourceService, + resourceService: OptionalResourceService, manifest: ManifestType ) { + this.#resourceService = resourceService; + protocol.handle('emoji', async req => { const url = new URL(req.url); const emoji = url.searchParams.get('emoji'); @@ -74,7 +77,7 @@ export class EmojiService { let imageMap = this.#sheetCache.get(sheet); if (!imageMap) { - const proto = await this.resourceService.getData( + const proto = await this.#resourceService.getData( `emoji-sheet-${sheet}.proto` ); if (!proto) { diff --git a/app/OptionalResourceService.main.ts b/app/OptionalResourceService.main.ts index 79cfcd71cd..3a3181ffd7 100644 --- a/app/OptionalResourceService.main.ts +++ b/app/OptionalResourceService.main.ts @@ -31,6 +31,7 @@ const RESOURCES_DICT_PATH = join( const MAX_CACHE_SIZE = 50 * 1024 * 1024; export class OptionalResourceService { + readonly #resourcesDir: string; #maybeDeclaration: OptionalResourcesDictType | undefined; readonly #cache = new LRUCache>({ @@ -41,7 +42,9 @@ export class OptionalResourceService { readonly #fileQueues = new Map(); - private constructor(private readonly resourcesDir: string) { + private constructor(resourcesDir: string) { + this.#resourcesDir = resourcesDir; + ipcMain.handle('OptionalResourceService:getData', (_event, name) => this.getData(name) ); @@ -61,7 +64,7 @@ export class OptionalResourceService { return undefined; } - const filePath = join(this.resourcesDir, name); + const filePath = join(this.#resourcesDir, name); return this.#queueFileWork(filePath, async () => { const inMemory = this.#cache.get(name); if (inMemory) { @@ -117,7 +120,7 @@ export class OptionalResourceService { // Clean unknown resources let subPaths: Array; try { - subPaths = await readdir(this.resourcesDir); + subPaths = await readdir(this.#resourcesDir); } catch (error) { // Directory wasn't created yet if (error.code === 'ENOENT') { @@ -132,7 +135,7 @@ export class OptionalResourceService { return; } - const fullPath = join(this.resourcesDir, subPath); + const fullPath = join(this.#resourcesDir, subPath); try { await unlink(fullPath); diff --git a/app/PreventDisplaySleepService.std.ts b/app/PreventDisplaySleepService.std.ts index 05d2698b97..3e205b44b7 100644 --- a/app/PreventDisplaySleepService.std.ts +++ b/app/PreventDisplaySleepService.std.ts @@ -7,9 +7,12 @@ import { createLogger } from '../ts/logging/log.std.ts'; const log = createLogger('PreventDisplaySleepService'); export class PreventDisplaySleepService { + readonly #powerSaveBlocker: PowerSaveBlocker; private blockerId: undefined | number; - constructor(private powerSaveBlocker: PowerSaveBlocker) {} + constructor(powerSaveBlocker: PowerSaveBlocker) { + this.#powerSaveBlocker = powerSaveBlocker; + } isEnabled(): boolean { return this.blockerId !== undefined; @@ -33,14 +36,14 @@ export class PreventDisplaySleepService { if (this.blockerId !== undefined) { return; } - this.blockerId = this.powerSaveBlocker.start('prevent-display-sleep'); + this.blockerId = this.#powerSaveBlocker.start('prevent-display-sleep'); } #disable(): void { if (this.blockerId === undefined) { return; } - this.powerSaveBlocker.stop(this.blockerId); + this.#powerSaveBlocker.stop(this.blockerId); delete this.blockerId; } } diff --git a/app/SystemTrayService.main.ts b/app/SystemTrayService.main.ts index 79f811cc59..79650680d6 100644 --- a/app/SystemTrayService.main.ts +++ b/app/SystemTrayService.main.ts @@ -33,7 +33,7 @@ export class SystemTrayService { #isEnabled = false; #isQuitting = false; #unreadCount = 0; - #createTrayInstance: (icon: NativeImage) => Tray; + readonly #createTrayInstance: (icon: NativeImage) => Tray; constructor({ i18n, createTrayInstance }: SystemTrayServiceOptionsType) { log.info('System tray service: created'); @@ -120,7 +120,7 @@ export class SystemTrayService { return this.#tray !== undefined; } - #render = (): void => { + readonly #render = (): void => { if (this.#isEnabled && this.#browserWindow) { this.#renderEnabled(); return; diff --git a/app/SystemTraySettingCache.node.ts b/app/SystemTraySettingCache.node.ts index 915fa22721..e9d49d78a9 100644 --- a/app/SystemTraySettingCache.node.ts +++ b/app/SystemTraySettingCache.node.ts @@ -17,13 +17,18 @@ const log = createLogger('SystemTraySettingCache'); * process. */ export class SystemTraySettingCache { + readonly #ephemeralConfig: Pick; + readonly #argv: Array; #cachedValue: undefined | SystemTraySetting; #getPromise: undefined | Promise; constructor( - private readonly ephemeralConfig: Pick, - private readonly argv: Array - ) {} + ephemeralConfig: Pick, + argv: Array + ) { + this.#ephemeralConfig = ephemeralConfig; + this.#argv = argv; + } async get(): Promise { if (this.#cachedValue !== undefined) { @@ -43,18 +48,18 @@ export class SystemTraySettingCache { // These command line flags are not officially supported, but many users rely on them. // Be careful when removing them or making changes. - if (this.argv.some(arg => arg === '--start-in-tray')) { + if (this.#argv.some(arg => arg === '--start-in-tray')) { result = SystemTraySetting.MinimizeToAndStartInSystemTray; log.info( `getSystemTraySetting saw --start-in-tray flag. Returning ${result}` ); - } else if (this.argv.some(arg => arg === '--use-tray-icon')) { + } else if (this.#argv.some(arg => arg === '--use-tray-icon')) { result = SystemTraySetting.MinimizeToSystemTray; log.info( `getSystemTraySetting saw --use-tray-icon flag. Returning ${result}` ); } else if (isSystemTraySupported(OS)) { - const value = this.ephemeralConfig.get('system-tray-setting'); + const value = this.#ephemeralConfig.get('system-tray-setting'); if (value !== undefined) { log.info('getSystemTraySetting got value', value); } @@ -68,7 +73,7 @@ export class SystemTraySettingCache { } if (result !== value) { - this.ephemeralConfig.set('system-tray-setting', result); + this.#ephemeralConfig.set('system-tray-setting', result); } } else { result = SystemTraySetting.DoNotUseSystemTray; diff --git a/app/crashReports.main.ts b/app/crashReports.main.ts index c8ce9c6d44..13c26f24a7 100644 --- a/app/crashReports.main.ts +++ b/app/crashReports.main.ts @@ -186,7 +186,7 @@ export function setup( } // Node.js Addons are useful - if (/\.node$/.test(filename)) { + if (filename.endsWith('.node')) { return true; } diff --git a/app/main.main.ts b/app/main.main.ts index 1c83cfad5a..3be19f604e 100644 --- a/app/main.main.ts +++ b/app/main.main.ts @@ -1823,6 +1823,7 @@ async function initializeSQL( return { ok: false, + // oxlint-disable-next-line typescript/restrict-template-expressions error: new Error(`initializeSQL: Caught a non-error '${error}'`), }; } finally { diff --git a/app/protocol_filter.node.ts b/app/protocol_filter.node.ts index a84bae520e..3d436e6d26 100644 --- a/app/protocol_filter.node.ts +++ b/app/protocol_filter.node.ts @@ -110,7 +110,7 @@ function _createFileHandler({ } log.info( - `Warning: denying request to path '${realPath}' (allowedRoots: '${allowedRoots}')` + `Warning: denying request to path '${realPath}' (allowedRoots: '${allowedRoots.join(', ')}')` ); callback({ error: -10 }); } catch (err) { diff --git a/app/user_config.main.ts b/app/user_config.main.ts index 0cbab49e6f..f96fe70a42 100644 --- a/app/user_config.main.ts +++ b/app/user_config.main.ts @@ -17,6 +17,7 @@ if (config.has('storagePath')) { } else if (config.has('storageProfile')) { userData = join( app.getPath('appData'), + // oxlint-disable-next-line typescript/restrict-template-expressions `Signal-${config.get('storageProfile')}` ); } else if (OS.isAppImage()) { diff --git a/build/intl-linter/utils/rule.std.ts b/build/intl-linter/utils/rule.std.ts index 9975520172..78a3cff667 100644 --- a/build/intl-linter/utils/rule.std.ts +++ b/build/intl-linter/utils/rule.std.ts @@ -20,9 +20,7 @@ export type Context = { ): void; }; -export type RuleFactory = { - (context: Context): Visitor; -}; +export type RuleFactory = (context: Context) => Visitor; export type Rule = { id: string; diff --git a/danger/rules/enforceTailwindDepsMatch.mjs b/danger/rules/enforceTailwindDepsMatch.mjs index 89ae757d72..286a8f29f6 100644 --- a/danger/rules/enforceTailwindDepsMatch.mjs +++ b/danger/rules/enforceTailwindDepsMatch.mjs @@ -31,6 +31,11 @@ function isTailwindPackage(pkgName) { for (const depType of ['dependencies', 'devDependencies']) { for (const [depName, depSpec] of Object.entries(pkgJson[depType])) { + if (typeof depSpec !== 'string') { + throw new TypeError( + `Expected string value for "${depName}" in package.json#${depType}` + ); + } if (isTailwindPackage(depName) && depSpec !== expectedVersion) { fail( `**Tailwind package versions must all match**\n` + diff --git a/scripts/check-upgradeable-deps.mjs b/scripts/check-upgradeable-deps.mjs index a43322872c..5fed64e005 100644 --- a/scripts/check-upgradeable-deps.mjs +++ b/scripts/check-upgradeable-deps.mjs @@ -176,7 +176,7 @@ const { approvedDeps } = await enquirer.prompt({ return { name: deps.name, - message: `${deps.name.padEnd(longestNameLength)}`, + message: deps.name.padEnd(longestNameLength), hint: `(${color(deps.diff)}: ${deps.resolvedVersion} -> ${color(deps.latestVersion)})`, }; }), diff --git a/scripts/generate-acknowledgments.mjs b/scripts/generate-acknowledgments.mjs index 76c0dd5013..66466ee85e 100644 --- a/scripts/generate-acknowledgments.mjs +++ b/scripts/generate-acknowledgments.mjs @@ -13,11 +13,7 @@ import packageJson from '../package.json' with { type: 'json' }; // Enable this flag to throw an error. const REQUIRE_SIGNAL_LIB_FILES = Boolean(process.env.REQUIRE_SIGNAL_LIB_FILES); -const { - dependencies = {}, - devDependencies = {}, - optionalDependencies = {}, -} = packageJson; +const { dependencies, devDependencies, optionalDependencies } = packageJson; const SIGNAL_LIBS = [ '@signalapp/libsignal-client', diff --git a/scripts/get-emoji-locales.mjs b/scripts/get-emoji-locales.mjs index cd35007681..5edd83880c 100644 --- a/scripts/get-emoji-locales.mjs +++ b/scripts/get-emoji-locales.mjs @@ -34,8 +34,8 @@ async function fetchJSON(url) { const manifest = ManifestSchema.parse(await fetchJSON(MANIFEST_URL)); -manifest.languageToSmartlingLocale['zh_TW'] = 'zh-Hant'; -manifest.languageToSmartlingLocale['sr'] = 'sr'; +manifest.languageToSmartlingLocale.zh_TW = 'zh-Hant'; +manifest.languageToSmartlingLocale.sr = 'sr'; /** @type {Map} */ const extraResources = new Map(); diff --git a/scripts/notarize-universal-dmg.mjs b/scripts/notarize-universal-dmg.mjs index c4955fb916..ffa54dc710 100644 --- a/scripts/notarize-universal-dmg.mjs +++ b/scripts/notarize-universal-dmg.mjs @@ -19,7 +19,9 @@ export async function afterAllArtifactBuild({ platform => platform.name ); if (platforms.length !== 1) { - console.log(`notarize: Skipping, too many platforms ${platforms}`); + console.log( + `notarize: Skipping, too many platforms ${platforms.join(', ')}` + ); return; } @@ -63,7 +65,9 @@ export async function afterAllArtifactBuild({ /^.*mac-universal.*\.dmg$/.test(artifactPath) ); if (artifactsToStaple.length !== 1) { - console.log(`notarize: Skipping, too many dmgs ${artifactsToStaple}`); + console.log( + `notarize: Skipping, too many dmgs ${artifactsToStaple.join(', ')}` + ); return; } diff --git a/scripts/prepare_linux_build.mjs b/scripts/prepare_linux_build.mjs index 17a10cc91f..5434c28312 100644 --- a/scripts/prepare_linux_build.mjs +++ b/scripts/prepare_linux_build.mjs @@ -13,7 +13,7 @@ if ( !targets.every(target => TARGETS.has(target.toLowerCase())) ) { console.error( - `Invalid linux targets ${targets}. Valid options: ${[...TARGETS]}` + `Invalid linux targets ${targets.join(', ')}. Valid options: ${[...TARGETS].join(', ')}` ); process.exit(1); } diff --git a/scripts/sign-macos.mjs b/scripts/sign-macos.mjs index 3e510f6840..4a3af61ae7 100644 --- a/scripts/sign-macos.mjs +++ b/scripts/sign-macos.mjs @@ -37,6 +37,7 @@ export async function sign(configuration) { }); if (returnCode) { + // oxlint-disable-next-line typescript/restrict-template-expressions throw new Error(`sign-macos: Script returned code ${returnCode}`); } } diff --git a/scripts/sign-windows.mjs b/scripts/sign-windows.mjs index b2cd1c3865..445b03ff50 100644 --- a/scripts/sign-windows.mjs +++ b/scripts/sign-windows.mjs @@ -34,6 +34,7 @@ export async function sign(configuration) { }); if (returnCode) { + // oxlint-disable-next-line typescript/restrict-template-expressions throw new Error(`sign-windows: Script returned code ${returnCode}`); } } diff --git a/scripts/test-electron.mjs b/scripts/test-electron.mjs index 511e4982ba..2d21f62ab1 100644 --- a/scripts/test-electron.mjs +++ b/scripts/test-electron.mjs @@ -183,7 +183,7 @@ async function launchElectron(worker, attempt) { ]); } catch (error) { if (exitSignal && RETRIABLE_SIGNALS.includes(exitSignal)) { - return launchElectron(worker, attempt + 1); + return await launchElectron(worker, attempt + 1); } throw error; } finally { diff --git a/ts/CI.preload.ts b/ts/CI.preload.ts index 3a4d962592..70598d58ca 100644 --- a/ts/CI.preload.ts +++ b/ts/CI.preload.ts @@ -216,7 +216,7 @@ export function getCI({ const { error } = await backupsService.stageLocalBackupForImport(snapshotDir); if (error) { - throw error; + throw new Error(error); } } diff --git a/ts/ConversationController.preload.ts b/ts/ConversationController.preload.ts index d4a46a1893..3153d42925 100644 --- a/ts/ConversationController.preload.ts +++ b/ts/ConversationController.preload.ts @@ -172,9 +172,9 @@ export class ConversationController { #_initialPromise: undefined | Promise; #_conversations: Array = []; - #_conversationOpenStart = new Map(); + readonly #_conversationOpenStart = new Map(); #_hasQueueEmptied = false; - #_combineConversationsQueue = new PQueue({ concurrency: 1 }); + readonly #_combineConversationsQueue = new PQueue({ concurrency: 1 }); #_signalConversationId: undefined | string; #delayBeforeUpdatingRedux: (() => number) | undefined; @@ -187,7 +187,7 @@ export class ConversationController { #_byGroupId: Record = Object.create(null); #_byId: Record = Object.create(null); - #debouncedUpdateUnreadCount = debounce( + readonly #debouncedUpdateUnreadCount = debounce( this.updateUnreadCount.bind(this), SECOND, { @@ -197,7 +197,7 @@ export class ConversationController { } ); - #convoUpdateBatcher = createBatcher< + readonly #convoUpdateBatcher = createBatcher< | { type: 'change' | 'add'; conversation: ConversationModel } | { type: 'remove'; id: string } >({ @@ -955,8 +955,7 @@ export class ConversationController { } else if (targetConversation && !targetConversation?.get(key)) { // This is mostly for the situation where PNI was erased when updating e164 log.debug( - `${logId}: Re-adding ${key} on target conversation - ` + - `${targetConversation.idForLogging()}` + `${logId}: Re-adding ${key} on target conversation - ${targetConversation.idForLogging()}` ); applyChangeToConversation(targetConversation, pniSignatureVerified, { [key]: value, @@ -1413,8 +1412,7 @@ export class ConversationController { log.warn( `${logId}: Ensure that all V1 groups have new conversationId instead of old` ); - const groups = - await this.getAllGroupsInvolvingServiceId(obsoleteServiceId); + const groups = this.getAllGroupsInvolvingServiceId(obsoleteServiceId); groups.forEach(group => { const members = group.get('members'); const withoutObsolete = without(members, obsoleteId); diff --git a/ts/RemoteConfig.dom.ts b/ts/RemoteConfig.dom.ts index cb83cd928a..d9456d3cb1 100644 --- a/ts/RemoteConfig.dom.ts +++ b/ts/RemoteConfig.dom.ts @@ -203,7 +203,7 @@ export const _refreshRemoteConfig = async ({ // new configuration only includes enabled flags we can't distinguish betewen // a remote flag being deleted or being disabled. We synthesize that for our // known keys. - const newConfigValues: Map = new Map( + const newConfigValues = new Map( KnownConfigKeys.map(name => [name, undefined]) ); for (const [name, value] of newConfig) { diff --git a/ts/SignalProtocolStore.preload.ts b/ts/SignalProtocolStore.preload.ts index a313a9c685..824c19d531 100644 --- a/ts/SignalProtocolStore.preload.ts +++ b/ts/SignalProtocolStore.preload.ts @@ -121,6 +121,7 @@ function validateIdentityKey(attrs: unknown): attrs is IdentityKeyType { */ function formatKeys(keys: Array): string { return formatGroups( + // oxlint-disable-next-line typescript/require-array-sort-compare groupWhile(keys.sort(), (a, b) => a + 1 === b).slice(0, 10), '-', ', ', @@ -246,9 +247,9 @@ export class SignalProtocolStore extends EventEmitter { // Cached values - #ourIdentityKeys = new Map(); + readonly #ourIdentityKeys = new Map(); - #ourRegistrationIds = new Map(); + readonly #ourRegistrationIds = new Map(); #cachedPniSignatureMessage: PniSignatureMessageType | undefined; identityKeys?: Map< diff --git a/ts/WebAudioRecorder.std.ts b/ts/WebAudioRecorder.std.ts index 90dbc0c37d..ea8a8ada2a 100644 --- a/ts/WebAudioRecorder.std.ts +++ b/ts/WebAudioRecorder.std.ts @@ -20,12 +20,12 @@ type OptionsType = { }; export class WebAudioRecorder { - #buffer: Array; - #options: OptionsType; - #context: BaseAudioContext; - #input: GainNode; - #onComplete: (recorder: WebAudioRecorder, blob: Blob) => unknown; - #onError: (recorder: WebAudioRecorder, error: string) => unknown; + readonly #buffer: Array; + readonly #options: OptionsType; + readonly #context: BaseAudioContext; + readonly #input: GainNode; + readonly #onComplete: (recorder: WebAudioRecorder, blob: Blob) => unknown; + readonly #onError: (recorder: WebAudioRecorder, error: string) => unknown; private processor?: ScriptProcessorNode; public worker?: Worker; diff --git a/ts/axo/AxoScrollArea.dom.tsx b/ts/axo/AxoScrollArea.dom.tsx index a2476e8819..c251a279cc 100644 --- a/ts/axo/AxoScrollArea.dom.tsx +++ b/ts/axo/AxoScrollArea.dom.tsx @@ -100,7 +100,7 @@ export namespace AxoScrollArea { orientation = 'vertical', maxWidth, maxHeight, - scrollbarWidth = 'thin', + scrollbarWidth, scrollbarGutter = 'stable-both-edges', scrollbarVisibility = 'auto', scrollBehavior = 'auto', diff --git a/ts/axo/_internal/assert.std.tsx b/ts/axo/_internal/assert.std.tsx index 38171ebd0f..b137e22d5a 100644 --- a/ts/axo/_internal/assert.std.tsx +++ b/ts/axo/_internal/assert.std.tsx @@ -11,6 +11,7 @@ export function assert(input: T, message?: string): NonNullable { if (input === false || input == null) { // oxlint-disable-next-line no-debugger debugger; + // oxlint-disable-next-line typescript/restrict-template-expressions throw new AssertionError(message ?? `input is ${input}`); } return input; diff --git a/ts/axo/_internal/scrollbars.dom.tsx b/ts/axo/_internal/scrollbars.dom.tsx index d21afe3347..07765497b3 100644 --- a/ts/axo/_internal/scrollbars.dom.tsx +++ b/ts/axo/_internal/scrollbars.dom.tsx @@ -17,11 +17,11 @@ type Listener = () => void; type Unsubscribe = () => void; class ScrollbarGuttersObserver { - #container: HTMLDivElement; - #scroller: HTMLDivElement; + readonly #container: HTMLDivElement; + readonly #scroller: HTMLDivElement; #current: ScrollbarGutters | null; - #observer: ResizeObserver; - #listeners = new Set(); + readonly #observer: ResizeObserver; + readonly #listeners = new Set(); constructor(scrollbarWidth: Exclude) { const container = document.createElement('div'); diff --git a/ts/background.preload.ts b/ts/background.preload.ts index c2d4a136d6..1d9f9227b3 100644 --- a/ts/background.preload.ts +++ b/ts/background.preload.ts @@ -815,6 +815,7 @@ export async function startApp(): Promise { await convo.shutdownJobQueue(); } catch (err) { log.error( + // oxlint-disable-next-line typescript/restrict-template-expressions `shutdown: error waiting for conversation ${convo.idForLogging} job queue shutdown`, Errors.toLogFormat(err) ); @@ -1375,7 +1376,7 @@ export async function startApp(): Promise { StorageService.enableStorageService(); if (andSync != null) { - await StorageService.runStorageServiceSyncJob({ + StorageService.runStorageServiceSyncJob({ reason: andSync, }); StorageService.runStorageServiceSyncJob.flush(); @@ -1682,12 +1683,12 @@ export async function startApp(): Promise { if (!itemStorage.user.getAci()) { log.error(`${logId}: ACI not captured during registration, unlinking`); - return unlinkAndDisconnect(); + return await unlinkAndDisconnect(); } if (!itemStorage.user.getPni()) { log.error(`${logId}: PNI not captured during registration, unlinking`); - return unlinkAndDisconnect(); + return await unlinkAndDisconnect(); } // 2. Fetch remote config, before we process the message queue @@ -1857,7 +1858,7 @@ export async function startApp(): Promise { } async function afterEveryLinkedStartupOnNewVersion({ - skipSyncRequests = false, + skipSyncRequests, }: { skipSyncRequests: boolean; }) { @@ -3587,7 +3588,7 @@ export async function startApp(): Promise { } } - await StorageService.runStorageServiceSyncJob({ reason: 'onKeysSync' }); + StorageService.runStorageServiceSyncJob({ reason: 'onKeysSync' }); } ev.confirm(); } diff --git a/ts/badges/badgeImageFileDownloader.preload.ts b/ts/badges/badgeImageFileDownloader.preload.ts index 7b0bff729b..611b650ffb 100644 --- a/ts/badges/badgeImageFileDownloader.preload.ts +++ b/ts/badges/badgeImageFileDownloader.preload.ts @@ -20,7 +20,7 @@ enum BadgeDownloaderState { class BadgeImageFileDownloader { #state = BadgeDownloaderState.Idle; - #queue = new PQueue({ concurrency: 3 }); + readonly #queue = new PQueue({ concurrency: 3 }); public async checkForFilesToDownload(): Promise { switch (this.#state) { diff --git a/ts/calling/VideoSupport.preload.ts b/ts/calling/VideoSupport.preload.ts index c6948717b0..5251f09f73 100644 --- a/ts/calling/VideoSupport.preload.ts +++ b/ts/calling/VideoSupport.preload.ts @@ -52,7 +52,7 @@ export class GumVideoCapturer { private mediaStream?: MediaStream; private spawnedSenderRunning = false; private preferredDeviceId?: string; - private reportVideoSizeCallback = this.reportVideoSize.bind(this); + private readonly reportVideoSizeCallback = this.reportVideoSize.bind(this); capturing(): boolean { return this.captureOptions !== undefined; @@ -418,7 +418,7 @@ export const MAX_VIDEO_CAPTURE_BUFFER_SIZE = MAX_VIDEO_CAPTURE_AREA * 4; export class CanvasVideoRenderer { private canvas?: RefObject; private sizeCallback?: SizeCallbackType; - private buffer: Uint8Array; + private readonly buffer: Uint8Array; private imageData?: ImageData; private source?: VideoFrameSource; private rafId?: ReturnType; diff --git a/ts/challenge.dom.ts b/ts/challenge.dom.ts index 6b4f902b64..ed6d3d4533 100644 --- a/ts/challenge.dom.ts +++ b/ts/challenge.dom.ts @@ -126,6 +126,8 @@ 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 { + readonly #options: Options; + #solving = 0; #isLoaded = false; #challengeToken: string | undefined; @@ -142,7 +144,9 @@ export class ChallengeHandler { readonly #startTimers = new Map(); readonly #pendingStarts = new Set(); - constructor(private readonly options: Options) {} + constructor(options: Options) { + this.#options = options; + } public async load(): Promise { if (this.#isLoaded) { @@ -151,13 +155,13 @@ export class ChallengeHandler { this.#isLoaded = true; const challenges: ReadonlyArray = - this.options.storage.get(STORAGE_KEY) || []; + this.#options.storage.get(STORAGE_KEY) || []; log.info(`loading ${challenges.length} challenges`); await Promise.all( challenges.map(async challenge => { - const expireAfter = this.options.expireAfter || DEFAULT_EXPIRE_AFTER; + const expireAfter = this.#options.expireAfter || DEFAULT_EXPIRE_AFTER; if (isOlderThan(challenge.createdAt, expireAfter)) { log.info( `expired challenge for conversation ${challenge.conversationId}` @@ -192,7 +196,7 @@ export class ChallengeHandler { log.info(`online, starting ${pending.length} queues`); // Start queues for challenges that matured while we were offline - await this.#startAllQueues(); + this.#startAllQueues(); } public maybeSolve({ conversationId, reason }: MaybeSolveOptionsType): void { @@ -342,7 +346,7 @@ export class ChallengeHandler { const request: IPCRequest = { seq: this.#seq, reason }; this.#seq += 1; - this.options.requestChallenge(request); + this.#options.requestChallenge(request); const response = await new Promise((resolve, reject) => { this.#responseHandlers.set(request.seq, { token, resolve, reject }); @@ -356,7 +360,7 @@ export class ChallengeHandler { this.#isLoaded, 'ChallengeHandler has to be loaded before persisting new data' ); - await this.options.storage.put( + await this.#options.storage.put( STORAGE_KEY, Array.from(this.#registeredConversations.values()) ); @@ -391,16 +395,16 @@ export class ChallengeHandler { await this.unregister(conversationId, 'startQueue'); if (this.#registeredConversations.size === 0) { - this.options.setChallengeStatus('idle'); + this.#options.setChallengeStatus('idle'); } log.info(`startQueue: starting queue ${conversationId}`); - this.options.startQueue(conversationId); + this.#options.startQueue(conversationId); } async #solve({ reason, token }: SolveOptionsType): Promise { this.#solving += 1; - this.options.setChallengeStatus('required'); + this.#options.setChallengeStatus('required'); this.#challengeToken = token; const captcha = await this.requestCaptcha({ reason, token }); @@ -414,12 +418,12 @@ export class ChallengeHandler { const lastToken = this.#challengeToken; this.#challengeToken = undefined; - this.options.setChallengeStatus('pending'); + this.#options.setChallengeStatus('pending'); log.info(`challenge(${reason}): sending challenge to server`); try { - await this.options.sendChallengeResponse({ + await this.#options.sendChallengeResponse({ type: 'captcha', token: lastToken, captcha, @@ -454,8 +458,8 @@ export class ChallengeHandler { // Remove the challenge dialog, and trigger the conversationJobQueue to retry the // sends, which will likely trigger another captcha - this.options.setChallengeStatus('idle'); - this.options.onChallengeFailed(retryAfter); + this.#options.setChallengeStatus('idle'); + this.#options.onChallengeFailed(retryAfter); this.forceWaitOnAll(retryAt); return; } finally { @@ -464,8 +468,8 @@ export class ChallengeHandler { log.info(`challenge(${reason}): challenge success. force sending`); - this.options.setChallengeStatus('idle'); - this.options.onChallengeSolved(); + this.#options.setChallengeStatus('idle'); + this.#options.onChallengeSolved(); this.#startAllQueues({ force: true }); } } diff --git a/ts/components/AvatarLightbox.dom.tsx b/ts/components/AvatarLightbox.dom.tsx index e01332130a..a9cf707e01 100644 --- a/ts/components/AvatarLightbox.dom.tsx +++ b/ts/components/AvatarLightbox.dom.tsx @@ -63,7 +63,7 @@ export function AvatarLightbox({ width: 'auto', minHeight: '64px', height: '100%', - maxHeight: `min(${512}px, 100%)`, + maxHeight: `min(512px, 100%)`, aspectRatio: '1 / 1', }} /> diff --git a/ts/components/CallScreen.dom.stories.tsx b/ts/components/CallScreen.dom.stories.tsx index 9ab08d48dd..7539040b7e 100644 --- a/ts/components/CallScreen.dom.stories.tsx +++ b/ts/components/CallScreen.dom.stories.tsx @@ -753,11 +753,9 @@ function useMakeEveryoneTalk( Math.random() * call.remoteParticipants.length ); - const demuxIdToStartSpeaking = ( - call.remoteParticipants[ - idxToStartSpeaking - ] as GroupCallRemoteParticipantType - ).demuxId; + const demuxIdToStartSpeaking = + // oxlint-disable-next-line typescript/no-non-null-assertion + call.remoteParticipants[idxToStartSpeaking]!.demuxId; const remoteAudioLevels = new Map(); @@ -776,9 +774,7 @@ function useMakeEveryoneTalk( hasRemoteAudio: idx === idxToStartSpeaking ? true : part.hasRemoteAudio, speakerTime: - idx === idxToStartSpeaking - ? Date.now() - : (part as GroupCallRemoteParticipantType).speakerTime, + idx === idxToStartSpeaking ? Date.now() : part.speakerTime, }; }), remoteAudioLevels, @@ -899,7 +895,8 @@ function useReactionsEmitter({ { timestamp: timeNow, demuxId, - value: sample(emojis) as string, + // oxlint-disable-next-line typescript/no-non-null-assertion + value: sample(emojis)!, }, ]; diff --git a/ts/components/CallScreen.dom.tsx b/ts/components/CallScreen.dom.tsx index fdeb0118b8..07bc2fb58a 100644 --- a/ts/components/CallScreen.dom.tsx +++ b/ts/components/CallScreen.dom.tsx @@ -161,7 +161,7 @@ export type PropsType = { export const isInSpeakerView = ( call: Pick | undefined ): boolean => { - return Boolean( + return ( call?.viewMode === CallViewMode.Presentation || call?.viewMode === CallViewMode.Speaker ); diff --git a/ts/components/CallingPendingParticipants.dom.tsx b/ts/components/CallingPendingParticipants.dom.tsx index 320fe0c320..26922f1959 100644 --- a/ts/components/CallingPendingParticipants.dom.tsx +++ b/ts/components/CallingPendingParticipants.dom.tsx @@ -128,7 +128,7 @@ export function CallingPendingParticipants({ }, [serviceIdsStagedForAction, batchUserAction, hideConfirmDialog]); const renderApprovalButtons = useCallback( - (participant: ConversationType, isEnabled: boolean = true) => { + (participant: ConversationType, isEnabled = true) => { if (participant.serviceId == null) { return null; } diff --git a/ts/components/CallingRaisedHandsList.dom.tsx b/ts/components/CallingRaisedHandsList.dom.tsx index cbded176f3..7cf7670e45 100644 --- a/ts/components/CallingRaisedHandsList.dom.tsx +++ b/ts/components/CallingRaisedHandsList.dom.tsx @@ -42,7 +42,7 @@ export function CallingRaisedHandsList({ : undefined; const participants = React.useMemo>(() => { - const serviceIds: Set = new Set(); + const serviceIds = new Set(); const conversations: Array = []; raisedHands.forEach(demuxId => { const conversation = conversationsByDemuxId.get(demuxId); diff --git a/ts/components/CompositionArea.dom.stories.tsx b/ts/components/CompositionArea.dom.stories.tsx index d82973eb3f..7d88029840 100644 --- a/ts/components/CompositionArea.dom.stories.tsx +++ b/ts/components/CompositionArea.dom.stories.tsx @@ -50,7 +50,7 @@ const memberColors = new Map( return null; } return [ - admin.member.id?.toString(), + admin.member.id, // oxlint-disable-next-line typescript/no-non-null-assertion ContactNameColors[i % ContactNameColors.length]!, ]; diff --git a/ts/components/CompositionInput.dom.tsx b/ts/components/CompositionInput.dom.tsx index f0d85330e1..99709cef89 100644 --- a/ts/components/CompositionInput.dom.tsx +++ b/ts/components/CompositionInput.dom.tsx @@ -665,7 +665,7 @@ export function CompositionInput(props: Props): React.ReactElement { } if (propsRef.current.onDirtyChange) { - let isDirty: boolean = false; + let isDirty = false; if (!draftEditMessage) { isDirty = text.length > 0; diff --git a/ts/components/CompositionTextArea.dom.tsx b/ts/components/CompositionTextArea.dom.tsx index 521e868ba4..f7a1be5530 100644 --- a/ts/components/CompositionTextArea.dom.tsx +++ b/ts/components/CompositionTextArea.dom.tsx @@ -25,12 +25,12 @@ export type CompositionTextAreaProps = { maxLength?: number; placeholder?: string; whenToShowRemainingCount?: number; - onScroll?: (ev: React.UIEvent) => void; + onScroll?: (ev: React.UIEvent) => void; onSelectEmoji: (emojiSelection: FunEmojiSelection) => void; onChange: ( messageText: string, draftBodyRanges: HydratedBodyRangesType, - caretLocation?: number | undefined + caretLocation?: number ) => void; emojiSkinToneDefault: EmojiSkinTone; onEmojiSkinToneDefaultChange: (emojiSkinToneDefault: EmojiSkinTone) => void; diff --git a/ts/components/ConversationList.dom.tsx b/ts/components/ConversationList.dom.tsx index f8567bbd79..460da35c3b 100644 --- a/ts/components/ConversationList.dom.tsx +++ b/ts/components/ConversationList.dom.tsx @@ -605,6 +605,7 @@ export function ConversationList({ showConversation={showConversation} /> ); + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions key = `start-new-conversation:${row.phoneNumber}`; break; case RowType.UsernameSearchResult: diff --git a/ts/components/EditHistoryMessagesModal.dom.tsx b/ts/components/EditHistoryMessagesModal.dom.tsx index 32cf5834da..a825c40e7d 100644 --- a/ts/components/EditHistoryMessagesModal.dom.tsx +++ b/ts/components/EditHistoryMessagesModal.dom.tsx @@ -191,12 +191,11 @@ export function EditHistoryMessagesModal({ {pastEdits.map(messageAttributes => { const syntheticId = `${messageAttributes.id}.${messageAttributes.timestamp}`; - const shouldShowDateHeader = Boolean( + const shouldShowDateHeader = !previousItem || // This comparison avoids strange header behavior for out-of-order messages. (messageAttributes.timestamp > previousItem.timestamp && - !isSameDay(previousItem.timestamp, messageAttributes.timestamp)) - ); + !isSameDay(previousItem.timestamp, messageAttributes.timestamp)); const dateHeaderElement = shouldShowDateHeader ? ( = React.memo( imageData?.data.buffer !== frameBuffer.buffer || imageData?.data.byteOffset !== frameBuffer.byteOffset ) { - const view = new Uint8ClampedArray( + const view = new Uint8ClampedArray( frameBuffer.buffer, frameBuffer.byteOffset, frameWidth * frameHeight * 4 diff --git a/ts/components/GroupCallRemoteParticipants.dom.tsx b/ts/components/GroupCallRemoteParticipants.dom.tsx index 01750f9c89..514b64d28e 100644 --- a/ts/components/GroupCallRemoteParticipants.dom.tsx +++ b/ts/components/GroupCallRemoteParticipants.dom.tsx @@ -724,7 +724,7 @@ function getGridParticipantsByPage({ ...pageLayoutProps, }); - let nextPage: ParticipantsInPageType | undefined; + let nextPage: ParticipantsInPageType | undefined; if ( nextPageInSortedOrder.numParticipants === @@ -806,12 +806,9 @@ function getGridParticipantsByPage({ break; } - const nextPageTiles = - nextPage as ParticipantsInPageType; - // Add a previous page tile if needed if (pages.length > 0) { - const firstRow = nextPageTiles.rows[0]; + const firstRow = nextPage.rows[0]; strictAssert(firstRow, 'Missing firstRow'); firstRow.unshift({ isPaginationButton: true, @@ -837,7 +834,7 @@ function getGridParticipantsByPage({ // Add a next page tile if needed if (remainingParticipants.length) { - nextPageTiles.rows.at(-1)?.push({ + nextPage.rows.at(-1)?.push({ isPaginationButton: true, paginationButtonType: 'next', videoAspectRatio: PAGINATION_BUTTON_ASPECT_RATIO, diff --git a/ts/components/Lightbox.dom.tsx b/ts/components/Lightbox.dom.tsx index 89601c7ca7..af79b9c92a 100644 --- a/ts/components/Lightbox.dom.tsx +++ b/ts/components/Lightbox.dom.tsx @@ -174,7 +174,7 @@ export function Lightbox({ setShouldShowDownloadToast(false); }, [isDownloading, setShouldShowDownloadToast]); const onUserInteractionOnVideo = useCallback( - (event: React.MouseEvent) => { + (event: React.MouseEvent) => { if (downloadToastTimeout.current) { clearTimeout(downloadToastTimeout.current); downloadToastTimeout.current = undefined; @@ -196,9 +196,7 @@ export function Lightbox({ ); const onPrevious = useCallback( - ( - event: KeyboardEvent | React.MouseEvent - ) => { + (event: KeyboardEvent | React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); @@ -212,9 +210,7 @@ export function Lightbox({ ); const onNext = useCallback( - ( - event: KeyboardEvent | React.MouseEvent - ) => { + (event: KeyboardEvent | React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); @@ -235,9 +231,7 @@ export function Lightbox({ }, [setVideoTime, videoElement]); const handleSave = useCallback( - ( - event: KeyboardEvent | React.MouseEvent - ) => { + (event: KeyboardEvent | React.MouseEvent) => { if (isViewOnce) { return; } @@ -254,9 +248,7 @@ export function Lightbox({ [isViewOnce, media, saveAttachment, selectedIndex] ); - const handleForward = ( - event: React.MouseEvent - ) => { + const handleForward = (event: React.MouseEvent) => { if (isViewOnce) { return; } @@ -524,7 +516,7 @@ export function Lightbox({ ); const zoomButtonHandler = useCallback( - (ev: React.MouseEvent) => { + (ev: React.MouseEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -850,10 +842,7 @@ export function Lightbox({ key={item.attachment.thumbnail?.url} type="button" onClick={( - event: React.MouseEvent< - HTMLButtonElement, - MouseEvent - > + event: React.MouseEvent ) => { event.stopPropagation(); event.preventDefault(); diff --git a/ts/components/ListTile.dom.tsx b/ts/components/ListTile.dom.tsx index 026ee08189..24752834e9 100644 --- a/ts/components/ListTile.dom.tsx +++ b/ts/components/ListTile.dom.tsx @@ -14,7 +14,7 @@ export type Props = { trailing?: string | React.JSX.Element; moduleClassName?: string; onClick?: () => void; - onContextMenu?: (ev: React.MouseEvent) => void; + onContextMenu?: (ev: React.MouseEvent) => void; // show hover highlight, // defaults to true if onClick is defined clickable?: boolean; diff --git a/ts/components/MediaEditor.dom.stories.tsx b/ts/components/MediaEditor.dom.stories.tsx index fd5c0398a9..6670907aef 100644 --- a/ts/components/MediaEditor.dom.stories.tsx +++ b/ts/components/MediaEditor.dom.stories.tsx @@ -22,6 +22,7 @@ export default { getPreferredBadge: () => undefined, isHighQuality: false, i18n, + // oxlint-disable-next-line typescript/no-base-to-string imageToBlurHash: input => Promise.resolve(input.toString()), imageSrc: IMAGE_2, isFormattingEnabled: true, diff --git a/ts/components/Preferences.dom.tsx b/ts/components/Preferences.dom.tsx index 8a59146388..89a06177e0 100644 --- a/ts/components/Preferences.dom.tsx +++ b/ts/components/Preferences.dom.tsx @@ -559,7 +559,7 @@ export function Preferences({ localeOverride, theme, themeSetting, - universalExpireTimer = DurationInSeconds.ZERO, + universalExpireTimer, validateBackup, whoCanFindMe, whoCanSeeMe, @@ -660,7 +660,7 @@ export function Preferences({ const handleContentProtectionChange = useCallback( (value: boolean) => { - if (value === true || !isContentProtectionNeeded) { + if (value || !isContentProtectionNeeded) { onContentProtectionChange(value); } else { setConfirmContentProtection(true); @@ -1969,7 +1969,7 @@ export function Preferences({ <> { setStagedProfile(profileData => ({ ...profileData, - firstName: String(newFirstName), + firstName: newFirstName, })); }} placeholder={i18n('icu:ProfileEditor--first-name')} diff --git a/ts/components/StoryLinkPreview.dom.tsx b/ts/components/StoryLinkPreview.dom.tsx index e7a647b876..84eabbe638 100644 --- a/ts/components/StoryLinkPreview.dom.tsx +++ b/ts/components/StoryLinkPreview.dom.tsx @@ -28,7 +28,7 @@ export function StoryLinkPreview({ url, }: Props): React.JSX.Element { const isImage = isImageAttachment(image); - const location = domain || getSafeDomain(String(url)); + const location = domain || getSafeDomain(url); const isCompact = forceCompactMode || !image; let content: React.JSX.Element | undefined; diff --git a/ts/components/TextAttachment.dom.tsx b/ts/components/TextAttachment.dom.tsx index 3933a0ef5a..aa1d5597ae 100644 --- a/ts/components/TextAttachment.dom.tsx +++ b/ts/components/TextAttachment.dom.tsx @@ -297,7 +297,7 @@ export const TextAttachment = forwardRef( )} { setSelectedBackground(backgroundValue); setIsColorPickerShowing(false); diff --git a/ts/components/ToastManager.dom.tsx b/ts/components/ToastManager.dom.tsx index 06b627c4bf..10915ceed7 100644 --- a/ts/components/ToastManager.dom.tsx +++ b/ts/components/ToastManager.dom.tsx @@ -280,7 +280,7 @@ export function renderToast({ toastAction={{ label: i18n('icu:conversationArchivedUndo'), onClick: () => { - onUndoArchive(String(toast.parameters.conversationId), { + onUndoArchive(toast.parameters.conversationId, { wasPinned: toast.parameters.wasPinned, }); }, diff --git a/ts/components/conversation/ContactName.dom.tsx b/ts/components/conversation/ContactName.dom.tsx index aa78f399f9..c14a2299a5 100644 --- a/ts/components/conversation/ContactName.dom.tsx +++ b/ts/components/conversation/ContactName.dom.tsx @@ -89,7 +89,7 @@ export function ContactName({ contactNameColor ? getClassName(`--${contactNameColor}`) : null )} dir="auto" - onClick={(event: React.MouseEvent) => { + onClick={(event: React.MouseEvent) => { if (onClick) { onClick(); event.stopPropagation(); diff --git a/ts/components/conversation/ConversationHeader.dom.tsx b/ts/components/conversation/ConversationHeader.dom.tsx index 6812546fa8..96540edcec 100644 --- a/ts/components/conversation/ConversationHeader.dom.tsx +++ b/ts/components/conversation/ConversationHeader.dom.tsx @@ -642,12 +642,11 @@ function HeaderDropdownMenuContent({ }) { const muteOptions = getMuteOptions(conversation.muteExpiresAt, i18n); const isGroup = conversation.type === 'group'; - const disableTimerChanges = Boolean( + const disableTimerChanges = !conversation.canChangeTimer || !conversation.acceptedMessageRequest || conversation.left || - isMissingMandatoryProfileSharing - ); + isMissingMandatoryProfileSharing; const hasGV2AdminEnabled = isGroup && conversation.groupVersion === 2; const disappearingMessagesValue = useMemo(() => { diff --git a/ts/components/conversation/GIF.dom.tsx b/ts/components/conversation/GIF.dom.tsx index 6bb2faad5a..af7ae578a7 100644 --- a/ts/components/conversation/GIF.dom.tsx +++ b/ts/components/conversation/GIF.dom.tsx @@ -44,7 +44,7 @@ export type Props = { cancelDownload(): void; }; -type MediaEvent = React.SyntheticEvent; +type MediaEvent = React.SyntheticEvent; export function GIF(props: Props): React.JSX.Element { const { diff --git a/ts/components/conversation/Message.dom.tsx b/ts/components/conversation/Message.dom.tsx index 49f3655e4c..27e05568fa 100644 --- a/ts/components/conversation/Message.dom.tsx +++ b/ts/components/conversation/Message.dom.tsx @@ -493,7 +493,7 @@ const MessageReactions = forwardRef(function MessageReactions( // Take the first three groups for rendering const toRender = take(ordered, 3).map(group => { - const isMe = group.some(re => Boolean(re.from.isMe)); + const isMe = group.some(re => re.from.isMe); const count = group.length; const firstReaction = group[0]; strictAssert(firstReaction, 'Missing firstReaction'); @@ -531,7 +531,7 @@ const MessageReactions = forwardRef(function MessageReactions( ); const notRenderedIsMe = someNotRendered && - maybeNotRendered.some(res => res.some(re => Boolean(re.from.isMe))); + maybeNotRendered.some(res => res.some(re => re.from.isMe)); const popperPlacement = outgoing ? 'bottom-end' : 'bottom-start'; @@ -658,11 +658,12 @@ export class Message extends React.PureComponent { public reactionsContainerRef: React.RefObject = React.createRef(); - #hasSelectedTextRef: React.MutableRefObject = { + readonly #hasSelectedTextRef: React.MutableRefObject = { current: false, }; - #metadataRef: React.RefObject = React.createRef(); + readonly #metadataRef: React.RefObject = + React.createRef(); public expirationCheckInterval: NodeJS.Timeout | undefined; @@ -1066,7 +1067,7 @@ export class Message extends React.PureComponent { return true; } - #updateMetadataWidth = (newMetadataWidth: number): void => { + readonly #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. @@ -1074,7 +1075,7 @@ export class Message extends React.PureComponent { })); }; - #handleSelectionChange = () => { + readonly #handleSelectionChange = () => { const selection = document.getSelection(); if (selection != null && !selection.isCollapsed) { this.#hasSelectedTextRef.current = true; @@ -2102,7 +2103,7 @@ export class Message extends React.PureComponent { ); } - #doubleCheckMissingQuoteReference = () => { + readonly #doubleCheckMissingQuoteReference = () => { return this.props.doubleCheckMissingQuoteReference(this.props.id); }; @@ -2976,22 +2977,23 @@ export class Message extends React.PureComponent { ); } - #popperPreventOverflowModifier = (): Partial => { - const { containerElementRef } = this.props; - return { - name: 'preventOverflow', - options: { - altAxis: true, - boundary: containerElementRef.current || undefined, - padding: { - bottom: 16, - left: 8, - right: 8, - top: 16, + readonly #popperPreventOverflowModifier = + (): Partial => { + const { containerElementRef } = this.props; + return { + name: 'preventOverflow', + options: { + altAxis: true, + boundary: containerElementRef.current || undefined, + padding: { + bottom: 16, + left: 8, + right: 8, + top: 16, + }, }, - }, + }; }; - }; public toggleReactionViewer = (onlyRemove = false): void => { this.setState(oldState => { diff --git a/ts/components/conversation/MessageMetadata.dom.tsx b/ts/components/conversation/MessageMetadata.dom.tsx index 8c6f005192..1fc5fcd9fc 100644 --- a/ts/components/conversation/MessageMetadata.dom.tsx +++ b/ts/components/conversation/MessageMetadata.dom.tsx @@ -85,9 +85,7 @@ export const MessageMetadata = forwardRef>( const [confirmationType, setConfirmationType] = useState< ConfirmationType | undefined >(); - const withImageNoCaption = Boolean( - !isSticker && !hasText && isShowingImage - ); + const withImageNoCaption = !isSticker && !hasText && isShowingImage; const metadataDirection = isSticker ? undefined : direction; let timestampNode: ReactNode; diff --git a/ts/components/conversation/MessageTextRenderer.dom.tsx b/ts/components/conversation/MessageTextRenderer.dom.tsx index 260a0ed634..7803a258c0 100644 --- a/ts/components/conversation/MessageTextRenderer.dom.tsx +++ b/ts/components/conversation/MessageTextRenderer.dom.tsx @@ -159,9 +159,8 @@ function renderNode({ const key = node.start; if (node.isSpoiler && node.spoilerChildren?.length) { - const isSpoilerHidden = Boolean( - node.isSpoiler && !isSpoilerExpanded[node.spoilerId || 0] - ); + const isSpoilerHidden = + node.isSpoiler && !isSpoilerExpanded[node.spoilerId || 0]; const content = node.spoilerChildren?.map(spoilerNode => renderNode({ direction, diff --git a/ts/components/conversation/Timeline.dom.tsx b/ts/components/conversation/Timeline.dom.tsx index 2f5adfc604..897ba84231 100644 --- a/ts/components/conversation/Timeline.dom.tsx +++ b/ts/components/conversation/Timeline.dom.tsx @@ -166,7 +166,7 @@ export class Timeline extends React.Component< readonly #atBottomDetectorRef = React.createRef(); readonly #lastSeenIndicatorRef = React.createRef(); #intersectionObserver?: IntersectionObserver; - #intersectionRatios: Map = new Map(); + #intersectionRatios = new Map(); // This is a best guess. It will likely be overridden when the timeline is measured. #maxVisibleRows = Math.ceil(window.innerHeight / MIN_ROW_HEIGHT); @@ -184,7 +184,7 @@ export class Timeline extends React.Component< widthBreakpoint: WidthBreakpoint.Wide, }; - #onScrollLockChange = (): void => { + readonly #onScrollLockChange = (): void => { const scrollLocked = this.#scrollerLock.isLocked(); this.setState(() => { // Prevent scroll due to elements shrinking or disappearing (e.g. typing indicators) @@ -198,9 +198,12 @@ export class Timeline extends React.Component< }); }; - #scrollerLock = createScrollerLock('Timeline', this.#onScrollLockChange); + readonly #scrollerLock = createScrollerLock( + 'Timeline', + this.#onScrollLockChange + ); - #onScroll = (event: UIEvent): void => { + readonly #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 @@ -246,7 +249,7 @@ export class Timeline extends React.Component< ?.scrollIntoView({ block: 'center' }); } - #scrollToBottom = (setFocus?: boolean): void => { + readonly #scrollToBottom = (setFocus?: boolean): void => { if (this.#scrollerLock.isLocked()) { return; } @@ -266,12 +269,12 @@ export class Timeline extends React.Component< } }; - #onClickScrollDownButton = (): void => { + readonly #onClickScrollDownButton = (): void => { this.#scrollerLock.onUserInterrupt('onClickScrollDownButton'); this.#scrollDown(false); }; - #scrollDown = (setFocus?: boolean): void => { + readonly #scrollDown = (setFocus?: boolean): void => { if (this.#scrollerLock.isLocked()) { return; } @@ -536,93 +539,97 @@ export class Timeline extends React.Component< return centerMessageId; } - #markNewestBottomVisibleMessageRead = throttle((itemId?: string): void => { - const { id, items, markMessageRead } = this.props; - const messageIdToMarkRead = - itemId ?? this.state.newestBottomVisibleMessageId; + readonly #markNewestBottomVisibleMessageRead = throttle( + (itemId?: string): void => { + const { id, items, markMessageRead } = this.props; + const messageIdToMarkRead = + itemId ?? this.state.newestBottomVisibleMessageId; - if (!messageIdToMarkRead) { - return; - } + if (!messageIdToMarkRead) { + return; + } - const lastIndex = items.length - 1; - const newestBottomVisibleItemIndex = items.findIndex( - item => item.id === messageIdToMarkRead - ); + const lastIndex = items.length - 1; + const newestBottomVisibleItemIndex = items.findIndex( + item => item.id === messageIdToMarkRead + ); - // Mark the newest visible message read if we're at the bottom, or override provided - if ( - messageIdToMarkRead && - (itemId || lastIndex === newestBottomVisibleItemIndex) - ) { - const item = items[newestBottomVisibleItemIndex]; - if (!item || item.type === 'none') { + // Mark the newest visible message read if we're at the bottom, or override provided + if ( + messageIdToMarkRead && + (itemId || lastIndex === newestBottomVisibleItemIndex) + ) { + const item = items[newestBottomVisibleItemIndex]; + if (!item || item.type === 'none') { + markMessageRead(id, messageIdToMarkRead); + return; + } + } + + // We can return early if the newest partially-visible item is not a CollapseSet + const newestPartiallyVisibleIndex = Math.min( + lastIndex, + newestBottomVisibleItemIndex + 1 + ); + const newestPartiallyVisibleItem = items[newestPartiallyVisibleIndex]; + if ( + newestPartiallyVisibleItem && + newestPartiallyVisibleItem.type === 'none' + ) { markMessageRead(id, messageIdToMarkRead); return; } - } - // We can return early if the newest partially-visible item is not a CollapseSet - const newestPartiallyVisibleIndex = Math.min( - lastIndex, - newestBottomVisibleItemIndex + 1 - ); - const newestPartiallyVisibleItem = items[newestPartiallyVisibleIndex]; - if ( - newestPartiallyVisibleItem && - newestPartiallyVisibleItem.type === 'none' - ) { - markMessageRead(id, messageIdToMarkRead); - return; - } - - // Now we need to figure out which of the CollapseSet's inner messages are visible - const collapseSetEl = this.#messagesRef.current?.querySelector( - `[data-item-index="${newestPartiallyVisibleIndex}"]` - ); - const containerWindowRect = - this.#containerRef.current?.getBoundingClientRect(); - if (!collapseSetEl || !containerWindowRect) { - markMessageRead(id, messageIdToMarkRead); - return; - } - - const messageEls = collapseSetEl.querySelectorAll('[data-message-id]'); - const containerWindowBottom = - containerWindowRect.y + containerWindowRect.height; - - let newestFullyVisibleMessage; - for (let i = messageEls.length - 1; i >= 0; i -= 1) { - const messageEl = messageEls[i]; - strictAssert(messageEl, 'No messageEl at index i'); - - // The messages might be rendered, but opacity = 0 - if (!messageEl.checkVisibility({ opacityProperty: true })) { - break; + // Now we need to figure out which of the CollapseSet's inner messages are visible + const collapseSetEl = this.#messagesRef.current?.querySelector( + `[data-item-index="${newestPartiallyVisibleIndex}"]` + ); + const containerWindowRect = + this.#containerRef.current?.getBoundingClientRect(); + if (!collapseSetEl || !containerWindowRect) { + markMessageRead(id, messageIdToMarkRead); + return; } - // Make sure the messages are scrolled into view - const messageRect = messageEl.getBoundingClientRect(); - const bottom = messageRect.y + messageRect.height; + const messageEls = collapseSetEl.querySelectorAll('[data-message-id]'); + const containerWindowBottom = + containerWindowRect.y + containerWindowRect.height; - if (bottom <= containerWindowBottom) { - newestFullyVisibleMessage = messageEl; - break; + let newestFullyVisibleMessage; + for (let i = messageEls.length - 1; i >= 0; i -= 1) { + const messageEl = messageEls[i]; + strictAssert(messageEl, 'No messageEl at index i'); + + // The messages might be rendered, but opacity = 0 + if (!messageEl.checkVisibility({ opacityProperty: true })) { + break; + } + + // Make sure the messages are scrolled into view + const messageRect = messageEl.getBoundingClientRect(); + const bottom = messageRect.y + messageRect.height; + + if (bottom <= containerWindowBottom) { + newestFullyVisibleMessage = messageEl; + break; + } } - } - if (!newestFullyVisibleMessage) { - markMessageRead(id, messageIdToMarkRead); - return; - } + if (!newestFullyVisibleMessage) { + markMessageRead(id, messageIdToMarkRead); + return; + } - const messageId = newestFullyVisibleMessage.getAttribute('data-message-id'); - markMessageRead(id, messageId || messageIdToMarkRead); - }, 500); + const messageId = + newestFullyVisibleMessage.getAttribute('data-message-id'); + markMessageRead(id, messageId || messageIdToMarkRead); + }, + 500 + ); // When the the window becomes active, or when a fullsceen call is ended, we mark read // with a delay, to allow users to navigate away quickly without marking messages read - #markNewestBottomVisibleMessageReadAfterDelay = throttle( + readonly #markNewestBottomVisibleMessageReadAfterDelay = throttle( this.#markNewestBottomVisibleMessageRead, DELAY_BEFORE_MARKING_READ_AFTER_FOCUS, { @@ -854,7 +861,7 @@ export class Timeline extends React.Component< } } - #handleBlur = (event: React.FocusEvent): void => { + readonly #handleBlur = (event: React.FocusEvent): void => { const { clearTargetedMessage } = this.props; const { currentTarget } = event; @@ -878,7 +885,9 @@ export class Timeline extends React.Component< }, 0); }; - #handleKeyDown = (event: React.KeyboardEvent): void => { + readonly #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; @@ -1123,11 +1132,10 @@ export class Timeline extends React.Component< .slice(-SCROLL_DOWN_BUTTON_THRESHOLD) .find(item => item.id === newestBottomVisibleMessageId)); - const areUnreadBelowCurrentPosition = Boolean( + const areUnreadBelowCurrentPosition = areThereAnyMessages && areAnyMessagesUnread && - areAnyMessagesBelowCurrentPosition - ); + areAnyMessagesBelowCurrentPosition; const shouldShowScrollDownButtons = Boolean( areThereAnyMessages && (areUnreadBelowCurrentPosition || areAboveScrollDownButtonThreshold) diff --git a/ts/components/conversation/WaveformScrubber.dom.tsx b/ts/components/conversation/WaveformScrubber.dom.tsx index fb33c83a75..c126536422 100644 --- a/ts/components/conversation/WaveformScrubber.dom.tsx +++ b/ts/components/conversation/WaveformScrubber.dom.tsx @@ -76,11 +76,11 @@ export const WaveformScrubber = React.forwardRef(function WaveformScrubber( let increment: number; if (event.key === 'ArrowUp' || event.key === arrow('end')) { - increment = +SMALL_INCREMENT; + increment = SMALL_INCREMENT; } else if (event.key === 'ArrowDown' || event.key === arrow('start')) { increment = -SMALL_INCREMENT; } else if (event.key === 'PageUp') { - increment = +BIG_INCREMENT; + increment = BIG_INCREMENT; } else if (event.key === 'PageDown') { increment = -BIG_INCREMENT; } else { diff --git a/ts/components/conversation/conversation-details/ConversationDetailsIcon.dom.tsx b/ts/components/conversation/conversation-details/ConversationDetailsIcon.dom.tsx index ab195f02dc..10ca090263 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsIcon.dom.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsIcon.dom.tsx @@ -81,7 +81,7 @@ export function ConversationDetailsIcon({ role="button" className={bem('button')} tabIndex={0} - onClick={(event: React.MouseEvent) => { + onClick={(event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); onClick(); @@ -106,7 +106,7 @@ export function ConversationDetailsIcon({ className={bem('button')} disabled={disabled} type="button" - onClick={(event: React.MouseEvent) => { + onClick={(event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); onClick(); diff --git a/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.dom.tsx b/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.dom.tsx index c188b9442b..8e5cc4145c 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.dom.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.dom.tsx @@ -197,7 +197,7 @@ export function ConversationDetailsMembershipList({ /> ); })} - {showAllMembers === false && shouldHideRestMembers && ( + {!showAllMembers && shouldHideRestMembers && ( { - if (isSaving === false && previousIsSaving !== isSaving && !isDirty) { + if (!isSaving && previousIsSaving !== isSaving && !isDirty) { popPanelForConversation(); } }, [isDirty, isSaving, popPanelForConversation, previousIsSaving]); diff --git a/ts/components/conversation/media-gallery/MediaGallery.dom.tsx b/ts/components/conversation/media-gallery/MediaGallery.dom.tsx index fe7bf1acf6..476ad7c48f 100644 --- a/ts/components/conversation/media-gallery/MediaGallery.dom.tsx +++ b/ts/components/conversation/media-gallery/MediaGallery.dom.tsx @@ -257,7 +257,7 @@ export function MediaGallery({ // Reset local state when redux finishes loading useEffect(() => { - if (reduxLoading === false) { + if (!reduxLoading) { setLoading(false); } }, [reduxLoading]); diff --git a/ts/components/conversation/poll-message/PollMessageContents.dom.tsx b/ts/components/conversation/poll-message/PollMessageContents.dom.tsx index 1dbdb4a4a1..1878d3bd90 100644 --- a/ts/components/conversation/poll-message/PollMessageContents.dom.tsx +++ b/ts/components/conversation/poll-message/PollMessageContents.dom.tsx @@ -290,7 +290,7 @@ export function PollMessageContents({ - handlePollOptionClicked(index, Boolean(next)) + handlePollOptionClicked(index, next) } isIncoming={isIncoming} isPending={isVotePending} diff --git a/ts/components/conversationList/BaseConversationListItem.dom.tsx b/ts/components/conversationList/BaseConversationListItem.dom.tsx index 595a91d54f..c747e036a5 100644 --- a/ts/components/conversationList/BaseConversationListItem.dom.tsx +++ b/ts/components/conversationList/BaseConversationListItem.dom.tsx @@ -128,9 +128,7 @@ export const BaseConversationListItem: FunctionComponent = const testId = overrideTestId || groupId || serviceId; const isUnread = isConversationUnread({ markedUnread, unreadCount }); - const isAvatarNoteToSelf = isBoolean(isNoteToSelf) - ? isNoteToSelf - : Boolean(isMe); + const isAvatarNoteToSelf = isBoolean(isNoteToSelf) ? isNoteToSelf : isMe; const isCheckbox = isBoolean(checked); diff --git a/ts/components/conversationList/ContactCheckbox.dom.tsx b/ts/components/conversationList/ContactCheckbox.dom.tsx index e9acf329bf..41f124f7f7 100644 --- a/ts/components/conversationList/ContactCheckbox.dom.tsx +++ b/ts/components/conversationList/ContactCheckbox.dom.tsx @@ -101,7 +101,7 @@ export const ContactCheckbox: FunctionComponent = React.memo( avatarUrl={avatarUrl} color={color} conversationType={type} - noteToSelf={Boolean(isMe)} + noteToSelf={isMe} i18n={i18n} phoneNumber={phoneNumber} profileName={profileName} diff --git a/ts/components/conversationList/ContactListItem.dom.tsx b/ts/components/conversationList/ContactListItem.dom.tsx index cb593ec047..569702a159 100644 --- a/ts/components/conversationList/ContactListItem.dom.tsx +++ b/ts/components/conversationList/ContactListItem.dom.tsx @@ -262,7 +262,7 @@ export const ContactListItem: FunctionComponent = React.memo( avatarUrl={avatarUrl} color={color} conversationType={type} - noteToSelf={Boolean(isMe)} + noteToSelf={isMe} i18n={i18n} phoneNumber={phoneNumber} profileName={profileName} diff --git a/ts/components/conversationList/MessageSearchResult.dom.stories.tsx b/ts/components/conversationList/MessageSearchResult.dom.stories.tsx index 466b416182..fe3a0316a3 100644 --- a/ts/components/conversationList/MessageSearchResult.dom.stories.tsx +++ b/ts/components/conversationList/MessageSearchResult.dom.stories.tsx @@ -49,8 +49,10 @@ const useProps = (overrideProps: Partial = {}): PropsType => ({ snippet: overrideProps.snippet || "What's <>going<> on?", body: overrideProps.body || "What's going on?", bodyRanges: overrideProps.bodyRanges || [], - from: overrideProps.from as PropsType['from'], - to: overrideProps.to as PropsType['to'], + // oxlint-disable-next-line typescript/no-non-null-assertion + from: overrideProps.from!, + // oxlint-disable-next-line typescript/no-non-null-assertion + to: overrideProps.to!, getPreferredBadge: overrideProps.getPreferredBadge || (() => undefined), isSelected: overrideProps.isSelected || false, showConversation: action('showConversation'), diff --git a/ts/components/fun/data/emojis.std.ts b/ts/components/fun/data/emojis.std.ts index 7c369d6f90..b84a516a02 100644 --- a/ts/components/fun/data/emojis.std.ts +++ b/ts/components/fun/data/emojis.std.ts @@ -66,7 +66,7 @@ export const EMOJI_SKIN_TONE_ORDER: ReadonlyArray = [ ]; /** @deprecated We should use `EmojiSkinTone` everywhere */ -export const EMOJI_SKIN_TONE_TO_NUMBER: Map = new Map([ +export const EMOJI_SKIN_TONE_TO_NUMBER = new Map([ [EmojiSkinTone.None, 0], [EmojiSkinTone.Type1, 1], [EmojiSkinTone.Type2, 2], @@ -85,7 +85,7 @@ export const KEY_TO_EMOJI_SKIN_TONE = new Map([ ]); /** @deprecated We should use `EmojiSkinTone` everywhere */ -export const EMOJI_SKIN_TONE_TO_KEY: Map = new Map([ +export const EMOJI_SKIN_TONE_TO_KEY = new Map([ [EmojiSkinTone.Type1, '1F3FB'], [EmojiSkinTone.Type2, '1F3FC'], [EmojiSkinTone.Type3, '1F3FD'], @@ -453,7 +453,9 @@ for (const rawEmoji of RAW_EMOJI_DATA) { const variantKey = one ?? two; if (variantKey == null) { const keys = Object.keys(rawEmoji.skin_variations); - throw new Error(`Missing variant key ${parentKey} -> ${key} (${keys})`); + throw new Error( + `Missing variant key ${parentKey} -> ${key} (${keys.join(', ')})` + ); } result[skinTone] = variantKey; EMOJI_INDEX.variantKeyToSkinTone.set(variantKey, skinTone); diff --git a/ts/components/fun/data/segments.std.ts b/ts/components/fun/data/segments.std.ts index 0d8b4a6af5..225dd3ad31 100644 --- a/ts/components/fun/data/segments.std.ts +++ b/ts/components/fun/data/segments.std.ts @@ -84,6 +84,7 @@ export function _getSegmentRanges( function assertExpected(actual: T, expected: T, message: string) { strictAssert( Object.is(actual, expected), + // oxlint-disable-next-line typescript/restrict-template-expressions `${message}: ${actual} (expected: ${expected})` ); } diff --git a/ts/components/fun/keyboard/GridKeyboardDelegate.dom.tsx b/ts/components/fun/keyboard/GridKeyboardDelegate.dom.tsx index feaced0c92..5ccf2b4492 100644 --- a/ts/components/fun/keyboard/GridKeyboardDelegate.dom.tsx +++ b/ts/components/fun/keyboard/GridKeyboardDelegate.dom.tsx @@ -38,8 +38,8 @@ function toState(state: State, cell: Cell | null): State { } export class GridKeyboardDelegate extends KeyboardDelegate { - #virtualizer: Virtualizer; - #layout: Layout; + readonly #virtualizer: Virtualizer; + readonly #layout: Layout; constructor( virtualizer: Virtualizer, diff --git a/ts/components/fun/keyboard/WaterfallKeyboardDelegate.dom.tsx b/ts/components/fun/keyboard/WaterfallKeyboardDelegate.dom.tsx index 5a6c37caf9..11fe80a307 100644 --- a/ts/components/fun/keyboard/WaterfallKeyboardDelegate.dom.tsx +++ b/ts/components/fun/keyboard/WaterfallKeyboardDelegate.dom.tsx @@ -48,7 +48,7 @@ function toKey(item: VirtualItem | null): Key | null { } export class WaterfallKeyboardDelegate extends KeyboardDelegate { - #virtualizer: Virtualizer; + readonly #virtualizer: Virtualizer; constructor(virtualizer: Virtualizer) { super(); diff --git a/ts/components/leftPane/LeftPaneSearchHelper.dom.tsx b/ts/components/leftPane/LeftPaneSearchHelper.dom.tsx index 152a1c2b16..b587fd05b1 100644 --- a/ts/components/leftPane/LeftPaneSearchHelper.dom.tsx +++ b/ts/components/leftPane/LeftPaneSearchHelper.dom.tsx @@ -426,7 +426,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper results.isLoading); } - #onEnterKeyDown = ( + readonly #onEnterKeyDown = ( clearSearchQuery: () => unknown, showConversation: ShowConversationType ): void => { diff --git a/ts/components/stickers/mocks.std.ts b/ts/components/stickers/mocks.std.ts index 2a95eeeac1..de8f6673b4 100644 --- a/ts/components/stickers/mocks.std.ts +++ b/ts/components/stickers/mocks.std.ts @@ -79,9 +79,7 @@ export const packs = [ createPack({ id: 'wide' }, wideSticker), ...Array(20) .fill(0) - .map((_, n) => - createPack({ id: `pack-${n}` }, sample(choosableStickers) as StickerType) - ), + .map((_, n) => createPack({ id: `pack-${n}` }, sample(choosableStickers))), ]; export const recentStickers = [ diff --git a/ts/groups.preload.ts b/ts/groups.preload.ts index 3a3e1b23ad..76928504d1 100644 --- a/ts/groups.preload.ts +++ b/ts/groups.preload.ts @@ -274,6 +274,7 @@ export async function getPreJoinGroupInfo( const data = deriveGroupFields(Bytes.fromBase64(masterKeyBase64)); return makeRequestWithCredentials({ + // oxlint-disable-next-line typescript/restrict-template-expressions logId: `getPreJoinInfo/groupv2(${data.id})`, publicParams: Bytes.toBase64(data.publicParams), secretParams: Bytes.toBase64(data.secretParams), @@ -1423,7 +1424,7 @@ export function buildPromoteMemberChange({ group, profileKeyCredentialBase64, serverPublicParamsBase64, - isPendingPniAciProfileKey = false, + isPendingPniAciProfileKey, }: BuildPromoteMemberChangeOptionsType): Actions.Params { if (!group.secretParams) { throw new Error( @@ -1550,7 +1551,9 @@ export async function modifyGroupV2({ ); if (logIds.length !== 0) { - log.info(`modifyGroupV2/${logId}: Fetching profiles for ${logIds}`); + log.info( + `modifyGroupV2/${logId}: Fetching profiles for ${logIds.join(', ')}` + ); } // oxlint-disable-next-line no-await-in-loop @@ -1676,7 +1679,7 @@ export async function modifyGroupV2({ if (logIds.length !== 0) { log.warn( `modifyGroupV2/${logId}: Profile key credentials were not ` + - `up-to-date. Updating profiles for ${logIds} and retrying` + `up-to-date. Updating profiles for ${logIds.join(', ')} and retrying` ); } @@ -1991,7 +1994,7 @@ export async function createGroupV2( log.warn( `createGroupV2/${logId}: Profile key credentials were not ` + - `up-to-date. Updating profiles for ${logIds} and retrying` + `up-to-date. Updating profiles for ${logIds.join(', ')} and retrying` ); return createGroupV2({ @@ -4763,7 +4766,7 @@ function extractDiffs({ const oldMemberLookup = new Map( (old.membersV2 || []).map(member => [member.aci, member]) ); - const didWeStartInGroup = Boolean(ourAci && oldMemberLookup.has(ourAci)); + const didWeStartInGroup = oldMemberLookup.has(ourAci); const oldPendingMemberLookup = new Map< ServiceIdString, @@ -5828,6 +5831,7 @@ export async function decryptGroupAvatar( if (blob.content?.avatar == null) { throw new Error( + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions `decryptGroupAvatar: Returned blob had incorrect content: ${blob.content}` ); } @@ -6448,7 +6452,7 @@ function decryptGroupChange( return { added: decrypted, - joinFromInviteLink: Boolean(addMember.joinFromInviteLink), + joinFromInviteLink: addMember.joinFromInviteLink, }; }) ); @@ -7001,7 +7005,7 @@ function decryptGroupChange( if (actions.modifyAnnouncementsOnly) { const { announcementsOnly } = actions.modifyAnnouncementsOnly; result.modifyAnnouncementsOnly = { - announcementsOnly: Boolean(announcementsOnly), + announcementsOnly, }; } diff --git a/ts/growing-file.d.ts b/ts/growing-file.d.ts index 65ce523516..7c58ab8e68 100644 --- a/ts/growing-file.d.ts +++ b/ts/growing-file.d.ts @@ -7,6 +7,7 @@ declare module 'growing-file' { interval?: number; }; + // oxlint-disable-next-line typescript/no-extraneous-class class GrowingFile { static open(path: string, options: GrowingFileOptions): Readable; } diff --git a/ts/hooks/useIsMounted.std.ts b/ts/hooks/useIsMounted.std.ts index cdebec5f53..2e289ff2b5 100644 --- a/ts/hooks/useIsMounted.std.ts +++ b/ts/hooks/useIsMounted.std.ts @@ -24,5 +24,5 @@ export function useIsMounted(): () => boolean { }; }, []); - return useCallback(() => isMounted.current === true, []); + return useCallback(() => isMounted.current, []); } diff --git a/ts/jobs/AttachmentBackupManager.preload.ts b/ts/jobs/AttachmentBackupManager.preload.ts index 72503a4d06..c93a3ab519 100644 --- a/ts/jobs/AttachmentBackupManager.preload.ts +++ b/ts/jobs/AttachmentBackupManager.preload.ts @@ -508,7 +508,7 @@ async function uploadToTransitTier({ } // Legacy attachments - return dependencies.encryptAndUploadAttachment({ + return await dependencies.encryptAndUploadAttachment({ keys: fromBase64(keys), needIncrementalMac, plaintext: { absolutePath }, diff --git a/ts/jobs/AttachmentDownloadManager.preload.ts b/ts/jobs/AttachmentDownloadManager.preload.ts index 063de9844c..e17bf06e3e 100644 --- a/ts/jobs/AttachmentDownloadManager.preload.ts +++ b/ts/jobs/AttachmentDownloadManager.preload.ts @@ -171,9 +171,9 @@ function getJobIdForLogging(job: CoreAttachmentDownloadJobType): string { } export class AttachmentDownloadManager extends JobManager { - #visibleTimelineMessages: Set = new Set(); + #visibleTimelineMessages = new Set(); - #saveJobsBatcher = createBatcher({ + readonly #saveJobsBatcher = createBatcher({ name: 'saveAttachmentDownloadJobs', wait: 150, maxSize: 1000, @@ -183,15 +183,15 @@ export class AttachmentDownloadManager extends JobManager Promise; - #getMessageQueueTime: () => number; - #hasMediaBackups: () => boolean; - #statfs: typeof statfs; - #maxAttachmentSize: number; - #maxTextAttachmentSize: number; - #minimumFreeDiskSpace: number; + readonly #onLowDiskSpaceBackupImport: (bytesNeeded: number) => Promise; + readonly #getMessageQueueTime: () => number; + readonly #hasMediaBackups: () => boolean; + readonly #statfs: typeof statfs; + readonly #maxAttachmentSize: number; + readonly #maxTextAttachmentSize: number; + readonly #minimumFreeDiskSpace: number; - #attachmentBackfill = new AttachmentBackfill(); + readonly #attachmentBackfill = new AttachmentBackfill(); private static _instance: AttachmentDownloadManager | undefined; override logPrefix = 'AttachmentDownloadManager'; diff --git a/ts/jobs/AttachmentLocalBackupManager.preload.ts b/ts/jobs/AttachmentLocalBackupManager.preload.ts index ad002e35d0..0f379ac278 100644 --- a/ts/jobs/AttachmentLocalBackupManager.preload.ts +++ b/ts/jobs/AttachmentLocalBackupManager.preload.ts @@ -36,7 +36,7 @@ type RunAttachmentBackupJobDependenciesType = { export function getJobIdForLogging( job: CoreAttachmentLocalBackupJobType ): string { - return `${redactGenericText(job.mediaName)}`; + return redactGenericText(job.mediaName); } export async function runAttachmentBackupJob( diff --git a/ts/jobs/CallLinkFinalizeDeleteManager.preload.ts b/ts/jobs/CallLinkFinalizeDeleteManager.preload.ts index 1cbd57eb12..5b2c08d66a 100644 --- a/ts/jobs/CallLinkFinalizeDeleteManager.preload.ts +++ b/ts/jobs/CallLinkFinalizeDeleteManager.preload.ts @@ -50,7 +50,7 @@ function getJobId(job: CoreCallLinkDeleteJobType): string { // synchronously and prior to running this job, so we can show confirmation // or error to the user. export class CallLinkFinalizeDeleteManager extends JobManager { - jobs: Map = new Map(); + jobs = new Map(); private static _instance: CallLinkFinalizeDeleteManager | undefined; override logPrefix = 'CallLinkFinalizeDeleteManager'; diff --git a/ts/jobs/Job.std.ts b/ts/jobs/Job.std.ts index b4b689d8ca..7f4586d1eb 100644 --- a/ts/jobs/Job.std.ts +++ b/ts/jobs/Job.std.ts @@ -7,11 +7,23 @@ import type { ParsedJob } from './types.std.ts'; * A single job instance. Shouldn't be instantiated directly, except by `JobQueue`. */ export class Job implements ParsedJob { + public readonly id: string; + public readonly timestamp: number; + public readonly queueType: string; + public readonly data: T; + public readonly completion: Promise; + constructor( - readonly id: string, - readonly timestamp: number, - readonly queueType: string, - readonly data: T, - readonly completion: Promise - ) {} + id: string, + timestamp: number, + queueType: string, + data: T, + completion: Promise + ) { + this.id = id; + this.timestamp = timestamp; + this.queueType = queueType; + this.data = data; + this.completion = completion; + } } diff --git a/ts/jobs/JobError.std.ts b/ts/jobs/JobError.std.ts index b722adfa14..1685e01c42 100644 --- a/ts/jobs/JobError.std.ts +++ b/ts/jobs/JobError.std.ts @@ -9,8 +9,11 @@ import { reallyJsonStringify } from '../util/reallyJsonStringify.std.ts'; * Should not be instantiated directly, except by `JobQueue`. */ export class JobError extends Error { - constructor(public readonly lastErrorThrownByJob: unknown) { + public readonly lastErrorThrownByJob: unknown; + + constructor(lastErrorThrownByJob: unknown) { super(`Job failed. Last error: ${formatError(lastErrorThrownByJob)}`); + this.lastErrorThrownByJob = lastErrorThrownByJob; } } diff --git a/ts/jobs/JobLogger.std.ts b/ts/jobs/JobLogger.std.ts index b02025a373..5627233dfb 100644 --- a/ts/jobs/JobLogger.std.ts +++ b/ts/jobs/JobLogger.std.ts @@ -5,47 +5,49 @@ import type { LoggerType } from '../types/Logging.std.ts'; import type { ParsedJob } from './types.std.ts'; export class JobLogger implements LoggerType { - #id: string; - #queueType: string; + readonly #id: string; + readonly #queueType: string; + readonly #logger: LoggerType; public attempt = -1; constructor( job: Readonly, 'id' | 'queueType'>>, - private logger: LoggerType + logger: LoggerType ) { this.#id = job.id; this.#queueType = job.queueType; + this.#logger = logger; } fatal(...args: ReadonlyArray): void { - this.logger.fatal(this.#prefix(), ...args); + this.#logger.fatal(this.#prefix(), ...args); } error(...args: ReadonlyArray): void { - this.logger.error(this.#prefix(), ...args); + this.#logger.error(this.#prefix(), ...args); } warn(...args: ReadonlyArray): void { - this.logger.warn(this.#prefix(), ...args); + this.#logger.warn(this.#prefix(), ...args); } info(...args: ReadonlyArray): void { - this.logger.info(this.#prefix(), ...args); + this.#logger.info(this.#prefix(), ...args); } debug(...args: ReadonlyArray): void { - this.logger.debug(this.#prefix(), ...args); + this.#logger.debug(this.#prefix(), ...args); } trace(...args: ReadonlyArray): void { - this.logger.trace(this.#prefix(), ...args); + this.#logger.trace(this.#prefix(), ...args); } child(name: string): JobLogger { return new JobLogger( { id: this.#id, queueType: this.#queueType }, - this.logger.child(name) + this.#logger.child(name) ); } diff --git a/ts/jobs/JobManager.std.ts b/ts/jobs/JobManager.std.ts index fe8f1dd190..fa96bb5b73 100644 --- a/ts/jobs/JobManager.std.ts +++ b/ts/jobs/JobManager.std.ts @@ -78,16 +78,25 @@ export type ActiveJobData = { }; export abstract class JobManager { - #enabled: boolean = false; - #activeJobs: Map> = new Map(); - #jobStartPromises: Map> = new Map(); - #jobCompletePromises: Map> = new Map(); + protected readonly params: JobManagerParamsType; + #enabled = false; + readonly #activeJobs = new Map>(); + readonly #jobStartPromises = new Map< + string, + ExplodePromiseResultType + >(); + readonly #jobCompletePromises = new Map< + string, + ExplodePromiseResultType + >(); #tickTimeout: NodeJS.Timeout | null = null; #idleCallbacks = new Array<() => void>(); protected logPrefix = 'JobManager'; public tickInterval = DEFAULT_TICK_INTERVAL; - constructor(readonly params: JobManagerParamsType) {} + constructor(params: JobManagerParamsType) { + this.params = params; + } async start(): Promise { log.info(`${this.logPrefix}: starting`); diff --git a/ts/jobs/JobQueueDatabaseStore.preload.ts b/ts/jobs/JobQueueDatabaseStore.preload.ts index d949ca577d..53969cdfe7 100644 --- a/ts/jobs/JobQueueDatabaseStore.preload.ts +++ b/ts/jobs/JobQueueDatabaseStore.preload.ts @@ -20,11 +20,14 @@ type Database = { }; export class JobQueueDatabaseStore implements JobQueueStore { - #activeQueueTypes = new Set(); - #queues = new Map>(); - #initialFetchPromises = new Map>(); + readonly #db: Database; + readonly #activeQueueTypes = new Set(); + readonly #queues = new Map>(); + readonly #initialFetchPromises = new Map>(); - constructor(private readonly db: Database) {} + constructor(db: Database) { + this.#db = db; + } async insert( job: Readonly, @@ -42,7 +45,7 @@ export class JobQueueDatabaseStore implements JobQueueStore { } if (shouldPersist) { - await this.db.insertJob(formatJobForInsert(job)); + await this.#db.insertJob(formatJobForInsert(job)); } if (initialFetchPromise) { @@ -51,7 +54,7 @@ export class JobQueueDatabaseStore implements JobQueueStore { } async delete(id: string): Promise { - await this.db.deleteJob(id); + await this.#db.deleteJob(id); } stream(queueType: string): AsyncIterable { @@ -90,7 +93,7 @@ export class JobQueueDatabaseStore implements JobQueueStore { }); this.#initialFetchPromises.set(queueType, initialFetchPromise); - const result = await this.db.getJobsInQueue(queueType); + const result = await this.#db.getJobsInQueue(queueType); log.info( `finished fetching existing ${ result.length diff --git a/ts/jobs/callLinkRefreshJobQueue.preload.ts b/ts/jobs/callLinkRefreshJobQueue.preload.ts index d5d2e88e06..225cdc1126 100644 --- a/ts/jobs/callLinkRefreshJobQueue.preload.ts +++ b/ts/jobs/callLinkRefreshJobQueue.preload.ts @@ -50,7 +50,7 @@ export type CallLinkRefreshJobData = z.infer< >; export class CallLinkRefreshJobQueue extends JobQueue { - #parallelQueue = new PQueue({ concurrency: MAX_PARALLEL_JOBS }); + readonly #parallelQueue = new PQueue({ concurrency: MAX_PARALLEL_JOBS }); readonly #pendingCallLinks = new Map(); protected override getQueues(): ReadonlySet { diff --git a/ts/jobs/helpers/attachmentBackfill.preload.ts b/ts/jobs/helpers/attachmentBackfill.preload.ts index 847c95a588..24f608faa2 100644 --- a/ts/jobs/helpers/attachmentBackfill.preload.ts +++ b/ts/jobs/helpers/attachmentBackfill.preload.ts @@ -67,7 +67,7 @@ function isBackfillEnabled(): boolean { } export class AttachmentBackfill { - #pendingRequests = new Map< + readonly #pendingRequests = new Map< ReadonlyMessageAttributesType['id'], NodeJS.Timeout >(); diff --git a/ts/jobs/helpers/sendDeleteStoryForEveryone.preload.ts b/ts/jobs/helpers/sendDeleteStoryForEveryone.preload.ts index b3d0dd6cbb..308775838e 100644 --- a/ts/jobs/helpers/sendDeleteStoryForEveryone.preload.ts +++ b/ts/jobs/helpers/sendDeleteStoryForEveryone.preload.ts @@ -255,7 +255,7 @@ export async function sendDeleteStoryForEveryone( function doesMessageHaveSuccessfulSends(message: MessageModel): boolean { const map = message.get('deletedForEveryoneSendStatus') ?? {}; - return Object.values(map).some(value => value === true); + return Object.values(map).includes(true); } async function updateMessageWithSuccessfulSends( diff --git a/ts/jobs/singleProtoJobQueue.preload.ts b/ts/jobs/singleProtoJobQueue.preload.ts index 37ad32284f..53a447eb6d 100644 --- a/ts/jobs/singleProtoJobQueue.preload.ts +++ b/ts/jobs/singleProtoJobQueue.preload.ts @@ -35,7 +35,7 @@ const MAX_PARALLEL_JOBS = 5; const MAX_ATTEMPTS = exponentialBackoffMaxAttempts(MAX_RETRY_TIME); export class SingleProtoJobQueue extends JobQueue { - #parallelQueue = new PQueue({ concurrency: MAX_PARALLEL_JOBS }); + readonly #parallelQueue = new PQueue({ concurrency: MAX_PARALLEL_JOBS }); protected override getQueues(): ReadonlySet { return new Set([this.#parallelQueue]); diff --git a/ts/linkPreviews/linkPreviewFetch.preload.ts b/ts/linkPreviews/linkPreviewFetch.preload.ts index 328fb72918..1e09d95057 100644 --- a/ts/linkPreviews/linkPreviewFetch.preload.ts +++ b/ts/linkPreviews/linkPreviewFetch.preload.ts @@ -44,7 +44,7 @@ const MIN_HTML_CONTENT_LENGTH = 8; // Similar to the above. We don't want to show tiny images (even though the more likely // case is that the Content-Length is 0). const MIN_IMAGE_CONTENT_LENGTH = 8; -const VALID_IMAGE_MIME_TYPES: Set = new Set([ +const VALID_IMAGE_MIME_TYPES = new Set([ IMAGE_GIF, IMAGE_ICO, IMAGE_JPEG, diff --git a/ts/logging/log.std.ts b/ts/logging/log.std.ts index d710f3844a..b6c7e5b230 100644 --- a/ts/logging/log.std.ts +++ b/ts/logging/log.std.ts @@ -155,6 +155,7 @@ function debugLog( // `fatal` has no respective analog in `console` console[consoleMethod === 'fatal' ? 'error' : consoleMethod]( + // oxlint-disable-next-line typescript/restrict-template-expressions `%c${msgPrefix ?? ''}%c${message}`, `color: ${color}; font-weight: bold`, 'color: inherit; font-weight: inherit', diff --git a/ts/logging/main_process_logging.main.ts b/ts/logging/main_process_logging.main.ts index 7b6c2f3fd4..5998435104 100644 --- a/ts/logging/main_process_logging.main.ts +++ b/ts/logging/main_process_logging.main.ts @@ -49,7 +49,7 @@ export async function initialize( if (level >= LogLevel.Error) { getMainWindow()?.webContents.send( 'logging-error', - `${msgPrefix ? `${msgPrefix}` : ''}${logLine}` + `${msgPrefix ?? ''}${logLine}` ); } }); diff --git a/ts/logging/set_up_renderer_logging.preload.ts b/ts/logging/set_up_renderer_logging.preload.ts index 7f25a8a8a0..0eb2ec5011 100644 --- a/ts/logging/set_up_renderer_logging.preload.ts +++ b/ts/logging/set_up_renderer_logging.preload.ts @@ -49,6 +49,7 @@ export function initialize(): void { window.onerror = (message, source, line, column, error) => { const errorInfo = Errors.toLogFormat(error); log.error( + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions `Top-level unhandled error: ${message}, ${errorInfo}`, Errors.toLocation(source, line, column) ); @@ -90,6 +91,7 @@ initLogger( } else if (level === SignalClientLogLevel.Error) { libSignalLog.error(logString); } else { + // oxlint-disable-next-line typescript/restrict-template-expressions libSignalLog.error(`${logString} (unknown log level ${level})`); } } diff --git a/ts/logging/uploadDebugLog.node.ts b/ts/logging/uploadDebugLog.node.ts index b167fab6f7..063b722ae9 100644 --- a/ts/logging/uploadDebugLog.node.ts +++ b/ts/logging/uploadDebugLog.node.ts @@ -74,6 +74,7 @@ export const upload = async ({ }); const { fields, url } = parseTokenBody(signedForm.body); + // oxlint-disable-next-line typescript/restrict-template-expressions const uploadKey = `${fields.key}.${extension}`; const form = new FormData(); diff --git a/ts/main/challengeMain.main.ts b/ts/main/challengeMain.main.ts index ff491e0c70..ba6aa1fa4f 100644 --- a/ts/main/challengeMain.main.ts +++ b/ts/main/challengeMain.main.ts @@ -16,7 +16,7 @@ const log = createLogger('challengeMain'); export class ChallengeMainHandler { #handlers: Array<(response: ChallengeResponse) => void> = []; - #hardcodedResult?: ChallengeResponse; + readonly #hardcodedResult?: ChallengeResponse; constructor(hardcodedResult?: string) { if (hardcodedResult && getEnvironment() === Environment.Development) { diff --git a/ts/main/powerChannel.main.ts b/ts/main/powerChannel.main.ts index 646c30edf6..6d9b18e5e1 100644 --- a/ts/main/powerChannel.main.ts +++ b/ts/main/powerChannel.main.ts @@ -7,14 +7,14 @@ export type InitializeOptions = { send(event: string): void; }; -export class PowerChannel { - private static isInitialized = false; +export namespace PowerChannel { + let isInitialized = false; - static initialize({ send }: InitializeOptions): void { - if (PowerChannel.isInitialized) { + export function initialize({ send }: InitializeOptions): void { + if (isInitialized) { throw new Error('PowerChannel already initialized'); } - PowerChannel.isInitialized = true; + isInitialized = true; powerMonitor.on('suspend', () => { send('power-channel:suspend'); diff --git a/ts/mediaEditor/MediaEditorFabricCropRect.dom.ts b/ts/mediaEditor/MediaEditorFabricCropRect.dom.ts index 40b827db05..228bb6899b 100644 --- a/ts/mediaEditor/MediaEditorFabricCropRect.dom.ts +++ b/ts/mediaEditor/MediaEditorFabricCropRect.dom.ts @@ -21,7 +21,7 @@ export class MediaEditorFabricCropRect extends fabric.Rect { this.on('moving', this.#containBounds); } - #containBounds = () => { + readonly #containBounds = () => { if (!this.canvas) { return; } diff --git a/ts/messageModifiers/AttachmentDownloads.preload.ts b/ts/messageModifiers/AttachmentDownloads.preload.ts index 9dc79b69b3..de9815917c 100644 --- a/ts/messageModifiers/AttachmentDownloads.preload.ts +++ b/ts/messageModifiers/AttachmentDownloads.preload.ts @@ -70,8 +70,11 @@ export async function markAttachmentAsCorrupted( } export class AttachmentNotNeededForMessageError extends Error { - constructor(public readonly attachment: AttachmentType) { + public readonly attachment: AttachmentType; + + constructor(attachment: AttachmentType) { super('AttachmentNotNeededForMessageError'); + this.attachment = attachment; } } diff --git a/ts/messageModifiers/MessageReceipts.preload.ts b/ts/messageModifiers/MessageReceipts.preload.ts index ccc77c250c..a98610dc33 100644 --- a/ts/messageModifiers/MessageReceipts.preload.ts +++ b/ts/messageModifiers/MessageReceipts.preload.ts @@ -81,10 +81,10 @@ const processReceiptBatcher = createWaitBatcher({ // Once we find the message, we'll group them by messageId to process // all receipts for a given message - const receiptsByMessageId: Map< + const receiptsByMessageId = new Map< string, Array - > = new Map(); + >(); function addReceiptAndTargetMessage( message: MessageModel, diff --git a/ts/messageModifiers/ReadSyncs.preload.ts b/ts/messageModifiers/ReadSyncs.preload.ts index 82303897bf..b6d02bab8f 100644 --- a/ts/messageModifiers/ReadSyncs.preload.ts +++ b/ts/messageModifiers/ReadSyncs.preload.ts @@ -63,7 +63,7 @@ async function maybeItIsAReactionReadSync( const readReaction = await DataWriter.markReactionAsRead( readSync.senderAci, - Number(readSync.timestamp) + readSync.timestamp ); if ( diff --git a/ts/messages/MessageSendState.std.ts b/ts/messages/MessageSendState.std.ts index 439b82ca32..6e901829f2 100644 --- a/ts/messages/MessageSendState.std.ts +++ b/ts/messages/MessageSendState.std.ts @@ -249,12 +249,11 @@ const summarizeMessageSendStatuses = memoizee( statusesWithOnlyOneConversationId: Map; length: number; } => { - const statuses: Set = new Set(); + const statuses = new Set(); // We keep track of statuses with only one conversationId associated with it // so that we can ignore a status if it is only for ourConversationId, as needed - const statusesWithOnlyOneConversationId: Map = - new Map(); + const statusesWithOnlyOneConversationId = new Map(); const entries = Object.entries(sendStateByConversationId); diff --git a/ts/messages/handleDataMessage.preload.ts b/ts/messages/handleDataMessage.preload.ts index 4fc9ec94ec..1895ab1018 100644 --- a/ts/messages/handleDataMessage.preload.ts +++ b/ts/messages/handleDataMessage.preload.ts @@ -255,7 +255,7 @@ export async function handleDataMessage( initialMessage.groupV2.publicParams ) { // Repair core GroupV2 data if needed - await conversation.maybeRepairGroupV2({ + conversation.maybeRepairGroupV2({ masterKey: initialMessage.groupV2.masterKey, secretParams: initialMessage.groupV2.secretParams, publicParams: initialMessage.groupV2.publicParams, @@ -541,7 +541,7 @@ export async function handleDataMessage( } const ourPni = itemStorage.user.getCheckedPni(); - const ourServiceIds: Set = new Set([ourAci, ourPni]); + const ourServiceIds = new Set([ourAci, ourPni]); // oxlint-disable-next-line no-param-reassign message = window.MessageCache.register(message); @@ -792,7 +792,7 @@ export async function handleDataMessage( `${idLog}: gift badge with level ${level} not found on server` ); } else { - await window.reduxActions.badges.updateOrCreate([badge]); + window.reduxActions.badges.updateOrCreate([badge]); giftBadge.id = badge.id; } } diff --git a/ts/messages/helpers.std.ts b/ts/messages/helpers.std.ts index 2c30414a2d..db0e4001f4 100644 --- a/ts/messages/helpers.std.ts +++ b/ts/messages/helpers.std.ts @@ -60,7 +60,7 @@ export const shouldTryToCopyFromQuotedMessage = ({ quoteAttachment: ReadonlyDeep | undefined; }): boolean => { // If we've tried and can't find the message, try again. - if (referencedMessageNotFound === true) { + if (referencedMessageNotFound) { return true; } diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index fd2b771c00..43c4cfa04d 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -121,6 +121,7 @@ type StoryReplyContextType = { export type GroupV1Update = { avatarUpdated?: boolean; joined?: ReadonlyArray; + // oxlint-disable-next-line typescript/no-redundant-type-constituents left?: string | 'You'; name?: string; }; diff --git a/ts/models/conversations.preload.ts b/ts/models/conversations.preload.ts index fa5a6d8d5b..0619268085 100644 --- a/ts/models/conversations.preload.ts +++ b/ts/models/conversations.preload.ts @@ -362,9 +362,9 @@ export class ConversationModel { #lastIsTyping?: boolean; #muteTimer?: NodeJS.Timeout; - #privVerifiedEnum?: typeof signalProtocolStore.VerifiedStatus; + readonly #privVerifiedEnum?: typeof signalProtocolStore.VerifiedStatus; #isShuttingDown = false; - #savePromises = new Set>(); + readonly #savePromises = new Set>(); public get id(): string { return this.#_attributes.id; @@ -593,7 +593,7 @@ export class ConversationModel { ): Promise { const idLog = this.idForLogging(); const current = this.get('expireTimer'); - const bothFalsey = Boolean(current) === false && Boolean(seconds) === false; + const bothFalsey = !current && !seconds; if (current === seconds || bothFalsey) { log.warn( @@ -4100,8 +4100,8 @@ export class ConversationModel { const { clearUnreadMetrics } = window.reduxActions.conversations; clearUnreadMetrics(this.id); - const enabledProfileSharing = Boolean(!this.get('profileSharing')); - const unarchivedConversation = Boolean(this.get('isArchived')); + const enabledProfileSharing = !this.get('profileSharing'); + const unarchivedConversation = this.get('isArchived'); log.info( `beforeMessageSend(${this.idForLogging()}): ` + @@ -4619,12 +4619,12 @@ export class ConversationModel { } setMarkedUnread(markedUnread: boolean): void { - const previousMarkedUnread = this.get('markedUnread'); + const previousMarkedUnread = this.get('markedUnread') ?? false; this.set({ markedUnread }); drop(DataWriter.updateConversation(this.attributes)); - if (Boolean(previousMarkedUnread) !== Boolean(markedUnread)) { + if (previousMarkedUnread !== markedUnread) { this.captureChange('markedUnread'); } } @@ -5089,9 +5089,7 @@ export class ConversationModel { } const ourGroups = - await window.ConversationController.getAllGroupsInvolvingServiceId( - ourAci - ); + window.ConversationController.getAllGroupsInvolvingServiceId(ourAci); return ourGroups .filter(c => c.hasMember(ourAci) && c.hasMember(theirAci)) .sort( diff --git a/ts/quill/emoji/completion.dom.tsx b/ts/quill/emoji/completion.dom.tsx index 61a119534d..8e4c5276db 100644 --- a/ts/quill/emoji/completion.dom.tsx +++ b/ts/quill/emoji/completion.dom.tsx @@ -231,9 +231,9 @@ export class EmojiCompletion { getAttributesForInsert(index: number): Record { const character = index > 0 ? index - 1 : 0; const contents = this.quill.getContents(character, 1); - return contents.ops.reduce( - (acc, op) => ({ acc, ...op.attributes }), - {} as Record + return contents.ops.reduce>( + (acc, op) => ({ ...acc, ...op.attributes }), + {} ); } diff --git a/ts/quill/memberRepository.std.ts b/ts/quill/memberRepository.std.ts index b7d38257c8..ea2e46af30 100644 --- a/ts/quill/memberRepository.std.ts +++ b/ts/quill/memberRepository.std.ts @@ -73,7 +73,7 @@ export class MemberRepository { #rawMembers: ReadonlyArray; #members: ReadonlyArray | null = null; #isFuseReady = false; - #fuse = new Fuse([], FUSE_OPTIONS); + readonly #fuse = new Fuse([], FUSE_OPTIONS); constructor(conversations: ReadonlyArray = []) { this.#rawMembers = conversations; diff --git a/ts/quill/mentions/completion.dom.tsx b/ts/quill/mentions/completion.dom.tsx index 6cf6f74598..7877eeb338 100644 --- a/ts/quill/mentions/completion.dom.tsx +++ b/ts/quill/mentions/completion.dom.tsx @@ -203,9 +203,9 @@ export class MentionCompletion { getAttributesForInsert(index: number): Record { const character = index > 0 ? index - 1 : 0; const contents = this.quill.getContents(character, 1); - return contents.ops.reduce( - (acc, op) => ({ acc, ...op.attributes }), - {} as Record + return contents.ops.reduce>( + (acc, op) => ({ ...acc, ...op.attributes }), + {} ); } diff --git a/ts/quill/signal-clipboard/util.dom.ts b/ts/quill/signal-clipboard/util.dom.ts index fdeb2d0d0c..430c061297 100644 --- a/ts/quill/signal-clipboard/util.dom.ts +++ b/ts/quill/signal-clipboard/util.dom.ts @@ -192,7 +192,8 @@ function getRangeWithContainer(range: Range): Node { return fragment; } - currentNode = startContainer.parentElement as HTMLElement; + // oxlint-disable-next-line typescript/no-non-null-assertion + currentNode = startContainer.parentElement!; while ( currentNode && CONTAINER_CLASSES.every(item => !currentNode?.classList.contains(item)) diff --git a/ts/quill/util.dom.ts b/ts/quill/util.dom.ts index ce686eac5e..ce30d1f409 100644 --- a/ts/quill/util.dom.ts +++ b/ts/quill/util.dom.ts @@ -195,6 +195,7 @@ export const getTextAndRangesFromOps = ( const preTrimText = ops.reduce((acc, op) => { // We special-case all-newline ops because Quill doesn't apply styles to them if (isNewlineOnlyOp(op)) { + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-plus-operands return acc + op.insert; } @@ -459,7 +460,7 @@ export const insertEmojiOps = ( incomingOps: ReadonlyArray, existingAttributes: AttributeMap ): Array => { - return incomingOps.reduce((ops, op) => { + return incomingOps.reduce>((ops, op) => { if (typeof op.insert === 'string') { const text = op.insert; const { attributes } = op; @@ -493,5 +494,5 @@ export const insertEmojiOps = ( } return ops; - }, [] as Array); + }, []); }; diff --git a/ts/reactions/util.std.ts b/ts/reactions/util.std.ts index 26a0238670..db53266e19 100644 --- a/ts/reactions/util.std.ts +++ b/ts/reactions/util.std.ts @@ -27,8 +27,7 @@ const isOutgoingReactionPending = negate(isOutgoingReactionFullySent); const isOutgoingReactionCompletelyUnsent = ({ isSentByConversationId = {}, }: Readonly>): boolean => { - const sendStates = Object.values(isSentByConversationId); - return sendStates.length > 0 && sendStates.every(state => state === false); + return !Object.values(isSentByConversationId).includes(true); }; export function addOutgoingReaction( diff --git a/ts/routineProfileRefresh.preload.ts b/ts/routineProfileRefresh.preload.ts index c2b19d5fb7..2c8faf4245 100644 --- a/ts/routineProfileRefresh.preload.ts +++ b/ts/routineProfileRefresh.preload.ts @@ -29,17 +29,19 @@ const MIN_REFRESH_DELAY = MINUTE; let idCounter = 1; -export class RoutineProfileRefresher { - #started = false; - #id: number; +type Options = Readonly<{ + getAllConversations: () => ReadonlyArray; + getOurConversationId: () => string | undefined; + storage: Pick; +}>; - constructor( - private readonly options: { - getAllConversations: () => ReadonlyArray; - getOurConversationId: () => string | undefined; - storage: Pick; - } - ) { +export class RoutineProfileRefresher { + readonly #options: Options; + #started = false; + readonly #id: number; + + constructor(options: Options) { + this.#options = options; // We keep track of how many of these classes we create, because we suspect that // there might be too many... idCounter += 1; @@ -58,7 +60,8 @@ export class RoutineProfileRefresher { } this.#started = true; - const { storage, getAllConversations, getOurConversationId } = this.options; + const { storage, getAllConversations, getOurConversationId } = + this.#options; // oxlint-disable-next-line no-constant-condition while (true) { diff --git a/ts/services/ActiveWindowService.std.ts b/ts/services/ActiveWindowService.std.ts index 38572047fe..40d2e79501 100644 --- a/ts/services/ActiveWindowService.std.ts +++ b/ts/services/ActiveWindowService.std.ts @@ -36,7 +36,7 @@ class ActiveWindowService { #lastActiveEventAt = -Infinity; #lastActiveNonFocusingEventAt = -Infinity; #lastBlurredAt = -Infinity; - #callActiveCallbacks: () => void; + readonly #callActiveCallbacks: () => void; constructor() { this.#callActiveCallbacks = throttle(() => { diff --git a/ts/services/BeforeNavigate.std.ts b/ts/services/BeforeNavigate.std.ts index 22d3ff4e69..240c9cb38a 100644 --- a/ts/services/BeforeNavigate.std.ts +++ b/ts/services/BeforeNavigate.std.ts @@ -33,7 +33,7 @@ export type BeforeNavigateEntry = { }; export class BeforeNavigateService { - #beforeNavigateCallbacks = new Set(); + readonly #beforeNavigateCallbacks = new Set(); private findMatchingEntry( entry: BeforeNavigateEntry diff --git a/ts/services/MessageCache.preload.ts b/ts/services/MessageCache.preload.ts index 2515b5997c..fd0f84713b 100644 --- a/ts/services/MessageCache.preload.ts +++ b/ts/services/MessageCache.preload.ts @@ -29,7 +29,7 @@ export class MessageCache { return instance; } - #state = { + readonly #state = { messages: new Map(), messageIdsBySentAt: new Map>(), lastAccessedAt: new Map(), @@ -300,7 +300,7 @@ export class MessageCache { ); } - #throttledReduxUpdaters = new LRUCache< + readonly #throttledReduxUpdaters = new LRUCache< string, (attributes: MessageAttributesType) => void >({ diff --git a/ts/services/ZoomFactorService.main.ts b/ts/services/ZoomFactorService.main.ts index 1a24ace392..8f8f72fdf4 100644 --- a/ts/services/ZoomFactorService.main.ts +++ b/ts/services/ZoomFactorService.main.ts @@ -31,7 +31,7 @@ type ZoomFactorServiceConfig = Readonly<{ }>; export class ZoomFactorService extends EventEmitter { - #config: ZoomFactorServiceConfig; + readonly #config: ZoomFactorServiceConfig; #cachedZoomFactor: number | null = null; #isListeningForZoom = false; diff --git a/ts/services/audioRecorder.dom.ts b/ts/services/audioRecorder.dom.ts index cc56ef4775..954e47d701 100644 --- a/ts/services/audioRecorder.dom.ts +++ b/ts/services/audioRecorder.dom.ts @@ -55,6 +55,7 @@ export class RecorderClass { return false; } + // oxlint-disable-next-line typescript/await-thenable await window.reduxActions.globalModals.ensureSystemMediaPermissions( 'microphone', 'voiceNote' diff --git a/ts/services/backups/api.preload.ts b/ts/services/backups/api.preload.ts index 46e316f7b5..74adcf7b8f 100644 --- a/ts/services/backups/api.preload.ts +++ b/ts/services/backups/api.preload.ts @@ -54,17 +54,20 @@ export type EphemeralDownloadOptionsType = Readonly<{ DownloadOptionsType; export class BackupAPI { - #cachedBackupInfo = new Map< + readonly #credentials: BackupCredentials; + readonly #cachedBackupInfo = new Map< BackupCredentialType, GetBackupInfoResponseType >(); - constructor(private readonly credentials: BackupCredentials) {} + constructor(credentials: BackupCredentials) { + this.#credentials = credentials; + } public async refresh(): Promise { const headers = await Promise.all( [BackupCredentialType.Messages, BackupCredentialType.Media].map(type => - this.credentials.getHeadersForToday(type) + this.#credentials.getHeadersForToday(type) ) ); await Promise.all(headers.map(h => refreshBackup(h))); @@ -74,7 +77,7 @@ export class BackupAPI { credentialType: BackupCredentialType ): Promise { const backupInfo = await getBackupInfo( - await this.credentials.getHeadersForToday(credentialType) + await this.#credentials.getHeadersForToday(credentialType) ); this.#cachedBackupInfo.set(credentialType, backupInfo); return backupInfo; @@ -101,7 +104,7 @@ export class BackupAPI { public async upload(filePath: string, fileSize: number): Promise { const form = await getBackupUploadForm( - await this.credentials.getHeadersForToday(BackupCredentialType.Messages) + await this.#credentials.getHeadersForToday(BackupCredentialType.Messages) ); await uploadFile({ @@ -119,7 +122,7 @@ export class BackupAPI { const { cdn, backupDir, backupName } = await this.getInfo( BackupCredentialType.Messages ); - const { headers } = await this.credentials.getCDNReadCredentials( + const { headers } = await this.#credentials.getCDNReadCredentials( cdn, BackupCredentialType.Messages ); @@ -142,7 +145,7 @@ export class BackupAPI { const { cdn, backupDir, backupName } = await this.#getCachedInfo( BackupCredentialType.Messages ); - const { headers } = await this.credentials.getCDNReadCredentials( + const { headers } = await this.#credentials.getCDNReadCredentials( cdn, BackupCredentialType.Messages ); @@ -157,7 +160,7 @@ export class BackupAPI { return { backupExists: true, size, createdAt }; } catch (error) { if (error instanceof HTTPError && error.code === 401) { - this.credentials.onCdnCredentialError(); + this.#credentials.onCdnCredentialError(); } else if (error instanceof HTTPError && error.code === 404) { return { backupExists: false }; } @@ -190,7 +193,7 @@ export class BackupAPI { public async getMediaUploadForm(): Promise { return getBackupMediaUploadForm( - await this.credentials.getHeadersForToday(BackupCredentialType.Media) + await this.#credentials.getHeadersForToday(BackupCredentialType.Media) ); } @@ -198,7 +201,7 @@ export class BackupAPI { items: ReadonlyArray ): Promise { return doBackupMediaBatch({ - headers: await this.credentials.getHeadersForToday( + headers: await this.#credentials.getHeadersForToday( BackupCredentialType.Media ), items, @@ -213,7 +216,7 @@ export class BackupAPI { limit: number; }): Promise { return backupListMedia({ - headers: await this.credentials.getHeadersForToday( + headers: await this.#credentials.getHeadersForToday( BackupCredentialType.Media ), cursor, diff --git a/ts/services/backups/credentials.preload.ts b/ts/services/backups/credentials.preload.ts index 23f0dd2b7b..5cb883b7db 100644 --- a/ts/services/backups/credentials.preload.ts +++ b/ts/services/backups/credentials.preload.ts @@ -65,7 +65,7 @@ const BACKUP_CDN_READ_CREDENTIALS_VALID_DURATION = 12 * HOUR; export class BackupCredentials { #activeFetch: Promise> | undefined; - #scheduler = new CheckScheduler({ + readonly #scheduler = new CheckScheduler({ name: 'BackupCredentials', interval: 3 * DAY, storageKey: 'backupCombinedCredentialsLastRequestTime', diff --git a/ts/services/backups/errors.std.ts b/ts/services/backups/errors.std.ts index e12932719b..35cb7a7c4d 100644 --- a/ts/services/backups/errors.std.ts +++ b/ts/services/backups/errors.std.ts @@ -3,11 +3,11 @@ import { InstallScreenBackupError } from '../../types/InstallScreen.std.ts'; export class BackupInstallerError extends Error { - constructor( - name: string, - public readonly installerError: InstallScreenBackupError - ) { + public readonly installerError: InstallScreenBackupError; + + constructor(name: string, installerError: InstallScreenBackupError) { super(name); + this.installerError = installerError; } } diff --git a/ts/services/backups/export.preload.ts b/ts/services/backups/export.preload.ts index 2d1d42d27e..22183574cb 100644 --- a/ts/services/backups/export.preload.ts +++ b/ts/services/backups/export.preload.ts @@ -266,9 +266,14 @@ type NonBubbleResultType = Readonly< } >; +type Options = Readonly & { + validationRun?: boolean; +}; + export class BackupExportStream extends Readable { + readonly #options: Options; // Shared between all methods for consistency. - #now = Date.now(); + readonly #now = Date.now(); readonly #backupTimeMs = getSafeLongFromTimestamp(this.#now); readonly #convoIdToRecipientId = new Map(); @@ -307,14 +312,11 @@ export class BackupExportStream extends Readable { // Map from custom color uuid to an index in accountSettings.customColors // array. - #customColorIdByUuid = new Map(); + readonly #customColorIdByUuid = new Map(); - constructor( - private readonly options: Readonly & { - validationRun?: boolean; - } - ) { + constructor(options: Options) { super(); + this.#options = options; } async #cleanupAfterError() { @@ -345,7 +347,7 @@ export class BackupExportStream extends Readable { await this.#unsafeRun(); await resumeWriteAccess(); // TODO (DESKTOP-7344): Clear & add backup jobs in a single transaction - const { type } = this.options; + const { type } = this.#options; switch (type) { case 'remote': log.info( @@ -417,7 +419,7 @@ export class BackupExportStream extends Readable { debugInfo: null, }; - if (this.options.type === 'plaintext-export') { + if (this.#options.type === 'plaintext-export') { const { exporter, chunk: initialChunk } = BackupJsonExporter.start( Backups.BackupInfo.encode(backupInfo), { validate: false } @@ -876,7 +878,7 @@ export class BackupExportStream extends Readable { this.#stats.messages += 1; if ( - this.options.validationRun || + this.#options.validationRun || this.#stats.messages % FLUSH_EVERY === 0 ) { // flush every chatItem to expose all validation errors @@ -912,7 +914,7 @@ export class BackupExportStream extends Readable { #pushFrame(frame: Backups.Frame.Params['item']): void { const encodedFrame = Backups.Frame.encode({ item: frame }); - if (this.options.type === 'plaintext-export') { + if (this.#options.type === 'plaintext-export') { const delimitedFrame = Buffer.concat(encodeDelimited(encodedFrame)); strictAssert( this.#jsonExporter != null, @@ -1586,7 +1588,7 @@ export class BackupExportStream extends Readable { } if (message.expireTimer) { - if (this.options.type === 'plaintext-export') { + if (this.#options.type === 'plaintext-export') { // All disappearing messages are excluded in plaintext export return undefined; } @@ -3326,15 +3328,15 @@ export class BackupExportStream extends Readable { }): Promise { const { filePointer, backupJob } = await getFilePointerForAttachment({ attachment, - backupOptions: this.options, + backupOptions: this.#options, messageReceivedAt, getBackupCdnInfo, }); let mediaName: string | undefined; if ( - this.options.type === 'local-encrypted' || - this.options.type === 'plaintext-export' + this.#options.type === 'local-encrypted' || + this.#options.type === 'plaintext-export' ) { if (hasRequiredInformationForLocalBackup(attachment)) { mediaName = getLocalBackupFileNameForAttachment(attachment); @@ -3688,7 +3690,7 @@ export class BackupExportStream extends Readable { // Integration tests use the 'link-and-sync' version of export, which will include // view-once attachments const shouldIncludeAttachments = - this.options.type !== 'plaintext-export' && isTestOrMockEnvironment(); + this.#options.type !== 'plaintext-export' && isTestOrMockEnvironment(); return { attachment: !shouldIncludeAttachments || attachment == null diff --git a/ts/services/backups/import.preload.ts b/ts/services/backups/import.preload.ts index 977f237c6c..c17523bb33 100644 --- a/ts/services/backups/import.preload.ts +++ b/ts/services/backups/import.preload.ts @@ -256,7 +256,8 @@ function addressToContactAddressType( } export class BackupImportStream extends Writable { - #now = Date.now(); + readonly #options: BackupImportOptions; + readonly #now = Date.now(); #parsedBackupInfo = false; #logId = 'BackupImportStream(unknown)'; #aboutMe: AboutMe | undefined; @@ -280,16 +281,17 @@ export class BackupImportStream extends Writable { #flushMessagesPromise: Promise | undefined; readonly #stickerPacks = new Array(); #ourConversation?: ConversationAttributesType; - #pinnedConversations = new Array<[number, string]>(); - #customColorById = new Map(); + readonly #pinnedConversations = new Array<[number, string]>(); + readonly #customColorById = new Map(); #releaseNotesRecipientId: bigint | undefined; #releaseNotesChatId: bigint | undefined; - #pinnedMessages: Array = []; - #frameErrorCount: number = 0; + readonly #pinnedMessages: Array = []; + #frameErrorCount = 0; #backupTier: BackupLevel | undefined; - private constructor(private readonly options: BackupImportOptions) { + private constructor(options: BackupImportOptions) { super({ objectMode: true }); + this.#options = options; } public static async create( @@ -451,8 +453,7 @@ export class BackupImportStream extends Writable { await convo.updateLastMessage(); } catch (error) { log.error( - `${this.#logId}: failed to update conversation's last message` + - `${Errors.toLogFormat(error)}` + `${this.#logId}: failed to update conversation's last message ${Errors.toLogFormat(error)}` ); } }, @@ -463,7 +464,7 @@ export class BackupImportStream extends Writable { await pMap( allConversations, async conversation => { - if (this.options.type === 'cross-client-integration-test') { + if (this.#options.type === 'cross-client-integration-test') { return; } if ( @@ -494,7 +495,7 @@ export class BackupImportStream extends Writable { ); if ( - this.options.type !== 'cross-client-integration-test' && + this.#options.type !== 'cross-client-integration-test' && !isTestEnvironment(getEnvironment()) ) { await startBackupMediaDownload(); @@ -599,6 +600,7 @@ export class BackupImportStream extends Writable { await this.#fromChatFolder(item.chatFolder); } else { log.warn( + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions `${this.#logId}: unknown unsupported frame item ${frame.item}` ); throw new Error('Unknown unsupported frame type'); @@ -606,8 +608,8 @@ export class BackupImportStream extends Writable { } catch (error) { this.#frameErrorCount += 1; log.error( - `${this.#logId}: failed to process a frame ${frame.item}, ` + - `${Errors.toLogFormat(error)}` + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions + `${this.#logId}: failed to process a frame ${frame.item}, ${Errors.toLogFormat(error)}` ); } } @@ -648,8 +650,7 @@ export class BackupImportStream extends Writable { return await upgradeMessageSchema(attributes); } catch (error) { log.error( - `${this.#logId}: failed to migrate a message ${attributes.sent_at}, ` + - `${Errors.toLogFormat(error)}` + `${this.#logId}: failed to migrate a message ${attributes.sent_at}, ${Errors.toLogFormat(error)}` ); return attributes; } @@ -1130,13 +1131,13 @@ export class BackupImportStream extends Writable { ? Bytes.toBase64(deriveAccessKeyFromProfileKey(contact.profileKey)) : undefined, sealedSender: SEALED_SENDER.UNKNOWN, - profileSharing: contact.profileSharing === true, + profileSharing: contact.profileSharing, profileName: dropNull(contact.profileGivenName), profileFamilyName: dropNull(contact.profileFamilyName), systemGivenName: dropNull(contact.systemGivenName), systemFamilyName: dropNull(contact.systemFamilyName), systemNickname: dropNull(contact.systemNickname), - hideStory: contact.hideStory === true, + hideStory: contact.hideStory, username: dropNull(contact.username), expireTimerVersion: 1, nicknameGivenName: dropNull(contact.nickname?.given), @@ -1244,12 +1245,11 @@ export class BackupImportStream extends Writable { groupId, secretParams: Bytes.toBase64(secretParams), publicParams: Bytes.toBase64(publicParams), - profileSharing: group.whitelisted === true, - messageRequestResponseType: - group.whitelisted === true - ? SignalService.SyncMessage.MessageRequestResponse.Type.ACCEPT - : undefined, - hideStory: group.hideStory === true, + profileSharing: group.whitelisted, + messageRequestResponseType: group.whitelisted + ? SignalService.SyncMessage.MessageRequestResponse.Type.ACCEPT + : undefined, + hideStory: group.hideStory, storySendMode, avatar: avatarUrl ? { @@ -1438,7 +1438,7 @@ export class BackupImportStream extends Writable { result = { ...commonFields, name: list.name ?? '', - allowsReplies: list.allowReplies === true, + allowsReplies: list.allowReplies, isBlockList, members: (list.memberRecipientIds || []).map(recipientId => { const convo = this.#recipientIdToConvo.get(recipientId); @@ -1530,7 +1530,7 @@ export class BackupImportStream extends Writable { conversation.test_chatFrameImportedFromBackup = true; } - conversation.isArchived = chat.archived === true; + conversation.isArchived = chat.archived; conversation.isPinned = (chat.pinnedOrder || 0) !== 0; conversation.expireTimer = @@ -1550,9 +1550,9 @@ export class BackupImportStream extends Writable { chat.muteUntilMs ); } - conversation.markedUnread = chat.markedUnread === true; + conversation.markedUnread = chat.markedUnread; conversation.dontNotifyForMentionsIfMuted = - chat.dontNotifyForMentionsIfMuted === true; + chat.dontNotifyForMentionsIfMuted; const chatStyle = this.#fromChatStyle(chat.style); @@ -1672,7 +1672,7 @@ export class BackupImportStream extends Writable { type: directionalDetails.outgoing != null ? 'outgoing' : 'incoming', expirationStartTimestamp, expireTimer, - sms: chatItem.sms === true ? true : undefined, + sms: chatItem.sms ? true : undefined, ...directionDetails, }; const additionalMessages: Array = []; @@ -1867,7 +1867,7 @@ export class BackupImportStream extends Writable { expiresAt = toNumber(pinExpiry.pinExpiresAtTimestamp); } else { strictAssert( - pinExpiry.pinNeverExpires === true, + pinExpiry.pinNeverExpires, 'pinDetails: pinNeverExpires should be true if theres no pinExpiresAtTimestamp' ); expiresAt = null; @@ -1999,6 +1999,7 @@ export class BackupImportStream extends Writable { sendStatus = SendStatus.Skipped; } else { log.error( + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions `${timestamp}: Unknown sendStatus received: ${status}, falling back to Pending` ); // We fallback to pending for unknown send statuses @@ -2037,7 +2038,7 @@ export class BackupImportStream extends Writable { incoming.dateServerSent ); - const unidentifiedDeliveryReceived = incoming.sealedSender === true; + const unidentifiedDeliveryReceived = incoming.sealedSender; if (incoming.read) { return { @@ -2176,14 +2177,14 @@ export class BackupImportStream extends Writable { bodyRanges: this.#fromBodyRanges(data.text), })), bodyAttachment: data.longText - ? convertFilePointerToAttachment(data.longText, this.options) + ? convertFilePointerToAttachment(data.longText, this.#options) : undefined, attachments: data.attachments?.length ? data.attachments .map(attachment => convertBackupMessageAttachmentToAttachment( attachment, - this.options + this.#options ) ) .filter(isNotNil) @@ -2229,7 +2230,7 @@ export class BackupImportStream extends Writable { description: dropNull(preview.description), date: getCheckedTimestampOrUndefinedFromLong(preview.date), image: preview.image - ? convertFilePointerToAttachment(preview.image, this.options) + ? convertFilePointerToAttachment(preview.image, this.#options) : undefined, }; }) @@ -2248,7 +2249,7 @@ export class BackupImportStream extends Writable { ? [ convertBackupMessageAttachmentToAttachment( attachment, - this.options + this.#options ), ].filter(isNotNil) : undefined, @@ -2292,7 +2293,7 @@ export class BackupImportStream extends Writable { result.body = textReply.text?.body ?? undefined; result.bodyRanges = this.#fromBodyRanges(textReply.text); result.bodyAttachment = textReply.longText - ? convertFilePointerToAttachment(textReply.longText, this.options) + ? convertFilePointerToAttachment(textReply.longText, this.#options) : undefined; } else if (emoji) { result.storyReaction = { @@ -2322,7 +2323,7 @@ export class BackupImportStream extends Writable { body: textReply.text?.body ?? undefined, bodyRanges: this.#fromBodyRanges(textReply.text), bodyAttachment: textReply.longText - ? convertFilePointerToAttachment(textReply.longText, this.options) + ? convertFilePointerToAttachment(textReply.longText, this.#options) : undefined, }; } @@ -2453,7 +2454,7 @@ export class BackupImportStream extends Writable { ? stringToMIMEType(contentType) : APPLICATION_OCTET_STREAM, thumbnail: thumbnail?.pointer - ? convertFilePointerToAttachment(thumbnail.pointer, this.options) + ? convertFilePointerToAttachment(thumbnail.pointer, this.#options) : undefined, }; }) ?? [], @@ -2640,7 +2641,7 @@ export class BackupImportStream extends Writable { ? { avatar: convertFilePointerToAttachment( avatar, - this.options + this.#options ), isProfile: false, } @@ -2715,7 +2716,7 @@ export class BackupImportStream extends Writable { packKey: Bytes.toBase64(packKey), stickerId, data: data - ? convertFilePointerToAttachment(data, this.options) + ? convertFilePointerToAttachment(data, this.#options) : undefined, }, reactions: this.#fromReactions(item.stickerMessage.reactions), @@ -2788,7 +2789,7 @@ export class BackupImportStream extends Writable { receiptCredentialPresentation: Bytes.toBase64( giftBadge.receiptCredentialPresentation ), - expiration: Number(receipt.getReceiptExpirationTime()) * SECOND, + expiration: receipt.getReceiptExpirationTime() * SECOND, id: undefined, level: Number(receipt.getReceiptLevel()), state, @@ -3948,10 +3949,10 @@ export class BackupImportStream extends Writable { emoji: dropNull(emoji), color: dropNull(color) ?? DEFAULT_PROFILE_COLOR, createdAtMs: getCheckedTimestampOrUndefinedFromLong(createdAtMs) ?? 0, - allowAllCalls: Boolean(allowAllCalls), - allowAllMentions: Boolean(allowAllMentions), + allowAllCalls, + allowAllMentions, allowedMembers: new Set(allowedMemberConversationIds ?? []), - scheduleEnabled: Boolean(scheduleEnabled), + scheduleEnabled: scheduleEnabled, scheduleStartTime: dropNull(scheduleStartTime), scheduleEndTime: dropNull(scheduleEndTime), scheduleDaysEnabled: parseScheduleDaysEnabled(scheduleDaysEnabled), @@ -4255,7 +4256,7 @@ export class BackupImportStream extends Writable { } #isLocalBackup() { - return this.options.type === 'local-encrypted'; + return this.#options.type === 'local-encrypted'; } #isMediaEnabledBackup() { diff --git a/ts/services/backups/util/FileStream.node.ts b/ts/services/backups/util/FileStream.node.ts index ed8c4d61c4..4730b2bd81 100644 --- a/ts/services/backups/util/FileStream.node.ts +++ b/ts/services/backups/util/FileStream.node.ts @@ -6,13 +6,15 @@ import { Buffer } from 'node:buffer'; import { InputStream } from '@signalapp/libsignal-client/dist/io.js'; export class FileStream extends InputStream { + readonly #filePath: string; #file: FileHandle | undefined; #position = 0; #buffer = Buffer.alloc(16 * 1024); #initPromise: Promise | undefined; - constructor(private readonly filePath: string) { + constructor(filePath: string) { super(); + this.#filePath = filePath; } public override async close(): Promise { @@ -46,7 +48,7 @@ export class FileStream extends InputStream { return this.#file; } - const filePromise = open(this.filePath); + const filePromise = open(this.#filePath); this.#initPromise = filePromise; this.#file = await filePromise; return this.#file; diff --git a/ts/services/calling.preload.ts b/ts/services/calling.preload.ts index 096a6a2790..808198ebd3 100644 --- a/ts/services/calling.preload.ts +++ b/ts/services/calling.preload.ts @@ -201,10 +201,10 @@ const { cleanExpiredGroupCallRingCancellations, } = DataWriter; -const RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD: Map< +const RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD = new Map< HttpMethod, 'GET' | 'PUT' | 'POST' | 'DELETE' -> = new Map([ +>([ [HttpMethod.Get, 'GET'], [HttpMethod.Put, 'PUT'], [HttpMethod.Post, 'POST'], @@ -486,12 +486,14 @@ async function ensureSystemPermissions({ hasLocalAudio: boolean; }): Promise { if (hasLocalAudio) { + // oxlint-disable-next-line typescript/await-thenable await window.reduxActions.globalModals.ensureSystemMediaPermissions( 'microphone', 'call' ); } if (hasLocalVideo) { + // oxlint-disable-next-line typescript/await-thenable await window.reduxActions.globalModals.ensureSystemMediaPermissions( 'camera', 'call' @@ -590,13 +592,13 @@ export class CallingClass { #deviceReselectionTimer?: NodeJS.Timeout; #callsLookup: { [key: string]: Call | GroupCall }; - #cameraEnabled: boolean = false; + #cameraEnabled = false; - #registeredAssets: Map = new Map(); + readonly #registeredAssets = new Map(); // Send our profile key to other participants in call link calls to ensure they // can see our profile info. Only send once per aci until the next app start. - #sendProfileKeysForAdhocCallCache: Set; + readonly #sendProfileKeysForAdhocCallCache: Set; constructor() { this.#videoCapturer = new GumVideoCapturer(); @@ -2601,6 +2603,7 @@ export class CallingClass { if (enabled) { // Make sure we have access to camera + // oxlint-disable-next-line typescript/await-thenable await window.reduxActions.globalModals.ensureSystemMediaPermissions( 'camera', 'call' @@ -3056,6 +3059,7 @@ export class CallingClass { } async enableLocalCamera(mode: CallMode): Promise { + // oxlint-disable-next-line typescript/await-thenable await window.reduxActions.globalModals.ensureSystemMediaPermissions( 'camera', 'call' @@ -3608,7 +3612,7 @@ export class CallingClass { ageInSeconds: number, wasVideoCall: boolean, receivedAtCounter: number | undefined, - receivedAtMS: number | undefined = undefined + receivedAtMS?: number ) { const conversation = window.ConversationController.get(remoteUserId); if (!conversation) { @@ -3777,7 +3781,7 @@ export class CallingClass { call.handleRemoteSharingScreen = () => { reduxInterface.remoteSharingScreenChange({ conversationId, - isSharingScreen: Boolean(call.remoteSharingScreen), + isSharingScreen: call.remoteSharingScreen, }); }; diff --git a/ts/services/distributionListLoader.preload.ts b/ts/services/distributionListLoader.preload.ts index 9b92dc54bf..8424d5f4a7 100644 --- a/ts/services/distributionListLoader.preload.ts +++ b/ts/services/distributionListLoader.preload.ts @@ -17,10 +17,10 @@ export function getDistributionListsForRedux(): Array ({ - allowsReplies: Boolean(list.allowsReplies), + allowsReplies: list.allowsReplies, deletedAtTimestamp: list.deletedAtTimestamp, id: list.id, - isBlockList: Boolean(list.isBlockList), + isBlockList: list.isBlockList, name: list.name, memberServiceIds: list.members, })) diff --git a/ts/services/donations.preload.ts b/ts/services/donations.preload.ts index 13d7e807aa..aa9061b2b9 100644 --- a/ts/services/donations.preload.ts +++ b/ts/services/donations.preload.ts @@ -659,7 +659,7 @@ async function withConcurrencyCheck Promise>( isDonationStepInProgress = true; try { - return fn(); + return await fn(); } finally { isDonationStepInProgress = false; } @@ -1144,7 +1144,7 @@ export async function _redeemReceipt( async function failDonation( errorType: DonationErrorType, - details: string | undefined = undefined + details?: string ): Promise { const workflow = _getWorkflowFromRedux(); const logId = `failDonation(${workflow?.id ? redactId(workflow.id) : 'NONE'})`; @@ -1234,6 +1234,7 @@ export function _getWorkflowFromStorage(): DonationWorkflow | undefined { const result = safeParseUnknown(donationWorkflowSchema, workflowData); if (!result.success) { log.error( + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions `${logId}: Workflow from storage was malformed: ${result.error.flatten()}` ); return undefined; @@ -1261,6 +1262,7 @@ export async function _saveWorkflowToStorage( const result = safeParseStrict(donationWorkflowSchema, workflow); if (!result.success) { log.error( + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions `${logId}: Provided workflow was malformed: ${result.error.flatten()}` ); throw result.error; diff --git a/ts/services/expiringMessagesDeletion.preload.ts b/ts/services/expiringMessagesDeletion.preload.ts index c95a8d95ed..5df82e5b19 100644 --- a/ts/services/expiringMessagesDeletion.preload.ts +++ b/ts/services/expiringMessagesDeletion.preload.ts @@ -18,7 +18,10 @@ const log = createLogger('expiringMessagesDeletion'); class ExpiringMessagesDeletionService { #timeout?: ReturnType; - #debouncedCheckExpiringMessages = debounce(this.#checkExpiringMessages, 1000); + readonly #debouncedCheckExpiringMessages = debounce( + this.#checkExpiringMessages, + 1000 + ); update() { drop(this.#debouncedCheckExpiringMessages()); @@ -64,7 +67,7 @@ class ExpiringMessagesDeletionService { } log.info('destroyExpiredMessages: done, scheduling another check'); - void this.update(); + this.update(); } async #checkExpiringMessages() { diff --git a/ts/services/globalMessageAudio.std.ts b/ts/services/globalMessageAudio.std.ts index ade34506e1..563c825c92 100644 --- a/ts/services/globalMessageAudio.std.ts +++ b/ts/services/globalMessageAudio.std.ts @@ -12,7 +12,7 @@ const { noop } = lodash; */ class GlobalMessageAudio { // oxlint-disable-next-line no-undef FIXME - #audio: HTMLAudioElement = new Audio(); + readonly #audio: HTMLAudioElement = new Audio(); #url: string | undefined; // true immediately after play() is called, even if still loading diff --git a/ts/services/keyTransparency.preload.ts b/ts/services/keyTransparency.preload.ts index 514437d2a2..7037e5e115 100644 --- a/ts/services/keyTransparency.preload.ts +++ b/ts/services/keyTransparency.preload.ts @@ -56,7 +56,7 @@ export function isKeyTransparencyAvailable(): boolean { export class KeyTransparency { #isRunning = false; - #scheduler = new CheckScheduler({ + readonly #scheduler = new CheckScheduler({ name: 'KeyTransparency', interval: WEEK, storageKey: 'lastKeyTransparencySelfCheck', @@ -71,7 +71,7 @@ export class KeyTransparency { }, }); - #selfCheckDedup = new TaskDeduplicator( + readonly #selfCheckDedup = new TaskDeduplicator( 'KeyTransparency.selfCheck', abortSignal => this.#selfCheck(abortSignal) ); diff --git a/ts/services/megaphone.preload.ts b/ts/services/megaphone.preload.ts index 92409505fc..0afb6a5215 100644 --- a/ts/services/megaphone.preload.ts +++ b/ts/services/megaphone.preload.ts @@ -48,7 +48,7 @@ export function initMegaphoneCheckService(): void { export async function runMegaphoneCheck(): Promise { try { const megaphones = await DataReader.getAllMegaphones(); - const shownIds: Set = new Set(); + const shownIds = new Set(); log.info( `runMegaphoneCheck: Checking ${megaphones.length} locally saved megaphones` diff --git a/ts/services/notificationProfilesService.preload.ts b/ts/services/notificationProfilesService.preload.ts index 6510171435..2a6a686fe5 100644 --- a/ts/services/notificationProfilesService.preload.ts +++ b/ts/services/notificationProfilesService.preload.ts @@ -36,7 +36,7 @@ const log = createLogger('notificationProfilesService'); export class NotificationProfilesService { #timeout?: ReturnType | null; - #debouncedRefreshNextEvent = debounce(this.#refreshNextEvent, 1000); + readonly #debouncedRefreshNextEvent = debounce(this.#refreshNextEvent, 1000); update(): void { drop(this.#debouncedRefreshNextEvent()); diff --git a/ts/services/notifications.preload.ts b/ts/services/notifications.preload.ts index e7fbb915ef..c582476782 100644 --- a/ts/services/notifications.preload.ts +++ b/ts/services/notifications.preload.ts @@ -98,7 +98,7 @@ class NotificationService extends EventEmitter { // to manually close them. This introduces a minimum amount of time between calls, // and batches up the quick successive update() calls we get from an incoming // read sync, which might have a number of messages referenced inside of it. - #update: () => unknown; + readonly #update: () => unknown; constructor() { super(); diff --git a/ts/services/profiles.preload.ts b/ts/services/profiles.preload.ts index a1024e80c5..4f581aa250 100644 --- a/ts/services/profiles.preload.ts +++ b/ts/services/profiles.preload.ts @@ -90,14 +90,16 @@ const OBSERVED_CAPABILITY_KEYS = Object.keys({ const PROFILE_FETCH_CONCURRENCY = 30; export class ProfileService { - #jobQueue: PQueue; - #jobsByConversationId: Map = new Map(); + readonly #fetchProfile: typeof doGetProfile; + readonly #jobQueue: PQueue; + readonly #jobsByConversationId = new Map(); #isPaused = false; constructor( - private fetchProfile = doGetProfile, + fetchProfile = doGetProfile, concurrency = PROFILE_FETCH_CONCURRENCY ) { + this.#fetchProfile = fetchProfile; this.#jobQueue = new PQueue({ concurrency, timeout: MINUTE * 2, @@ -151,7 +153,7 @@ export class ProfileService { } try { - await this.fetchProfile(conversation, groupId); + await this.#fetchProfile(conversation, groupId); resolve(); } catch (error) { resolve(); diff --git a/ts/services/releaseNoteAndMegaphoneFetcher.preload.ts b/ts/services/releaseNoteAndMegaphoneFetcher.preload.ts index 7fdd440569..51887e7c61 100644 --- a/ts/services/releaseNoteAndMegaphoneFetcher.preload.ts +++ b/ts/services/releaseNoteAndMegaphoneFetcher.preload.ts @@ -110,7 +110,7 @@ export class ReleaseNoteAndMegaphoneFetcher { static initComplete = false; #timeout: NodeJS.Timeout | undefined; #isRunning = false; - #server: ServerType; + readonly #server: ServerType; constructor(server: ServerType) { this.#server = server; @@ -145,11 +145,9 @@ export class ReleaseNoteAndMegaphoneFetcher { #getLocales(): ReadonlyArray { const globalLocale = new Intl.Locale(window.SignalContext.getI18nLocale()); - return [ - globalLocale.toString(), - globalLocale.language.toString(), - 'en', - ].map(locale => locale.toLocaleLowerCase().replace('-', '_')); + return [globalLocale.toString(), globalLocale.language, 'en'].map(locale => + locale.toLocaleLowerCase().replace('-', '_') + ); } async #maybeGetLocaleMegaphone( diff --git a/ts/services/retryPlaceholders.std.ts b/ts/services/retryPlaceholders.std.ts index e701b6ad63..6d394bb522 100644 --- a/ts/services/retryPlaceholders.std.ts +++ b/ts/services/retryPlaceholders.std.ts @@ -48,7 +48,7 @@ export class RetryPlaceholders { #items = new Array(); #byConversation: ByConversationLookupType = {}; #byMessage: ByMessageLookupType = new Map(); - #retryReceiptLifespan: number; + readonly #retryReceiptLifespan: number; #storage: Pick | undefined; constructor(options: { retryReceiptLifespan?: number } = {}) { diff --git a/ts/services/senderCertificate.preload.ts b/ts/services/senderCertificate.preload.ts index c339da1c69..ba9d9e8c35 100644 --- a/ts/services/senderCertificate.preload.ts +++ b/ts/services/senderCertificate.preload.ts @@ -41,10 +41,10 @@ type ServerType = Readonly<{ export class SenderCertificateService { #server?: ServerType; - #fetchPromises: Map< + readonly #fetchPromises = new Map< SenderCertificateMode, Promise - > = new Map(); + >(); #events?: Pick; #storage?: StorageInterface; diff --git a/ts/services/storage.preload.ts b/ts/services/storage.preload.ts index a2f3c6e1b9..510ac6bcb1 100644 --- a/ts/services/storage.preload.ts +++ b/ts/services/storage.preload.ts @@ -281,8 +281,7 @@ async function generateManifest( if (conversationType === ConversationTypes.Me) { storageRecord = { record: { - // oxlint-disable-next-line no-await-in-loop - account: await toAccountRecord(conversation, { + account: toAccountRecord(conversation, { notificationProfileSyncDisabled, }), }, @@ -894,10 +893,10 @@ async function generateManifest( // manifest: let recordIkm: Uint8Array | undefined; if (previousManifest) { - const pendingInserts: Set = new Set(); - const pendingDeletes: Set = new Set(); + const pendingInserts = new Set(); + const pendingDeletes = new Set(); - const remoteKeys: Set = new Set(); + const remoteKeys = new Set(); (previousManifest.identifiers ?? []).forEach( (identifier: IManifestRecordIdentifier) => { strictAssert(identifier.raw, 'Identifier without raw field'); @@ -906,7 +905,7 @@ async function generateManifest( } ); - const localKeys: Set = new Set(); + const localKeys = new Set(); for (const storageID of recordsByID.keys()) { localKeys.add(storageID); @@ -997,8 +996,8 @@ async function encryptManifest( version: number, { recordsByID, recordIkm, insertKeys }: EncryptManifestOptionsType ): Promise { - const manifestRecordKeys: Set = new Set(); - const newItems: Set = new Set(); + const manifestRecordKeys = new Set(); + const newItems = new Set(); for (const [storageID, { itemType, storageRecord }] of recordsByID) { const identifier: Proto.ManifestRecord.Identifier.Params = { @@ -2145,7 +2144,7 @@ async function processRemoteRecords( needProfileFetch.map(convo => drop(convo.getProfiles())); // Collect full map of previously and currently unknown records - const unknownRecords: Map = new Map(); + const unknownRecords = new Map(); const previousUnknownRecords: ReadonlyArray = itemStorage.get( @@ -2373,7 +2372,7 @@ async function upload({ } const localManifestVersion = itemStorage.get('manifestVersion', 0); - const version = Number(localManifestVersion) + 1; + const version = localManifestVersion + 1; log.info(`${logId}/${version}: will update to manifest version`); diff --git a/ts/services/storageRecordOps.preload.ts b/ts/services/storageRecordOps.preload.ts index 82ab35d17d..8102ca5d27 100644 --- a/ts/services/storageRecordOps.preload.ts +++ b/ts/services/storageRecordOps.preload.ts @@ -327,9 +327,9 @@ export async function toContactRecord( identityKey: serviceId ? ((await signalProtocolStore.loadIdentityKey(serviceId)) ?? null) : null, - identityState: verified ? toRecordVerified(Number(verified)) : null, + identityState: verified ? toRecordVerified(verified) : null, pniSignatureVerified: conversation.get('pniSignatureVerified') ?? false, - profileKey: profileKey ? Bytes.fromBase64(String(profileKey)) : null, + profileKey: profileKey ? Bytes.fromBase64(profileKey) : null, givenName: conversation.get('profileName') || null, familyName: conversation.get('profileFamilyName') || null, nickname: @@ -353,7 +353,7 @@ export async function toContactRecord( MAX_VALUE ), avatarColor: conversation.get('colorFromPrimary') ?? null, - hideStory: hideStory != null ? Boolean(hideStory) : null, + hideStory: hideStory ?? null, unregisteredAtTimestamp: getSafeLongFromTimestamp( conversation.get('firstUnregisteredAt') ), @@ -528,7 +528,7 @@ export function toAccountRecord( } return { - profileKey: profileKey ? Bytes.fromBase64(String(profileKey)) : null, + profileKey: profileKey ? Bytes.fromBase64(profileKey) : null, givenName: conversation.get('profileName') || null, familyName: conversation.get('profileFamilyName') || null, avatarUrlPath: itemStorage.get('avatarUrl') || null, @@ -540,8 +540,7 @@ export function toAccountRecord( typingIndicators: getTypingIndicatorSetting(), linkPreviews: getLinkPreviewSetting(), - preferContactAvatars: - preferContactAvatars != null ? Boolean(preferContactAvatars) : null, + preferContactAvatars: preferContactAvatars ?? null, preferredReactionEmoji: preferredReactionEmoji.canBeSynced( rawPreferredReactionEmoji ) @@ -660,8 +659,8 @@ export function toStoryDistributionListRecord( deletedAtTimestamp: getSafeLongFromTimestamp( storyDistributionList.deletedAtTimestamp ), - allowsReplies: Boolean(storyDistributionList.allowsReplies), - isBlockList: Boolean(storyDistributionList.isBlockList), + allowsReplies: storyDistributionList.allowsReplies, + isBlockList: storyDistributionList.isBlockList, recipientServiceIdsBinary: isProtoBinaryEncodingEnabled() ? storyDistributionList.members.map(serviceId => { return toServiceIdObject(serviceId).getServiceIdBinary(); @@ -1236,12 +1235,10 @@ export async function mergeGroupV2Record( ); conversation.set({ - hideStory: Boolean(groupV2Record.hideStory), - isArchived: Boolean(groupV2Record.archived), - markedUnread: Boolean(groupV2Record.markedUnread), - dontNotifyForMentionsIfMuted: Boolean( - groupV2Record.dontNotifyForMentionsIfMuted - ), + hideStory: groupV2Record.hideStory, + isArchived: groupV2Record.archived, + markedUnread: groupV2Record.markedUnread, + dontNotifyForMentionsIfMuted: groupV2Record.dontNotifyForMentionsIfMuted, storageID, storageVersion, storySendMode, @@ -1468,9 +1465,9 @@ export async function mergeContactRecord( const oldStorageVersion = conversation.get('storageVersion'); conversation.set({ - hideStory: Boolean(contactRecord.hideStory), - isArchived: Boolean(contactRecord.archived), - markedUnread: Boolean(contactRecord.markedUnread), + hideStory: contactRecord.hideStory, + isArchived: contactRecord.archived, + markedUnread: contactRecord.markedUnread, storageID, storageVersion, needsStorageServiceSync: false, @@ -1568,14 +1565,14 @@ export async function mergeAccountRecord( const details = logRecordChanges( toAccountRecord(conversation, { - notificationProfileSyncDisabled: Boolean(notificationProfileSyncDisabled), + notificationProfileSyncDisabled, }), accountRecord ); const updatedConversations = new Array(); - await itemStorage.put('read-receipt-setting', Boolean(readReceipts)); + await itemStorage.put('read-receipt-setting', readReceipts); if (typeof sealedSenderIndicators === 'boolean') { await itemStorage.put('sealedSenderIndicators', sealedSenderIndicators); @@ -1596,10 +1593,7 @@ export async function mergeAccountRecord( const postRegistrationSyncsComplete = itemStorage.get('postRegistrationSyncsStatus') !== 'incomplete'; - if ( - Boolean(previous) !== Boolean(preferContactAvatars) && - postRegistrationSyncsComplete - ) { + if (previous !== preferContactAvatars && postRegistrationSyncsComplete) { await window.ConversationController.forceRerender(); } } @@ -1800,66 +1794,40 @@ export async function mergeAccountRecord( await saveBackupsSubscriberData(backupSubscriberData); await saveBackupTier(toNumber(backupTier) ?? undefined); - await itemStorage.put( - 'displayBadgesOnProfile', - Boolean(displayBadgesOnProfile) - ); - await itemStorage.put( - 'keepMutedChatsArchived', - Boolean(keepMutedChatsArchived) - ); - await itemStorage.put( - 'hasSetMyStoriesPrivacy', - Boolean(hasSetMyStoriesPrivacy) - ); + await itemStorage.put('displayBadgesOnProfile', displayBadgesOnProfile); + await itemStorage.put('keepMutedChatsArchived', keepMutedChatsArchived); + await itemStorage.put('hasSetMyStoriesPrivacy', hasSetMyStoriesPrivacy); { - const hasViewedOnboardingStoryBool = Boolean(hasViewedOnboardingStory); - await itemStorage.put( - 'hasViewedOnboardingStory', - hasViewedOnboardingStoryBool - ); - if (hasViewedOnboardingStoryBool) { + await itemStorage.put('hasViewedOnboardingStory', hasViewedOnboardingStory); + if (hasViewedOnboardingStory) { drop(findAndDeleteOnboardingStoryIfExists()); } else { drop(downloadOnboardingStory()); } } - { - const hasCompletedUsernameOnboardingBool = Boolean( - hasCompletedUsernameOnboarding - ); - await itemStorage.put( - 'hasCompletedUsernameOnboarding', - hasCompletedUsernameOnboardingBool - ); - } - { - const hasCompletedUsernameOnboardingBool = Boolean( - hasSeenGroupStoryEducationSheet - ); - await itemStorage.put( - 'hasSeenGroupStoryEducationSheet', - hasCompletedUsernameOnboardingBool - ); - } + await itemStorage.put( + 'hasCompletedUsernameOnboarding', + hasCompletedUsernameOnboarding + ); + await itemStorage.put( + 'hasSeenGroupStoryEducationSheet', + hasSeenGroupStoryEducationSheet + ); await itemStorage.put( 'hasSeenAdminDeleteEducationDialog', hasSeenAdminDeleteEducationDialog ?? false ); { - const hasKeyTransparencyDisabled = Boolean( - automaticKeyVerificationDisabled - ); await itemStorage.put( 'hasKeyTransparencyDisabled', - hasKeyTransparencyDisabled + automaticKeyVerificationDisabled ); - if (hasKeyTransparencyDisabled) { + if (automaticKeyVerificationDisabled) { await keyTransparency.disable(); } } { - const hasStoriesDisabled = Boolean(storiesDisabled); + const hasStoriesDisabled = storiesDisabled; await itemStorage.put('hasStoriesDisabled', hasStoriesDisabled); onHasStoriesDisabledChange(hasStoriesDisabled); } @@ -1918,6 +1886,7 @@ export async function mergeAccountRecord( log.info( `process(${storageVersion}): Account just flipped from notificationProfileSyncDisabled=${previousSyncDisabled} to ${notificationProfileSyncDisabled}` ); + // oxlint-disable-next-line typescript/await-thenable await window.reduxActions.notificationProfiles.setIsSyncEnabled( !notificationProfileSyncDisabled, { fromStorageService: true } @@ -1979,8 +1948,8 @@ export async function mergeAccountRecord( } conversation.set({ - isArchived: Boolean(noteToSelfArchived), - markedUnread: Boolean(noteToSelfMarkedUnread), + isArchived: noteToSelfArchived, + markedUnread: noteToSelfMarkedUnread, storageID, storageVersion, needsStorageServiceSync: false, @@ -2087,10 +2056,10 @@ export async function mergeStoryDistributionListRecord( const storyDistribution: StoryDistributionWithMembersType = { id: listId, - name: String(storyDistributionListRecord.name), + name: storyDistributionListRecord.name, deletedAtTimestamp: isMyStory ? undefined : deletedAtTimestamp, - allowsReplies: Boolean(storyDistributionListRecord.allowsReplies), - isBlockList: Boolean(storyDistributionListRecord.isBlockList), + allowsReplies: storyDistributionListRecord.allowsReplies, + isBlockList: storyDistributionListRecord.isBlockList, members: remoteListMembers, senderKeyInfo: localStoryDistributionList?.senderKeyInfo, @@ -2154,10 +2123,10 @@ export async function mergeStoryDistributionListRecord( toRemove, }); window.reduxActions.storyDistributionLists.modifyDistributionList({ - allowsReplies: Boolean(storyDistribution.allowsReplies), + allowsReplies: storyDistribution.allowsReplies, deletedAtTimestamp: storyDistribution.deletedAtTimestamp, id: storyDistribution.id, - isBlockList: Boolean(storyDistribution.isBlockList), + isBlockList: storyDistribution.isBlockList, membersToAdd: toAdd, membersToRemove: toRemove, name: storyDistribution.name, @@ -2865,10 +2834,10 @@ export async function mergeNotificationProfileRecord( emoji: dropNull(emoji), color: dropNull(color) ?? DEFAULT_PROFILE_COLOR, createdAtMs: toNumber(createdAtMs) ?? Date.now(), - allowAllCalls: Boolean(allowAllCalls), - allowAllMentions: Boolean(allowAllMentions), + allowAllCalls, + allowAllMentions, allowedMembers: new Set(allowedMemberConversationIds), - scheduleEnabled: Boolean(scheduleEnabled), + scheduleEnabled, scheduleStartTime: dropNull(scheduleStartTime), scheduleEndTime: dropNull(scheduleEndTime), scheduleDaysEnabled: fromDayOfWeekArray( diff --git a/ts/services/tapToViewMessagesDeletionService.preload.ts b/ts/services/tapToViewMessagesDeletionService.preload.ts index ccfa13a483..ab4bfe6f51 100644 --- a/ts/services/tapToViewMessagesDeletionService.preload.ts +++ b/ts/services/tapToViewMessagesDeletionService.preload.ts @@ -56,7 +56,7 @@ async function eraseTapToViewMessages() { class TapToViewMessagesDeletionService { #timeout?: ReturnType; #isPaused = false; - #debouncedUpdate = debounce(this.#checkTapToViewMessages); + readonly #debouncedUpdate = debounce(this.#checkTapToViewMessages); update() { drop(this.#debouncedUpdate()); diff --git a/ts/services/usernameIntegrity.preload.ts b/ts/services/usernameIntegrity.preload.ts index 9dc21d4168..5bbab24b49 100644 --- a/ts/services/usernameIntegrity.preload.ts +++ b/ts/services/usernameIntegrity.preload.ts @@ -27,7 +27,7 @@ const STORAGE_SERVICE_TIMEOUT = 30 * MINUTE; class UsernameIntegrityService { #isStarted = false; - #scheduler = new CheckScheduler({ + readonly #scheduler = new CheckScheduler({ name: 'UsernameIntegrityService', interval: DAY, storageKey: 'usernameLastIntegrityCheck', diff --git a/ts/services/writeProfile.preload.ts b/ts/services/writeProfile.preload.ts index 4d3e2aea19..c472dd1af6 100644 --- a/ts/services/writeProfile.preload.ts +++ b/ts/services/writeProfile.preload.ts @@ -110,7 +110,7 @@ export async function writeProfile( encryptedAvatarData ); - const hash = await computeHash(newAvatar); + const hash = computeHash(newAvatar); if (hash !== avatarHash) { log.info('removing old avatar and saving the new one'); diff --git a/ts/sql/Client.preload.ts b/ts/sql/Client.preload.ts index d707db5abb..16a98321d0 100644 --- a/ts/sql/Client.preload.ts +++ b/ts/sql/Client.preload.ts @@ -481,7 +481,9 @@ async function getItemById( const data = await readableChannel.getItemById(id); try { - return spec ? specToBytes(spec, data) : (data as unknown as ItemType); + return spec + ? await specToBytes(spec, data) + : (data as unknown as ItemType); } catch (error) { log.warn(`getItemById(${id}): Failed to parse item from spec`, error); return undefined; diff --git a/ts/sql/Server.node.ts b/ts/sql/Server.node.ts index fba6d67398..88ee5625d0 100644 --- a/ts/sql/Server.node.ts +++ b/ts/sql/Server.node.ts @@ -4117,8 +4117,8 @@ function getAllStories( return hydrateMessages(db, rows).map(msg => ({ ...msg, - hasReplies: Boolean(repliesLookup.has(msg.id)), - hasRepliesFromSelf: Boolean(repliesFromSelfLookup.has(msg.id)), + hasReplies: repliesLookup.has(msg.id), + hasRepliesFromSelf: repliesFromSelfLookup.has(msg.id), })); })(); } @@ -6416,7 +6416,7 @@ function getBackupAttachmentDownloadProgress( function getNextAttachmentDownloadJobs( db: WritableDB, { - limit = 3, + limit, sources, prioritizeMessageIds, timestamp = Date.now(), @@ -6535,6 +6535,7 @@ function saveAttachmentDownloadJobs( return; } if (errors.length === 1) { + // oxlint-disable-next-line typescript/only-throw-error throw errors[0]; } throw new AggregateError( @@ -7342,7 +7343,9 @@ function getAllStickerPacks(db: ReadableDB): Array { // The columns have STRING type so if they have numeric value, sqlite // will return integers. + // oxlint-disable-next-line typescript/no-unnecessary-type-conversion author: String(row.author), + // oxlint-disable-next-line typescript/no-unnecessary-type-conversion title: String(row.title), }; }); diff --git a/ts/sql/channels.preload.ts b/ts/sql/channels.preload.ts index e9eee969ff..c83c7831f8 100644 --- a/ts/sql/channels.preload.ts +++ b/ts/sql/channels.preload.ts @@ -27,11 +27,9 @@ export async function ipcInvoke( name: string, args: ReadonlyArray ): Promise { - const fnName = String(name); - if (shutdownPromise && name !== 'close') { throw new Error( - `Rejecting SQL channel job (${access}, ${fnName}); ` + + `Rejecting SQL channel job (${access}, ${name}); ` + 'application is shutting down' ); } @@ -59,7 +57,7 @@ export async function ipcInvoke( resolveShutdown?.(); } } - }, `SQL channel call (${access}, ${fnName})`); + }, `SQL channel call (${access}, ${name})`); } export async function doShutdown(): Promise { diff --git a/ts/sql/main.main.ts b/ts/sql/main.main.ts index 1f42f60314..511d7323c2 100644 --- a/ts/sql/main.main.ts +++ b/ts/sql/main.main.ts @@ -146,9 +146,9 @@ export class MainSQL { #logger?: LoggerType; // oxlint-disable-next-line typescript/no-explicit-any - #onResponse = new Map>(); + readonly #onResponse = new Map>(); - #shouldLogQueryTime: (queryName: string) => boolean; + readonly #shouldLogQueryTime: (queryName: string) => boolean; #shouldTrackQueryStats = false; #queryStats?: { @@ -508,7 +508,7 @@ export class MainSQL { this.#logger?.info( `Top ${maxQueriesToLog} queries by cumulative duration (ms) over last ${epochDuration}ms` + `${epochName ? ` during '${epochName}'` : ''}: ` + - `${sortedByCumulativeDuration + sortedByCumulativeDuration .slice(0, maxQueriesToLog) .map(stats => { return ( @@ -518,7 +518,7 @@ export class MainSQL { `count: ${stats.count}` ); }) - .join(' ||| ')}` + + .join(' ||| ') + `; Total cumulative duration of all SQL queries during this epoch: ${this.#roundDuration(cumulativeDuration)}ms` ); } diff --git a/ts/sql/migrations/41-uuid-keys.std.ts b/ts/sql/migrations/41-uuid-keys.std.ts index bc9d3009df..5e41e424a2 100644 --- a/ts/sql/migrations/41-uuid-keys.std.ts +++ b/ts/sql/migrations/41-uuid-keys.std.ts @@ -233,7 +233,7 @@ export default function updateToSchemaVersion41( updateSenderKey.run({ id, newId, - newSenderId: `${senderId.replace(conversationId, uuid)}`, + newSenderId: senderId.replace(conversationId, uuid), }); } diff --git a/ts/sql/migrations/88-service-ids.std.ts b/ts/sql/migrations/88-service-ids.std.ts index b577b1fef3..818df0c462 100644 --- a/ts/sql/migrations/88-service-ids.std.ts +++ b/ts/sql/migrations/88-service-ids.std.ts @@ -994,6 +994,7 @@ function migrateJobs( try { const parsedData: unknown = JSON.parse(data); + // oxlint-disable-next-line typescript/no-redundant-type-constituents let updatedData: unknown | undefined; if (queueType === 'conversation') { const convoJob = parsedData as LegacyConversationJob; diff --git a/ts/sql/migrations/91-clean-keys.std.ts b/ts/sql/migrations/91-clean-keys.std.ts index 947c5d3206..3077f60539 100644 --- a/ts/sql/migrations/91-clean-keys.std.ts +++ b/ts/sql/migrations/91-clean-keys.std.ts @@ -18,38 +18,38 @@ export default function updateToSchemaVersion91( db.exec(` --- First, prekeys DROP INDEX preKeys_ourServiceId; - + ALTER TABLE preKeys DROP COLUMN ourServiceId; ALTER TABLE preKeys ADD COLUMN ourServiceId NUMBER GENERATED ALWAYS AS (json_extract(json, '$.ourServiceId')); - - CREATE INDEX preKeys_ourServiceId ON preKeys (ourServiceId); + + CREATE INDEX preKeys_ourServiceId ON preKeys (ourServiceId); -- Second, kyber prekeys DROP INDEX kyberPreKeys_ourServiceId; - + ALTER TABLE kyberPreKeys DROP COLUMN ourServiceId; ALTER TABLE kyberPreKeys ADD COLUMN ourServiceId NUMBER GENERATED ALWAYS AS (json_extract(json, '$.ourServiceId')); - - CREATE INDEX kyberPreKeys_ourServiceId ON kyberPreKeys (ourServiceId); + + CREATE INDEX kyberPreKeys_ourServiceId ON kyberPreKeys (ourServiceId); -- Finally, signed prekeys DROP INDEX signedPreKeys_ourServiceId; - + ALTER TABLE signedPreKeys DROP COLUMN ourServiceId; ALTER TABLE signedPreKeys ADD COLUMN ourServiceId NUMBER GENERATED ALWAYS AS (json_extract(json, '$.ourServiceId')); - - CREATE INDEX signedPreKeys_ourServiceId ON signedPreKeys (ourServiceId); + + CREATE INDEX signedPreKeys_ourServiceId ON signedPreKeys (ourServiceId); `); // Do overall count - if it's less than 1000, move on @@ -94,6 +94,7 @@ export default function updateToSchemaVersion91( pluck: true, }) .get(beforeParams); + // oxlint-disable-next-line typescript/restrict-template-expressions logger.info(`Found ${beforeKeys} preKeys for PNI`); // Create index to help us with all these queries @@ -102,7 +103,7 @@ export default function updateToSchemaVersion91( ALTER TABLE preKeys ADD COLUMN createdAt NUMBER GENERATED ALWAYS AS (json_extract(json, '$.createdAt')); - + CREATE INDEX preKeys_date ON preKeys (ourServiceId, createdAt); `); @@ -113,7 +114,7 @@ export default function updateToSchemaVersion91( const [oldQuery, oldParams] = sql` SELECT createdAt FROM preKeys - WHERE + WHERE createdAt IS NOT NULL AND ourServiceId = ${pni} ORDER BY createdAt ASC @@ -125,6 +126,7 @@ export default function updateToSchemaVersion91( pluck: true, }) .get(oldParams); + // oxlint-disable-next-line typescript/restrict-template-expressions logger.info(`Found 500th-oldest timestamp: ${oldBoundary}`); // Fetch 500th-newest timestamp for PNI @@ -132,7 +134,7 @@ export default function updateToSchemaVersion91( const [newQuery, newParams] = sql` SELECT createdAt FROM preKeys - WHERE + WHERE createdAt IS NOT NULL AND ourServiceId = ${pni} ORDER BY createdAt DESC @@ -144,6 +146,7 @@ export default function updateToSchemaVersion91( pluck: true, }) .get(newParams); + // oxlint-disable-next-line typescript/restrict-template-expressions logger.info(`Found 500th-newest timestamp: ${newBoundary}`); // Delete everything in between for PNI @@ -180,6 +183,7 @@ export default function updateToSchemaVersion91( pluck: true, }) .get(afterParams); + // oxlint-disable-next-line typescript/restrict-template-expressions logger.info(`Found ${afterCount} preKeys for PNI after delete`); db.exec(` diff --git a/ts/sql/migrations/920-clean-more-keys.std.ts b/ts/sql/migrations/920-clean-more-keys.std.ts index 9927368475..5111cec7e7 100644 --- a/ts/sql/migrations/920-clean-more-keys.std.ts +++ b/ts/sql/migrations/920-clean-more-keys.std.ts @@ -87,6 +87,7 @@ export function cleanKeys( pluck: true, }) .get(beforeParams); + // oxlint-disable-next-line typescript/restrict-template-expressions logger.info(`${logId}: Found ${beforeKeys} keys for PNI`); // Create index to help us with all these queries @@ -95,7 +96,7 @@ export function cleanKeys( ALTER TABLE ${tableName} ADD COLUMN createdAt NUMBER GENERATED ALWAYS AS (json_extract(json, '$.${columnName}')); - + CREATE INDEX ${tableName}_date ON ${tableName} (${idField}, createdAt); `[0] @@ -106,7 +107,7 @@ export function cleanKeys( const [oldQuery, oldParams] = sql` SELECT createdAt FROM ${tableName} - WHERE + WHERE createdAt IS NOT NULL AND ${idField} = ${pni} ORDER BY createdAt ASC @@ -118,13 +119,14 @@ export function cleanKeys( pluck: true, }) .get(oldParams); + // oxlint-disable-next-line typescript/restrict-template-expressions logger.info(`${logId}: Found 500th-oldest timestamp: ${oldBoundary}`); // Fetch 500th-newest timestamp for PNI const [newQuery, newParams] = sql` SELECT createdAt FROM ${tableName} - WHERE + WHERE createdAt IS NOT NULL AND ${idField} = ${pni} ORDER BY createdAt DESC @@ -136,6 +138,7 @@ export function cleanKeys( pluck: true, }) .get(newParams); + // oxlint-disable-next-line typescript/restrict-template-expressions logger.info(`${logId}: Found 500th-newest timestamp: ${newBoundary}`); // Delete everything in between for PNI @@ -167,6 +170,7 @@ export function cleanKeys( pluck: true, }) .get(afterParams); + // oxlint-disable-next-line typescript/restrict-template-expressions logger.info(`${logId}: Found ${afterCount} keys for PNI after delete`); db.exec( diff --git a/ts/sql/sqlLogger.node.ts b/ts/sql/sqlLogger.node.ts index 091e67a75c..3946ed0e6a 100644 --- a/ts/sql/sqlLogger.node.ts +++ b/ts/sql/sqlLogger.node.ts @@ -12,7 +12,7 @@ import { consoleLogger } from '../util/consoleLogger.std.ts'; import { strictAssert } from '../util/assert.std.ts'; class SQLLogger { - #msgPrefix: string; + readonly #msgPrefix: string; constructor(msgPrefix: string) { this.#msgPrefix = msgPrefix; @@ -47,6 +47,7 @@ class SQLLogger { const wrappedResponse: WrappedWorkerResponse = { type: 'log', level, + // oxlint-disable-next-line typescript/restrict-plus-operands args: ([this.#msgPrefix + fmt] as Array).concat(rest), }; parentPort.postMessage(wrappedResponse); diff --git a/ts/sql/util.std.ts b/ts/sql/util.std.ts index 49a8b4220d..6316899d2b 100644 --- a/ts/sql/util.std.ts +++ b/ts/sql/util.std.ts @@ -45,10 +45,16 @@ export type QueryTemplateParam = export type QueryFragmentValue = QueryFragment | QueryTemplateParam; export class QueryFragment { + public readonly fragment: string; + public readonly fragmentParams: ReadonlyArray; + constructor( - public readonly fragment: string, - public readonly fragmentParams: ReadonlyArray - ) {} + fragment: string, + fragmentParams: ReadonlyArray + ) { + this.fragment = fragment; + this.fragmentParams = fragmentParams; + } } /** @@ -105,6 +111,7 @@ export function sqlConstant(value: QueryTemplateParam): QueryFragment { } else if (typeof value === 'boolean') { fragment = `${value}`; } else { + // oxlint-disable-next-line typescript/restrict-template-expressions fragment = `'${value}'`; } return new QueryFragment(fragment, []); @@ -397,16 +404,20 @@ export function getCountFromTable(db: ReadableDB, table: TableType): number { // oxlint-disable-next-line max-classes-per-file export class TableIterator { - constructor( - private readonly db: ReadableDB, - private readonly table: TableType, - private readonly pageSize = 500 - ) {} + readonly #db: ReadableDB; + readonly #table: TableType; + readonly #pageSize: number; + + constructor(db: ReadableDB, table: TableType, pageSize = 500) { + this.#db = db; + this.#table = table; + this.#pageSize = pageSize; + } *[Symbol.iterator](): Iterator { - const fetchObject = this.db.prepare( + const fetchObject = this.#db.prepare( ` - SELECT json FROM ${this.table} + SELECT json FROM ${this.#table} WHERE id > $id ORDER BY id ASC LIMIT $pageSize; @@ -418,7 +429,7 @@ export class TableIterator { while (!complete) { const rows: JSONRows = fetchObject.all({ id, - pageSize: this.pageSize, + pageSize: this.#pageSize, }); const messages: Array = rows.map(row => @@ -430,7 +441,7 @@ export class TableIterator { if (lastMessage) { ({ id } = lastMessage); } - complete = messages.length < this.pageSize; + complete = messages.length < this.#pageSize; } } } diff --git a/ts/state/ducks/calling.preload.ts b/ts/state/ducks/calling.preload.ts index 0e3e016f11..f61d1e0490 100644 --- a/ts/state/ducks/calling.preload.ts +++ b/ts/state/ducks/calling.preload.ts @@ -2060,7 +2060,7 @@ function setLocalVideo( return; } - let enabled = payload?.enabled; + let enabled = payload.enabled; if (await requestCameraPermissions()) { if ( isGroupOrAdhocCallState(activeCall) || @@ -2068,9 +2068,9 @@ function setLocalVideo( ) { await calling.setOutgoingVideo( activeCall.conversationId, - Boolean(payload?.enabled) + payload.enabled ); - } else if (payload?.enabled) { + } else if (payload.enabled) { await calling.enableLocalCamera(activeCall.callMode); } else { calling.disableLocalVideo(); @@ -2082,7 +2082,7 @@ function setLocalVideo( dispatch({ type: SET_LOCAL_VIDEO_FULFILLED, payload: { - enabled: Boolean(enabled), + enabled, }, }); }; @@ -4354,7 +4354,7 @@ export function reducer( ...state, activeCallState: { ...state.activeCallState, - hasLocalVideo: Boolean(action.payload?.enabled), + hasLocalVideo: action.payload.enabled, }, }; } diff --git a/ts/state/ducks/conversations.preload.ts b/ts/state/ducks/conversations.preload.ts index 36206fd86c..8395eb92da 100644 --- a/ts/state/ducks/conversations.preload.ts +++ b/ts/state/ducks/conversations.preload.ts @@ -3121,6 +3121,7 @@ function createGroup( ), }, }); + // oxlint-disable-next-line typescript/await-thenable await showConversation({ conversationId: conversation.id, switchToAssociatedView: true, @@ -3507,8 +3508,7 @@ function messagesReset({ for (const message of messages) { strictAssert( message.conversationId === conversationId, - `messagesReset(${conversationId}): invalid message conversationId ` + - `${message.conversationId}` + `messagesReset(${conversationId}): invalid message conversationId ${message.conversationId}` ); } @@ -3533,8 +3533,7 @@ function addPreloadData( for (const message of messages) { strictAssert( message.conversationId === conversationId, - `addPreloadData(${conversationId}): invalid message conversationId ` + - `${message.conversationId}` + `addPreloadData(${conversationId}): invalid message conversationId ${message.conversationId}` ); } @@ -4905,6 +4904,7 @@ function showConversation({ }); // Attempt to change the location - note that this might be canceled + // oxlint-disable-next-line typescript/await-thenable await changeLocation({ tab: NavTab.Chats, details: { @@ -5090,6 +5090,7 @@ function onConversationClosed( // If we're still on this conversation, but we want to close it, go to splash screen if (selectedConversationId === conversationId) { + // oxlint-disable-next-line typescript/await-thenable await changeLocation({ tab: NavTab.Chats, details: { diff --git a/ts/state/ducks/donations.preload.ts b/ts/state/ducks/donations.preload.ts index e00e758330..67b78fd78b 100644 --- a/ts/state/ducks/donations.preload.ts +++ b/ts/state/ducks/donations.preload.ts @@ -375,7 +375,7 @@ export function applyDonationBadge({ badges: updatedBadges, }; - await dispatch( + dispatch( conversationActions.myProfileChanged(profileData, { keepAvatar: true }) ); newDisplayBadgesOnProfile = true; @@ -391,7 +391,7 @@ export function applyDonationBadge({ badges: [], }; - await dispatch( + dispatch( conversationActions.myProfileChanged(profileData, { keepAvatar: true }) ); newDisplayBadgesOnProfile = false; diff --git a/ts/state/ducks/nav.std.ts b/ts/state/ducks/nav.std.ts index ccc7a92b91..e113113f86 100644 --- a/ts/state/ducks/nav.std.ts +++ b/ts/state/ducks/nav.std.ts @@ -33,7 +33,7 @@ function printLocation(location: Location): string { return `${location.tab}/${location.details.page}`; } - return `${location.tab}`; + return location.tab; } function getDefaultPanels(): PanelInfo { diff --git a/ts/state/ducks/notificationProfiles.preload.ts b/ts/state/ducks/notificationProfiles.preload.ts index 7b9531b777..14f1563aba 100644 --- a/ts/state/ducks/notificationProfiles.preload.ts +++ b/ts/state/ducks/notificationProfiles.preload.ts @@ -237,9 +237,7 @@ function setIsSyncEnabled( await itemStorage.put('notificationProfileSyncDisabled', disabled); if (disabled) { if (!fromStorageService) { - const globalOverride = await itemStorage.get( - 'notificationProfileOverride' - ); + const globalOverride = itemStorage.get('notificationProfileOverride'); await itemStorage.put( 'notificationProfileOverrideFromPrimary', @@ -394,6 +392,7 @@ function updateOverride( const enabled = payload?.enabled; await itemStorage.put('notificationProfileOverride', payload); + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions const logId = `updateOverride/${id ? redactNotificationProfileId(id) : 'undefined'}/enabled=${enabled}`; dispatch({ diff --git a/ts/state/ducks/search.preload.ts b/ts/state/ducks/search.preload.ts index 9acf227856..e608af754a 100644 --- a/ts/state/ducks/search.preload.ts +++ b/ts/state/ducks/search.preload.ts @@ -540,14 +540,14 @@ async function queryMessages({ } if (searchConversationId) { - return dataSearchMessages({ + return await dataSearchMessages({ query, conversationId: searchConversationId, contactServiceIdsMatchingQuery, }); } - return dataSearchMessages({ + return await dataSearchMessages({ query, contactServiceIdsMatchingQuery, }); @@ -636,7 +636,7 @@ async function queryConversationsAndContacts( // inject synthetic Note to Self only in the contacts list. // If we're filtering by unread, no contacts are shown anyway, so we show it in the // normal flow of the conversations list. - if (!filterByUnread && noteToSelf.indexOf(query.toLowerCase()) !== -1) { + if (!filterByUnread && noteToSelf.includes(query.toLowerCase())) { // ensure that we don't have duplicates in our results contactIds = contactIds.filter(id => id !== ourConversationId); conversationIds = conversationIds.filter(id => id !== ourConversationId); diff --git a/ts/state/ducks/storyDistributionLists.preload.ts b/ts/state/ducks/storyDistributionLists.preload.ts index 6c63f8eb37..56b0eff1a7 100644 --- a/ts/state/ducks/storyDistributionLists.preload.ts +++ b/ts/state/ducks/storyDistributionLists.preload.ts @@ -190,10 +190,10 @@ function createDistributionList( dispatch({ type: CREATE_LIST, payload: { - allowsReplies: Boolean(storyDistribution.allowsReplies), + allowsReplies: storyDistribution.allowsReplies, deletedAtTimestamp: storyDistribution.deletedAtTimestamp, id: storyDistribution.id, - isBlockList: Boolean(storyDistribution.isBlockList), + isBlockList: storyDistribution.isBlockList, memberServiceIds, name: storyDistribution.name, }, diff --git a/ts/state/ducks/username.preload.ts b/ts/state/ducks/username.preload.ts index 67866bab93..ba2b935a2a 100644 --- a/ts/state/ducks/username.preload.ts +++ b/ts/state/ducks/username.preload.ts @@ -100,10 +100,10 @@ type ConfirmUsernameActionType = ReadonlyDeep< PromiseAction >; type DeleteUsernameActionType = ReadonlyDeep< - PromiseAction + PromiseAction >; type ResetUsernameLinkActionType = ReadonlyDeep< - PromiseAction + PromiseAction >; export type UsernameActionType = ReadonlyDeep< diff --git a/ts/state/selectors/conversations.dom.ts b/ts/state/selectors/conversations.dom.ts index d703eec816..d1cb2dd5c4 100644 --- a/ts/state/selectors/conversations.dom.ts +++ b/ts/state/selectors/conversations.dom.ts @@ -241,7 +241,7 @@ export const getLastSelectedMessage = createSelector( export const getShowArchived = createSelector( getConversations, (state: ConversationsStateType): boolean => { - return Boolean(state.showArchived); + return state.showArchived; } ); @@ -679,7 +679,7 @@ function isTrusted( conversation.serviceId != null && serviceIdsInGroups.has(conversation.serviceId); - return Boolean( + return ( isInSystemContacts(conversation) || hasSharedGroups || conversation.profileSharing || @@ -704,7 +704,7 @@ function canComposeConversation( conversation: ConversationType, serviceIdsInGroups: Set ): boolean { - return Boolean( + return ( !isSignalConversation(conversation) && !conversation.isBlocked && !conversation.removalStage && @@ -1150,7 +1150,7 @@ export const getCachedConversationMemberColorsSelector = createSelector( ) => { return memoizee( (conversationId: string | undefined) => { - const contactNameColors: Map = new Map(); + const contactNameColors = new Map(); const { membersV2 = [], type, diff --git a/ts/state/selectors/items.dom.ts b/ts/state/selectors/items.dom.ts index afb0c67306..49ae432519 100644 --- a/ts/state/selectors/items.dom.ts +++ b/ts/state/selectors/items.dom.ts @@ -43,7 +43,8 @@ export const getProfileMovedModalNeeded = createSelector( export const getUserAgent = createSelector( getItems, - (state: ItemsStateType): string => state.userAgent as string + // oxlint-disable-next-line typescript/no-non-null-assertion + (state: ItemsStateType): string => state.userAgent! ); export const getPinnedConversationIds = createSelector( @@ -214,9 +215,7 @@ export const getHasReadReceiptSetting = createSelector( export const getHasStoryViewReceiptSetting = createSelector( getItems, (state: ItemsStateType): boolean => - Boolean( - state.storyViewReceiptsEnabled ?? state['read-receipt-setting'] ?? false - ) + state.storyViewReceiptsEnabled ?? state['read-receipt-setting'] ?? false ); export const getNotificationProfileSyncDisabled = createSelector( @@ -227,10 +226,7 @@ export const getNotificationProfileSyncDisabled = createSelector( export const getRemoteBuildExpiration = createSelector( getItems, - (state: ItemsStateType): number | undefined => - state.remoteBuildExpiration === undefined - ? undefined - : Number(state.remoteBuildExpiration) + (state: ItemsStateType): number | undefined => state.remoteBuildExpiration ); export const getAutoDownloadUpdate = createSelector( @@ -240,7 +236,7 @@ export const getAutoDownloadUpdate = createSelector( return false; } - return Boolean(state['auto-download-update'] ?? true); + return state['auto-download-update'] ?? true; } ); @@ -253,12 +249,12 @@ export const getBadgeCountMutedConversations = createSelector( export const getTextFormattingEnabled = createSelector( getItems, - (state: ItemsStateType): boolean => Boolean(state.textFormatting ?? true) + (state: ItemsStateType): boolean => state.textFormatting ?? true ); export const getNavTabsCollapsed = createSelector( getItems, - (state: ItemsStateType): boolean => Boolean(state.navTabsCollapsed ?? false) + (state: ItemsStateType): boolean => state.navTabsCollapsed ?? false ); export const getShowStickersIntroduction = createSelector( diff --git a/ts/state/selectors/message.preload.ts b/ts/state/selectors/message.preload.ts index 145a407206..cb24f89548 100644 --- a/ts/state/selectors/message.preload.ts +++ b/ts/state/selectors/message.preload.ts @@ -2595,9 +2595,7 @@ export function canDownload( conversationSelector: GetConversationByIdType ): boolean { const conversation = getConversation(message, conversationSelector); - const isAccepted = Boolean( - conversation && conversation.acceptedMessageRequest - ); + const isAccepted = conversation.acceptedMessageRequest; if (isIncoming(message) && !isAccepted) { return false; } @@ -2911,7 +2909,7 @@ export const getMessageDetailsSelector = createSelector( selectedMessageIds, defaultConversationColor, }), - receivedAt: Number(message.received_at_ms || message.received_at), + receivedAt: message.received_at_ms ?? message.received_at ?? 0, }; } ); diff --git a/ts/state/selectors/search.preload.ts b/ts/state/selectors/search.preload.ts index f3d04e71d8..1884ec31c3 100644 --- a/ts/state/selectors/search.preload.ts +++ b/ts/state/selectors/search.preload.ts @@ -171,10 +171,7 @@ export const getSearchResults = createSelector( filterByUnread: state.filterByUnread, }; - if ( - state.filterByUnread && - searchResults.conversationResults.isLoading === false - ) { + if (state.filterByUnread && !searchResults.conversationResults.isLoading) { searchResults.conversationResults.results = searchResults.conversationResults.results.map(conversation => { return { diff --git a/ts/state/selectors/updates.std.ts b/ts/state/selectors/updates.std.ts index 10c2416c81..b41720e674 100644 --- a/ts/state/selectors/updates.std.ts +++ b/ts/state/selectors/updates.std.ts @@ -65,5 +65,5 @@ export const isOSUnsupported = createSelector( export const getHasPendingUpdate = createSelector( getUpdatesState, - ({ didSnooze }) => didSnooze === true + ({ didSnooze }) => didSnooze ); diff --git a/ts/state/selectors/user.std.ts b/ts/state/selectors/user.std.ts index 2d3228e829..8675a15e6d 100644 --- a/ts/state/selectors/user.std.ts +++ b/ts/state/selectors/user.std.ts @@ -90,9 +90,7 @@ export const getPreferredTheme = createSelector( const getIsInFullScreenCall = createSelector( (state: StateType): CallingStateType => state.calling, (state: CallingStateType): boolean => - Boolean( - state.activeCallState?.state === 'Active' && !state.activeCallState.pip - ) + state.activeCallState?.state === 'Active' && !state.activeCallState.pip ); export const getTheme = createSelector( diff --git a/ts/state/smart/CallManager.preload.tsx b/ts/state/smart/CallManager.preload.tsx index be0c77bb26..4317929a5f 100644 --- a/ts/state/smart/CallManager.preload.tsx +++ b/ts/state/smart/CallManager.preload.tsx @@ -179,7 +179,7 @@ const mapStateToActiveCallProp = ( const pendingParticipants: Array = []; const conversationsByDemuxId: ConversationsByDemuxIdType = new Map(); const { localDemuxId } = call; - const raisedHands: Set = new Set(call.raisedHands ?? []); + const raisedHands = new Set(call.raisedHands ?? []); const { memberships = [] } = conversation; diff --git a/ts/test-electron/ContactsParser_test.preload.ts b/ts/test-electron/ContactsParser_test.preload.ts index 69b3053281..c350abf40e 100644 --- a/ts/test-electron/ContactsParser_test.preload.ts +++ b/ts/test-electron/ContactsParser_test.preload.ts @@ -142,8 +142,11 @@ describe('ContactsParser', () => { }); class SmallChunksTransform extends Transform { - constructor(private chunkSize: number) { + readonly #chunkSize: number; + + constructor(chunkSize: number) { super(); + this.#chunkSize = chunkSize; } override _transform( @@ -159,16 +162,16 @@ class SmallChunksTransform extends Transform { try { const totalSize = incomingChunk.byteLength; - const chunkCount = Math.floor(totalSize / this.chunkSize); - const remainder = totalSize % this.chunkSize; + const chunkCount = Math.floor(totalSize / this.#chunkSize); + const remainder = totalSize % this.#chunkSize; for (let i = 0; i < chunkCount; i += 1) { - const start = i * this.chunkSize; - const end = start + this.chunkSize; + const start = i * this.#chunkSize; + const end = start + this.#chunkSize; this.push(incomingChunk.subarray(start, end)); } if (remainder > 0) { - this.push(incomingChunk.subarray(chunkCount * this.chunkSize)); + this.push(incomingChunk.subarray(chunkCount * this.#chunkSize)); } } catch (error) { done(error); diff --git a/ts/test-electron/SignalProtocolStore_test.preload.ts b/ts/test-electron/SignalProtocolStore_test.preload.ts index f68236e212..2386e13f75 100644 --- a/ts/test-electron/SignalProtocolStore_test.preload.ts +++ b/ts/test-electron/SignalProtocolStore_test.preload.ts @@ -800,7 +800,7 @@ describe('SignalProtocolStore', () => { }); await store.hydrateCaches(); - const untrusted = await store.isUntrusted(theirAci); + const untrusted = store.isUntrusted(theirAci); assert.strictEqual(untrusted, false); }); @@ -815,7 +815,7 @@ describe('SignalProtocolStore', () => { }); await store.hydrateCaches(); - const untrusted = await store.isUntrusted(theirAci); + const untrusted = store.isUntrusted(theirAci); assert.strictEqual(untrusted, false); }); @@ -830,7 +830,7 @@ describe('SignalProtocolStore', () => { }); await store.hydrateCaches(); - const untrusted = await store.isUntrusted(theirAci); + const untrusted = store.isUntrusted(theirAci); assert.strictEqual(untrusted, false); }); @@ -845,7 +845,7 @@ describe('SignalProtocolStore', () => { }); await store.hydrateCaches(); - const untrusted = await store.isUntrusted(theirAci); + const untrusted = store.isUntrusted(theirAci); assert.strictEqual(untrusted, true); }); }); diff --git a/ts/test-electron/backup/integration_test.preload.ts b/ts/test-electron/backup/integration_test.preload.ts index 72613d846b..5f711ea4a2 100644 --- a/ts/test-electron/backup/integration_test.preload.ts +++ b/ts/test-electron/backup/integration_test.preload.ts @@ -22,7 +22,7 @@ const { BACKUP_INTEGRATION_DIR } = process.env; describe('backup/integration', () => { before(async () => { - await initializeExpiringMessageService(); + initializeExpiringMessageService(); }); beforeEach(async () => { diff --git a/ts/test-electron/context/createNativeThemeListener_test.node.ts b/ts/test-electron/context/createNativeThemeListener_test.node.ts index 179a646d39..649f04a882 100644 --- a/ts/test-electron/context/createNativeThemeListener_test.node.ts +++ b/ts/test-electron/context/createNativeThemeListener_test.node.ts @@ -13,13 +13,16 @@ import type { NativeThemeState } from '../../types/NativeThemeNotifier.d.ts'; import { SystemThemeType } from '../../types/Util.std.ts'; class FakeIPC extends EventEmitter implements MinimalIPC { - constructor(private readonly state: NativeThemeState) { + readonly #state: NativeThemeState; + + constructor(state: NativeThemeState) { super(); + this.#state = state; } public sendSync(channel: string) { assert.strictEqual(channel, 'native-theme:init'); - return this.state; + return this.#state; } public send() { diff --git a/ts/test-electron/deleteMessageAttachments_test.preload.ts b/ts/test-electron/deleteMessageAttachments_test.preload.ts index 30d1853207..f8494ff764 100644 --- a/ts/test-electron/deleteMessageAttachments_test.preload.ts +++ b/ts/test-electron/deleteMessageAttachments_test.preload.ts @@ -82,7 +82,7 @@ describe('deleteMessageAttachments', () => { attachmentIndex = 0; downloadIndex = 0; await DataWriter.removeAll(); - await window.ConversationController.reset(); + window.ConversationController.reset(); await window.ConversationController.load(); await emptyDir( getAttachmentsPath(window.SignalContext.config.userDataPath) @@ -92,7 +92,7 @@ describe('deleteMessageAttachments', () => { afterEach(async () => { await DataWriter.removeAll(); - await window.ConversationController.reset(); + window.ConversationController.reset(); await emptyDir( getAttachmentsPath(window.SignalContext.config.userDataPath) ); diff --git a/ts/test-electron/quill/emoji/completion_test.dom.tsx b/ts/test-electron/quill/emoji/completion_test.dom.tsx index 66288f262d..cae0c581a4 100644 --- a/ts/test-electron/quill/emoji/completion_test.dom.tsx +++ b/ts/test-electron/quill/emoji/completion_test.dom.tsx @@ -94,8 +94,7 @@ describe('emojiCompletion', () => { emojiLocalizer, }; - // oxlint-disable-next-line typescript/no-explicit-any - emojiCompletion = new EmojiCompletion(mockQuill as any, options); + emojiCompletion = new EmojiCompletion(mockQuill, options); // Stub rendering to avoid missing DOM until we bring in Enzyme emojiCompletion.render = sinon.stub(); diff --git a/ts/test-electron/quill/signal-clipboard_test.dom.ts b/ts/test-electron/quill/signal-clipboard_test.dom.ts index 962b744691..b7603ae1a6 100644 --- a/ts/test-electron/quill/signal-clipboard_test.dom.ts +++ b/ts/test-electron/quill/signal-clipboard_test.dom.ts @@ -88,7 +88,7 @@ function createMockClipboardEvent( function createMockQuillWithContent( content: string, - hasStrike: boolean = false + hasStrike = false ): MockQuill { const mockQuill = new MockQuill(); diff --git a/ts/test-electron/services/AttachmentBackupManager_test.preload.ts b/ts/test-electron/services/AttachmentBackupManager_test.preload.ts index fcee98c2ab..64d7b5335f 100644 --- a/ts/test-electron/services/AttachmentBackupManager_test.preload.ts +++ b/ts/test-electron/services/AttachmentBackupManager_test.preload.ts @@ -214,14 +214,14 @@ describe('AttachmentBackupManager/JobManager', function attachmentBackupManager( function waitForJobToBeStarted( job: CoreAttachmentBackupJobType, - attempts: number = 0 + attempts = 0 ) { return backupManager?.waitForJobToBeStarted({ ...job, attempts }); } function waitForJobToBeCompleted( job: CoreAttachmentBackupJobType, - attempts: number = 0 + attempts = 0 ) { return backupManager?.waitForJobToBeCompleted({ ...job, attempts }); } diff --git a/ts/test-electron/services/AttachmentDownloadManager_test.preload.ts b/ts/test-electron/services/AttachmentDownloadManager_test.preload.ts index df8adbed4f..6fef3aabc9 100644 --- a/ts/test-electron/services/AttachmentDownloadManager_test.preload.ts +++ b/ts/test-electron/services/AttachmentDownloadManager_test.preload.ts @@ -746,9 +746,8 @@ describe('AttachmentDownloadManager', () => { // oxlint-disable-next-line typescript/no-floating-promises downloadManager?.cancelJobs(JobCancelReason.UserInitiated, () => true); - await assert.isRejected( - assertAt(jobAttempts, 0).completed as Promise - ); + // oxlint-disable-next-line typescript/no-non-null-assertion + await assert.isRejected(assertAt(jobAttempts, 0).completed!); await downloadManagerIdled; // Ensure it will not be retried diff --git a/ts/test-electron/services/ReleaseNoteAndMegaphoneFetcher_test.preload.ts b/ts/test-electron/services/ReleaseNoteAndMegaphoneFetcher_test.preload.ts index 11cc495dca..1e49c46301 100644 --- a/ts/test-electron/services/ReleaseNoteAndMegaphoneFetcher_test.preload.ts +++ b/ts/test-electron/services/ReleaseNoteAndMegaphoneFetcher_test.preload.ts @@ -343,7 +343,8 @@ describe('ReleaseNoteAndMegaphoneFetcher', () => { sandbox.reset(); // Restore original global values (even if they were undefined) - window.SignalCI = originalSignalCI as CIType; + // oxlint-disable-next-line typescript/no-non-null-assertion + window.SignalCI = originalSignalCI!; // Reset storage state await itemStorage.fetch(); diff --git a/ts/test-electron/sql/pollVoteMarkRead_test.preload.ts b/ts/test-electron/sql/pollVoteMarkRead_test.preload.ts index f50d7958dd..2591b7157f 100644 --- a/ts/test-electron/sql/pollVoteMarkRead_test.preload.ts +++ b/ts/test-electron/sql/pollVoteMarkRead_test.preload.ts @@ -580,6 +580,7 @@ describe('sql/pollVoteMarkRead', () => { ); assert.ok( result?.hasUnreadPollVotes == null || + // oxlint-disable-next-line typescript/no-unnecessary-boolean-literal-compare result?.hasUnreadPollVotes === false, 'hasUnreadPollVotes should be false or null/undefined after marking as read' ); diff --git a/ts/test-electron/state/ducks/conversations_test.preload.ts b/ts/test-electron/state/ducks/conversations_test.preload.ts index aceb172864..aea743005d 100644 --- a/ts/test-electron/state/ducks/conversations_test.preload.ts +++ b/ts/test-electron/state/ducks/conversations_test.preload.ts @@ -567,6 +567,7 @@ describe('both/state/ducks/conversations', () => { assert( result.composer?.step === ComposerStep.SetGroupMetadata && + // oxlint-disable-next-line typescript/no-unnecessary-boolean-literal-compare result.composer.hasError === false ); }); diff --git a/ts/test-electron/updateConversationsWithUuidLookup_test.preload.ts b/ts/test-electron/updateConversationsWithUuidLookup_test.preload.ts index 28f506f100..5d9956d97d 100644 --- a/ts/test-electron/updateConversationsWithUuidLookup_test.preload.ts +++ b/ts/test-electron/updateConversationsWithUuidLookup_test.preload.ts @@ -16,12 +16,14 @@ import { describe('updateConversationsWithUuidLookup', () => { class FakeConversationController { - constructor( - private readonly conversations: Array = [] - ) {} + readonly #conversations: Array; + + constructor(conversations: Array = []) { + this.#conversations = conversations; + } get(id?: string | null): ConversationModel | undefined { - return this.conversations.find( + return this.#conversations.find( conversation => conversation.id === id || conversation.get('e164') === id || @@ -53,8 +55,7 @@ describe('updateConversationsWithUuidLookup', () => { reason, 'FakeConversationController must be provided a reason when merging' ); - // oxlint-disable-next-line typescript/no-non-null-assertion - const normalizedAci = normalizeAci(aciFromServer!, 'test'); + const normalizedAci = normalizeAci(aciFromServer, 'test'); const convoE164 = this.get(e164); const convoUuid = this.get(normalizedAci); @@ -97,8 +98,7 @@ describe('updateConversationsWithUuidLookup', () => { 'FakeConversationController is not set up for this case (UUID must be provided)' ); const normalizedServiceId = normalizeServiceId( - // oxlint-disable-next-line typescript/no-non-null-assertion - serviceIdFromServer!, + serviceIdFromServer, 'test' ); diff --git a/ts/test-electron/util/encryptProfileData_test.preload.ts b/ts/test-electron/util/encryptProfileData_test.preload.ts index 6bca1a79cf..780d7c5480 100644 --- a/ts/test-electron/util/encryptProfileData_test.preload.ts +++ b/ts/test-electron/util/encryptProfileData_test.preload.ts @@ -79,7 +79,7 @@ describe('encryptProfileData', () => { } if (encrypted.aboutEmoji) { - const decryptedAboutEmojiBytes = await decryptProfile( + const decryptedAboutEmojiBytes = decryptProfile( Bytes.fromBase64(encrypted.aboutEmoji), keyBuffer ); diff --git a/ts/test-electron/windows/attachments_test.preload.ts b/ts/test-electron/windows/attachments_test.preload.ts index f0d636c868..5e36d2a6b2 100644 --- a/ts/test-electron/windows/attachments_test.preload.ts +++ b/ts/test-electron/windows/attachments_test.preload.ts @@ -71,16 +71,16 @@ describe('Attachments', () => { it('returns a function that rejects if the source path is not a string', async () => { const copier = Attachments.copyIntoAttachmentsDirectory({ - sourceDir: await getFakeAttachmentsDirectory(), - targetDir: await getFakeAttachmentsDirectory(), + sourceDir: getFakeAttachmentsDirectory(), + targetDir: getFakeAttachmentsDirectory(), }); await assert.isRejected(copier(123 as unknown as string)); }); it('returns a function that rejects if the source path is not in the user config directory', async () => { const copier = Attachments.copyIntoAttachmentsDirectory({ - sourceDir: await getFakeAttachmentsDirectory(), - targetDir: await getFakeAttachmentsDirectory(), + sourceDir: getFakeAttachmentsDirectory(), + targetDir: getFakeAttachmentsDirectory(), }); await assert.isRejected( copier(path.join(tempRootDirectory, 'hello.txt')), @@ -89,7 +89,7 @@ describe('Attachments', () => { }); it('returns a function that copies the source path into the attachments directory and returns its path and size', async () => { - const attachmentsPath = await getFakeAttachmentsDirectory(); + const attachmentsPath = getFakeAttachmentsDirectory(); const someOtherPath = path.join(USER_DATA, 'somethingElse'); await fse.outputFile(someOtherPath, 'hello world'); filesToRemove.push(someOtherPath); diff --git a/ts/test-helpers/generateBackup.node.ts b/ts/test-helpers/generateBackup.node.ts index 45807ab6c2..ed6cc8075c 100644 --- a/ts/test-helpers/generateBackup.node.ts +++ b/ts/test-helpers/generateBackup.node.ts @@ -100,7 +100,7 @@ function* createRecords({ }: BackupGeneratorConfigType): Iterable> { yield* encodeDelimited( Backups.BackupInfo.encode({ - version: BigInt(BACKUP_VERSION), + version: BACKUP_VERSION, backupTimeMs: getTimestamp(), mediaRootBackupKey, currentAppVersion: null, diff --git a/ts/test-mock/backups/backups_test.node.ts b/ts/test-mock/backups/backups_test.node.ts index e45e2c8236..3cd47a0063 100644 --- a/ts/test-mock/backups/backups_test.node.ts +++ b/ts/test-mock/backups/backups_test.node.ts @@ -69,7 +69,7 @@ describe('backups', function (this: Mocha.Suite) { async function generateTestDataThenRestoreBackup( thisVal: Mocha.Context, - exportBackupFn: () => void, + exportBackupFn: () => Promise, getBootstrapLinkParams: () => LinkOptionsType ) { let state = StorageState.getEmpty(); @@ -324,7 +324,7 @@ describe('backups', function (this: Mocha.Suite) { await snapshot('styled bubbles'); debug('Waiting for unread count'); - const unreadCount = await leftPane + const unreadCount = leftPane .locator( '.module-conversation-list__item--contact-or-conversation__unread-indicator.module-conversation-list__item--contact-or-conversation__unread-indicator--unread-messages' ) diff --git a/ts/test-mock/calling/callLinkAdmin_test.node.ts b/ts/test-mock/calling/callLinkAdmin_test.node.ts index 99241ce312..b5e2bdb8f4 100644 --- a/ts/test-mock/calling/callLinkAdmin_test.node.ts +++ b/ts/test-mock/calling/callLinkAdmin_test.node.ts @@ -35,9 +35,7 @@ describe('calling/callLinkAdmin', function (this: Mocha.Suite) { const name = 'New Name'; await createCallLink(window, { name }); - const title = await window - .locator('.CallsList__ItemTile') - .getByText(name); + const title = window.locator('.CallsList__ItemTile').getByText(name); await expect(title).toContainText(name); } @@ -49,7 +47,7 @@ describe('calling/callLinkAdmin', function (this: Mocha.Suite) { isAdminApprovalRequired: false, }); - const callLinkItem = await window.getByText(name); + const callLinkItem = window.getByText(name); await callLinkItem.click(); const callLinkDetails = window.locator( @@ -57,7 +55,7 @@ describe('calling/callLinkAdmin', function (this: Mocha.Suite) { ); await callLinkDetails.waitFor(); - const restrictionsSelect = await window.locator( + const restrictionsSelect = window.locator( '.CallLinkRestrictionsSelect select' ); await expect(restrictionsSelect).toHaveJSProperty('value', '0'); @@ -70,7 +68,7 @@ describe('calling/callLinkAdmin', function (this: Mocha.Suite) { isAdminApprovalRequired: true, }); - const callLinkItem = await window.getByText(name); + const callLinkItem = window.getByText(name); await callLinkItem.click(); const callLinkDetails = window.locator( @@ -78,7 +76,7 @@ describe('calling/callLinkAdmin', function (this: Mocha.Suite) { ); await callLinkDetails.waitFor(); - const restrictionsSelect = await window.locator( + const restrictionsSelect = window.locator( '.CallLinkRestrictionsSelect select' ); await expect(restrictionsSelect).toHaveJSProperty('value', '1'); diff --git a/ts/test-mock/calling/callMessages_test.docker.node.ts b/ts/test-mock/calling/callMessages_test.docker.node.ts index a5c8132d6c..ee21348c4a 100644 --- a/ts/test-mock/calling/callMessages_test.docker.node.ts +++ b/ts/test-mock/calling/callMessages_test.docker.node.ts @@ -81,6 +81,7 @@ describe('callMessages', function callMessages(this: Mocha.Suite) { ], (error, stdout, stderr) => { if (error) { + // oxlint-disable-next-line typescript/only-throw-error throw error; } debug(stdout); diff --git a/ts/test-mock/helpers.node.ts b/ts/test-mock/helpers.node.ts index 3169464893..e680420e02 100644 --- a/ts/test-mock/helpers.node.ts +++ b/ts/test-mock/helpers.node.ts @@ -252,7 +252,7 @@ export function sendReaction({ to, targetAuthor, targetMessageTimestamp, - emoji = 'πŸ‘', + emoji, reactionTimestamp = Date.now(), desktop, }: { @@ -491,7 +491,7 @@ export async function createCallLink( page: Page, { name, - isAdminApprovalRequired = undefined, + isAdminApprovalRequired, }: { name: string; isAdminApprovalRequired?: boolean | undefined } ): Promise { await page.locator('[data-testid="NavTabsItem--Calls"]').click(); @@ -532,11 +532,9 @@ export async function createCallLink( const doneBtn = editModal.getByText('Done'); await doneBtn.click(); - const callLinkTitle = await page - .locator('.CallsList__ItemTile') - .getByText(name); + const callLinkTitle = page.locator('.CallsList__ItemTile').getByText(name); - const callLinkItem = await page.locator('.CallsList__Item', { + const callLinkItem = page.locator('.CallsList__Item', { has: callLinkTitle, }); const testId = await callLinkItem.getAttribute('data-testid'); diff --git a/ts/test-mock/messaging/edit_test.node.ts b/ts/test-mock/messaging/edit_test.node.ts index 97e0906393..a55bee4c52 100644 --- a/ts/test-mock/messaging/edit_test.node.ts +++ b/ts/test-mock/messaging/edit_test.node.ts @@ -422,7 +422,7 @@ describe('editing', function (this: Mocha.Suite) { .locator('.module-message__metadata__edited') .click(); - const history = await window.locator( + const history = window.locator( '.EditHistoryMessagesModal .module-message' ); assert.strictEqual(await history.count(), 3); diff --git a/ts/test-mock/messaging/reaction_test.node.ts b/ts/test-mock/messaging/reaction_test.node.ts index 92e77ced24..9bf0fcc70e 100644 --- a/ts/test-mock/messaging/reaction_test.node.ts +++ b/ts/test-mock/messaging/reaction_test.node.ts @@ -26,7 +26,7 @@ async function getReactionsForMessage(page: Page, timestamp: number) { const reactionsByEmoji: Record> = {}; try { - const message = await getMessageInTimelineByTimestamp(page, timestamp); + const message = getMessageInTimelineByTimestamp(page, timestamp); await message.locator('.module-message__reactions').click(); @@ -417,7 +417,7 @@ describe('reactions', function (this: Mocha.Suite) { await leftPane.getByText('ThumbsToneGroup').click(); // Click the reaction button on that message - const msg = await getMessageInTimelineByTimestamp(window, ts); + const msg = getMessageInTimelineByTimestamp(window, ts); await msg.locator('.module-message__reactions').click(); // Grab the header emoji in the overlay (next to the total count) diff --git a/ts/test-mock/messaging/retries_test.node.ts b/ts/test-mock/messaging/retries_test.node.ts index 00bc7bebd7..57e1ab80ae 100644 --- a/ts/test-mock/messaging/retries_test.node.ts +++ b/ts/test-mock/messaging/retries_test.node.ts @@ -127,7 +127,7 @@ describe('retries', function (this: Mocha.Suite) { .waitFor(); debug('verify that no resend request was sent'); - const count = await first.getDecryptionErrorQueueSize(); + const count = first.getDecryptionErrorQueueSize(); assert.equal(count, 0); }); @@ -176,7 +176,7 @@ describe('retries', function (this: Mocha.Suite) { await sleep(500); debug('verify that no other resend requests were sent'); - const count = await first.getDecryptionErrorQueueSize(); + const count = first.getDecryptionErrorQueueSize(); assert.equal(count, 0); }); }); diff --git a/ts/test-mock/network/serverAlerts_test.node.ts b/ts/test-mock/network/serverAlerts_test.node.ts index efcba6dcf3..b063969e12 100644 --- a/ts/test-mock/network/serverAlerts_test.node.ts +++ b/ts/test-mock/network/serverAlerts_test.node.ts @@ -85,7 +85,7 @@ describe('serverAlerts', function (this: Mocha.Suite) { for (const testCase of TEST_CASES) { // oxlint-disable-next-line no-loop-func - it(`${testCase.name}`, async () => { + it(testCase.name, async () => { bootstrap.server.setWebsocketUpgradeResponseHeaders(testCase.headers); app = await bootstrap.link(); const window = await app.getWindow(); diff --git a/ts/test-mock/playwright.node.ts b/ts/test-mock/playwright.node.ts index 67fcfa4221..b356d98ac9 100644 --- a/ts/test-mock/playwright.node.ts +++ b/ts/test-mock/playwright.node.ts @@ -52,22 +52,24 @@ export type AppOptionsType = Readonly<{ const WAIT_FOR_EVENT_TIMEOUT = 30 * SECOND; export class App extends EventEmitter { + readonly #options: AppOptionsType; #privApp: ElectronApplication | undefined; - constructor(private readonly options: AppOptionsType) { + constructor(options: AppOptionsType) { super(); + this.#options = options; } public async start(): Promise { try { // launch the electron processs this.#privApp = await electron.launch({ - executablePath: this.options.main, - args: this.options.args.slice(), + executablePath: this.#options.main, + args: this.#options.args.slice(), env: { ...process.env, MOCK_TEST: 'true', - SIGNAL_CI_CONFIG: this.options.config, + SIGNAL_CI_CONFIG: this.#options.config, }, locale: 'en', timeout: 30 * SECOND, diff --git a/ts/test-mock/pnp/merge_test.node.ts b/ts/test-mock/pnp/merge_test.node.ts index 37c1991bdc..70cbc53273 100644 --- a/ts/test-mock/pnp/merge_test.node.ts +++ b/ts/test-mock/pnp/merge_test.node.ts @@ -235,9 +235,9 @@ describe('pnp/merge', function (this: Mocha.Suite) { } for (const withPniContact of [false, true]) { - const testName = - 'accepts storage service contact splitting ' + - `${withPniContact ? 'with PNI contact' : 'without PNI contact'}`; + const testName = `accepts storage service contact splitting ${ + withPniContact ? 'with PNI contact' : 'without PNI contact' + }`; // oxlint-disable-next-line no-loop-func it(testName, async () => { @@ -541,7 +541,7 @@ describe('pnp/merge', function (this: Mocha.Suite) { await typeIntoInput(searchBox, aciContact.device.number, ''); - const firstSearchResult = await window.locator( + const firstSearchResult = window.locator( '.module-left-pane__no-search-results' ); const firstSearchResultText = await firstSearchResult.innerText(); diff --git a/ts/test-mock/pnp/username_test.node.ts b/ts/test-mock/pnp/username_test.node.ts index a43b814221..3683219635 100644 --- a/ts/test-mock/pnp/username_test.node.ts +++ b/ts/test-mock/pnp/username_test.node.ts @@ -180,7 +180,7 @@ describe('pnp/username', function (this: Mocha.Suite) { 'notification count' ); - const first = await notifications.first(); + const first = notifications.first(); assert.strictEqual( await first.innerText(), `You started this chat with ${USERNAME}` diff --git a/ts/test-mock/release-notes/release_notes_test.node.ts b/ts/test-mock/release-notes/release_notes_test.node.ts index 640813d383..442de2aece 100644 --- a/ts/test-mock/release-notes/release_notes_test.node.ts +++ b/ts/test-mock/release-notes/release_notes_test.node.ts @@ -64,7 +64,7 @@ describe('release notes', function (this: Mocha.Suite) { await clickOnConversationWithAci(secondWindow, SIGNAL_ACI); - const timelineMessage = await getTimelineMessageWithText( + const timelineMessage = getTimelineMessageWithText( secondWindow, 'Call links' ); @@ -117,7 +117,7 @@ describe('release notes', function (this: Mocha.Suite) { 'expected message to have monospace text' ); - const secondTimelineMessage = await getTimelineMessageWithText( + const secondTimelineMessage = getTimelineMessageWithText( secondWindow, 'Bold text has invalid ranges, italic has valid' ); diff --git a/ts/test-mock/storage/archive_test.node.ts b/ts/test-mock/storage/archive_test.node.ts index f68a85ab32..48f8b484f3 100644 --- a/ts/test-mock/storage/archive_test.node.ts +++ b/ts/test-mock/storage/archive_test.node.ts @@ -106,8 +106,8 @@ describe('storage service', function (this: Mocha.Suite) { const newState = await phone.waitForStorageState({ after: state, }); - assert.ok(!(await newState.isPinned(firstContact)), 'contact not pinned'); - const record = await newState.getContact(firstContact); + assert.ok(!newState.isPinned(firstContact), 'contact not pinned'); + const record = newState.getContact(firstContact); assert.ok(record, 'contact record not found'); assert.ok(record?.archived, 'contact archived'); diff --git a/ts/test-mock/storage/call_links_test.node.ts b/ts/test-mock/storage/call_links_test.node.ts index 46590339a5..b001288358 100644 --- a/ts/test-mock/storage/call_links_test.node.ts +++ b/ts/test-mock/storage/call_links_test.node.ts @@ -106,23 +106,21 @@ describe('storage service', function (this: Mocha.Suite) { assert.notOk(deletedAtBeforeDelete, 'deletedAt falsey'); debug('Deleting call link'); - const callLinkItem = await window.getByText('Link to delete'); + const callLinkItem = window.getByText('Link to delete'); await callLinkItem.click(); - const callLinkDetails = await window.locator( + const callLinkDetails = window.locator( '.CallsTab__ConversationCallDetails' ); await callLinkDetails.waitFor(); - const deleteButton = await window.getByRole('button', { + const deleteButton = window.getByRole('button', { name: 'Delete link', }); await deleteButton.click(); - const confirmModal = await window.getByTestId( + const confirmModal = window.getByTestId( 'ConfirmationDialog.CallLinkDetails__DeleteLinkModal' ); await confirmModal.waitFor(); - const deleteConfirm = await window - .locator('.module-Button') - .getByText('Delete'); + const deleteConfirm = window.locator('.module-Button').getByText('Delete'); await deleteConfirm.click(); debug('Waiting for storage update'); diff --git a/ts/test-mock/storage/conflict_test.node.ts b/ts/test-mock/storage/conflict_test.node.ts index b5ddf44a40..69868e932d 100644 --- a/ts/test-mock/storage/conflict_test.node.ts +++ b/ts/test-mock/storage/conflict_test.node.ts @@ -74,8 +74,8 @@ describe('storage service', function (this: Mocha.Suite) { const record = kind === 'contact' - ? await newState.getContact(first) - : await newState.getGroup(group); + ? newState.getContact(first) + : newState.getGroup(group); assert.ok(record, 'contact record not found'); assert.ok(record?.archived, 'contact archived'); @@ -303,7 +303,7 @@ describe('storage service', function (this: Mocha.Suite) { .getByRole('button', { name: 'Delete link' }) .click(); - const confirmModal = await window.getByTestId( + const confirmModal = window.getByTestId( 'ConfirmationDialog.CallLinkDetails__DeleteLinkModal' ); await confirmModal.locator('.module-Button').getByText('Delete').click(); diff --git a/ts/test-mock/storage/max_read_keys_test.node.ts b/ts/test-mock/storage/max_read_keys_test.node.ts index 295fb99a93..b97c1806cd 100644 --- a/ts/test-mock/storage/max_read_keys_test.node.ts +++ b/ts/test-mock/storage/max_read_keys_test.node.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { PrimaryDevice } from '@signalapp/mock-server'; import { Proto } from '@signalapp/mock-server'; import * as durations from '../../util/durations/index.std.ts'; @@ -38,8 +37,10 @@ describe('storage service', function (this: Mocha.Suite) { debug('prepare for a slow test'); const { phone, contacts } = bootstrap; - const firstContact = contacts[0] as PrimaryDevice; - const lastContact = contacts[contacts.length - 1] as PrimaryDevice; + // oxlint-disable-next-line typescript/no-non-null-assertion + const firstContact = contacts[0]!; + // oxlint-disable-next-line typescript/no-non-null-assertion + const lastContact = contacts[contacts.length - 1]!; const window = await app.getWindow(); diff --git a/ts/test-mock/storage/pin_unpin_test.node.ts b/ts/test-mock/storage/pin_unpin_test.node.ts index 3232b3d53d..d892adb155 100644 --- a/ts/test-mock/storage/pin_unpin_test.node.ts +++ b/ts/test-mock/storage/pin_unpin_test.node.ts @@ -77,7 +77,7 @@ describe('storage service', function (this: Mocha.Suite) { const newState = await phone.waitForStorageState({ after: state, }); - assert.isTrue(await newState.isGroupPinned(group), 'group not pinned'); + assert.isTrue(newState.isGroupPinned(group), 'group not pinned'); // AccountRecord const { added, removed } = newState.diff(state); @@ -141,8 +141,7 @@ describe('storage service', function (this: Mocha.Suite) { const newState = await phone.waitForStorageState({ after: state, }); - // oxlint-disable-next-line no-await-in-loop - assert.isTrue(await newState.isPinned(contact), 'contact not pinned'); + assert.isTrue(newState.isPinned(contact), 'contact not pinned'); // AccountRecord const { added, removed } = newState.diff(state); diff --git a/ts/test-node/app/PreventDisplaySleepService_test.std.ts b/ts/test-node/app/PreventDisplaySleepService_test.std.ts index f45471499c..a46b59ee66 100644 --- a/ts/test-node/app/PreventDisplaySleepService_test.std.ts +++ b/ts/test-node/app/PreventDisplaySleepService_test.std.ts @@ -10,7 +10,7 @@ import { PreventDisplaySleepService } from '../../../app/PreventDisplaySleepServ describe('PreventDisplaySleepService', () => { class FakePowerSaveBlocker implements PowerSaveBlocker { #nextId = 0; - #idsStarted = new Set(); + readonly #idsStarted = new Set(); isStarted(id: number): boolean { return this.#idsStarted.has(id); diff --git a/ts/test-node/app/locale_test.main.ts b/ts/test-node/app/locale_test.main.ts index 0ad5b69f4b..867520e6b6 100644 --- a/ts/test-node/app/locale_test.main.ts +++ b/ts/test-node/app/locale_test.main.ts @@ -28,7 +28,7 @@ describe('locale', async () => { preferredSystemLocales: Array, expectedLocale: string ) { - const actualLocale = await load({ + const actualLocale = load({ rootDir, hourCyclePreference: HourCyclePreference.UnknownPreference, isPackaged: false, diff --git a/ts/test-node/jobs/JobQueue_test.node.ts b/ts/test-node/jobs/JobQueue_test.node.ts index d8e13a6d93..a9ef59ff02 100644 --- a/ts/test-node/jobs/JobQueue_test.node.ts +++ b/ts/test-node/jobs/JobQueue_test.node.ts @@ -744,7 +744,7 @@ describe('JobQueue', () => { }); class FakeStream implements AsyncIterable { - #eventEmitter = new EventEmitter(); + readonly #eventEmitter = new EventEmitter(); async *[Symbol.asyncIterator]() { // oxlint-disable-next-line no-constant-condition @@ -874,7 +874,7 @@ describe('JobQueue', () => { await noopQueue.add(undefined); - sinon.assert.notCalled(fakeStore.stream as sinon.SinonStub); + sinon.assert.notCalled(fakeStore.stream); }); }); }); diff --git a/ts/test-node/jobs/TestJobQueueStore.node.ts b/ts/test-node/jobs/TestJobQueueStore.node.ts index 21c5cedaa6..36a503447b 100644 --- a/ts/test-node/jobs/TestJobQueueStore.node.ts +++ b/ts/test-node/jobs/TestJobQueueStore.node.ts @@ -10,8 +10,8 @@ import { drop } from '../../util/drop.std.ts'; export class TestJobQueueStore implements JobQueueStore { events = new EventEmitter(); - #openStreams = new Set(); - #pipes = new Map(); + readonly #openStreams = new Set(); + readonly #pipes = new Map(); storedJobs: Array = []; @@ -82,7 +82,7 @@ export class TestJobQueueStore implements JobQueueStore { // oxlint-disable-next-line max-classes-per-file class Pipe implements AsyncIterable { #queue: Array = []; - #eventEmitter = new EventEmitter(); + readonly #eventEmitter = new EventEmitter(); #isLocked = false; #isPaused = false; diff --git a/ts/test-node/sql/cleanDataForIpc_test.std.ts b/ts/test-node/sql/cleanDataForIpc_test.std.ts index dd237d97b4..f3705d18b0 100644 --- a/ts/test-node/sql/cleanDataForIpc_test.std.ts +++ b/ts/test-node/sql/cleanDataForIpc_test.std.ts @@ -214,12 +214,15 @@ describe('cleanDataForIpc', () => { it('deeply cleans class instances', () => { class Person { + public firstName: string; + public lastName: string; + public toBeDiscarded = Symbol('to be discarded'); - constructor( - public firstName: string, - public lastName: string - ) {} + constructor(firstName: string, lastName: string) { + this.firstName = firstName; + this.lastName = lastName; + } get name() { return this.getName(); diff --git a/ts/test-node/sql/migration_1040_test.node.ts b/ts/test-node/sql/migration_1040_test.node.ts index 47adfbbdc4..9129eaa5c7 100644 --- a/ts/test-node/sql/migration_1040_test.node.ts +++ b/ts/test-node/sql/migration_1040_test.node.ts @@ -43,7 +43,7 @@ type UnflattenedAttachmentDownloadJobType = Omit< function insertNewJob( db: WritableDB, job: UnflattenedAttachmentDownloadJobType, - addMessageFirst: boolean = true + addMessageFirst = true ): void { if (addMessageFirst) { try { @@ -63,9 +63,9 @@ function insertNewJob( digest, contentType, size, - receivedAt, + receivedAt, sentAt, - active, + active, attempts, retryAfter, lastAttemptTimestamp diff --git a/ts/test-node/sql/migration_1180_test.node.ts b/ts/test-node/sql/migration_1180_test.node.ts index 68d0618475..50cd912175 100644 --- a/ts/test-node/sql/migration_1180_test.node.ts +++ b/ts/test-node/sql/migration_1180_test.node.ts @@ -14,7 +14,7 @@ const { omit } = lodash; function insertOldJob( db: WritableDB, job: Omit<_AttachmentDownloadJobTypeV1040, 'source' | 'ciphertextSize'>, - addMessageFirst: boolean = true + addMessageFirst = true ): void { if (addMessageFirst) { try { @@ -34,9 +34,9 @@ function insertOldJob( digest, contentType, size, - receivedAt, + receivedAt, sentAt, - active, + active, attempts, retryAfter, lastAttemptTimestamp @@ -116,7 +116,7 @@ describe('SQL/updateToSchemaVersion1180', () => { const details = explain( db, sql` - SELECT SUM(ciphertextSize) FROM attachment_downloads + SELECT SUM(ciphertextSize) FROM attachment_downloads WHERE source = 'backup_import'; ` ); @@ -131,8 +131,8 @@ describe('SQL/updateToSchemaVersion1180', () => { const details = explain( db, sql` - DELETE FROM attachment_downloads - WHERE source = 'backup_import'; + DELETE FROM attachment_downloads + WHERE source = 'backup_import'; ` ); diff --git a/ts/test-node/sql/migrations_test.node.ts b/ts/test-node/sql/migrations_test.node.ts index 03e8f91ef2..6b134e66d8 100644 --- a/ts/test-node/sql/migrations_test.node.ts +++ b/ts/test-node/sql/migrations_test.node.ts @@ -1837,8 +1837,8 @@ describe('SQL migrations test', () => { queueType: 'report spam', timestamp: 2, data: { - serverGuids: [`${MESSAGE_ID_1}`], - uuid: `${E164_1}`, + serverGuids: [MESSAGE_ID_1], + uuid: E164_1, }, }, ]); diff --git a/ts/test-node/updater/differential_test.main.ts b/ts/test-node/updater/differential_test.main.ts index d720c1f835..495b62943d 100644 --- a/ts/test-node/updater/differential_test.main.ts +++ b/ts/test-node/updater/differential_test.main.ts @@ -110,6 +110,7 @@ describe('updater/differential', () => { const range = value.match(/^(\d+)-(\d+)$/) as | (RegExpMatchArray & { 1: string; 2: string }) | null; + // oxlint-disable-next-line typescript/restrict-template-expressions strictAssert(range, `Invalid header: ${rangeHeader}`); return [parseInt(range[1], 10), parseInt(range[2], 10)] as const; diff --git a/ts/test-node/util/grapheme_test.std.ts b/ts/test-node/util/grapheme_test.std.ts index 56410afd07..02e16ffe2d 100644 --- a/ts/test-node/util/grapheme_test.std.ts +++ b/ts/test-node/util/grapheme_test.std.ts @@ -14,6 +14,7 @@ describe('grapheme utilities', () => { describe('getGraphemes', () => { it('returns extended graphemes in a string', () => { assert.deepEqual([...getGraphemes('')], []); + // oxlint-disable-next-line typescript/no-misused-spread assert.deepEqual([...getGraphemes('hello')], [...'hello']); assert.deepEqual( [...getGraphemes('BokmΓ₯l')], diff --git a/ts/test-node/util/libphonenumberUtil_test.std.ts b/ts/test-node/util/libphonenumberUtil_test.std.ts index 271971f985..ac7a22ae55 100644 --- a/ts/test-node/util/libphonenumberUtil_test.std.ts +++ b/ts/test-node/util/libphonenumberUtil_test.std.ts @@ -1,16 +1,14 @@ // Copyright 2015 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { assert, AssertionError } from 'chai'; +import { assert } from 'chai'; import { parseNumber } from '../../util/libphonenumberUtil.std.ts'; describe('libphonenumber util', () => { describe('parseNumber', () => { it('numbers with + are valid without providing regionCode', () => { const result = parseNumber('+14155555555'); - if (!result.isValidNumber) { - throw new AssertionError('Phone number is not valid'); - } + assert.isTrue(result.isValidNumber, 'Phone number is not valid'); assert.strictEqual(result.e164, '+14155555555'); assert.strictEqual(result.regionCode, 'US'); assert.strictEqual(result.countryCode, '1'); @@ -18,9 +16,7 @@ describe('libphonenumber util', () => { it('variant numbers with the right regionCode are valid', () => { ['4155555555', '14155555555', '+14155555555'].forEach(number => { const result = parseNumber(number, 'US'); - if (!result.isValidNumber) { - throw new AssertionError('Phone number is not valid'); - } + assert.isTrue(result.isValidNumber, 'Phone number is not valid'); assert.strictEqual(result.e164, '+14155555555'); assert.strictEqual(result.regionCode, 'US'); assert.strictEqual(result.countryCode, '1'); diff --git a/ts/test-node/util/privacy_test.node.ts b/ts/test-node/util/privacy_test.node.ts index be048d0b94..fcaccd5108 100644 --- a/ts/test-node/util/privacy_test.node.ts +++ b/ts/test-node/util/privacy_test.node.ts @@ -43,8 +43,7 @@ describe('Privacy', () => { '123-4-1-2-3-4-1-2-3-4-1-2-3-4-1-2-3\n' + '123-4-1-2-3-4-1-2-3-4-1-2-3-4-1-2-3-4\n' + '123a412-3-4-1-2-3-4-1-2-3-4-1-2-3-4\n' + - '123-4-1-2-3-4-1-2-3-4-1-2-3-4-1-a-2-3-4\n' + - ''; + '123-4-1-2-3-4-1-2-3-4-1-2-3-4-1-a-2-3-4\n'; const actual = Privacy.redactCardNumbers(text); const expected = @@ -65,8 +64,7 @@ describe('Privacy', () => { '[REDACTED]\n' + '[REDACTED]-4\n' + '123a[REDACTED]\n' + - '[REDACTED]-a-2-3-4\n' + - ''; + '[REDACTED]-a-2-3-4\n'; assert.equal(actual, expected); }); diff --git a/ts/test-node/util/uploads/helpers.node.ts b/ts/test-node/util/uploads/helpers.node.ts index 3986a465f5..cf28f769ff 100644 --- a/ts/test-node/util/uploads/helpers.node.ts +++ b/ts/test-node/util/uploads/helpers.node.ts @@ -24,7 +24,7 @@ export type LastRequestData = Readonly<{ }>; export class TestServer extends EventEmitter { - #server: Server; + readonly #server: Server; #nextResponse: NextResponse = { status: 200, headers: {} }; #lastRequest: { request: IncomingMessage; body: Buffer } | null = null; @@ -86,7 +86,10 @@ export class TestServer extends EventEmitter { }; } - #onRequest = (request: IncomingMessage, response: ServerResponse) => { + readonly #onRequest = ( + request: IncomingMessage, + response: ServerResponse + ) => { this.emit('request'); const nextResponse = this.#nextResponse; const lastRequest = { request, body: Buffer.alloc(0) }; diff --git a/ts/textsecure/AccountManager.preload.ts b/ts/textsecure/AccountManager.preload.ts index bb8e584159..5c0df2896b 100644 --- a/ts/textsecure/AccountManager.preload.ts +++ b/ts/textsecure/AccountManager.preload.ts @@ -686,7 +686,7 @@ export default class AccountManager extends EventTarget { ); } - const key = await generateSignedPreKey(identityKey, signedKeyId); + const key = generateSignedPreKey(identityKey, signedKeyId); log.info(`${logId}: Saving new signed prekey`, key.keyId); await itemStorage.put( @@ -705,7 +705,7 @@ export default class AccountManager extends EventTarget { const identityKey = this.#getIdentityKeyOrThrow(ourServiceId); const logId = `AccountManager.maybeUpdateSignedPreKey(${serviceIdKind}, ${ourServiceId})`; - const keys = await signalProtocolStore.loadSignedPreKeys(ourServiceId); + const keys = signalProtocolStore.loadSignedPreKeys(ourServiceId); const sortedKeys = orderBy(keys, ['created_at'], ['desc']); const confirmedKeys = sortedKeys.filter(key => key.confirmed); const mostRecent = confirmedKeys[0]; @@ -758,7 +758,7 @@ export default class AccountManager extends EventTarget { } const keyId = kyberKeyId; - const record = await generateKyberPreKey(identityKey, keyId); + const record = generateKyberPreKey(identityKey, keyId); log.info(`${logId}: Saving new last resort prekey`, keyId); await itemStorage.put( @@ -1304,7 +1304,7 @@ export default class AccountManager extends EventTarget { Bytes.toBase64(deriveStorageServiceKey(derivedMasterKey)) ); - await itemStorage.put('read-receipt-setting', Boolean(readReceipts)); + await itemStorage.put('read-receipt-setting', readReceipts); const regionCode = getRegionCodeForNumber(number); await itemStorage.put('regionCode', regionCode); diff --git a/ts/textsecure/MessageReceiver.preload.ts b/ts/textsecure/MessageReceiver.preload.ts index c287f9bc4f..dff9bba206 100644 --- a/ts/textsecure/MessageReceiver.preload.ts +++ b/ts/textsecure/MessageReceiver.preload.ts @@ -303,18 +303,18 @@ export default class MessageReceiver extends EventTarget implements IRequestHandler { - #storage: Storage; + readonly #storage: Storage; - #appQueue: PQueue; - #decryptAndCacheBatcher: BatcherType; - #cacheRemoveBatcher: BatcherType; + readonly #appQueue: PQueue; + readonly #decryptAndCacheBatcher: BatcherType; + readonly #cacheRemoveBatcher: BatcherType; #processedCount: number; - #incomingQueue: PQueue; + readonly #incomingQueue: PQueue; #isEmptied?: boolean; - #encryptedQueue: PQueue; - #decryptedQueue: PQueue; + readonly #encryptedQueue: PQueue; + readonly #decryptedQueue: PQueue; #retryCachedTimeout: NodeJS.Timeout | undefined; - #serverTrustRoots: Array; + readonly #serverTrustRoots: Array; #stoppingProcessing?: boolean; #pniIdentityKeyCheckRequired?: boolean; @@ -870,7 +870,7 @@ export default class MessageReceiver serverGuid: item.serverGuid, serverTimestamp: item.serverTimestamp, urgent: isBoolean(item.urgent) ? item.urgent : true, - story: Boolean(item.story), + story: item.story, reportingToken: item.reportingToken, groupId: item.groupId, }; @@ -2117,7 +2117,7 @@ export default class MessageReceiver device: envelope.sourceDevice, unidentifiedStatus, message, - isRecipientUpdate: Boolean(isRecipientUpdate), + isRecipientUpdate: isRecipientUpdate, receivedAtCounter: envelope.receivedAtCounter, receivedAtDate: envelope.receivedAtDate, expirationStartTimestamp: toNumber(expirationStartTimestamp) ?? 0, @@ -2236,7 +2236,7 @@ export default class MessageReceiver envelopeId: envelope.id, destinationServiceId: envelope.destinationServiceId, device: envelope.sourceDevice, - isRecipientUpdate: Boolean(sentMessage.isRecipientUpdate), + isRecipientUpdate: sentMessage.isRecipientUpdate, message, receivedAtCounter: envelope.receivedAtCounter, receivedAtDate: envelope.receivedAtDate, @@ -2250,7 +2250,7 @@ export default class MessageReceiver return { destinationServiceId, - isAllowedToReplyToStory: Boolean(isAllowedToReply), + isAllowedToReplyToStory: isAllowedToReply, destinationPniIdentityKey: undefined, unidentified: false, }; @@ -2294,10 +2294,7 @@ export default class MessageReceiver ); } - isAllowedToReply.set( - destinationServiceId, - recipient.isAllowedToReply !== false - ); + isAllowedToReply.set(destinationServiceId, recipient.isAllowedToReply); }); distributionListToSentServiceId.forEach((sentToServiceIds, listId) => { @@ -2317,7 +2314,7 @@ export default class MessageReceiver }) ), message, - isRecipientUpdate: Boolean(sentMessage.isRecipientUpdate), + isRecipientUpdate: sentMessage.isRecipientUpdate, receivedAtCounter: envelope.receivedAtCounter, receivedAtDate: envelope.receivedAtDate, storyDistributionListId: normalizeStoryDistributionId( @@ -3195,7 +3192,7 @@ export default class MessageReceiver ...message, editedMessageTimestamp: toNumber(editMessage.targetSentTimestamp), }, - isRecipientUpdate: Boolean(isRecipientUpdate), + isRecipientUpdate: isRecipientUpdate, receivedAtCounter: envelope.receivedAtCounter, receivedAtDate: envelope.receivedAtDate, expirationStartTimestamp: toNumber(expirationStartTimestamp) ?? 0, @@ -3906,7 +3903,7 @@ export default class MessageReceiver const contactSync = new ContactSyncEvent( processAttachment(blob), - Boolean(contactSyncProto.complete), + contactSyncProto.complete, envelope.receivedAtCounter, envelope.timestamp ); diff --git a/ts/textsecure/OutgoingMessage.preload.ts b/ts/textsecure/OutgoingMessage.preload.ts index 9b79b80e88..efaace70c0 100644 --- a/ts/textsecure/OutgoingMessage.preload.ts +++ b/ts/textsecure/OutgoingMessage.preload.ts @@ -566,7 +566,7 @@ export default class OutgoingMessage { log.warn( `doSendMessage: Failing over to unsealed send for serviceId ${serviceId}` ); - if (this.failoverServiceIds.indexOf(serviceId) === -1) { + if (!this.failoverServiceIds.includes(serviceId)) { this.failoverServiceIds.push(serviceId); } @@ -678,8 +678,7 @@ export default class OutgoingMessage { }, innerError => { log.error( - 'doSendMessage: Error closing sessions: ' + - `${Errors.toLogFormat(innerError)}` + `doSendMessage: Error closing sessions: ${Errors.toLogFormat(innerError)}` ); throw error; } diff --git a/ts/textsecure/SendMessage.preload.ts b/ts/textsecure/SendMessage.preload.ts index 87d749351e..aa54ae21f4 100644 --- a/ts/textsecure/SendMessage.preload.ts +++ b/ts/textsecure/SendMessage.preload.ts @@ -484,7 +484,7 @@ class Message { if (contactEntry.avatar?.avatar) { avatar = { avatar: contactEntry.avatar.avatar, - isProfile: Boolean(contactEntry.avatar.isProfile), + isProfile: contactEntry.avatar.isProfile, }; } @@ -587,7 +587,7 @@ class Message { if (this.pollCreate) { pollCreate = { question: this.pollCreate.question, - allowMultiple: Boolean(this.pollCreate.allowMultiple), + allowMultiple: this.pollCreate.allowMultiple, options: this.pollCreate.options.slice(), }; requiredProtocolVersion = Math.max( @@ -809,9 +809,7 @@ export class MessageSender { return { text: attachmentAttrs.text ?? null, - textStyle: attachmentAttrs.textStyle - ? Number(attachmentAttrs.textStyle) - : 0, + textStyle: attachmentAttrs.textStyle ?? 0, textForegroundColor: attachmentAttrs.textForegroundColor ?? null, textBackgroundColor: attachmentAttrs.textBackgroundColor ?? null, @@ -1845,10 +1843,7 @@ export class MessageSender { new Array(), } satisfies Proto.SyncMessage.DeleteForMe.Params; - const messageDeletes: Map< - string, - Array - > = new Map(); + const messageDeletes = new Map>(); data.forEach(item => { if (item.type === 'delete-message') { @@ -2635,7 +2630,7 @@ export class MessageSender { recipients, sendLogCallback, story, - timestamp = Date.now(), + timestamp, urgent, }: Readonly<{ contentHint: number; diff --git a/ts/textsecure/SocketManager.preload.ts b/ts/textsecure/SocketManager.preload.ts index 73a9f58295..043fc212fe 100644 --- a/ts/textsecure/SocketManager.preload.ts +++ b/ts/textsecure/SocketManager.preload.ts @@ -86,7 +86,8 @@ export type SocketExpirationReason = 'remote' | 'build'; // Incoming requests on unauthenticated resource are not currently supported. // IChatConnection is responsible for their immediate termination. export class SocketManager extends EventListener { - #backOff = new BackOff(FIBONACCI_TIMEOUTS, { + readonly #libsignalNet: Net.Net; + readonly #backOff = new BackOff(FIBONACCI_TIMEOUTS, { jitter: JITTER, }); @@ -94,13 +95,13 @@ export class SocketManager extends EventListener { #unauthenticated?: AbortableProcess>; #unauthenticatedExpirationTimer?: NodeJS.Timeout; #credentials?: WebAPICredentials; - #authenticatedStatus: SocketInfo = { + readonly #authenticatedStatus: SocketInfo = { status: SocketStatus.CLOSED, }; - #unathenticatedStatus: SocketInfo = { + readonly #unathenticatedStatus: SocketInfo = { status: SocketStatus.CLOSED, }; - #requestHandlers = new Set(); + readonly #requestHandlers = new Set(); #incomingRequestQueue = new Array(); #isNavigatorOffline = false; #privIsOnline: boolean | undefined; @@ -109,8 +110,9 @@ export class SocketManager extends EventListener { #reconnectController: AbortController | undefined; #envelopeCount = 0; - constructor(private readonly libsignalNet: Net.Net) { + constructor(libsignalNet: Net.Net) { super(); + this.#libsignalNet = libsignalNet; } public getStatus(): SocketStatuses { @@ -188,7 +190,7 @@ export class SocketManager extends EventListener { ); const process = connectAuthenticated({ - libsignalNet: this.libsignalNet, + libsignalNet: this.#libsignalNet, name: AUTHENTICATED_CHANNEL_NAME, credentials: this.#credentials, handler: (req: IncomingWebSocketRequest): void => { @@ -382,7 +384,7 @@ export class SocketManager extends EventListener { }, timeout); try { - return await this.libsignalNet.connectProvisioning(listener, { + return await this.#libsignalNet.connectProvisioning(listener, { abortSignal: abortController.signal, }); } finally { @@ -518,7 +520,7 @@ export class SocketManager extends EventListener { log.info('onNavigatorOnline'); this.#isNavigatorOffline = false; this.#backOff.reset(FIBONACCI_TIMEOUTS); - this.libsignalNet.onNetworkChange(); + this.#libsignalNet.onNetworkChange(); // Reconnect earlier if waiting if (this.#credentials !== undefined) { @@ -612,7 +614,7 @@ export class SocketManager extends EventListener { const process: AbortableProcess> = connectUnauthenticated({ - libsignalNet: this.libsignalNet, + libsignalNet: this.#libsignalNet, name: UNAUTHENTICATED_CHANNEL_NAME, userLanguages, keepalive: { path: '/v1/keepalive' }, diff --git a/ts/textsecure/Types.d.ts b/ts/textsecure/Types.d.ts index d6df8d6c05..c2223c03b8 100644 --- a/ts/textsecure/Types.d.ts +++ b/ts/textsecure/Types.d.ts @@ -18,7 +18,7 @@ import type { AnyPaymentEvent } from '../types/Payment.std.ts'; import type { RawBodyRange } from '../types/BodyRange.std.ts'; import type { StoryMessageRecipientsType } from '../types/Stories.std.ts'; -export { +export type { IdentityKeyType, IdentityKeyIdType, KyberPreKeyType, @@ -92,7 +92,7 @@ export type ProcessedEnvelope = Readonly<{ type: Proto.Envelope.Type; source: string | undefined; sourceServiceId: ServiceIdString | undefined; - sourceDevice: number | Undefined; + sourceDevice: number | undefined; destinationServiceId: ServiceIdString; updatedPni: PniString | undefined; timestamp: number; diff --git a/ts/textsecure/WebAPI.preload.ts b/ts/textsecure/WebAPI.preload.ts index e509dc7848..3ca78fba7b 100644 --- a/ts/textsecure/WebAPI.preload.ts +++ b/ts/textsecure/WebAPI.preload.ts @@ -507,6 +507,7 @@ async function _promiseAjax( } } + // oxlint-disable-next-line typescript/no-redundant-type-constituents let result: string | Uint8Array | Readable | unknown; try { if (DEBUG && !isSuccess(response.status)) { @@ -2819,6 +2820,7 @@ async function _withNewCredentials< const result = await callback(); + // oxlint-disable-next-line typescript/no-useless-default-assignment FIXME const { uuid: aci = newUsername, deviceId = 1 } = result; // Set final REST credentials to let `registerKeys` succeed. @@ -3198,6 +3200,7 @@ export function createFetchForAttachmentUpload({ ...init, headers: { ...fetchOptions.headers, + // oxlint-disable-next-line typescript/no-misused-spread FIXME ...init.headers, }, }); @@ -4633,9 +4636,7 @@ export async function getGroupFromLink( disableSessionResumption: true, httpType: 'GET', responseType: 'bytes', - urlParameters: safeInviteLinkPassword - ? `${safeInviteLinkPassword}` - : undefined, + urlParameters: safeInviteLinkPassword, redactUrl: _createRedactor(safeInviteLinkPassword), }); @@ -4729,9 +4730,9 @@ export async function getGroupLog( }, urlParameters: `/${startVersion}?` + - `includeFirstState=${Boolean(includeFirstState)}&` + - `includeLastState=${Boolean(includeLastState)}&` + - `maxSupportedChangeEpoch=${Number(maxSupportedChangeEpoch)}`, + `includeFirstState=${includeFirstState}&` + + `includeLastState=${includeLastState}&` + + `maxSupportedChangeEpoch=${maxSupportedChangeEpoch}`, }); const { data, response } = withDetails; const changes = Proto.GroupChanges.decode(data); diff --git a/ts/textsecure/WebsocketResources.preload.ts b/ts/textsecure/WebsocketResources.preload.ts index 825629c444..c3ac9aa90f 100644 --- a/ts/textsecure/WebsocketResources.preload.ts +++ b/ts/textsecure/WebsocketResources.preload.ts @@ -150,15 +150,25 @@ export enum ServerRequestType { } export class IncomingWebSocketRequest { + public readonly requestType: ServerRequestType; + public readonly body: Uint8Array | undefined; + public readonly timestamp: number | undefined; + readonly #ack: Pick | undefined; + constructor( - readonly requestType: ServerRequestType, - readonly body: Uint8Array | undefined, - readonly timestamp: number | undefined, - private readonly ack: Pick | undefined - ) {} + requestType: ServerRequestType, + body: Uint8Array | undefined, + timestamp: number | undefined, + ack: Pick | undefined + ) { + this.requestType = requestType; + this.body = body; + this.timestamp = timestamp; + this.#ack = ack; + } respond(status: number, _message: string): void { - this.ack?.send(status); + this.#ack?.send(status); } } @@ -185,11 +195,13 @@ export type WebSocketResourceOptions = { // oxlint-disable-next-line max-classes-per-file export class CloseEvent extends Event { - constructor( - public readonly code: number, - public readonly reason: string - ) { + public readonly code: number; + public readonly reason: string; + + constructor(code: number, reason: string) { super('close'); + this.code = code; + this.reason = reason; } } @@ -392,6 +404,11 @@ export class WebSocketResource extends EventTarget implements IChatConnection { + readonly #chatService: ChatConnection; + readonly #socketIpVersion: IpVersion; + readonly #localPortNumber: number; + readonly #logId: string; + // The reason that the connection was closed, if it was closed. // // When setting this to anything other than `undefined`, the "close" event @@ -402,28 +419,32 @@ export class WebSocketResource // - Server uses /v1/keepalive requests to do some consistency checks // - external events (like waking from sleep) can prompt us to do a shorter keepalive // So at least for now, we want to keep this mechanism around too. - #keepalive: KeepAlive; + readonly #keepalive: KeepAlive; constructor( - private readonly chatService: ChatConnection, - private readonly socketIpVersion: IpVersion, - private readonly localPortNumber: number, - private readonly logId: string, + chatService: ChatConnection, + socketIpVersion: IpVersion, + localPortNumber: number, + logId: string, keepalive: KeepAliveOptionsType ) { super(); + this.#chatService = chatService; + this.#socketIpVersion = socketIpVersion; + this.#localPortNumber = localPortNumber; + this.#logId = logId; - this.#keepalive = new KeepAlive(this, this.logId, keepalive); + this.#keepalive = new KeepAlive(this, this.#logId, keepalive); this.#keepalive.reset(); this.addEventListener('close', () => this.#keepalive?.stop()); } public localPort(): number { - return this.localPortNumber; + return this.#localPortNumber; } public ipVersion(): IpVersion { - return this.socketIpVersion; + return this.#socketIpVersion; } public override addEventListener( @@ -437,12 +458,12 @@ export class WebSocketResource public close(code = NORMAL_DISCONNECT_CODE, reason?: string): void { if (this.#closedReasonCode !== undefined) { - log.info(`${this.logId}.close: Already closed! ${code}/${reason}`); + log.info(`${this.#logId}.close: Already closed! ${code}/${reason}`); return; } this.#closedReasonCode = code; - drop(this.chatService.disconnect()); + drop(this.#chatService.disconnect()); // Since we set `closedReasonCode`, we must dispatch the close event. this.dispatchEvent(new CloseEvent(code, reason || 'no reason provided')); @@ -459,12 +480,12 @@ export class WebSocketResource // request and an error on the connection. It's likely benign but in // case it's not, make sure we know about it. log.info( - `${this.logId}: onConnectionInterrupted called after resource is closed: ${cause.message}` + `${this.#logId}: onConnectionInterrupted called after resource is closed: ${cause.message}` ); } return; } - log.warn(`${this.logId}: connection closed`); + log.warn(`${this.#logId}: connection closed`); // This is a workaround to map libsignal error codes to close codes that // SocketManager's existing clients expect. @@ -495,13 +516,13 @@ export class WebSocketResource } get libsignalWebsocket(): ChatConnection { - return this.chatService; + return this.#chatService; } public async sendRequestGetDebugInfo( options: SendRequestOptions ): Promise { - const response = await this.chatService.fetch({ + const response = await this.#chatService.fetch({ verb: options.verb, path: options.path, headers: options.headers ? options.headers : [], @@ -545,7 +566,7 @@ const LOG_KEEPALIVE_AFTER_MS = 500; * intervals. */ class KeepAliveSender { - #path: string; + readonly #path: string; protected wsr: IWebSocketResource; diff --git a/ts/textsecure/cds/CDSBase.node.ts b/ts/textsecure/cds/CDSBase.node.ts index c9249b6bbd..b350249bd6 100644 --- a/ts/textsecure/cds/CDSBase.node.ts +++ b/ts/textsecure/cds/CDSBase.node.ts @@ -30,11 +30,13 @@ export type CachedAuthType = Readonly<{ export abstract class CDSBase< Options extends CDSBaseOptionsType = CDSBaseOptionsType, > { + readonly #options: Options; protected readonly logger: LoggerType; protected proxyAgent?: ProxyAgent; protected cachedAuth?: CachedAuthType; - constructor(protected readonly options: Options) { + constructor(options: Options) { + this.#options = options; this.logger = options.logger; } @@ -44,8 +46,8 @@ export abstract class CDSBase< protected async getAuth(): Promise { // Lazily create proxy agent - if (!this.proxyAgent && this.options.proxyUrl) { - this.proxyAgent = await createProxyAgent(this.options.proxyUrl); + if (!this.proxyAgent && this.#options.proxyUrl) { + this.proxyAgent = await createProxyAgent(this.#options.proxyUrl); } if (this.cachedAuth) { @@ -56,7 +58,7 @@ export abstract class CDSBase< } } - const auth = await this.options.getAuth(); + const auth = await this.#options.getAuth(); this.cachedAuth = { auth, diff --git a/ts/textsecure/cds/CDSI.node.ts b/ts/textsecure/cds/CDSI.node.ts index 7a8f980710..edeba3bf7e 100644 --- a/ts/textsecure/cds/CDSI.node.ts +++ b/ts/textsecure/cds/CDSI.node.ts @@ -20,14 +20,13 @@ export type CDSIOptionsType = CDSBaseOptionsType; const REQUEST_TIMEOUT = 10 * durations.SECOND; -export class CDSI extends CDSBase { +export class CDSI extends CDSBase { + readonly #libsignalNet: Net.Net; #retryAfter?: number; - constructor( - private readonly libsignalNet: Net.Net, - options: CDSIOptionsType - ) { + constructor(libsignalNet: Net.Net, options: CDSIOptionsType) { super(options); + this.#libsignalNet = libsignalNet; } public async request( @@ -51,7 +50,7 @@ export class CDSI extends CDSBase { const { timeout = REQUEST_TIMEOUT } = options; const response = await pTimeout( - this.libsignalNet.cdsiLookup(auth, { + this.#libsignalNet.cdsiLookup(auth, { acisAndAccessKeys, e164s, }), diff --git a/ts/textsecure/messageReceiverEvents.std.ts b/ts/textsecure/messageReceiverEvents.std.ts index d373f4dc30..4645f02b9b 100644 --- a/ts/textsecure/messageReceiverEvents.std.ts +++ b/ts/textsecure/messageReceiverEvents.std.ts @@ -50,16 +50,12 @@ export type TypingEventConfig = { // oxlint-disable-next-line max-classes-per-file export class TypingEvent extends Event { public readonly sender?: string; - public readonly senderAci?: AciString; - public readonly senderDevice: number; - public readonly typing: TypingEventData; constructor({ sender, senderAci, senderDevice, typing }: TypingEventConfig) { super('typing'); - this.sender = sender; this.senderAci = senderAci; this.senderDevice = senderDevice; @@ -68,32 +64,50 @@ export class TypingEvent extends Event { } export class ErrorEvent extends Event { - constructor(public readonly error: Error) { + public readonly error: Error; + + constructor(error: Error) { super('error'); + this.error = error; } } export class ContactSyncEvent extends Event { + public readonly contactAttachment: ProcessedAttachment; + public readonly complete: boolean; + public readonly receivedAtCounter: number; + public readonly sentAt: number; + constructor( - public readonly contactAttachment: ProcessedAttachment, - public readonly complete: boolean, - public readonly receivedAtCounter: number, - public readonly sentAt: number + contactAttachment: ProcessedAttachment, + complete: boolean, + receivedAtCounter: number, + sentAt: number ) { super('contactSync'); + this.contactAttachment = contactAttachment; + this.complete = complete; + this.receivedAtCounter = receivedAtCounter; + this.sentAt = sentAt; } } // Emitted right before we do full decrypt on a message, but after Sealed Sender unseal export class EnvelopeUnsealedEvent extends Event { - constructor(public readonly envelope: ProcessedEnvelope) { + public readonly envelope: ProcessedEnvelope; + + constructor(envelope: ProcessedEnvelope) { super('envelopeUnsealed'); + this.envelope = envelope; } } export class EnvelopeQueuedEvent extends Event { - constructor(public readonly envelope: ProcessedEnvelope) { + public readonly envelope: ProcessedEnvelope; + + constructor(envelope: ProcessedEnvelope) { super('envelopeQueued'); + this.envelope = envelope; } } @@ -104,11 +118,11 @@ export class EnvelopeQueuedEvent extends Event { export type ConfirmCallback = () => void; export class ConfirmableEvent extends Event { - constructor( - type: string, - public readonly confirm: ConfirmCallback - ) { + public readonly confirm: ConfirmCallback; + + constructor(type: string, confirm: ConfirmCallback) { super(type); + this.confirm = confirm; } } @@ -121,13 +135,20 @@ export type DeliveryEventData = Readonly<{ }>; export class DeliveryEvent extends ConfirmableEvent { + public readonly deliveryReceipts: ReadonlyArray; + public readonly envelopeId: string; + public readonly envelopeTimestamp: number; + constructor( - public readonly deliveryReceipts: ReadonlyArray, - public readonly envelopeId: string, - public readonly envelopeTimestamp: number, + deliveryReceipts: ReadonlyArray, + envelopeId: string, + envelopeTimestamp: number, confirm: ConfirmCallback ) { super('delivery', confirm); + this.deliveryReceipts = deliveryReceipts; + this.envelopeId = envelopeId; + this.envelopeTimestamp = envelopeTimestamp; } } @@ -138,8 +159,11 @@ export type SuccessfulDecryptEventData = Readonly<{ }>; export class SuccessfulDecryptEvent extends Event { - constructor(public readonly data: SuccessfulDecryptEventData) { + public readonly data: SuccessfulDecryptEventData; + + constructor(data: SuccessfulDecryptEventData) { super('successful-decrypt'); + this.data = data; } } @@ -156,11 +180,13 @@ export type DecryptionErrorEventData = Readonly<{ }>; export class DecryptionErrorEvent extends ConfirmableEvent { + public readonly decryptionError: DecryptionErrorEventData; constructor( - public readonly decryptionError: DecryptionErrorEventData, + decryptionError: DecryptionErrorEventData, confirm: ConfirmCallback ) { super('decryption-error', confirm); + this.decryptionError = decryptionError; } } @@ -171,8 +197,11 @@ export type InvalidPlaintextEventData = Readonly<{ }>; export class InvalidPlaintextEvent extends Event { - constructor(public readonly data: InvalidPlaintextEventData) { + public readonly data: InvalidPlaintextEventData; + + constructor(data: InvalidPlaintextEventData) { super('invalid-plaintext'); + this.data = data; } } @@ -186,11 +215,11 @@ export type RetryRequestEventData = Readonly<{ }>; export class RetryRequestEvent extends ConfirmableEvent { - constructor( - public readonly retryRequest: RetryRequestEventData, - confirm: ConfirmCallback - ) { + public readonly retryRequest: RetryRequestEventData; + + constructor(retryRequest: RetryRequestEventData, confirm: ConfirmCallback) { super('retry-request', confirm); + this.retryRequest = retryRequest; } } @@ -211,11 +240,11 @@ export type SentEventData = Readonly<{ }>; export class SentEvent extends ConfirmableEvent { - constructor( - public readonly data: SentEventData, - confirm: ConfirmCallback - ) { + public readonly data: SentEventData; + + constructor(data: SentEventData, confirm: ConfirmCallback) { super('sent', confirm); + this.data = data; } } @@ -226,12 +255,18 @@ export type ProfileKeyUpdateData = Readonly<{ }>; export class ProfileKeyUpdateEvent extends ConfirmableEvent { + public readonly data: ProfileKeyUpdateData; + public readonly reason: string; + constructor( - public readonly data: ProfileKeyUpdateData, - public readonly reason: string, + data: ProfileKeyUpdateData, + reason: string, confirm: ConfirmCallback ) { super('profileKeyUpdate', confirm); + + this.data = data; + this.reason = reason; } } @@ -251,11 +286,11 @@ export type MessageEventData = Readonly<{ }>; export class MessageEvent extends ConfirmableEvent { - constructor( - public readonly data: MessageEventData, - confirm: ConfirmCallback - ) { + public readonly data: MessageEventData; + + constructor(data: MessageEventData, confirm: ConfirmCallback) { super('message', confirm); + this.data = data; } } @@ -268,33 +303,51 @@ export type ReadOrViewEventData = Readonly<{ }>; export class ReadEvent extends ConfirmableEvent { + public readonly receipts: ReadonlyArray; + public readonly envelopeId: string; + public readonly envelopeTimestamp: number; + constructor( - public readonly receipts: ReadonlyArray, - public readonly envelopeId: string, - public readonly envelopeTimestamp: number, + receipts: ReadonlyArray, + envelopeId: string, + envelopeTimestamp: number, confirm: ConfirmCallback ) { super('read', confirm); + + this.receipts = receipts; + this.envelopeId = envelopeId; + this.envelopeTimestamp = envelopeTimestamp; } } export class ViewEvent extends ConfirmableEvent { + public readonly receipts: ReadonlyArray; + public readonly envelopeId: string; + public readonly envelopeTimestamp: number; + constructor( - public readonly receipts: ReadonlyArray, - public readonly envelopeId: string, - public readonly envelopeTimestamp: number, + receipts: ReadonlyArray, + envelopeId: string, + envelopeTimestamp: number, confirm: ConfirmCallback ) { super('view', confirm); + this.receipts = receipts; + this.envelopeId = envelopeId; + this.envelopeTimestamp = envelopeTimestamp; } } export class ConfigurationEvent extends ConfirmableEvent { + public readonly configuration: Proto.SyncMessage.Configuration; + constructor( - public readonly configuration: Proto.SyncMessage.Configuration, + configuration: Proto.SyncMessage.Configuration, confirm: ConfirmCallback ) { super('configuration', confirm); + this.configuration = configuration; } } @@ -315,7 +368,6 @@ export class ViewOnceOpenSyncEvent extends ConfirmableEvent { confirm: ConfirmCallback ) { super('viewOnceOpenSync', confirm); - this.sourceAci = sourceAci; this.timestamp = timestamp; this.envelopeTimestamp = envelopeTimestamp; @@ -336,19 +388,12 @@ export type MessageRequestResponseOptions = { export class MessageRequestResponseEvent extends ConfirmableEvent { public readonly threadAci?: AciString; - public readonly messageRequestResponseType?: MessageRequestResponseOptions['messageRequestResponseType']; - public readonly groupId?: string; - public readonly groupV2Id?: string; - public readonly envelopeId?: string; - public readonly receivedAtMs: number; - public readonly receivedAtCounter: number; - public readonly sentAt: number; constructor( @@ -365,7 +410,6 @@ export class MessageRequestResponseEvent extends ConfirmableEvent { confirm: ConfirmCallback ) { super('messageRequestResponse', confirm); - this.envelopeId = envelopeId; this.threadAci = threadAci; this.messageRequestResponseType = messageRequestResponseType; @@ -378,11 +422,14 @@ export class MessageRequestResponseEvent extends ConfirmableEvent { } export class FetchLatestEvent extends ConfirmableEvent { + public readonly eventType: Proto.SyncMessage.FetchLatest['type']; + constructor( - public readonly eventType: Proto.SyncMessage.FetchLatest['type'], + eventType: Proto.SyncMessage.FetchLatest['type'], confirm: ConfirmCallback ) { super('fetchLatest', confirm); + this.eventType = eventType; } } @@ -402,7 +449,6 @@ export class KeysEvent extends ConfirmableEvent { confirm: ConfirmCallback ) { super('keys', confirm); - this.masterKey = masterKey; this.accountEntropyPool = accountEntropyPool; this.mediaRootBackupKey = mediaRootBackupKey; @@ -417,11 +463,14 @@ export type StickerPackEventData = Readonly<{ }>; export class StickerPackEvent extends ConfirmableEvent { + public readonly stickerPacks: ReadonlyArray; + constructor( - public readonly stickerPacks: ReadonlyArray, + stickerPacks: ReadonlyArray, confirm: ConfirmCallback ) { super('sticker-pack', confirm); + this.stickerPacks = stickerPacks; } } @@ -434,13 +483,20 @@ export type ReadSyncEventData = Readonly<{ }>; export class ReadSyncEvent extends ConfirmableEvent { + public readonly reads: ReadonlyArray; + public readonly envelopeId: string; + public readonly envelopeTimestamp: number; + constructor( - public readonly reads: ReadonlyArray, - public readonly envelopeId: string, - public readonly envelopeTimestamp: number, + reads: ReadonlyArray, + envelopeId: string, + envelopeTimestamp: number, confirm: ConfirmCallback ) { super('readSync', confirm); + this.reads = reads; + this.envelopeId = envelopeId; + this.envelopeTimestamp = envelopeTimestamp; } } @@ -451,13 +507,20 @@ export type ViewSyncEventData = Readonly<{ }>; export class ViewSyncEvent extends ConfirmableEvent { + public readonly views: ReadonlyArray; + public readonly envelopeId: string; + public readonly envelopeTimestamp: number; + constructor( - public readonly views: ReadonlyArray, - public readonly envelopeId: string, - public readonly envelopeTimestamp: number, + views: ReadonlyArray, + envelopeId: string, + envelopeTimestamp: number, confirm: ConfirmCallback ) { super('viewSync', confirm); + this.views = views; + this.envelopeId = envelopeId; + this.envelopeTimestamp = envelopeTimestamp; } } @@ -468,11 +531,11 @@ export type CallEventSyncEventData = Readonly<{ }>; export class CallEventSyncEvent extends ConfirmableEvent { - constructor( - public readonly callEvent: CallEventSyncEventData, - confirm: ConfirmCallback - ) { + public readonly callEvent: CallEventSyncEventData; + + constructor(callEvent: CallEventSyncEventData, confirm: ConfirmCallback) { super('callEventSync', confirm); + this.callEvent = callEvent; } } @@ -483,11 +546,14 @@ export type CallLinkUpdateSyncEventData = Readonly<{ }>; export class CallLinkUpdateSyncEvent extends ConfirmableEvent { + public readonly callLinkUpdate: CallLinkUpdateSyncEventData; + constructor( - public readonly callLinkUpdate: CallLinkUpdateSyncEventData, + callLinkUpdate: CallLinkUpdateSyncEventData, confirm: ConfirmCallback ) { super('callLinkUpdateSync', confirm); + this.callLinkUpdate = callLinkUpdate; } } @@ -581,13 +647,21 @@ export type DeleteForMeSyncTarget = z.infer; export type DeleteForMeSyncEventData = ReadonlyArray; export class DeleteForMeSyncEvent extends ConfirmableEvent { + public readonly deleteForMeSync: DeleteForMeSyncEventData; + public readonly timestamp: number; + public readonly envelopeId: string; + constructor( - public readonly deleteForMeSync: DeleteForMeSyncEventData, - public readonly timestamp: number, - public readonly envelopeId: string, + deleteForMeSync: DeleteForMeSyncEventData, + timestamp: number, + envelopeId: string, confirm: ConfirmCallback ) { super('deleteForMeSync', confirm); + + this.deleteForMeSync = deleteForMeSync; + this.timestamp = timestamp; + this.envelopeId = envelopeId; } } @@ -616,13 +690,20 @@ export type AttachmentBackfillResponseSyncEventData = Readonly< >; export class AttachmentBackfillResponseSyncEvent extends ConfirmableEvent { + public readonly response: AttachmentBackfillResponseSyncEventData; + public readonly timestamp: number; + public readonly envelopeId: string; + constructor( - public readonly response: AttachmentBackfillResponseSyncEventData, - public readonly timestamp: number, - public readonly envelopeId: string, + response: AttachmentBackfillResponseSyncEventData, + timestamp: number, + envelopeId: string, confirm: ConfirmCallback ) { super('attachmentBackfillResponseSync', confirm); + this.response = response; + this.timestamp = timestamp; + this.envelopeId = envelopeId; } } @@ -632,11 +713,11 @@ export type CallLogEventSyncEventData = Readonly<{ }>; export class CallLogEventSyncEvent extends ConfirmableEvent { - constructor( - public readonly data: CallLogEventSyncEventData, - confirm: ConfirmCallback - ) { + public readonly data: CallLogEventSyncEventData; + + constructor(data: CallLogEventSyncEventData, confirm: ConfirmCallback) { super('callLogEventSync', confirm); + this.data = data; } } @@ -647,10 +728,10 @@ export type StoryRecipientUpdateData = Readonly<{ }>; export class StoryRecipientUpdateEvent extends ConfirmableEvent { - constructor( - public readonly data: StoryRecipientUpdateData, - confirm: ConfirmCallback - ) { + public readonly data: StoryRecipientUpdateData; + + constructor(data: StoryRecipientUpdateData, confirm: ConfirmCallback) { super('storyRecipientUpdate', confirm); + this.data = data; } } diff --git a/ts/textsecure/processDataMessage.preload.ts b/ts/textsecure/processDataMessage.preload.ts index beb8497427..0a645e1f44 100644 --- a/ts/textsecure/processDataMessage.preload.ts +++ b/ts/textsecure/processDataMessage.preload.ts @@ -472,7 +472,7 @@ export function processGiftBadge( ); return { - expiration: Number(receipt.getReceiptExpirationTime()) * SECOND, + expiration: receipt.getReceiptExpirationTime() * SECOND, id: undefined, level: Number(receipt.getReceiptLevel()), receiptCredentialPresentation: Bytes.toBase64( diff --git a/ts/textsecure/storage/Blocked.std.ts b/ts/textsecure/storage/Blocked.std.ts index c130a7b479..cd97e23b41 100644 --- a/ts/textsecure/storage/Blocked.std.ts +++ b/ts/textsecure/storage/Blocked.std.ts @@ -18,10 +18,14 @@ export const BLOCKED_UUIDS_ID = 'blocked-uuids'; export const BLOCKED_GROUPS_ID = 'blocked-groups'; export class Blocked { - constructor(private readonly storage: StorageInterface) {} + readonly #storage: StorageInterface; + + constructor(storage: StorageInterface) { + this.#storage = storage; + } public getBlockedNumbers(): Array { - return this.storage.get(BLOCKED_NUMBERS_ID, new Array()); + return this.#storage.get(BLOCKED_NUMBERS_ID, new Array()); } public isBlocked(number: string): boolean { @@ -35,7 +39,7 @@ export class Blocked { } log.info('adding', number, 'to blocked list'); - await this.storage.put(BLOCKED_NUMBERS_ID, numbers.concat(number)); + await this.#storage.put(BLOCKED_NUMBERS_ID, numbers.concat(number)); } public async removeBlockedNumber(number: string): Promise { @@ -45,11 +49,11 @@ export class Blocked { } log.info('removing', number, 'from blocked list'); - await this.storage.put(BLOCKED_NUMBERS_ID, without(numbers, number)); + await this.#storage.put(BLOCKED_NUMBERS_ID, without(numbers, number)); } public getBlockedServiceIds(): Array { - return this.storage.get(BLOCKED_UUIDS_ID, new Array()); + return this.#storage.get(BLOCKED_UUIDS_ID, new Array()); } public isServiceIdBlocked(serviceId: ServiceIdString): boolean { @@ -63,7 +67,7 @@ export class Blocked { } log.info('adding', serviceId, 'to blocked list'); - await this.storage.put(BLOCKED_UUIDS_ID, serviceIds.concat(serviceId)); + await this.#storage.put(BLOCKED_UUIDS_ID, serviceIds.concat(serviceId)); } public async removeBlockedServiceId( @@ -75,11 +79,11 @@ export class Blocked { } log.info('removing', serviceId, 'from blocked list'); - await this.storage.put(BLOCKED_UUIDS_ID, without(numbers, serviceId)); + await this.#storage.put(BLOCKED_UUIDS_ID, without(numbers, serviceId)); } public getBlockedGroups(): Array { - return this.storage.get(BLOCKED_GROUPS_ID, new Array()); + return this.#storage.get(BLOCKED_GROUPS_ID, new Array()); } public isGroupBlocked(groupId: string): boolean { @@ -93,7 +97,7 @@ export class Blocked { } log.info(`adding group(${groupId}) to blocked list`); - await this.storage.put(BLOCKED_GROUPS_ID, groupIds.concat(groupId)); + await this.#storage.put(BLOCKED_GROUPS_ID, groupIds.concat(groupId)); } public async removeBlockedGroup(groupId: string): Promise { @@ -103,7 +107,7 @@ export class Blocked { } log.info(`removing group(${groupId} from blocked list`); - await this.storage.put(BLOCKED_GROUPS_ID, without(groupIds, groupId)); + await this.#storage.put(BLOCKED_GROUPS_ID, without(groupIds, groupId)); } public getBlockedData(): { diff --git a/ts/textsecure/storage/User.dom.ts b/ts/textsecure/storage/User.dom.ts index 724e95aacc..1a66c627f0 100644 --- a/ts/textsecure/storage/User.dom.ts +++ b/ts/textsecure/storage/User.dom.ts @@ -27,13 +27,17 @@ export type SetCredentialsOptions = { }; export class User { - constructor(private readonly storage: StorageInterface) {} + readonly #storage: StorageInterface; + + constructor(storage: StorageInterface) { + this.#storage = storage; + } public async setAciAndDeviceId( aci: AciString, deviceId: number ): Promise { - await this.storage.put('uuid_id', `${aci}.${deviceId}`); + await this.#storage.put('uuid_id', `${aci}.${deviceId}`); log.info('storage.user: aci and device id changed'); } @@ -52,8 +56,8 @@ export class User { log.info('storage.user: number changed'); await Promise.all([ - this.storage.put('number_id', `${number}.${deviceId}`), - this.storage.remove('senderCertificate'), + this.#storage.put('number_id', `${number}.${deviceId}`), + this.#storage.remove('senderCertificate'), ]); // Notify redux about phone number change @@ -61,7 +65,7 @@ export class User { } public getNumber(): string | undefined { - const numberId = this.storage.get('number_id'); + const numberId = this.#storage.get('number_id'); if (numberId === undefined) { return undefined; } @@ -69,7 +73,7 @@ export class User { } public getPni(): PniString | undefined { - const pni = this.storage.get('pni'); + const pni = this.#storage.get('pni'); if (pni === undefined || !isPniString(pni)) { return undefined; } @@ -77,7 +81,7 @@ export class User { } public getAci(): AciString | undefined { - const uuidId = this.storage.get('uuid_id'); + const uuidId = this.#storage.get('uuid_id'); if (!uuidId) { return undefined; } @@ -121,7 +125,7 @@ export class User { } public async setPni(pni: PniString): Promise { - await this.storage.put('pni', pni); + await this.#storage.put('pni', pni); } public getOurServiceIdKind(serviceId: ServiceIdString): ServiceIdKind { @@ -158,31 +162,31 @@ export class User { } public getDeviceCreatedAt(): number | undefined { - return this.storage.get('deviceCreatedAt'); + return this.#storage.get('deviceCreatedAt'); } public async setDeviceCreatedAt(createdAt: number): Promise { - return this.storage.put('deviceCreatedAt', createdAt); + return this.#storage.put('deviceCreatedAt', createdAt); } public getDeviceName(): string | undefined { - return this.storage.get('device_name'); + return this.#storage.get('device_name'); } public async setDeviceName(name: string): Promise { - return this.storage.put('device_name', name); + return this.#storage.put('device_name', name); } public async setDeviceNameEncrypted(): Promise { - return this.storage.put('deviceNameEncrypted', true); + return this.#storage.put('deviceNameEncrypted', true); } public getDeviceNameEncrypted(): boolean | undefined { - return this.storage.get('deviceNameEncrypted'); + return this.#storage.get('deviceNameEncrypted'); } public async removeSignalingKey(): Promise { - return this.storage.remove('signaling_key'); + return this.#storage.remove('signaling_key'); } public async setCredentials( @@ -191,9 +195,9 @@ export class User { const { aci, pni, number, deviceId, deviceName, password } = credentials; await Promise.all([ - this.storage.put('number_id', `${number}.${deviceId}`), - this.storage.put('uuid_id', `${aci}.${deviceId}`), - this.storage.put('password', password), + this.#storage.put('number_id', `${number}.${deviceId}`), + this.#storage.put('uuid_id', `${aci}.${deviceId}`), + this.#storage.put('password', password), this.setPni(pni), deviceName ? this.setDeviceName(deviceName) : Promise.resolve(), ]); @@ -203,23 +207,23 @@ export class User { log.info('storage.user: removeCredentials'); await Promise.all([ - this.storage.remove('number_id'), - this.storage.remove('uuid_id'), - this.storage.remove('password'), - this.storage.remove('device_name'), + this.#storage.remove('number_id'), + this.#storage.remove('uuid_id'), + this.#storage.remove('password'), + this.#storage.remove('device_name'), ]); } public getWebAPICredentials(): WebAPICredentials { return { username: - this.storage.get('uuid_id') || this.storage.get('number_id') || '', - password: this.storage.get('password', ''), + this.#storage.get('uuid_id') || this.#storage.get('number_id') || '', + password: this.#storage.get('password', ''), }; } #_getDeviceIdFromUuid(): string | undefined { - const uuid = this.storage.get('uuid_id'); + const uuid = this.#storage.get('uuid_id'); if (uuid === undefined) { return undefined; } @@ -227,7 +231,7 @@ export class User { } #_getDeviceIdFromNumber(): string | undefined { - const numberId = this.storage.get('number_id'); + const numberId = this.#storage.get('number_id'); if (numberId === undefined) { return undefined; } diff --git a/ts/types/Address.std.ts b/ts/types/Address.std.ts index 920228714e..b0531bbbc5 100644 --- a/ts/types/Address.std.ts +++ b/ts/types/Address.std.ts @@ -6,10 +6,13 @@ import type { ServiceIdString } from './ServiceId.std.ts'; export type AddressStringType = `${ServiceIdString}.${number}`; export class Address { - constructor( - public readonly serviceId: ServiceIdString, - public readonly deviceId: number - ) {} + public readonly serviceId: ServiceIdString; + public readonly deviceId: number; + + constructor(serviceId: ServiceIdString, deviceId: number) { + this.serviceId = serviceId; + this.deviceId = deviceId; + } public toString(): AddressStringType { return `${this.serviceId}.${this.deviceId}`; diff --git a/ts/types/BodyRange.std.ts b/ts/types/BodyRange.std.ts index 7f80e6774f..11fb299bbf 100644 --- a/ts/types/BodyRange.std.ts +++ b/ts/types/BodyRange.std.ts @@ -255,6 +255,7 @@ export function insertRange( ]; } + // oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions log.error(`MessageTextRenderer: unhandled range ${range}`); throw new Error('unhandled range'); } diff --git a/ts/types/LinkPreview.std.ts b/ts/types/LinkPreview.std.ts index b4909e2262..f6c91101c6 100644 --- a/ts/types/LinkPreview.std.ts +++ b/ts/types/LinkPreview.std.ts @@ -231,6 +231,7 @@ const VALID_URI_CHARACTERS = new Set([ ';', '=', // unreserved + // oxlint-disable-next-line typescript/no-misused-spread ...String.fromCharCode(...range(65, 91), ...range(97, 123)), ...range(10).map(String), '-', @@ -309,6 +310,7 @@ export function isLinkSneaky(href: string): boolean { const startOfPathAndHash = href.indexOf('/', url.protocol.length + 4); const pathAndHash = startOfPathAndHash === -1 ? '' : href.substr(startOfPathAndHash); + // oxlint-disable-next-line typescript/no-misused-spread return [...pathAndHash].some( character => !VALID_URI_CHARACTERS.has(character) ); diff --git a/ts/types/LocalExport.std.ts b/ts/types/LocalExport.std.ts index 19ad8b37e9..b3a273e111 100644 --- a/ts/types/LocalExport.std.ts +++ b/ts/types/LocalExport.std.ts @@ -12,14 +12,18 @@ export enum LocalExportErrors { } export class NotEnoughStorageError extends Error { - constructor(public readonly bytesNeeded: number) { + public readonly bytesNeeded: number; + constructor(bytesNeeded: number) { super('NotEnoughStorageError'); + this.bytesNeeded = bytesNeeded; } } // oxlint-disable-next-line max-classes-per-file export class RanOutOfStorageError extends Error { - constructor(public readonly bytesNeeded: number) { + public readonly bytesNeeded: number; + constructor(bytesNeeded: number) { super('RanOutOfStorageError'); + this.bytesNeeded = bytesNeeded; } } export class StoragePermissionsError extends Error { diff --git a/ts/types/Message2.preload.ts b/ts/types/Message2.preload.ts index 4e34d87c80..f512231baf 100644 --- a/ts/types/Message2.preload.ts +++ b/ts/types/Message2.preload.ts @@ -846,7 +846,7 @@ export const processNewSticker = async ( } const local = await writeNewStickerData(stickerData); - const url = await getLocalAttachmentUrl(local, { + const url = getLocalAttachmentUrl(local, { disposition: isEphemeral ? AttachmentDisposition.Temporary : AttachmentDisposition.Sticker, diff --git a/ts/types/QualifiedAddress.std.ts b/ts/types/QualifiedAddress.std.ts index 31d4825d75..2eda0c114c 100644 --- a/ts/types/QualifiedAddress.std.ts +++ b/ts/types/QualifiedAddress.std.ts @@ -26,10 +26,13 @@ export type QualifiedAddressStringType = `${ServiceIdString}:${AddressStringType}`; export class QualifiedAddress { - constructor( - public readonly ourServiceId: ServiceIdString, - public readonly address: Address - ) {} + public readonly ourServiceId: ServiceIdString; + public readonly address: Address; + + constructor(ourServiceId: ServiceIdString, address: Address) { + this.ourServiceId = ourServiceId; + this.address = address; + } public get serviceId(): ServiceIdString { return this.address.serviceId; diff --git a/ts/types/SchemaVersion.std.ts b/ts/types/SchemaVersion.std.ts index 3ebff0983c..cf84b37004 100644 --- a/ts/types/SchemaVersion.std.ts +++ b/ts/types/SchemaVersion.std.ts @@ -6,5 +6,5 @@ import lodash from 'lodash'; const { isNumber } = lodash; export const isValid = (value: unknown): value is number => { - return Boolean(isNumber(value) && value >= 0); + return isNumber(value) && value >= 0; }; diff --git a/ts/types/Stickers.preload.ts b/ts/types/Stickers.preload.ts index e4fb7a351b..9eea87a441 100644 --- a/ts/types/Stickers.preload.ts +++ b/ts/types/Stickers.preload.ts @@ -934,7 +934,7 @@ async function doDownloadStickerPack( if (existingStatus === 'installed') { // No-op } else if (finalStatus === 'installed') { - await installStickerPack(packId, packKey, { + installStickerPack(packId, packKey, { actionSource, }); } else { diff --git a/ts/types/Util.std.ts b/ts/types/Util.std.ts index ff16fadab5..824e33e6d6 100644 --- a/ts/types/Util.std.ts +++ b/ts/types/Util.std.ts @@ -21,7 +21,7 @@ export type RenderTextCallbackType = (options: { key: number; }) => React.JSX.Element | string; -export { ICUJSXMessageParamsByKeyType, ICUStringMessageParamsByKeyType }; +export type { ICUJSXMessageParamsByKeyType, ICUStringMessageParamsByKeyType }; export type LocalizerOptions = { /** diff --git a/ts/updater/common.main.ts b/ts/updater/common.main.ts index 8e0f1a1029..37f50406ec 100644 --- a/ts/updater/common.main.ts +++ b/ts/updater/common.main.ts @@ -143,7 +143,7 @@ export abstract class Updater { protected readonly getMainWindow: () => BrowserWindow | undefined; - #throttledSendDownloadingUpdate: (( + readonly #throttledSendDownloadingUpdate: (( downloadedSize: number, downloadSize: number ) => void) & { @@ -159,7 +159,7 @@ export abstract class Updater { // Just a stable randomness that is used for determining the update time. The // value does not have to be consistent across restarts. - #pollId = getGuid(); + readonly #pollId = getGuid(); constructor({ canRunSilently, diff --git a/ts/updater/differential.main.ts b/ts/updater/differential.main.ts index ccad2cd00e..c44390906e 100644 --- a/ts/updater/differential.main.ts +++ b/ts/updater/differential.main.ts @@ -476,7 +476,7 @@ async function takeDiffFromPart( | null; strictAssert( match, - `Invalid Content-Range header for the part: "${contentRange}"` + `Invalid Content-Range header for the part: "${contentRange.join(', ')}"` ); const range = match[1]; diff --git a/ts/util/AbortableProcess.std.ts b/ts/util/AbortableProcess.std.ts index fecc309b3a..b7f2f8a284 100644 --- a/ts/util/AbortableProcess.std.ts +++ b/ts/util/AbortableProcess.std.ts @@ -8,15 +8,21 @@ export type IController = { }; export class AbortableProcess implements IController { - #abortReject: (error: Error) => void; + readonly #name: string; + readonly #controller: IController; + + readonly #abortReject: (error: Error) => void; public readonly resultPromise: Promise; constructor( - private readonly name: string, - private readonly controller: IController, + name: string, + controller: IController, resultPromise: Promise ) { + this.#name = name; + this.#controller = controller; + const { promise: abortPromise, reject: abortReject } = explodePromise(); @@ -25,8 +31,8 @@ export class AbortableProcess implements IController { } public abort(): void { - this.controller.abort(); - this.#abortReject(new Error(`Process "${this.name}" was aborted`)); + this.#controller.abort(); + this.#abortReject(new Error(`Process "${this.#name}" was aborted`)); } public getResult(): Promise { diff --git a/ts/util/Attachment.std.ts b/ts/util/Attachment.std.ts index 5a5e989414..efbc5ed0f4 100644 --- a/ts/util/Attachment.std.ts +++ b/ts/util/Attachment.std.ts @@ -207,7 +207,7 @@ export function getExtensionForDisplay({ fileName?: string; contentType: MIME.MIMEType; }): string | undefined { - if (fileName && fileName.indexOf('.') >= 0) { + if (fileName && fileName.includes('.')) { const lastPeriod = fileName.lastIndexOf('.'); const extension = fileName.slice(lastPeriod + 1); if (extension.length) { @@ -933,8 +933,8 @@ export function partitionBodyAndNormalAttachments< }; } -const MESSAGE_ATTACHMENT_TYPES_NEEDING_THUMBNAILS: Set = - new Set(['attachment', 'sticker']); +const MESSAGE_ATTACHMENT_TYPES_NEEDING_THUMBNAILS = + new Set(['attachment', 'sticker']); export function shouldGenerateThumbnailForAttachmentType( type: MessageAttachmentType diff --git a/ts/util/BackOff.std.ts b/ts/util/BackOff.std.ts index 3c7fffd49b..d36fcef2cd 100644 --- a/ts/util/BackOff.std.ts +++ b/ts/util/BackOff.std.ts @@ -38,17 +38,22 @@ export type BackOffOptionsType = Readonly<{ const DEFAULT_RANDOM = () => Math.random(); export class BackOff { + #timeouts: ReadonlyArray; + readonly #options: BackOffOptionsType; #count = 0; constructor( - private timeouts: ReadonlyArray, - private readonly options: BackOffOptionsType = {} - ) {} + timeouts: ReadonlyArray, + options: BackOffOptionsType = {} + ) { + this.#timeouts = timeouts; + this.#options = options; + } public get(): number { // oxlint-disable-next-line typescript/no-non-null-assertion - let result = this.timeouts[this.#count]!; - const { jitter = 0, random = DEFAULT_RANDOM } = this.options; + let result = this.#timeouts[this.#count]!; + const { jitter = 0, random = DEFAULT_RANDOM } = this.#options; // Do not apply jitter larger than the timeout value. It is supposed to be // activated for longer timeouts. @@ -69,13 +74,13 @@ export class BackOff { public reset(newTimeouts?: ReadonlyArray): void { if (newTimeouts !== undefined) { - this.timeouts = newTimeouts; + this.#timeouts = newTimeouts; } this.#count = 0; } public isFull(): boolean { - return this.#count === this.timeouts.length - 1; + return this.#count === this.#timeouts.length - 1; } public getIndex(): number { diff --git a/ts/util/CheckScheduler.preload.ts b/ts/util/CheckScheduler.preload.ts index 911d259fef..01572e5f6d 100644 --- a/ts/util/CheckScheduler.preload.ts +++ b/ts/util/CheckScheduler.preload.ts @@ -23,8 +23,8 @@ export type CheckSchedulerOptionsType = Readonly<{ }>; export class CheckScheduler { - #options: CheckSchedulerOptionsType; - #log: ReturnType; + readonly #options: CheckSchedulerOptionsType; + readonly #log: ReturnType; #timer: LongTimeout | undefined; #isRunning = false; diff --git a/ts/util/MemoryStream.node.ts b/ts/util/MemoryStream.node.ts index ed9e1c24ed..3e274f2977 100644 --- a/ts/util/MemoryStream.node.ts +++ b/ts/util/MemoryStream.node.ts @@ -4,14 +4,16 @@ import { InputStream } from '@signalapp/libsignal-client/dist/io.js'; export class MemoryStream extends InputStream { + readonly #buffer: Uint8Array; #offset = 0; - constructor(private readonly buffer: Uint8Array) { + constructor(buffer: Uint8Array) { super(); + this.#buffer = buffer; } public override async read(amount: number): Promise> { - const result = this.buffer.subarray(this.#offset, this.#offset + amount); + const result = this.#buffer.subarray(this.#offset, this.#offset + amount); this.#offset += amount; return result; } diff --git a/ts/util/StartupQueue.std.ts b/ts/util/StartupQueue.std.ts index 84147db262..1bd1525c14 100644 --- a/ts/util/StartupQueue.std.ts +++ b/ts/util/StartupQueue.std.ts @@ -41,7 +41,7 @@ export class StartupQueue { for (const { callback } of values) { void this.#running.add(async () => { try { - return callback(); + return await callback(); } catch (error) { log.error( 'Failed to process item due to error', diff --git a/ts/util/TaskDeduplicator.std.ts b/ts/util/TaskDeduplicator.std.ts index b404501496..bbe07a1dff 100644 --- a/ts/util/TaskDeduplicator.std.ts +++ b/ts/util/TaskDeduplicator.std.ts @@ -19,15 +19,17 @@ import { explodePromise } from './explodePromise.std.ts'; // await task.run(otherAbortSignal); // export class TaskDeduplicator { - #task: (abortSignal: AbortSignal) => Promise; + readonly #name: string; + readonly #task: (abortSignal: AbortSignal) => Promise; #current: Promise | undefined; #remaining = 0; #abortController: AbortController | undefined; constructor( - public readonly name: string, + name: string, task: (abortSignal: AbortSignal) => Promise ) { + this.#name = name; this.#task = task; } @@ -42,7 +44,7 @@ export class TaskDeduplicator { if (this.#remaining === 0) { strictAssert( this.#abortController != null, - `TaskDeduplicator(${this.name}): missing abort controller` + `TaskDeduplicator(${this.#name}): missing abort controller` ); this.#abortController.abort(); } diff --git a/ts/util/Username.dom.ts b/ts/util/Username.dom.ts index a8be76bd34..15ceefac85 100644 --- a/ts/util/Username.dom.ts +++ b/ts/util/Username.dom.ts @@ -42,10 +42,10 @@ export function isUsernameValid(username: string): boolean { if (!discriminator || discriminator.length === 0) { return false; } - if (discriminator[0] === '0' && discriminator[1] === '0') { + if (discriminator.startsWith('0') && discriminator[1] === '0') { return false; } - if (discriminator[0] === '0' && discriminator.length !== 2) { + if (discriminator.startsWith('0') && discriminator.length !== 2) { return false; } diff --git a/ts/util/Zone.std.ts b/ts/util/Zone.std.ts index f65541f268..5029d41cbb 100644 --- a/ts/util/Zone.std.ts +++ b/ts/util/Zone.std.ts @@ -10,28 +10,31 @@ export type ZoneOptions = { }; export class Zone { - constructor( - public readonly name: string, - private readonly options: ZoneOptions = {} - ) {} + public readonly name: string; + readonly #options: ZoneOptions = {}; + + constructor(name: string, options: ZoneOptions = {}) { + this.name = name; + this.#options = options; + } public supportsPendingKyberPreKeysToRemove(): boolean { - return this.options.pendingKyberPreKeysToRemove === true; + return this.#options.pendingKyberPreKeysToRemove === true; } public supportsPendingPreKeysToRemove(): boolean { - return this.options.pendingPreKeysToRemove === true; + return this.#options.pendingPreKeysToRemove === true; } public supportsPendingSenderKeys(): boolean { - return this.options.pendingSenderKeys === true; + return this.#options.pendingSenderKeys === true; } public supportsPendingSessions(): boolean { - return this.options.pendingSessions === true; + return this.#options.pendingSessions === true; } public supportsPendingUnprocessed(): boolean { - return this.options.pendingUnprocessed === true; + return this.#options.pendingUnprocessed === true; } } diff --git a/ts/util/asyncIterables.std.ts b/ts/util/asyncIterables.std.ts index d4d1561bae..d9a6d78170 100644 --- a/ts/util/asyncIterables.std.ts +++ b/ts/util/asyncIterables.std.ts @@ -10,10 +10,14 @@ export function concat( } class ConcatAsyncIterable implements AsyncIterable { - constructor(private readonly iterables: Iterable>) {} + readonly #iterables: Iterable>; + + constructor(iterables: Iterable>) { + this.#iterables = iterables; + } async *[Symbol.asyncIterator](): AsyncIterator { - for (const iterable of this.iterables) { + for (const iterable of this.#iterables) { // oxlint-disable-next-line no-await-in-loop for await (const value of iterable) { yield value; @@ -30,10 +34,14 @@ export function wrapPromise( // oxlint-disable-next-line max-classes-per-file class WrapPromiseAsyncIterable implements AsyncIterable { - constructor(private readonly promise: Promise>) {} + readonly #promise: Promise>; + + constructor(promise: Promise>) { + this.#promise = promise; + } async *[Symbol.asyncIterator](): AsyncIterator { - for await (const value of await this.promise) { + for await (const value of await this.#promise) { yield value; } } diff --git a/ts/util/attachmentDownloadQueue.preload.ts b/ts/util/attachmentDownloadQueue.preload.ts index b947285a17..01f141204a 100644 --- a/ts/util/attachmentDownloadQueue.preload.ts +++ b/ts/util/attachmentDownloadQueue.preload.ts @@ -22,7 +22,7 @@ const MAX_ATTACHMENT_MSGS_TO_DOWNLOAD = 250; let isEnabled = true; let attachmentDownloadQueue: Array | undefined = []; -const queueEmptyCallbacks: Set<() => void> = new Set(); +const queueEmptyCallbacks = new Set<() => void>(); export function shouldUseAttachmentDownloadQueue(): boolean { return isEnabled; diff --git a/ts/util/benchmark/stats.std.ts b/ts/util/benchmark/stats.std.ts index 8f7981ce92..3670fa766e 100644 --- a/ts/util/benchmark/stats.std.ts +++ b/ts/util/benchmark/stats.std.ts @@ -88,10 +88,11 @@ export function regress(samples: ReadonlyArray): Regression { // Within each iteration bin identify the outliers for reporting purposes. for (const [, ys] of bins) { + // oxlint-disable-next-line typescript/require-array-sort-compare ys.sort(); const p25 = ys[Math.floor(ys.length * 0.25)] ?? -Infinity; - const p75 = ys[Math.ceil(ys.length * 0.75)] ?? +Infinity; + const p75 = ys[Math.ceil(ys.length * 0.75)] ?? Infinity; const iqr = p75 - p25; const outlierLow = p25 - iqr * 1.5; diff --git a/ts/util/callDisposition.preload.ts b/ts/util/callDisposition.preload.ts index b19ab5b188..c3e05ca685 100644 --- a/ts/util/callDisposition.preload.ts +++ b/ts/util/callDisposition.preload.ts @@ -145,11 +145,11 @@ export function formatLocalDeviceState( } export function getCallIdFromRing(ringId: bigint): string { - return BigInt(callIdFromRingId(ringId)).toString(); + return callIdFromRingId(ringId).toString(); } export function getCallIdFromEra(eraId: string): string { - return BigInt(callIdFromEra(eraId)).toString(); + return callIdFromEra(eraId).toString(); } export function getCreatorAci(creator: Uint8Array): AciString { diff --git a/ts/util/callingMessageToProto.node.ts b/ts/util/callingMessageToProto.node.ts index 123ca19f6f..cdb60a4e33 100644 --- a/ts/util/callingMessageToProto.node.ts +++ b/ts/util/callingMessageToProto.node.ts @@ -25,6 +25,7 @@ export function callingMessageToProto( let opaqueField: undefined | Proto.CallMessage.Opaque.Params; if (opaque) { opaqueField = { + // oxlint-disable-next-line typescript/no-misused-spread ...opaque, urgency: null, data: opaque.data != null ? opaqueToBytes(opaque.data) : null, @@ -40,6 +41,7 @@ export function callingMessageToProto( return { offer: offer ? { + // oxlint-disable-next-line typescript/no-misused-spread ...offer, id: offer.callId, type: offer.type as number, @@ -48,6 +50,7 @@ export function callingMessageToProto( : null, answer: answer ? { + // oxlint-disable-next-line typescript/no-misused-spread ...answer, id: answer.callId, opaque: opaqueToBytes(answer.opaque), @@ -56,6 +59,7 @@ export function callingMessageToProto( iceUpdate: iceCandidates ? iceCandidates.map((candidate): Proto.CallMessage.IceUpdate.Params => { return { + // oxlint-disable-next-line typescript/no-misused-spread ...candidate, id: candidate.callId, opaque: opaqueToBytes(candidate.opaque), @@ -64,12 +68,14 @@ export function callingMessageToProto( : null, busy: busy ? { + // oxlint-disable-next-line typescript/no-misused-spread ...busy, id: busy.callId, } : null, hangup: hangup ? { + // oxlint-disable-next-line typescript/no-misused-spread ...hangup, id: hangup.callId, type: hangup.type as number, diff --git a/ts/util/checkOurPniIdentityKey.preload.ts b/ts/util/checkOurPniIdentityKey.preload.ts index 4b7dfef3f7..865b73b243 100644 --- a/ts/util/checkOurPniIdentityKey.preload.ts +++ b/ts/util/checkOurPniIdentityKey.preload.ts @@ -18,7 +18,7 @@ export async function checkOurPniIdentityKey(): Promise { return; } - const localKeyPair = await signalProtocolStore.getIdentityKeyPair(ourPni); + const localKeyPair = signalProtocolStore.getIdentityKeyPair(ourPni); if (!localKeyPair) { log.warn(`no local key pair for ${ourPni}, unlinking`); window.Whisper.events.emit('unlinkAndDisconnect'); diff --git a/ts/util/cleanup.preload.ts b/ts/util/cleanup.preload.ts index 340aebe4a9..b97e31e859 100644 --- a/ts/util/cleanup.preload.ts +++ b/ts/util/cleanup.preload.ts @@ -44,8 +44,8 @@ import type { AttachmentType } from '../types/Attachment.std.ts'; const log = createLogger('cleanup'); export async function postSaveUpdates(): Promise { - await updateExpiringMessagesService(); - await tapToViewMessagesDeletionService.update(); + updateExpiringMessagesService(); + tapToViewMessagesDeletionService.update(); } export async function eraseMessageContents( diff --git a/ts/util/computeBlurHashUrl.std.ts b/ts/util/computeBlurHashUrl.std.ts index 299f9e4ae0..da6cad6346 100644 --- a/ts/util/computeBlurHashUrl.std.ts +++ b/ts/util/computeBlurHashUrl.std.ts @@ -118,11 +118,11 @@ export function computeBlurHashUrl( ) { // BMP uses BGR ordering // oxlint-disable-next-line typescript/no-non-null-assertion - bitmap[j + 2]! = rgba[i]!; + bitmap[j + 2] = rgba[i]!; // oxlint-disable-next-line typescript/no-non-null-assertion - bitmap[j + 1]! = rgba[i + 1]!; + bitmap[j + 1] = rgba[i + 1]!; // oxlint-disable-next-line typescript/no-non-null-assertion - bitmap[j]! = rgba[i + 2]!; + bitmap[j] = rgba[i + 2]!; } return `data:image/bmp;base64,${Bytes.toBase64(bitmap)}`; diff --git a/ts/util/desktopCapturer.preload.ts b/ts/util/desktopCapturer.preload.ts index bafef9565f..eae57a4c14 100644 --- a/ts/util/desktopCapturer.preload.ts +++ b/ts/util/desktopCapturer.preload.ts @@ -89,6 +89,7 @@ export type DesktopCapturerBaton = Readonly<{ // oxlint-disable-next-line max-classes-per-file export class DesktopCapturer { + readonly #options: DesktopCapturerOptionsType; #state: State; private static getDisplayMediaPromise: Promise | undefined; @@ -98,7 +99,9 @@ export class DesktopCapturer { // For use as a key in weak maps public readonly baton = {} as DesktopCapturerBaton; - constructor(private readonly options: DesktopCapturerOptionsType) { + constructor(options: DesktopCapturerOptionsType) { + this.#options = options; + if (!DesktopCapturer.isInitialized) { DesktopCapturer.initialize(); } @@ -183,7 +186,7 @@ export class DesktopCapturer { >(); this.#state = { step: Step.SelectingSource, promise, sources, onSource }; - this.options.onPresentableSources(presentableSources); + this.#options.onPresentableSources(presentableSources); return source; } @@ -226,7 +229,7 @@ export class DesktopCapturer { `Invalid state in "getStream.success" ${this.#state.step}` ); - this.options.onMediaStream(stream); + this.#options.onMediaStream(stream); this.#state = { step: Step.Done }; } catch (error) { strictAssert( @@ -234,7 +237,7 @@ export class DesktopCapturer { this.#state.step === Step.SelectedSource, `Invalid state in "getStream.error" ${this.#state.step}` ); - this.options.onError(error); + this.#options.onError(error); this.#state = { step: Step.Error }; } finally { liveCapturers.delete(this); @@ -288,7 +291,7 @@ export class DesktopCapturer { } }, SECOND); - this.options.onMediaStream(mediaStream); + this.#options.onMediaStream(mediaStream); }, onStop() { if (!isRunning) { @@ -327,7 +330,7 @@ export class DesktopCapturer { } #translateSourceName(source: DesktopCapturerSource): string { - const { i18n } = this.options; + const { i18n } = this.#options; const { name } = source; if (!isScreenSource(source)) { diff --git a/ts/util/durations/duration-in-seconds.std.ts b/ts/util/durations/duration-in-seconds.std.ts index 0e84545672..a53b81b32d 100644 --- a/ts/util/durations/duration-in-seconds.std.ts +++ b/ts/util/durations/duration-in-seconds.std.ts @@ -29,9 +29,9 @@ export namespace DurationInSeconds { export const toHours = (d: DurationInSeconds): number => (d * Constants.SECOND) / Constants.HOUR; - export const ZERO = DurationInSeconds.fromSeconds(0); - export const HOUR = DurationInSeconds.fromHours(1); - export const MINUTE = DurationInSeconds.fromMinutes(1); - export const DAY = DurationInSeconds.fromDays(1); - export const WEEK = DurationInSeconds.fromWeeks(1); + export const ZERO = fromSeconds(0); + export const HOUR = fromHours(1); + export const MINUTE = fromMinutes(1); + export const DAY = fromDays(1); + export const WEEK = fromWeeks(1); } diff --git a/ts/util/filterAndSortConversations.std.ts b/ts/util/filterAndSortConversations.std.ts index a26949d762..b514f146de 100644 --- a/ts/util/filterAndSortConversations.std.ts +++ b/ts/util/filterAndSortConversations.std.ts @@ -174,7 +174,7 @@ export function filterAndSortConversations( conversations: ReadonlyArray, searchTerm: string, regionCode: string | undefined, - filterByUnread: boolean = false, + filterByUnread = false, conversationToInject?: ConversationType ): Array { let filteredConversations = filterByUnread diff --git a/ts/util/fuse.std.ts b/ts/util/fuse.std.ts index 079671b606..47691bb0a0 100644 --- a/ts/util/fuse.std.ts +++ b/ts/util/fuse.std.ts @@ -5,10 +5,10 @@ import Fuse from 'fuse.js'; import { removeDiacritics } from './removeDiacritics.std.ts'; -const cachedIndices: Map< +const cachedIndices = new Map< Fuse.IFuseOptions, WeakMap, Fuse> -> = new Map(); +>(); export function getCachedFuseIndex( list: ReadonlyArray, diff --git a/ts/util/generateBlurHash.std.ts b/ts/util/generateBlurHash.std.ts index 9b6c9c4afe..15d941bc83 100644 --- a/ts/util/generateBlurHash.std.ts +++ b/ts/util/generateBlurHash.std.ts @@ -7,7 +7,7 @@ import { encode } from 'blurhash'; * * The color is specified as an ARGB value, where the alpha channel is ignored. */ -export function generateBlurHash(argb: number = 0xff_fbfbfb): string { +export function generateBlurHash(argb = 0xff_fbfbfb): string { return encode( new Uint8ClampedArray([ // Flipping from argb to rgba diff --git a/ts/util/getMutedUntilText.std.ts b/ts/util/getMutedUntilText.std.ts index e554c5a702..04a9dadf02 100644 --- a/ts/util/getMutedUntilText.std.ts +++ b/ts/util/getMutedUntilText.std.ts @@ -14,7 +14,7 @@ export function getMutedUntilText( muteExpiresAt: number, i18n: LocalizerType ): string { - if (Number(muteExpiresAt) >= Number.MAX_SAFE_INTEGER) { + if (muteExpiresAt >= Number.MAX_SAFE_INTEGER) { return i18n('icu:muteExpirationLabelAlways'); } diff --git a/ts/util/getSendOptions.preload.ts b/ts/util/getSendOptions.preload.ts index a16119a40d..7069c3a34f 100644 --- a/ts/util/getSendOptions.preload.ts +++ b/ts/util/getSendOptions.preload.ts @@ -90,9 +90,7 @@ export async function getSendOptions( const { accessKey } = conversationAttrs; const { e164, serviceId } = conversationAttrs; - let sealedSender = conversationAttrs.sealedSender as - | SEALED_SENDER - | undefined; + let sealedSender = conversationAttrs.sealedSender; const senderCertificate = await getSenderCertificateForDirectConversation(conversationAttrs); diff --git a/ts/util/getServiceIdsForE164s.dom.ts b/ts/util/getServiceIdsForE164s.dom.ts index 83cff69877..4059ca45f4 100644 --- a/ts/util/getServiceIdsForE164s.dom.ts +++ b/ts/util/getServiceIdsForE164s.dom.ts @@ -102,7 +102,7 @@ export async function getServiceIdsForE164s( const expandedE164sArray = Array.from(expandedE164s); log.info( - `getServiceIdsForE164s(${expandedE164sArray}): acis=${acisAndAccessKeys.length} ` + + `getServiceIdsForE164s(${expandedE164sArray.join(', ')}): acis=${acisAndAccessKeys.length} ` + `accessKeys=${acisAndAccessKeys.length}` ); const response = await doCdsLookup({ diff --git a/ts/util/getTitle.preload.ts b/ts/util/getTitle.preload.ts index 257cdf8af3..a3e45f3140 100644 --- a/ts/util/getTitle.preload.ts +++ b/ts/util/getTitle.preload.ts @@ -78,7 +78,6 @@ export function canHaveUsername( | 'systemNickname' | 'nicknameGivenName' | 'nicknameFamilyName' - | 'type' >, ourConversationId: string | undefined ): boolean { diff --git a/ts/util/groupSendEndorsements.preload.ts b/ts/util/groupSendEndorsements.preload.ts index e3137a1b63..f506698de4 100644 --- a/ts/util/groupSendEndorsements.preload.ts +++ b/ts/util/groupSendEndorsements.preload.ts @@ -162,16 +162,16 @@ export function validateGroupSendEndorsementsExpiration( } export class GroupSendEndorsementState { - #logId: string; - #combinedEndorsement: GroupSendCombinedEndorsementRecord; - #memberEndorsements = new Map< + readonly #logId: string; + readonly #combinedEndorsement: GroupSendCombinedEndorsementRecord; + readonly #memberEndorsements = new Map< ServiceIdString, GroupSendMemberEndorsementRecord >(); - #memberEndorsementsAcis = new Set(); - #groupSecretParamsBase64: string; - #ourAci: ServiceIdString; - #endorsementCache = new WeakMap< + readonly #memberEndorsementsAcis = new Set(); + readonly #groupSecretParamsBase64: string; + readonly #ourAci: ServiceIdString; + readonly #endorsementCache = new WeakMap< Uint8Array, GroupSendEndorsement >(); diff --git a/ts/util/handleEditMessage.preload.ts b/ts/util/handleEditMessage.preload.ts index a6391045be..b260aeb6c5 100644 --- a/ts/util/handleEditMessage.preload.ts +++ b/ts/util/handleEditMessage.preload.ts @@ -137,7 +137,7 @@ export async function handleEditMessage( editAttributes.message ); - const previewSignatures: Map = new Map(); + const previewSignatures = new Map(); mainMessage.preview?.forEach(preview => { if (!preview.image) { return; diff --git a/ts/util/handleRetry.preload.ts b/ts/util/handleRetry.preload.ts index 252cc4c50e..fb4e69948f 100644 --- a/ts/util/handleRetry.preload.ts +++ b/ts/util/handleRetry.preload.ts @@ -330,7 +330,7 @@ export async function handleDecryptionError( if (RemoteConfig.isEnabled('desktop.senderKey.retry')) { await requestResend(decryptionError); } else { - await startAutomaticSessionReset(decryptionError); + startAutomaticSessionReset(decryptionError); } confirm(); diff --git a/ts/util/isConversationUnread.std.ts b/ts/util/isConversationUnread.std.ts index 67b4f01786..ea4e1287e0 100644 --- a/ts/util/isConversationUnread.std.ts +++ b/ts/util/isConversationUnread.std.ts @@ -11,5 +11,4 @@ export const isConversationUnread = ({ }: Readonly<{ unreadCount?: number; markedUnread?: boolean; -}>): boolean => - Boolean(markedUnread || (isNumber(unreadCount) && unreadCount > 0)); +}>): boolean => markedUnread || (isNumber(unreadCount) && unreadCount > 0); diff --git a/ts/util/isKnownProtoEnumMember.std.ts b/ts/util/isKnownProtoEnumMember.std.ts index 5c6c953c2d..bd145697f9 100644 --- a/ts/util/isKnownProtoEnumMember.std.ts +++ b/ts/util/isKnownProtoEnumMember.std.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only export function isKnownProtoEnumMember( + // oxlint-disable-next-line typescript/no-redundant-type-constituents enum_: Record, value: unknown ): value is E { diff --git a/ts/util/iterables.std.ts b/ts/util/iterables.std.ts index 7074e6e4a0..59a38fc150 100644 --- a/ts/util/iterables.std.ts +++ b/ts/util/iterables.std.ts @@ -35,10 +35,14 @@ export function concat( } class ConcatIterable implements Iterable { - constructor(private readonly iterables: ReadonlyArray>) {} + readonly #iterables: ReadonlyArray>; + + constructor(iterables: ReadonlyArray>) { + this.#iterables = iterables; + } *[Symbol.iterator](): Iterator { - for (const iterable of this.iterables) { + for (const iterable of this.#iterables) { yield* iterable; } } @@ -73,27 +77,36 @@ export function filter( // oxlint-disable-next-line max-classes-per-file class FilterIterable implements Iterable { - constructor( - private readonly iterable: Iterable, - private readonly predicate: (value: T) => unknown - ) {} + readonly #iterable: Iterable; + readonly #predicate: (value: T) => unknown; + + constructor(iterable: Iterable, predicate: (value: T) => unknown) { + this.#iterable = iterable; + this.#predicate = predicate; + } [Symbol.iterator](): Iterator { - return new FilterIterator(this.iterable[Symbol.iterator](), this.predicate); + return new FilterIterator( + this.#iterable[Symbol.iterator](), + this.#predicate + ); } } class FilterIterator implements Iterator { - constructor( - private readonly iterator: Iterator, - private readonly predicate: (value: T) => unknown - ) {} + readonly #iterator: Iterator; + readonly #predicate: (value: T) => unknown; + + constructor(iterator: Iterator, predicate: (value: T) => unknown) { + this.#iterator = iterator; + this.#predicate = predicate; + } next(): IteratorResult { // oxlint-disable-next-line no-constant-condition while (true) { - const nextIteration = this.iterator.next(); - if (nextIteration.done || this.predicate(nextIteration.value)) { + const nextIteration = this.#iterator.next(); + if (nextIteration.done || this.#predicate(nextIteration.value)) { return nextIteration; } } @@ -122,30 +135,36 @@ export function collectFirst( } class CollectIterable implements Iterable { - constructor( - private readonly iterable: Iterable, - private readonly fn: (value: T) => S | undefined - ) {} + readonly #iterable: Iterable; + readonly #fn: (value: T) => S | undefined; + + constructor(iterable: Iterable, fn: (value: T) => S | undefined) { + this.#iterable = iterable; + this.#fn = fn; + } [Symbol.iterator](): Iterator { - return new CollectIterator(this.iterable[Symbol.iterator](), this.fn); + return new CollectIterator(this.#iterable[Symbol.iterator](), this.#fn); } } class CollectIterator implements Iterator { - constructor( - private readonly iterator: Iterator, - private readonly fn: (value: T) => S | undefined - ) {} + readonly #iterator: Iterator; + readonly #fn: (value: T) => S | undefined; + + constructor(iterator: Iterator, fn: (value: T) => S | undefined) { + this.#iterator = iterator; + this.#fn = fn; + } next(): IteratorResult { // oxlint-disable-next-line no-constant-condition while (true) { - const nextIteration = this.iterator.next(); + const nextIteration = this.#iterator.next(); if (nextIteration.done) { return nextIteration; } - const nextValue = this.fn(nextIteration.value); + const nextValue = this.#fn(nextIteration.value); if (nextValue !== undefined) { return { done: false, @@ -192,6 +211,7 @@ export function join(iterable: Iterable, separator: string): string { let hasProcessedFirst = false; let result = ''; for (const value of iterable) { + // oxlint-disable-next-line typescript/no-base-to-string const stringifiedValue = value == null ? '' : String(value); if (hasProcessedFirst) { result += separator + stringifiedValue; @@ -211,30 +231,36 @@ export function map( } class MapIterable implements Iterable { - constructor( - private readonly iterable: Iterable, - private readonly fn: (value: T) => ResultT - ) {} + readonly #iterable: Iterable; + readonly #fn: (value: T) => ResultT; + + constructor(iterable: Iterable, fn: (value: T) => ResultT) { + this.#iterable = iterable; + this.#fn = fn; + } [Symbol.iterator](): Iterator { - return new MapIterator(this.iterable[Symbol.iterator](), this.fn); + return new MapIterator(this.#iterable[Symbol.iterator](), this.#fn); } } class MapIterator implements Iterator { - constructor( - private readonly iterator: Iterator, - private readonly fn: (value: T) => ResultT - ) {} + readonly #iterator: Iterator; + readonly #fn: (value: T) => ResultT; + + constructor(iterator: Iterator, fn: (value: T) => ResultT) { + this.#iterator = iterator; + this.#fn = fn; + } next(): IteratorResult { - const nextIteration = this.iterator.next(); + const nextIteration = this.#iterator.next(); if (nextIteration.done) { return nextIteration; } return { done: false, - value: this.fn(nextIteration.value), + value: this.#fn(nextIteration.value), }; } } @@ -273,10 +299,14 @@ export function* chunk( } class RepeatIterable implements Iterable { - constructor(private readonly value: T) {} + readonly #value: T; + + constructor(value: T) { + this.#value = value; + } [Symbol.iterator](): Iterator { - return new RepeatIterator(this.value); + return new RepeatIterator(this.#value); } } @@ -300,28 +330,34 @@ export function take(iterable: Iterable, amount: number): Iterable { } class TakeIterable implements Iterable { - constructor( - private readonly iterable: Iterable, - private readonly amount: number - ) {} + readonly #iterable: Iterable; + readonly #amount: number; + + constructor(iterable: Iterable, amount: number) { + this.#iterable = iterable; + this.#amount = amount; + } [Symbol.iterator](): Iterator { - return new TakeIterator(this.iterable[Symbol.iterator](), this.amount); + return new TakeIterator(this.#iterable[Symbol.iterator](), this.#amount); } } class TakeIterator implements Iterator { - constructor( - private readonly iterator: Iterator, - private amount: number - ) {} + readonly #iterator: Iterator; + #amount: number; + + constructor(iterator: Iterator, amount: number) { + this.#iterator = iterator; + this.#amount = amount; + } next(): IteratorResult { - const nextIteration = this.iterator.next(); - if (nextIteration.done || this.amount === 0) { + const nextIteration = this.#iterator.next(); + if (nextIteration.done || this.#amount === 0) { return { done: true, value: undefined }; } - this.amount -= 1; + this.#amount -= 1; return nextIteration; } } diff --git a/ts/util/lint/license_comments.node.ts b/ts/util/lint/license_comments.node.ts index 1bd18515d7..ef76139e4b 100644 --- a/ts/util/lint/license_comments.node.ts +++ b/ts/util/lint/license_comments.node.ts @@ -205,6 +205,7 @@ async function main() { `Expected: "Copyright ${commit.commitYear} Signal Messenger, LLC"` ) ), + // oxlint-disable-next-line typescript/restrict-template-expressions indent(chalk.yellow(`Actual: "${firstLine}"`)), indent( chalk.italic.dim( @@ -230,6 +231,7 @@ async function main() { indent( chalk.green('Expected: "SPDX-License-Identifier: AGPL-3.0-only"') ), + // oxlint-disable-next-line typescript/restrict-template-expressions indent(chalk.yellow(`Actual: "${secondLine}"`)) ); } diff --git a/ts/util/markConversationRead.preload.ts b/ts/util/markConversationRead.preload.ts index 6e488f8f02..8a2a8ed7c8 100644 --- a/ts/util/markConversationRead.preload.ts +++ b/ts/util/markConversationRead.preload.ts @@ -260,8 +260,8 @@ export async function markConversationRead( } } - void updateExpiringMessagesService(); - void tapToViewMessagesDeletionService.update(); + updateExpiringMessagesService(); + tapToViewMessagesDeletionService.update(); return true; } diff --git a/ts/util/onStoryRecipientUpdate.preload.ts b/ts/util/onStoryRecipientUpdate.preload.ts index 0ff5969814..5c8ceedfd4 100644 --- a/ts/util/onStoryRecipientUpdate.preload.ts +++ b/ts/util/onStoryRecipientUpdate.preload.ts @@ -88,7 +88,7 @@ export async function onStoryRecipientUpdate( existing.add(convo.id); } } - isAllowedToReply.set(convo.id, item.isAllowedToReply !== false); + isAllowedToReply.set(convo.id, item.isAllowedToReply); }); const ourConversationId = diff --git a/ts/util/preload.preload.ts b/ts/util/preload.preload.ts index 9cbe4ef69f..20d615f9ba 100644 --- a/ts/util/preload.preload.ts +++ b/ts/util/preload.preload.ts @@ -167,7 +167,9 @@ export function installEphemeralSetting(name: keyof EphemeralSettings): void { const updaterName = getUpdaterName(name); ipcRenderer.on(`settings:update:${name}`, async (_event, value) => { - const updateFn = window.Events[updaterName] as (value: unknown) => void; + const updateFn = window.Events[updaterName] as ( + value: unknown + ) => Promise | void; if (!updateFn) { return; } diff --git a/ts/util/queueAttachmentDownloads.preload.ts b/ts/util/queueAttachmentDownloads.preload.ts index 596d36a9e2..fcfeb734e2 100644 --- a/ts/util/queueAttachmentDownloads.preload.ts +++ b/ts/util/queueAttachmentDownloads.preload.ts @@ -75,9 +75,7 @@ export async function handleAttachmentDownloadsForNewMessage( message: MessageModel, conversation: ConversationModel ): Promise { - const logId = - `handleAttachmentDownloadsForNewMessage/${conversation.idForLogging()} ` + - `${getMessageIdForLogging(message.attributes)}`; + const logId = `handleAttachmentDownloadsForNewMessage/${conversation.idForLogging()} ${getMessageIdForLogging(message.attributes)}`; // Only queue attachments for downloads if this is a story (with additional logic), or // if it's either an outgoing message or we've accepted the conversation @@ -295,7 +293,7 @@ export async function queueAttachmentDownloads( } if (!isManualDownload) { - if (autoDownloadAttachment.photos === false) { + if (!autoDownloadAttachment.photos) { return item; } } @@ -510,7 +508,7 @@ export async function queueNormalAttachments({ // than once. // We don't also register the signatures for "attachments" because they would // then not be added to the AttachmentDownloads job. - const attachmentSignatures: Map = new Map(); + const attachmentSignatures = new Map(); otherAttachments?.forEach(attachment => { cacheAttachmentBySignature(attachmentSignatures, attachment); }); @@ -564,21 +562,18 @@ export async function queueNormalAttachments({ ); if (isVideo(contentType)) { - if (autoDownloadAttachment.videos === false) { + if (!autoDownloadAttachment.videos) { return attachment; } } else if (isImage(contentType)) { - if (autoDownloadAttachment.photos === false) { + if (!autoDownloadAttachment.photos) { return attachment; } } else if (isAudio(contentType)) { - if ( - autoDownloadAttachment.audio === false && - !isVoiceMessage(attachment) - ) { + if (!autoDownloadAttachment.audio && !isVoiceMessage(attachment)) { return attachment; } - } else if (autoDownloadAttachment.documents === false) { + } else if (!autoDownloadAttachment.documents) { return attachment; } } @@ -630,7 +625,7 @@ async function queuePreviews({ ) => boolean; }): Promise<{ preview: Array; count: number }> { const log = getLogger(source); - const previewSignatures: Map = new Map(); + const previewSignatures = new Map(); otherPreviews?.forEach(preview => { if (preview.image) { cacheAttachmentBySignature(previewSignatures, preview.image); @@ -678,7 +673,7 @@ async function queuePreviews({ DEFAULT_AUTO_DOWNLOAD_ATTACHMENT ); - if (autoDownloadAttachment.photos === false) { + if (!autoDownloadAttachment.photos) { return item; } } @@ -745,7 +740,7 @@ async function queueQuoteAttachments({ // Similar to queueNormalAttachments' logic for detecting same attachments // except here we also pick by quote sent timestamp. - const thumbnailSignatures: Map = new Map(); + const thumbnailSignatures = new Map(); otherQuotes.forEach(otherQuote => { for (const attachment of otherQuote.attachments) { if (attachment.thumbnail) { diff --git a/ts/util/search.std.ts b/ts/util/search.std.ts index 1625e7bf6b..7f7136c8cb 100644 --- a/ts/util/search.std.ts +++ b/ts/util/search.std.ts @@ -22,7 +22,7 @@ export const SNIPPET_TRUNCATION_PLACEHOLDER = '<>'; export function generateSnippetAroundMention({ body, mentionStart, - mentionLength = 1, + mentionLength, approxSnippetLength = 50, maxCharsBeforeHighlight = 30, }: { diff --git a/ts/util/sendStoryMessage.preload.ts b/ts/util/sendStoryMessage.preload.ts index 1d56f0559b..b028e6016a 100644 --- a/ts/util/sendStoryMessage.preload.ts +++ b/ts/util/sendStoryMessage.preload.ts @@ -86,26 +86,25 @@ export async function sendStoryMessage( if (distributionList.id === MY_STORY_ID && distributionList.isBlockList) { const inBlockList = new Set(distributionList.members); - distributionListMembers = getSignalConnections().reduce( - (acc, convo) => { - const uuid = convo.getServiceId(); - if (!uuid) { - return acc; - } - - if (inBlockList.has(uuid)) { - return acc; - } - - if (convo.isEverUnregistered()) { - return acc; - } - - acc.push(uuid); + distributionListMembers = getSignalConnections().reduce< + Array + >((acc, convo) => { + const uuid = convo.getServiceId(); + if (!uuid) { return acc; - }, - [] as Array - ); + } + + if (inBlockList.has(uuid)) { + return acc; + } + + if (convo.isEverUnregistered()) { + return acc; + } + + acc.push(uuid); + return acc; + }, []); } else { distributionListMembers = distributionList.members; } diff --git a/ts/util/sendToGroup.preload.ts b/ts/util/sendToGroup.preload.ts index a636244818..e56f8f5ba6 100644 --- a/ts/util/sendToGroup.preload.ts +++ b/ts/util/sendToGroup.preload.ts @@ -484,6 +484,7 @@ export async function sendToGroupViaSenderKey( // want the successful SKDM sends to be considered an overall success. if (error instanceof SendMessageProtoError) { throw new SendMessageProtoError({ + // oxlint-disable-next-line typescript/no-misused-spread ...error, sendIsNotFinal: true, }); @@ -935,6 +936,7 @@ function mergeSendResult({ senderKeyRecipientsWithDevices: Record>; }): CallbackResultType { return { + // oxlint-disable-next-line typescript/no-misused-spread ...result, successfulServiceIds: [ ...(result.successfulServiceIds || []), @@ -1532,7 +1534,7 @@ async function fetchKeysForServiceId( groupSendEndorsementState: GroupSendEndorsementState | null ): Promise { const logId = `fetchKeysForServiceId/${serviceId}`; - log.info(`${logId}: Fetching ${devices || 'all'} devices`); + log.info(`${logId}: Fetching ${devices?.join(', ') || 'all'} devices`); const emptyConversation = window.ConversationController.getOrCreate( serviceId, @@ -1591,7 +1593,7 @@ async function fetchKeysForServiceId( } } log.error( - `${logId}: Error fetching ${devices || 'all'} devices`, + `${logId}: Error fetching ${devices?.join(', ') || 'all'} devices`, Errors.toLogFormat(error) ); throw error; diff --git a/ts/util/sleeper.std.ts b/ts/util/sleeper.std.ts index af0407c3a4..4c9099835a 100644 --- a/ts/util/sleeper.std.ts +++ b/ts/util/sleeper.std.ts @@ -12,7 +12,7 @@ const log = createLogger('sleeper'); */ export class Sleeper { #shuttingDown = false; - #shutdownCallbacks: Set<() => void> = new Set(); + readonly #shutdownCallbacks = new Set<() => void>(); /** * delay by ms, careful when using on a loop if resolving on shutdown (default) diff --git a/ts/util/throttle.std.ts b/ts/util/throttle.std.ts index aa7f1df9fc..66f85a4e9a 100644 --- a/ts/util/throttle.std.ts +++ b/ts/util/throttle.std.ts @@ -1,7 +1,7 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -// oxlint-disable-next-line typescript/ban-types +// oxlint-disable-next-line typescript/ban-types, typescript/no-unsafe-function-type export function throttle(func: Function, wait: number): () => void { let lastCallTime: number; // oxlint-disable-next-line typescript/no-explicit-any diff --git a/ts/util/timelineUtil.std.ts b/ts/util/timelineUtil.std.ts index a210b47c82..bd530449bf 100644 --- a/ts/util/timelineUtil.std.ts +++ b/ts/util/timelineUtil.std.ts @@ -130,7 +130,7 @@ export function areMessagesInSameGroup( return false; } - return Boolean( + return ( !olderMessage.reactions?.length && olderMessage.author.id === newerMessage.author.id && (olderMessage.isEditedMessage || diff --git a/ts/util/timeout.std.ts b/ts/util/timeout.std.ts index 4a66c3ef5c..c4cf78adeb 100644 --- a/ts/util/timeout.std.ts +++ b/ts/util/timeout.std.ts @@ -40,8 +40,8 @@ export function safeSetTimeout( // Set timeout for a delay that might be longer than MAX_SAFE_TIMEOUT_DELAY. The // callback is guaranteed to execute after desired delay. export class LongTimeout { - #callback: VoidFunction; - #fireTime: number; + readonly #callback: VoidFunction; + readonly #fireTime: number; #timer: NodeJS.Timeout | undefined; constructor(callback: VoidFunction, providedDelayMs: number) { diff --git a/ts/util/url.std.ts b/ts/util/url.std.ts index 602840d341..aeb127cb83 100644 --- a/ts/util/url.std.ts +++ b/ts/util/url.std.ts @@ -23,6 +23,7 @@ export function setUrlSearchParams( if (value == null) { continue; } + // oxlint-disable-next-line typescript/no-base-to-string result.searchParams.append(key, String(value)); } return result; diff --git a/ts/util/viewOnceEligibility.std.ts b/ts/util/viewOnceEligibility.std.ts index 699e01135a..84cd0b4d73 100644 --- a/ts/util/viewOnceEligibility.std.ts +++ b/ts/util/viewOnceEligibility.std.ts @@ -8,7 +8,7 @@ export function isViewOnceEligible( attachments: ReadonlyArray, hasQuote: boolean ): boolean { - return Boolean( + return ( attachments.length === 1 && (isImageAttachment(attachments[0]) || isVideoAttachment(attachments[0])) && !hasQuote diff --git a/ts/util/wrapWithSyncMessageSend.preload.ts b/ts/util/wrapWithSyncMessageSend.preload.ts index bad9a11d5a..b2780772ce 100644 --- a/ts/util/wrapWithSyncMessageSend.preload.ts +++ b/ts/util/wrapWithSyncMessageSend.preload.ts @@ -59,6 +59,7 @@ export async function wrapWithSyncMessageSend({ error = thrown; } else { log.error(`${logId}: Thrown value was not an Error, returning early`); + // oxlint-disable-next-line typescript/only-throw-error throw error; } } diff --git a/ts/windows/about/preload.preload.ts b/ts/windows/about/preload.preload.ts index 07fd82631c..c1d24f5ea9 100644 --- a/ts/windows/about/preload.preload.ts +++ b/ts/windows/about/preload.preload.ts @@ -9,7 +9,7 @@ import { environment } from '../../context/environment.preload.ts'; const environments: Array = [environment]; if (config.appInstance) { - environments.push(String(config.appInstance)); + environments.push(config.appInstance); } const Signal = { diff --git a/ts/windows/main/phase1-ipc.preload.ts b/ts/windows/main/phase1-ipc.preload.ts index bfb153a3ff..5b3c2c74aa 100644 --- a/ts/windows/main/phase1-ipc.preload.ts +++ b/ts/windows/main/phase1-ipc.preload.ts @@ -414,7 +414,7 @@ ipc.on('donation-paypal-canceled', (_event, { returnToken }) => { ipc.on('show-conversation-via-token', (_event, token: string) => { const { showConversationViaToken } = window.Events; if (showConversationViaToken) { - void showConversationViaToken(token); + showConversationViaToken(token); } }); ipc.on('show-conversation-via-signal.me', (_event, info) => { diff --git a/ts/windows/minimalContext.preload.ts b/ts/windows/minimalContext.preload.ts index 8468ff54ee..038b0d12cc 100644 --- a/ts/windows/minimalContext.preload.ts +++ b/ts/windows/minimalContext.preload.ts @@ -36,14 +36,13 @@ export const MinimalSignalContext: MinimalSignalContextType = { ): Promise { await ipcRenderer.invoke('executeMenuRole', role); }, - getAppInstance: (): string | undefined => - config.appInstance ? String(config.appInstance) : undefined, + getAppInstance: (): string | undefined => config.appInstance, getEnvironment: () => environment, - getNodeVersion: (): string => String(config.nodeVersion), + getNodeVersion: (): string => config.nodeVersion, getPath: (name: 'userData' | 'home' | 'install'): string => { - return String(config[`${name}Path`]); + return config[`${name}Path`]; }, - getVersion: (): string => String(config.version), + getVersion: (): string => config.version, async getMainWindowStats(): Promise { return ipcRenderer.invoke('getMainWindowStats'); },