Skip to content

Commit 536bdcc

Browse files
committed
fix: address CodeRabbit review feedback on #3370
Hardening fixes from CodeRabbit review (verified against code, false positives rejected): Security / stability: - videoCallWindow/ipc.ts: validate URL protocol (http/https) before the g.co external-open escape hatch (was reachable via ftp://g.co/...); tighten host match to exact g.co / *.g.co (was overmatching evilg.co); guard getRootWindow() rejection in restoreServerViewHandler so teardown can't produce an unhandled rejection (display-media restore still runs). - serverView/index.ts: gate the 'Permission request' debug log behind NODE_ENV==='development' (was logging payloads in production); wrap isProtocolAllowed() in try/catch so the openExternal permission callback always fires even on malformed URLs. - telephony/main.ts: handle shell.openExternal() promise rejection with .catch(); attach 'error' listeners to gnome/kcmshell spawns before unref() so a missing executable can't throw unhandled. - screenSharing/serverViewScreenSharing.ts: reset cached init state on provider init failure so later requests can retry instead of reusing a rejected promise until restart. - telephony/dialpad.ts: guard webContents.send against a destroyed handle. Data integrity: - PersistableValues.ts: preserve persisted telephonyGlobalShortcutConfig in the >=4.14.0 migration instead of resetting it to defaults on upgrade. UI / i18n: - TelephonyDiagnostics.tsx: catch rejected get-diagnostics IPC and set a controlled empty state instead of leaking an unhandled rejection. - i18n/ar, i18n/es: split reservedAccelerator into reservedByApp / reservedByOS to match the runtime keys (other locales fall back to en). QA tooling / installer: - validate-flows.mjs: enforce qase_id PRESENCE (key may be null per the flow contract) rather than truthiness, which would have rejected every existing flow; handle CRLF frontmatter delimiters. - export-qase-csv.mjs: normalize CRLF on read for cross-platform parsing. - package.json: declare yaml as a devDependency (used by export-qase-csv). - msiProjectCreated.js: fail fast on telephony RegWrite errors in the WriteTelephonyCapabilities custom action. Tests: - main.spec.ts: mock shell.openExternal as a resolved promise. - dialpad.spec.ts / deepLinks/main.spec.ts: add isDestroyed() to webContents mocks for the new destroyed-handle guard. - TelephonyGlobalShortcut.spec.tsx: add configurable:true so the process.platform restore in afterAll doesn't throw. Rejected: - Rename telephony/main.spec.ts -> main.main.spec.ts: false positive. jest testMatch already routes src/**/main.spec.ts to the main-process project; the rename would break discovery. Verified: tsc clean, lint clean, full suite 456 passed / 2 skipped / 0 failed, validate-flows passes all 14 telephony flows.
1 parent 070cd27 commit 536bdcc

17 files changed

Lines changed: 114 additions & 33 deletions

File tree

build/msiProjectCreated.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ exports.default = async function msiProjectCreated(projectFile) {
210210
Return="check">
211211
<![CDATA[
212212
On Error Resume Next
213-
Dim shell, installDir, exePath
213+
Dim shell, installDir, exePath, writeErr
214214
215215
Set shell = CreateObject("WScript.Shell")
216216
installDir = Session.Property("CustomActionData")
@@ -224,6 +224,11 @@ exports.default = async function msiProjectCreated(projectFile) {
224224
exePath = Chr(34) & installDir & "Rocket.Chat.exe" & Chr(34) & " " & Chr(34) & "%1" & Chr(34)
225225
226226
shell.RegWrite "HKLM\\SOFTWARE\\Classes\\RocketChat.tel\\", "URL:Rocket.Chat Telephony", "REG_SZ"
227+
If Err.Number <> 0 Then
228+
writeErr = Err.Description
229+
Err.Clear
230+
Err.Raise 1, "WriteTelephonyCapabilities", "Failed to write HKLM\\SOFTWARE\\Classes\\RocketChat.tel: " & writeErr
231+
End If
227232
shell.RegWrite "HKLM\\SOFTWARE\\Classes\\RocketChat.tel\\URL Protocol", "", "REG_SZ"
228233
shell.RegWrite "HKLM\\SOFTWARE\\Classes\\RocketChat.tel\\DefaultIcon\\", installDir & "Rocket.Chat.exe,0", "REG_SZ"
229234
shell.RegWrite "HKLM\\SOFTWARE\\Classes\\RocketChat.tel\\shell\\open\\command\\", exePath, "REG_SZ"
@@ -239,6 +244,11 @@ exports.default = async function msiProjectCreated(projectFile) {
239244
shell.RegWrite "HKLM\\SOFTWARE\\Rocket.Chat\\Capabilities\\URLAssociations\\tel", "RocketChat.tel", "REG_SZ"
240245
shell.RegWrite "HKLM\\SOFTWARE\\Rocket.Chat\\Capabilities\\URLAssociations\\callto", "RocketChat.callto", "REG_SZ"
241246
shell.RegWrite "HKLM\\SOFTWARE\\RegisteredApplications\\Rocket.Chat", "Software\\Rocket.Chat\\Capabilities", "REG_SZ"
247+
If Err.Number <> 0 Then
248+
writeErr = Err.Description
249+
Err.Clear
250+
Err.Raise 1, "WriteTelephonyCapabilities", "Failed to write HKLM\\SOFTWARE\\RegisteredApplications\\Rocket.Chat: " & writeErr
251+
End If
242252
]]>
243253
</CustomAction>
244254

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@
151151
"ts-jest": "~29.1.4",
152152
"ts-node": "~10.9.2",
153153
"typescript": "~5.7.3",
154-
"xvfb-maybe": "~0.2.1"
154+
"xvfb-maybe": "~0.2.1",
155+
"yaml": "^1.10.2"
155156
},
156157
"optionalDependencies": {
157158
"fsevents": "2.3.3"

qa/scripts/export-qase-csv.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const extractSection = (content, heading) => {
7878
};
7979

8080
const parseFlow = (filePath) => {
81-
const content = fs.readFileSync(filePath, 'utf8');
81+
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
8282
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
8383

8484
if (!frontmatterMatch) {

qa/scripts/validate-flows.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const errors = [];
5252

5353
const parseFlow = (filePath) => {
5454
const content = fs.readFileSync(filePath, 'utf8');
55-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
55+
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
5656

5757
if (!frontmatterMatch) {
5858
throw new Error('missing YAML frontmatter');
@@ -154,6 +154,10 @@ if (!fs.existsSync(flowsPath)) {
154154
errors.push(`${file}: frontmatter.qase.${field} is required`);
155155
}
156156
}
157+
// qase_id must be present but may be null for not-yet-imported flows.
158+
if (!('qase_id' in frontmatter.qase)) {
159+
errors.push(`${file}: frontmatter.qase.qase_id is required`);
160+
}
157161
}
158162

159163
if (stepRows.length === 0) {

src/app/PersistableValues.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,12 @@ export const migrations = {
221221
(before as Partial<PersistableValues_4_14_0>).telephonyPreferredServer ??
222222
null,
223223
telephonyGlobalShortcutConfig: {
224-
enabled: false,
225-
accelerator: null,
224+
enabled:
225+
(before as Partial<PersistableValues_4_14_0>)
226+
.telephonyGlobalShortcutConfig?.enabled ?? false,
227+
accelerator:
228+
(before as Partial<PersistableValues_4_14_0>)
229+
.telephonyGlobalShortcutConfig?.accelerator ?? null,
226230
},
227231
}),
228232
'>=4.15.0': (before: PersistableValues_4_14_0): PersistableValues_4_15_0 => ({

src/deepLinks/main.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ describe('deepLinks/main.ts', () => {
190190
describe('performTelephonyCall', () => {
191191
const mockWebContents = {
192192
send: jest.fn(),
193+
isDestroyed: jest.fn(() => false),
193194
};
194195

195196
const mockLink: TelephonyLink = {
@@ -500,6 +501,7 @@ describe('deepLinks/main.ts', () => {
500501

501502
const mockWebContents = {
502503
send: jest.fn(),
504+
isDestroyed: jest.fn(() => false),
503505
loadURL: jest.fn(),
504506
};
505507

@@ -655,6 +657,7 @@ describe('deepLinks/main.ts', () => {
655657

656658
const mockWebContents = {
657659
send: jest.fn(),
660+
isDestroyed: jest.fn(() => false),
658661
loadURL: jest.fn(),
659662
};
660663

src/i18n/ar.i18n.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"save": "حفظ",
3333
"clear": "مسح",
3434
"registered": "تم تسجيل الاختصار",
35-
"reservedAccelerator": "{{accelerator}} محجوز بواسطة Rocket.Chat أو نظام التشغيل لديك."
35+
"reservedByApp": "{{accelerator}} مستخدم بالفعل بواسطة Rocket.Chat. اختر تركيبة أخرى.",
36+
"reservedByOS": "{{accelerator}} محجوز بواسطة نظام التشغيل لديك. اختر تركيبة أخرى."
3637
}
3738
}
3839
},

src/i18n/es.i18n.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,8 @@
298298
"save": "Guardar",
299299
"clear": "Borrar",
300300
"registered": "Atajo registrado",
301-
"reservedAccelerator": "{{accelerator}} está reservado por Rocket.Chat o por tu sistema operativo."
301+
"reservedByApp": "{{accelerator}} ya está en uso por Rocket.Chat. Elige otra combinación.",
302+
"reservedByOS": "{{accelerator}} está reservado por tu sistema operativo. Elige otra combinación."
302303
},
303304
"clearPermittedScreenCaptureServers": {
304305
"title": "Borrar permisos de captura de pantalla",

src/screenSharing/serverViewScreenSharing.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ const initializeProvider = (): Promise<void> => {
5555

5656
providerReady = true;
5757
console.log(`Server view screen sharing: using ${provider.type} provider`);
58-
})();
58+
})().catch((error) => {
59+
initPromise = null;
60+
providerReady = false;
61+
provider = null;
62+
throw error;
63+
});
5964

6065
return initPromise;
6166
};

src/telephony/__tests__/dialpad.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ const mockState = (state: {
4646

4747
const stubWebContents = () => {
4848
const send = jest.fn();
49-
getWebContentsByServerUrlMock.mockReturnValue({ send } as any);
49+
const isDestroyed = jest.fn(() => false);
50+
getWebContentsByServerUrlMock.mockReturnValue({ send, isDestroyed } as any);
5051
return { send };
5152
};
5253

0 commit comments

Comments
 (0)