mirror of
https://github.com/element-hq/element-desktop.git
synced 2025-12-05 01:10:21 +00:00
Compare commits
217 Commits
85cdd9a493
...
v1.11.106
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27582b5d8c | ||
|
|
3f94cb2619 | ||
|
|
48a138102b | ||
|
|
93a72b3d72 | ||
|
|
343b7d75d0 | ||
|
|
804933023a | ||
|
|
5149911e38 | ||
|
|
c9a53ba25d | ||
|
|
abf15ef471 | ||
|
|
4d0add6309 | ||
|
|
74f2ecb4a9 | ||
|
|
8ce5cf0f10 | ||
|
|
b92d31e7a4 | ||
|
|
fd29e65112 | ||
|
|
75f93ab631 | ||
|
|
4b56f3b1ca | ||
|
|
a1b1245c77 | ||
|
|
053bcd10e1 | ||
|
|
91036dcd22 | ||
|
|
c0f3703c5a | ||
|
|
960a413c8a | ||
|
|
5e8373d6cd | ||
|
|
858e4fdbde | ||
|
|
8e5761be2d | ||
|
|
be6565656d | ||
|
|
02f2274765 | ||
|
|
0d04e3d2ac | ||
|
|
f4b3816888 | ||
|
|
db153374f0 | ||
|
|
0d4f02cde6 | ||
|
|
0061966718 | ||
|
|
9fc64550dd | ||
|
|
51d2f6a29e | ||
|
|
58ef3d277f | ||
|
|
fd62231856 | ||
|
|
0510fa4ee4 | ||
|
|
58d129f565 | ||
|
|
c72c18e998 | ||
|
|
906125c738 | ||
|
|
bf97ef4904 | ||
|
|
273afae84e | ||
|
|
e18ddee37e | ||
|
|
c43e8d684f | ||
|
|
75463f0296 | ||
|
|
2401c1f7ef | ||
|
|
fe7d10539b | ||
|
|
cb5f5bc94a | ||
|
|
b123e0f60c | ||
|
|
39f460b636 | ||
|
|
86f6136257 | ||
|
|
2e039f4bab | ||
|
|
8f24f45090 | ||
|
|
6901bff548 | ||
|
|
cf88e520a0 | ||
|
|
3bc59fb6ce | ||
|
|
f136c7aaad | ||
|
|
5b33e2866d | ||
|
|
c0ca58e930 | ||
|
|
77038a5a86 | ||
|
|
b22d2480ae | ||
|
|
7142b6fe57 | ||
|
|
b4c2bc7165 | ||
|
|
4ea7b679e9 | ||
|
|
89ead2e56f | ||
|
|
360665cd41 | ||
|
|
93c0b81cc4 | ||
|
|
bd194306dd | ||
|
|
b4a23ff505 | ||
|
|
8559a740f4 | ||
|
|
dea64f7e38 | ||
|
|
044de246a1 | ||
|
|
2465607ac5 | ||
|
|
d9ad2faac6 | ||
|
|
0c4c950fc7 | ||
|
|
9660c4b2be | ||
|
|
73ddf2a19b | ||
|
|
cfea34766a | ||
|
|
77bea6db7e | ||
|
|
abd508fc0d | ||
|
|
8e4826b4e9 | ||
|
|
3cd88352b6 | ||
|
|
1d79fa633a | ||
|
|
ec4c610158 | ||
|
|
468d2249d1 | ||
|
|
63e1e0d894 | ||
|
|
e326246669 | ||
|
|
53672fadd7 | ||
|
|
20f28abb47 | ||
|
|
a0db96d50d | ||
|
|
2bb1544064 | ||
|
|
827d514628 | ||
|
|
28e558162a | ||
|
|
85556ecd74 | ||
|
|
46080c66d0 | ||
|
|
80b88583b8 | ||
|
|
2aaf42b8e8 | ||
|
|
660ccd414a | ||
|
|
32cf6b2ebf | ||
|
|
5507f2859f | ||
|
|
689179c5ae | ||
|
|
3116f596f2 | ||
|
|
69cfe0bda6 | ||
|
|
194798497f | ||
|
|
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 |
29
.github/workflows/build_and_deploy.yaml
vendored
29
.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:
|
||||
@@ -107,14 +107,14 @@ jobs:
|
||||
environment: ${{ needs.prepare.outputs.deploy == 'true' && 'packages.element.io' || '' }}
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
|
||||
- name: Prepare artifacts for deployment
|
||||
run: |
|
||||
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
|
||||
@@ -197,24 +197,17 @@ jobs:
|
||||
|
||||
- name: Stash packages.element.io
|
||||
if: needs.prepare.outputs.deploy == 'false'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
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 }}
|
||||
@@ -242,7 +235,7 @@ jobs:
|
||||
|
||||
- name: Stash debs
|
||||
if: needs.prepare.outputs.deploy == 'false' && needs.linux.result == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: debs
|
||||
path: |
|
||||
@@ -281,14 +274,14 @@ jobs:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::264135176173:role/Push-ElementDesktop-MSI
|
||||
role-session-name: githubaction-run-${{ github.run_id }}
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
pattern: win-*
|
||||
|
||||
|
||||
110
.github/workflows/build_and_test.yaml
vendored
110
.github/workflows/build_and_test.yaml
vendored
@@ -13,8 +13,9 @@ 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' || '' }}
|
||||
branch-matching: true
|
||||
|
||||
windows:
|
||||
needs: fetch
|
||||
@@ -25,6 +26,7 @@ jobs:
|
||||
arch: [x64, ia32, arm64]
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
blob_report: true
|
||||
|
||||
linux:
|
||||
needs: fetch
|
||||
@@ -37,101 +39,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/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # 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@d3f86a106a0bac45b974a628896c90dbdf5c8093 # 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
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 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
|
||||
|
||||
30
.github/workflows/build_linux.yaml
vendored
30
.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
|
||||
@@ -56,21 +60,21 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion', 'dockerbuild/*') }}
|
||||
path: |
|
||||
./.hak
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
@@ -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@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
if: steps.changed_files.outputs.any_modified == 'true'
|
||||
with:
|
||||
file: dockerbuild/Dockerfile
|
||||
@@ -169,7 +173,7 @@ jobs:
|
||||
|
||||
# We exclude *-unpacked as it loses permissions and the tarball contains it with correct permissions
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
|
||||
path: |
|
||||
@@ -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 }}
|
||||
|
||||
33
.github/workflows/build_macos.yaml
vendored
33
.github/workflows/build_macos.yaml
vendored
@@ -27,21 +27,25 @@ 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:
|
||||
runs-on: macos-14 # M1
|
||||
environment: ${{ inputs.sign && 'packages.element.io' || '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakHash', 'electronVersion') }}
|
||||
path: |
|
||||
@@ -56,11 +60,11 @@ jobs:
|
||||
rustup target add x86_64-apple-darwin
|
||||
|
||||
# M1 macos-14 comes without Python preinstalled
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.13"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
@@ -133,7 +137,7 @@ jobs:
|
||||
|
||||
# We exclude mac-universal as the unpacked app takes forever to upload and zip and dmg already contains it
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: macos
|
||||
path: |
|
||||
@@ -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 }}
|
||||
|
||||
35
.github/workflows/build_prepare.yaml
vendored
35
.github/workflows/build_prepare.yaml
vendored
@@ -20,6 +20,11 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
description: "Whether the build should be deployed to production"
|
||||
branch-matching:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
description: "Whether the branch name should be matched to find the element-web commit"
|
||||
secrets:
|
||||
# Required if `nightly` is set
|
||||
CF_R2_ACCESS_KEY_ID:
|
||||
@@ -49,9 +54,9 @@ jobs:
|
||||
outputs:
|
||||
nightly-version: ${{ steps.versions.outputs.nightly }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
@@ -59,7 +64,25 @@ jobs:
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
|
||||
- name: Fetch Element Web
|
||||
- name: Fetch Element Web (matching branch)
|
||||
id: branch-matching
|
||||
if: inputs.branch-matching
|
||||
continue-on-error: true
|
||||
run: |
|
||||
scripts/branch-match.sh
|
||||
cp "$CONFIG_DIR/config.json" element-web/
|
||||
yarn --cwd element-web install --frozen-lockfile
|
||||
yarn --cwd element-web run build
|
||||
mv element-web/webapp .
|
||||
yarn asar-webapp
|
||||
env:
|
||||
# These must be set for branch-match.sh to get the right branch
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
CONFIG_DIR: ${{ inputs.config }}
|
||||
|
||||
- name: Fetch Element Web (${{ inputs.version }})
|
||||
if: steps.branch-matching.outcome == 'failure' || steps.branch-matching.outcome == 'skipped'
|
||||
run: yarn run fetch --noverify -d ${{ inputs.config }} ${{ inputs.version }}
|
||||
|
||||
# We split this out to save the build_* scripts having to do it to make use of `hashFiles` in the cache action
|
||||
@@ -80,7 +103,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 +113,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 }}
|
||||
|
||||
@@ -139,7 +160,7 @@ jobs:
|
||||
echo "| Element Web | [$WEB_VERSION](https://github.com/element-hq/element-web/commit/$WEB_VERSION) |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| JS SDK | [$JS_VERSION](https://github.com/matrix-org/matrix-js-sdk/commit/$JS_VERSION) |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: webapp
|
||||
retention-days: 1
|
||||
|
||||
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: ${{ inputs.artifact }}-test
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
29
.github/workflows/build_windows.yaml
vendored
29
.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,19 +61,19 @@ 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.\"}}"
|
||||
}
|
||||
}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion') }}
|
||||
path: |
|
||||
@@ -98,7 +102,7 @@ jobs:
|
||||
rustup default stable
|
||||
rustup target add ${{ steps.config.outputs.target }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
@@ -202,7 +206,7 @@ jobs:
|
||||
| ForEach-Object -Process {. $env:SIGNTOOL_PATH verify /pa $_.FullName; if(!$?) { throw }}
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: win-${{ inputs.arch }}
|
||||
path: |
|
||||
@@ -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 }}
|
||||
|
||||
10
.github/workflows/dockerbuild.yaml
vendored
10
.github/workflows/dockerbuild.yaml
vendored
@@ -19,18 +19,18 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Build test image
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # 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@3d100841f68d4548bf57e52eb27bd33ec5069f55
|
||||
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@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
with:
|
||||
file: dockerbuild/Dockerfile
|
||||
push: true
|
||||
|
||||
16
.github/workflows/static_analysis.yaml
vendored
16
.github/workflows/static_analysis.yaml
vendored
@@ -9,9 +9,9 @@ jobs:
|
||||
name: "Typescript Syntax Check"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "yarn"
|
||||
@@ -35,9 +35,9 @@ jobs:
|
||||
name: "ESLint"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "yarn"
|
||||
@@ -53,9 +53,9 @@ jobs:
|
||||
name: "Workflow Lint"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "yarn"
|
||||
@@ -71,9 +71,9 @@ jobs:
|
||||
name: "Analyse Dead Code"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "yarn"
|
||||
|
||||
2
.github/workflows/triage-stale.yml
vendored
2
.github/workflows/triage-stale.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
|
||||
with:
|
||||
operations-per-run: 250
|
||||
days-before-issue-stale: -1
|
||||
|
||||
@@ -1 +1 @@
|
||||
v22.14.0
|
||||
v22.16.0
|
||||
|
||||
294
CHANGELOG.md
294
CHANGELOG.md
@@ -1,3 +1,297 @@
|
||||
Changes in [1.11.106](https://github.com/element-hq/element-desktop/releases/tag/v1.11.106) (2025-07-15)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* [Backport staging] Fix e2e icon colour ([#30304](https://github.com/element-hq/element-web/pull/30304)). Contributed by @RiotRobot.
|
||||
* Add support for module message hint `allowDownloadingMedia` ([#30252](https://github.com/element-hq/element-web/pull/30252)). Contributed by @Half-Shot.
|
||||
* Update the mobile\_guide page to the new design and link out to Element X by default. ([#30172](https://github.com/element-hq/element-web/pull/30172)). Contributed by @pixlwave.
|
||||
* Filter settings exported when rageshaking ([#30236](https://github.com/element-hq/element-web/pull/30236)). Contributed by @Half-Shot.
|
||||
* Allow Element Call to learn the room name ([#30213](https://github.com/element-hq/element-web/pull/30213)). Contributed by @robintown.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Fix missing image download button ([#30322](https://github.com/element-hq/element-web/pull/30322)). Contributed by @RiotRobot.
|
||||
* Fix transparent verification checkmark in dark mode ([#30235](https://github.com/element-hq/element-web/pull/30235)). Contributed by @Banbuii.
|
||||
* Fix logic in DeviceListener ([#30230](https://github.com/element-hq/element-web/pull/30230)). Contributed by @uhoreg.
|
||||
* Disable file drag-and-drop if insufficient permissions ([#30186](https://github.com/element-hq/element-web/pull/30186)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.105](https://github.com/element-hq/element-desktop/releases/tag/v1.11.105) (2025-07-01)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Add support for migrating to kwallet6 ([#2390](https://github.com/element-hq/element-desktop/pull/2390)). Contributed by @t3chguy.
|
||||
* New room list: add context menu to room list item ([#29952](https://github.com/element-hq/element-web/pull/29952)). Contributed by @florianduros.
|
||||
* Support for custom message components via Module API ([#30074](https://github.com/element-hq/element-web/pull/30074)). Contributed by @Half-Shot.
|
||||
* Prompt users to set up recovery ([#30075](https://github.com/element-hq/element-web/pull/30075)). Contributed by @uhoreg.
|
||||
* Update `IconButton` colors ([#30124](https://github.com/element-hq/element-web/pull/30124)). Contributed by @florianduros.
|
||||
* New room list: filter list can be collapsed ([#29992](https://github.com/element-hq/element-web/pull/29992)). Contributed by @florianduros.
|
||||
* Show `EmptyRoomListView` when low priority filter matches zero rooms ([#30122](https://github.com/element-hq/element-web/pull/30122)). Contributed by @MidhunSureshR.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix element-desktop-ssoid profile deeplinking for OIDC ([#2396](https://github.com/element-hq/element-desktop/pull/2396)). Contributed by @t3chguy.
|
||||
* Add support for migrating to kwallet6 ([#2390](https://github.com/element-hq/element-desktop/pull/2390)). Contributed by @t3chguy.
|
||||
* Fix untranslatable string "People" in notifications beta ([#30165](https://github.com/element-hq/element-web/pull/30165)). Contributed by @t3chguy.
|
||||
* Force verification even after logging in via delegate ([#30141](https://github.com/element-hq/element-web/pull/30141)). Contributed by @andybalaam.
|
||||
* Hide add integrations button based on UIComponent.AddIntegrations ([#30140](https://github.com/element-hq/element-web/pull/30140)). Contributed by @t3chguy.
|
||||
* Use nav for new room list and label sections ([#30134](https://github.com/element-hq/element-web/pull/30134)). Contributed by @dbkr.
|
||||
* Spacestore should emit event after rebuilding home space ([#30132](https://github.com/element-hq/element-web/pull/30132)). Contributed by @MidhunSureshR.
|
||||
* Handle m.room.pinned\_events being invalid ([#30129](https://github.com/element-hq/element-web/pull/30129)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.104](https://github.com/element-hq/element-desktop/releases/tag/v1.11.104) (2025-06-17)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Update the mobile\_guide page to the new design. ([#30006](https://github.com/element-hq/element-web/pull/30006)). Contributed by @pixlwave.
|
||||
* Provide a devtool for manually verifying other devices ([#30094](https://github.com/element-hq/element-web/pull/30094)). Contributed by @andybalaam.
|
||||
* Implement MSC4155: Invite filtering ([#29603](https://github.com/element-hq/element-web/pull/29603)). Contributed by @Half-Shot.
|
||||
* Add low priority avatar decoration to room tile ([#30065](https://github.com/element-hq/element-web/pull/30065)). Contributed by @MidhunSureshR.
|
||||
* Add ability to prevent window content being captured by other apps (Desktop) ([#30098](https://github.com/element-hq/element-web/pull/30098)). Contributed by @t3chguy.
|
||||
* New room list: move message preview in user settings ([#30023](https://github.com/element-hq/element-web/pull/30023)). Contributed by @florianduros.
|
||||
* New room list: change room options icon ([#30029](https://github.com/element-hq/element-web/pull/30029)). Contributed by @florianduros.
|
||||
* RoomListStore: Sort low priority rooms to the bottom of the list ([#30070](https://github.com/element-hq/element-web/pull/30070)). Contributed by @MidhunSureshR.
|
||||
* Add low priority filter pill to the room list UI ([#30060](https://github.com/element-hq/element-web/pull/30060)). Contributed by @MidhunSureshR.
|
||||
* New room list: remove color gradient in space panel ([#29721](https://github.com/element-hq/element-web/pull/29721)). Contributed by @florianduros.
|
||||
* /share?msg=foo endpoint using forward message dialog ([#29874](https://github.com/element-hq/element-web/pull/29874)). Contributed by @ara4n.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix restart loop in safeStorage ([#2373](https://github.com/element-hq/element-desktop/pull/2373)). Contributed by @dbkr.
|
||||
* Do not send empty auth when setting up cross-signing keys ([#29914](https://github.com/element-hq/element-web/pull/29914)). Contributed by @gnieto.
|
||||
* Settings: flip local video feed by default ([#29501](https://github.com/element-hq/element-web/pull/29501)). Contributed by @jbtrystram.
|
||||
* AccessSecretStorageDialog: various fixes ([#30093](https://github.com/element-hq/element-web/pull/30093)). Contributed by @richvdh.
|
||||
* AccessSecretStorageDialog: fix inability to enter recovery key ([#30090](https://github.com/element-hq/element-web/pull/30090)). Contributed by @richvdh.
|
||||
* Fix failure to upload thumbnail causing image to send as file ([#30086](https://github.com/element-hq/element-web/pull/30086)). Contributed by @t3chguy.
|
||||
* Low priority menu item should be a toggle ([#30071](https://github.com/element-hq/element-web/pull/30071)). Contributed by @MidhunSureshR.
|
||||
* Add sanity checks to prevent users from ignoring themselves ([#30079](https://github.com/element-hq/element-web/pull/30079)). Contributed by @MidhunSureshR.
|
||||
* Fix issue with duplicate images ([#30073](https://github.com/element-hq/element-web/pull/30073)). Contributed by @fatlewis.
|
||||
* Handle errors returned from Seshat ([#30083](https://github.com/element-hq/element-web/pull/30083)). Contributed by @richvdh.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.103](https://github.com/element-hq/element-desktop/releases/tag/v1.11.103) (2025-06-10)
|
||||
========================================================================================================
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
+ Check the sender of an event matches owner of session, preventing sender spoofing by homeserver owners.
|
||||
[13c1d20](https://github.com/matrix-org/matrix-rust-sdk/commit/13c1d2048286bbabf5e7bc6b015aafee98f04d55) (High, [GHSA-x958-rvg6-956w](https://github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-x958-rvg6-956w)).
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.102](https://github.com/element-hq/element-desktop/releases/tag/v1.11.102) (2025-06-03)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Support build-time specified protocol scheme for oidc callback ([#2285](https://github.com/element-hq/element-desktop/pull/2285)). Contributed by @t3chguy.
|
||||
* EW: Modernize the recovery key input modal ([#29819](https://github.com/element-hq/element-web/pull/29819)). Contributed by @uhoreg.
|
||||
* New room list: move secondary filters into primary filters ([#29972](https://github.com/element-hq/element-web/pull/29972)). Contributed by @florianduros.
|
||||
* Prompt the user when key storage is unexpectedly off ([#29912](https://github.com/element-hq/element-web/pull/29912)). Contributed by @andybalaam.
|
||||
* New room list: move sort menu in room list header ([#29983](https://github.com/element-hq/element-web/pull/29983)). Contributed by @florianduros.
|
||||
* New room list: rework spacing of room list item ([#29965](https://github.com/element-hq/element-web/pull/29965)). Contributed by @florianduros.
|
||||
* RLS: Remove forgotten room from skiplist ([#29933](https://github.com/element-hq/element-web/pull/29933)). Contributed by @MidhunSureshR.
|
||||
* Add room list sorting ([#29951](https://github.com/element-hq/element-web/pull/29951)). Contributed by @dbkr.
|
||||
* Don't use the minimised width(68px) on the new room list ([#29778](https://github.com/element-hq/element-web/pull/29778)). Contributed by @langleyd.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Enable plain text encryption before checking if encryption is available ([#2343](https://github.com/element-hq/element-desktop/pull/2343)). Contributed by @MidhunSureshR.
|
||||
* Enable plain text encryption early if we actually mean to use `basic_text` as backend ([#2341](https://github.com/element-hq/element-desktop/pull/2341)). Contributed by @MidhunSureshR.
|
||||
* [Backport staging] Close call options popup menu when option has been selected ([#30054](https://github.com/element-hq/element-web/pull/30054)). Contributed by @RiotRobot.
|
||||
* RoomListStoreV3: Only add new rooms that pass `VisibilityProvider` check ([#29974](https://github.com/element-hq/element-web/pull/29974)). Contributed by @MidhunSureshR.
|
||||
* Re-order primary filters ([#29957](https://github.com/element-hq/element-web/pull/29957)). Contributed by @dbkr.
|
||||
* Fix leaky CSS adding `!` to all H1 elements ([#29964](https://github.com/element-hq/element-web/pull/29964)). Contributed by @t3chguy.
|
||||
* Fix extensions panel style ([#29273](https://github.com/element-hq/element-web/pull/29273)). Contributed by @langleyd.
|
||||
* Fix state events being hidden from widgets in read\_events actions ([#29954](https://github.com/element-hq/element-web/pull/29954)). Contributed by @robintown.
|
||||
* Remove old filter test ([#29963](https://github.com/element-hq/element-web/pull/29963)). Contributed by @dbkr.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.101](https://github.com/element-hq/element-desktop/releases/tag/v1.11.101) (2025-05-20)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Migrate from keytar to safeStorage ([#2227](https://github.com/element-hq/element-desktop/pull/2227)). Contributed by @t3chguy.
|
||||
* New room list: add keyboard navigation support ([#29805](https://github.com/element-hq/element-web/pull/29805)). Contributed by @florianduros.
|
||||
* Use the JoinRuleSettings component for the guest link access prompt. ([#28614](https://github.com/element-hq/element-web/pull/28614)). Contributed by @toger5.
|
||||
* Add loading state to the new room list view ([#29725](https://github.com/element-hq/element-web/pull/29725)). Contributed by @langleyd.
|
||||
* Make OIDC identity reset consistent with EX ([#29854](https://github.com/element-hq/element-web/pull/29854)). Contributed by @andybalaam.
|
||||
* Support error code for email / phone adding unsupported (MSC4178) ([#29855](https://github.com/element-hq/element-web/pull/29855)). Contributed by @dbkr.
|
||||
* Update identity reset UI (Make consistent with EX) ([#29701](https://github.com/element-hq/element-web/pull/29701)). Contributed by @andybalaam.
|
||||
* Add secondary filters to the new room list ([#29818](https://github.com/element-hq/element-web/pull/29818)). Contributed by @dbkr.
|
||||
* Fix battery drain from Web Audio ([#29203](https://github.com/element-hq/element-web/pull/29203)). Contributed by @mbachry.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix go home shortcut on macos and change toggle action events shortcut ([#29929](https://github.com/element-hq/element-web/pull/29929)). Contributed by @florianduros.
|
||||
* New room list: fix outdated message preview when space or filter change ([#29925](https://github.com/element-hq/element-web/pull/29925)). Contributed by @florianduros.
|
||||
* Stop migrating to MSC4278 if the config exists. ([#29924](https://github.com/element-hq/element-web/pull/29924)). Contributed by @Half-Shot.
|
||||
* Ensure consistent download file name on download from ImageView ([#29913](https://github.com/element-hq/element-web/pull/29913)). Contributed by @t3chguy.
|
||||
* Add error toast when service worker registration fails ([#29895](https://github.com/element-hq/element-web/pull/29895)). Contributed by @t3chguy.
|
||||
* New Room List: Prevent old tombstoned rooms from appearing in the list ([#29881](https://github.com/element-hq/element-web/pull/29881)). Contributed by @MidhunSureshR.
|
||||
* Remove lag in search field ([#29885](https://github.com/element-hq/element-web/pull/29885)). Contributed by @florianduros.
|
||||
* Respect UIFeature.Voip ([#29873](https://github.com/element-hq/element-web/pull/29873)). Contributed by @langleyd.
|
||||
* Allow jumping to message search from spotlight ([#29850](https://github.com/element-hq/element-web/pull/29850)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Docker image to facilitate building Element Desktop's native bits using a glibc version (2.31)
|
||||
# with broader compatibility, down to Debian bullseye & Ubuntu focal.
|
||||
FROM rust:bullseye
|
||||
FROM rust:bullseye@sha256:af1a29a166198e1295ca667007e95d2e70c866e3928ba9b25f3907035581c39e
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
- [Introduction](../README.md)
|
||||
|
||||
# Build
|
||||
# Build/Debug
|
||||
|
||||
- [Native Node modules](native-node-modules.md)
|
||||
- [Windows requirements](windows-requirements.md)
|
||||
- [Debugging](debugging.md)
|
||||
- [Using gdb](gdb.md)
|
||||
|
||||
# Distribution
|
||||
|
||||
|
||||
28
docs/debugging.md
Normal file
28
docs/debugging.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Debugging Element-Desktop
|
||||
|
||||
There are two parts of the desktop app that you might want to debug.
|
||||
|
||||
## The renderer process
|
||||
|
||||
This is the regular element-web codeand can be debugged by just selecting 'toggle developer tools'
|
||||
from the menu, even on ppackaged builds. This then works the same as chrome dev tools for element web.
|
||||
|
||||
## The main process
|
||||
|
||||
This is debugged as a node app, so:
|
||||
|
||||
1. Open any chrome dev tools window
|
||||
1. Start element with the `--inspect-brk` flag
|
||||
1. Notice that you now have a little green icon in the top left of your chrome devtools window, click it.
|
||||
|
||||
You are now debugging the code of the desktop app itself.
|
||||
|
||||
## The main process of a package app
|
||||
|
||||
When the app is shipped, electron's "fuses" are flipped, editing the electron binary itself to prevent certain features from being usable, one of which is debugging using `--inspect-brk` as above. You can flip the fuse back on Linux as follows:
|
||||
|
||||
```
|
||||
sudo npx @electron/fuses write --app /opt/Element/element-desktop EnableNodeCliInspectArguments=on
|
||||
```
|
||||
|
||||
A similar command will work, in theory, on mac and windows, except that this will break code signing (which is the point of fuses) so you would have to re-sign the app or somesuch.
|
||||
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 { type Configuration as BaseConfiguration, type Protocol } from "electron-builder";
|
||||
|
||||
/**
|
||||
* This script has different outputs depending on your os platform.
|
||||
@@ -20,9 +16,13 @@ import { readFile, writeFile } from "node:fs/promises";
|
||||
* Passes $ED_DEBIAN_CHANGELOG to build.deb.fpm if specified
|
||||
*/
|
||||
|
||||
const DEFAULT_APP_ID = "im.riot.app";
|
||||
const NIGHTLY_APP_ID = "im.riot.nightly";
|
||||
const NIGHTLY_DEB_NAME = "element-nightly";
|
||||
|
||||
const DEFAULT_PROTOCOL_SCHEME = "io.element.desktop";
|
||||
const NIGHTLY_PROTOCOL_SCHEME = "io.element.nightly";
|
||||
|
||||
interface Pkg {
|
||||
name: string;
|
||||
productName: string;
|
||||
@@ -37,7 +37,11 @@ type Writable<T> = NonNullable<
|
||||
const pkg: Pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||
|
||||
interface Configuration extends BaseConfiguration {
|
||||
extraMetadata: Partial<Pick<Pkg, "version">> & Omit<Pkg, "version">;
|
||||
extraMetadata: Partial<Pick<Pkg, "version">> &
|
||||
Omit<Pkg, "version"> & {
|
||||
electron_appId: string;
|
||||
electron_protocol: string;
|
||||
};
|
||||
linux: BaseConfiguration["linux"];
|
||||
win: BaseConfiguration["win"];
|
||||
mac: BaseConfiguration["mac"];
|
||||
@@ -46,26 +50,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
|
||||
@@ -74,7 +58,7 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
// Make all fuses required to ensure they are all explicitly specified
|
||||
electronFuses: Required<Configuration["electronFuses"]>;
|
||||
} = {
|
||||
appId: "im.riot.app",
|
||||
appId: DEFAULT_APP_ID,
|
||||
asarUnpack: "**/*.node",
|
||||
electronFuses: {
|
||||
enableCookieEncryption: true,
|
||||
@@ -90,9 +74,6 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
loadBrowserProcessSpecificV8Snapshot: false,
|
||||
enableEmbeddedAsarIntegrityValidation: true,
|
||||
},
|
||||
afterPack: async (context: AfterPackContext) => {
|
||||
await injectAsarIntegrity(context);
|
||||
},
|
||||
files: [
|
||||
"package.json",
|
||||
{
|
||||
@@ -112,12 +93,15 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
name: pkg.name,
|
||||
productName: pkg.productName,
|
||||
description: pkg.description,
|
||||
electron_appId: DEFAULT_APP_ID,
|
||||
electron_protocol: DEFAULT_PROTOCOL_SCHEME,
|
||||
},
|
||||
linux: {
|
||||
target: ["tar.gz", "deb"],
|
||||
category: "Network;InstantMessaging;Chat",
|
||||
maintainer: "support@element.io",
|
||||
icon: "build/icons",
|
||||
executableName: pkg.name, // element-desktop or element-desktop-nightly
|
||||
},
|
||||
deb: {
|
||||
packageCategory: "net",
|
||||
@@ -154,6 +138,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"],
|
||||
@@ -168,12 +153,10 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
directories: {
|
||||
output: "dist",
|
||||
},
|
||||
protocols: [
|
||||
{
|
||||
name: "element",
|
||||
schemes: ["io.element.desktop", "element"],
|
||||
},
|
||||
],
|
||||
protocols: {
|
||||
name: "element",
|
||||
schemes: [DEFAULT_PROTOCOL_SCHEME, "element"],
|
||||
},
|
||||
nativeRebuilder: "sequential",
|
||||
nodeGypRebuild: false,
|
||||
npmRebuild: true,
|
||||
@@ -196,11 +179,13 @@ if (process.env.ED_SIGNTOOL_SUBJECT_NAME && process.env.ED_SIGNTOOL_THUMBPRINT)
|
||||
if (process.env.ED_NIGHTLY) {
|
||||
config.deb.fpm = []; // Clear the fpm as the breaks deb fields don't apply to nightly
|
||||
|
||||
config.appId = NIGHTLY_APP_ID;
|
||||
config.appId = config.extraMetadata.electron_appId = NIGHTLY_APP_ID;
|
||||
config.extraMetadata.productName += " Nightly";
|
||||
config.extraMetadata.name += "-nightly";
|
||||
config.extraMetadata.description += " (nightly unstable build)";
|
||||
config.linux.executableName += "-nightly";
|
||||
config.deb.fpm.push("--name", NIGHTLY_DEB_NAME);
|
||||
(config.protocols as Protocol).schemes[0] = config.extraMetadata.electron_protocol = NIGHTLY_PROTOCOL_SCHEME;
|
||||
|
||||
let version = process.env.ED_NIGHTLY;
|
||||
if (os.platform() === "win32") {
|
||||
|
||||
29
package.json
29
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.106",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": "Element",
|
||||
"homepage": "https://element.io",
|
||||
@@ -58,7 +58,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/electron": "^6.0.0",
|
||||
"@standardnotes/electron-clear-data": "^1.0.5",
|
||||
"auto-launch": "^5.0.5",
|
||||
"counterpart": "^0.18.6",
|
||||
"electron-store": "^10.0.0",
|
||||
@@ -74,22 +73,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": "4.0.0",
|
||||
"@playwright/test": "1.53.1",
|
||||
"@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.112",
|
||||
"@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.16",
|
||||
"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.5.0",
|
||||
"electron-builder": "26.0.16",
|
||||
"electron-builder-squirrel-windows": "26.0.16",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
@@ -101,22 +100,22 @@
|
||||
"glob": "^11.0.0",
|
||||
"husky": "^9.1.6",
|
||||
"knip": "^5.0.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"lint-staged": "^16.0.0",
|
||||
"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.112",
|
||||
"config-file-ts": "0.2.8-rc1",
|
||||
"node-abi": "4.9.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.53.1-jammy@sha256:a51c6393b5a6d85897f347c7e5cf03086f50739dde02077e2bb5dc73044c732a
|
||||
|
||||
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.toMatch(/io\.element\.(desktop|nightly):\/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();
|
||||
|
||||
|
||||
48
scripts/branch-match.sh
Executable file
48
scripts/branch-match.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script for downloading a branch of element-web matching the branch a PR is contributed from
|
||||
|
||||
set -x
|
||||
|
||||
deforg="element-hq"
|
||||
defrepo="element-web"
|
||||
|
||||
# The PR_NUMBER variable must be set explicitly.
|
||||
default_org_repo=${GITHUB_REPOSITORY:-"$deforg/$defrepo"}
|
||||
PR_ORG=${PR_ORG:-${default_org_repo%%/*}}
|
||||
PR_REPO=${PR_REPO:-${default_org_repo##*/}}
|
||||
|
||||
# A function that clones a branch of a repo based on the org, repo and branch
|
||||
clone() {
|
||||
org=$1
|
||||
repo=$2
|
||||
branch=$3
|
||||
if [ -n "$branch" ]
|
||||
then
|
||||
echo "Trying to use $org/$repo#$branch"
|
||||
# Disable auth prompts: https://serverfault.com/a/665959
|
||||
GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Getting info about a PR with number $PR_NUMBER"
|
||||
apiEndpoint="https://api.github.com/repos/$PR_ORG/$PR_REPO/pulls/$PR_NUMBER"
|
||||
head=$(curl "$apiEndpoint" | jq -r '.head.label')
|
||||
|
||||
# for forks, $head will be in the format "fork:branch", so we split it by ":"
|
||||
# into an array. On non-forks, this has the effect of splitting into a single
|
||||
# element array given ":" shouldn't appear in the head - it'll just be the
|
||||
# branch name. Based on the results, we clone.
|
||||
BRANCH_ARRAY=(${head//:/ })
|
||||
TRY_ORG=$deforg
|
||||
TRY_BRANCH=${BRANCH_ARRAY[0]}
|
||||
if [[ "$head" == *":"* ]]; then
|
||||
# ... but only match that fork if it's a real fork
|
||||
if [ "${BRANCH_ARRAY[0]}" != "$PR_ORG" ]; then
|
||||
TRY_ORG=${BRANCH_ARRAY[0]}
|
||||
fi
|
||||
TRY_BRANCH=${BRANCH_ARRAY[1]}
|
||||
fi
|
||||
clone "$TRY_ORG" "$defrepo" "$TRY_BRANCH"
|
||||
|
||||
exit 1
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
|
||||
deforg="$1"
|
||||
defrepo="$2"
|
||||
defbranch="$3"
|
||||
|
||||
[ -z "$defbranch" ] && defbranch="develop"
|
||||
|
||||
rm -r "$defrepo" || true
|
||||
|
||||
clone() {
|
||||
org=$1
|
||||
repo=$2
|
||||
branch=$3
|
||||
if [ -n "$branch" ]
|
||||
then
|
||||
echo "Trying to use $org/$repo#$branch"
|
||||
# Disable auth prompts: https://serverfault.com/a/665959
|
||||
GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Try the PR author's branch in case it exists on the deps as well.
|
||||
# If BUILDKITE_BRANCH is set, it will contain either:
|
||||
# * "branch" when the author's branch and target branch are in the same repo
|
||||
# * "author:branch" when the author's branch is in their fork
|
||||
# We can split on `:` into an array to check.
|
||||
BUILDKITE_BRANCH_ARRAY=(${BUILDKITE_BRANCH//:/ })
|
||||
if [[ "${#BUILDKITE_BRANCH_ARRAY[@]}" == "1" ]]; then
|
||||
clone $deforg $defrepo $BUILDKITE_BRANCH
|
||||
elif [[ "${#BUILDKITE_BRANCH_ARRAY[@]}" == "2" ]]; then
|
||||
clone ${BUILDKITE_BRANCH_ARRAY[0]} $defrepo ${BUILDKITE_BRANCH_ARRAY[1]}
|
||||
fi
|
||||
# Try the target branch of the push or PR.
|
||||
clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
|
||||
# Try the current branch from Jenkins.
|
||||
clone $deforg $defrepo `"echo $GIT_BRANCH" | sed -e 's/^origin\///'`
|
||||
# Use the default branch as the last resort.
|
||||
clone $deforg $defrepo $defbranch
|
||||
@@ -8,7 +8,7 @@
|
||||
"module": "node16",
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"lib": ["es2020"]
|
||||
"lib": ["es2021"]
|
||||
},
|
||||
"include": ["../src/@types", "./**/*.ts"]
|
||||
}
|
||||
|
||||
13
src/@types/global.d.ts
vendored
13
src/@types/global.d.ts
vendored
@@ -7,30 +7,23 @@ 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";
|
||||
|
||||
// global type extensions need to use var for whatever reason
|
||||
/* eslint-disable no-var */
|
||||
declare global {
|
||||
type IConfigOptions = Record<string, any>;
|
||||
|
||||
var mainWindow: BrowserWindow | null;
|
||||
var appQuitting: boolean;
|
||||
var appLocalization: AppLocalization;
|
||||
var launcher: AutoLaunch;
|
||||
var vectorConfig: Record<string, any>;
|
||||
var vectorConfig: IConfigOptions;
|
||||
var trayConfig: {
|
||||
// eslint-disable-next-line camelcase
|
||||
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 */
|
||||
|
||||
26
src/build-config.ts
Normal file
26
src/build-config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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 path, { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { type JsonObject, loadJsonFile } from "./utils.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
interface BuildConfig {
|
||||
appId: string;
|
||||
protocol: string;
|
||||
}
|
||||
|
||||
export function readBuildConfig(): BuildConfig {
|
||||
const packageJson = loadJsonFile(path.join(__dirname, "..", "package.json")) as JsonObject;
|
||||
return {
|
||||
appId: (packageJson["electron_appId"] as string) || "im.riot.app",
|
||||
protocol: (packageJson["electron_protocol"] as string) || "io.element.desktop",
|
||||
};
|
||||
}
|
||||
@@ -10,13 +10,12 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
// Squirrel on windows starts the app with various flags as hooks to tell us when we've been installed/uninstalled etc.
|
||||
import "./squirrelhooks.js";
|
||||
import { app, BrowserWindow, Menu, autoUpdater, protocol, dialog, type Input, type Event, session } from "electron";
|
||||
import { app, BrowserWindow, Menu, autoUpdater, dialog, type Input, type Event, session, protocol } from "electron";
|
||||
// eslint-disable-next-line n/file-extension-in-import
|
||||
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,15 +24,17 @@ 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";
|
||||
import { getProfileFromDeeplink, protocolInit } from "./protocol.js";
|
||||
import ProtocolHandler 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";
|
||||
import { readBuildConfig } from "./build-config.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -44,7 +45,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 +64,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
|
||||
@@ -66,16 +73,19 @@ function isRealUserDataDir(d: string): boolean {
|
||||
return fs.existsSync(path.join(d, "IndexedDB"));
|
||||
}
|
||||
|
||||
const buildConfig = readBuildConfig();
|
||||
const protocolHandler = new ProtocolHandler(buildConfig.protocol);
|
||||
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
let userDataPath: string;
|
||||
|
||||
const userDataPathInProtocol = getProfileFromDeeplink(argv["_"]);
|
||||
const userDataPathInProtocol = protocolHandler.getProfileFromDeeplink(argv["_"]);
|
||||
if (userDataPathInProtocol) {
|
||||
userDataPath = 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 +146,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 +165,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 +177,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 +277,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 +286,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 +302,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) {
|
||||
@@ -318,7 +315,7 @@ if (!gotLock) {
|
||||
}
|
||||
|
||||
// do this after we know we are the primary instance of the app
|
||||
protocolInit();
|
||||
protocolHandler.initialise(userDataPath);
|
||||
|
||||
// Register the scheme the app is served from as 'standard'
|
||||
// which allows things like relative URLs and IndexedDB to
|
||||
@@ -350,13 +347,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 +437,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 +447,19 @@ 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,
|
||||
});
|
||||
|
||||
// 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 +470,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,
|
||||
@@ -475,6 +484,17 @@ app.on("ready", async () => {
|
||||
webgl: true,
|
||||
},
|
||||
});
|
||||
|
||||
global.mainWindow.setContentProtection(store.get("enableContentProtection"));
|
||||
|
||||
try {
|
||||
console.debug("Ensuring storage is ready");
|
||||
if (!(await store.prepareSafeStorage(global.mainWindow.webContents.session))) return;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
app.exit(1);
|
||||
}
|
||||
|
||||
void global.mainWindow.loadURL("vector://vector/webapp/");
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
@@ -483,10 +503,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 +520,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 +582,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 +618,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(buildConfig.appId);
|
||||
|
||||
@@ -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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "Váš systém má podporovanou klíčenku, ale šifrování není k dispozici.",
|
||||
"backend_no_encryption_detail": "Electron zjistil, že pro vaši klíčenku %(backend)s není k dispozici šifrování. Ujistěte se, že máte nainstalovanou klíčenku. Pokud ji máte, restartujte počítač a zkuste to znovu. Volitelně můžete povolit %(brand)s použít slabší formu šifrování.",
|
||||
"backend_no_encryption_title": "Bez podpory šifrování",
|
||||
"unsupported_keyring": "Váš systém má nepodporovanou klíčenku, což znamená, že databázi nelze otevřít.",
|
||||
"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",
|
||||
"unsupported_keyring_use_basic_text": "Používat slabší šifrování",
|
||||
"unsupported_keyring_use_plaintext": "Nepoužívat žádné šifrová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,9 +20,11 @@
|
||||
},
|
||||
"common": {
|
||||
"about": "Ynghylch",
|
||||
"brand_help": "%(brand)s Cymorth",
|
||||
"brand_help": "Cymorth %(brand)s",
|
||||
"help": "Cymorth",
|
||||
"preferences": "Dewisiadau"
|
||||
"no": "Na",
|
||||
"preferences": "Dewisiadau",
|
||||
"yes": "Iawn"
|
||||
},
|
||||
"confirm_quit": "Ydych chi'n siŵr eich bod am roi'r gorau iddi?",
|
||||
"edit_menu": {
|
||||
@@ -40,18 +42,31 @@
|
||||
"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"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Clirio data ac ail-lwytho?",
|
||||
"backend_changed_detail": "Methu cael mynediad at y gyfrinach o allweddi'r system, mae'n ymddangos ei fod wedi newid.",
|
||||
"backend_changed_title": "Methwyd llwytho'r gronfa ddata",
|
||||
"backend_no_encryption": "Mae gan eich system cylch allwedd sy'n cael ei gefnogi ond nid yw amgryptio ar gael.",
|
||||
"backend_no_encryption_detail": "Mae Electron wedi canfod nad yw amgryptio ar gael ar eich cylch allweddi %(backend)s. Gwnewch yn siŵr bod y cylch allweddi wedi'i osod. Os oes y cylch allweddi wedi'i osod, ail gychwynnwch a cheisiwch eto. Yn ddewisol, gallwch ganiatáu i %(brand)s ddefnyddio ffurf wannach o amgryptio.",
|
||||
"backend_no_encryption_title": "Dim cefnogaeth amgryptio",
|
||||
"unsupported_keyring": "Mae gan eich system allweddell nad yw'n cael ei chefnogi sy'n golygu nad oes modd agor y gronfa ddata.",
|
||||
"unsupported_keyring_detail": "Heb ganfod allweddell Electron gefn. Gallwch geisio ffurfweddu'r gefn â llaw trwy gychwyn %(brand)s gyda dadl llinell orchymyn, gweithrediad untro. Gweler %(link)s.",
|
||||
"unsupported_keyring_title": "System heb ei chefnogi"
|
||||
}
|
||||
},
|
||||
"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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "Ihr System verfügt über einen unterstützten Keyring, aber die Verschlüsselung ist nicht verfügbar.",
|
||||
"backend_no_encryption_detail": "Electron hat festgestellt, dass Verschlüsselung in Ihrem Keyring %(backend)s nicht verfügbar ist. Bitte stellen Sie sicher, dass Sie den Keyringinstalliert haben. Wenn Sie den Keyring installiert haben, starten Sie ihn bitte neu und versuchen Sie es erneut. Optional können Sie die Verwendung einer schwächeren Form der Verschlüsselung zulassen %(brand)s.",
|
||||
"backend_no_encryption_title": "Keine Verschlüsselungsunterstützung",
|
||||
"unsupported_keyring": "Der Schlüsselbund ihres Systems wird nicht unterstützt, wodurch die Datenbank nicht geöffnet werden kann.",
|
||||
"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",
|
||||
"unsupported_keyring_use_basic_text": "Schwächere Verschlüsselung verwenden",
|
||||
"unsupported_keyring_use_plaintext": "Verwenden Sie keine Verschlüsselung"
|
||||
}
|
||||
},
|
||||
"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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "Your system has a supported keyring but encryption is not available.",
|
||||
"backend_no_encryption_detail": "Electron has detected that encryption is not available on your keyring %(backend)s. Please ensure that you have the keyring installed. If you do have the keyring installed, please reboot and try again. Optionally, you can allow %(brand)s to use a weaker form of encryption.",
|
||||
"backend_no_encryption_title": "No encryption support",
|
||||
"unsupported_keyring": "Your system has an unsupported keyring meaning the database cannot be opened.",
|
||||
"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",
|
||||
"unsupported_keyring_use_basic_text": "Use weaker encryption",
|
||||
"unsupported_keyring_use_plaintext": "Use no encryption"
|
||||
}
|
||||
},
|
||||
"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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "Sinu süsteem kasutab toetatud võtmerõngast, kuid krüptimist pole saadaval.",
|
||||
"backend_no_encryption_detail": "Electron on tuvastanud, et krüptimine pole sinu %(backend)s võtmerõnga jaoks saadaval. Palun kontrolli, et võtmerõngas oleks korrektselt paigaldatud. Kui sul on võtmerõngas paigaldatud, siis palun taaskäivita ta ja proovi uuesti. Lisavõimalusena saad lubada, et %(brand)s kasutab nõrgemat krüptimislahendust.",
|
||||
"backend_no_encryption_title": "Krüptimise tugi puudub",
|
||||
"unsupported_keyring": "Sinu süsteemis on kasutusel mittetoetatud võtmerõnga versioon ning see tähendab, et andmebaasi ei saa avada.",
|
||||
"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",
|
||||
"unsupported_keyring_use_basic_text": "Kasuta nõrgemat krüptimist",
|
||||
"unsupported_keyring_use_plaintext": "Ära üldse kasuta krüptimist"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Näita tavasuuruses",
|
||||
"toggle_developer_tools": "Arendaja töövahendid sisse/välja",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Tietoa",
|
||||
"brand_help": "%(brand)s-tuki",
|
||||
"help": "Ohje",
|
||||
"preferences": "Valinnat"
|
||||
"no": "Ei",
|
||||
"preferences": "Valinnat",
|
||||
"yes": "Kyllä"
|
||||
},
|
||||
"confirm_quit": "Haluatko varmasti poistua?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,12 @@
|
||||
"save_image_as_error_description": "Kuvan tallennus epäonnistui",
|
||||
"save_image_as_error_title": "Kuvan tallennus epäonnistui"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed_title": "Tietokannan lataaminen epäonnistui",
|
||||
"unsupported_keyring_title": "Järjestelmä ei ole tuettu"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Alkuperäinen koko",
|
||||
"toggle_developer_tools": "Näytä tai piilota kehittäjätyökalut",
|
||||
|
||||
@@ -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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "Votre système dispose d'un trousseau de clés compatible mais le chiffrement est indisponible.",
|
||||
"backend_no_encryption_detail": "Electron a détecté que le chiffrement n'est pas disponible pour le trousseau %(backend)s. Veuillez vérifier que votre trousseau est installé. Si c'est le cas, redémarrez et réessayez. Comme alternative, vous pouvez autoriser %(brand)s à utiliser une option de chiffrement moins sécurisée.",
|
||||
"backend_no_encryption_title": "Aucune prise en charge du chiffrement",
|
||||
"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_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",
|
||||
"unsupported_keyring_use_basic_text": "Utiliser un chiffrement plus faible",
|
||||
"unsupported_keyring_use_plaintext": "N'utilise pas de chiffrement"
|
||||
}
|
||||
},
|
||||
"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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "A rendszer támogatott kulcstartóval rendelkezik, de a titkosítás nem érhető el.",
|
||||
"backend_no_encryption_detail": "Az Electron észlelte, hogy a titkosítás nem érhető el a(z) %(backend)s kulcstartóján. Győződjön meg róla, hogy telepítve van-e a kulcstartó. Ha telepítve van, indítsa újra és próbálja újra. Esetleg engedélyezheti a gyengébb titkosítást az %(brand)s számára.",
|
||||
"backend_no_encryption_title": "Nincs titkosítási támogatás",
|
||||
"unsupported_keyring": "A rendszer nem támogatott kulcstartóval rendelkezik, ami azt jelenti, hogy az adatbázis nem nyitható meg.",
|
||||
"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",
|
||||
"unsupported_keyring_use_basic_text": "Gyengébb titkosítás használata",
|
||||
"unsupported_keyring_use_plaintext": "Ne használjon titkosítást"
|
||||
}
|
||||
},
|
||||
"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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "Sistem Anda memiliki keyring yang didukung tetapi enkripsi tidak tersedia.",
|
||||
"backend_no_encryption_detail": "Electron telah mendeteksi bahwa enkripsi tidak tersedia pada keyring %(backend)s Anda. Harap pastikan bahwa Anda telah memasang keyring. Jika Anda telah memasang keyring, silakan mulai ulang dan coba lagi. Secara opsional, Anda dapat mengizinkan %(brand)s untuk menggunakan bentuk enkripsi yang lebih lemah.",
|
||||
"backend_no_encryption_title": "Tidak ada dukungan enkripsi",
|
||||
"unsupported_keyring": "Sistem Anda memiliki keyring yang tidak didukung yang berarti basis data tidak dapat dibuka.",
|
||||
"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",
|
||||
"unsupported_keyring_use_basic_text": "Gunakan enkripsi yang lebih lemah",
|
||||
"unsupported_keyring_use_plaintext": "Jangan gunakan enkripsi"
|
||||
}
|
||||
},
|
||||
"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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "Systemet ditt har en støttet nøkkelring, men kryptering er ikke tilgjengelig.",
|
||||
"backend_no_encryption_detail": "Electron har oppdaget at kryptering ikke er tilgjengelig på nøkkelringen %(backend)s din. Forsikre deg om at du har nøkkelringen installert. Hvis du har nøkkelringen installert, vennligst start på nytt og prøv igjen. Eventuelt kan du tillate %(brand)s å bruke en svakere form for kryptering.",
|
||||
"backend_no_encryption_title": "Ingen støtte for kryptering",
|
||||
"unsupported_keyring": "Systemet ditt har en nøkkelring som ikke støttes, noe som betyr at databasen ikke kan åpnes.",
|
||||
"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",
|
||||
"unsupported_keyring_use_basic_text": "Bruk svakere kryptering",
|
||||
"unsupported_keyring_use_plaintext": "Ikke bruk kryptering"
|
||||
}
|
||||
},
|
||||
"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,16 @@
|
||||
"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",
|
||||
"unsupported_keyring": "System zawiera niewspierany keyring, nie można otworzyć bazy danych.",
|
||||
"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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "Seu sistema tem um cofre compatível, mas a criptografia não está disponível.",
|
||||
"backend_no_encryption_detail": "O Electron detetou que a encriptação não está disponível no seu cofre %(backend)s. Certifique-se de que tem o cofre instalado. Se tiver o cofre instalado, reinicie e tente novamente. Opcionalmente, você pode permitir que %(brand)s use uma forma mais fraca de criptografia.",
|
||||
"backend_no_encryption_title": "Sem suporte para criptografia",
|
||||
"unsupported_keyring": "Seu sistema possui um cofre não compatível, o que impede a abertura do banco de dados.",
|
||||
"unsupported_keyring_detail": "A detecção de cofre 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",
|
||||
"unsupported_keyring_use_basic_text": "Use criptografia mais fraca",
|
||||
"unsupported_keyring_use_plaintext": "Não usar criptografia"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Tamanho de Verdade",
|
||||
"toggle_developer_tools": "Ativar/Desativar Ferramentas de Desenvolvimento",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "О программе",
|
||||
"brand_help": "Помощь %(brand)s",
|
||||
"help": "Помощь",
|
||||
"preferences": "Предпочтения"
|
||||
"no": "Нет",
|
||||
"preferences": "Предпочтения",
|
||||
"yes": "Да"
|
||||
},
|
||||
"confirm_quit": "Вы уверены, что хотите выйти?",
|
||||
"edit_menu": {
|
||||
|
||||
@@ -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,21 @@
|
||||
"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",
|
||||
"backend_no_encryption": "Váš systém má podporovaný zväzok kľúčov, ale šifrovanie nie je k dispozícii.",
|
||||
"backend_no_encryption_detail": "Electron zistil, že šifrovanie nie je k dispozícii na vašom zväzku kľúčov %(backend)s. Uistite sa, že máte nainštalovaný zväzok kľúčov. Ak máte zväzok kľúčov nainštalovaný, reštartujte počítač a skúste to znova. Voliteľne môžete povoliť aplikácii %(brand)s používať slabšiu formu šifrovania.",
|
||||
"backend_no_encryption_title": "Žiadna podpora šifrovania",
|
||||
"unsupported_keyring": "Váš systém má nepodporovaný zväzok kľúčov, čo znamená, že databázu nemožno otvoriť.",
|
||||
"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ý",
|
||||
"unsupported_keyring_use_basic_text": "Použiť slabšie šifrovanie",
|
||||
"unsupported_keyring_use_plaintext": "Nepoužiť žiadne šifrovanie"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Aktuálna veľkosť",
|
||||
"toggle_developer_tools": "Nástroje pre vývojárov",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Om",
|
||||
"brand_help": "%(brand)s-hjälp",
|
||||
"help": "Hjälp",
|
||||
"preferences": "Inställningar"
|
||||
"no": "Nej",
|
||||
"preferences": "Inställningar",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"confirm_quit": "Är du säker att du vill avsluta?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,16 @@
|
||||
"save_image_as_error_description": "Bilden sparades inte",
|
||||
"save_image_as_error_title": "Misslyckades med att spara bilden"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Rensa data och ladda om?",
|
||||
"backend_changed_detail": "Kunde inte komma åt hemligheten från systemnyckelringen, det verkar ha ändrats.",
|
||||
"backend_changed_title": "Misslyckades att ladda databasen",
|
||||
"unsupported_keyring": "Ditt system har en nyckelring som inte stöds, vilket innebär att databasen inte kan öppnas.",
|
||||
"unsupported_keyring_detail": "Electrons nyckelringsdetektering hittade inte en backend som stöds. Du kan försöka konfigurera backend manuellt genom att starta %(brand)s med ett kommandoradsargument, en engångsåtgärd. Se %(link)s.",
|
||||
"unsupported_keyring_title": "Systemet stöds inte"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Verklig storlek",
|
||||
"toggle_developer_tools": "Växla utvecklarverktyg",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Про застосунок",
|
||||
"brand_help": "Довідка %(brand)s",
|
||||
"help": "Довідка",
|
||||
"preferences": "Параметри"
|
||||
"no": "Ні",
|
||||
"preferences": "Параметри",
|
||||
"yes": "Так"
|
||||
},
|
||||
"confirm_quit": "Ви впевнені, що хочете вийти?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,21 @@
|
||||
"save_image_as_error_description": "Не вдалося зберегти зображення",
|
||||
"save_image_as_error_title": "Не вдалося зберегти зображення"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Очистити дані та перезавантажити?",
|
||||
"backend_changed_detail": "Не вдається отримати доступ до таємного ключа з системного набору ключів, видається, він змінився.",
|
||||
"backend_changed_title": "Не вдалося завантажити базу даних",
|
||||
"backend_no_encryption": "Ваша система підтримує сховище ключів, але шифрування недоступне.",
|
||||
"backend_no_encryption_detail": "Electron виявив, що шифрування недоступне у вашому сховищі ключів %(backend)s. Переконайтеся, що у вас встановлено сховище ключів. Якщо так, тоді перезавантажте комп'ютер і повторіть спробу. За бажанням, ви можете дозволити %(brand)s використовувати слабшу форму шифрування.",
|
||||
"backend_no_encryption_title": "Шифрування не підтримується",
|
||||
"unsupported_keyring": "Ваша система має непідтримуваний набір ключів. Це означає, що базу даних неможливо відкрити.",
|
||||
"unsupported_keyring_detail": "Electron не виявив підтримуваного бекенда для роботи зі сховищем паролів. Ви можете вручну налаштувати його, запустивши %(brand)s з відповідним аргументом у командному рядку. Цю дію потрібно виконати лише один раз. Докладніше – %(link)s.",
|
||||
"unsupported_keyring_title": "Система не підтримується",
|
||||
"unsupported_keyring_use_basic_text": "Використовувати слабше шифрування",
|
||||
"unsupported_keyring_use_plaintext": "Не використовувати шифрування"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Фактичний розмір",
|
||||
"toggle_developer_tools": "Перемкнути інструменти розробника",
|
||||
|
||||
59
src/ipc.ts
59
src/ipc.ts
@@ -6,14 +6,11 @@ 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 +58,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;
|
||||
@@ -70,18 +68,6 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
case "getUpdateFeedUrl":
|
||||
ret = autoUpdater.getFeedURL();
|
||||
break;
|
||||
case "getSettingValue": {
|
||||
const [settingName] = args;
|
||||
const setting = Settings[settingName];
|
||||
ret = await setting.read();
|
||||
break;
|
||||
}
|
||||
case "setSettingValue": {
|
||||
const [settingName, value] = args;
|
||||
const setting = Settings[settingName];
|
||||
await setting.write(value);
|
||||
break;
|
||||
}
|
||||
case "setLanguage":
|
||||
global.appLocalization.setAppLocale(args[0]);
|
||||
break;
|
||||
@@ -96,9 +82,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
break;
|
||||
case "getConfig":
|
||||
ret = global.vectorConfig;
|
||||
break;
|
||||
|
||||
case "navigateBack":
|
||||
if (global.mainWindow.webContents.canGoBack()) {
|
||||
global.mainWindow.webContents.goBack();
|
||||
@@ -113,11 +97,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":
|
||||
@@ -135,18 +119,9 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
ret = global.mainWindow.webContents.session.availableSpellCheckerLanguages;
|
||||
break;
|
||||
|
||||
case "startSSOFlow":
|
||||
recordSSOSession(args[0]);
|
||||
break;
|
||||
|
||||
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 +132,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 +142,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 +161,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(global.mainWindow.webContents.session);
|
||||
return; // the app is about to stop, we don't need to reply to the IPC
|
||||
|
||||
case "breadcrumbs": {
|
||||
@@ -259,3 +228,5 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle("getConfig", () => global.vectorConfig);
|
||||
|
||||
@@ -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!);
|
||||
}
|
||||
|
||||
@@ -49,4 +49,25 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
}
|
||||
ipcRenderer.send(channel, ...args);
|
||||
},
|
||||
|
||||
async initialise(): Promise<{
|
||||
protocol: string;
|
||||
sessionId: string;
|
||||
config: IConfigOptions;
|
||||
supportedSettings: Record<string, boolean>;
|
||||
}> {
|
||||
const [{ protocol, sessionId }, config, supportedSettings] = await Promise.all([
|
||||
ipcRenderer.invoke("getProtocol"),
|
||||
ipcRenderer.invoke("getConfig"),
|
||||
ipcRenderer.invoke("getSupportedSettings"),
|
||||
]);
|
||||
return { protocol, sessionId, config, supportedSettings };
|
||||
},
|
||||
|
||||
async setSettingValue(settingName: string, value: any): Promise<void> {
|
||||
return ipcRenderer.invoke("setSettingValue", settingName, value);
|
||||
},
|
||||
async getSettingValue(settingName: string): Promise<any> {
|
||||
return ipcRenderer.invoke("getSettingValue", settingName);
|
||||
},
|
||||
});
|
||||
|
||||
199
src/protocol.ts
199
src/protocol.ts
@@ -6,119 +6,136 @@ 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 { app } from "electron";
|
||||
import { app, ipcMain } from "electron";
|
||||
import { URL } from "node:url";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
const LEGACY_PROTOCOL = "element";
|
||||
const PROTOCOL = "io.element.desktop";
|
||||
const SEARCH_PARAM = "element-desktop-ssoid";
|
||||
const STORE_FILE_NAME = "sso-sessions.json";
|
||||
|
||||
// we getPath userData before electron-main changes it, so this is the default value
|
||||
const storePath = path.join(app.getPath("userData"), STORE_FILE_NAME);
|
||||
|
||||
function processUrl(url: string): void {
|
||||
if (!global.mainWindow) return;
|
||||
export default class ProtocolHandler {
|
||||
private readonly store: Record<string, string> = {};
|
||||
private readonly sessionId: string;
|
||||
|
||||
const parsed = new URL(url);
|
||||
// sanity check: we only register for the one protocol, so we shouldn't
|
||||
// be getting anything else unless the user is forcing a URL to open
|
||||
// with the Element app.
|
||||
if (parsed.protocol !== `${PROTOCOL}:` && parsed.protocol !== `${LEGACY_PROTOCOL}:`) {
|
||||
console.log("Ignoring unexpected protocol: ", parsed.protocol);
|
||||
return;
|
||||
public constructor(private readonly protocol: string) {
|
||||
// get all args except `hidden` as it'd mean the app would not get focused
|
||||
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
|
||||
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
|
||||
const args = process.argv.slice(1).filter((arg) => arg !== "--hidden" && arg !== "-hidden");
|
||||
if (app.isPackaged) {
|
||||
app.setAsDefaultProtocolClient(this.protocol, process.execPath, args);
|
||||
app.setAsDefaultProtocolClient(LEGACY_PROTOCOL, process.execPath, args);
|
||||
} else if (process.platform === "win32") {
|
||||
// on Mac/Linux this would just cause the electron binary to open
|
||||
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
|
||||
app.setAsDefaultProtocolClient(this.protocol, process.execPath, [app.getAppPath(), ...args]);
|
||||
app.setAsDefaultProtocolClient(LEGACY_PROTOCOL, process.execPath, [app.getAppPath(), ...args]);
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
// Protocol handler for macos
|
||||
app.on("open-url", (ev, url) => {
|
||||
ev.preventDefault();
|
||||
this.processUrl(url);
|
||||
});
|
||||
} else {
|
||||
// Protocol handler for win32/Linux
|
||||
app.on("second-instance", (ev, commandLine) => {
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
if (!url.startsWith(`${this.protocol}:/`) && !url.startsWith(`${LEGACY_PROTOCOL}://`)) return;
|
||||
this.processUrl(url);
|
||||
});
|
||||
}
|
||||
|
||||
this.store = this.readStore();
|
||||
this.sessionId = randomUUID();
|
||||
|
||||
ipcMain.handle("getProtocol", this.onGetProtocol);
|
||||
}
|
||||
|
||||
const urlToLoad = new URL("vector://vector/webapp/");
|
||||
// ignore anything other than the search (used for SSO login redirect)
|
||||
// and the hash (for general element deep links)
|
||||
// There's no reason to allow anything else, particularly other paths,
|
||||
// since this would allow things like the internal jitsi wrapper to
|
||||
// be loaded, which would get the app stuck on that page and generally
|
||||
// be a bit strange and confusing.
|
||||
urlToLoad.search = parsed.search;
|
||||
urlToLoad.hash = parsed.hash;
|
||||
private readonly onGetProtocol = (): { protocol: string; sessionId: string } => {
|
||||
return {
|
||||
protocol: this.protocol,
|
||||
sessionId: this.sessionId,
|
||||
};
|
||||
};
|
||||
|
||||
console.log("Opening URL: ", urlToLoad.href);
|
||||
void global.mainWindow.loadURL(urlToLoad.href);
|
||||
}
|
||||
private processUrl(url: string): void {
|
||||
if (!global.mainWindow) return;
|
||||
|
||||
function readStore(): Record<string, string> {
|
||||
try {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch {
|
||||
return {};
|
||||
const parsed = new URL(url);
|
||||
// sanity check: we only register for the one protocol, so we shouldn't
|
||||
// be getting anything else unless the user is forcing a URL to open
|
||||
// with the Element app.
|
||||
if (parsed.protocol !== `${this.protocol}:` && parsed.protocol !== `${LEGACY_PROTOCOL}:`) {
|
||||
console.log("Ignoring unexpected protocol: ", parsed.protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
const urlToLoad = new URL("vector://vector/webapp/");
|
||||
// ignore anything other than the search (used for SSO login redirect)
|
||||
// and the hash (for general element deep links)
|
||||
// There's no reason to allow anything else, particularly other paths,
|
||||
// since this would allow things like the internal jitsi wrapper to
|
||||
// be loaded, which would get the app stuck on that page and generally
|
||||
// be a bit strange and confusing.
|
||||
urlToLoad.search = parsed.search;
|
||||
urlToLoad.hash = parsed.hash;
|
||||
|
||||
console.log("Opening URL: ", urlToLoad.href);
|
||||
void global.mainWindow.loadURL(urlToLoad.href);
|
||||
}
|
||||
}
|
||||
|
||||
function writeStore(data: Record<string, string>): void {
|
||||
fs.writeFileSync(storePath, JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function recordSSOSession(sessionID: string): void {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const store = readStore();
|
||||
for (const key in store) {
|
||||
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
|
||||
if (store[key] === userDataPath) {
|
||||
delete store[key];
|
||||
break;
|
||||
private readStore(): Record<string, string> {
|
||||
try {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
store[sessionID] = userDataPath;
|
||||
writeStore(store);
|
||||
}
|
||||
|
||||
export function getProfileFromDeeplink(args: string[]): string | undefined {
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
const deeplinkUrl = args.find((arg) => arg.startsWith(`${PROTOCOL}://`) || arg.startsWith(`${LEGACY_PROTOCOL}://`));
|
||||
if (deeplinkUrl?.includes(SEARCH_PARAM)) {
|
||||
const parsedUrl = new URL(deeplinkUrl);
|
||||
if (parsedUrl.protocol === `${PROTOCOL}:` || parsedUrl.protocol === `${LEGACY_PROTOCOL}:`) {
|
||||
const store = readStore();
|
||||
let ssoID = parsedUrl.searchParams.get(SEARCH_PARAM);
|
||||
if (!ssoID) {
|
||||
// In OIDC, we must shuttle the value in the `state` param rather than `element-desktop-ssoid`
|
||||
// We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ`
|
||||
ssoID = parsedUrl.searchParams.get("state")!.split(`:${SEARCH_PARAM}:`)[1];
|
||||
private writeStore(): void {
|
||||
fs.writeFileSync(storePath, JSON.stringify(this.store));
|
||||
}
|
||||
|
||||
public initialise(userDataPath: string): void {
|
||||
for (const key in this.store) {
|
||||
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
|
||||
if (this.store[key] === userDataPath) {
|
||||
delete this.store[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.store[this.sessionId] = userDataPath;
|
||||
this.writeStore();
|
||||
}
|
||||
|
||||
public getProfileFromDeeplink(args: string[]): string | undefined {
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
const deeplinkUrl = args.find(
|
||||
(arg) => arg.startsWith(`${this.protocol}:/`) || arg.startsWith(`${LEGACY_PROTOCOL}://`),
|
||||
);
|
||||
if (deeplinkUrl?.includes(SEARCH_PARAM)) {
|
||||
const parsedUrl = new URL(deeplinkUrl);
|
||||
if (parsedUrl.protocol === `${this.protocol}:` || parsedUrl.protocol === `${LEGACY_PROTOCOL}:`) {
|
||||
const store = this.readStore();
|
||||
let sessionId = parsedUrl.searchParams.get(SEARCH_PARAM);
|
||||
if (!sessionId) {
|
||||
// In OIDC, we must shuttle the value in the `state` param rather than `element-desktop-ssoid`
|
||||
// We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ`
|
||||
sessionId = parsedUrl.searchParams.get("state")!.split(`:${SEARCH_PARAM}:`)[1];
|
||||
}
|
||||
console.log("Forwarding to profile: ", store[sessionId]);
|
||||
return store[sessionId];
|
||||
}
|
||||
console.log("Forwarding to profile: ", store[ssoID]);
|
||||
return store[ssoID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function protocolInit(): void {
|
||||
// get all args except `hidden` as it'd mean the app would not get focused
|
||||
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
|
||||
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
|
||||
const args = process.argv.slice(1).filter((arg) => arg !== "--hidden" && arg !== "-hidden");
|
||||
if (app.isPackaged) {
|
||||
app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, args);
|
||||
app.setAsDefaultProtocolClient(LEGACY_PROTOCOL, process.execPath, args);
|
||||
} else if (process.platform === "win32") {
|
||||
// on Mac/Linux this would just cause the electron binary to open
|
||||
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
|
||||
app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [app.getAppPath(), ...args]);
|
||||
app.setAsDefaultProtocolClient(LEGACY_PROTOCOL, process.execPath, [app.getAppPath(), ...args]);
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
// Protocol handler for macos
|
||||
app.on("open-url", function (ev, url) {
|
||||
ev.preventDefault();
|
||||
processUrl(url);
|
||||
});
|
||||
} else {
|
||||
// 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;
|
||||
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 !== undefined) {
|
||||
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 });
|
||||
|
||||
@@ -5,14 +5,18 @@ 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 { ipcMain } from "electron";
|
||||
|
||||
import * as tray from "./tray.js";
|
||||
import Store from "./store.js";
|
||||
|
||||
interface Setting {
|
||||
read(): Promise<any>;
|
||||
write(value: any): Promise<void>;
|
||||
supported?(): boolean; // if undefined, the setting is always supported
|
||||
}
|
||||
|
||||
export const Settings: Record<string, Setting> = {
|
||||
const Settings: Record<string, Setting> = {
|
||||
"Electron.autoLaunch": {
|
||||
async read(): Promise<any> {
|
||||
return global.launcher.isEnabled();
|
||||
@@ -27,25 +31,31 @@ 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": {
|
||||
// not supported on macOS
|
||||
// This isn't relevant on Mac as Menu bars don't live in the app window
|
||||
supported(): boolean {
|
||||
return process.platform !== "darwin";
|
||||
},
|
||||
async read(): Promise<any> {
|
||||
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);
|
||||
},
|
||||
},
|
||||
"Electron.showTrayIcon": {
|
||||
// not supported on macOS
|
||||
// Things other than Mac support tray icons
|
||||
supported(): boolean {
|
||||
return process.platform !== "darwin";
|
||||
},
|
||||
async read(): Promise<any> {
|
||||
return tray.hasTray();
|
||||
},
|
||||
@@ -56,15 +66,54 @@ 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);
|
||||
},
|
||||
},
|
||||
"Electron.enableContentProtection": {
|
||||
// Unsupported on Linux https://www.electronjs.org/docs/latest/api/browser-window#winsetcontentprotectionenable-macos-windows
|
||||
// Broken on macOS https://github.com/electron/electron/issues/19880
|
||||
supported(): boolean {
|
||||
return process.platform === "win32";
|
||||
},
|
||||
async read(): Promise<any> {
|
||||
return Store.instance?.get("enableContentProtection");
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.mainWindow?.setContentProtection(value);
|
||||
Store.instance?.set("enableContentProtection", value);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
ipcMain.handle("getSupportedSettings", async () => {
|
||||
const supportedSettings: Record<string, boolean> = {};
|
||||
for (const [key, setting] of Object.entries(Settings)) {
|
||||
supportedSettings[key] = setting.supported?.() ?? true;
|
||||
}
|
||||
return supportedSettings;
|
||||
});
|
||||
ipcMain.handle("setSettingValue", async (_ev, settingName: string, value: any) => {
|
||||
const setting = Settings[settingName];
|
||||
if (!setting) {
|
||||
throw new Error(`Unknown setting: ${settingName}`);
|
||||
}
|
||||
console.debug(`Writing setting value for: ${settingName} = ${value}`);
|
||||
await setting.write(value);
|
||||
});
|
||||
ipcMain.handle("getSettingValue", async (_ev, settingName: string) => {
|
||||
const setting = Settings[settingName];
|
||||
if (!setting) {
|
||||
throw new Error(`Unknown setting: ${settingName}`);
|
||||
}
|
||||
const value = await setting.read();
|
||||
console.debug(`Reading setting value for: ${settingName} = ${value}`);
|
||||
return value;
|
||||
});
|
||||
|
||||
538
src/store.ts
Normal file
538
src/store.ts
Normal file
@@ -0,0 +1,538 @@
|
||||
/*
|
||||
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, type Session } from "electron";
|
||||
|
||||
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";
|
||||
/**
|
||||
* The "unknown" backend is not a valid backend, so we exclude it from the type.
|
||||
*/
|
||||
type SaneSafeStorageBackend = Exclude<SafeStorageBackend, "unknown">;
|
||||
|
||||
/**
|
||||
* 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<SaneSafeStorageBackend, string>, "system" | "plaintext"> = {
|
||||
basic_text: "basic",
|
||||
gnome_libsecret: "gnome-libsecret",
|
||||
kwallet: "kwallet",
|
||||
kwallet5: "kwallet5",
|
||||
kwallet6: "kwallet6",
|
||||
};
|
||||
|
||||
function relaunchApp(): void {
|
||||
console.info("Relaunching app...");
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data and relaunch the app.
|
||||
*/
|
||||
export async function clearDataAndRelaunch(electronSession: Session): Promise<void> {
|
||||
Store.instance?.clear();
|
||||
electronSession.flushStorageData();
|
||||
await electronSession.clearStorageData();
|
||||
relaunchApp();
|
||||
}
|
||||
|
||||
interface StoreData {
|
||||
warnBeforeExit: boolean;
|
||||
minimizeToTray: boolean;
|
||||
spellCheckerEnabled: boolean;
|
||||
autoHideMenuBar: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration: boolean;
|
||||
enableContentProtection: 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 StorageWriter {
|
||||
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 | undefined {
|
||||
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 StorageWriter {
|
||||
public set(key: string, secret: string): void {
|
||||
this.store.set(this.getKey(key), safeStorage.encryptString(secret).toString("base64"));
|
||||
}
|
||||
|
||||
public get(key: string): string | undefined {
|
||||
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 undefined;
|
||||
}
|
||||
}
|
||||
|
||||
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") || store.get("safeStorageBackendMigrate"))
|
||||
) {
|
||||
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?: StorageWriter;
|
||||
|
||||
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,
|
||||
},
|
||||
enableContentProtection: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
safeStorage: {
|
||||
type: "object",
|
||||
},
|
||||
safeStorageBackend: {
|
||||
type: "string",
|
||||
},
|
||||
safeStorageBackendOverride: {
|
||||
type: "boolean",
|
||||
},
|
||||
safeStorageBackendMigrate: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private safeStorageReadyPromise?: Promise<boolean>;
|
||||
public async safeStorageReady(): Promise<void> {
|
||||
if (!this.safeStorageReadyPromise) {
|
||||
throw new Error("prepareSafeStorage must be called before using storage methods");
|
||||
}
|
||||
await this.safeStorageReadyPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the backend to a sane value (exclude `unknown`), respect forcePlaintext mode,
|
||||
* and ensure that if an encrypted backend is picked that encryption is available, falling back to plaintext if not.
|
||||
* @param forcePlaintext - whether to force plaintext mode
|
||||
* @private
|
||||
*/
|
||||
private chooseBackend(forcePlaintext: boolean): SaneSafeStorageBackend {
|
||||
if (forcePlaintext) {
|
||||
return "plaintext";
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
// The following enables plain text encryption if the backend used is basic_text.
|
||||
// It has no significance for any other backend.
|
||||
// We do this early so that in case we end up using the basic_text backend (either because that's the only one available
|
||||
// or as a fallback when the configured backend lacks encryption support), encryption is already turned on.
|
||||
safeStorage.setUsePlainTextEncryption(true);
|
||||
|
||||
// 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 selectedBackend = safeStorage.getSelectedStorageBackend();
|
||||
|
||||
if (selectedBackend === "unknown" || !safeStorage.isEncryptionAvailable()) {
|
||||
return "plaintext";
|
||||
}
|
||||
return selectedBackend;
|
||||
}
|
||||
|
||||
return safeStorage.isEncryptionAvailable() ? "system" : "plaintext";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* This will relaunch the app in some cases, in which case it will return false and the caller should abort startup.
|
||||
*
|
||||
* @param electronSession - The Electron session to use for storage (will be used to clear storage if necessary).
|
||||
* @returns true if safeStorage was initialised successfully or false if the app will be relaunched
|
||||
*/
|
||||
public async prepareSafeStorage(electronSession: Session): Promise<boolean> {
|
||||
this.safeStorageReadyPromise = this.reallyPrepareSafeStorage(electronSession);
|
||||
return this.safeStorageReadyPromise;
|
||||
}
|
||||
|
||||
private async reallyPrepareSafeStorage(electronSession: Session): Promise<boolean> {
|
||||
await app.whenReady();
|
||||
|
||||
// The backend the existing data is written with if any
|
||||
let existingSafeStorageBackend = this.get("safeStorageBackend");
|
||||
// The backend and encryption status of the currently loaded backend
|
||||
const backend = this.chooseBackend(this.mode === Mode.ForcePlaintext);
|
||||
|
||||
// Handle migrations
|
||||
if (existingSafeStorageBackend) {
|
||||
if (existingSafeStorageBackend === "basic_text" && backend !== "plaintext" && backend !== "basic_text") {
|
||||
this.prepareMigrateBasicTextToPlaintext();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.get("safeStorageBackendMigrate") && backend === "basic_text") {
|
||||
this.migrateBasicTextToPlaintext();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (existingSafeStorageBackend === "plaintext" && backend !== "plaintext") {
|
||||
this.migratePlaintextToEncrypted();
|
||||
// Ensure we update existingSafeStorageBackend so we don't fall into the "backend changed" clause below
|
||||
existingSafeStorageBackend = this.get("safeStorageBackend");
|
||||
}
|
||||
}
|
||||
|
||||
if (!existingSafeStorageBackend) {
|
||||
// First launch of the app or first launch since the update
|
||||
if (this.mode === Mode.Encrypted && (backend === "plaintext" || backend === "basic_text")) {
|
||||
// Ask the user for consent to use a degraded mode
|
||||
await this.consultUserConsentDegradedMode(backend);
|
||||
}
|
||||
// Store the backend used for the safeStorage data so we can detect if it changes, and we know how the data is encoded
|
||||
this.recordSafeStorageBackend(backend);
|
||||
} else if (existingSafeStorageBackend !== backend) {
|
||||
// We already appear to have started using a backend other than the one that we picked, so
|
||||
// set the override flag and relaunch with the backend we were previously using, unless we
|
||||
// already have the override flag, in which case we must assume the previous backend is no
|
||||
// longer usable, in which case we should fall into the next block and warn the user we can't
|
||||
// migrate.
|
||||
console.warn(`safeStorage backend changed from ${existingSafeStorageBackend} to ${backend}`);
|
||||
|
||||
if (existingSafeStorageBackend in safeStorageBackendMap && !this.get("safeStorageBackendOverride")) {
|
||||
this.set("safeStorageBackendOverride", true);
|
||||
relaunchApp();
|
||||
return false;
|
||||
} else {
|
||||
// This will either relaunch the app or throw an execption
|
||||
await this.consultUserBackendChangedUnableToMigrate(electronSession);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
console.info(`Using storage mode '${this.mode}' with backend '${backend}'`);
|
||||
if (backend !== "plaintext") {
|
||||
this.secrets = new SafeStorageWriter(this);
|
||||
} else {
|
||||
this.secrets = new StorageWriter(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async consultUserBackendChangedUnableToMigrate(electronSession: Session): Promise<void> {
|
||||
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");
|
||||
}
|
||||
return clearDataAndRelaunch(electronSession);
|
||||
}
|
||||
|
||||
private async consultUserConsentDegradedMode(backend: "plaintext" | "basic_text"): Promise<void> {
|
||||
if (backend === "plaintext") {
|
||||
// Sometimes we may have a working backend that for some reason does not support encryption at the moment.
|
||||
// This may be because electron reported an incorrect backend or because of some known issues with the keyring itself.
|
||||
// Or the environment specified `--storage-mode=force-plaintext`.
|
||||
// In any case, when this happens, we give the user an option to use a weaker form of encryption.
|
||||
const { response } = await dialog.showMessageBox({
|
||||
title: _t("store|error|backend_no_encryption_title"),
|
||||
message: _t("store|error|backend_no_encryption"),
|
||||
detail: _t("store|error|backend_no_encryption_detail", {
|
||||
backend: safeStorage.getSelectedStorageBackend(),
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
}),
|
||||
type: "error",
|
||||
buttons: [_t("action|cancel"), _t("store|error|unsupported_keyring_use_plaintext")],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
});
|
||||
if (response === 0) {
|
||||
throw new Error("isEncryptionAvailable=false and user rejected plaintext");
|
||||
}
|
||||
} else {
|
||||
// Electron did not identify a compatible encrypted backend, ask user for consent to degraded mode
|
||||
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_use_basic_text")],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
});
|
||||
if (response === 0) {
|
||||
throw new Error("safeStorage backend basic_text and user rejected it");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
* 1. We restart the app in safeStorageBackendMigrate mode
|
||||
* 2. Now that we are in the mode which our data is written in we decrypt the data, write it back in plaintext
|
||||
* & restart back in default backend mode,
|
||||
* 3. Finally, we load the plaintext data & encrypt it.
|
||||
*/
|
||||
private prepareMigrateBasicTextToPlaintext(): void {
|
||||
console.info(`Starting safeStorage migration to ${safeStorage.getSelectedStorageBackend()}`);
|
||||
this.set("safeStorageBackendMigrate", true);
|
||||
relaunchApp();
|
||||
}
|
||||
private migrateBasicTextToPlaintext(): void {
|
||||
const secrets = new SafeStorageWriter(this);
|
||||
console.info("Performing safeStorage migration");
|
||||
const data = this.get("safeStorage");
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
this.set(secrets.getKey(key), secrets.get(key));
|
||||
}
|
||||
this.recordSafeStorageBackend("plaintext");
|
||||
}
|
||||
this.delete("safeStorageBackendMigrate");
|
||||
relaunchApp();
|
||||
}
|
||||
private migratePlaintextToEncrypted(): void {
|
||||
const secrets = new SafeStorageWriter(this);
|
||||
const selectedSafeStorageBackend = safeStorage.getSelectedStorageBackend();
|
||||
console.info(`Finishing safeStorage migration to ${selectedSafeStorageBackend}`);
|
||||
const data = this.get("safeStorage");
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
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 | undefined> {
|
||||
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 | undefined> {
|
||||
return (
|
||||
(await keytar.getPassword(KEYTAR_SERVICE, key)) ??
|
||||
(await keytar.getPassword(LEGACY_KEYTAR_SERVICE, key)) ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
13
src/utils.ts
13
src/utils.ts
@@ -23,10 +23,10 @@ export async function randomArray(size: number): Promise<string> {
|
||||
|
||||
type JsonValue = null | string | number;
|
||||
type JsonArray = Array<JsonValue | JsonObject | JsonArray>;
|
||||
interface JsonObject {
|
||||
export 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