Skip to content

Commit 082fa54

Browse files
committed
Merge remote-tracking branch 'upstream/master' into dev
2 parents 7da2599 + 622f467 commit 082fa54

File tree

13 files changed

+310
-192
lines changed

13 files changed

+310
-192
lines changed

_locales/en/messages.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,7 @@
10761076
"unavailable": "Unavailable",
10771077
"undo": "Undo",
10781078
"unknown": "Unknown",
1079+
"unsupportedCpu": "Unsupported CPU",
10791080
"updateApp": "App updates",
10801081
"updateCommunityInformation": "Update Community Information",
10811082
"updateCommunityInformationDescription": "Community name and description are visible to all community members",
@@ -1124,6 +1125,7 @@
11241125
"you": "You",
11251126
"yourRecoveryPassword": "Your Recovery Password",
11261127
"zoomFactor": "Zoom Factor",
1127-
"zoomFactorDescription": "Adjust the size of text and visual elements."
1128+
"zoomFactorDescription": "Adjust the size of text and visual elements.",
1129+
"yourCpuIsUnsupportedSSE42": "Your CPU does not support SSE 4.2 instructions, which are required by Session on Linux x64 operating systems to process images. Please upgrade to a compatible CPU or use a different operating system."
11281130
}
11291131

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "session-desktop",
33
"productName": "Session",
44
"description": "Private messaging from your desktop",
5-
"version": "1.16.6",
5+
"version": "1.16.10",
66
"license": "GPL-3.0",
77
"author": {
88
"name": "Session Foundation",
@@ -110,7 +110,7 @@
110110
"rimraf": "6.0.1",
111111
"sanitize.css": "^12.0.1",
112112
"semver": "^7.7.1",
113-
"sharp": "^0.34.3",
113+
"sharp": "^0.34.4",
114114
"styled-components": "^6.1.15",
115115
"uuid": "11.1.0",
116116
"viem": "^2.26.0",

ts/mains/main_node.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import {
88
app,
99
BrowserWindow,
10+
crashReporter,
11+
dialog,
1012
protocol as electronProtocol,
1113
ipcMain as ipc,
1214
ipcMain,
@@ -152,6 +154,27 @@ if (!process.mas) {
152154
}
153155
}
154156

157+
/**
158+
* Starts the crash reporter, which saves crashes to disk.
159+
* The dumps will be saved in the `userData/CrashPad/pending` directory.
160+
*
161+
* The minidumps can be read using the `minidump_stackwalk` tool.
162+
* If you do not have cargo installed, you can do those steps:
163+
* ```bash
164+
* git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
165+
* cd depot_tools
166+
* mkdir breakpad && cd breakpad
167+
* ../fetch breakpad && cd src
168+
* ./configure && make
169+
* file src/processor/minidump_stackwalk
170+
* ./src/processor/minidump_stackwalk <path_to.dmp> <any_symbols_you_have_optional>
171+
*/
172+
crashReporter.start({
173+
submitURL: '', // leave empty as we don't want to upload them, but only save them locally
174+
uploadToServer: false,
175+
compress: true,
176+
});
177+
155178
const windowFromUserConfig = userConfig.get('window');
156179
const windowFromEphemeral = ephemeralConfig.get('window');
157180
let windowConfig = windowFromEphemeral || windowFromUserConfig;
@@ -177,6 +200,9 @@ import { initializeMainProcessLogger } from '../util/logger/main_process_logging
177200
import * as log from '../util/logger/log';
178201
import { DURATION } from '../session/constants';
179202
import { tr } from '../localization/localeTools';
203+
import { isSharpSupported } from '../node/sharp_support/sharpSupport';
204+
205+
import { logCrash } from '../node/crash/log_crash';
180206

181207
function prepareURL(pathSegments: Array<string>, moreKeys?: { theme: any }) {
182208
const urlObject: url.UrlObject = {
@@ -200,6 +226,11 @@ function prepareURL(pathSegments: Array<string>, moreKeys?: { theme: any }) {
200226
return url.format(urlObject);
201227
}
202228

229+
async function showUnsupportedCpuDialog() {
230+
dialog.showErrorBox(tr('unsupportedCpu'), tr('yourCpuIsUnsupportedSSE42'));
231+
app.quit();
232+
}
233+
203234
function handleUrl(event: any, target: string) {
204235
event?.preventDefault();
205236
const { protocol } = url.parse(target);
@@ -360,6 +391,12 @@ async function createWindow() {
360391
mainWindow.on('focus', setWindowFocus);
361392
mainWindow.on('blur', setWindowFocus);
362393
mainWindow.once('ready-to-show', setWindowFocus);
394+
395+
mainWindow.webContents.on('render-process-gone', (_event, details) => {
396+
// details.reason can be: 'crashed', 'killed', 'oom', etc.
397+
logCrash('renderer', details);
398+
});
399+
363400
// This is a fallback in case we drop an event for some reason.
364401
global.setInterval(setWindowFocus, 5000);
365402

@@ -739,6 +776,11 @@ app.on('ready', async () => {
739776
console.log(`crowdin locale is ${loadedLocale.crowdinLocale}`);
740777
}
741778

779+
if (!isSharpSupported()) {
780+
await showUnsupportedCpuDialog();
781+
return;
782+
}
783+
742784
const key = getDefaultSQLKey();
743785
// Try to show the main window with the default key
744786
// If that fails then show the password window

ts/node/crash/log_crash.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { app } from 'electron';
2+
import { appendFileSync } from 'fs-extra';
3+
import path from 'node:path';
4+
5+
/**
6+
* Logs a crash of the renderer/main process to the userData directory, file `crash-log.txt`.
7+
* Also, see the crashReporter for dumps of the crash itself
8+
*/
9+
export function logCrash(type: string, details: any) {
10+
const crashLogPath = path.join(app.getPath('userData'), 'crash-log.txt');
11+
const logLine = `[${new Date().toISOString()}] ${type} crash: ${JSON.stringify(details)}\n`;
12+
appendFileSync(crashLogPath, logLine, 'utf8');
13+
}

ts/node/global_errors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import os from 'node:os';
33
import { reallyJsonStringify } from '../util/reallyJsonStringify';
44
import { Errors } from '../types/Errors';
55
import { redactAll } from '../util/privacy';
6+
import { logCrash } from './crash/log_crash';
67

78
// TODO: use localised strings
89
const quitText = 'Quit';
@@ -53,10 +54,14 @@ export const addHandler = (): void => {
5354
// Note: we could maybe add a handler for when the renderer process died here?
5455
// (but also ignore the valid death like on restart/quit)
5556
process.on('uncaughtException', (reason: unknown) => {
57+
logCrash('main', { reason: 'uncaughtException', error: reason });
58+
5659
handleError('Unhandled Error', _getError(reason));
5760
});
5861

5962
process.on('unhandledRejection', (reason: unknown) => {
63+
logCrash('main', { reason: 'unhandledRejection', error: reason });
64+
6065
handleError('Unhandled Promise Rejection', _getError(reason));
6166
});
6267
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { readFileSync } from 'fs';
2+
import { isLinux } from '../../OS';
3+
4+
/**
5+
* The return of this function should be used as override of `sse42FromCPUInfo` if different than null.
6+
* Used for testing purposes.
7+
* @returns true when process.env.SESSION_SSE42_OVERRIDE === '1', false when process.env.SESSION_SSE42_OVERRIDE === '0', null otherwise
8+
*/
9+
function overrideSSE42IsSupported() {
10+
if (process.env.SESSION_SSE42_OVERRIDE === '1') {
11+
return true;
12+
}
13+
14+
if (process.env.SESSION_SSE42_OVERRIDE === '0') {
15+
return false;
16+
}
17+
return null;
18+
}
19+
20+
/**
21+
* Checks from /proc/cpuinfo if the CPU supports SSE 4.2 instructions.
22+
*/
23+
function sse42FromCPUInfo() {
24+
try {
25+
const cpuinfo = readFileSync('/proc/cpuinfo', 'utf8');
26+
const flagsMatch = cpuinfo.match(/flags\s*:\s*(.+)/m);
27+
28+
if (flagsMatch) {
29+
const flags = flagsMatch[1].split(/\s+/);
30+
31+
const sseFlagFound = flags.includes('sse4_2');
32+
console.info('SSE 4.2 flag found:', sseFlagFound);
33+
return sseFlagFound;
34+
}
35+
return false;
36+
} catch (error) {
37+
console.warn('Could not read /proc/cpuinfo:', error);
38+
return false;
39+
}
40+
}
41+
42+
function isLinuxX64() {
43+
return isLinux() && process.arch === 'x64';
44+
}
45+
46+
/**
47+
* Returns true if sharp is supported on the current platform.
48+
* On linux x64, the CPU needs to support SSE 4.2 instructions to process images.
49+
* This function checks if we are on linux x64 and if yes, checks `/proc/cpuinfo` for the SSE 4.2 flag.
50+
*/
51+
export function isSharpSupported() {
52+
const sse42Override = overrideSSE42IsSupported();
53+
if (sse42Override !== null) {
54+
return sse42Override;
55+
}
56+
if (isLinuxX64()) {
57+
return sse42FromCPUInfo();
58+
}
59+
// sharp doesn't need sse42 on other platforms than linux x64 as of 18/09/2025
60+
return true;
61+
}

ts/session/utils/job_runners/JobDeserialization.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,50 @@ import {
66
import { AvatarDownload } from './jobs/AvatarDownloadJob';
77
import { UserSync } from './jobs/UserSyncJob';
88
import { PersistedJob, TypeOfPersistedData } from './PersistedJob';
9+
import { assertUnreachable } from '../../../types/sqlSharedTypes';
10+
import { AvatarMigrate } from './jobs/AvatarMigrateJob';
11+
import { FetchMsgExpirySwarm } from './jobs/FetchMsgExpirySwarmJob';
12+
import { GroupInvite } from './jobs/GroupInviteJob';
13+
import { GroupPendingRemovals } from './jobs/GroupPendingRemovalsJob';
14+
import { GroupSync } from './jobs/GroupSyncJob';
15+
import { UpdateMsgExpirySwarm } from './jobs/UpdateMsgExpirySwarmJob';
916

1017
export function persistedJobFromData<T extends TypeOfPersistedData>(
1118
data: T
1219
): PersistedJob<T> | null {
1320
if (!data || isEmpty(data.jobType) || !isString(data?.jobType)) {
1421
return null;
1522
}
23+
const { jobType } = data;
1624

17-
switch (data.jobType) {
25+
switch (jobType) {
1826
case 'UserSyncJobType':
1927
return new UserSync.UserSyncJob(data) as unknown as PersistedJob<T>;
2028
case 'AvatarDownloadJobType':
2129
return new AvatarDownload.AvatarDownloadJob(data) as unknown as PersistedJob<T>;
30+
case 'AvatarMigrateJobType':
31+
return new AvatarMigrate.AvatarMigrateJob(data) as unknown as PersistedJob<T>;
32+
case 'FetchMsgExpirySwarmJobType':
33+
return new FetchMsgExpirySwarm.FetchMsgExpirySwarmJob(data) as unknown as PersistedJob<T>;
34+
case 'GroupInviteJobType':
35+
return new GroupInvite.GroupInviteJob(data) as unknown as PersistedJob<T>;
36+
case 'GroupPendingRemovalJobType':
37+
return new GroupPendingRemovals.GroupPendingRemovalsJob(data) as unknown as PersistedJob<T>;
38+
case 'GroupSyncJobType':
39+
return new GroupSync.GroupSyncJob(data) as unknown as PersistedJob<T>;
40+
case 'UpdateMsgExpirySwarmJobType':
41+
return new UpdateMsgExpirySwarm.UpdateMsgExpirySwarmJob(data) as unknown as PersistedJob<T>;
42+
2243
case 'FakeSleepForJobType':
2344
return new FakeSleepForJob(data) as unknown as PersistedJob<T>;
2445
case 'FakeSleepForJobMultiType':
2546
return new FakeSleepForMultiJob(data) as unknown as PersistedJob<T>;
2647
default:
27-
window?.log?.error('unknown persisted job type:', (data as any).jobType);
48+
try {
49+
assertUnreachable(jobType, `persistedJobFromData unknown job type: "${jobType}"`);
50+
} catch (e) {
51+
window?.log?.warn('assertUnreachable failed:', e.message);
52+
}
2853
return null;
2954
}
3055
}

ts/session/utils/job_runners/JobRunner.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
FetchMsgExpirySwarmPersistedData,
99
GroupInvitePersistedData,
1010
GroupPendingRemovalsPersistedData,
11-
GroupPromotePersistedData,
1211
GroupSyncPersistedData,
1312
PersistedJob,
1413
RunJobResult,
@@ -390,11 +389,6 @@ const avatarMigrateRunner = new PersistedJobRunner<AvatarMigratePersistedData>('
390389

391390
const groupInviteJobRunner = new PersistedJobRunner<GroupInvitePersistedData>('GroupInviteJob', 4);
392391

393-
const groupPromoteJobRunner = new PersistedJobRunner<GroupPromotePersistedData>(
394-
'GroupPromoteJob',
395-
4
396-
);
397-
398392
const groupPendingRemovalJobRunner = new PersistedJobRunner<GroupPendingRemovalsPersistedData>(
399393
'GroupPendingRemovalJob',
400394
4
@@ -416,6 +410,5 @@ export const runners = {
416410
avatarDownloadRunner,
417411
avatarMigrateRunner,
418412
groupInviteJobRunner,
419-
groupPromoteJobRunner,
420413
groupPendingRemovalJobRunner,
421414
};

ts/session/utils/job_runners/PersistedJob.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export type PersistedJobType =
77
| 'AvatarDownloadJobType'
88
| 'AvatarMigrateJobType'
99
| 'GroupInviteJobType'
10-
| 'GroupPromoteJobType'
1110
| 'GroupPendingRemovalJobType'
1211
| 'FetchMsgExpirySwarmJobType'
1312
| 'UpdateMsgExpirySwarmJobType'
@@ -51,12 +50,6 @@ export interface GroupInvitePersistedData extends PersistedJobData {
5150
inviteAsAdmin: boolean;
5251
}
5352

54-
export interface GroupPromotePersistedData extends PersistedJobData {
55-
jobType: 'GroupPromoteJobType';
56-
groupPk: GroupPubkeyType;
57-
member: PubkeyType;
58-
}
59-
6053
export interface GroupPendingRemovalsPersistedData extends PersistedJobData {
6154
jobType: 'GroupPendingRemovalJobType';
6255
groupPk: GroupPubkeyType;
@@ -90,7 +83,6 @@ export type TypeOfPersistedData =
9083
| FakeSleepForMultiJobData
9184
| GroupSyncPersistedData
9285
| GroupInvitePersistedData
93-
| GroupPromotePersistedData
9486
| GroupPendingRemovalsPersistedData;
9587

9688
export type AddJobCheckReturn = 'skipAddSameJobPresent' | null;

ts/session/utils/job_runners/jobs/AvatarMigrateJob.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,5 @@ async function scheduleAllAvatarMigrateJobs() {
234234

235235
export const AvatarMigrate = {
236236
scheduleAllAvatarMigrateJobs,
237+
AvatarMigrateJob,
237238
};

0 commit comments

Comments
 (0)