Skip to content

refactor: Convert missing JavaScript modules to TypeScript#38357

Open
tassoevan wants to merge 81 commits intodevelopfrom
refactor/js-to-ts
Open

refactor: Convert missing JavaScript modules to TypeScript#38357
tassoevan wants to merge 81 commits intodevelopfrom
refactor/js-to-ts

Conversation

@tassoevan
Copy link
Contributor

@tassoevan tassoevan commented Jan 27, 2026

Proposed changes (including videos or screenshots)

It does a raw migration to TypeScript on modules current coded as JavaScript.

Issue(s)

Steps to test or reproduce

Further comments

Heavily AI-assisted, needs careful review.

Summary by CodeRabbit

  • New Features

    • OAuth proxy redirect support and a token-based OAuth login flow.
  • Bug Fixes

    • Upload file-type detection now resolves before processing to improve reliability.
    • Email domain validation skipped when no email is provided to avoid false failures.
  • Refactor

    • Extensive TypeScript typings and API hardening across server, apps, and integrations.
  • Chores

    • Tests migrated to Jest; Storybook story discovery narrowed; test mocks and tooling updated.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Jan 27, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Jan 27, 2026

⚠️ No Changeset found

Latest commit: ef173c9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

Walkthrough

Widespread TypeScript migration and typings added across the Meteor app, test infra migrated to Jest, Storybook mock replaced with a typed TS mock, many converters/bridges refactored and typed, OAuth access-token login handler reintroduced, uploads/file-type detection made async, numerous JS→TS handler rewrites, and tests/mocks updated.

Changes

Cohort / File(s) Summary
Test infra & Storybook
apps/meteor/.mocharc.js, apps/meteor/jest.config.ts, apps/meteor/.storybook/main.ts
Adjusted Mocha spec patterns, expanded Jest testMatch and moduleNameMapper, narrowed Storybook stories glob, and switched Storybook Meteor mock replacement to the new TS mock.
Storybook mocks
apps/meteor/.storybook/mocks/meteor.js, apps/meteor/.storybook/mocks/meteor.ts
Removed the untyped JS mock and added a comprehensive typed TS mock exporting Meteor, Tracker, Accounts, Mongo, Template, Session, etc. stubs.
Converters & tests
apps/meteor/app/apps/server/converters/*, apps/meteor/app/apps/server/converters/*.spec.*
Many converters (rooms, messages, users, visitors, uploads, departments, settings, threads) rewritten/typed in TS with overloads; tests migrated to Jest and mocks updated.
Bridges & orchestrator
apps/meteor/app/apps/server/bridges/bridges.ts, apps/meteor/ee/server/apps/orchestrator.ts, packages/apps-engine/src/server/bridges/*
Added strongly-typed bridge fields/getters, constructor typing to IAppServerOrchestrator, orchestrator refactor to typed class, and reorganized bridge exports (added ThreadBridge).
Uploads & file storage
apps/meteor/app/apps/server/bridges/uploads.ts, apps/meteor/app/apps/server/converters/uploads.{js,ts}, apps/meteor/ee/lib/misc/determineFileType.ts, apps/meteor/app/file/server/*, apps/meteor/app/importer/*
Made determineFileType async (await FileType.fromBuffer), awaited MIME detection in upload flows, added typed AppUploadsConverter, and adjusted createWriteStream/createReadStream typings and callsites.
OAuth & authentication
apps/meteor/app/custom-oauth/server/*, apps/meteor/app/lib/server/oauth/*, apps/meteor/app/authentication/server/startup/index.ts
Added typed CustomOAuth module, reintroduced registerAccessTokenService and Accounts.registerLoginHandler flow for access-token logins, and strengthened authentication startup typings and handlers.
IRC / RFC2813
apps/meteor/app/irc/server/irc-bridge/*, apps/meteor/app/irc/server/servers/RFC2813/*
Ported IRC bridge, local/peer handlers and RFC2813 parsing to TypeScript, added typed command/arg shapes, and converted exports to named ES modules.
Markdown, mentions & parser rewrites
apps/meteor/app/markdown/**, apps/meteor/app/mentions/**, apps/meteor/tests/**
Tightened Markdown/token typings, migrated parsers to TS, added typed parser factories, migrated many tests from Mocha/Chai/proxyquire to Jest/TS, and introduced typed test mocks.
Notifications & Spotlight
apps/meteor/app/lib/server/functions/notifications/*, apps/meteor/server/lib/spotlight.ts
Added stronger typings, null checks, and stricter payload shapes for email/push helpers; introduced extensive Spotlight search typings and refined return shapes.
Rate-limiter, debug & Meteor wrappers
apps/meteor/app/lib/server/lib/RateLimiter.ts, apps/meteor/app/lib/server/lib/debug.ts, apps/meteor/app/lib/server/startup/rateLimiter.ts
Added RateLimiter input/reply types, typed DDPRateLimiter/RateLimiter overrides, and typed debug wrappers for Meteor.methods/publish.
Metrics & statistics
apps/meteor/app/metrics/server/lib/statsTracker.{js,ts}, apps/meteor/app/statistics/**
Replaced JS StatsTracker with a typed TS class singleton, added UA parser typings, and migrated statistics tests to Jest.
JS → TS replacements / handlers
many apps/meteor/**/*.js*.ts (localHandlers, peerHandlers, converters, etc.)
Numerous legacy JS modules replaced with TypeScript equivalents; behavior largely preserved while signatures and typings were updated.
Type definitions & core typings
apps/meteor/definition/externals/*.d.ts, packages/*/src/*
Adjusted external declarations (MethodInvocation, DDP._CurrentInvocation, Meteor.User), added/changed public types (ISetting.group, IDepartment.departmentsAllowedToForward, Google shape), and narrowed several model signatures.
Tests & mocks added/changed
apps/meteor/tests/**, apps/meteor/tests/mocks/server/meteor.ts, apps/meteor/tests/unit/.../mocks/*
Introduced typed Jest mocks and many test rewrites; removed old proxyquire/Chai mocks and some legacy JS test files.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant LoginHandler as Accounts.registerLoginHandler
    participant Registry as AccessTokenServices
    participant Config as ServiceConfiguration
    participant AccountsSvc as Accounts.updateOrCreateUserFromExternalService
    participant DB as Database

    Client->>LoginHandler: login({ accessToken, serviceName })
    LoginHandler->>Registry: lookup handler for serviceName
    alt handler found
      LoginHandler->>Config: ensure ServiceConfiguration exists
      LoginHandler->>Registry: invoke handler.handleAccessTokenRequest(options)
      Registry-->>LoginHandler: { serviceData, options }
      LoginHandler->>AccountsSvc: updateOrCreateUserFromExternalService(serviceName, serviceData, options)
      AccountsSvc->>DB: create/update user
      DB-->>AccountsSvc: user created/updated
      AccountsSvc-->>LoginHandler: login result
      LoginHandler-->>Client: success
    else no handler
      LoginHandler-->>Client: Meteor.Error (no registered oauth service)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • ggazzo

"🐰 I hopped through types, tests, and code so tight,
Jest and TS now dance from morning to night,
Converters and bridges, all typed and in tune,
Registry and uploads hummed under the moon,
A carrot for reviewers — please review soon! 🥕"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: Convert missing JavaScript modules to TypeScript' clearly and specifically summarizes the main objective of the changeset—converting JavaScript files to TypeScript across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/js-to-ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Jan 27, 2026

Codecov Report

❌ Patch coverage is 78.82736% with 130 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.62%. Comparing base (5b5082f) to head (ef173c9).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #38357      +/-   ##
===========================================
+ Coverage    70.40%   70.62%   +0.22%     
===========================================
  Files         3161     3168       +7     
  Lines       110639   112445    +1806     
  Branches     19870    20481     +611     
===========================================
+ Hits         77895    79417    +1522     
- Misses       30717    30843     +126     
- Partials      2027     2185     +158     
Flag Coverage Δ
e2e 60.38% <65.78%> (+<0.01%) ⬆️
e2e-api 47.61% <31.03%> (-0.28%) ⬇️
unit 71.75% <83.09%> (+0.35%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 27, 2026

📦 Docker Image Size Report

➡️ Changes

Service Current Baseline Change Percent
sum of all images 0B 0B 0B
account-service 0B 0B 0B
authorization-service 0B 0B 0B
ddp-streamer-service 0B 0B 0B
omnichannel-transcript-service 0B 0B 0B
presence-service 0B 0B 0B
queue-worker-service 0B 0B 0B
rocketchat 0B 0B 0B

📊 Historical Trend

---
config:
  theme: "dark"
  xyChart:
    width: 900
    height: 400
---
xychart
  title "Image Size Evolution by Service (Last 30 Days + This PR)"
  x-axis ["11/18 22:53", "11/19 23:02", "11/21 16:49", "11/24 17:34", "11/27 22:32", "11/28 19:05", "12/01 23:01", "12/02 21:57", "12/03 21:00", "12/04 18:17", "12/05 21:56", "12/08 20:15", "12/09 22:17", "12/10 23:26", "12/11 21:56", "12/12 22:45", "12/13 01:34", "12/15 22:31", "12/16 22:18", "12/17 21:04", "12/18 23:12", "12/19 23:27", "12/20 21:03", "12/22 18:54", "12/23 16:16", "12/24 19:38", "12/25 17:51", "12/26 13:18", "12/29 19:01", "12/30 20:52", "02/04 21:45 (PR)"]
  y-axis "Size (GB)" 0 --> 0.5
  line "account-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "authorization-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "ddp-streamer-service" [0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.00]
  line "omnichannel-transcript-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  line "presence-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "queue-worker-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  line "rocketchat" [0.35, 0.35, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.00]
Loading

Statistics (last 30 days):

  • 📊 Average: 1.5GiB
  • ⬇️ Minimum: 1.4GiB
  • ⬆️ Maximum: 1.6GiB
  • 🎯 Current PR: 0B
ℹ️ About this report

This report compares Docker image sizes from this build against the develop baseline.

  • Tag: pr-38357
  • Baseline: develop
  • Timestamp: 2026-02-04 21:45:40 UTC
  • Historical data points: 30

Updated: Wed, 04 Feb 2026 21:45:41 GMT

@tassoevan tassoevan force-pushed the refactor/js-to-ts branch 25 times, most recently from c8df5d1 to 7327b4b Compare February 1, 2026 17:17
…ageReport.spec.ts` test to Jest

- Replace proxyquire with jest.mock() for module mocking
- Replace sinon stubs with jest.fn() mock functions
- Convert all assertions to Jest matchers
- Remove file from Mocha configuration
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (15)
apps/meteor/app/utils/lib/getURL.spec.ts (1)

106-113: ⚠️ Potential issue | 🟡 Minor

Fix typo in test descriptions: "fase" → "false".

Lines 106 and 111 contain the typo "fase" instead of "false" in the test descriptions.

✏️ Proposed fix
 		if (!options.full && !options.cdn) {
-			it('should return with cloud host if full: fase and cdn: false', () => {
+			it('should return with cloud host if full: false and cdn: false', () => {
 				testPaths(options, (path) => getCloudUrl(_site_url, path));
 			});
 		}
 	} else if (!options.full && !options.cdn) {
-		it('should return with cloud host if full: fase and cdn: false', () => {
+		it('should return with cloud host if full: false and cdn: false', () => {
 			testPaths(options, (path) => getCloudUrl(_site_url, path));
 		});
 	}
apps/meteor/app/irc/server/irc-bridge/peerHandlers/disconnected.ts (1)

23-28: ⚠️ Potential issue | 🟡 Minor

Inconsistent status value representation.

Line 23 correctly uses UserStatus.OFFLINE, but line 28 still uses the string literal 'offline' in the notifyOnUserChange call. For consistency and type safety, both should use the same representation.

Proposed fix
-	void notifyOnUserChange({ id: user._id, clientAction: 'updated', diff: { status: 'offline' } });
+	void notifyOnUserChange({ id: user._id, clientAction: 'updated', diff: { status: UserStatus.OFFLINE } });
apps/meteor/definition/externals/meteor/ddp-common.d.ts (1)

6-19: ⚠️ Potential issue | 🟠 Major

Missing MethodInvocation instance type definition.

The constructor signature returns MethodInvocation (line 16), but no corresponding instance type is defined. Only the let MethodInvocation: MethodInvocationConstructor variable is declared (line 19). In TypeScript, the return type annotation refers to a TYPE that must represent the instance shape. Without an interface MethodInvocation { ... }, TypeScript cannot properly type instances created via new DDPCommon.MethodInvocation(...).

Suggested fix
+		interface MethodInvocation {
+			userId: string | null;
+			isSimulation: boolean;
+			connection: {
+				id: string;
+				close: () => void;
+				clientAddress: string;
+				httpHeaders: Record<string, any>;
+			};
+			setUserId(userId: string | null): void;
+			unblock(): void;
+		}
+
 		interface MethodInvocationConstructor {
 			new (options: {
 				connection: {
 					id: string;
 					close: () => void;
 					clientAddress: string;
 					httpHeaders: Record<string, any>;
 				};
 				isSimulation?: boolean;
 				userId?: string;
 			}): MethodInvocation;
 		}

 		let MethodInvocation: MethodInvocationConstructor;
apps/meteor/app/lib/server/functions/parseUrlsInMessage.ts (1)

8-29: ⚠️ Potential issue | 🟡 Minor

Missing return statement leaves function returning undefined.

The function signature implies it should return the modified message, but there's no explicit return message; at the end. After the delete operations on lines 27-28, the function falls through without returning anything. This appears unintentional given the early return on line 10.

Proposed fix
 	message.msg = message.html || message.msg;
 	delete message.html;
 	delete message.tokens;
+
+	return message;
 };
apps/meteor/app/markdown/lib/parser/original/code.ts (1)

46-55: ⚠️ Potential issue | 🟠 Major

Race condition: register(lang) is async but not awaited, causing hljs.highlight() to execute before language registration completes.

The register function (hljs.ts line 11) is async and dynamically imports language modules, but void register(lang) discards the promise. This means hljs.highlight(lang, code) on line 49 may execute before the language is registered, causing incorrect highlighting or errors that fall back to hljs.highlightAuto().

Resolve by awaiting registration. This requires making the IIFE async:

Proposed fix
-				const result = (() => {
+				const result = (async () => {
 					if (lang) {
 						try {
-							void register(lang);
+							await register(lang);
 							return hljs.highlight(lang, code);
 						} catch (error) {
 							console.error(error);
 						}
 					}
 					return hljs.highlightAuto(lang + code);
-				})();
+				})();

Note: The parent codeblocks function would need to become async as well to properly handle the promise.

apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.ts (2)

105-111: ⚠️ Potential issue | 🔴 Critical

Preserve authorization constraints when departmentId is provided.

Overwriting extraFilter removes the permission-based $or and can allow querying departments the user should not access. Keep the auth filter and add departmentId as an additional AND constraint.

🔒 Suggested fix
-	if (departmentId) {
-		extraFilter = [
-			{
-				departmentId,
-			},
-		];
-	}
+	if (departmentId) {
+		extraFilter = [...extraFilter, { departmentId }];
+	}

113-119: ⚠️ Potential issue | 🟠 Major

Guard textFilter creation to avoid runtime errors.

text is optional, but text! forces a non-null assertion and can throw when omitted. Create the regex only when text is present.

✅ Suggested fix
-	const textFilter = new RegExp(escapeRegExp(text!), 'i');
+	const textFilter = text ? new RegExp(escapeRegExp(text), 'i') : undefined;

-			...(text ? [{ $or: [{ shortcut: textFilter }, { text: textFilter }] }] : []),
+			...(textFilter ? [{ $or: [{ shortcut: textFilter }, { text: textFilter }] }] : []),
apps/meteor/app/google-oauth/server/index.ts (1)

31-53: ⚠️ Potential issue | 🟡 Minor

Rename local escape helper to avoid shadowing the global escape.

Biome flags this as a variable shadowing issue in the recommended rules. The local function shadows the global escape() function, which can confuse readers and debuggers. Rename to escapeHtml across all 4 usages within the config object (lines 48-51).

Proposed fix
-		const escape = (s: string | undefined): string | undefined => {
+		const escapeHtml = (s: string | undefined): string | undefined => {
 			if (!s) {
 				return s;
 			}
@@
-			credentialToken: escape(options.credentialToken),
-			credentialSecret: escape(options.credentialSecret),
-			storagePrefix: escape((OAuth as any)._storageTokenPrefix),
-			redirectUrl: escape(options.redirectUrl),
+			credentialToken: escapeHtml(options.credentialToken),
+			credentialSecret: escapeHtml(options.credentialSecret),
+			storagePrefix: escapeHtml((OAuth as any)._storageTokenPrefix),
+			redirectUrl: escapeHtml(options.redirectUrl),
apps/meteor/app/irc/server/irc-bridge/peerHandlers/sentMessage.ts (1)

50-59: ⚠️ Potential issue | 🔴 Critical

Guard against missing recipientNick to avoid incorrect user lookup.

The function accepts both roomName and recipientNick as optional parameters. When roomName is absent, it queries users by recipientNick without validating it is defined. If recipientNick is undefined or empty, the MongoDB query {'profile.irc.nick': undefined} can match unintended users (those without an IRC nick set), risking messages sent to the wrong recipient. Add an explicit guard before querying.

Proposed fix
 } else {
+	if (!args.recipientNick) {
+		throw new Error('recipientNick is required when roomName is not provided');
+	}
 	const recipientUser = await Users.findOne({
 		'profile.irc.nick': args.recipientNick,
 	});
apps/meteor/app/authentication/server/startup/index.ts (1)

112-136: ⚠️ Potential issue | 🟡 Minor

Guard email template placeholders when user.name is missing.

IUser.name is optional, but lines 113, 128, and 136 assume it always exists via type assertion as IUser & { name: string }. If missing at runtime, safeHtmlDots will throw a TypeError when calling text.replace(), and Mailer.replace will render the literal string "undefined" in emails. Use the nullish coalescing operator as shown in the enrollAccount.html handler (line 150):

🛠️ Suggested fix
 Accounts.emailTemplates.verifyEmail.html = function (userModel, url) {
-	const name = safeHtmlDots((userModel as IUser & { name: string }).name);
+	const name = safeHtmlDots((userModel as IUser).name ?? '');

 	return Mailer.replace(verifyEmailTemplate, { Verification_Url: url, name });
 };

 Accounts.emailTemplates.resetPassword.subject = function (userModel) {
 	return Mailer.replace(settings.get<string>('Forgot_Password_Email_Subject') || '', {
-		name: (userModel as IUser & { name: string }).name,
+		name: (userModel as IUser).name ?? '',
 	});
 };

 Accounts.emailTemplates.resetPassword.html = function (userModel, url) {
 	return Mailer.replacekey(
 		Mailer.replace(resetPasswordTemplate, {
-			name: (userModel as IUser & { name: string }).name,
+			name: (userModel as IUser).name ?? '',
 		}),
 		'Forgot_Password_Url',
 		url,
 	);
 };
apps/meteor/app/custom-oauth/server/custom_oauth_server.ts (1)

362-413: ⚠️ Potential issue | 🟡 Minor

Preserve the original error as a cause.

Lines 371, 384, 398, and 411 pass error.message as a second argument to the Error constructor, which is ignored. The Error constructor accepts only one parameter, so the original error context is lost. Use error cause with { cause: error } instead to preserve the error chain.

🛠️ Suggested fix (apply to all four catch blocks)
-		} catch (error: any) {
-			throw new Error('CustomOAuth: Failed to extract username', error.message);
+		} catch (error: any) {
+			throw new Error('CustomOAuth: Failed to extract username', { cause: error });
 		}
apps/meteor/ee/server/apps/orchestrator.ts (1)

262-299: ⚠️ Potential issue | 🟠 Major

Guard missing installedAt before computing upgrade date.

Line 293 uses stat.installedAt! without checking if it exists. The findInstallationDates() method returns Promise<Pick<IStats, 'version' | 'installedAt'>[]>, and installedAt is optional in IStats (defined as installedAt?: string). If installedAt is undefined, new Date() creates an Invalid Date and toISOString() throws. Line 274 already handles this case with || 0, but line 293 doesn't. Add a guard to skip entries without installedAt.

Suggested fix
-				if (hadPreTargetVersion && majorVersion >= targetVersion) {
-					upgradeToV7Date = new Date(stat.installedAt!);
+				if (hadPreTargetVersion && majorVersion >= targetVersion) {
+					if (!stat.installedAt) {
+						continue;
+					}
+					upgradeToV7Date = new Date(stat.installedAt);
 					this._rocketchatLogger.info({
 						msg: 'Found upgrade to target version date',
 						targetVersion,
 						upgradeToDate: upgradeToV7Date.toISOString(),
 					});
 					break;
 				}
apps/meteor/app/apps/server/converters/messages.ts (2)

111-149: ⚠️ Potential issue | 🟠 Major

Don't delete message.u before the fallback conversion.

message.u is deleted at line 141 before the fallback call at line 148, so the fallback always receives undefined and cannot recover a sender. Preserve the original value until after the fallback, then delete. Also remove the inline FIXME comments in this block once corrected, as they violate the coding guideline to avoid code comments in the implementation.

🔧 Suggested fix
 			sender: async (message: IMessage) => {
 				if (!message.u?._id) {
 					return undefined;
 				}
 
 				// When the message contains token, means the message is from the visitor(omnichannel)
-				const user = await (isMessageFromVisitor(msgObj)
-					? cache.get('user.convertToApp')(message.u)
-					: cache.get('user.convertById')(message.u._id));
-
-				delete (message as any).u; // FIXME the property is used right after, so we can't delete it before, what???
+				const originalUser = message.u;
+				const user = await (isMessageFromVisitor(msgObj)
+					? cache.get('user.convertToApp')(originalUser)
+					: cache.get('user.convertById')(originalUser._id));
+
+				delete (message as any).u;
 
 				/**
 				 * Old System Messages from visitor doesn't have the `token` field, to not return
 				 * `sender` as undefined, so we need to add this fallback here.
 				 */
 
-				return user || cache.get('user.convertToApp')(message.u);
+				return user || cache.get('user.convertToApp')(originalUser);
 			},

197-204: ⚠️ Potential issue | 🟠 Major

Add null guard for editor lookup to prevent crash.

Users.findOneById() returns Promise<IUser | null>, so the non-null assertion editor! will crash if the user no longer exists. The similar sender lookup above uses proper null guarding (if (user)); apply the same pattern here with fallback to message.editor.id and message.editor.username.

🔧 Suggested fix
 		let editedBy;
 		if (message.editor) {
 			const editor = await Users.findOneById(message.editor.id);
-			editedBy = {
-				_id: editor!._id,
-				username: editor!.username,
-			};
+			if (editor) {
+				editedBy = {
+					_id: editor._id,
+					username: editor.username,
+				};
+			} else {
+				editedBy = {
+					_id: message.editor.id,
+					username: message.editor.username,
+				};
+			}
 		}
apps/meteor/app/apps/server/converters/rooms.ts (1)

146-166: ⚠️ Potential issue | 🟠 Major

Guard missing visitorChannelInfo before destructuring.

visitorChannelInfo is optional in the ILivechatRoom interface and has no null check before being destructured with a non-null assertion. Use a safe default instead.

🔧 Suggested fix
-		const { lastMessageTs, phone } = visitorChannelInfo!;
+		const { lastMessageTs, phone } = visitorChannelInfo ?? {};
🤖 Fix all issues with AI agents
In `@apps/meteor/app/apps/server/converters/messages.ts`:
- Around line 341-345: The timestamp converter in messages.ts uses a non-null
assertion on attachment.ts causing Invalid Date when undefined; update the
timestamp function (the converter for MessageAttachment) to first check if
attachment.ts is defined (e.g., a ternary or if) and only call new
Date(attachment.ts) when present, otherwise return undefined (matching the
threads.ts sibling), and preserve the delete attachment.ts behavior when ts
exists; adjust the return type if necessary to match other converters.

In `@apps/meteor/app/apps/server/converters/rooms.ts`:
- Around line 18-20: convertByName currently uses a non-null assertion on
IRoom['name'] when calling Rooms.findOneByName; add a guard clause at the start
of convertByName to handle undefined roomName (e.g., if (!roomName) return
undefined) and remove the non-null assertion so Rooms.findOneByName is only
called with a real string; update convertByName to explicitly return undefined
for missing names and proceed with the lookup when roomName is present.

In `@apps/meteor/app/apps/server/converters/users.ts`:
- Around line 16-18: The convertByUsername function currently uses a non-null
assertion on IUser['username'] and may pass undefined into
Users.findOneByUsername; update convertByUsername to explicitly guard for
undefined by checking the username parameter at the top (e.g., if (!username)
return undefined or handle error) and remove the non-null assertion so you only
call Users.findOneByUsername(username) when username is a defined string;
reference convertByUsername and Users.findOneByUsername in your change.

In `@apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts`:
- Around line 43-45: The call to decodeURIComponent when computing fileId inside
the WebApp.connectHandlers.use callback can throw a URIError for malformed
percent-encoding; wrap the decodeURIComponent(...) that defines fileId in a
try/catch, catch URIError (and other decoding errors), and return a 400 response
(set statusCode 400 and end with a short error message) instead of allowing the
error to bubble to a 500; update the handler around the fileId computation in
this connectHandlers callback so subsequent logic only runs when decoding
succeeds.
- Line 86: Replace the non-null assertion on file.contentType when calling
res.setHeader: instead of using file.contentType! pass a safe fallback value
(e.g. 'application/octet-stream') so that res.setHeader('Content-Type', ...)
never receives undefined; locate the call to res.setHeader and update it to use
file.contentType ?? 'application/octet-stream' (or equivalent) to match the
FileSystem implementation.

In `@apps/meteor/app/file/server/file.server.ts`:
- Around line 25-27: The interface methods createWriteStream and
createReadStream currently return unknown; change their return types to the
appropriate Node stream base types so callers can use .on()/.pipe(): update the
interface signature for createWriteStream(fileName: string, contentType: string)
to return NodeJS.WritableStream and createReadStream(fileName: string) to return
NodeJS.ReadableStream; ensure this matches existing implementations (GridFS
returning GridFSBucketWriteStream/GridFSBucketReadStream and FileSystem
returning fs.WriteStream/fs.ReadStream) so no behavioral changes are needed.

In `@apps/meteor/app/importer/server/startup/setImportsToInvalid.ts`:
- Line 10: The conditional is using an unnecessary cast and array includes
check: locate the check that references lastOperation and ProgressStep (the line
containing [ProgressStep.USER_SELECTION].includes(lastOperation.status as any))
and replace it with a direct equality comparison against the enum (compare
lastOperation.status === ProgressStep.USER_SELECTION) and remove the as any cast
so the type system validates the enum value directly.

In `@apps/meteor/app/irc/server/irc-bridge/localHandlers/onCreateUser.ts`:
- Line 41: The forEach callback currently uses an implicit return by writing
rooms.forEach((room: any) => this.sendCommand('joinedChannel', { room, user })),
which the linter flags; change the callback to a block-bodied arrow function to
avoid returning a value (e.g., use rooms.forEach((room: any) => {
this.sendCommand('joinedChannel', { room, user }); })), ensuring the call to
this.sendCommand in the onCreateUser handler remains unchanged but is executed
without an implicit return.

In `@apps/meteor/app/lib/server/functions/notifications/email.ts`:
- Around line 237-244: The Reply-To header construction uses
settings.get('Direct_Reply_Separator') directly in split(), which can be
undefined or empty; read the separator into a local const (e.g., const sep =
(settings.get('Direct_Reply_Separator') as string) || '+'), ensure sep is
non-empty (fallback to a safe default like '+'), then use sep for both the
left-side split and when concatenating the message id in the
email.headers['Reply-To'] assignment (replace direct uses of
settings.get('Direct_Reply_Separator') and the inline split with the new sep
variable) so you never call split with undefined or an empty string and the
Reply-To remains well-formed using message.tmid || message._id.

In `@apps/meteor/app/lib/server/lib/interceptDirectReplyEmails.ts`:
- Around line 137-138: The isActive() method incorrectly treats the helper as
active because stop() never sets the running flag to false and callback typing
was weakened; update stop() to set this.running = false to properly deactivate,
and replace usages of undefined as any for the stored callback with an optional
parameter type (e.g., callback?: ...) and adjust the code paths that
assign/clear this.callback (e.g., in start(), stop(), and any setter) so the
callback is set when starting and cleared when stopping; ensure isActive()
continues to read this.running !== false and that related logic around methods
referenced (isActive, stop, start, running, callback) is updated accordingly.

In `@apps/meteor/app/lib/server/oauth/twitter.ts`:
- Around line 25-26: In the catch block that throws the OAuth error (the code
creating the Error with message "Failed to fetch identity from Twwiter.
${err.message}"), fix the typo by changing "Twwiter" to "Twitter" so the
thrown/logged message reads "Failed to fetch identity from Twitter.
${err.message}"; update this string in the catch inside
apps/meteor/app/lib/server/oauth/twitter.ts (the error thrown when failing to
fetch identity).
- Around line 51-55: The current whitelisting loop drops falsy but valid values
because it uses a truthy check; update the loop that builds fields from
whitelistedFields (the block referencing fields, whitelistedFields, and
identity) to test property existence instead (e.g., use "key in identity" or
Object.prototype.hasOwnProperty.call(identity, key)) so empty strings, 0, false,
etc. are preserved when assigning fields[key] = identity[key].

In `@apps/meteor/app/lib/server/startup/rateLimiter.ts`:
- Around line 143-164: The rate-limit slowdown is ineffective because the async
callback (callback) that awaits sleep() is invoked via rule._executeCallback()
from check() without awaiting its Promise; update the call sites where
rule._executeCallback(...) is invoked (the two places that call it from check())
to await the returned Promise and make check() async (propagate async/await) so
the sleep actually blocks, or alternatively convert the callback to a
synchronous path (remove async/sleep) if making check() async is
undesirable—ensure consistency between callback (function callback) and its
invoker rule._executeCallback() so the slowdown delay is awaited.

In `@apps/meteor/app/mentions/lib/MentionsParser.spec.ts`:
- Around line 87-97: The three skipped tests in the getChannelMentions describe
block incorrectly call mentionsParser.getUserMentions; update each skipped spec
to call mentionsParser.getChannelMentions instead (the tests titled 'should
return without the "." from "#general."', 'should return without the "_" from
"#general_"', and 'should return without the "-" from "#general."') so that when
unskipped they exercise the correct function; keep the rest of each expectation
the same.

In `@apps/meteor/definition/externals/meteor/google-oauth.d.ts`:
- Around line 2-12: The module declares Google and references LoginError but
LoginError is missing; fix by adding a local type/import for LoginError in this
declaration file—either declare type LoginError = Meteor.Error (or import Meteor
and alias Meteor.Error as LoginError) or define the LoginError shape expected by
signIn; update the google-oauth.d.ts module so the referenced LoginError type is
defined alongside the existing Google, requestCredential, and signIn symbols.

In `@apps/meteor/tests/unit/app/apps/server/mocks/models/BaseModel.mock.ts`:
- Around line 4-6: findOneById declares return type T | null but currently
returns this.data[id] which yields undefined when not present; update the
function to be consistent by either changing its signature to return T |
undefined or by explicitly returning null for missing entries (e.g., in
findOneById use a null coalescing behavior to return this.data[id] ?? null).
Ensure references to the data property and the findOneById method are updated so
callers and types align.

In `@apps/meteor/tests/unit/app/apps/server/mocks/models/Messages.mock.ts`:
- Around line 5-6: Override of BaseModelMock's property "data" in class
MessagesMock currently lacks visibility and becomes public; change the
declaration in MessagesMock to explicitly use the protected modifier (i.e., make
"protected data = { ... }") so it matches BaseModelMock's protected visibility
and preserves encapsulation for the IMessage data.
🧹 Nitpick comments (38)
apps/meteor/app/lib/server/startup/robots.ts (1)

9-9: Remove the inline comment to comply with implementation comment ban.

The /* , next*/ comment violates the “avoid code comments in the implementation” guideline; it can be removed with no behavior change.

🧹 Proposed change
-return WebApp.connectHandlers.use('/robots.txt', (_req: IncomingMessage, res: ServerResponse /* , next*/): void => {
+return WebApp.connectHandlers.use('/robots.txt', (_req: IncomingMessage, res: ServerResponse): void => {
As per coding guidelines “Avoid code comments in the implementation”.
apps/meteor/app/importer/server/startup/store.ts (1)

12-16: Redundant double call to settings.get.

The setting is retrieved twice: once for the null check and again to store in filePath. This can be simplified to a single call.

♻️ Suggested simplification
-	if (settings.get<string>('ImportFile_FileSystemPath') != null) {
-		const filePath = settings.get<string>('ImportFile_FileSystemPath');
-		if (typeof filePath === 'string' && filePath.trim() !== '') {
-			path = filePath;
-		}
+	const filePath = settings.get<string>('ImportFile_FileSystemPath');
+	if (typeof filePath === 'string' && filePath.trim() !== '') {
+		path = filePath;
 	}
apps/meteor/app/irc/server/irc-bridge/peerHandlers/userRegistered.ts (2)

26-26: Inconsistent status value representation.

Line 26 uses the string literal 'online', line 54 uses UserStatus.ONLINE, and line 62 uses 'online' again. For consistency and type safety, consider using the UserStatus enum throughout.

♻️ Suggested fix for consistency
 		const userToInsert = {
 			name: args.nick,
 			username: `${args.username}-irc`,
-			status: 'online',
+			status: UserStatus.ONLINE,
 			utcOffset: 0,
-		void notifyOnUserChange({ id: user._id, clientAction: 'updated', diff: { status: 'online' } });
+		void notifyOnUserChange({ id: user._id, clientAction: 'updated', diff: { status: UserStatus.ONLINE } });

Also applies to: 54-54, 62-62


40-41: as any assertion weakens type safety.

The as any cast bypasses TypeScript checks. This is understandable for a migration since userToInsert is a partial user object, but consider creating a proper input type for Users.create() or using a more specific assertion like as Parameters<typeof Users.create>[0] if available.

apps/meteor/app/mailer/server/api.ts (1)

34-34: Minor type inconsistency across related functions.

The replace and replaceEscaped functions now use Record<string, any>, while wrap (line 73) and send (line 199) still use { [key: string]: unknown }. This works correctly since unknown is assignable to any, but consider aligning all these functions to use the same type for consistency.

For a migration PR, Record<string, any> is acceptable, though Record<string, unknown> would provide stronger type safety if you revisit this later.

Also applies to: 57-57

apps/meteor/app/markdown/lib/hljs.ts (1)

10-11: Avoid inline eslint-disable; refactor or move suppression to config.

The added // eslint-disable-next-line complexity violates the “no comments in implementation” guideline. Prefer refactoring to reduce complexity or apply an eslint override in configuration for this file instead of an inline comment. As per coding guidelines: Avoid code comments in the implementation.

apps/meteor/app/statistics/server/functions/sendUsageReport.spec.ts (1)

5-7: Consider making the mock logger more robust.

The mockLogger only implements error, but Logger has many methods (info, warn, debug, log, etc.). If sendUsageReport calls any other method, the test will fail at runtime with an undefined function error.

♻️ Suggested improvement
 const mockLogger: Logger = {
 	error: jest.fn(),
+	info: jest.fn(),
+	warn: jest.fn(),
+	debug: jest.fn(),
+	log: jest.fn(),
 } as any;

Alternatively, consider using a utility to create a complete mock:

const mockLogger = {
	error: jest.fn(),
	info: jest.fn(),
	warn: jest.fn(),
	debug: jest.fn(),
	log: jest.fn(),
	startup: jest.fn(),
	success: jest.fn(),
	method: jest.fn(),
	subscription: jest.fn(),
	fatal: jest.fn(),
} as unknown as Logger;
apps/meteor/app/lib/server/functions/validateCustomFields.ts (1)

28-33: customFields is populated but never used.

The customFields object is built inside the loop but is never returned or used after the function validates the fields. This appears to be dead code carried over from the original JS.

♻️ Suggested removal of dead code
-	const customFields: Record<string, any> = {};
-
 	Object.keys(customFieldsMeta).forEach((fieldName) => {
 		const field = customFieldsMeta[fieldName];
-
-		customFields[fieldName] = fields[fieldName];
 		const fieldValue = trim(fields[fieldName]);
apps/meteor/app/irc/server/irc-bridge/peerHandlers/leftChannel.ts (1)

10-10: Consider stronger typing for this context in future.

Using this: any works for this migration, but for better type safety, consider defining an interface for the IRC bridge context (with the log method) and using it across all peer handlers. This is optional and can be deferred.

apps/meteor/app/lib/server/functions/notifications/mobile.ts (1)

37-45: Well-structured parameter types with proper null safety.

The inline type definition is clear and the AtLeast generics ensure required fields are present. The || '' fallbacks on lines 63 and 74 correctly handle cases where username may be undefined.

One optional consideration: the receiver type requires fields (settings, active, emails, status, statusConnection) that aren't accessed in the function body—only language and username are used. You could narrow this to AtLeast<IUser, 'language' | 'username'> for minimal requirements, though the current broader type likely matches callers' object shapes.

apps/meteor/app/irc/server/irc-bridge/localHandlers/onSaveMessage.ts (1)

15-21: Consider narrowing the as any casts with a type guard or interface extension.

The repeated (userData as any).profile casts work but are fragile. A small type guard or local interface for the IRC profile shape would improve maintainability:

interface IrcProfile { irc?: { nick?: string } }
const profile = (userData as { profile?: IrcProfile }).profile;

However, this is a minor readability nit for a migration PR; the current approach is functionally correct.

apps/meteor/app/irc/server/irc-bridge/localHandlers/onLogin.ts (1)

44-50: Consider typing room as IRoom for better type safety.

The room: any cast loses type safety. Since IRoom is available from @rocket.chat/core-typings, consider:

-	rooms.forEach((room: any) => {
+	rooms.forEach((room: IRoom) => {

This is a minor improvement for a migration PR.

apps/meteor/app/irc/server/irc-bridge/index.ts (2)

192-194: Consider preserving error type information.

Casting the error to any loses type information. Consider logging with more context:

 		} catch (e) {
-			this.logQueue(e as any);
+			this.logQueue({ msg: 'Error processing command', error: String(e) });
 		}

Or update logQueue to accept unknown for error cases. This is a minor improvement.


153-158: Non-null assertion on initTime is safe but could be guarded.

The this.initTime! assertion is safe because init() sets it before runQueue() is called. However, adding a guard would be more defensive:

 		const lastResetTime = await Settings.findOneById('IRC_Bridge_Reset_Time');
-		if (lastResetTime?.value && lastResetTime.value > this.initTime!) {
+		if (lastResetTime?.value && this.initTime && lastResetTime.value > this.initTime) {

This is optional as the current flow guarantees initTime is set.

apps/meteor/app/markdown/lib/parser/original/markdown.ts (1)

34-38: Consider narrowing any[] for better type safety.

The replaceFunction: (...args: any[]) => string signature loses type information. While this is acceptable for regex replace callbacks where argument counts vary, you could consider typing it more precisely based on the actual regex groups used.

apps/meteor/app/markdown/lib/markdown.ts (1)

73-81: Type annotation could be more precise for the options parameter.

The { ...options } spread with no type annotation loses type information. Consider adding explicit typing to ensure callers pass valid options:

Proposed improvement
 export const createMarkdownMessageRenderer =
-	({ ...options }) =>
+	(options: { supportSchemesForLink?: string; headers?: boolean; rootUrl?: string }) =>
 	(message: { html: string; tokens?: Token[] }) => {
apps/meteor/app/lib/server/functions/notifications/email.ts (1)

230-233: Redundant length check.

The expression sender.emails?.length && sender.emails.length > 0 is redundant. If sender.emails?.length is truthy, it's already greater than 0.

♻️ Simplified check
-	if (sender.emails?.length && sender.emails.length > 0 && settings.get('Add_Sender_To_ReplyTo')) {
+	if (sender.emails?.length && settings.get('Add_Sender_To_ReplyTo')) {
apps/meteor/app/irc/server/servers/RFC2813/parseMessage.ts (1)

38-47: Non-null assertions could cause runtime errors on malformed input.

The non-null assertions at lines 39-40 (match![1]) will throw if match is null (empty or whitespace-only line after prefix removal). While this may represent existing behavior from the JS version, consider adding a guard or returning early for robustness.

♻️ Optional defensive check
 	// Parse command
 	match = line.match(/^([^ ]+) */);
+	if (!match) {
+		throw new Error(`Malformed IRC message: no command found in "${line}"`);
+	}
-	message.command = match![1] as keyof typeof peerCommandHandlers;
-	message.rawCommand = match![1];
+	message.command = match[1] as keyof typeof peerCommandHandlers;
+	message.rawCommand = match[1];
apps/meteor/app/irc/server/servers/RFC2813/index.ts (2)

150-152: Non-null assertion on socket may cause runtime error.

write() is a public method that could be called before register() initializes the socket. Consider adding a guard or returning false if socket is undefined.

♻️ Optional defensive check
-	return this.socket!.write(`${buffer}\r\n`);
+	if (!this.socket) {
+		this.log({ msg: 'Cannot write: socket not initialized' });
+		return false;
+	}
+	return this.socket.write(`${buffer}\r\n`);

168-168: Consider removing the control character from the regex.

Similar to localCommandHandlers.ts, this regex contains a control character (\u0007) which Biome flags. Apply the same normalization pattern for consistency.

As per coding guidelines: Avoid code comments in the implementation.

♻️ Suggested fix
-	const lines = this.receiveBuffer.toString().split(/\r\n|\r|\n|\u0007/); // eslint-disable-line no-control-regex
+	const bufferStr = this.receiveBuffer.toString().replace(/\u0007/g, '\n');
+	const lines = bufferStr.split(/\r\n|\r|\n/);
packages/apps-engine/src/definition/settings/ISetting.ts (1)

24-25: Avoid introducing new inline comments.

If we’re adhering to the no-comments guideline, please drop the newly added JSDoc line and keep the type-only change.

♻️ Suggested edit
-	/** The group this setting belongs to. */
 	group?: string;

As per coding guidelines, "Avoid code comments in the implementation".

apps/meteor/app/apps/server/converters/settings.ts (1)

6-6: Remove commented‑out import.

This keeps the implementation comment‑free and avoids dead code noise.

♻️ Suggested edit
-// import { exhaustiveCheck } from '../../../../lib/utils/exhaustiveCheck';

As per coding guidelines, "Avoid code comments in the implementation".

apps/meteor/app/mentions/server/Mentions.spec.ts (1)

22-34: Remove inline comments from the fixture setup.

Guidelines ask to avoid code comments; these can be expressed directly in code.

Proposed cleanup
-		// return RocketChat.models.Rooms.find({ name: {$in: _.unique(channels)}, t: 'c'	}, { fields: {_id: 1, name: 1 }}).fetch();
 		getUser: async (userId) => ({ _id: userId, language: 'en' }) as any,
-		getTotalChannelMembers: async (/* rid*/) => 2,
-		onMaxRoomMembersExceeded: async () => {
-			/* do nothing */
-		},
+		getTotalChannelMembers: async (_rid) => 2,
+		onMaxRoomMembersExceeded: async () => undefined,

As per coding guidelines: “Avoid code comments in the implementation”.

apps/meteor/app/lib/server/startup/rateLimiter.ts (1)

13-13: Consider adding explicit radix to parseInt.

While parseInt typically handles numeric strings correctly, explicitly specifying the radix is a best practice to avoid edge cases.

🔧 Suggested fix
-const slowDownRate = parseInt(process.env.RATE_LIMITER_SLOWDOWN_RATE || '0');
+const slowDownRate = parseInt(process.env.RATE_LIMITER_SLOWDOWN_RATE || '0', 10);
apps/meteor/app/lib/server/lib/interceptDirectReplyEmails.ts (1)

1-2: Prefer a minimal ambient type instead of @ts-expect-error.

Line 1 suppresses typing and adds an implementation comment. Consider adding a minimal ambient declaration for @rocket.chat/poplib (or installing official types) and remove the directive. As per coding guidelines: Avoid code comments in the implementation.

♻️ Proposed change
-// `@ts-expect-error` - no types available for `@rocket.chat/poplib`
 import POP3Lib from '@rocket.chat/poplib';

Add a declaration file (e.g., types/@rocket.chat__poplib.d.ts):

declare module '@rocket.chat/poplib';
apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts (1)

43-43: Remove the inline commented next parameter.
It’s dead commentary in implementation code.

🧹 Suggested cleanup
-	return WebApp.connectHandlers.use('/custom-sounds/', async (req: IncomingMessage, res: ServerResponse /* , next*/) => {
+	return WebApp.connectHandlers.use('/custom-sounds/', async (req: IncomingMessage, res: ServerResponse) => {

As per coding guidelines: Avoid code comments in the implementation.

apps/meteor/app/authentication/server/startup/index.ts (1)

155-205: Remove newly added inline comments/TODO to align with the no-comment guideline.

Consider relying on naming/structure instead of inline comments and move the TODO to an issue tracker.

🧹 Proposed cleanup
 const getLinkedInName = ({ firstName, lastName }: LinkedInName): string => {
-	// Check if it's the old format (simple strings)
 	if (typeof firstName === 'string' && typeof lastName === 'string') {
 		return lastName ? `${firstName} ${lastName}` : firstName;
 	}

-	// LinkedIn new format (objects with localized data)
 	if (typeof firstName === 'object' && typeof lastName === 'object') {
 		const { preferredLocale, localized: firstNameLocalized } = firstName;
 		const { localized: lastNameLocalized } = lastName;

 		if (preferredLocale && firstNameLocalized && preferredLocale.language && preferredLocale.country) {
 			const locale = `${preferredLocale.language}_${preferredLocale.country}`;

 			if (firstNameLocalized[locale] && lastNameLocalized[locale]) {
 				return `${firstNameLocalized[locale]} ${lastNameLocalized[locale]}`;
 			}
 			if (firstNameLocalized[locale]) {
 				return firstNameLocalized[locale];
 			}
 		}
 	}

-	// Fallback: return empty string or first available value
 	return typeof firstName === 'string' ? firstName : '';
 };
@@
-	// TODO I believe this find is unnecessary
 	user = (await Users.findOne({
 		_id,
 	})) as Meteor.User & { globalRoles?: string[] };

As per coding guidelines: Avoid code comments in the implementation.

Also applies to: 362-366

apps/meteor/app/apps/server/converters/threads.ts (2)

12-14: Redundant assignment in constructor.

The parameter property private readonly orch already handles the assignment. The explicit this.orch = orch in the constructor body is unnecessary.

♻️ Suggested fix
-	constructor(private readonly orch: IAppServerOrchestrator) {
-		this.orch = orch;
-	}
+	constructor(private readonly orch: IAppServerOrchestrator) {}

42-46: Consider extracting repeated converter access.

this.orch.getConverters().get('users') is called three times. Extracting to a local variable would improve readability.

♻️ Suggested improvement
+		const usersConverter = this.orch.getConverters().get('users');
-		const convertToApp = cachedFunction(this.orch.getConverters().get('users').convertToApp.bind(this.orch.getConverters().get('users')));
-
-		const convertUserById = cachedFunction(this.orch.getConverters().get('users').convertById.bind(this.orch.getConverters().get('users')));
+		const convertToApp = cachedFunction(usersConverter.convertToApp.bind(usersConverter));
+		const convertUserById = cachedFunction(usersConverter.convertById.bind(usersConverter));
apps/meteor/server/configuration/accounts_meld.ts (1)

20-32: Remove the inline parameter comment.
Line 26 adds /* , options*/, which conflicts with the no‑comments guideline.

♻️ Suggested cleanup
-		...args: any[] /* , options*/
+		...args: any[]
apps/meteor/app/custom-oauth/server/transform_helpers.ts (1)

120-138: Array overload should reflect key transformations.
The array overload returns T, but renameInvalidProperties transforms object keys inside arrays, so callers keep stale types. Use TransformArrayItems<T> for array inputs.

♻️ Suggested typing fix
-export function renameInvalidProperties<T extends any[]>(input: T): T;
+export function renameInvalidProperties<T extends any[]>(input: T): TransformArrayItems<T>;
apps/meteor/ee/server/apps/orchestrator.ts (1)

211-215: Drop the inline FIXME comment per guideline.
Keep implementation comment‑free; track the note in an issue if needed.

♻️ Suggested cleanup
-		// FIXME: Logger.debug expects only one argument, but the method signature allows multiple
 		this.getRocketChatLogger().debug(...(args as [any]));
apps/meteor/app/apps/server/bridges/bridges.ts (1)

165-167: Avoid any cast + inline FIXME in getListenerBridge.
To keep typings consistent (and follow the no‑comments guideline), consider updating AppListenerBridge to implement the expected interface and return it directly, then drop the cast/comment.

apps/meteor/app/apps/server/converters/uploads.ts (1)

105-109: Drop inline // FIXME comments in implementation.

Prefer tracking this via an issue or a typed TODO elsewhere rather than inline comments in production code.

Suggested cleanup
-			...{ _updatedAt: upload.updatedAt }, // FIXME
+			...{ _updatedAt: upload.updatedAt },
@@
-			...{ visitorToken }, // FIXME
+			...{ visitorToken },

As per coding guidelines "Avoid code comments in the implementation".

apps/meteor/app/apps/server/converters/rooms.spec.ts (1)

68-78: Use more descriptive test names.

“should return a proper schema” doesn’t convey expected behavior or scenario. Consider naming the expected mapping or conditions directly.

As per coding guidelines "Use descriptive test names that clearly communicate expected behavior in Playwright tests".

Also applies to: 105-115, 117-146

apps/meteor/app/apps/server/converters/messages.spec.ts (1)

74-91: Make test names more specific.

“should return a proper schema” is vague; naming the expected fields/behavior would be clearer.

As per coding guidelines "Use descriptive test names that clearly communicate expected behavior in Playwright tests".

Also applies to: 131-144

apps/meteor/app/apps/server/converters/users.ts (1)

52-52: Remove inline FIXME by typing appId explicitly.

Instead of an inline FIXME, narrow the cast (or extend the type) and drop the comment. As per coding guidelines, Avoid code comments in the implementation.

🔧 Suggested fix
-			appId: (user as any).appId, // FIXME
+			appId: (user as IUser & { appId?: string }).appId,
apps/meteor/app/apps/server/converters/rooms.ts (1)

82-86: Resolve inline FIXME markers in implementation.

Either fix the underlying logic or move the TODOs to an issue tracker; avoid inline FIXME comments in the code. As per coding guidelines, Avoid code comments in the implementation.

Also applies to: 462-462

Comment on lines +341 to 345
timestamp: (attachment: MessageAttachment) => {
const result = new Date(attachment.ts!);
delete attachment.ts;
return result;
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find and examine the file
fd "messages.ts" apps/meteor/app/apps/server/converters/

Repository: RocketChat/Rocket.Chat

Length of output: 116


🏁 Script executed:

# Read the relevant section of the file
cat -n apps/meteor/app/apps/server/converters/messages.ts | sed -n '330,360p'

Repository: RocketChat/Rocket.Chat

Length of output: 819


🏁 Script executed:

# Search for MessageAttachment type definition to understand ts property
rg "interface MessageAttachment|type MessageAttachment" --type ts

Repository: RocketChat/Rocket.Chat

Length of output: 820


🏁 Script executed:

# Look for the converter context to understand the expected return type
cat -n apps/meteor/app/apps/server/converters/messages.ts | sed -n '335,355p'

Repository: RocketChat/Rocket.Chat

Length of output: 596


🏁 Script executed:

# Check the MessageAttachmentDefault to see if ts is optional
cat packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentDefault.ts

Repository: RocketChat/Rocket.Chat

Length of output: 699


🏁 Script executed:

# Check MessageAttachmentBase for ts property
cat packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentBase.ts

Repository: RocketChat/Rocket.Chat

Length of output: 831


🏁 Script executed:

# Look at the converter context - what type does the timestamp field expect?
cat -n apps/meteor/app/apps/server/converters/messages.ts | sed -n '310,350p'

Repository: RocketChat/Rocket.Chat

Length of output: 1694


🏁 Script executed:

# Check what the converter output type is - look for the map or conversion structure
cat -n apps/meteor/app/apps/server/converters/messages.ts | sed -n '1,50p'

Repository: RocketChat/Rocket.Chat

Length of output: 2209


🏁 Script executed:

# Search for how the converter is used to understand expected return type
rg "transformMappedData" apps/meteor/app/apps/server/converters/messages.ts -B 5 -A 5

Repository: RocketChat/Rocket.Chat

Length of output: 1142


🏁 Script executed:

# Check tests to see how attachment.ts is used
fd "messages.test" apps/meteor/app/apps/server/converters/ --type f

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Search for usage of ts in MessageAttachment context
rg "\.ts\s*=" apps/meteor/app/apps/server/converters/messages.ts -B 2 -A 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check the transformMappedData function to understand expected return type
cat -n apps/meteor/app/apps/server/converters/transformMappedData.ts

Repository: RocketChat/Rocket.Chat

Length of output: 6234


🏁 Script executed:

# Check IMessageAttachment from apps-engine to see what the expected output is
rg "timestamp" packages --type ts -A 2 -B 2 | head -50

Repository: RocketChat/Rocket.Chat

Length of output: 3607


🏁 Script executed:

# Look at the actual return type or interface for converted attachments
rg "IMessageAttachment" packages/core-typings -l

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Search for where attachments come from to see if ts is always defined
rg "attachment\.ts" apps/meteor/app/apps/server/converters/messages.ts

Repository: RocketChat/Rocket.Chat

Length of output: 136


🏁 Script executed:

# Check if there's any validation before attachments are processed
cat -n apps/meteor/app/apps/server/converters/messages.ts | sed -n '280,320p'

Repository: RocketChat/Rocket.Chat

Length of output: 1716


🏁 Script executed:

# Verify behavior of new Date(undefined)
node -e "console.log(new Date(undefined)); console.log(isNaN(new Date(undefined))); console.log(new Date(undefined).toString());"

Repository: RocketChat/Rocket.Chat

Length of output: 96


🏁 Script executed:

# Check if there are any tests that show expected behavior with undefined ts
rg "\.ts" apps/meteor/app/apps/server/converters --type ts | grep -i test

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Look at how the converted attachments are consumed - do they expect Date or Date | undefined?
rg "timestamp" apps/meteor/app/apps/server/converters -A 2 -B 2 | grep -A 5 -B 5 "IMessageAttachment\|timestamp"

Repository: RocketChat/Rocket.Chat

Length of output: 3001


🏁 Script executed:

# Check the IMessageAttachment interface definition to see expected types
cat packages/core-typings/src/IMessage/MessageAttachment/MessageAttachment.ts

Repository: RocketChat/Rocket.Chat

Length of output: 627


Guard against undefined ts before creating Date.

attachment.ts is optional in MessageAttachmentBase and can be undefined. The current code uses a non-null assertion, but new Date(undefined) produces an Invalid Date. The sibling converter in threads.ts handles this correctly with a ternary guard.

🔧 Suggested fix
 			timestamp: (attachment: MessageAttachment) => {
-				const result = new Date(attachment.ts!);
-				delete attachment.ts;
-				return result;
+				if (!attachment.ts) {
+					return undefined;
+				}
+				const result = new Date(attachment.ts);
+				delete attachment.ts;
+				return result;
 			},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
timestamp: (attachment: MessageAttachment) => {
const result = new Date(attachment.ts!);
delete attachment.ts;
return result;
},
timestamp: (attachment: MessageAttachment) => {
if (!attachment.ts) {
return undefined;
}
const result = new Date(attachment.ts);
delete attachment.ts;
return result;
},
🤖 Prompt for AI Agents
In `@apps/meteor/app/apps/server/converters/messages.ts` around lines 341 - 345,
The timestamp converter in messages.ts uses a non-null assertion on
attachment.ts causing Invalid Date when undefined; update the timestamp function
(the converter for MessageAttachment) to first check if attachment.ts is defined
(e.g., a ternary or if) and only call new Date(attachment.ts) when present,
otherwise return undefined (matching the threads.ts sibling), and preserve the
delete attachment.ts behavior when ts exists; adjust the return type if
necessary to match other converters.

Comment on lines +18 to 20
async convertByName(roomName: IRoom['name']): Promise<IAppsRoom | undefined> {
const room = await Rooms.findOneByName(roomName!);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file in question
cat -n apps/meteor/app/apps/server/converters/rooms.ts | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 1524


🏁 Script executed:

# Find the IRoom type definition
fd -t f IRoom.ts --search-path . | head -5

Repository: RocketChat/Rocket.Chat

Length of output: 155


🏁 Script executed:

# Search for IRoom definition in the codebase
rg "interface IRoom|type IRoom" -A 5 --type ts --type tsx | head -50

Repository: RocketChat/Rocket.Chat

Length of output: 93


🏁 Script executed:

# Look at the IRoom type definition
cat packages/core-typings/src/IRoom.ts | head -100

Repository: RocketChat/Rocket.Chat

Length of output: 2648


🏁 Script executed:

# Search for Rooms.findOneByName method
rg "findOneByName" -A 3 -B 1 --type ts | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 2551


🏁 Script executed:

# Search for Rooms.findOneByName specifically
rg "findOneByName\(" packages/models/src/models/Rooms.ts -A 5 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 369


🏁 Script executed:

# Check the Rooms model interface/class definition
cat packages/models/src/models/Rooms.ts | grep -A 3 "findOneByName"

Repository: RocketChat/Rocket.Chat

Length of output: 826


Add a guard clause to handle undefined room names.

IRoom['name'] is optional (string | undefined), but Rooms.findOneByName() expects a non-null value. The non-null assertion bypasses this type safety check.

🔧 Suggested fix
 async convertByName(roomName: IRoom['name']): Promise<IAppsRoom | undefined> {
-		const room = await Rooms.findOneByName(roomName!);
+		if (!roomName) {
+			return undefined;
+		}
+		const room = await Rooms.findOneByName(roomName);
 
 		return this.convertRoom(room);
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async convertByName(roomName: IRoom['name']): Promise<IAppsRoom | undefined> {
const room = await Rooms.findOneByName(roomName!);
async convertByName(roomName: IRoom['name']): Promise<IAppsRoom | undefined> {
if (!roomName) {
return undefined;
}
const room = await Rooms.findOneByName(roomName);
return this.convertRoom(room);
}
🤖 Prompt for AI Agents
In `@apps/meteor/app/apps/server/converters/rooms.ts` around lines 18 - 20,
convertByName currently uses a non-null assertion on IRoom['name'] when calling
Rooms.findOneByName; add a guard clause at the start of convertByName to handle
undefined roomName (e.g., if (!roomName) return undefined) and remove the
non-null assertion so Rooms.findOneByName is only called with a real string;
update convertByName to explicitly return undefined for missing names and
proceed with the lookup when roomName is present.

Comment on lines +16 to 18
async convertByUsername(username: IUser['username']): Promise<IAppsUser | undefined> {
const user = await Users.findOneByUsername(username!);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find the IUser type definition
find . -type f -name "*.ts" -o -name "*.tsx" | head -20
rg "interface IUser|type IUser" --type ts -A 5 | head -50

Repository: RocketChat/Rocket.Chat

Length of output: 4633


🏁 Script executed:

# Look at the file under review
find . -path "*/apps/meteor/app/apps/server/converters/users.ts" -type f

Repository: RocketChat/Rocket.Chat

Length of output: 115


🏁 Script executed:

# Search for Users.findOneByUsername implementation
rg "findOneByUsername" --type ts -B 2 -A 5 | head -80

Repository: RocketChat/Rocket.Chat

Length of output: 6285


🏁 Script executed:

# Get the full IUser interface definition
rg "export interface IUser" --type ts -A 30 packages/core-typings/src/IUser.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1055


🏁 Script executed:

# Read the file under review
cat -n apps/meteor/app/apps/server/converters/users.ts

Repository: RocketChat/Rocket.Chat

Length of output: 4889


🏁 Script executed:

# Check what IAppsUser is
rg "interface IAppsUser|type IAppsUser" --type ts -A 10 | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 48


Guard against undefined username before querying.

IUser['username'] is optional (string | undefined); the non-null assertion masks this and would pass undefined to Users.findOneByUsername(), creating a query with undefined instead of handling it explicitly.

🔧 Suggested fix
 async convertByUsername(username: IUser['username']): Promise<IAppsUser | undefined> {
-		const user = await Users.findOneByUsername(username!);
+		if (!username) {
+			return undefined;
+		}
+		const user = await Users.findOneByUsername(username);
 
 		return this.convertToApp(user);
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async convertByUsername(username: IUser['username']): Promise<IAppsUser | undefined> {
const user = await Users.findOneByUsername(username!);
async convertByUsername(username: IUser['username']): Promise<IAppsUser | undefined> {
if (!username) {
return undefined;
}
const user = await Users.findOneByUsername(username);
return this.convertToApp(user);
}
🤖 Prompt for AI Agents
In `@apps/meteor/app/apps/server/converters/users.ts` around lines 16 - 18, The
convertByUsername function currently uses a non-null assertion on
IUser['username'] and may pass undefined into Users.findOneByUsername; update
convertByUsername to explicitly guard for undefined by checking the username
parameter at the top (e.g., if (!username) return undefined or handle error) and
remove the non-null assertion so you only call Users.findOneByUsername(username)
when username is a defined string; reference convertByUsername and
Users.findOneByUsername in your change.

Comment on lines +43 to 45
return WebApp.connectHandlers.use('/custom-sounds/', async (req: IncomingMessage, res: ServerResponse /* , next*/) => {
const fileId = decodeURIComponent(req.url?.replace(/^\//, '').replace(/\?.*$/, '') || '');

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

JavaScript decodeURIComponent throw malformed percent-encoding

💡 Result:

decodeURIComponent() throws URIError: malformed URI sequence when the input is not valid percent-encoding / UTF‑8—i.e., it contains a % that isn’t followed by two hex digits (0-9A-Fa-f), or it contains incomplete/invalid UTF‑8 byte sequences (example: "%E0%A4%A"). [1]

Common fixes:

  • Encode literal % as %25 (a raw % in user input will break decoding).
  • If decoding query strings from form encoding, convert + to space first:
    const s = decodeURIComponent(str.replace(/\+/g, " "));
  • Validate or guard before decoding:
    function safeDecodeURIComponent(str) {
      try { return decodeURIComponent(str); }
      catch { return str; } // or handle/log/reject
    }
  • If you want to pre-check for obvious bad % sequences:
    const hasBadPercents = /%(?![0-9A-Fa-f]{2})/.test(str);

Best practice: only call decodeURIComponent on strings you know were produced by proper URL encoding (e.g., encodeURIComponent), or treat invalid inputs as data-quality errors and handle them explicitly. [1]

Sources
[1] MDN – “URIError: malformed URI sequence” (covers invalid encoding/decoding and the decodeURIComponent("%E0%A4%A") example) (developer.mozilla.org)

Citations:


🏁 Script executed:

cat -n apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts | head -80

Repository: RocketChat/Rocket.Chat

Length of output: 3006


🏁 Script executed:

rg "decodeURIComponent" apps/meteor/app/custom-sounds/ -B 2 -A 2

Repository: RocketChat/Rocket.Chat

Length of output: 608


🏁 Script executed:

rg "decodeURIComponent" apps/meteor/ -B 3 -A 3 --max-count=5

Repository: RocketChat/Rocket.Chat

Length of output: 9769


Guard decodeURIComponent against malformed URLs.
Malformed percent-encoding (e.g., %2 or incomplete UTF-8 sequences) will throw URIError and bubble up as a 500 response. Return 400 instead to signal client error.

🔧 Suggested fix
-		const fileId = decodeURIComponent(req.url?.replace(/^\//, '').replace(/\?.*$/, '') || '');
+		let fileId = '';
+		try {
+			fileId = decodeURIComponent(req.url?.replace(/^\//, '').replace(/\?.*$/, '') || '');
+		} catch {
+			res.writeHead(400);
+			res.write('Bad Request');
+			res.end();
+			return;
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return WebApp.connectHandlers.use('/custom-sounds/', async (req: IncomingMessage, res: ServerResponse /* , next*/) => {
const fileId = decodeURIComponent(req.url?.replace(/^\//, '').replace(/\?.*$/, '') || '');
return WebApp.connectHandlers.use('/custom-sounds/', async (req: IncomingMessage, res: ServerResponse /* , next*/) => {
let fileId = '';
try {
fileId = decodeURIComponent(req.url?.replace(/^\//, '').replace(/\?.*$/, '') || '');
} catch {
res.writeHead(400);
res.write('Bad Request');
res.end();
return;
}
🤖 Prompt for AI Agents
In `@apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts` around lines
43 - 45, The call to decodeURIComponent when computing fileId inside the
WebApp.connectHandlers.use callback can throw a URIError for malformed
percent-encoding; wrap the decodeURIComponent(...) that defines fileId in a
try/catch, catch URIError (and other decoding errors), and return a 400 response
(set statusCode 400 and end with a short error message) instead of allowing the
error to bubble to a 500; update the handler around the fileId computation in
this connectHandlers callback so subsequent logic only runs when decoding
succeeds.

}

res.setHeader('Content-Type', file.contentType);
res.setHeader('Content-Type', file.contentType!);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts | sed -n '75,95p'

Repository: RocketChat/Rocket.Chat

Length of output: 582


🏁 Script executed:

rg -A 5 -B 5 "res.setHeader\('Content-Type'" apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts

Repository: RocketChat/Rocket.Chat

Length of output: 332


🏁 Script executed:

rg "contentType" apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts -A 2 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 171


🌐 Web query:

Node.js ServerResponse setHeader undefined value behavior throw error

💡 Result:

In Node.js, calling res.setHeader(name, undefined) (or outgoingMessage.setHeader) throws a synchronous TypeError with code: 'ERR_HTTP_INVALID_HEADER_VALUE' (message like Invalid value "undefined" for header "X"). This is part of Node’s built-in header validation (http.validateHeaderValue() documents the same behavior for undefined). [1]

setHeader() only accepts header values that can be sent as HTTP header content (documented as number | string | string[]), so undefined is treated as an invalid value rather than “skip setting”. [2]

If you want “no header when the value is missing”, either:

  • don’t call setHeader when the value is null/undefined, or
  • remove it: res.removeHeader(name) (if it may have been set earlier). [2]

Rationale: undefined usually indicates a bug (e.g., an unset variable), so Node throws to surface it early instead of silently sending a wrong/missing header. [3]

Sources:
[1] Node.js HTTP docs — http.validateHeaderValue() (undefined => ERR_HTTP_INVALID_HEADER_VALUE) https://nodejs.org/api/http.html
[2] Node.js HTTP docs — response.setHeader() / response.removeHeader() https://nodejs.org/api/http.html
[3] Stack Overflow discussion on ERR_HTTP_INVALID_HEADER_VALUE with undefined https://stackoverflow.com/questions/63032787/why-is-undefined-illegal-as-a-request-header-value-of-nodes-lib-http-js


🏁 Script executed:

rg "file:" apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts -B 10 -A 3 | head -40

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

rg -A 20 "getFileByPath\|findFileByPath" apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

rg "contentType" apps/meteor/app/custom-sounds/ -t ts --max-count=15

Repository: RocketChat/Rocket.Chat

Length of output: 689


🏁 Script executed:

head -n 100 apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts | tail -n 50

Repository: RocketChat/Rocket.Chat

Length of output: 1426


🏁 Script executed:

cat -n apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts | sed -n '1,90p'

Repository: RocketChat/Rocket.Chat

Length of output: 3339


🏁 Script executed:

find apps/meteor -name "*.ts" -type f | xargs grep -l "class RocketChatFile" | head -5

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

rg "getFileWithReadStream" apps/meteor -A 10 -t ts | head -50

Repository: RocketChat/Rocket.Chat

Length of output: 3437


🏁 Script executed:

find apps/meteor/app/file -name "*.ts" -type f | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 145


Use a fallback for contentType to handle cases where it may be undefined.

The contentType property is typed as optional (contentType?: string), but the non-null assertion (!) only suppresses TypeScript warnings and doesn't prevent runtime errors. If the file lacks a contentType (possible with GridFS storage), Node.js setHeader will throw ERR_HTTP_INVALID_HEADER_VALUE since it rejects undefined values. The FileSystem implementation already uses 'application/octet-stream' as a fallback; apply the same pattern here:

🔧 Suggested fix
-		res.setHeader('Content-Type', file.contentType!);
+		res.setHeader('Content-Type', file.contentType || 'application/octet-stream');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
res.setHeader('Content-Type', file.contentType!);
res.setHeader('Content-Type', file.contentType || 'application/octet-stream');
🤖 Prompt for AI Agents
In `@apps/meteor/app/custom-sounds/server/startup/custom-sounds.ts` at line 86,
Replace the non-null assertion on file.contentType when calling res.setHeader:
instead of using file.contentType! pass a safe fallback value (e.g.
'application/octet-stream') so that res.setHeader('Content-Type', ...) never
receives undefined; locate the call to res.setHeader and update it to use
file.contentType ?? 'application/octet-stream' (or equivalent) to match the
FileSystem implementation.

Comment on lines +143 to +164
const callback =
(msg: string, name: string) =>
async (reply: RateLimiterReply, input: RateLimiterInput): Promise<void> => {
if (reply.allowed === false) {
rateLimiterLog({ msg, reply, input });
metrics.ddpRateLimitExceeded.inc({
limit_name: name,
user_id: input.userId,
client_address: input.clientAddress,
type: input.type,
name: input.name,
connection_id: input.connectionId,
});
// sleep before sending the error to slow down next requests
if (slowDownRate > 0 && reply.numInvocationsExceeded) {
await sleep(slowDownRate * reply.numInvocationsExceeded);
}
// } else {
// console.log('DDP RATE LIMIT:', message);
// console.log(JSON.stringify({ ...reply, ...input }, null, 2));
}
// } else {
// console.log('DDP RATE LIMIT:', message);
// console.log(JSON.stringify({ ...reply, ...input }, null, 2));
}
};
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's read the file to understand the context around the callback invocation
wc -l apps/meteor/app/lib/server/startup/rateLimiter.ts

Repository: RocketChat/Rocket.Chat

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Read the relevant sections of the file
cat -n apps/meteor/app/lib/server/startup/rateLimiter.ts | sed -n '110,170p'

Repository: RocketChat/Rocket.Chat

Length of output: 2717


🏁 Script executed:

#!/bin/bash
# Search for _executeCallback to understand how it's implemented and used
rg -n '_executeCallback' --type ts --type js -C 2

Repository: RocketChat/Rocket.Chat

Length of output: 1057


🏁 Script executed:

#!/bin/bash
# Search for where rule is created and how the callback is registered
rg -n 'rule.onError|rule._executeCallback|addRule|createRule' apps/meteor/app/lib/server/startup/rateLimiter.ts -B 3 -A 1

Repository: RocketChat/Rocket.Chat

Length of output: 1136


🏁 Script executed:

#!/bin/bash
# Look for imports from meteor rate-limit or similar
rg -n 'import|from|require' apps/meteor/app/lib/server/startup/rateLimiter.ts | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 459


🏁 Script executed:

#!/bin/bash
# Search for where callback is actually assigned to rule
rg -n '\.onError\s*=' apps/meteor/app/lib/server/startup/rateLimiter.ts -C 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look for where the callback is actually registered with the rule
rg -n 'callback' apps/meteor/app/lib/server/startup/rateLimiter.ts | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 951


🏁 Script executed:

#!/bin/bash
# Check the full context around the addRule call
cat -n apps/meteor/app/lib/server/startup/rateLimiter.ts | sed -n '175,200p'

Repository: RocketChat/Rocket.Chat

Length of output: 960


🏁 Script executed:

#!/bin/bash
# Search for how rules are created in the Meteor package files
find . -type f -name "*.ts" -o -name "*.js" | xargs grep -l "DDPRateLimiter\|_executeCallback" 2>/dev/null | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 580


🏁 Script executed:

#!/bin/bash
# Check the type definitions for DDPRateLimiter
cat -n ./apps/meteor/definition/externals/meteor/ddp-rate-limiter.d.ts

Repository: RocketChat/Rocket.Chat

Length of output: 441


🏁 Script executed:

#!/bin/bash
# Search for _executeCallback in the codebase to understand what it does
rg -n '_executeCallback' . --type ts --type js -B 5 -A 5

Repository: RocketChat/Rocket.Chat

Length of output: 2162


🏁 Script executed:

#!/bin/bash
# Let's check if there's any Meteor rate-limit package source or docs in the repo
find . -type f \( -name "*.ts" -o -name "*.js" -o -name "*.d.ts" \) | xargs grep -l "rate-limit\|RateLimiter" 2>/dev/null | head -15

Repository: RocketChat/Rocket.Chat

Length of output: 798


🏁 Script executed:

#!/bin/bash
# Check if there's any information about how callbacks are supposed to work
rg -n 'onError|_executeCallback' ./apps/meteor/app/lib/server/lib/RateLimiter.ts -C 3

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look at the entire context of the override to understand what's being done
cat -n apps/meteor/app/lib/server/startup/rateLimiter.ts | sed -n '60,140p'

Repository: RocketChat/Rocket.Chat

Length of output: 3840


🏁 Script executed:

#!/bin/bash
# Let's check the RateLimiter.ts to understand how the framework is being used
cat -n ./apps/meteor/app/lib/server/lib/RateLimiter.ts

Repository: RocketChat/Rocket.Chat

Length of output: 841


🏁 Script executed:

#!/bin/bash
# Search for any documentation or comments about async callback handling in Meteor packages
rg -n 'async|promise|Promise' apps/meteor/app/lib/server/startup/rateLimiter.ts

Repository: RocketChat/Rocket.Chat

Length of output: 147


🏁 Script executed:

#!/bin/bash
# Check if there are any tests or usage examples showing how async callbacks should work
find . -type f \( -name "*.test.ts" -o -name "*.spec.ts" \) | xargs grep -l "rateLimiter\|RateLimiter" 2>/dev/null

Repository: RocketChat/Rocket.Chat

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Search for how the callback function is assigned/registered to understand the flow
cat -n apps/meteor/app/lib/server/startup/rateLimiter.ts | sed -n '183,190p'

Repository: RocketChat/Rocket.Chat

Length of output: 388


🏁 Script executed:

#!/bin/bash
# Let's trace backwards - see what callback function is passed to addRule
rg -n 'callback\(' apps/meteor/app/lib/server/startup/rateLimiter.ts -A 2 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 298


🏁 Script executed:

#!/bin/bash
# Search for documentation or source comments about how Meteor's rate-limit handles callbacks
rg -n 'onError\|_executeCallback\|callback' . --type ts --type d.ts | grep -i "meteor\|rate" | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Let's search for any usage of the rule._executeCallback in context
rg -n 'rule\.' apps/meteor/app/lib/server/startup/rateLimiter.ts | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 923


🏁 Script executed:

#!/bin/bash
# Check if there are any other places where callbacks are being used with async/await patterns
rg -n 'await.*callback|callback.*await' . --type ts --type js

Repository: RocketChat/Rocket.Chat

Length of output: 17143


🏁 Script executed:

#!/bin/bash
# Let's see if there's any information about how the callback is supposed to work by looking at comments
cat -n apps/meteor/app/lib/server/startup/rateLimiter.ts | sed -n '1,70p'

Repository: RocketChat/Rocket.Chat

Length of output: 3165


Async callback not awaited; slowdown delay won't block rate-limited responses.

The callback at lines 143-164 is async and awaits sleep() when rate-limited, but rule._executeCallback() at lines 118 and 131 calls it without await. Since check() is synchronous and returns immediately, the Promise from the async callback is fire-and-forget, and the sleep delay won't actually block the response. The slowDownRate feature won't have its intended effect. Either await the callback's Promise or restructure to handle async callbacks in a synchronous context.

🤖 Prompt for AI Agents
In `@apps/meteor/app/lib/server/startup/rateLimiter.ts` around lines 143 - 164,
The rate-limit slowdown is ineffective because the async callback (callback)
that awaits sleep() is invoked via rule._executeCallback() from check() without
awaiting its Promise; update the call sites where rule._executeCallback(...) is
invoked (the two places that call it from check()) to await the returned Promise
and make check() async (propagate async/await) so the sleep actually blocks, or
alternatively convert the callback to a synchronous path (remove async/sleep) if
making check() async is undesirable—ensure consistency between callback
(function callback) and its invoker rule._executeCallback() so the slowdown
delay is awaited.

Comment on lines 87 to 97
it.skip('should return without the "." from "#general."', () => {
expect(result).to.be.deep.equal(mentionsParser.getUserMentions('#general.'));
expect(result).toEqual(mentionsParser.getUserMentions('#general.'));
});

it.skip('should return without the "_" from "#general_"', () => {
expect(result).to.be.deep.equal(mentionsParser.getUserMentions('#general_'));
expect(result).toEqual(mentionsParser.getUserMentions('#general_'));
});

it.skip('should return without the "-" from "#general."', () => {
expect(result).to.be.deep.equal(mentionsParser.getUserMentions('#general-'));
expect(result).toEqual(mentionsParser.getUserMentions('#general-'));
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Skipped tests call the wrong method.

These skipped tests are in the getChannelMentions describe block but call getUserMentions instead of getChannelMentions. If these tests are ever unskipped, they will test the wrong functionality.

🐛 Proposed fix
 		it.skip('should return without the "." from "#general."', () => {
-			expect(result).toEqual(mentionsParser.getUserMentions('#general.'));
+			expect(result).toEqual(mentionsParser.getChannelMentions('#general.'));
 		});

 		it.skip('should return without the "_" from "#general_"', () => {
-			expect(result).toEqual(mentionsParser.getUserMentions('#general_'));
+			expect(result).toEqual(mentionsParser.getChannelMentions('#general_'));
 		});

 		it.skip('should return without the "-" from "#general."', () => {
-			expect(result).toEqual(mentionsParser.getUserMentions('#general-'));
+			expect(result).toEqual(mentionsParser.getChannelMentions('#general-'));
 		});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it.skip('should return without the "." from "#general."', () => {
expect(result).to.be.deep.equal(mentionsParser.getUserMentions('#general.'));
expect(result).toEqual(mentionsParser.getUserMentions('#general.'));
});
it.skip('should return without the "_" from "#general_"', () => {
expect(result).to.be.deep.equal(mentionsParser.getUserMentions('#general_'));
expect(result).toEqual(mentionsParser.getUserMentions('#general_'));
});
it.skip('should return without the "-" from "#general."', () => {
expect(result).to.be.deep.equal(mentionsParser.getUserMentions('#general-'));
expect(result).toEqual(mentionsParser.getUserMentions('#general-'));
});
it.skip('should return without the "." from "#general."', () => {
expect(result).toEqual(mentionsParser.getChannelMentions('#general.'));
});
it.skip('should return without the "_" from "#general_"', () => {
expect(result).toEqual(mentionsParser.getChannelMentions('#general_'));
});
it.skip('should return without the "-" from "#general."', () => {
expect(result).toEqual(mentionsParser.getChannelMentions('#general-'));
});
🤖 Prompt for AI Agents
In `@apps/meteor/app/mentions/lib/MentionsParser.spec.ts` around lines 87 - 97,
The three skipped tests in the getChannelMentions describe block incorrectly
call mentionsParser.getUserMentions; update each skipped spec to call
mentionsParser.getChannelMentions instead (the tests titled 'should return
without the "." from "#general."', 'should return without the "_" from
"#general_"', and 'should return without the "-" from "#general."') so that when
unskipped they exercise the correct function; keep the rest of each expectation
the same.

Comment on lines +2 to +12
export const Google: {
readonly name: string;
readonly whitelistedFields: string[];
requestCredential(
options: Meteor.LoginWithExternalServiceOptions | undefined,
credentialRequestCompleteCallback: (credentialTokenOrError?: string | Error) => void,
): void;
signIn?(
options: Meteor.LoginWithExternalServiceOptions | undefined,
callback?: (error: LoginError | undefined, result?: unknown) => void,
): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for LoginError declaration and usage in the codebase
echo "=== Searching for LoginError declaration/definition ==="
rg "type LoginError|interface LoginError|declare.*LoginError" --type ts --type tsx --type=d.ts -B 2 -A 2

echo ""
echo "=== Checking google-oauth.d.ts context and imports ==="
head -20 apps/meteor/definition/externals/meteor/google-oauth.d.ts

echo ""
echo "=== Checking for LoginError in related Meteor type definitions ==="
fd "\.d\.ts$" apps/meteor/definition/externals/meteor/ --exec grep -l "LoginError" {}

Repository: RocketChat/Rocket.Chat

Length of output: 826


🏁 Script executed:

#!/bin/bash
# Search for LoginError everywhere in the codebase
echo "=== All occurrences of LoginError ==="
rg "LoginError" -t ts -t js -t d.ts --context=3

echo ""
echo "=== Check node_modules or meteor types (if available) ==="
find . -path ./node_modules -prune -o -name "*.d.ts" -type f -print | head -20 | xargs grep -l "LoginError" 2>/dev/null || echo "No matches in .d.ts files outside node_modules"

echo ""
echo "=== Check the full google-oauth.d.ts file ==="
cat apps/meteor/definition/externals/meteor/google-oauth.d.ts

Repository: RocketChat/Rocket.Chat

Length of output: 780


🏁 Script executed:

#!/bin/bash
# Check other meteor definition files for how they import/use account types
echo "=== List all meteor definition files ==="
fd "\.d\.ts$" apps/meteor/definition/externals/meteor/

echo ""
echo "=== Check accounts-base.d.ts or similar for error types ==="
cat apps/meteor/definition/externals/meteor/accounts-base.d.ts 2>/dev/null | grep -A 5 -B 5 "Error\|error" | head -40

echo ""
echo "=== Check if any other files import from accounts-base ==="
rg "accounts-base" apps/meteor/definition/externals/meteor/ -A 2 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 3430


🏁 Script executed:

#!/bin/bash
# Check similar OAuth files to see if they have the same issue
echo "=== facebook-oauth.d.ts ==="
cat apps/meteor/definition/externals/meteor/facebook-oauth.d.ts

echo ""
echo "=== twitter-oauth.d.ts ==="
cat apps/meteor/definition/externals/meteor/twitter-oauth.d.ts

Repository: RocketChat/Rocket.Chat

Length of output: 263


Add missing LoginError type to this module declaration.

Line 11 references LoginError which is not defined in the codebase. Either import or declare this type within the module. If LoginError should match Meteor's standard error types, consider using Meteor.Error instead, or define LoginError as a type alias if it has a specific shape.

🤖 Prompt for AI Agents
In `@apps/meteor/definition/externals/meteor/google-oauth.d.ts` around lines 2 -
12, The module declares Google and references LoginError but LoginError is
missing; fix by adding a local type/import for LoginError in this declaration
file—either declare type LoginError = Meteor.Error (or import Meteor and alias
Meteor.Error as LoginError) or define the LoginError shape expected by signIn;
update the google-oauth.d.ts module so the referenced LoginError type is defined
alongside the existing Google, requestCredential, and signIn symbols.

Comment on lines +4 to +6
findOneById(id: string): T | null {
return this.data[id];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Return type mismatch: returns undefined but declares T | null.

When id is not found in data, the property access returns undefined, not null. This creates a type inconsistency that could cause issues in consuming code expecting null for missing entries.

🔧 Option 1: Fix return type to match behavior
-	findOneById(id: string): T | null {
-		return this.data[id];
+	findOneById(id: string): T | undefined {
+		return this.data[id];
 	}
🔧 Option 2: Explicitly return null for missing entries
 	findOneById(id: string): T | null {
-		return this.data[id];
+		return this.data[id] ?? null;
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
findOneById(id: string): T | null {
return this.data[id];
}
findOneById(id: string): T | undefined {
return this.data[id];
}
Suggested change
findOneById(id: string): T | null {
return this.data[id];
}
findOneById(id: string): T | null {
return this.data[id] ?? null;
}
🤖 Prompt for AI Agents
In `@apps/meteor/tests/unit/app/apps/server/mocks/models/BaseModel.mock.ts` around
lines 4 - 6, findOneById declares return type T | null but currently returns
this.data[id] which yields undefined when not present; update the function to be
consistent by either changing its signature to return T | undefined or by
explicitly returning null for missing entries (e.g., in findOneById use a null
coalescing behavior to return this.data[id] ?? null). Ensure references to the
data property and the findOneById method are updated so callers and types align.

Comment on lines +5 to +6
export class MessagesMock extends BaseModelMock<IMessage> {
override data = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep data protected when overriding BaseModelMock.

BaseModelMock declares data as protected, but the override lacks a visibility modifier, unintentionally widening it to public.

🔧 Suggested fix
 export class MessagesMock extends BaseModelMock<IMessage> {
-	override data = {
+	protected override data = {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export class MessagesMock extends BaseModelMock<IMessage> {
override data = {
export class MessagesMock extends BaseModelMock<IMessage> {
protected override data = {
🤖 Prompt for AI Agents
In `@apps/meteor/tests/unit/app/apps/server/mocks/models/Messages.mock.ts` around
lines 5 - 6, Override of BaseModelMock's property "data" in class MessagesMock
currently lacks visibility and becomes public; change the declaration in
MessagesMock to explicitly use the protected modifier (i.e., make "protected
data = { ... }") so it matches BaseModelMock's protected visibility and
preserves encapsulation for the IMessage data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant