diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 503907c03d..59000b546e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -234,7 +234,7 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
repository: 'signalapp/Signal-Message-Backup-Tests'
- ref: '455fbe5854bd3be5002f17ae929a898c0975adc4'
+ ref: '551f9ad1186d196e8698df4a5750b239f0796a70'
path: 'backup-integration-tests'
- run: xvfb-run --auto-servernum pnpm run test-electron
diff --git a/.gitignore b/.gitignore
index 02aaef518f..e8645b1149 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ node_modules_bkp
coverage/*
build/curve25519_compiled.js
build/compact-locales
+build/*.policy
stylesheets/*.css.map
/dist
.DS_Store
diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md
index bdafc7707c..735ec23d15 100644
--- a/ACKNOWLEDGMENTS.md
+++ b/ACKNOWLEDGMENTS.md
@@ -9836,7 +9836,7 @@ DEALINGS IN THE SOFTWARE.
```
-## gimli 0.31.1, heck 0.5.0, unicode-xid 0.2.6
+## gimli 0.31.1, heck 0.5.0, unicode-segmentation 1.12.0, unicode-xid 0.2.6
```
Copyright (c) 2015 The Rust Project Developers
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 352b602380..df26f2f0ae 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -7200,6 +7200,118 @@
"messageformat": "Notification content",
"description": "Label for the notification content setting select box"
},
+ "icu:PlaintextExport--PreferencesRow--Header": {
+ "messageformat": "Export chat history",
+ "description": "Shown in the Preferences/Chats page, header for a row"
+ },
+ "icu:PlaintextExport--PreferencesRow--Description": {
+ "messageformat": "Export a machine-readable JSON copy of all your chats",
+ "description": "Shown in the Preferences/Chats page, detail text for a row"
+ },
+ "icu:PlaintextExport--ActionButton": {
+ "messageformat": "Export",
+ "description": "Shown on a button to take the action of exporting chat history"
+ },
+ "icu:PlaintextExport--Confirmation--Header": {
+ "messageformat": "Export chat history?",
+ "description": "Title of confirmation dialog shown before the export starts"
+ },
+ "icu:PlaintextExport--Confirmation--Description": {
+ "messageformat": "BE CAREFUL! Do NOT share this file with anyone. Your chat history will be saved to your computer and other apps can access it depending on your computer’s permissions.",
+ "description": "Description of confirmation dialog shown before the export starts"
+ },
+ "icu:PlaintextExport--Confirmation--IncludeMedia": {
+ "messageformat": "Include media (larger file size)",
+ "description": "Description for a checkbox shown on the confirmation dialog, to choose whether to include media in the export"
+ },
+ "icu:PlaintextExport--Confirmation--ContinueButton": {
+ "messageformat": "Continue",
+ "description": "Button on confirmation dialog to continue on through the export process"
+ },
+ "icu:PlaintextExport--Confirmation--WaitingLabel": {
+ "messageformat": "Waiting for input",
+ "description": "Label for spinner on Confirmation dialog when user as pressed Continue, and OS Auth/Export Location dialogs are show"
+ },
+ "icu:PlaintextExport--OSPrompt--Mac": {
+ "messageformat": "export your chat history",
+ "description": "Shown as part of an OS prompt with a final string like 'Signal Desktop is trying to export your chat history'"
+ },
+ "icu:PlaintextExport--OSPrompt--Windows": {
+ "messageformat": "Verify your identity to export your chat history.",
+ "description": "Shown as the body text of an OS prompt with a title like \"Making sure it's you\""
+ },
+ "icu:PlaintextExport--OSPrompt--description--Linux": {
+ "messageformat": "Export",
+ "description": "If linux system has pkcheck and we were able to install .policy files, this is the dialog header the user will see on the polkit prompt before doing an export"
+ },
+ "icu:PlaintextExport--OSPrompt--message--Linux": {
+ "messageformat": "Authentication is required to export your chat history.",
+ "description": "If linux system has pkcheck and we were able to install .policy files, this is the dialog text the user will see on the polkit prompt before doing an export"
+ },
+ "icu:PlaintextExport--ProgressDialog--Header": {
+ "messageformat": "Exporting chat history",
+ "description": "Title of a progress dialog that shows while the export happens"
+ },
+ "icu:PlaintextExport--ProgressDialog--Progress": {
+ "messageformat": "Exporting {currentBytes} of {totalBytes} ({percentage, number, percent})...",
+ "description": "Shown on progress dialog below the progress bar to provide the details of progress. currentBytes and totalBytes will be like 5 GB or 10 GB."
+ },
+ "icu:PlaintextExport--ProgressDialog--TimeWarning": {
+ "messageformat": "This may take a few minutes",
+ "description": "Shown on progress dialog to explain that it might take a while"
+ },
+ "icu:PlaintextExport--CompleteDialog--Header": {
+ "messageformat": "Export complete",
+ "description": "Title of the dialog shown once the export is complete"
+ },
+ "icu:PlaintextExport--CompleteDialog--Description": {
+ "messageformat": "BE CAREFUL where you store your chat export file and do not share it with anyone. Other apps on your computer can access it depending on your computer’s permissions. ",
+ "description": "Additional explanation on the dialog shown when the export is complete"
+ },
+ "icu:PlaintextExport--CompleteDialog--ShowFiles--Mac": {
+ "messageformat": "Show in Finder",
+ "description": "Button on the dialog shown when the export is complete to show the exported files - on a mac computer"
+ },
+ "icu:PlaintextExport--CompleteDialog--ShowFiles--Linux": {
+ "messageformat": "Show in folder",
+ "description": "Button on the dialog shown when the export is complete to show the exported files - on a linux computer"
+ },
+ "icu:PlaintextExport--CompleteDialog--ShowFiles--Windows": {
+ "messageformat": "Show in folder",
+ "description": "Button on the dialog shown when the export is complete to show the exported files - on a windows computer"
+ },
+ "icu:PlaintextExport--Error--General--Title": {
+ "messageformat": "Couldn’t export chat history",
+ "description": "Title of dialog shown when a general plaintext export error has happened"
+ },
+ "icu:PlaintextExport--Error--General--Description": {
+ "messageformat": "An error occurred and your chat history could not be exported.",
+ "description": "Detail text in dialog shown when a general plaintext export error has happened"
+ },
+ "icu:PlaintextExport--Error--NotEnoughStorage--Title": {
+ "messageformat": "Not enough storage space",
+ "description": "Title of dialog shown when we detect that user doesn't have enough space for export"
+ },
+ "icu:PlaintextExport--Error--NotEnoughStorage--Detail": {
+ "messageformat": "Your chat history can’t be exported because your computer doesn’t have enough free storage space. Free up {bytes} of space and then try again.",
+ "description": "Detail text in dialog shown when we detect that user doesn't have enough space for export. Bytes will be formatted like 12 MB"
+ },
+ "icu:PlaintextExport--Error--RanOutOfStorage--Title": {
+ "messageformat": "Couldn’t export chat history",
+ "description": "Title of dialog shown when we attempted to save a file during export but disk is out of space"
+ },
+ "icu:PlaintextExport--Error--RanOutOfStorage--Detail": {
+ "messageformat": "Your chat history couldn’t be exported because your computer doesn’t have enough free storage space. Free up {bytes} of space and then try again.",
+ "description": "Detail text in dialog shown when we attempted to save a file during export but disk is out of space. Bytes will formated like 12 MB"
+ },
+ "icu:PlaintextExport--Error--DiskPermssions--Title": {
+ "messageformat": "Can’t export chat history",
+ "description": "Title of dialog shown when we attempted to save a file during export but got a permissions error"
+ },
+ "icu:PlaintextExport--Error--DiskPermssions--Detail": {
+ "messageformat": "Your chat history can’t be exported because Signal doesn’t have permission to write files to disk. Try changing your disk's permissions or updating your system settings and then export again.",
+ "description": "Detail text in dialog shown when we attempted to save a file during export but got a permissions error"
+ },
"icu:NotificationProfile--moon-icon": {
"messageformat": "Moon icon",
"description": "Screenreader description for the moon icon used to signify notification profiles"
@@ -7632,6 +7744,22 @@
"messageformat": "Done",
"description": "Button to dismiss the backup key viewer, shown when reviewing your backup key for local on-device backups."
},
+ "icu:Preferences--local-backups--view-backup-key--os-prompt--mac": {
+ "messageformat": "show your backup key",
+ "description": "Shown as part of an OS prompt with a final string like 'Signal Desktop is trying to show your backup key'"
+ },
+ "icu:Preferences--local-backups--view-backup-key--os-prompt--windows": {
+ "messageformat": "Verify your identity to view your backup key.",
+ "description": "Shown as the body text of an OS prompt with a title like \"Making sure it's you\""
+ },
+ "icu:Preferences--local-backups--view-backup-key--os-prompt-description--linux": {
+ "messageformat": "View backup key",
+ "description": "If linux system has pkcheck and we were able to install .policy files, this is the dialog header the user will see on the polkit prompt before showing the key"
+ },
+ "icu:Preferences--local-backups--view-backup-key--os-prompt-message--linux": {
+ "messageformat": "Authentication is required to view your backup key.",
+ "description": "If linux system has pkcheck and we were able to install .policy files, this is the dialog text the user will see on the polkit prompt before showing the key"
+ },
"icu:Preferences--local-backups-backup-key-text-box": {
"messageformat": "Backup key text box",
"description": "ARIA label for the text box used to view or confirm the backup key for local message backups."
@@ -7688,6 +7816,22 @@
"messageformat": "Backup key copied",
"description": "Toast message after you copied the backup key to clipboard from settings for local on-device backups"
},
+ "icu:Preferences__local-backups--enable--os-prompt--mac": {
+ "messageformat": "enable backups",
+ "description": "Shown as part of an OS prompt with a final string like 'Signal Desktop is trying to enable backups'"
+ },
+ "icu:Preferences__local-backups--enable--os-prompt--windows": {
+ "messageformat": "Verify your identity to enable backups.",
+ "description": "Shown as the body text of an OS prompt with a title like \"Making sure it's you\""
+ },
+ "icu:Preferences__local-backups--enable--os-prompt-description--linux": {
+ "messageformat": "Enable backups",
+ "description": "If linux system has pkcheck and we were able to install .policy files, this is the dialog header the user will see on the polkit prompt before enabling backups"
+ },
+ "icu:Preferences__local-backups--enable--os-prompt-message--linux": {
+ "messageformat": "Authentication is required to enable backups.",
+ "description": "If linux system has pkcheck and we were able to install .policy files, this is the dialog text the user will see on the polkit prompt before enabling backups"
+ },
"icu:Preferences__view-key": {
"messageformat": "View key",
"description": "Button to view the backup key which is used to restore a message history backup"
diff --git a/app/main.main.ts b/app/main.main.ts
index 9fd3394614..c1c9203e18 100644
--- a/app/main.main.ts
+++ b/app/main.main.ts
@@ -2807,6 +2807,7 @@ ipc.on('get-config', async event => {
// paths
crashDumpsPath: app.getPath('crashDumps'),
+ downloadsPath: app.getPath('downloads'),
homePath: app.getPath('home'),
installPath: app.getAppPath(),
userDataPath: app.getPath('userData'),
diff --git a/build/org.signalapp.enable-backups.policy b/build/policy-templates/org.signalapp.enable-backups.policy
similarity index 79%
rename from build/org.signalapp.enable-backups.policy
rename to build/policy-templates/org.signalapp.enable-backups.policy
index eda697aa9a..e58265d95f 100644
--- a/build/org.signalapp.enable-backups.policy
+++ b/build/policy-templates/org.signalapp.enable-backups.policy
@@ -4,12 +4,12 @@
Signal Desktophttps://signal.org/
- Enable backups
- Authentication is required to enable backups.
+
+
auth_adminauth_adminauth_admin
-
\ No newline at end of file
+
diff --git a/build/policy-templates/org.signalapp.plaintext-export.policy b/build/policy-templates/org.signalapp.plaintext-export.policy
new file mode 100644
index 0000000000..1c9faeec39
--- /dev/null
+++ b/build/policy-templates/org.signalapp.plaintext-export.policy
@@ -0,0 +1,16 @@
+
+
+
+
+ Signal Desktop
+ https://signal.org/
+
+
+
+
+ auth_admin
+ auth_admin
+ auth_admin
+
+
+
diff --git a/build/org.signalapp.view-aep.policy b/build/policy-templates/org.signalapp.view-aep.policy
similarity index 78%
rename from build/org.signalapp.view-aep.policy
rename to build/policy-templates/org.signalapp.view-aep.policy
index b68660d97b..9d7662b965 100644
--- a/build/org.signalapp.view-aep.policy
+++ b/build/policy-templates/org.signalapp.view-aep.policy
@@ -4,12 +4,12 @@
Signal Desktophttps://signal.org/
- View backup key
- Authentication is required to view your backup key.
+
+
auth_adminauth_adminauth_admin
-
\ No newline at end of file
+
diff --git a/package.json b/package.json
index 7d8e819501..e34a6602bd 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"start": "electron .",
"generate": "run-s generate:phase-0 generate:phase-1",
"generate:phase-0": "run-p build:esbuild:scripts",
- "generate:phase-1": "run-p --aggregate-output --print-label generate:phase-1:bundle build:icu-types build:compact-locales build:styles get-expire-time copy-components",
+ "generate:phase-1": "run-p --aggregate-output --print-label generate:phase-1:bundle build:icu-types build:compact-locales build:styles get-expire-time copy-components build:policy-files",
"generate:phase-1:bundle": "run-s build-protobuf build:esbuild:bundle",
"build-release": "pnpm run build",
"sign-release": "node ts/updater/generateSignature.js",
@@ -83,7 +83,7 @@
"test:storybook:test": "wait-on http://127.0.0.1:6006/ --timeout 5000 && test-storybook --testTimeout 60000",
"build": "run-s --print-label generate build:esbuild:prod build:release",
"build-win32-all": "run-s --print-label generate build:esbuild:prod build:release-win32-all",
- "build-linux": "run-s generate build:esbuild:prod && pnpm run build:release --publish=never",
+ "build-linux": "run-s build:policy-files generate build:esbuild:prod && pnpm run build:release --publish=never",
"build:acknowledgments": "node scripts/generate-acknowledgments.js",
"build:dns-fallback": "node ts/scripts/generate-dns-fallback.node.js",
"build:icu-types": "node ts/scripts/generate-icu-types.node.js",
@@ -94,6 +94,7 @@
"build:esbuild:scripts": "node scripts/esbuild.js --no-bundle",
"build:esbuild:bundle": "node scripts/esbuild.js --no-scripts",
"build:esbuild:prod": "node scripts/esbuild.js --prod",
+ "build:policy-files": "node ts/scripts/gen-policy-files.node.js",
"build:styles": "pnpm run \"/^build:styles:.*/\"",
"build:styles:sass": "sass stylesheets/manifest.scss:stylesheets/manifest.css stylesheets/manifest_bridge.scss:stylesheets/manifest_bridge.css --fatal-deprecation=1.80.7",
"build:styles:tailwind": "tailwindcss -i ./stylesheets/tailwind-config.css -o ./stylesheets/tailwind.css",
@@ -132,7 +133,7 @@
"@react-aria/utils": "3.25.3",
"@react-spring/web": "9.7.5",
"@react-types/shared": "3.27.0",
- "@signalapp/libsignal-client": "0.83.0",
+ "@signalapp/libsignal-client": "0.86.3",
"@signalapp/minimask": "1.0.1",
"@signalapp/mute-state-change": "workspace:1.0.0",
"@signalapp/quill-cjs": "2.1.2",
diff --git a/patches/app-builder-lib.patch b/patches/app-builder-lib.patch
index 50b3411954..8b473ed9e6 100644
--- a/patches/app-builder-lib.patch
+++ b/patches/app-builder-lib.patch
@@ -11,10 +11,10 @@ index 47e6f48fcbed88b6ac07cff15c888c1b8b59721f..76dd6cc7265054222f2d70c76aa8456d
productFilename: packager.appInfo.productFilename,
...packager.platformSpecificBuildOptions,
diff --git a/templates/linux/after-install.tpl b/templates/linux/after-install.tpl
-index 6cf860bd2847bae35ca8885cb680dd6c8c516e39..a19f9610d7101c925bdad8a88c434d839ebdf8f8 100644
+index 6cf860bd2847bae35ca8885cb680dd6c8c516e39..a3cb08a6dc7970ab2b32c731f41ea6e471204a19 100644
--- a/templates/linux/after-install.tpl
+++ b/templates/linux/after-install.tpl
-@@ -55,3 +55,24 @@ if apparmor_status --enabled > /dev/null 2>&1; then
+@@ -55,3 +55,26 @@ if apparmor_status --enabled > /dev/null 2>&1; then
echo "Skipping the installation of the AppArmor profile as this version of AppArmor does not seem to support the bundled profile"
fi
fi
@@ -27,10 +27,12 @@ index 6cf860bd2847bae35ca8885cb680dd6c8c516e39..a19f9610d7101c925bdad8a88c434d83
+ POLICY_ORG='org.signalapp'
+ POLICY_ENABLE_BACKUPS='enable-backups.policy'
+ POLICY_VIEW_AEP='view-aep.policy'
++ POLICY_EXPORT='plaintext-export.policy'
+ mkdir -p "$POLICY_TARGET_PATH";
+ # Separate policies for staging and production builds
+ cp -f "$POLICY_SOURCE_PATH/$POLICY_ORG.$POLICY_ENABLE_BACKUPS" "$POLICY_TARGET_PATH/$POLICY_ORG.${sanitizedName}.$POLICY_ENABLE_BACKUPS"
+ cp -f "$POLICY_SOURCE_PATH/$POLICY_ORG.$POLICY_VIEW_AEP" "$POLICY_TARGET_PATH/$POLICY_ORG.${sanitizedName}.$POLICY_VIEW_AEP"
++ cp -f "$POLICY_SOURCE_PATH/$POLICY_ORG.$POLICY_EXPORT" "$POLICY_TARGET_PATH/$POLICY_ORG.${sanitizedName}.$POLICY_EXPORT"
+else
+ echo "Skipping installation of policies as polkit does not seem to be installed. This may affect the availability of some features.";
+fi
@@ -40,7 +42,7 @@ index 6cf860bd2847bae35ca8885cb680dd6c8c516e39..a19f9610d7101c925bdad8a88c434d83
+
+# SIGNAL CHANGES END
diff --git a/templates/linux/after-remove.tpl b/templates/linux/after-remove.tpl
-index 19b3decabe18a816f9ed5440fa9124ebfd6e3907..b5011d1b8cdb741ba6453f942a3c0660b66d41a0 100644
+index 19b3decabe18a816f9ed5440fa9124ebfd6e3907..676e76bae609cd3309be87a4606346f75945e900 100644
--- a/templates/linux/after-remove.tpl
+++ b/templates/linux/after-remove.tpl
@@ -13,3 +13,12 @@ APPARMOR_PROFILE_DEST='/etc/apparmor.d/${executable}'
@@ -56,7 +58,6 @@ index 19b3decabe18a816f9ed5440fa9124ebfd6e3907..b5011d1b8cdb741ba6453f942a3c0660
+fi
+
+# SIGNAL CHANGES END
-\ No newline at end of file
diff --git a/templates/nsis/include/installer.nsh b/templates/nsis/include/installer.nsh
index 34e91dfe82fdbb2e929820f2e8deb771b7f7893c..73bfffc6c227a018cbbeb690d6d7b882ed142fc8 100644
--- a/templates/nsis/include/installer.nsh
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d04cf3f6ce..c6a5d08548 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -29,7 +29,7 @@ patchedDependencies:
hash: e8a96f71e52bf903c9f1eadba4740489a0beb48da33db52354adca484fe1f495
path: patches/@vitest+expect+2.0.5.patch
app-builder-lib:
- hash: b412b44a47bb3d2be98e6edffed5dc4286cc62ac3c02fef42d1557927baa2420
+ hash: a1775a435732fdbd3b69305053bea4776c854378984940cbd2a541d692902664
path: patches/app-builder-lib.patch
casual@1.6.2:
hash: b88b5052437cbdc1882137778b76ca5037f71b2a030ae9ef39dc97f51670d599
@@ -124,8 +124,8 @@ importers:
specifier: 3.27.0
version: 3.27.0(react@18.3.1)
'@signalapp/libsignal-client':
- specifier: 0.83.0
- version: 0.83.0
+ specifier: 0.86.3
+ version: 0.86.3
'@signalapp/minimask':
specifier: 1.0.1
version: 1.0.1
@@ -3487,8 +3487,8 @@ packages:
'@signalapp/libsignal-client@0.76.7':
resolution: {integrity: sha512-iGWTlFkko7IKlm96Iy91Wz5sIN089nj02ifOk6BWtLzeVi0kFaNj+jK26Sl1JRXy/VfXevcYtiOivOg43BPqpg==}
- '@signalapp/libsignal-client@0.83.0':
- resolution: {integrity: sha512-QaXviPAvj4PA2QDmN6YyPnlkp699BE3fIgaJmKrfvZMsvBfMGeJ3H3BHFt0CV2vUWMbc3oEgxbwdXu//f6oTrA==}
+ '@signalapp/libsignal-client@0.86.3':
+ resolution: {integrity: sha512-aN/pgT9YqacuABrtxBtBbQ0AMesZJIHVNqU8nUq75kRTleIU5aKeuOXt7ZHYUUJW7ot4O2n6O6eaMnMLbwBXFQ==}
'@signalapp/minimask@1.0.1':
resolution: {integrity: sha512-QAwo0joA60urTNbW9RIz6vLKQjy+jdVtH7cvY0wD9PVooD46MAjE40MLssp4xUJrph91n2XvtJ3pbEUDrmT2AA==}
@@ -14281,7 +14281,7 @@ snapshots:
type-fest: 4.26.1
uuid: 11.0.2
- '@signalapp/libsignal-client@0.83.0':
+ '@signalapp/libsignal-client@0.86.3':
dependencies:
node-gyp-build: 4.8.4
type-fest: 4.26.1
@@ -15660,7 +15660,7 @@ snapshots:
app-builder-bin@5.0.0-alpha.12: {}
- app-builder-lib@26.0.14(patch_hash=b412b44a47bb3d2be98e6edffed5dc4286cc62ac3c02fef42d1557927baa2420)(dmg-builder@26.0.14)(electron-builder-squirrel-windows@26.0.14):
+ app-builder-lib@26.0.14(patch_hash=a1775a435732fdbd3b69305053bea4776c854378984940cbd2a541d692902664)(dmg-builder@26.0.14)(electron-builder-squirrel-windows@26.0.14):
dependencies:
'@develar/schema-utils': 2.6.5
'@electron/asar': 3.4.1
@@ -16879,7 +16879,7 @@ snapshots:
dmg-builder@26.0.14(patch_hash=cb72ed47fa8d45513a36db33fcb41cb75c30cada4737da067bf3fa1f063725f2)(electron-builder-squirrel-windows@26.0.14):
dependencies:
- app-builder-lib: 26.0.14(patch_hash=b412b44a47bb3d2be98e6edffed5dc4286cc62ac3c02fef42d1557927baa2420)(dmg-builder@26.0.14)(electron-builder-squirrel-windows@26.0.14)
+ app-builder-lib: 26.0.14(patch_hash=a1775a435732fdbd3b69305053bea4776c854378984940cbd2a541d692902664)(dmg-builder@26.0.14)(electron-builder-squirrel-windows@26.0.14)
builder-util: 26.0.13
builder-util-runtime: 9.3.2
fs-extra: 10.1.0
@@ -17025,7 +17025,7 @@ snapshots:
electron-builder-squirrel-windows@26.0.14(dmg-builder@26.0.14):
dependencies:
- app-builder-lib: 26.0.14(patch_hash=b412b44a47bb3d2be98e6edffed5dc4286cc62ac3c02fef42d1557927baa2420)(dmg-builder@26.0.14)(electron-builder-squirrel-windows@26.0.14)
+ app-builder-lib: 26.0.14(patch_hash=a1775a435732fdbd3b69305053bea4776c854378984940cbd2a541d692902664)(dmg-builder@26.0.14)(electron-builder-squirrel-windows@26.0.14)
builder-util: 26.0.13
electron-winstaller: 5.4.0
transitivePeerDependencies:
@@ -17035,7 +17035,7 @@ snapshots:
electron-builder@26.0.14(electron-builder-squirrel-windows@26.0.14):
dependencies:
- app-builder-lib: 26.0.14(patch_hash=b412b44a47bb3d2be98e6edffed5dc4286cc62ac3c02fef42d1557927baa2420)(dmg-builder@26.0.14)(electron-builder-squirrel-windows@26.0.14)
+ app-builder-lib: 26.0.14(patch_hash=a1775a435732fdbd3b69305053bea4776c854378984940cbd2a541d692902664)(dmg-builder@26.0.14)(electron-builder-squirrel-windows@26.0.14)
builder-util: 26.0.13
builder-util-runtime: 9.3.2
chalk: 4.1.2
diff --git a/protos/Backups.proto b/protos/Backups.proto
index 241c48024c..fa9332f7df 100644
--- a/protos/Backups.proto
+++ b/protos/Backups.proto
@@ -5,6 +5,7 @@ syntax = "proto3";
package signalbackups;
option java_package = "org.thoughtcrime.securesms.backup.v2.proto";
+option swift_prefix = "BackupProto_";
message BackupInfo {
uint64 version = 1;
@@ -12,6 +13,7 @@ message BackupInfo {
bytes mediaRootBackupKey = 3; // 32-byte random value generated when the backup is uploaded for the first time.
string currentAppVersion = 4;
string firstAppVersion = 5;
+ bytes debugInfo = 6; // Client-specific data field for debug info during testing
}
// Frames must follow in the following ordering rules:
@@ -68,6 +70,26 @@ message AccountData {
Color color = 3;
}
+ enum SentMediaQuality {
+ UNKNOWN_QUALITY = 0; // Interpret as "Standard"
+ STANDARD = 1;
+ HIGH = 2;
+ }
+
+ message AutoDownloadSettings {
+ enum AutoDownloadOption {
+ UNKNOWN = 0; // Interpret as "Never"
+ NEVER = 1;
+ WIFI = 2;
+ WIFI_AND_CELLULAR = 3;
+ }
+
+ AutoDownloadOption images = 1;
+ AutoDownloadOption audio = 2;
+ AutoDownloadOption video = 3;
+ AutoDownloadOption documents = 4;
+ }
+
message AccountSettings {
bool readReceipts = 1;
bool sealedSenderIndicators = 2;
@@ -91,6 +113,12 @@ message AccountData {
bool optimizeOnDeviceStorage = 20;
// See zkgroup for integer particular values. Unset if backups are not enabled.
optional uint64 backupTier = 21;
+ reserved /* showSealedSenderIndicators */ 22;
+ SentMediaQuality defaultSentMediaQuality = 23;
+ AutoDownloadSettings autoDownloadSettings = 24;
+ reserved /* wifiAutoDownloadSettings */ 25;
+ optional uint32 screenLockTimeoutMinutes = 26; // If unset, consider screen lock to be disabled.
+ optional bool pinReminders = 27; // If unset, consider pin reminders to be enabled.
}
message SubscriberData {
@@ -111,6 +139,11 @@ message AccountData {
}
}
+ message AndroidSpecificSettings {
+ bool useSystemEmoji = 1;
+ bool screenshotSecurity = 2;
+ }
+
bytes profileKey = 1;
optional string username = 2;
UsernameLink usernameLink = 3;
@@ -122,6 +155,9 @@ message AccountData {
AccountSettings accountSettings = 9;
IAPSubscriberData backupsSubscriberData = 10;
string svrPin = 11;
+ AndroidSpecificSettings androidSpecificSettings = 12;
+ string bioText = 13;
+ string bioEmoji = 14;
}
message Recipient {
@@ -344,7 +380,7 @@ message CallLink {
string name = 3;
Restrictions restrictions = 4;
uint64 expirationMs = 5;
- optional bytes epoch = 6;
+ optional bytes epoch = 6; // May be absent/empty for older links
}
message AdHocCall {
@@ -695,7 +731,6 @@ message MessageAttachment {
}
message FilePointer {
-
message LocatorInfo {
// Must be non-empty if transitCdnKey or plaintextHash are set/nonempty.
// Otherwise must be empty.
@@ -828,11 +863,6 @@ message Poll {
repeated Reaction reactions = 5;
}
-message PollTerminateUpdate {
- uint64 targetSentTimestamp = 1;
- string question = 2; // Between 1-100 characters
-}
-
message ChatUpdateMessage {
// If unset, importers should ignore the update message without throwing an error.
oneof update {
@@ -1151,7 +1181,7 @@ message GroupJoinRequestCanceledUpdate {
bytes requestorAci = 1;
}
-// A single requestor has requested to join and canceled
+// A single requestor has requested to join and cancelled
// their request repeatedly with no other updates in between.
// The last action encompassed by this update is always a
// cancellation; if there was another open request immediately
@@ -1211,6 +1241,11 @@ message GroupExpirationTimerUpdate {
optional bytes updaterAci = 2;
}
+message PollTerminateUpdate {
+ uint64 targetSentTimestamp = 1;
+ string question = 2; // Between 1-100 characters
+}
+
message StickerPack {
bytes packId = 1;
bytes packKey = 2;
diff --git a/ts/CI.preload.ts b/ts/CI.preload.ts
index 7c8d9e84b0..5d92942c35 100644
--- a/ts/CI.preload.ts
+++ b/ts/CI.preload.ts
@@ -201,8 +201,13 @@ export function getCI({
}
async function exportLocalBackup(backupsBaseDir: string): Promise {
- const { snapshotDir } =
- await backupsService.exportLocalBackup(backupsBaseDir);
+ const { snapshotDir } = await backupsService.exportLocalBackup(
+ backupsBaseDir,
+ {
+ type: 'local-encrypted',
+ localBackupSnapshotDir: backupsBaseDir,
+ }
+ );
return snapshotDir;
}
diff --git a/ts/RemoteConfig.dom.ts b/ts/RemoteConfig.dom.ts
index ea9c666c86..6c41984eae 100644
--- a/ts/RemoteConfig.dom.ts
+++ b/ts/RemoteConfig.dom.ts
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import lodash from 'lodash';
+import semver from 'semver';
import type { getConfig } from './textsecure/WebAPI.preload.js';
import { createLogger } from './logging/log.std.js';
@@ -14,12 +15,21 @@ import { HashType } from './types/Crypto.std.js';
import { getCountryCode } from './types/PhoneNumber.std.js';
import { parseRemoteClientExpiration } from './util/parseRemoteClientExpiration.dom.js';
import type { StorageInterface } from './types/Storage.d.ts';
+import { ToastType } from './types/Toast.dom.js';
const { get, throttle } = lodash;
const log = createLogger('RemoteConfig');
-const KnownConfigKeys = [
+// Semver flags must always be set to a valid semver (no empty enabled-only keys)
+const SemverKeys = [
+ 'desktop.plaintextExport.beta',
+ 'desktop.plaintextExport.prod',
+] as const;
+
+export type SemverKeyType = (typeof SemverKeys)[number];
+
+const ScalarKeys = [
'desktop.chatFolders.alpha',
'desktop.chatFolders.beta',
'desktop.chatFolders.prod',
@@ -56,6 +66,8 @@ const KnownConfigKeys = [
'global.textAttachmentLimitBytes',
] as const;
+const KnownConfigKeys = [...SemverKeys, ...ScalarKeys] as const;
+
export type ConfigKeyType = (typeof KnownConfigKeys)[number];
type ConfigValueType = {
@@ -139,6 +151,7 @@ export const _refreshRemoteConfig = async ({
}
const oldConfig = config;
+ let semverError = false;
config = Array.from(newConfigValues.entries()).reduce(
(acc, [name, value]) => {
const enabled = value !== undefined && value.toLowerCase() !== 'false';
@@ -169,6 +182,17 @@ export const _refreshRemoteConfig = async ({
const hasChanged =
previouslyEnabled !== enabled || previousValue !== configValue.value;
+ if (
+ SemverKeys.includes(configValue.name as SemverKeyType) &&
+ configValue.enabled &&
+ (!configValue.value || !semver.parse(configValue.value))
+ ) {
+ log.error(
+ `Key ${name} had invalid semver value '${configValue.value}'`
+ );
+ semverError = true;
+ }
+
// If enablement changes at all, notify listeners
const currentListeners = listeners[name] || [];
if (hasChanged) {
@@ -187,6 +211,12 @@ export const _refreshRemoteConfig = async ({
{}
);
+ if (semverError && config['desktop.internalUser']?.enabled) {
+ window.reduxActions.toast.showToast({
+ toastType: ToastType.Error,
+ });
+ }
+
const remoteExpirationValue = getValue('desktop.clientExpiration');
if (!remoteExpirationValue) {
// If remote configuration fetch worked - we are not expired anymore.
diff --git a/ts/axo/AxoAlertDialog.dom.tsx b/ts/axo/AxoAlertDialog.dom.tsx
index 30254748ba..654cedfc84 100644
--- a/ts/axo/AxoAlertDialog.dom.tsx
+++ b/ts/axo/AxoAlertDialog.dom.tsx
@@ -215,7 +215,7 @@ export namespace AxoAlertDialog {
* ----------------------------------
*/
- export type ActionVariant = 'primary' | 'destructive';
+ export type ActionVariant = 'primary' | 'secondary' | 'destructive';
export type ActionProps = Readonly<{
variant: ActionVariant;
diff --git a/ts/axo/AxoDialog.dom.tsx b/ts/axo/AxoDialog.dom.tsx
index 1e3f20a481..83839c8887 100644
--- a/ts/axo/AxoDialog.dom.tsx
+++ b/ts/axo/AxoDialog.dom.tsx
@@ -363,6 +363,7 @@ export namespace AxoDialog {
variant: ActionVariant;
symbol?: AxoSymbol.InlineGlyphName;
arrow?: boolean;
+ experimentalSpinner?: { 'aria-label': string } | null;
onClick: () => void;
children: ReactNode;
}>;
@@ -373,9 +374,10 @@ export namespace AxoDialog {
variant={props.variant}
symbol={props.symbol}
arrow={props.arrow}
+ experimentalSpinner={props.experimentalSpinner}
+ onClick={props.onClick}
size="md"
width="grow"
- onClick={props.onClick}
>
{props.children}
diff --git a/ts/components/GlobalModalContainer.dom.tsx b/ts/components/GlobalModalContainer.dom.tsx
index aecbdf4234..7ca3656791 100644
--- a/ts/components/GlobalModalContainer.dom.tsx
+++ b/ts/components/GlobalModalContainer.dom.tsx
@@ -156,6 +156,9 @@ export type PropsType = {
// LowDiskSpaceBackupImportModal
lowDiskSpaceBackupImportModal: { bytesNeeded: number } | null;
hideLowDiskSpaceBackupImportModal: () => void;
+ // PlaintextExportWorkflow
+ shouldShowPlaintextExportWorkflow: boolean;
+ renderPlaintextExportWorkflow: () => JSX.Element;
};
export function GlobalModalContainer({
@@ -255,13 +258,21 @@ export function GlobalModalContainer({
// LowDiskSpaceBackupImportModal
lowDiskSpaceBackupImportModal,
hideLowDiskSpaceBackupImportModal,
+ // PlaintextExportWorkflow
+ shouldShowPlaintextExportWorkflow,
+ renderPlaintextExportWorkflow,
}: PropsType): JSX.Element | null {
// We want the following dialogs to show in this order:
+ // 0. Stateful multi-modal workflows
// 1. Errors
// 2. Safety Number Changes
// 3. Forward Modal, so other modals can open it
// 4. The Rest (in no particular order, but they're ordered alphabetically)
+ if (shouldShowPlaintextExportWorkflow) {
+ return renderPlaintextExportWorkflow();
+ }
+
// Errors
if (errorModalProps) {
return renderErrorModal(errorModalProps);
diff --git a/ts/components/PlaintextExportWorkflow.dom.stories.tsx b/ts/components/PlaintextExportWorkflow.dom.stories.tsx
new file mode 100644
index 0000000000..a36f3d5e65
--- /dev/null
+++ b/ts/components/PlaintextExportWorkflow.dom.stories.tsx
@@ -0,0 +1,175 @@
+// Copyright 2025 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import React from 'react';
+import { action } from '@storybook/addon-actions';
+import { PlaintextExportWorkflow } from './PlaintextExportWorkflow.dom.js';
+import {
+ PlaintextExportErrors,
+ PlaintextExportSteps,
+} from '../types/Backups.std.js';
+
+import type { PropsType } from './PlaintextExportWorkflow.dom.js';
+import type { ComponentMeta } from '../storybook/types.std.js';
+
+const { i18n } = window.SignalContext;
+
+export default {
+ title: 'Components/PlaintextExportWorkflow',
+ component: PlaintextExportWorkflow,
+ args: {
+ cancelWorkflow: action('cancelWorkflow'),
+ clearWorkflow: action('clearWorkflow'),
+ i18n,
+ openFileInFolder: action('openFileInFolder'),
+ osName: undefined,
+ verifyWithOSForExport: action('verifyWithOSForExport'),
+ workflow: {
+ step: PlaintextExportSteps.ConfirmingExport,
+ },
+ },
+} satisfies ComponentMeta;
+
+export function ConfirmingExport(args: PropsType): JSX.Element {
+ return ;
+}
+
+export function ConfirmingWithOS(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
+
+export function ChoosingLocation(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
+
+export function ExportingMessages(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
+
+export function ExportingAttachments(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
+
+export function CompleteMac(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
+
+export function CompleteLinux(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
+
+export function ErrorGeneric(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
+
+export function ErrorNotEnoughStorage(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
+
+export function ErrorRanOutOfStorage(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
+
+export function ErrorStoragePermissions(args: PropsType): JSX.Element {
+ return (
+
+ );
+}
diff --git a/ts/components/PlaintextExportWorkflow.dom.tsx b/ts/components/PlaintextExportWorkflow.dom.tsx
new file mode 100644
index 0000000000..ec43f886f2
--- /dev/null
+++ b/ts/components/PlaintextExportWorkflow.dom.tsx
@@ -0,0 +1,306 @@
+// Copyright 2025 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import React from 'react';
+
+import {
+ PlaintextExportErrors,
+ PlaintextExportSteps,
+} from '../types/Backups.std.js';
+import { AxoDialog } from '../axo/AxoDialog.dom.js';
+import { AxoAlertDialog } from '../axo/AxoAlertDialog.dom.js';
+
+import type { PlaintextExportWorkflowType } from '../types/Backups.std.js';
+import type { LocalizerType } from '../types/I18N.std.js';
+import { AxoCheckbox } from '../axo/AxoCheckbox.dom.js';
+import { formatFileSize } from '../util/formatFileSize.std.js';
+import { ProgressBar } from './ProgressBar.dom.js';
+import { missingCaseError } from '../util/missingCaseError.std.js';
+import { tw } from '../axo/tw.dom.js';
+import { I18n } from './I18n.dom.js';
+
+export type PropsType = {
+ cancelWorkflow: () => unknown;
+ clearWorkflow: () => unknown;
+ i18n: LocalizerType;
+ openFileInFolder: (path: string) => unknown;
+ osName: 'linux' | 'macos' | 'windows' | undefined;
+ verifyWithOSForExport: (includeMedia: boolean) => unknown;
+ workflow: PlaintextExportWorkflowType;
+};
+
+function Bold(parts: Array) {
+ return {parts};
+}
+function Secondary(parts: Array) {
+ return {parts};
+}
+
+export function PlaintextExportWorkflow({
+ cancelWorkflow,
+ clearWorkflow,
+ i18n,
+ openFileInFolder,
+ osName,
+ verifyWithOSForExport,
+ workflow,
+}: PropsType): JSX.Element {
+ const [includeMedia, setIncludeMedia] = React.useState(true);
+ const { step } = workflow;
+
+ if (
+ step === PlaintextExportSteps.ConfirmingExport ||
+ step === PlaintextExportSteps.ConfirmingWithOS ||
+ step === PlaintextExportSteps.ChoosingLocation
+ ) {
+ const shouldShowSpinner = step !== PlaintextExportSteps.ConfirmingExport;
+
+ return (
+
+
+
+
+