mirror of
https://github.com/element-hq/element-desktop.git
synced 2025-12-05 01:10:21 +00:00
Compare commits
114 Commits
85cdd9a493
...
v1.11.101-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68216568b0 | ||
|
|
9cf81e4484 | ||
|
|
bb2435b529 | ||
|
|
c0efe2334b | ||
|
|
0c2c26af75 | ||
|
|
3c9b229664 | ||
|
|
e5815f3e3c | ||
|
|
b5febd1a07 | ||
|
|
747245e63f | ||
|
|
a1bba6f8d3 | ||
|
|
3c39998b0a | ||
|
|
10f09c90c7 | ||
|
|
cd9b48d87f | ||
|
|
10e2c5c1e4 | ||
|
|
0ad720454c | ||
|
|
ccb1c59076 | ||
|
|
9e315e9b05 | ||
|
|
3a8726f953 | ||
|
|
abaaadd2eb | ||
|
|
2699d04fd1 | ||
|
|
00f3777fcf | ||
|
|
2b928c3f50 | ||
|
|
0b167fe278 | ||
|
|
8db9c620df | ||
|
|
2c60929139 | ||
|
|
9a8ca9980f | ||
|
|
451129d468 | ||
|
|
42a3b8b68c | ||
|
|
56bbea995f | ||
|
|
c72d23f995 | ||
|
|
570a8bdefe | ||
|
|
1d3408bb37 | ||
|
|
eef66d9795 | ||
|
|
a70c5e0215 | ||
|
|
d388b1e64a | ||
|
|
216b1c2157 | ||
|
|
72da1a90db | ||
|
|
37bc884d77 | ||
|
|
a43d087c02 | ||
|
|
906b5bc93c | ||
|
|
41ff61421d | ||
|
|
65cdeaa9de | ||
|
|
262abf5847 | ||
|
|
4aa0454442 | ||
|
|
681365fd2f | ||
|
|
f566c83c81 | ||
|
|
1aab67e874 | ||
|
|
4ded16f12e | ||
|
|
12ea5fa91a | ||
|
|
83775c9104 | ||
|
|
1817e7b216 | ||
|
|
31868f833a | ||
|
|
e02bbf80ff | ||
|
|
42ac29359c | ||
|
|
db4c544b72 | ||
|
|
fca69978aa | ||
|
|
0559886b77 | ||
|
|
f59c38a07e | ||
|
|
bff9c344b6 | ||
|
|
30a464fcdc | ||
|
|
2cdf1cf3b6 | ||
|
|
0e8785e7ac | ||
|
|
7ea9bf1944 | ||
|
|
4fe0384523 | ||
|
|
aabbb8772d | ||
|
|
dde93f30f4 | ||
|
|
4486f6046e | ||
|
|
b2fff67cad | ||
|
|
575a9fa564 | ||
|
|
8d78eedb34 | ||
|
|
791bfc63b3 | ||
|
|
c1b2139ca2 | ||
|
|
559bb5b672 | ||
|
|
98e865ebae | ||
|
|
df576b8dc5 | ||
|
|
76e55014e0 | ||
|
|
6911eb203b | ||
|
|
74aebb59e1 | ||
|
|
e05afba0b5 | ||
|
|
0bb6c47e6f | ||
|
|
bbfb8d3f47 | ||
|
|
789798e595 | ||
|
|
a8a5a955e5 | ||
|
|
07c01e7117 | ||
|
|
e6e46aa45f | ||
|
|
11a76d60b1 | ||
|
|
585b5ea5cb | ||
|
|
4181c6f95a | ||
|
|
20d3e0af9a | ||
|
|
4fd75b608a | ||
|
|
1d7a2e632d | ||
|
|
6be83576eb | ||
|
|
443be796ee | ||
|
|
1e0f25725e | ||
|
|
1fef39fcf1 | ||
|
|
2f872c3548 | ||
|
|
424bb8172a | ||
|
|
0212b77b20 | ||
|
|
536918fb1c | ||
|
|
e4d5863606 | ||
|
|
21244d454f | ||
|
|
f38e76db01 | ||
|
|
50277e00a5 | ||
|
|
4979c8fbed | ||
|
|
610cd5d370 | ||
|
|
7c858d358b | ||
|
|
7098789689 | ||
|
|
618c04d613 | ||
|
|
702c9d2b60 | ||
|
|
75b77274f5 | ||
|
|
fa9f0f4474 | ||
|
|
11c58a90ca | ||
|
|
ef6c8871a2 | ||
|
|
bc49c1d58b |
19
.github/workflows/build_and_deploy.yaml
vendored
19
.github/workflows/build_and_deploy.yaml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
name: Windows ${{ matrix.arch }}
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ia32, x64, arm64]
|
||||
arch: [x64, arm64]
|
||||
uses: ./.github/workflows/build_windows.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
set -x
|
||||
|
||||
# Windows
|
||||
for arch in x64 ia32 arm64
|
||||
for arch in x64 arm64
|
||||
do
|
||||
if [ -d "win-$arch" ]; then
|
||||
mkdir -p packages.element.io/{install,update}/win32/$arch
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
set -x
|
||||
|
||||
# Windows
|
||||
for arch in x64 ia32 arm64
|
||||
for arch in x64 arm64
|
||||
do
|
||||
[ -d "win-$arch" ] && mv packages.element.io/install/win32/$arch/{*,"Element Nightly Setup"}.exe
|
||||
done
|
||||
@@ -167,7 +167,7 @@ jobs:
|
||||
set -x
|
||||
|
||||
# Windows
|
||||
for arch in x64 ia32 arm64
|
||||
for arch in x64 arm64
|
||||
do
|
||||
if [ -d "win-$arch" ]; then
|
||||
pushd packages.element.io/install/win32/$arch
|
||||
@@ -202,19 +202,12 @@ jobs:
|
||||
name: packages.element.io
|
||||
path: packages.element.io
|
||||
|
||||
# Workaround for https://www.cloudflarestatus.com/incidents/t5nrjmpxc1cj
|
||||
- uses: unfor19/install-aws-cli-action@e8b481e524a99f37fbd39fdc1dcb3341ab091367 # v1
|
||||
if: needs.prepare.outputs.deploy == 'true'
|
||||
with:
|
||||
version: 2.22.35
|
||||
verbose: false
|
||||
arch: amd64
|
||||
|
||||
# Checksum algorithm specified as per https://developers.cloudflare.com/r2/examples/aws/aws-cli/
|
||||
- name: Deploy artifacts
|
||||
if: needs.prepare.outputs.deploy == 'true'
|
||||
run: |
|
||||
set -x
|
||||
aws s3 cp --recursive packages.element.io/ s3://$R2_BUCKET/$DEPLOYMENT_DIR --endpoint-url $R2_URL --region auto
|
||||
aws s3 cp --recursive packages.element.io/ s3://$R2_BUCKET/$DEPLOYMENT_DIR --endpoint-url $R2_URL --region auto --checksum-algorithm CRC32
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
|
||||
|
||||
103
.github/workflows/build_and_test.yaml
vendored
103
.github/workflows/build_and_test.yaml
vendored
@@ -13,8 +13,8 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
config: ${{ github.event.pull_request.base.ref == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
version: ${{ github.event.pull_request.base.ref == 'develop' && 'develop' || '' }}
|
||||
config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
version: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'develop' || '' }}
|
||||
|
||||
windows:
|
||||
needs: fetch
|
||||
@@ -25,6 +25,7 @@ jobs:
|
||||
arch: [x64, ia32, arm64]
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
blob_report: true
|
||||
|
||||
linux:
|
||||
needs: fetch
|
||||
@@ -37,101 +38,47 @@ jobs:
|
||||
with:
|
||||
sqlcipher: ${{ matrix.sqlcipher }}
|
||||
arch: ${{ matrix.arch }}
|
||||
blob_report: true
|
||||
|
||||
macos:
|
||||
needs: fetch
|
||||
name: macOS
|
||||
uses: ./.github/workflows/build_macos.yaml
|
||||
with:
|
||||
blob_report: true
|
||||
|
||||
test:
|
||||
needs:
|
||||
- macos
|
||||
- linux
|
||||
- windows
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: macOS Universal
|
||||
os: macos-14
|
||||
artifact: macos
|
||||
executable: "/Users/runner/Applications/Element.app/Contents/MacOS/Element"
|
||||
# We need to mount the DMG and copy the app to the Applications folder as a mounted DMG is
|
||||
# read-only and thus would not allow us to override the fuses as is required for Playwright.
|
||||
prepare_cmd: |
|
||||
hdiutil attach ./dist/*.dmg -mountpoint /Volumes/Element &&
|
||||
rsync -a /Volumes/Element/Element.app ~/Applications/ &&
|
||||
hdiutil detach /Volumes/Element
|
||||
- name: "Linux (amd64) (sqlcipher: system)"
|
||||
os: ubuntu-22.04
|
||||
artifact: linux-amd64-sqlcipher-system
|
||||
executable: "/opt/Element/element-desktop"
|
||||
prepare_cmd: "sudo apt-get -qq update && sudo apt install ./dist/*.deb"
|
||||
- name: "Linux (amd64) (sqlcipher: static)"
|
||||
os: ubuntu-22.04
|
||||
artifact: linux-amd64-sqlcipher-static
|
||||
executable: "/opt/Element/element-desktop"
|
||||
prepare_cmd: "sudo apt-get -qq update && sudo apt install ./dist/*.deb"
|
||||
- name: "Linux (arm64) (sqlcipher: system)"
|
||||
os: ubuntu-22.04-arm
|
||||
artifact: linux-arm64-sqlcipher-system
|
||||
executable: "/opt/Element/element-desktop"
|
||||
prepare_cmd: "sudo apt-get -qq update && sudo apt install -y ./dist/*.deb"
|
||||
- name: "Linux (arm64) (sqlcipher: static)"
|
||||
os: ubuntu-22.04-arm
|
||||
artifact: linux-arm64-sqlcipher-static
|
||||
executable: "/opt/Element/element-desktop"
|
||||
prepare_cmd: "sudo apt-get -qq update && sudo apt install -y ./dist/*.deb"
|
||||
- name: Windows (x86)
|
||||
os: windows-2022
|
||||
artifact: win-ia32
|
||||
executable: "./dist/win-ia32-unpacked/Element.exe"
|
||||
- name: Windows (x64)
|
||||
os: windows-2022
|
||||
artifact: win-x64
|
||||
executable: "./dist/win-unpacked/Element.exe"
|
||||
name: Test ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
tests-done:
|
||||
needs: [windows, linux, macos]
|
||||
runs-on: ubuntu-24.04
|
||||
if: always()
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: dist
|
||||
pattern: blob-report-*
|
||||
path: all-blob-reports
|
||||
merge-multiple: true
|
||||
|
||||
- name: Prepare for tests
|
||||
run: ${{ matrix.prepare_cmd }}
|
||||
if: matrix.prepare_cmd
|
||||
|
||||
# We previously disabled the `EnableNodeCliInspectArguments` fuse, but Playwright requires
|
||||
# it to be enabled to test Electron apps, so turn it back on.
|
||||
- name: Set EnableNodeCliInspectArguments fuse enabled
|
||||
run: $RUN_AS npx @electron/fuses write --app ${{ matrix.executable }} EnableNodeCliInspectArguments=on
|
||||
shell: bash
|
||||
env:
|
||||
# We need sudo on Linux as it is installed in /opt/
|
||||
RUN_AS: ${{ runner.os == 'Linux' && 'sudo' || '' }}
|
||||
|
||||
- name: Run tests
|
||||
uses: coactions/setup-xvfb@6b00cf1889f4e1d5a48635647013c0508128ee1a
|
||||
timeout-minutes: 5
|
||||
with:
|
||||
run: "yarn test ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }}"
|
||||
env:
|
||||
ELEMENT_DESKTOP_EXECUTABLE: ${{ matrix.executable }}
|
||||
- name: Merge into HTML Report
|
||||
run: yarn playwright merge-reports -c ./playwright.config.ts --reporter=html ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}-test
|
||||
path: playwright/html-report
|
||||
name: html-report
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
|
||||
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
||||
run: exit 1
|
||||
|
||||
20
.github/workflows/build_linux.yaml
vendored
20
.github/workflows/build_linux.yaml
vendored
@@ -16,6 +16,10 @@ on:
|
||||
type: string
|
||||
required: true
|
||||
description: "How to link sqlcipher, one of 'system' | 'static'"
|
||||
blob_report:
|
||||
type: boolean
|
||||
required: false
|
||||
description: "Whether to run the blob report"
|
||||
env:
|
||||
SQLCIPHER_BUNDLED: ${{ inputs.sqlcipher == 'static' && '1' || '' }}
|
||||
MAX_GLIBC: 2.31 # bullseye-era glibc, used by glibc-check.sh
|
||||
@@ -84,14 +88,14 @@ jobs:
|
||||
- name: "Get modified files"
|
||||
id: changed_files
|
||||
if: steps.cache.outputs.cache-hit != 'true' && github.event_name == 'pull_request'
|
||||
uses: tj-actions/changed-files@dcc7a0cba800f454d79fff4b993e8c3555bcc0a8 # v45
|
||||
uses: tj-actions/changed-files@823fcebdb31bb35fdf2229d9f769b400309430d0 # v46
|
||||
with:
|
||||
files: |
|
||||
dockerbuild/**
|
||||
|
||||
# This allows contributors to test changes to the dockerbuild image within a pull request
|
||||
- name: Build docker image
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
|
||||
if: steps.changed_files.outputs.any_modified == 'true'
|
||||
with:
|
||||
file: dockerbuild/Dockerfile
|
||||
@@ -183,3 +187,15 @@ jobs:
|
||||
test -f ./dist/element-desktop*.tar.gz
|
||||
env:
|
||||
ARCH: ${{ inputs.arch }}
|
||||
|
||||
test:
|
||||
needs: build
|
||||
uses: ./.github/workflows/build_test.yaml
|
||||
with:
|
||||
artifact: linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
|
||||
runs-on: ${{ inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04' }}
|
||||
executable: /opt/Element*/element-desktop*
|
||||
prepare_cmd: |
|
||||
sudo apt-get -qq update
|
||||
sudo apt install ./dist/*.deb
|
||||
blob_report: ${{ inputs.blob_report }}
|
||||
|
||||
21
.github/workflows/build_macos.yaml
vendored
21
.github/workflows/build_macos.yaml
vendored
@@ -27,6 +27,10 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
description: "The URL to which the output will be deployed."
|
||||
blob_report:
|
||||
type: boolean
|
||||
required: false
|
||||
description: "Whether to run the blob report"
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
build:
|
||||
@@ -58,7 +62,7 @@ jobs:
|
||||
# M1 macos-14 comes without Python preinstalled
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.13"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -145,3 +149,18 @@ jobs:
|
||||
run: |
|
||||
test -f ./dist/Element*.dmg
|
||||
test -f ./dist/Element*-mac.zip
|
||||
|
||||
test:
|
||||
needs: build
|
||||
uses: ./.github/workflows/build_test.yaml
|
||||
with:
|
||||
artifact: macos
|
||||
runs-on: macos-14
|
||||
executable: /Users/runner/Applications/Element*.app/Contents/MacOS/Element*
|
||||
# We need to mount the DMG and copy the app to the Applications folder as a mounted DMG is
|
||||
# read-only and thus would not allow us to override the fuses as is required for Playwright.
|
||||
prepare_cmd: |
|
||||
hdiutil attach ./dist/*.dmg -mountpoint /Volumes/Element &&
|
||||
rsync -a /Volumes/Element/Element*.app ~/Applications/ &&
|
||||
hdiutil detach /Volumes/Element
|
||||
blob_report: ${{ inputs.blob_report }}
|
||||
|
||||
4
.github/workflows/build_prepare.yaml
vendored
4
.github/workflows/build_prepare.yaml
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
aws s3 cp s3://$R2_BUCKET/debian/dists/default/main/binary-amd64/Packages - --endpoint-url $R2_URL --region auto | grep "Package: element-nightly" -A 50 | grep Version -m1 | sed -n 's/Version: //p' >> VERSIONS
|
||||
aws s3 cp s3://$R2_BUCKET/debian/dists/default/main/binary-arm64/Packages - --endpoint-url $R2_URL --region auto | grep "Package: element-nightly" -A 50 | grep Version -m1 | sed -n 's/Version: //p' >> VERSIONS
|
||||
aws s3 cp s3://$R2_BUCKET/nightly/update/win32/x64/RELEASES - --endpoint-url $R2_URL --region auto | awk '{print $2}' | cut -d "-" -f 5 | cut -c 8- >> VERSIONS
|
||||
aws s3 cp s3://$R2_BUCKET/nightly/update/win32/ia32/RELEASES - --endpoint-url $R2_URL --region auto | awk '{print $2}' | cut -d "-" -f 5 | cut -c 8- >> VERSIONS
|
||||
aws s3 cp s3://$R2_BUCKET/nightly/update/win32/arm64/RELEASES - --endpoint-url $R2_URL --region auto | awk '{print $2}' | cut -d "-" -f 5 | cut -c 8- >> VERSIONS
|
||||
|
||||
# Pick the greatest one
|
||||
VERSION=$(cat VERSIONS | sort -uf | tail -n1)
|
||||
@@ -90,8 +90,6 @@ jobs:
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
|
||||
# Workaround for https://www.cloudflarestatus.com/incidents/t5nrjmpxc1cj
|
||||
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
|
||||
R2_BUCKET: ${{ vars.R2_BUCKET }}
|
||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||
|
||||
|
||||
90
.github/workflows/build_test.yaml
vendored
Normal file
90
.github/workflows/build_test.yaml
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
# This action helps run Playwright tests within one of the build_* stages.
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
runs-on:
|
||||
type: string
|
||||
required: true
|
||||
description: "The runner image to use"
|
||||
artifact:
|
||||
type: string
|
||||
required: true
|
||||
description: "The name of the artifact to download"
|
||||
executable:
|
||||
type: string
|
||||
required: true
|
||||
description: "Path to the executable to test"
|
||||
prepare_cmd:
|
||||
type: string
|
||||
required: false
|
||||
description: "Command to run to prepare the executable or environment for testing"
|
||||
blob_report:
|
||||
type: boolean
|
||||
default: false
|
||||
description: "Whether to upload a blob report instead of the HTML report"
|
||||
permissions: {}
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ inputs.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.artifact }}
|
||||
path: dist
|
||||
|
||||
- name: Prepare for tests
|
||||
run: ${{ inputs.prepare_cmd }}
|
||||
if: inputs.prepare_cmd
|
||||
|
||||
- name: Expand executable path
|
||||
id: executable
|
||||
shell: bash
|
||||
env:
|
||||
EXECUTABLE: ${{ inputs.executable }}
|
||||
run: |
|
||||
FILES=($EXECUTABLE)
|
||||
echo "path=${FILES[0]}" >> $GITHUB_OUTPUT
|
||||
|
||||
# We previously disabled the `EnableNodeCliInspectArguments` fuse, but Playwright requires
|
||||
# it to be enabled to test Electron apps, so turn it back on.
|
||||
- name: Set EnableNodeCliInspectArguments fuse enabled
|
||||
run: $RUN_AS npx @electron/fuses write --app "$EXECUTABLE" EnableNodeCliInspectArguments=on
|
||||
shell: bash
|
||||
env:
|
||||
# We need sudo on Linux as it is installed in /opt/
|
||||
RUN_AS: ${{ runner.os == 'Linux' && 'sudo' || '' }}
|
||||
EXECUTABLE: ${{ steps.executable.outputs.path }}
|
||||
|
||||
- name: Run tests
|
||||
uses: coactions/setup-xvfb@6b00cf1889f4e1d5a48635647013c0508128ee1a
|
||||
timeout-minutes: 20
|
||||
with:
|
||||
run: yarn test --project=${{ inputs.artifact }} ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} ${{ inputs.blob_report == false && '--reporter=html' || '' }}
|
||||
env:
|
||||
ELEMENT_DESKTOP_EXECUTABLE: ${{ steps.executable.outputs.path }}
|
||||
|
||||
- name: Upload blob report
|
||||
if: always() && inputs.blob_report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: blob-report-${{ inputs.artifact }}
|
||||
path: blob-report
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload HTML report
|
||||
if: always() && inputs.blob_report == false
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.artifact }}-test
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
19
.github/workflows/build_windows.yaml
vendored
19
.github/workflows/build_windows.yaml
vendored
@@ -30,13 +30,17 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
description: "Whether to sign & notarise the build, requires 'packages.element.io' environment"
|
||||
blob_report:
|
||||
type: boolean
|
||||
required: false
|
||||
description: "Whether to run the blob report"
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
runs-on: windows-2025
|
||||
environment: ${{ inputs.sign && 'packages.element.io' || '' }}
|
||||
env:
|
||||
SIGNTOOL_PATH: "C:/Program Files (x86)/Windows Kits/10/bin/10.0.22000.0/x86/signtool.exe"
|
||||
SIGNTOOL_PATH: "C:/Program Files (x86)/Windows Kits/10/bin/10.0.26100.0/x86/signtool.exe"
|
||||
steps:
|
||||
- uses: nbucic/variable-mapper@0673f6891a0619ba7c002ecfed0f9f4f39017b6f
|
||||
id: config
|
||||
@@ -57,7 +61,7 @@ jobs:
|
||||
"target": "i686-pc-windows-msvc",
|
||||
"build-args": "--ia32",
|
||||
"arch": "x86",
|
||||
"extra_config": "{\"user_notice\": {\"title\": \"Your desktop support ends soon\",\"description\": \"Support for 32-bit Windows installations will end soon, this impacts you. Transition to the web or mobile app for continued access.\"}}"
|
||||
"extra_config": "{\"user_notice\": {\"title\": \"Your desktop environment is unsupported.\",\"description\": \"Support for 32-bit Windows installations has ended. Transition to the web or mobile app for continued access.\"}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,3 +220,12 @@ jobs:
|
||||
Test-Path './dist/squirrel-windows*/element-desktop-*-full.nupkg'
|
||||
Test-Path './dist/squirrel-windows*/RELEASES'
|
||||
Test-Path './dist/Element*.msi'
|
||||
|
||||
test:
|
||||
needs: build
|
||||
uses: ./.github/workflows/build_test.yaml
|
||||
with:
|
||||
artifact: win-${{ inputs.arch }}
|
||||
runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2022' }}
|
||||
executable: ./dist/win*-unpacked/Element*.exe
|
||||
blob_report: ${{ inputs.blob_report }}
|
||||
|
||||
6
.github/workflows/dockerbuild.yaml
vendored
6
.github/workflows/dockerbuild.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
install: true
|
||||
|
||||
- name: Build test image
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
|
||||
with:
|
||||
file: dockerbuild/Dockerfile
|
||||
push: false
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: docker run -v $PWD:/project element-desktop-dockerbuild yarn install
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@327cd5a69de6c009b9ce71bce8395f28e651bf99
|
||||
uses: docker/login-action@6d4b68b490aef8836e8fb5e50ee7b3bdfa5894f0
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
- name: Build and push Docker image
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
|
||||
with:
|
||||
file: dockerbuild/Dockerfile
|
||||
push: true
|
||||
|
||||
@@ -1 +1 @@
|
||||
v22.14.0
|
||||
v22.15.0
|
||||
|
||||
154
CHANGELOG.md
154
CHANGELOG.md
@@ -1,3 +1,157 @@
|
||||
Changes in [1.11.100](https://github.com/element-hq/element-desktop/releases/tag/v1.11.100) (2025-05-06)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Move rich topics out of labs / stabilise MSC3765 ([#29817](https://github.com/element-hq/element-web/pull/29817)). Contributed by @Johennes.
|
||||
* Spell out that Element Web does \*not\* work on mobile. ([#29211](https://github.com/element-hq/element-web/pull/29211)). Contributed by @ara4n.
|
||||
* Add message preview support to the new room list ([#29784](https://github.com/element-hq/element-web/pull/29784)). Contributed by @dbkr.
|
||||
* Global configuration flag for media previews ([#29582](https://github.com/element-hq/element-web/pull/29582)). Contributed by @Half-Shot.
|
||||
* New room list: add partial keyboard shortcuts support ([#29783](https://github.com/element-hq/element-web/pull/29783)). Contributed by @florianduros.
|
||||
* MVVM RoomSummaryCard Topic ([#29710](https://github.com/element-hq/element-web/pull/29710)). Contributed by @MarcWadai.
|
||||
* Warn on self change from settings > roles ([#28926](https://github.com/element-hq/element-web/pull/28926)). Contributed by @MarcWadai.
|
||||
* New room list: new visual for invitation ([#29773](https://github.com/element-hq/element-web/pull/29773)). Contributed by @florianduros.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Apply workaround to fix app launching on Linux ([#2308](https://github.com/element-hq/element-desktop/pull/2308)). Contributed by @dbkr.
|
||||
* Notification fixes for Windows - AppID name was messing up handler ([#2275](https://github.com/element-hq/element-desktop/pull/2275)). Contributed by @Fusseldieb.
|
||||
* Fix incorrect display of the user info display name ([#29826](https://github.com/element-hq/element-web/pull/29826)). Contributed by @langleyd.
|
||||
* RoomListStore: Remove invite rooms on decline ([#29804](https://github.com/element-hq/element-web/pull/29804)). Contributed by @MidhunSureshR.
|
||||
* Fix the buttons not being displayed with long preview text ([#29811](https://github.com/element-hq/element-web/pull/29811)). Contributed by @dbkr.
|
||||
* New room list: fix missing/incorrect notification decoration ([#29796](https://github.com/element-hq/element-web/pull/29796)). Contributed by @florianduros.
|
||||
* New Room List: Prevent potential scroll jump/flicker when switching spaces ([#29781](https://github.com/element-hq/element-web/pull/29781)). Contributed by @MidhunSureshR.
|
||||
* New room list: fix incorrect decoration ([#29770](https://github.com/element-hq/element-web/pull/29770)). Contributed by @florianduros.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.99](https://github.com/element-hq/element-desktop/releases/tag/v1.11.99) (2025-04-23)
|
||||
======================================================================================================
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Fix `io.element.desktop` protocol handler ([#2281](https://github.com/element-hq/element-desktop/pull/2281)). Contributed by @RiotRobot.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.98](https://github.com/element-hq/element-desktop/releases/tag/v1.11.98) (2025-04-22)
|
||||
======================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Remove support for 32 bit / ia32 Windows. ([#2225](https://github.com/element-hq/element-desktop/pull/2225)). Contributed by @Half-Shot.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* Update config logging to specify config file path ([#2231](https://github.com/element-hq/element-desktop/pull/2231)). Contributed by @nbolton.
|
||||
* Support specifying the profile dir path via env var (#2226) ([#2246](https://github.com/element-hq/element-desktop/pull/2246)). Contributed by @schuhj.
|
||||
* print better errors in the search view instead of a blocking modal ([#29724](https://github.com/element-hq/element-web/pull/29724)). Contributed by @Jujure.
|
||||
* New room list: video room and video call decoration ([#29693](https://github.com/element-hq/element-web/pull/29693)). Contributed by @florianduros.
|
||||
* Remove Secure Backup, Cross-signing and Cryptography sections in `Security & Privacy` user settings ([#29088](https://github.com/element-hq/element-web/pull/29088)). Contributed by @florianduros.
|
||||
* Allow reporting a room when rejecting an invite. ([#29570](https://github.com/element-hq/element-web/pull/29570)). Contributed by @Half-Shot.
|
||||
* RoomListViewModel: Reset primary and secondary filters on space change ([#29672](https://github.com/element-hq/element-web/pull/29672)). Contributed by @MidhunSureshR.
|
||||
* RoomListStore: Support specific sorting requirements for muted rooms ([#29665](https://github.com/element-hq/element-web/pull/29665)). Contributed by @MidhunSureshR.
|
||||
* New room list: add notification options menu ([#29639](https://github.com/element-hq/element-web/pull/29639)). Contributed by @florianduros.
|
||||
* Room List: Scroll to top of the list when active room is not in the list ([#29650](https://github.com/element-hq/element-web/pull/29650)). Contributed by @MidhunSureshR.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix unwanted form submit behaviour in memberlist ([#29747](https://github.com/element-hq/element-web/pull/29747)). Contributed by @MidhunSureshR.
|
||||
* New room list: fix public room icon visibility when filter change ([#29737](https://github.com/element-hq/element-web/pull/29737)). Contributed by @florianduros.
|
||||
* Fix custom theme support for short hex \& rgba hex strings ([#29726](https://github.com/element-hq/element-web/pull/29726)). Contributed by @t3chguy.
|
||||
* New room list: minor visual fixes ([#29723](https://github.com/element-hq/element-web/pull/29723)). Contributed by @florianduros.
|
||||
* Fix getOidcCallbackUrl for Element Desktop ([#29711](https://github.com/element-hq/element-web/pull/29711)). Contributed by @t3chguy.
|
||||
* Fix some webp images improperly marked as animated ([#29713](https://github.com/element-hq/element-web/pull/29713)). Contributed by @Petersmit27.
|
||||
* Revert deletion of hydrateSession ([#29703](https://github.com/element-hq/element-web/pull/29703)). Contributed by @Jujure.
|
||||
* Fix converttoroom \& converttodm not working ([#29705](https://github.com/element-hq/element-web/pull/29705)). Contributed by @t3chguy.
|
||||
* Ensure forceCloseAllModals also closes priority/static modals ([#29706](https://github.com/element-hq/element-web/pull/29706)). Contributed by @t3chguy.
|
||||
* Continue button is disabled when uploading a recovery key file ([#29695](https://github.com/element-hq/element-web/pull/29695)). Contributed by @Giwayume.
|
||||
* Catch errors after syncing recovery ([#29691](https://github.com/element-hq/element-web/pull/29691)). Contributed by @andybalaam.
|
||||
* New room list: fix multiple visual issues ([#29673](https://github.com/element-hq/element-web/pull/29673)). Contributed by @florianduros.
|
||||
* New Room List: Fix mentions filter matching rooms with any highlight ([#29668](https://github.com/element-hq/element-web/pull/29668)). Contributed by @MidhunSureshR.
|
||||
* Fix truncated emoji label during emoji SAS ([#29643](https://github.com/element-hq/element-web/pull/29643)). Contributed by @florianduros.
|
||||
* Remove duplicate jitsi link ([#29642](https://github.com/element-hq/element-web/pull/29642)). Contributed by @dbkr.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.97](https://github.com/element-hq/element-desktop/releases/tag/v1.11.97) (2025-04-08)
|
||||
======================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* New room list: reduce padding between avatar and room list border ([#29634](https://github.com/element-hq/element-web/pull/29634)). Contributed by @florianduros.
|
||||
* Bundle Element Call with Element Web packages ([#29309](https://github.com/element-hq/element-web/pull/29309)). Contributed by @t3chguy.
|
||||
* Hide an event notification if it is redacted ([#29605](https://github.com/element-hq/element-web/pull/29605)). Contributed by @Half-Shot.
|
||||
* Docker: Use nginx-unprivileged as base image ([#29353](https://github.com/element-hq/element-web/pull/29353)). Contributed by @AndrewFerr.
|
||||
* Switch away from nesting React trees and mangling the DOM ([#29586](https://github.com/element-hq/element-web/pull/29586)). Contributed by @t3chguy.
|
||||
* New room list: add notification decoration ([#29552](https://github.com/element-hq/element-web/pull/29552)). Contributed by @florianduros.
|
||||
* RoomListStore: Unread filter should match rooms that were marked as unread ([#29580](https://github.com/element-hq/element-web/pull/29580)). Contributed by @MidhunSureshR.
|
||||
* Add support for hiding videos ([#29496](https://github.com/element-hq/element-web/pull/29496)). Contributed by @Half-Shot.
|
||||
* Use an outline icon for the report room button ([#29573](https://github.com/element-hq/element-web/pull/29573)). Contributed by @robintown.
|
||||
* Generate/load pickle key on SSO ([#29568](https://github.com/element-hq/element-web/pull/29568)). Contributed by @Jujure.
|
||||
* Add report room dialog button/dialog. ([#29513](https://github.com/element-hq/element-web/pull/29513)). Contributed by @Half-Shot.
|
||||
* RoomListViewModel: Make the active room sticky in the list ([#29551](https://github.com/element-hq/element-web/pull/29551)). Contributed by @MidhunSureshR.
|
||||
* Replace checkboxes with Compound checkboxes, and appropriately label each checkbox. ([#29363](https://github.com/element-hq/element-web/pull/29363)). Contributed by @Half-Shot.
|
||||
* New room list: add selection decoration ([#29531](https://github.com/element-hq/element-web/pull/29531)). Contributed by @florianduros.
|
||||
* Simplified Sliding Sync ([#28515](https://github.com/element-hq/element-web/pull/28515)). Contributed by @dbkr.
|
||||
* Add ability to hide images after clicking "show image" ([#29467](https://github.com/element-hq/element-web/pull/29467)). Contributed by @Half-Shot.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix scroll issues in memberlist ([#29392](https://github.com/element-hq/element-web/pull/29392)). Contributed by @MidhunSureshR.
|
||||
* Ensure clicks on spoilers do not get handled by the hidden content ([#29618](https://github.com/element-hq/element-web/pull/29618)). Contributed by @t3chguy.
|
||||
* New room list: add cursor pointer on room list item ([#29627](https://github.com/element-hq/element-web/pull/29627)). Contributed by @florianduros.
|
||||
* Fix missing ambiguous url tooltips on Element Desktop ([#29619](https://github.com/element-hq/element-web/pull/29619)). Contributed by @t3chguy.
|
||||
* New room list: fix spacing and padding ([#29607](https://github.com/element-hq/element-web/pull/29607)). Contributed by @florianduros.
|
||||
* Make fetchdep check out matching branch name ([#29601](https://github.com/element-hq/element-web/pull/29601)). Contributed by @dbkr.
|
||||
* Fix MFileBody fileName not considering `filename` ([#29589](https://github.com/element-hq/element-web/pull/29589)). Contributed by @t3chguy.
|
||||
* Fix token expiry racing with login causing wrong error to be shown ([#29566](https://github.com/element-hq/element-web/pull/29566)). Contributed by @t3chguy.
|
||||
* Fix bug which caused startup to hang if the clock was wound back since a previous session ([#29558](https://github.com/element-hq/element-web/pull/29558)). Contributed by @richvdh.
|
||||
* RoomListViewModel: Reset any primary filter on secondary filter change ([#29562](https://github.com/element-hq/element-web/pull/29562)). Contributed by @MidhunSureshR.
|
||||
* RoomListStore: Unread filter should only filter rooms having unread counts ([#29555](https://github.com/element-hq/element-web/pull/29555)). Contributed by @MidhunSureshR.
|
||||
* In force-verify mode, prevent bypassing by cancelling device verification ([#29487](https://github.com/element-hq/element-web/pull/29487)). Contributed by @andybalaam.
|
||||
* Add title attribute to user identifier ([#29547](https://github.com/element-hq/element-web/pull/29547)). Contributed by @arpitbatra123.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.96](https://github.com/element-hq/element-desktop/releases/tag/v1.11.96) (2025-03-25)
|
||||
======================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* RoomListViewModel: Track the index of the active room in the list ([#29519](https://github.com/element-hq/element-web/pull/29519)). Contributed by @MidhunSureshR.
|
||||
* New room list: add empty state ([#29512](https://github.com/element-hq/element-web/pull/29512)). Contributed by @florianduros.
|
||||
* Implement `MessagePreviewViewModel` ([#29514](https://github.com/element-hq/element-web/pull/29514)). Contributed by @MidhunSureshR.
|
||||
* RoomListViewModel: Add functionality to toggle message preview setting ([#29511](https://github.com/element-hq/element-web/pull/29511)). Contributed by @MidhunSureshR.
|
||||
* New room list: add more options menu on room list item ([#29445](https://github.com/element-hq/element-web/pull/29445)). Contributed by @florianduros.
|
||||
* RoomListViewModel: Provide a way to resort the room list and track the active sort method ([#29499](https://github.com/element-hq/element-web/pull/29499)). Contributed by @MidhunSureshR.
|
||||
* Change \*All rooms\* meta space name to \*All Chats\* ([#29498](https://github.com/element-hq/element-web/pull/29498)). Contributed by @florianduros.
|
||||
* Add setting to hide avatars of rooms you have been invited to. ([#29497](https://github.com/element-hq/element-web/pull/29497)). Contributed by @Half-Shot.
|
||||
* Room List Store: Save preferred sorting algorithm and use that on app launch ([#29493](https://github.com/element-hq/element-web/pull/29493)). Contributed by @MidhunSureshR.
|
||||
* Add key storage toggle to Encryption settings ([#29310](https://github.com/element-hq/element-web/pull/29310)). Contributed by @dbkr.
|
||||
* New room list: add primary filters ([#29481](https://github.com/element-hq/element-web/pull/29481)). Contributed by @florianduros.
|
||||
* Implement MSC4142: Remove unintentional intentional mentions in replies ([#28209](https://github.com/element-hq/element-web/pull/28209)). Contributed by @tulir.
|
||||
* White background for 'They do not match' button ([#29470](https://github.com/element-hq/element-web/pull/29470)). Contributed by @andybalaam.
|
||||
* RoomListViewModel: Support secondary filters in the view model ([#29465](https://github.com/element-hq/element-web/pull/29465)). Contributed by @MidhunSureshR.
|
||||
* RoomListViewModel: Support primary filters in the view model ([#29454](https://github.com/element-hq/element-web/pull/29454)). Contributed by @MidhunSureshR.
|
||||
* Room List Store: Implement secondary filters ([#29458](https://github.com/element-hq/element-web/pull/29458)). Contributed by @MidhunSureshR.
|
||||
* Room List Store: Implement rest of the primary filters ([#29444](https://github.com/element-hq/element-web/pull/29444)). Contributed by @MidhunSureshR.
|
||||
* Room List Store: Support filters by implementing just the favourite filter ([#29433](https://github.com/element-hq/element-web/pull/29433)). Contributed by @MidhunSureshR.
|
||||
* Move toggle switch for integration manager for a11y ([#29436](https://github.com/element-hq/element-web/pull/29436)). Contributed by @Half-Shot.
|
||||
* New room list: basic flat list ([#29368](https://github.com/element-hq/element-web/pull/29368)). Contributed by @florianduros.
|
||||
* Improve rageshake upload experience by providing useful error information ([#29378](https://github.com/element-hq/element-web/pull/29378)). Contributed by @Half-Shot.
|
||||
* Add more functionality to the room list vm ([#29402](https://github.com/element-hq/element-web/pull/29402)). Contributed by @MidhunSureshR.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix `--no-update` command line flag ([#2210](https://github.com/element-hq/element-desktop/pull/2210)). Contributed by @t3chguy.
|
||||
* New room list: fix compose menu action in space ([#29500](https://github.com/element-hq/element-web/pull/29500)). Contributed by @florianduros.
|
||||
* Change ToggleHiddenEventVisibility \& GoToHome KeyBindingActions ([#29374](https://github.com/element-hq/element-web/pull/29374)). Contributed by @gy-mate.
|
||||
* Fix Docker Healthcheck ([#29471](https://github.com/element-hq/element-web/pull/29471)). Contributed by @benbz.
|
||||
* Room List Store: Fetch rooms after space store is ready + attach store to window ([#29453](https://github.com/element-hq/element-web/pull/29453)). Contributed by @MidhunSureshR.
|
||||
* Room List Store: Fix bug where left rooms appear in room list ([#29452](https://github.com/element-hq/element-web/pull/29452)). Contributed by @MidhunSureshR.
|
||||
* Add space to the bottom of the room summary actions below leave room ([#29270](https://github.com/element-hq/element-web/pull/29270)). Contributed by @langleyd.
|
||||
* Show error screens in group calls ([#29254](https://github.com/element-hq/element-web/pull/29254)). Contributed by @robintown.
|
||||
* Prevent user from accidentally triggering multiple identity resets ([#29388](https://github.com/element-hq/element-web/pull/29388)). Contributed by @uhoreg.
|
||||
* Remove buggy tooltip on room intro \& homepage ([#29406](https://github.com/element-hq/element-web/pull/29406)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.95](https://github.com/element-hq/element-desktop/releases/tag/v1.11.95) (2025-03-11)
|
||||
======================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
- [Introduction](../README.md)
|
||||
|
||||
# Build
|
||||
# Build/Debug
|
||||
|
||||
- [Native Node modules](native-node-modules.md)
|
||||
- [Windows requirements](windows-requirements.md)
|
||||
- [Using gdb](gdb.md)
|
||||
|
||||
# Distribution
|
||||
|
||||
|
||||
46
docs/gdb.md
Normal file
46
docs/gdb.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Using gdb against Element-Desktop
|
||||
|
||||
Occasionally it is useful to be able to connect to a running Element-Desktop
|
||||
with [`gdb`](https://sourceware.org/gdb/), or to analayze a coredump. For this,
|
||||
you will need debug symbols.
|
||||
|
||||
1. If you don't already have the right version of Element-Desktop (eg because
|
||||
you are analyzing someone else's coredump), download and unpack the tarball
|
||||
from https://packages.element.io/desktop/install/linux/. If it was a
|
||||
nightly, your best bet may be to download the deb from
|
||||
https://packages.element.io/debian/pool/main/e/element-nightly/ and unpack
|
||||
it.
|
||||
2. Figure out which version of Electron your Element-Desktop is based on. The
|
||||
best way to do this is to figure out the version of Element-Desktop, then
|
||||
look at
|
||||
[`yarn.lock`](https://github.com/element-hq/element-desktop/blob/develop/yarn.lock)
|
||||
for the corresponding version. There should be an entry starting
|
||||
`electron@`, and under it a `version` line: this will tell you the version
|
||||
of Electron that was used for that version of Element-Desktop.
|
||||
|
||||
3. Go to [Electron's releases page](https://github.com/electron/electron/releases/)
|
||||
and find the version you just identified. Under "Assets", download
|
||||
`electron-v<version>-linux-x64-debug.zip` (or, the -debug zip corresponding to your
|
||||
architecture).
|
||||
|
||||
4. The debug zip has a structure like:
|
||||
|
||||
```
|
||||
.
|
||||
├── debug
|
||||
│ ├── chrome_crashpad_handler.debug
|
||||
│ ├── electron.debug
|
||||
│ ├── libEGL.so.debug
|
||||
│ ├── libffmpeg.so.debug
|
||||
│ ├── libGLESv2.so.debug
|
||||
│ └── libvk_swiftshader.so.debug
|
||||
├── LICENSE
|
||||
├── LICENSES.chromium.html
|
||||
└── version
|
||||
```
|
||||
|
||||
Take all the contents of `debug`, and copy them into the Element-Desktop directory,
|
||||
so that `electron.debug` is alongside the `element-desktop-nightly` executable.
|
||||
|
||||
5. You now have a thing you can gdb as normal, either as `gdb --args element-desktop-nightly`, or
|
||||
`gdb element-desktop-nightly core`.
|
||||
@@ -1,10 +1,6 @@
|
||||
import * as os from "node:os";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as plist from "plist";
|
||||
import { AfterPackContext, Arch, Configuration as BaseConfiguration, Platform } from "electron-builder";
|
||||
import { computeData } from "app-builder-lib/out/asar/integrity";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { Configuration as BaseConfiguration } from "electron-builder";
|
||||
|
||||
/**
|
||||
* This script has different outputs depending on your os platform.
|
||||
@@ -46,26 +42,6 @@ interface Configuration extends BaseConfiguration {
|
||||
} & BaseConfiguration["deb"];
|
||||
}
|
||||
|
||||
async function injectAsarIntegrity(context: AfterPackContext) {
|
||||
const packager = context.packager;
|
||||
|
||||
// We only need to re-generate asar on universal Mac builds, due to https://github.com/electron/universal/issues/116
|
||||
if (packager.platform !== Platform.MAC || context.arch !== Arch.universal) return;
|
||||
|
||||
const resourcesPath = packager.getResourcesDir(context.appOutDir);
|
||||
const asarIntegrity = await computeData({
|
||||
resourcesPath,
|
||||
resourcesRelativePath: "Resources",
|
||||
resourcesDestinationPath: resourcesPath,
|
||||
extraResourceMatchers: [],
|
||||
});
|
||||
|
||||
const plistPath = path.join(resourcesPath, "..", "Info.plist");
|
||||
const data = plist.parse(await readFile(plistPath, "utf8")) as unknown as Writable<plist.PlistObject>;
|
||||
data["ElectronAsarIntegrity"] = asarIntegrity as unknown as Writable<plist.PlistValue>;
|
||||
await writeFile(plistPath, plist.build(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import('electron-builder').Configuration}
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
@@ -90,9 +66,6 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
loadBrowserProcessSpecificV8Snapshot: false,
|
||||
enableEmbeddedAsarIntegrityValidation: true,
|
||||
},
|
||||
afterPack: async (context: AfterPackContext) => {
|
||||
await injectAsarIntegrity(context);
|
||||
},
|
||||
files: [
|
||||
"package.json",
|
||||
{
|
||||
@@ -154,6 +127,7 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
entitlements: "./build/entitlements.mac.plist",
|
||||
icon: "build/icons/icon.icns",
|
||||
mergeASARs: true,
|
||||
x64ArchFiles: "**/matrix-seshat/*.node", // hak already runs lipo
|
||||
},
|
||||
win: {
|
||||
target: ["squirrel", "msi"],
|
||||
|
||||
26
package.json
26
package.json
@@ -3,7 +3,7 @@
|
||||
"productName": "Element",
|
||||
"main": "lib/electron-main.js",
|
||||
"exports": "./lib/electron-main.js",
|
||||
"version": "1.11.95",
|
||||
"version": "1.11.101-rc.0",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": "Element",
|
||||
"homepage": "https://element.io",
|
||||
@@ -74,22 +74,22 @@
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@electron/asar": "3.3.1",
|
||||
"@playwright/test": "1.50.1",
|
||||
"@stylistic/eslint-plugin": "^3.0.0",
|
||||
"@electron/asar": "3.4.1",
|
||||
"@playwright/test": "1.52.0",
|
||||
"@stylistic/eslint-plugin": "^4.0.0",
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
"@types/counterpart": "^0.18.1",
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@types/node": "18.19.79",
|
||||
"@types/node": "18.19.87",
|
||||
"@types/pacote": "^11.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"app-builder-lib": "26.0.10",
|
||||
"app-builder-lib": "26.0.15",
|
||||
"chokidar": "^4.0.0",
|
||||
"detect-libc": "^2.0.0",
|
||||
"electron": "34.3.0",
|
||||
"electron-builder": "26.0.10",
|
||||
"electron-builder-squirrel-windows": "26.0.10",
|
||||
"electron": "36.0.0",
|
||||
"electron-builder": "26.0.15",
|
||||
"electron-builder-squirrel-windows": "26.0.15",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
@@ -105,18 +105,18 @@
|
||||
"matrix-web-i18n": "^3.2.1",
|
||||
"mkdirp": "^3.0.0",
|
||||
"pacote": "^21.0.0",
|
||||
"plist": "^3.1.0",
|
||||
"prettier": "^3.0.0",
|
||||
"rimraf": "^6.0.0",
|
||||
"tar": "^7.0.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "5.8.2"
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"hakDependencies": {
|
||||
"matrix-seshat": "^4.0.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "18.19.79",
|
||||
"config-file-ts": "0.2.8-rc1"
|
||||
"@types/node": "18.19.87",
|
||||
"config-file-ts": "0.2.8-rc1",
|
||||
"node-abi": "4.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,25 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { defineConfig } from "@playwright/test";
|
||||
|
||||
const projects = [
|
||||
"macos",
|
||||
"win-x64",
|
||||
"win-ia32",
|
||||
"win-arm64",
|
||||
"linux-amd64-sqlcipher-system",
|
||||
"linux-amd64-sqlcipher-static",
|
||||
"linux-arm64-sqlcipher-system",
|
||||
"linux-arm64-sqlcipher-static",
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
// Allows the GitHub action to specify a project name (OS + arch) for the combined report to make sense
|
||||
// workaround for https://github.com/microsoft/playwright/issues/33521
|
||||
projects: process.env.CI
|
||||
? projects.map((name) => ({
|
||||
name,
|
||||
}))
|
||||
: undefined,
|
||||
use: {
|
||||
viewport: { width: 1280, height: 720 },
|
||||
video: "retain-on-failure",
|
||||
@@ -18,7 +36,7 @@ export default defineConfig({
|
||||
outputDir: "playwright/test-results",
|
||||
workers: 1,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reporter: [["html", { outputFolder: "playwright/html-report" }]],
|
||||
reporter: process.env.CI ? [["blob"], ["github"]] : [["html", { outputFolder: "playwright/html-report" }]],
|
||||
snapshotDir: "playwright/snapshots",
|
||||
snapshotPathTemplate: "{snapshotDir}/{testFilePath}/{arg}-{platform}{ext}",
|
||||
timeout: 30 * 1000,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/playwright:v1.50.1-jammy
|
||||
FROM mcr.microsoft.com/playwright:v1.52.0-jammy
|
||||
|
||||
WORKDIR /work/element-desktop
|
||||
|
||||
|
||||
@@ -6,21 +6,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { platform } from "node:os";
|
||||
import keytar from "keytar-forked";
|
||||
|
||||
import { test, expect } from "../../element-desktop-test.js";
|
||||
|
||||
declare global {
|
||||
interface ElectronPlatform {
|
||||
getEventIndexingManager():
|
||||
| {
|
||||
supportsEventIndexing(): Promise<boolean>;
|
||||
}
|
||||
| undefined;
|
||||
getPickleKey(userId: string, deviceId: string): Promise<string | null>;
|
||||
createPickleKey(userId: string, deviceId: string): Promise<string | null>;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
mxPlatformPeg: {
|
||||
get(): {
|
||||
getEventIndexingManager():
|
||||
| {
|
||||
supportsEventIndexing(): Promise<boolean>;
|
||||
}
|
||||
| undefined;
|
||||
createPickleKey(userId: string, deviceId: string): Promise<string | null>;
|
||||
};
|
||||
get(): ElectronPlatform;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -46,13 +49,57 @@ test.describe("App launch", () => {
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
test("should launch and render the welcome view successfully and support keytar", async ({ page }) => {
|
||||
test.skip(platform() === "linux", "This test does not yet support Linux");
|
||||
test.describe("safeStorage", () => {
|
||||
const userId = "@user:server";
|
||||
const deviceId = "ABCDEF";
|
||||
|
||||
await expect(
|
||||
page.evaluate<string | null>(async () => {
|
||||
return await window.mxPlatformPeg.get().createPickleKey("@user:server", "ABCDEF");
|
||||
}),
|
||||
).resolves.not.toBeNull();
|
||||
test("should be supported", async ({ page }) => {
|
||||
await expect(
|
||||
page.evaluate(
|
||||
([userId, deviceId]) => window.mxPlatformPeg.get().createPickleKey(userId, deviceId),
|
||||
[userId, deviceId],
|
||||
),
|
||||
).resolves.not.toBeNull();
|
||||
});
|
||||
|
||||
test.describe("migrate from keytar", () => {
|
||||
test.skip(
|
||||
process.env.GITHUB_ACTIONS && ["linux", "darwin"].includes(process.platform),
|
||||
"GitHub Actions hosted runner are not a compatible environment for this test",
|
||||
);
|
||||
|
||||
const pickleKey = "DEADBEEF1234";
|
||||
const keytarService = "element.io";
|
||||
const keytarKey = `${userId}|${deviceId}`;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await keytar.setPassword(keytarService, keytarKey, pickleKey);
|
||||
await expect(keytar.getPassword(keytarService, keytarKey)).resolves.toBe(pickleKey);
|
||||
});
|
||||
test.afterAll(async () => {
|
||||
await keytar.deletePassword(keytarService, keytarKey);
|
||||
});
|
||||
|
||||
test("should migrate successfully", async ({ page }) => {
|
||||
await expect(
|
||||
page.evaluate(
|
||||
([userId, deviceId]) => window.mxPlatformPeg.get().getPickleKey(userId, deviceId),
|
||||
[userId, deviceId],
|
||||
),
|
||||
).resolves.toBe(pickleKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("--no-update", () => {
|
||||
test.use({
|
||||
extraArgs: ["--no-update"],
|
||||
});
|
||||
|
||||
// XXX: this test works fine locally but in CI the app start races with the test plumbing up the stdout/stderr pipes
|
||||
// which means the logs are missed, disabling for now.
|
||||
test.skip("should respect option", async ({ page, stdout }) => {
|
||||
expect(stdout.data.toString()).toContain("Auto update disabled via command line flag");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
36
playwright/e2e/launch/oidc.spec.ts
Normal file
36
playwright/e2e/launch/oidc.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { test, expect } from "../../element-desktop-test.js";
|
||||
|
||||
declare global {
|
||||
interface ElectronPlatform {
|
||||
getOidcCallbackUrl(): URL;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
mxPlatformPeg: {
|
||||
get(): ElectronPlatform;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
test.describe("OIDC Native", () => {
|
||||
test.slow();
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.locator(".mx_Welcome").waitFor();
|
||||
});
|
||||
|
||||
test("should use OIDC callback URL without authority component", async ({ page }) => {
|
||||
await expect(
|
||||
page.evaluate<string>(() => {
|
||||
return window.mxPlatformPeg.get().getOidcCallbackUrl().toString();
|
||||
}),
|
||||
).resolves.toBe("io.element.desktop:/vector/webapp/");
|
||||
});
|
||||
});
|
||||
@@ -11,12 +11,37 @@ import fs from "node:fs/promises";
|
||||
import path, { dirname } from "node:path";
|
||||
import os from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { PassThrough } from "node:stream";
|
||||
|
||||
/**
|
||||
* A PassThrough stream that captures all data written to it.
|
||||
*/
|
||||
class CapturedPassThrough extends PassThrough {
|
||||
private _chunks = [];
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
super.on("data", this.onData);
|
||||
}
|
||||
|
||||
private onData = (chunk): void => {
|
||||
this._chunks.push(chunk);
|
||||
};
|
||||
|
||||
public get data(): Buffer {
|
||||
return Buffer.concat(this._chunks);
|
||||
}
|
||||
}
|
||||
|
||||
interface Fixtures {
|
||||
app: ElectronApplication;
|
||||
tmpDir: string;
|
||||
extraEnv: Record<string, string>;
|
||||
extraArgs: string[];
|
||||
|
||||
// Utilities to capture stdout and stderr for tests to make assertions against
|
||||
stdout: CapturedPassThrough;
|
||||
stderr: CapturedPassThrough;
|
||||
}
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -24,15 +49,34 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
export const test = base.extend<Fixtures>({
|
||||
extraEnv: {},
|
||||
extraArgs: [],
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
stdout: async ({}, use) => {
|
||||
await use(new CapturedPassThrough());
|
||||
},
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
stderr: async ({}, use) => {
|
||||
await use(new CapturedPassThrough());
|
||||
},
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
tmpDir: async ({}, use) => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "element-desktop-tests-"));
|
||||
console.log("Using temp profile directory: ", tmpDir);
|
||||
await use(tmpDir);
|
||||
await fs.rm(tmpDir, { recursive: true });
|
||||
},
|
||||
app: async ({ tmpDir, extraEnv, extraArgs }, use) => {
|
||||
const args = ["--profile-dir", tmpDir];
|
||||
app: async ({ tmpDir, extraEnv, extraArgs, stdout, stderr }, use) => {
|
||||
const args = ["--profile-dir", tmpDir, ...extraArgs];
|
||||
|
||||
if (process.env.GITHUB_ACTIONS) {
|
||||
if (process.platform === "linux") {
|
||||
// GitHub Actions hosted runner lacks dbus and a compatible keyring, so we need to force plaintext storage
|
||||
args.push("--storage-mode", "force-plaintext");
|
||||
} else if (process.platform === "darwin") {
|
||||
// GitHub Actions hosted runner has no working default keychain, so allow plaintext storage
|
||||
args.push("--storage-mode", "allow-plaintext");
|
||||
}
|
||||
}
|
||||
|
||||
const executablePath = process.env["ELEMENT_DESKTOP_EXECUTABLE"];
|
||||
if (!executablePath) {
|
||||
@@ -40,17 +84,19 @@ export const test = base.extend<Fixtures>({
|
||||
args.unshift(path.join(__dirname, "..", "lib", "electron-main.js"));
|
||||
}
|
||||
|
||||
console.log(`Launching '${executablePath}' with args ${args.join(" ")}`);
|
||||
|
||||
const app = await electron.launch({
|
||||
env: {
|
||||
...process.env,
|
||||
...extraEnv,
|
||||
},
|
||||
executablePath,
|
||||
args: [...args, ...extraArgs],
|
||||
args,
|
||||
});
|
||||
|
||||
app.process().stdout.pipe(process.stdout);
|
||||
app.process().stderr.pipe(process.stderr);
|
||||
app.process().stdout.pipe(stdout).pipe(process.stdout);
|
||||
app.process().stderr.pipe(stderr).pipe(process.stderr);
|
||||
|
||||
await app.firstWindow();
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"module": "node16",
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"lib": ["es2020"]
|
||||
"lib": ["es2021"]
|
||||
},
|
||||
"include": ["../src/@types", "./**/*.ts"]
|
||||
}
|
||||
|
||||
9
src/@types/global.d.ts
vendored
9
src/@types/global.d.ts
vendored
@@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { type BrowserWindow } from "electron";
|
||||
|
||||
import type Store from "electron-store";
|
||||
import type AutoLaunch from "auto-launch";
|
||||
import { type AppLocalization } from "../language-helper.js";
|
||||
|
||||
@@ -24,13 +23,5 @@ declare global {
|
||||
icon_path: string;
|
||||
brand: string;
|
||||
};
|
||||
var store: Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration?: boolean;
|
||||
}>;
|
||||
}
|
||||
/* eslint-enable no-var */
|
||||
|
||||
@@ -16,7 +16,6 @@ import * as Sentry from "@sentry/electron/main";
|
||||
import AutoLaunch from "auto-launch";
|
||||
import path, { dirname } from "node:path";
|
||||
import windowStateKeeper from "electron-window-state";
|
||||
import Store from "electron-store";
|
||||
import fs, { promises as afs } from "node:fs";
|
||||
import { URL, fileURLToPath } from "node:url";
|
||||
import minimist from "minimist";
|
||||
@@ -25,6 +24,7 @@ import "./ipc.js";
|
||||
import "./seshat.js";
|
||||
import "./settings.js";
|
||||
import * as tray from "./tray.js";
|
||||
import Store from "./store.js";
|
||||
import { buildMenuTemplate } from "./vectormenu.js";
|
||||
import webContentsHandler from "./webcontents-handler.js";
|
||||
import * as updater from "./updater.js";
|
||||
@@ -32,7 +32,7 @@ import { getProfileFromDeeplink, protocolInit } from "./protocol.js";
|
||||
import { _t, AppLocalization } from "./language-helper.js";
|
||||
import { setDisplayMediaCallback } from "./displayMediaCallback.js";
|
||||
import { setupMacosTitleBar } from "./macos-titlebar.js";
|
||||
import { loadJsonFile } from "./utils.js";
|
||||
import { type Json, loadJsonFile } from "./utils.js";
|
||||
import { setupMediaAuth } from "./media-auth.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -44,7 +44,12 @@ const argv = minimist(process.argv, {
|
||||
if (argv["help"]) {
|
||||
console.log("Options:");
|
||||
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
||||
console.log(" --profile {name}: Name of alternate profile to use, allows for running multiple accounts.");
|
||||
console.log(
|
||||
` --profile {name}: Name of alternate profile to use, allows for running multiple accounts.\n` +
|
||||
` Ignored if --profile-dir is specified.\n` +
|
||||
` The ELEMENT_PROFILE_DIR environment variable may be used to change the default profile path.\n` +
|
||||
` It is overridden by --profile-dir, but can be combined with --profile.`,
|
||||
);
|
||||
console.log(" --devtools: Install and use react-devtools and react-perf.");
|
||||
console.log(
|
||||
` --config: Path to the config.json file. May also be specified via the ELEMENT_DESKTOP_CONFIG_JSON environment variable.\n` +
|
||||
@@ -58,6 +63,7 @@ if (argv["help"]) {
|
||||
}
|
||||
|
||||
const LocalConfigLocation = process.env.ELEMENT_DESKTOP_CONFIG_JSON ?? argv["config"];
|
||||
const LocalConfigFilename = "config.json";
|
||||
|
||||
// Electron creates the user data directory (with just an empty 'Dictionaries' directory...)
|
||||
// as soon as the app path is set, so pick a random path in it that must exist if it's a
|
||||
@@ -75,7 +81,7 @@ if (userDataPathInProtocol) {
|
||||
} else if (argv["profile-dir"]) {
|
||||
userDataPath = argv["profile-dir"];
|
||||
} else {
|
||||
let newUserDataPath = app.getPath("userData");
|
||||
let newUserDataPath = process.env.ELEMENT_PROFILE_DIR ?? app.getPath("userData");
|
||||
if (argv["profile"]) {
|
||||
newUserDataPath += "-" + argv["profile"];
|
||||
}
|
||||
@@ -136,6 +142,17 @@ function getAsarPath(): Promise<string> {
|
||||
return asarPathPromise;
|
||||
}
|
||||
|
||||
function loadLocalConfigFile(): Json {
|
||||
if (LocalConfigLocation) {
|
||||
console.log("Loading local config: " + LocalConfigLocation);
|
||||
return loadJsonFile(LocalConfigLocation);
|
||||
} else {
|
||||
const configDir = app.getPath("userData");
|
||||
console.log(`Loading local config: ${path.join(configDir, LocalConfigFilename)}`);
|
||||
return loadJsonFile(configDir, LocalConfigFilename);
|
||||
}
|
||||
}
|
||||
|
||||
// Loads the config from asar, and applies a config.json from userData atop if one exists
|
||||
// Writes config to `global.vectorConfig`. Does nothing if `global.vectorConfig` is already set.
|
||||
async function loadConfig(): Promise<void> {
|
||||
@@ -144,7 +161,8 @@ async function loadConfig(): Promise<void> {
|
||||
const asarPath = await getAsarPath();
|
||||
|
||||
try {
|
||||
global.vectorConfig = loadJsonFile(asarPath, "config.json");
|
||||
console.log(`Loading app config: ${path.join(asarPath, LocalConfigFilename)}`);
|
||||
global.vectorConfig = loadJsonFile(asarPath, LocalConfigFilename);
|
||||
} catch {
|
||||
// it would be nice to check the error code here and bail if the config
|
||||
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||
@@ -155,9 +173,7 @@ async function loadConfig(): Promise<void> {
|
||||
|
||||
try {
|
||||
// Load local config and use it to override values from the one baked with the build
|
||||
const localConfig = LocalConfigLocation
|
||||
? loadJsonFile(LocalConfigLocation)
|
||||
: loadJsonFile(app.getPath("userData"), "config.json");
|
||||
const localConfig = loadLocalConfigFile();
|
||||
|
||||
// If the local config has a homeserver defined, don't use the homeserver from the build
|
||||
// config. This is to avoid a problem where Riot thinks there are multiple homeservers
|
||||
@@ -257,8 +273,6 @@ async function moveAutoLauncher(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
global.store = new Store({ name: "electron-config" });
|
||||
|
||||
global.appQuitting = false;
|
||||
|
||||
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
@@ -268,32 +282,6 @@ const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
platform === "darwin" && input.meta && !input.control && input.key.toUpperCase() === "Q",
|
||||
];
|
||||
|
||||
const warnBeforeExit = (event: Event, input: Input): void => {
|
||||
const shouldWarnBeforeExit = global.store.get("warnBeforeExit", true);
|
||||
const exitShortcutPressed =
|
||||
input.type === "keyDown" && exitShortcuts.some((shortcutFn) => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed && global.mainWindow) {
|
||||
const shouldCancelCloseRequest =
|
||||
dialog.showMessageBoxSync(global.mainWindow, {
|
||||
type: "question",
|
||||
buttons: [
|
||||
_t("action|cancel"),
|
||||
_t("action|close_brand", {
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
}),
|
||||
],
|
||||
message: _t("confirm_quit"),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
}) === 0;
|
||||
|
||||
if (shouldCancelCloseRequest) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void configureSentry();
|
||||
|
||||
// handle uncaught errors otherwise it displays
|
||||
@@ -310,6 +298,11 @@ app.commandLine.appendSwitch("--enable-usermedia-screen-capturing");
|
||||
if (!app.commandLine.hasSwitch("enable-features")) {
|
||||
app.commandLine.appendSwitch("enable-features", "WebRTCPipeWireCapturer");
|
||||
}
|
||||
// Workaround bug in electron 36:https://github.com/electron/electron/issues/46538
|
||||
// Hopefully this will no longer be needed soon and can be removed
|
||||
if (process.platform === "linux") {
|
||||
app.commandLine.appendSwitch("gtk-version", "3");
|
||||
}
|
||||
|
||||
const gotLock = app.requestSingleInstanceLock();
|
||||
if (!gotLock) {
|
||||
@@ -350,13 +343,17 @@ app.enableSandbox();
|
||||
// We disable media controls here. We do this because calls use audio and video elements and they sometimes capture the media keys. See https://github.com/vector-im/element-web/issues/15704
|
||||
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling,MediaSessionService");
|
||||
|
||||
const store = Store.initialize(argv["storage-mode"]); // must be called before any async actions
|
||||
|
||||
// Disable hardware acceleration if the setting has been set.
|
||||
if (global.store.get("disableHardwareAcceleration", false) === true) {
|
||||
if (store.get("disableHardwareAcceleration")) {
|
||||
console.log("Disabling hardware acceleration.");
|
||||
app.disableHardwareAcceleration();
|
||||
}
|
||||
|
||||
app.on("ready", async () => {
|
||||
console.debug("Reached Electron ready state");
|
||||
|
||||
let asarPath: string;
|
||||
|
||||
try {
|
||||
@@ -436,8 +433,9 @@ app.on("ready", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (argv["no-update"]) {
|
||||
console.log('Auto update disabled via command line flag "--no-update"');
|
||||
// Minimist parses `--no-`-prefixed arguments as booleans with value `false` rather than verbatim.
|
||||
if (argv["update"] === false) {
|
||||
console.log("Auto update disabled via command line flag");
|
||||
} else if (global.vectorConfig["update_base_url"]) {
|
||||
console.log(`Starting auto update with base URL: ${global.vectorConfig["update_base_url"]}`);
|
||||
void updater.start(global.vectorConfig["update_base_url"]);
|
||||
@@ -445,12 +443,27 @@ app.on("ready", async () => {
|
||||
console.log("No update_base_url is defined: auto update is disabled");
|
||||
}
|
||||
|
||||
// Set up i18n before loading storage as we need translations for dialogs
|
||||
global.appLocalization = new AppLocalization({
|
||||
components: [(): void => tray.initApplicationMenu(), (): void => Menu.setApplicationMenu(buildMenuTemplate())],
|
||||
store,
|
||||
});
|
||||
|
||||
try {
|
||||
console.debug("Ensuring storage is ready");
|
||||
await store.safeStorageReady();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
app.exit(1);
|
||||
}
|
||||
|
||||
// Load the previous window state with fallback to defaults
|
||||
const mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 1024,
|
||||
defaultHeight: 768,
|
||||
});
|
||||
|
||||
console.debug("Opening main window");
|
||||
const preloadScript = path.normalize(`${__dirname}/preload.cjs`);
|
||||
global.mainWindow = new BrowserWindow({
|
||||
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
@@ -461,7 +474,7 @@ app.on("ready", async () => {
|
||||
|
||||
icon: global.trayConfig.icon_path,
|
||||
show: false,
|
||||
autoHideMenuBar: global.store.get("autoHideMenuBar", true),
|
||||
autoHideMenuBar: store.get("autoHideMenuBar"),
|
||||
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
@@ -483,10 +496,10 @@ app.on("ready", async () => {
|
||||
|
||||
// Handle spellchecker
|
||||
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true));
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(store.get("spellCheckerEnabled", true));
|
||||
|
||||
// Create trayIcon icon
|
||||
if (global.store.get("minimizeToTray", true)) tray.create(global.trayConfig);
|
||||
if (store.get("minimizeToTray")) tray.create(global.trayConfig);
|
||||
|
||||
global.mainWindow.once("ready-to-show", () => {
|
||||
if (!global.mainWindow) return;
|
||||
@@ -500,7 +513,31 @@ app.on("ready", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
global.mainWindow.webContents.on("before-input-event", warnBeforeExit);
|
||||
global.mainWindow.webContents.on("before-input-event", (event: Event, input: Input): void => {
|
||||
const shouldWarnBeforeExit = store.get("warnBeforeExit", true);
|
||||
const exitShortcutPressed =
|
||||
input.type === "keyDown" && exitShortcuts.some((shortcutFn) => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed && global.mainWindow) {
|
||||
const shouldCancelCloseRequest =
|
||||
dialog.showMessageBoxSync(global.mainWindow, {
|
||||
type: "question",
|
||||
buttons: [
|
||||
_t("action|cancel"),
|
||||
_t("action|close_brand", {
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
}),
|
||||
],
|
||||
message: _t("confirm_quit"),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
}) === 0;
|
||||
|
||||
if (shouldCancelCloseRequest) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
global.mainWindow.on("closed", () => {
|
||||
global.mainWindow = null;
|
||||
@@ -538,11 +575,6 @@ app.on("ready", async () => {
|
||||
|
||||
webContentsHandler(global.mainWindow.webContents);
|
||||
|
||||
global.appLocalization = new AppLocalization({
|
||||
store: global.store,
|
||||
components: [(): void => tray.initApplicationMenu(), (): void => Menu.setApplicationMenu(buildMenuTemplate())],
|
||||
});
|
||||
|
||||
session.defaultSession.setDisplayMediaRequestHandler((_, callback) => {
|
||||
global.mainWindow?.webContents.send("openDesktopCapturerSourcePicker");
|
||||
setDisplayMediaCallback(callback);
|
||||
@@ -579,8 +611,9 @@ app.on("second-instance", (ev, commandLine, workingDirectory) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Set the App User Model ID to match what the squirrel
|
||||
// installer uses for the shortcut icon.
|
||||
// This makes notifications work on windows 8.1 (and is
|
||||
// a noop on other platforms).
|
||||
app.setAppUserModelId("com.squirrel.element-desktop.Element");
|
||||
// This is required to make notification handlers work
|
||||
// on Windows 8.1/10/11 (and is a noop on other platforms);
|
||||
// It must also match the ID found in 'electron-builder'
|
||||
// in order to get the title and icon to show up correctly.
|
||||
// Ref: https://stackoverflow.com/a/77314604/3525780
|
||||
app.setAppUserModelId("im.riot.app");
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "O",
|
||||
"brand_help": "%(brand)s nápověda",
|
||||
"help": "Nápověda",
|
||||
"preferences": "Předvolby"
|
||||
"no": "Ne",
|
||||
"preferences": "Předvolby",
|
||||
"yes": "Ano"
|
||||
},
|
||||
"confirm_quit": "Opravdu chcete ukončit aplikaci?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "Obrázek se nepodařilo uložit",
|
||||
"save_image_as_error_title": "Chyba při ukládání obrázku"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Vymazat data a znovu načíst?",
|
||||
"backend_changed_detail": "Nelze získat přístup k tajnému klíči ze systémové klíčenky, zdá se, že se změnil.",
|
||||
"backend_changed_title": "Nepodařilo se načíst databázi",
|
||||
"unknown_backend_override": "Váš systém má nepodporovanou klíčenku, což znamená, že databázi nelze otevřít.",
|
||||
"unknown_backend_override_details": "Další podrobnosti naleznete v protokolech.",
|
||||
"unknown_backend_override_title": "Nepodařilo se načíst databázi",
|
||||
"unsupported_keyring": "Váš systém má nepodporovanou klíčenku, což znamená, že databázi nelze otevřít.",
|
||||
"unsupported_keyring_cta": "Používat slabší šifrování",
|
||||
"unsupported_keyring_detail": "Detekce klíčenky Electronu nenalezla podporovaný backend. Můžete se pokusit ručně nakonfigurovat backend spuštěním %(brand)s s argumentem příkazového řádku, jednorázovou operací. Viz %(link)s.",
|
||||
"unsupported_keyring_title": "Systém není podporován"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Aktuální velikost",
|
||||
"toggle_developer_tools": "Přepnout zobrazení nástrojů pro vývojáře",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"edit": "Golygu",
|
||||
"minimise": "Lleihau",
|
||||
"paste": "Gludo",
|
||||
"paste_match_style": "Gludo a Chyfateb Arddull",
|
||||
"paste_match_style": "Arddull Gludo a Chyfateb",
|
||||
"quit": "Gadael",
|
||||
"redo": "Ail-wneud",
|
||||
"select_all": "Dewis y Cyfan",
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"common": {
|
||||
"about": "Ynghylch",
|
||||
"brand_help": "%(brand)s Cymorth",
|
||||
"brand_help": "Cymorth %(brand)s",
|
||||
"help": "Cymorth",
|
||||
"preferences": "Dewisiadau"
|
||||
},
|
||||
@@ -40,18 +40,18 @@
|
||||
"unhide": "Datguddio"
|
||||
},
|
||||
"right_click_menu": {
|
||||
"add_to_dictionary": "Ychwanegu i'r Geiriadur",
|
||||
"add_to_dictionary": "Ychwanegu at y geiriadur",
|
||||
"copy_email": "Copïo cyfeiriad e-bost",
|
||||
"copy_image": "Copïo delwedd",
|
||||
"copy_image_url": "Copïo cyfeiriad delwedd",
|
||||
"copy_link_url": "Copïo cyfeiriad y ddolen",
|
||||
"save_image_as": "Cadw delwedd fel...",
|
||||
"save_image_as_error_description": "Methodd y ddelwedd â chadw",
|
||||
"save_image_as_error_title": "Wedi methu cadw'r ddelwedd"
|
||||
"save_image_as_error_description": "Methodd cadw'r ddelwedd",
|
||||
"save_image_as_error_title": "Methodd cadw'r ddelwedd"
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Maint Gwirioneddol",
|
||||
"toggle_developer_tools": "Toggle Developer Tools",
|
||||
"toggle_developer_tools": "Toglo Offer Datblygwyr",
|
||||
"toggle_full_screen": "Toglo Sgrin Lawn",
|
||||
"view": "Golwg"
|
||||
},
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Über",
|
||||
"brand_help": "%(brand)s Hilfe",
|
||||
"help": "Hilfe",
|
||||
"preferences": "Präferenzen"
|
||||
"no": "Nein",
|
||||
"preferences": "Präferenzen",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"confirm_quit": "Wirklich beenden?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "Das Bild konnte nicht gespeichert werden",
|
||||
"save_image_as_error_title": "Bild kann nicht gespeichert werden"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Daten löschen und neu laden?",
|
||||
"backend_changed_detail": "Zugriff auf Schlüssel im Systemschlüsselbund nicht möglich, er scheint sich geändert zu haben.",
|
||||
"backend_changed_title": "Datenbank konnte nicht geladen werden",
|
||||
"unknown_backend_override": "Der Schlüsselbund ihres Systems wird nicht unterstützt, wodurch die Datenbank nicht geöffnet werden kann.",
|
||||
"unknown_backend_override_details": "Weitere Informationen finden Sie in den Protokollen.",
|
||||
"unknown_backend_override_title": "Die Datenbank konnte nicht geladen werden",
|
||||
"unsupported_keyring": "Der Schlüsselbund ihres Systems wird nicht unterstützt, wodurch die Datenbank nicht geöffnet werden kann.",
|
||||
"unsupported_keyring_cta": "Schwächere Verschlüsselung verwenden",
|
||||
"unsupported_keyring_detail": "Die Schlüsselbunderkennung von Electron hat kein unterstütztes Backend gefunden. Möglicherweise können sie dennoch den ihres Systemes verwenden. Infos unter %(link)s.",
|
||||
"unsupported_keyring_title": "System nicht unterstützt"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Tatsächliche Größe",
|
||||
"toggle_developer_tools": "Developer-Tools an/aus",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "About",
|
||||
"brand_help": "%(brand)s Help",
|
||||
"help": "Help",
|
||||
"preferences": "Preferences"
|
||||
"no": "No",
|
||||
"preferences": "Preferences",
|
||||
"yes": "Yes"
|
||||
},
|
||||
"confirm_quit": "Are you sure you want to quit?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "The image failed to save",
|
||||
"save_image_as_error_title": "Failed to save image"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Clear data and reload?",
|
||||
"backend_changed_detail": "Unable to access secret from system keyring, it appears to have changed.",
|
||||
"backend_changed_title": "Failed to load database",
|
||||
"unknown_backend_override": "Your system has an unsupported keyring meaning the database cannot be opened.",
|
||||
"unknown_backend_override_details": "Please check the logs for more details.",
|
||||
"unknown_backend_override_title": "Failed to load database",
|
||||
"unsupported_keyring": "Your system has an unsupported keyring meaning the database cannot be opened.",
|
||||
"unsupported_keyring_cta": "Use weaker encryption",
|
||||
"unsupported_keyring_detail": "Electron's keyring detection did not find a supported backend. You can attempt to manually configure the backend by starting %(brand)s with a command-line argument, a one-time operation. See %(link)s.",
|
||||
"unsupported_keyring_title": "System unsupported"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Actual Size",
|
||||
"toggle_developer_tools": "Toggle Developer Tools",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Rakenduse teave",
|
||||
"brand_help": "%(brand)s abiteave",
|
||||
"help": "Abiteave",
|
||||
"preferences": "Eelistused"
|
||||
"no": "Ei",
|
||||
"preferences": "Eelistused",
|
||||
"yes": "Jah"
|
||||
},
|
||||
"confirm_quit": "Kas sa kindlasti soovid rakendusest väljuda?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "Seda pilti ei õnnestunud salvestada",
|
||||
"save_image_as_error_title": "Pildi salvestamine ei õnnestunud"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Kas kustutame andmed ja laadime uuesti?",
|
||||
"backend_changed_detail": "Süsteemsest võtmerõngast ei õnnestu laadida vajalikku saladust, tundub et ta on muutunud.",
|
||||
"backend_changed_title": "Andmebaasi ei õnnestunud laadida",
|
||||
"unknown_backend_override": "Sinu süsteemis on kasutusel mittetoetatud võtmerõnga versioon ning see tähendab, et andmebaasi ei saa avada.",
|
||||
"unknown_backend_override_details": "Lisateavet leiad logidest.",
|
||||
"unknown_backend_override_title": "Andmebaasi ei õnnestunud laadida",
|
||||
"unsupported_keyring": "Sinu süsteemis on kasutusel mittetoetatud võtmerõnga versioon ning see tähendab, et andmebaasi ei saa avada.",
|
||||
"unsupported_keyring_cta": "Kasuta nõrgemat krüptimist",
|
||||
"unsupported_keyring_detail": "Electroni võtmerõnga tuvastamine ei leidnud toetatud taustateenust. Kui käivitad rakenduse %(brand)s käsurealt õigete argumentidega, siis võib taustateenuse käsitsi seadistamine õnnestuda ning seda tegevust peaksid vaid üks kord tegema. Lisateave: %(link)s.",
|
||||
"unsupported_keyring_title": "Süsteem pole toetatud"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Näita tavasuuruses",
|
||||
"toggle_developer_tools": "Arendaja töövahendid sisse/välja",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "À propos",
|
||||
"brand_help": "Aide de %(brand)s",
|
||||
"help": "Aide",
|
||||
"preferences": "Préférences"
|
||||
"no": "Non",
|
||||
"preferences": "Préférences",
|
||||
"yes": "Oui"
|
||||
},
|
||||
"confirm_quit": "Êtes-vous sûr de vouloir quitter ?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "L’image n’a pas pu être sauvegardée",
|
||||
"save_image_as_error_title": "Échec de la sauvegarde de l’image"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Effacer les données et recharger ?",
|
||||
"backend_changed_detail": "Impossible d'accéder aux secrets depuis le trousseau de clés du système, il semble avoir changé.",
|
||||
"backend_changed_title": "Impossible de charger la base de données",
|
||||
"unknown_backend_override": "Votre système possède un trousseau de clés non pris en charge, ce qui signifie que la base de données ne peut pas être ouverte.",
|
||||
"unknown_backend_override_details": "Veuillez consulter les journaux pour plus de détails.",
|
||||
"unknown_backend_override_title": "Impossible de charger la base de données",
|
||||
"unsupported_keyring": "Votre système possède un trousseau de clés non pris en charge, la base de données ne peut pas être ouverte.",
|
||||
"unsupported_keyring_cta": "Utiliser un chiffrement plus faible",
|
||||
"unsupported_keyring_detail": "La détection du porte-clés par Electron n'a pas permis de trouver de backend compatible. Vous pouvez essayer de configurer manuellement le backend en utilisant %(brand)s avec un argument de ligne de commande. Cette opération doit être effectuer une seule fois. Voir%(link)s.",
|
||||
"unsupported_keyring_title": "Système non pris en charge"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Taille réelle",
|
||||
"toggle_developer_tools": "Basculer les outils de développement",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Névjegy",
|
||||
"brand_help": "%(brand)s Súgó",
|
||||
"help": "Súgó",
|
||||
"preferences": "Beállítások"
|
||||
"no": "Nem",
|
||||
"preferences": "Beállítások",
|
||||
"yes": "Igen"
|
||||
},
|
||||
"confirm_quit": "Biztos, hogy kilép?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "A kép mentése sikertelen",
|
||||
"save_image_as_error_title": "Kép mentése sikertelen"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Adatok törlése és újratöltés?",
|
||||
"backend_changed_detail": "Nem sikerült hozzáférni a rendszerkulcstartó titkos kódjához, úgy tűnik, megváltozott.",
|
||||
"backend_changed_title": "Nem sikerült betölteni az adatbázist",
|
||||
"unknown_backend_override": "A rendszer nem támogatott kulcstartóval rendelkezik, ami azt jelenti, hogy az adatbázis nem nyitható meg.",
|
||||
"unknown_backend_override_details": "További részletekért ellenőrizze a naplókat.",
|
||||
"unknown_backend_override_title": "Nem sikerült betölteni az adatbázist",
|
||||
"unsupported_keyring": "A rendszer nem támogatott kulcstartóval rendelkezik, ami azt jelenti, hogy az adatbázis nem nyitható meg.",
|
||||
"unsupported_keyring_cta": "Gyengébb titkosítás használata",
|
||||
"unsupported_keyring_detail": "Az Electron kulcstartóészlelése nem talált támogatott háttérrendszert. Megpróbálhatja kézileg beállítani a háttérrendszert az %(brand)s egyszeri, parancssori argumentummal való indításával. Lásd: %(link)s.",
|
||||
"unsupported_keyring_title": "A rendszer nem támogatott"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Jelenlegi méret",
|
||||
"toggle_developer_tools": "Fejlesztői eszközök",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Tentang",
|
||||
"brand_help": "Bantuan %(brand)s",
|
||||
"help": "Bantuan",
|
||||
"preferences": "Preferensi"
|
||||
"no": "Tidak",
|
||||
"preferences": "Preferensi",
|
||||
"yes": "Ya"
|
||||
},
|
||||
"confirm_quit": "Apakah Anda yakin ingin keluar?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "Gambar gagal disimpan",
|
||||
"save_image_as_error_title": "Gagal menyimpan gambar"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Hapus data dan muat ulang?",
|
||||
"backend_changed_detail": "Tidak dapat mengakses rahasia dari keyring sistem, tampaknya telah berubah.",
|
||||
"backend_changed_title": "Gagal memuat basis data",
|
||||
"unknown_backend_override": "Sistem Anda memiliki keyring yang tidak didukung yang berarti basis data tidak dapat dibuka.",
|
||||
"unknown_backend_override_details": "Silakan periksa log untuk detail lebih lanjut.",
|
||||
"unknown_backend_override_title": "Gagal memuat basis data",
|
||||
"unsupported_keyring": "Sistem Anda memiliki keyring yang tidak didukung yang berarti basis data tidak dapat dibuka.",
|
||||
"unsupported_keyring_cta": "Gunakan enkripsi yang lebih lemah",
|
||||
"unsupported_keyring_detail": "Deteksi keyring Electron tidak menemukan backend yang didukung. Anda dapat mencoba mengonfigurasi backend secara manual dengan memulai %(brand)s dengan argumen baris perintah, operasi satu kali. Lihat %(link)s.",
|
||||
"unsupported_keyring_title": "Sistem tidak didukung"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Ukuran Sebenarnya",
|
||||
"toggle_developer_tools": "Beralih Alat Pengembang",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Om",
|
||||
"brand_help": "%(brand)s Hjelp",
|
||||
"help": "Hjelp",
|
||||
"preferences": "Brukervalg"
|
||||
"no": "Nei",
|
||||
"preferences": "Brukervalg",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"confirm_quit": "Er du sikker på at du vil slutte?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "Bildet kunne ikke lagres",
|
||||
"save_image_as_error_title": "Kunne ikke lagre bildet"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Tøm data og last inn på nytt?",
|
||||
"backend_changed_detail": "Kan ikke få tilgang til hemmeligheten fra systemnøkkelringen, den ser ut til å ha blitt endret.",
|
||||
"backend_changed_title": "Kunne ikke laste inn databasen",
|
||||
"unknown_backend_override": "Systemet ditt har en nøkkelring som ikke støttes, noe som betyr at databasen ikke kan åpnes.",
|
||||
"unknown_backend_override_details": "Vennligst sjekk loggene for mer informasjon.",
|
||||
"unknown_backend_override_title": "Kunne ikke laste inn databasen",
|
||||
"unsupported_keyring": "Systemet ditt har en nøkkelring som ikke støttes, noe som betyr at databasen ikke kan åpnes.",
|
||||
"unsupported_keyring_cta": "Bruk svakere kryptering",
|
||||
"unsupported_keyring_detail": "Electrons nøkkelringdeteksjon fant ikke en støttet backend. Du kan prøve å konfigurere backend manuelt ved å starte %(brand)s med et kommandolinjeargument, en engangsoperasjon. Se%(link)s.",
|
||||
"unsupported_keyring_title": "Systemet støttes ikke"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Faktisk størrelse",
|
||||
"toggle_developer_tools": "Veksle Utvikleralternativer",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Informacje",
|
||||
"brand_help": "Pomoc %(brand)s",
|
||||
"help": "Pomoc",
|
||||
"preferences": "Preferencje"
|
||||
"no": "Nie",
|
||||
"preferences": "Preferencje",
|
||||
"yes": "Tak"
|
||||
},
|
||||
"confirm_quit": "Czy na pewno chcesz zamknąć?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "Obraz nie został zapisany",
|
||||
"save_image_as_error_title": "Nie udało się zapisać obrazu"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Wyczyścić dane i przeładować?",
|
||||
"backend_changed_detail": "Nie można uzyskać dostępu do sekretnego magazynu, wygląda na to, że uległ zmianie.",
|
||||
"backend_changed_title": "Nie udało się załadować bazy danych",
|
||||
"unknown_backend_override": "System zawiera niewspierany keyring, nie można otworzyć bazy danych.",
|
||||
"unknown_backend_override_details": "Sprawdź dziennik, aby uzyskać więcej informacji.",
|
||||
"unknown_backend_override_title": "Nie udało się załadować bazy danych",
|
||||
"unsupported_keyring": "System zawiera niewspierany keyring, nie można otworzyć bazy danych.",
|
||||
"unsupported_keyring_cta": "Użyj słabszego szyfrowania",
|
||||
"unsupported_keyring_detail": "Wykrywanie keyringu Electron nie znalazł wspieranego backendu. Możesz spróbować ręcznie ustawić backed, uruchamiając %(brand)s za pomocą wiesza poleceń. Zobacz %(link)s.",
|
||||
"unsupported_keyring_title": "System niewspierany"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Rozmiar rzeczywisty",
|
||||
"toggle_developer_tools": "Przełącz narzędzia deweloperskie",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Sobre",
|
||||
"brand_help": "%(brand)s Ajuda",
|
||||
"help": "Ajuda",
|
||||
"preferences": "Preferências"
|
||||
"no": "Não",
|
||||
"preferences": "Preferências",
|
||||
"yes": "Sim"
|
||||
},
|
||||
"confirm_quit": "Você tem certeza que você quer sair?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "A imagem falhou para salvar",
|
||||
"save_image_as_error_title": "Falha para salvar imagem"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Limpar dados e recarregar?",
|
||||
"backend_changed_detail": "Não foi possível acessar o segredo no cofre do sistema, parece que ele foi alterado.",
|
||||
"backend_changed_title": "Falha ao carregar o banco de dados",
|
||||
"unknown_backend_override": "Seu sistema possui um cofre não compatível, o que impede a abertura do banco de dados.",
|
||||
"unknown_backend_override_details": "Verifique os logs para obter mais detalhes.",
|
||||
"unknown_backend_override_title": "Falha ao carregar o banco de dados",
|
||||
"unsupported_keyring": "Seu sistema possui um cofre não compatível, o que impede a abertura do banco de dados.",
|
||||
"unsupported_keyring_cta": "Use criptografia mais fraca",
|
||||
"unsupported_keyring_detail": "A detecção de chaveiro do Electron não encontrou um backend compatível. Você pode tentar configurar manualmente o backend iniciando %(brand)s com um argumento de linha de comando, uma operação única. Consulte %(link)s.",
|
||||
"unsupported_keyring_title": "Sistema não suportado"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Tamanho de Verdade",
|
||||
"toggle_developer_tools": "Ativar/Desativar Ferramentas de Desenvolvimento",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Informácie",
|
||||
"brand_help": "%(brand)s Pomoc",
|
||||
"help": "Pomocník",
|
||||
"preferences": "Predvoľby"
|
||||
"no": "Nie",
|
||||
"preferences": "Predvoľby",
|
||||
"yes": "Áno"
|
||||
},
|
||||
"confirm_quit": "Naozaj chcete zavrieť aplikáciu?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "Obrázok sa nepodarilo uložiť",
|
||||
"save_image_as_error_title": "Chyba pri ukladaní obrázka"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Vymazať údaje a znova načítať?",
|
||||
"backend_changed_detail": "Nepodarilo sa získať prístup k tajnému kľúču zo systémového zväzku kľúčov, zdá sa, že sa zmenil.",
|
||||
"backend_changed_title": "Nepodarilo sa načítať databázu",
|
||||
"unknown_backend_override": "Váš systém má nepodporovaný zväzok kľúčov, čo znamená, že databázu nemožno otvoriť.",
|
||||
"unknown_backend_override_details": "Pre viac informácií si pozrite protokoly.",
|
||||
"unknown_backend_override_title": "Nepodarilo sa načítať databázu",
|
||||
"unsupported_keyring": "Váš systém má nepodporovaný zväzok kľúčov, čo znamená, že databázu nemožno otvoriť.",
|
||||
"unsupported_keyring_cta": "Použiť slabšie šifrovanie",
|
||||
"unsupported_keyring_detail": "Detekcia zväzku kľúčov aplikácie Electron nenašla podporovaný backend. Môžete sa pokúsiť manuálne nastaviť backend spustením aplikácie %(brand)s s argumentom príkazového riadka, je to jednorazová operácia. Pozrite si %(link)s .",
|
||||
"unsupported_keyring_title": "Systém nie je podporovaný"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Aktuálna veľkosť",
|
||||
"toggle_developer_tools": "Nástroje pre vývojárov",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Про застосунок",
|
||||
"brand_help": "Довідка %(brand)s",
|
||||
"help": "Довідка",
|
||||
"preferences": "Параметри"
|
||||
"no": "Ні",
|
||||
"preferences": "Параметри",
|
||||
"yes": "Так"
|
||||
},
|
||||
"confirm_quit": "Ви впевнені, що хочете вийти?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,20 @@
|
||||
"save_image_as_error_description": "Не вдалося зберегти зображення",
|
||||
"save_image_as_error_title": "Не вдалося зберегти зображення"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Очистити дані та перезавантажити?",
|
||||
"backend_changed_detail": "Не вдається отримати доступ до таємного ключа з системного набору ключів, видається, він змінився.",
|
||||
"backend_changed_title": "Не вдалося завантажити базу даних",
|
||||
"unknown_backend_override": "Ваша система має непідтримуваний ключ, що означає, що базу даних неможливо відкрити.",
|
||||
"unknown_backend_override_details": "Перегляньте журнал, щоб дізнатися подробиці.",
|
||||
"unknown_backend_override_title": "Не вдалося завантажити базу даних",
|
||||
"unsupported_keyring": "Ваша система має непідтримуваний набір ключів. Це означає, що базу даних неможливо відкрити.",
|
||||
"unsupported_keyring_cta": "Використовувати слабше шифрування",
|
||||
"unsupported_keyring_detail": "Electron не виявив підтримуваного бекенда для роботи зі сховищем паролів. Ви можете вручну налаштувати його, запустивши %(brand)s з відповідним аргументом у командному рядку. Цю дію потрібно виконати лише один раз. Докладніше – %(link)s.",
|
||||
"unsupported_keyring_title": "Система не підтримується"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Фактичний розмір",
|
||||
"toggle_developer_tools": "Перемкнути інструменти розробника",
|
||||
|
||||
35
src/ipc.ts
35
src/ipc.ts
@@ -6,14 +6,13 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { app, autoUpdater, desktopCapturer, ipcMain, powerSaveBlocker, TouchBar, nativeImage } from "electron";
|
||||
import { relaunchApp } from "@standardnotes/electron-clear-data";
|
||||
import keytar from "keytar-forked";
|
||||
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
import { recordSSOSession } from "./protocol.js";
|
||||
import { randomArray } from "./utils.js";
|
||||
import { Settings } from "./settings.js";
|
||||
import { getDisplayMediaCallback, setDisplayMediaCallback } from "./displayMediaCallback.js";
|
||||
import Store, { clearDataAndRelaunch } from "./store.js";
|
||||
|
||||
ipcMain.on("setBadgeCount", function (_ev: IpcMainEvent, count: number): void {
|
||||
if (process.platform !== "win32") {
|
||||
@@ -61,7 +60,8 @@ ipcMain.on("app_onAction", function (_ev: IpcMainEvent, payload) {
|
||||
});
|
||||
|
||||
ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
if (!global.mainWindow) return;
|
||||
const store = Store.instance;
|
||||
if (!global.mainWindow || !store) return;
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
@@ -113,11 +113,11 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
if (typeof args[0] !== "boolean") return;
|
||||
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(args[0]);
|
||||
global.store.set("spellCheckerEnabled", args[0]);
|
||||
store.set("spellCheckerEnabled", args[0]);
|
||||
break;
|
||||
|
||||
case "getSpellCheckEnabled":
|
||||
ret = global.store.get("spellCheckerEnabled", true);
|
||||
ret = store.get("spellCheckerEnabled");
|
||||
break;
|
||||
|
||||
case "setSpellCheckLanguages":
|
||||
@@ -141,12 +141,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
|
||||
case "getPickleKey":
|
||||
try {
|
||||
ret = await keytar.getPassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
if (ret === null) {
|
||||
ret = await keytar.getPassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
}
|
||||
ret = await store.getSecret(`${args[0]}|${args[1]}`);
|
||||
} catch {
|
||||
// if an error is thrown (e.g. keytar can't connect to the keychain),
|
||||
// then return null, which means the default pickle key will be used
|
||||
@@ -157,9 +152,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
case "createPickleKey":
|
||||
try {
|
||||
const pickleKey = await randomArray(32);
|
||||
// We purposefully throw if keytar is not available so the caller can handle it
|
||||
// rather than sending them a pickle key we did not store on their behalf.
|
||||
await keytar!.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
await store.setSecret(`${args[0]}|${args[1]}`, pickleKey);
|
||||
ret = pickleKey;
|
||||
} catch (e) {
|
||||
console.error("Failed to create pickle key", e);
|
||||
@@ -169,11 +162,10 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
|
||||
case "destroyPickleKey":
|
||||
try {
|
||||
await keytar.deletePassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
await keytar.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
} catch {}
|
||||
await store.deleteSecret(`${args[0]}|${args[1]}`);
|
||||
} catch (e) {
|
||||
console.error("Failed to destroy pickle key", e);
|
||||
}
|
||||
break;
|
||||
case "getDesktopCapturerSources":
|
||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||
@@ -189,10 +181,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
break;
|
||||
|
||||
case "clearStorage":
|
||||
global.store.clear();
|
||||
global.mainWindow.webContents.session.flushStorageData();
|
||||
await global.mainWindow.webContents.session.clearStorageData();
|
||||
relaunchApp();
|
||||
await clearDataAndRelaunch();
|
||||
return; // the app is about to stop, we don't need to reply to the IPC
|
||||
|
||||
case "breadcrumbs": {
|
||||
|
||||
@@ -10,9 +10,9 @@ import { type TranslationKey as TKey } from "matrix-web-i18n";
|
||||
import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import type Store from "electron-store";
|
||||
import type EN from "./i18n/strings/en_EN.json";
|
||||
import { loadJsonFile } from "./utils.js";
|
||||
import type Store from "./store.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -59,26 +59,24 @@ export function _t(text: TranslationKey, variables: Variables = {}): string {
|
||||
|
||||
type Component = () => void;
|
||||
|
||||
type TypedStore = Store<{ locale?: string | string[] }>;
|
||||
|
||||
export class AppLocalization {
|
||||
private static readonly STORE_KEY = "locale";
|
||||
|
||||
private readonly store: TypedStore;
|
||||
private readonly localizedComponents?: Set<Component>;
|
||||
private readonly store: Store;
|
||||
|
||||
public constructor({ store, components = [] }: { store: TypedStore; components: Component[] }) {
|
||||
public constructor({ components = [], store }: { components: Component[]; store: Store }) {
|
||||
counterpart.registerTranslations(FALLBACK_LOCALE, this.fetchTranslationJson("en_EN"));
|
||||
counterpart.setFallbackLocale(FALLBACK_LOCALE);
|
||||
counterpart.setSeparator("|");
|
||||
|
||||
this.store = store;
|
||||
if (Array.isArray(components)) {
|
||||
this.localizedComponents = new Set(components);
|
||||
}
|
||||
|
||||
this.store = store;
|
||||
if (this.store.has(AppLocalization.STORE_KEY)) {
|
||||
const locales = this.store.get(AppLocalization.STORE_KEY);
|
||||
if (store.has(AppLocalization.STORE_KEY)) {
|
||||
const locales = store.get(AppLocalization.STORE_KEY);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.setAppLocale(locales!);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export function protocolInit(): void {
|
||||
// Protocol handler for win32/Linux
|
||||
app.on("second-instance", (ev, commandLine) => {
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
if (!url.startsWith(`${PROTOCOL}://`) && !url.startsWith(`${LEGACY_PROTOCOL}://`)) return;
|
||||
if (!url.startsWith(`${PROTOCOL}:/`) && !url.startsWith(`${LEGACY_PROTOCOL}://`)) return;
|
||||
processUrl(url);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { app, ipcMain } from "electron";
|
||||
import { promises as afs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import keytar from "keytar-forked";
|
||||
|
||||
import type {
|
||||
Seshat as SeshatType,
|
||||
@@ -17,6 +16,7 @@ import type {
|
||||
} from "matrix-seshat"; // Hak dependency type
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
import { randomArray } from "./utils.js";
|
||||
import Store from "./store.js";
|
||||
|
||||
let seshatSupported = false;
|
||||
let Seshat: typeof SeshatType;
|
||||
@@ -40,21 +40,24 @@ try {
|
||||
let eventIndex: SeshatType | null = null;
|
||||
|
||||
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
|
||||
async function getOrCreatePassphrase(key: string): Promise<string> {
|
||||
if (keytar) {
|
||||
try {
|
||||
const storedPassphrase = await keytar.getPassword("element.io", key);
|
||||
if (storedPassphrase !== null) {
|
||||
return storedPassphrase;
|
||||
} else {
|
||||
const newPassphrase = await randomArray(32);
|
||||
await keytar.setPassword("element.io", key, newPassphrase);
|
||||
return newPassphrase;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error getting the event index passphrase out of the secret store", e);
|
||||
async function getOrCreatePassphrase(store: Store, key: string): Promise<string> {
|
||||
try {
|
||||
const storedPassphrase = await store.getSecret(key);
|
||||
if (storedPassphrase !== null) {
|
||||
return storedPassphrase;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error getting the event index passphrase out of the secret store", e);
|
||||
}
|
||||
|
||||
try {
|
||||
const newPassphrase = await randomArray(32);
|
||||
await store.setSecret(key, newPassphrase);
|
||||
return newPassphrase;
|
||||
} catch (e) {
|
||||
console.error("Error creating new event index passphrase, using default", e);
|
||||
}
|
||||
|
||||
return seshatDefaultPassphrase;
|
||||
}
|
||||
|
||||
@@ -74,7 +77,8 @@ const deleteContents = async (p: string): Promise<void> => {
|
||||
};
|
||||
|
||||
ipcMain.on("seshat", async function (_ev: IpcMainEvent, payload): Promise<void> {
|
||||
if (!global.mainWindow) return;
|
||||
const store = Store.instance;
|
||||
if (!global.mainWindow || !store) return;
|
||||
|
||||
// We do this here to ensure we get the path after --profile has been resolved
|
||||
const eventStorePath = path.join(app.getPath("userData"), "EventStore");
|
||||
@@ -101,7 +105,7 @@ ipcMain.on("seshat", async function (_ev: IpcMainEvent, payload): Promise<void>
|
||||
const deviceId = args[1];
|
||||
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
||||
|
||||
const passphrase = await getOrCreatePassphrase(passphraseKey);
|
||||
const passphrase = await getOrCreatePassphrase(store, passphraseKey);
|
||||
|
||||
try {
|
||||
await afs.mkdir(eventStorePath, { recursive: true });
|
||||
|
||||
@@ -6,6 +6,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import * as tray from "./tray.js";
|
||||
import Store from "./store.js";
|
||||
|
||||
interface Setting {
|
||||
read(): Promise<any>;
|
||||
@@ -27,10 +28,10 @@ export const Settings: Record<string, Setting> = {
|
||||
},
|
||||
"Electron.warnBeforeExit": {
|
||||
async read(): Promise<any> {
|
||||
return global.store.get("warnBeforeExit", true);
|
||||
return Store.instance?.get("warnBeforeExit");
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("warnBeforeExit", value);
|
||||
Store.instance?.set("warnBeforeExit", value);
|
||||
},
|
||||
},
|
||||
"Electron.alwaysShowMenuBar": {
|
||||
@@ -39,7 +40,7 @@ export const Settings: Record<string, Setting> = {
|
||||
return !global.mainWindow!.autoHideMenuBar;
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("autoHideMenuBar", !value);
|
||||
Store.instance?.set("autoHideMenuBar", !value);
|
||||
global.mainWindow!.autoHideMenuBar = !value;
|
||||
global.mainWindow!.setMenuBarVisibility(value);
|
||||
},
|
||||
@@ -56,15 +57,15 @@ export const Settings: Record<string, Setting> = {
|
||||
} else {
|
||||
tray.destroy();
|
||||
}
|
||||
global.store.set("minimizeToTray", value);
|
||||
Store.instance?.set("minimizeToTray", value);
|
||||
},
|
||||
},
|
||||
"Electron.enableHardwareAcceleration": {
|
||||
async read(): Promise<any> {
|
||||
return !global.store.get("disableHardwareAcceleration", false);
|
||||
return !Store.instance?.get("disableHardwareAcceleration");
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("disableHardwareAcceleration", !value);
|
||||
Store.instance?.set("disableHardwareAcceleration", !value);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
456
src/store.ts
Normal file
456
src/store.ts
Normal file
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
Copyright 2022-2025 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ElectronStore from "electron-store";
|
||||
import keytar from "keytar-forked";
|
||||
import { app, safeStorage, dialog, type SafeStorage } from "electron";
|
||||
import { clearAllUserData, relaunchApp } from "@standardnotes/electron-clear-data";
|
||||
|
||||
import { _t } from "./language-helper.js";
|
||||
|
||||
/**
|
||||
* Legacy keytar service name for storing secrets.
|
||||
* @deprecated
|
||||
*/
|
||||
const KEYTAR_SERVICE = "element.io";
|
||||
/**
|
||||
* Super legacy keytar service name for reading secrets.
|
||||
* @deprecated
|
||||
*/
|
||||
const LEGACY_KEYTAR_SERVICE = "riot.im";
|
||||
|
||||
/**
|
||||
* String union type representing all the safeStorage backends.
|
||||
* + The "unknown" backend shouldn't exist in practice once the app is ready
|
||||
* + The "plaintext" is the temporarily-unencrypted backend for migration, data is wholly unencrypted - uses PlaintextStorageWriter
|
||||
* + The "basic_text" backend is the 'plaintext' backend on Linux, data is encrypted but not using the keychain
|
||||
* + The "system" backend is the encrypted backend on Windows & macOS, data is encrypted using system keychain
|
||||
* + All other backends are linux-specific and are encrypted using the keychain
|
||||
*/
|
||||
type SafeStorageBackend = ReturnType<SafeStorage["getSelectedStorageBackend"]> | "system" | "plaintext";
|
||||
|
||||
/**
|
||||
* Map of safeStorage backends to their command line arguments.
|
||||
* kwallet6 cannot be specified via command line
|
||||
* https://www.electronjs.org/docs/latest/api/safe-storage#safestoragegetselectedstoragebackend-linux
|
||||
*/
|
||||
const safeStorageBackendMap: Omit<
|
||||
Record<SafeStorageBackend, string>,
|
||||
"unknown" | "kwallet6" | "system" | "plaintext"
|
||||
> = {
|
||||
basic_text: "basic",
|
||||
gnome_libsecret: "gnome-libsecret",
|
||||
kwallet: "kwallet",
|
||||
kwallet5: "kwallet5",
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all data and relaunch the app.
|
||||
*/
|
||||
export async function clearDataAndRelaunch(): Promise<void> {
|
||||
Store.instance?.clear();
|
||||
clearAllUserData();
|
||||
relaunchApp();
|
||||
}
|
||||
|
||||
interface StoreData {
|
||||
warnBeforeExit: boolean;
|
||||
minimizeToTray: boolean;
|
||||
spellCheckerEnabled: boolean;
|
||||
autoHideMenuBar: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration: boolean;
|
||||
safeStorage?: Record<string, string>;
|
||||
/** the safeStorage backend used for the safeStorage data as written */
|
||||
safeStorageBackend?: SafeStorageBackend;
|
||||
/** whether to explicitly override the safeStorage backend, used for migration */
|
||||
safeStorageBackendOverride?: boolean;
|
||||
/** whether to perform a migration of the safeStorage data */
|
||||
safeStorageBackendMigrate?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback storage writer for secrets, mainly used for automated tests and systems without any safeStorage support.
|
||||
*/
|
||||
class PlaintextStorageWriter {
|
||||
public constructor(protected readonly store: ElectronStore<StoreData>) {}
|
||||
|
||||
public getKey(key: string): `safeStorage.${string}` {
|
||||
return `safeStorage.${key.replaceAll(".", "-")}`;
|
||||
}
|
||||
|
||||
public set(key: string, secret: string): void {
|
||||
this.store.set(this.getKey(key), secret);
|
||||
}
|
||||
|
||||
public get(key: string): string | null {
|
||||
return this.store.get(this.getKey(key));
|
||||
}
|
||||
|
||||
public delete(key: string): void {
|
||||
this.store.delete(this.getKey(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage writer for secrets using safeStorage.
|
||||
*/
|
||||
class SafeStorageWriter extends PlaintextStorageWriter {
|
||||
public set(key: string, secret: string): void {
|
||||
this.store.set(this.getKey(key), safeStorage.encryptString(secret).toString("base64"));
|
||||
}
|
||||
|
||||
public get(key: string): string | null {
|
||||
const ciphertext = this.store.get<string, string | undefined>(this.getKey(key));
|
||||
if (ciphertext) {
|
||||
try {
|
||||
return safeStorage.decryptString(Buffer.from(ciphertext, "base64"));
|
||||
} catch (e) {
|
||||
console.error("Failed to decrypt secret", e);
|
||||
console.error("...ciphertext:", JSON.stringify(ciphertext));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const enum Mode {
|
||||
Encrypted = "encrypted", // default
|
||||
AllowPlaintext = "allow-plaintext",
|
||||
ForcePlaintext = "force-plaintext",
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON-backed store for settings which need to be accessible by the main process.
|
||||
* Secrets are stored within the `safeStorage` object, encrypted with safeStorage.
|
||||
* Any secrets operations are blocked on Electron app ready emit, and keytar migration if still needed.
|
||||
*/
|
||||
class Store extends ElectronStore<StoreData> {
|
||||
private static internalInstance?: Store;
|
||||
|
||||
public static get instance(): Store | undefined {
|
||||
return Store.internalInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the store, does not prepare safeStorage, which needs to be done after the app is ready.
|
||||
* Must be executed in the first tick of the event loop so that it can call Electron APIs before ready state.
|
||||
*/
|
||||
public static initialize(mode: Mode | undefined): Store {
|
||||
if (Store.internalInstance) {
|
||||
throw new Error("Store already initialized");
|
||||
}
|
||||
|
||||
const store = new Store(mode ?? Mode.Encrypted);
|
||||
Store.internalInstance = store;
|
||||
|
||||
if (process.platform === "linux" && store.get("safeStorageBackendOverride")) {
|
||||
const backend = store.get("safeStorageBackend")!;
|
||||
if (backend in safeStorageBackendMap) {
|
||||
// If the safeStorage backend which was used to write the data is one we can specify via the commandLine
|
||||
// then do so to ensure we use the same backend for reading the data.
|
||||
app.commandLine.appendSwitch(
|
||||
"password-store",
|
||||
safeStorageBackendMap[backend as keyof typeof safeStorageBackendMap],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
// Provides "raw" access to the underlying secrets storage,
|
||||
// should be avoided in favour of the getSecret/setSecret/deleteSecret methods.
|
||||
private secrets?: PlaintextStorageWriter | SafeStorageWriter;
|
||||
|
||||
private constructor(private mode: Mode) {
|
||||
super({
|
||||
name: "electron-config",
|
||||
clearInvalidConfig: false,
|
||||
schema: {
|
||||
warnBeforeExit: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
minimizeToTray: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
spellCheckerEnabled: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
autoHideMenuBar: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
locale: {
|
||||
anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
|
||||
},
|
||||
disableHardwareAcceleration: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
safeStorage: {
|
||||
type: "object",
|
||||
},
|
||||
safeStorageBackend: {
|
||||
type: "string",
|
||||
},
|
||||
safeStorageBackendOverride: {
|
||||
type: "boolean",
|
||||
},
|
||||
safeStorageBackendMigrate: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private safeStorageReadyPromise?: Promise<unknown>;
|
||||
public async safeStorageReady(): Promise<void> {
|
||||
if (!this.safeStorageReadyPromise) {
|
||||
this.safeStorageReadyPromise = this.prepareSafeStorage();
|
||||
}
|
||||
await this.safeStorageReadyPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the safeStorage backend for use.
|
||||
* We don't eagerly import from keytar as that would bring in data for all Element profiles and not just the current one,
|
||||
* so we import lazily in getSecret.
|
||||
*/
|
||||
private async prepareSafeStorage(): Promise<void> {
|
||||
await app.whenReady();
|
||||
|
||||
let safeStorageBackend = this.get("safeStorageBackend");
|
||||
if (process.platform === "linux") {
|
||||
// Linux safeStorage support is hellish, the support varies on the Desktop Environment used rather than the store itself.
|
||||
// https://github.com/electron/electron/issues/39789 https://github.com/microsoft/vscode/issues/185212
|
||||
const selectedSafeStorageBackend = safeStorage.getSelectedStorageBackend();
|
||||
console.info(
|
||||
`safeStorage backend '${selectedSafeStorageBackend}' selected, '${safeStorageBackend}' in config.`,
|
||||
);
|
||||
|
||||
if (selectedSafeStorageBackend === "unknown") {
|
||||
// This should never happen but good to be safe
|
||||
await dialog.showMessageBox({
|
||||
title: _t("store|error|unknown_backend_override_title"),
|
||||
message: _t("store|error|unknown_backend_override"),
|
||||
detail: _t("store|error|unknown_backend_override_details"),
|
||||
type: "error",
|
||||
});
|
||||
throw new Error("safeStorage backend unknown");
|
||||
}
|
||||
|
||||
if (this.get("safeStorageBackendMigrate")) {
|
||||
return this.upgradeLinuxBackend2();
|
||||
}
|
||||
|
||||
if (!safeStorageBackend) {
|
||||
if (selectedSafeStorageBackend === "basic_text" && this.mode === Mode.Encrypted) {
|
||||
const { response } = await dialog.showMessageBox({
|
||||
title: _t("store|error|unsupported_keyring_title"),
|
||||
message: _t("store|error|unsupported_keyring"),
|
||||
detail: _t("store|error|unsupported_keyring_detail", {
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
link: "https://www.electronjs.org/docs/latest/api/safe-storage#safestoragegetselectedstoragebackend-linux",
|
||||
}),
|
||||
type: "error",
|
||||
buttons: [_t("action|cancel"), _t("store|error|unsupported_keyring_cta")],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
});
|
||||
if (response === 0) {
|
||||
throw new Error("safeStorage backend basic_text and user rejected it");
|
||||
}
|
||||
this.mode = Mode.AllowPlaintext;
|
||||
}
|
||||
|
||||
// Store the backend used for the safeStorage data so we can detect if it changes
|
||||
this.recordSafeStorageBackend(selectedSafeStorageBackend);
|
||||
safeStorageBackend = selectedSafeStorageBackend;
|
||||
} else if (safeStorageBackend !== selectedSafeStorageBackend) {
|
||||
console.warn(`safeStorage backend changed from ${safeStorageBackend} to ${selectedSafeStorageBackend}`);
|
||||
|
||||
if (safeStorageBackend === "basic_text") {
|
||||
return this.upgradeLinuxBackend1();
|
||||
} else if (safeStorageBackend === "plaintext") {
|
||||
this.upgradeLinuxBackend3();
|
||||
} else if (safeStorageBackend in safeStorageBackendMap) {
|
||||
this.set("safeStorageBackendOverride", true);
|
||||
relaunchApp();
|
||||
return;
|
||||
} else {
|
||||
// Warn the user that the backend has changed and tell them that we cannot migrate
|
||||
const { response } = await dialog.showMessageBox({
|
||||
title: _t("store|error|backend_changed_title"),
|
||||
message: _t("store|error|backend_changed"),
|
||||
detail: _t("store|error|backend_changed_detail"),
|
||||
type: "question",
|
||||
buttons: [_t("common|no"), _t("common|yes")],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
});
|
||||
if (response === 0) {
|
||||
throw new Error("safeStorage backend changed and cannot migrate");
|
||||
}
|
||||
await clearDataAndRelaunch();
|
||||
}
|
||||
}
|
||||
|
||||
// We do not check allowPlaintextStorage here as it was already checked above if the storage is new
|
||||
// and if the storage is existing then we should continue to honour the backend used to write the data
|
||||
if (safeStorageBackend === "basic_text" && selectedSafeStorageBackend === safeStorageBackend) {
|
||||
safeStorage.setUsePlainTextEncryption(true);
|
||||
}
|
||||
} else if (!safeStorageBackend) {
|
||||
safeStorageBackend = this.mode === Mode.Encrypted ? "system" : "plaintext";
|
||||
this.recordSafeStorageBackend(safeStorageBackend);
|
||||
}
|
||||
|
||||
if (this.mode !== Mode.ForcePlaintext && safeStorage.isEncryptionAvailable()) {
|
||||
this.secrets = new SafeStorageWriter(this);
|
||||
} else if (this.mode !== Mode.Encrypted) {
|
||||
this.secrets = new PlaintextStorageWriter(this);
|
||||
} else {
|
||||
throw new Error(`safeStorage is not available`);
|
||||
}
|
||||
|
||||
console.info(`Using storage mode '${this.mode}' with backend '${safeStorageBackend}'`);
|
||||
}
|
||||
|
||||
private recordSafeStorageBackend(backend: SafeStorageBackend): void {
|
||||
this.set("safeStorageBackend", backend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Linux support for upgrading the backend from basic_text to one of the encrypted backends,
|
||||
* this is quite a tricky process as the backend is not known until the app is ready & cannot be changed once it is.
|
||||
* First we restart the app in basic_text backend mode, then decrypt the data & restart back in default backend mode,
|
||||
* and re-encrypt the data.
|
||||
*/
|
||||
private upgradeLinuxBackend1(): void {
|
||||
console.info(`Starting safeStorage migration to ${safeStorage.getSelectedStorageBackend()}`);
|
||||
this.set("safeStorageBackendMigrate", true);
|
||||
relaunchApp();
|
||||
}
|
||||
private upgradeLinuxBackend2(): void {
|
||||
if (!this.secrets) throw new Error("safeStorage not ready");
|
||||
console.info("Performing safeStorage migration");
|
||||
const data = this.get("safeStorage");
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
this.set(this.secrets.getKey(key), this.secrets!.get(key));
|
||||
}
|
||||
this.recordSafeStorageBackend("plaintext");
|
||||
}
|
||||
this.set("safeStorageBackendMigrate", false);
|
||||
relaunchApp();
|
||||
}
|
||||
private upgradeLinuxBackend3(): void {
|
||||
if (!this.secrets) throw new Error("safeStorage not ready");
|
||||
const selectedSafeStorageBackend = safeStorage.getSelectedStorageBackend();
|
||||
console.info(`Finishing safeStorage migration to ${selectedSafeStorageBackend}`);
|
||||
const data = this.get("safeStorage");
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
this.secrets.set(key, data[key]);
|
||||
}
|
||||
}
|
||||
this.recordSafeStorageBackend(selectedSafeStorageBackend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored secret for the key.
|
||||
* Lazily migrates keys from keytar if they are not yet in the store.
|
||||
*
|
||||
* @param key The string key name.
|
||||
*
|
||||
* @returns A promise for the secret string.
|
||||
*/
|
||||
public async getSecret(key: string): Promise<string | null> {
|
||||
await this.safeStorageReady();
|
||||
let secret = this.secrets!.get(key);
|
||||
if (secret) return secret;
|
||||
|
||||
try {
|
||||
secret = await this.getSecretKeytar(key);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to read data from keytar with key='${key}'`, e);
|
||||
}
|
||||
if (secret) {
|
||||
console.debug("Migrating secret from keytar", key);
|
||||
this.secrets!.set(key, secret);
|
||||
}
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the secret for the key to the keychain.
|
||||
* We write to both safeStorage & keytar to support downgrading the application.
|
||||
*
|
||||
* @param key The string key name.
|
||||
* @param secret The string password.
|
||||
*
|
||||
* @returns A promise for the set password completion.
|
||||
*/
|
||||
public async setSecret(key: string, secret: string): Promise<void> {
|
||||
await this.safeStorageReady();
|
||||
this.secrets!.set(key, secret);
|
||||
try {
|
||||
await keytar.setPassword(KEYTAR_SERVICE, key, secret);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to write safeStorage backwards-compatibility key='${key}' data to keytar`, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the stored password for the key.
|
||||
* Removes from safeStorage, keytar & keytar legacy.
|
||||
*
|
||||
* @param key The string key name.
|
||||
*/
|
||||
public async deleteSecret(key: string): Promise<void> {
|
||||
await this.safeStorageReady();
|
||||
this.secrets!.delete(key);
|
||||
try {
|
||||
await this.deleteSecretKeytar(key);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to delete secret with key='${key}' from keytar`, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated will be removed in the near future
|
||||
*/
|
||||
private async getSecretKeytar(key: string): Promise<string | null> {
|
||||
return (
|
||||
(await keytar.getPassword(KEYTAR_SERVICE, key)) ?? (await keytar.getPassword(LEGACY_KEYTAR_SERVICE, key))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated will be removed in the near future
|
||||
*/
|
||||
private async deleteSecretKeytar(key: string): Promise<void> {
|
||||
await keytar.deletePassword(LEGACY_KEYTAR_SERVICE, key);
|
||||
await keytar.deletePassword(KEYTAR_SERVICE, key);
|
||||
}
|
||||
}
|
||||
|
||||
export default Store;
|
||||
11
src/utils.ts
11
src/utils.ts
@@ -26,7 +26,7 @@ type JsonArray = Array<JsonValue | JsonObject | JsonArray>;
|
||||
interface JsonObject {
|
||||
[key: string]: JsonObject | JsonArray | JsonValue;
|
||||
}
|
||||
type Json = JsonArray | JsonObject;
|
||||
export type Json = JsonArray | JsonObject;
|
||||
|
||||
/**
|
||||
* Synchronously load a JSON file from the local filesystem.
|
||||
@@ -34,6 +34,13 @@ type Json = JsonArray | JsonObject;
|
||||
* @param paths - An array of path segments which will be joined using the system's path delimiter.
|
||||
*/
|
||||
export function loadJsonFile<T extends Json>(...paths: string[]): T {
|
||||
const file = fs.readFileSync(path.join(...paths), { encoding: "utf-8" });
|
||||
const joinedPaths = path.join(...paths);
|
||||
|
||||
if (!fs.existsSync(joinedPaths)) {
|
||||
console.log(`Skipping nonexistent file: ${joinedPaths}`);
|
||||
return {} as T;
|
||||
}
|
||||
|
||||
const file = fs.readFileSync(joinedPaths, { encoding: "utf-8" });
|
||||
return JSON.parse(file);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user