mirror of
https://github.com/CringeStudios/element-desktop.git
synced 2025-01-18 23:44:59 +01:00
Merge branch 'develop' of github.com:vector-im/element-desktop into SimonBrandner/feat/spell-disable
Conflicts: src/electron-main.ts
This commit is contained in:
commit
86e372b913
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<!-- Thanks for submitting a PR! Please ensure the following requirements are met in order for us to review your PR -->
|
||||
|
||||
## Checklist
|
||||
|
||||
* [ ] Ensure your code works with manual testing
|
||||
* [ ] Linter and other CI checks pass
|
||||
* [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/vector-im/element-desktop/blob/develop/CONTRIBUTING.md))
|
||||
|
||||
<!--
|
||||
If you would like to specify text for the changelog entry other than your PR title, add the following:
|
||||
|
||||
Notes: Add super cool feature
|
||||
-->
|
60
.github/workflows/build.yaml
vendored
60
.github/workflows/build.yaml
vendored
@ -7,7 +7,32 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
fetch:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Fetch Element Web
|
||||
run: yarn run fetch --noverify --cfgdir element.io/nightly
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
retention-days: 1
|
||||
path: |
|
||||
webapp.asar
|
||||
package.json
|
||||
|
||||
windows:
|
||||
needs: fetch
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@ -21,6 +46,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@ -69,7 +98,15 @@ jobs:
|
||||
- name: Build App
|
||||
run: "yarn build --publish never -w ${{ matrix.build-args }}"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: win-${{ matrix.arch }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
||||
linux:
|
||||
needs: fetch
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@ -81,6 +118,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@ -113,12 +154,24 @@ jobs:
|
||||
- name: Build App
|
||||
run: "yarn build --publish never"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux-sqlcipher-${{ matrix.sqlcipher }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
||||
macos:
|
||||
needs: fetch
|
||||
name: macOS (universal)
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@ -145,3 +198,10 @@ jobs:
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build:universal --publish never"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
111
CHANGELOG.md
111
CHANGELOG.md
@ -1,3 +1,114 @@
|
||||
Changes in [1.11.0](https://github.com/vector-im/element-desktop/releases/tag/v1.11.0) (2022-07-05)
|
||||
===================================================================================================
|
||||
|
||||
## 🚨 BREAKING CHANGES
|
||||
* Remove Piwik support ([\#8835](https://github.com/matrix-org/matrix-react-sdk/pull/8835)).
|
||||
|
||||
## ✨ Features
|
||||
* Support compilation on more Linux targets ([\#376](https://github.com/vector-im/element-desktop/pull/376)). Contributed by @jcgruenhage.
|
||||
* Document how to configure a custom `home.html`. ([\#21066](https://github.com/vector-im/element-web/pull/21066)). Contributed by @johannes-krude.
|
||||
* Move New Search Experience out of beta ([\#8859](https://github.com/matrix-org/matrix-react-sdk/pull/8859)). Contributed by @justjanne.
|
||||
* Switch video rooms to spotlight layout when in PiP mode ([\#8912](https://github.com/matrix-org/matrix-react-sdk/pull/8912)). Fixes vector-im/element-web#22574.
|
||||
* Live location sharing - render message deleted tile for redacted beacons ([\#8905](https://github.com/matrix-org/matrix-react-sdk/pull/8905)). Contributed by @kerryarchibald.
|
||||
* Improve view source dialog style ([\#8883](https://github.com/matrix-org/matrix-react-sdk/pull/8883)). Fixes vector-im/element-web#22636. Contributed by @luixxiul.
|
||||
* Improve integration manager dialog style ([\#8888](https://github.com/matrix-org/matrix-react-sdk/pull/8888)). Fixes vector-im/element-web#22642. Contributed by @luixxiul.
|
||||
* Implement MSC3827: Filtering of `/publicRooms` by room type ([\#8866](https://github.com/matrix-org/matrix-react-sdk/pull/8866)). Fixes vector-im/element-web#22578.
|
||||
* Show chat panel when opening a video room with unread messages ([\#8812](https://github.com/matrix-org/matrix-react-sdk/pull/8812)). Fixes vector-im/element-web#22527.
|
||||
* Live location share - forward latest location ([\#8860](https://github.com/matrix-org/matrix-react-sdk/pull/8860)). Contributed by @kerryarchibald.
|
||||
* Allow integration managers to validate user identity after opening ([\#8782](https://github.com/matrix-org/matrix-react-sdk/pull/8782)). Contributed by @Half-Shot.
|
||||
* Create a common header on right panel cards on BaseCard ([\#8808](https://github.com/matrix-org/matrix-react-sdk/pull/8808)). Contributed by @luixxiul.
|
||||
* Integrate searching public rooms and people into the new search experience ([\#8707](https://github.com/matrix-org/matrix-react-sdk/pull/8707)). Fixes vector-im/element-web#21354 and vector-im/element-web#19349. Contributed by @justjanne.
|
||||
* Bring back waveform for voice messages and retain seeking ([\#8843](https://github.com/matrix-org/matrix-react-sdk/pull/8843)). Fixes vector-im/element-web#21904.
|
||||
* Improve colors in settings ([\#7283](https://github.com/matrix-org/matrix-react-sdk/pull/7283)).
|
||||
* Keep draft in composer when a slash command syntax errors ([\#8811](https://github.com/matrix-org/matrix-react-sdk/pull/8811)). Fixes vector-im/element-web#22384.
|
||||
* Release video rooms as a beta feature ([\#8431](https://github.com/matrix-org/matrix-react-sdk/pull/8431)).
|
||||
* Clarify logout key backup warning dialog. Contributed by @notramo. ([\#8741](https://github.com/matrix-org/matrix-react-sdk/pull/8741)). Fixes vector-im/element-web#15565. Contributed by @MadLittleMods.
|
||||
* Slightly improve the look of the `Message edits` dialog ([\#8763](https://github.com/matrix-org/matrix-react-sdk/pull/8763)). Fixes vector-im/element-web#22410.
|
||||
* Add support for MD / HTML in room topics ([\#8215](https://github.com/matrix-org/matrix-react-sdk/pull/8215)). Fixes vector-im/element-web#5180. Contributed by @Johennes.
|
||||
* Live location share - link to timeline tile from share warning ([\#8752](https://github.com/matrix-org/matrix-react-sdk/pull/8752)). Contributed by @kerryarchibald.
|
||||
* Improve composer visiblity ([\#8578](https://github.com/matrix-org/matrix-react-sdk/pull/8578)). Fixes vector-im/element-web#22072 and vector-im/element-web#17362.
|
||||
* Makes the avatar of the user menu non-draggable ([\#8765](https://github.com/matrix-org/matrix-react-sdk/pull/8765)). Contributed by @luixxiul.
|
||||
* Improve widget buttons behaviour and layout ([\#8734](https://github.com/matrix-org/matrix-react-sdk/pull/8734)).
|
||||
* Use AccessibleButton for 'Reset All' link button on SetupEncryptionBody ([\#8730](https://github.com/matrix-org/matrix-react-sdk/pull/8730)). Contributed by @luixxiul.
|
||||
* Adjust message timestamp position on TimelineCard in non-bubble layouts ([\#8745](https://github.com/matrix-org/matrix-react-sdk/pull/8745)). Fixes vector-im/element-web#22426. Contributed by @luixxiul.
|
||||
* Use AccessibleButton for 'In reply to' link button on ReplyChain ([\#8726](https://github.com/matrix-org/matrix-react-sdk/pull/8726)). Fixes vector-im/element-web#22407. Contributed by @luixxiul.
|
||||
* Live location share - enable reply and react to tiles ([\#8721](https://github.com/matrix-org/matrix-react-sdk/pull/8721)). Contributed by @kerryarchibald.
|
||||
* Change dash to em dash issues fixed ([\#8455](https://github.com/matrix-org/matrix-react-sdk/pull/8455)). Fixes vector-im/element-web#21895. Contributed by @goelesha.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
* Upgrade to Electron 19 ([\#372](https://github.com/vector-im/element-desktop/pull/372)). Fixes vector-im/element-web#21147.
|
||||
* Reduce video rooms log spam ([\#22665](https://github.com/vector-im/element-web/pull/22665)).
|
||||
* Connect to Jitsi unmuted by default ([\#22660](https://github.com/vector-im/element-web/pull/22660)). Fixes vector-im/element-web#22637.
|
||||
* Work around a Jitsi bug with display name encoding ([\#22525](https://github.com/vector-im/element-web/pull/22525)). Fixes vector-im/element-web#22521.
|
||||
* Make invite dialogue fixed height ([\#8945](https://github.com/matrix-org/matrix-react-sdk/pull/8945)).
|
||||
* Correct issue with tab order in new search experience ([\#8919](https://github.com/matrix-org/matrix-react-sdk/pull/8919)). Fixes vector-im/element-web#22670. Contributed by @justjanne.
|
||||
* Clicking location replies now redirects to the replied event instead of opening the map ([\#8918](https://github.com/matrix-org/matrix-react-sdk/pull/8918)). Fixes vector-im/element-web#22667.
|
||||
* Keep clicks on pills within the app ([\#8917](https://github.com/matrix-org/matrix-react-sdk/pull/8917)). Fixes vector-im/element-web#22653.
|
||||
* Don't overlap tile bubbles with timestamps in modern layout ([\#8908](https://github.com/matrix-org/matrix-react-sdk/pull/8908)). Fixes vector-im/element-web#22425.
|
||||
* Connect to Jitsi unmuted by default ([\#8909](https://github.com/matrix-org/matrix-react-sdk/pull/8909)).
|
||||
* Maximize width value of display name on TimelineCard with IRC/modern layout ([\#8904](https://github.com/matrix-org/matrix-react-sdk/pull/8904)). Fixes vector-im/element-web#22651. Contributed by @luixxiul.
|
||||
* Align the avatar and the display name on TimelineCard ([\#8900](https://github.com/matrix-org/matrix-react-sdk/pull/8900)). Contributed by @luixxiul.
|
||||
* Remove inline margin from reactions row on IRC layout ([\#8891](https://github.com/matrix-org/matrix-react-sdk/pull/8891)). Fixes vector-im/element-web#22644. Contributed by @luixxiul.
|
||||
* Align "From a thread" on search result panel on IRC layout ([\#8892](https://github.com/matrix-org/matrix-react-sdk/pull/8892)). Fixes vector-im/element-web#22645. Contributed by @luixxiul.
|
||||
* Display description of E2E advanced panel as subsection text ([\#8889](https://github.com/matrix-org/matrix-react-sdk/pull/8889)). Contributed by @luixxiul.
|
||||
* Remove inline end margin from images on file panel ([\#8886](https://github.com/matrix-org/matrix-react-sdk/pull/8886)). Fixes vector-im/element-web#22640. Contributed by @luixxiul.
|
||||
* Disable option to `Quote` when we don't have sufficient permissions ([\#8893](https://github.com/matrix-org/matrix-react-sdk/pull/8893)). Fixes vector-im/element-web#22643.
|
||||
* Add padding to font scaling loader for message bubble layout ([\#8875](https://github.com/matrix-org/matrix-react-sdk/pull/8875)). Fixes vector-im/element-web#22626. Contributed by @luixxiul.
|
||||
* Set 100% max-width to display name on reply tiles ([\#8867](https://github.com/matrix-org/matrix-react-sdk/pull/8867)). Fixes vector-im/element-web#22615. Contributed by @luixxiul.
|
||||
* Fix alignment of pill letter ([\#8874](https://github.com/matrix-org/matrix-react-sdk/pull/8874)). Fixes vector-im/element-web#22622. Contributed by @luixxiul.
|
||||
* Move the beta pill to the right side and display the pill on video room only ([\#8873](https://github.com/matrix-org/matrix-react-sdk/pull/8873)). Fixes vector-im/element-web#22619 and vector-im/element-web#22620. Contributed by @luixxiul.
|
||||
* Stop using absolute property to place beta pill on RoomPreviewCard ([\#8872](https://github.com/matrix-org/matrix-react-sdk/pull/8872)). Fixes vector-im/element-web#22617. Contributed by @luixxiul.
|
||||
* Make the pill text single line ([\#8744](https://github.com/matrix-org/matrix-react-sdk/pull/8744)). Fixes vector-im/element-web#22427. Contributed by @luixxiul.
|
||||
* Hide overflow of public room description on spotlight dialog result ([\#8870](https://github.com/matrix-org/matrix-react-sdk/pull/8870)). Contributed by @luixxiul.
|
||||
* Fix position of message action bar on the info tile on TimelineCard in message bubble layout ([\#8865](https://github.com/matrix-org/matrix-react-sdk/pull/8865)). Fixes vector-im/element-web#22614. Contributed by @luixxiul.
|
||||
* Remove inline start margin from display name on reply tiles on TimelineCard ([\#8864](https://github.com/matrix-org/matrix-react-sdk/pull/8864)). Fixes vector-im/element-web#22613. Contributed by @luixxiul.
|
||||
* Improve homeserver dropdown dialog styling ([\#8850](https://github.com/matrix-org/matrix-react-sdk/pull/8850)). Fixes vector-im/element-web#22552. Contributed by @justjanne.
|
||||
* Fix crash when drawing blurHash for portrait videos PSB-139 ([\#8855](https://github.com/matrix-org/matrix-react-sdk/pull/8855)). Fixes vector-im/element-web#22597. Contributed by @andybalaam.
|
||||
* Fix grid blowout on pinned event tiles ([\#8816](https://github.com/matrix-org/matrix-react-sdk/pull/8816)). Fixes vector-im/element-web#22543. Contributed by @luixxiul.
|
||||
* Fix temporary sync errors if there's weird settings stored in account data ([\#8857](https://github.com/matrix-org/matrix-react-sdk/pull/8857)).
|
||||
* Fix reactions row overflow and gap between reactions ([\#8813](https://github.com/matrix-org/matrix-react-sdk/pull/8813)). Fixes vector-im/element-web#22093. Contributed by @luixxiul.
|
||||
* Fix issues with the Create new room button in Spotlight ([\#8851](https://github.com/matrix-org/matrix-react-sdk/pull/8851)). Contributed by @justjanne.
|
||||
* Remove margin from E2E icon between avatar and hidden event ([\#8584](https://github.com/matrix-org/matrix-react-sdk/pull/8584)). Fixes vector-im/element-web#22186. Contributed by @luixxiul.
|
||||
* Fix waveform on a message bubble ([\#8852](https://github.com/matrix-org/matrix-react-sdk/pull/8852)). Contributed by @luixxiul.
|
||||
* Location sharing maps are now loaded after reconnection ([\#8848](https://github.com/matrix-org/matrix-react-sdk/pull/8848)). Fixes vector-im/element-web#20993.
|
||||
* Update the avatar mask so it doesn’t cut off spaces’ avatars anymore ([\#8849](https://github.com/matrix-org/matrix-react-sdk/pull/8849)). Contributed by @justjanne.
|
||||
* Add a bit of safety around timestamp handling for threads ([\#8845](https://github.com/matrix-org/matrix-react-sdk/pull/8845)).
|
||||
* Remove top margin from event tile on a narrow viewport ([\#8814](https://github.com/matrix-org/matrix-react-sdk/pull/8814)). Contributed by @luixxiul.
|
||||
* Fix keyboard shortcuts on settings tab being wrapped ([\#8825](https://github.com/matrix-org/matrix-react-sdk/pull/8825)). Fixes vector-im/element-web#22547. Contributed by @luixxiul.
|
||||
* Add try-catch around blurhash loading ([\#8830](https://github.com/matrix-org/matrix-react-sdk/pull/8830)).
|
||||
* Prevent new composer from overflowing from non-breakable text ([\#8829](https://github.com/matrix-org/matrix-react-sdk/pull/8829)). Fixes vector-im/element-web#22507. Contributed by @justjanne.
|
||||
* Use common subheading on sidebar user settings tab ([\#8823](https://github.com/matrix-org/matrix-react-sdk/pull/8823)). Contributed by @luixxiul.
|
||||
* Fix clickable area of advanced toggle on appearance user settings tab ([\#8820](https://github.com/matrix-org/matrix-react-sdk/pull/8820)). Fixes vector-im/element-web#22546. Contributed by @luixxiul.
|
||||
* Disable redacting reactions if we don't have sufficient permissions ([\#8767](https://github.com/matrix-org/matrix-react-sdk/pull/8767)). Fixes vector-im/element-web#22262.
|
||||
* Update the live timeline when the JS SDK resets it ([\#8806](https://github.com/matrix-org/matrix-react-sdk/pull/8806)). Fixes vector-im/element-web#22421.
|
||||
* Fix flex blowout on image reply ([\#8809](https://github.com/matrix-org/matrix-react-sdk/pull/8809)). Fixes vector-im/element-web#22509 and vector-im/element-web#22510. Contributed by @luixxiul.
|
||||
* Enable background color on hover for chat panel and thread panel ([\#8644](https://github.com/matrix-org/matrix-react-sdk/pull/8644)). Fixes vector-im/element-web#22273. Contributed by @luixxiul.
|
||||
* Fix #20026: send read marker as soon as we change it ([\#8802](https://github.com/matrix-org/matrix-react-sdk/pull/8802)). Fixes vector-im/element-web#20026. Contributed by @andybalaam.
|
||||
* Allow AppTiles to shrink as much as necessary ([\#8805](https://github.com/matrix-org/matrix-react-sdk/pull/8805)). Fixes vector-im/element-web#22499.
|
||||
* Make widgets in video rooms immutable again ([\#8803](https://github.com/matrix-org/matrix-react-sdk/pull/8803)). Fixes vector-im/element-web#22497.
|
||||
* Use MessageActionBar style declarations on pinned message card ([\#8757](https://github.com/matrix-org/matrix-react-sdk/pull/8757)). Fixes vector-im/element-web#22444. Contributed by @luixxiul.
|
||||
* Expire video member events after 1 hour ([\#8776](https://github.com/matrix-org/matrix-react-sdk/pull/8776)).
|
||||
* Name lists on invite dialog ([\#8046](https://github.com/matrix-org/matrix-react-sdk/pull/8046)). Fixes vector-im/element-web#21400 and vector-im/element-web#19463. Contributed by @luixxiul.
|
||||
* Live location share - show loading UI for beacons with start timestamp in the future ([\#8775](https://github.com/matrix-org/matrix-react-sdk/pull/8775)). Fixes vector-im/element-web#22437. Contributed by @kerryarchibald.
|
||||
* Fix scroll jump issue with the composer ([\#8788](https://github.com/matrix-org/matrix-react-sdk/pull/8788)). Fixes vector-im/element-web#22464.
|
||||
* Fix the incorrect nesting of download button on MessageActionBar ([\#8785](https://github.com/matrix-org/matrix-react-sdk/pull/8785)). Contributed by @luixxiul.
|
||||
* Revert link color change in composer ([\#8784](https://github.com/matrix-org/matrix-react-sdk/pull/8784)). Fixes vector-im/element-web#22468.
|
||||
* Fix 'Logout' inline link on the splash screen ([\#8770](https://github.com/matrix-org/matrix-react-sdk/pull/8770)). Fixes vector-im/element-web#22449. Contributed by @luixxiul.
|
||||
* Fix disappearing widget poput button when changing the widget layout ([\#8754](https://github.com/matrix-org/matrix-react-sdk/pull/8754)).
|
||||
* Reduce gutter with the new read receipt UI ([\#8736](https://github.com/matrix-org/matrix-react-sdk/pull/8736)). Fixes vector-im/element-web#21890.
|
||||
* Add ellipsis effect to hidden beacon status ([\#8755](https://github.com/matrix-org/matrix-react-sdk/pull/8755)). Fixes vector-im/element-web#22441. Contributed by @luixxiul.
|
||||
* Make the pill on the basic message composer compatible with display name in RTL languages ([\#8758](https://github.com/matrix-org/matrix-react-sdk/pull/8758)). Fixes vector-im/element-web#22445. Contributed by @luixxiul.
|
||||
* Prevent the banner text from being selected, replacing the spacing values with the variable ([\#8756](https://github.com/matrix-org/matrix-react-sdk/pull/8756)). Fixes vector-im/element-web#22442. Contributed by @luixxiul.
|
||||
* Ensure the first device on a newly-registered account gets cross-signed properly ([\#8750](https://github.com/matrix-org/matrix-react-sdk/pull/8750)). Fixes vector-im/element-web#21977. Contributed by @duxovni.
|
||||
* Hide live location option in threads composer ([\#8746](https://github.com/matrix-org/matrix-react-sdk/pull/8746)). Fixes vector-im/element-web#22424. Contributed by @kerryarchibald.
|
||||
* Make sure MessageTimestamp is not hidden by EventTile_line on TimelineCard ([\#8748](https://github.com/matrix-org/matrix-react-sdk/pull/8748)). Contributed by @luixxiul.
|
||||
* Make PiP motion smoother and react to window resizes correctly ([\#8747](https://github.com/matrix-org/matrix-react-sdk/pull/8747)). Fixes vector-im/element-web#22292.
|
||||
* Prevent Invite and DevTools dialogs from being cut off ([\#8646](https://github.com/matrix-org/matrix-react-sdk/pull/8646)). Fixes vector-im/element-web#20911 and undefined/matrix-react-sdk#8165. Contributed by @justjanne.
|
||||
* Squish event bubble tiles less ([\#8740](https://github.com/matrix-org/matrix-react-sdk/pull/8740)).
|
||||
* Use random widget IDs for video rooms ([\#8739](https://github.com/matrix-org/matrix-react-sdk/pull/8739)). Fixes vector-im/element-web#22417.
|
||||
* Fix read avatars overflow from the right chat panel with a maximized widget on bubble message layout ([\#8470](https://github.com/matrix-org/matrix-react-sdk/pull/8470)). Contributed by @luixxiul.
|
||||
* Fix `CallView` crash ([\#8735](https://github.com/matrix-org/matrix-react-sdk/pull/8735)). Fixes vector-im/element-web#22394.
|
||||
|
||||
Changes in [1.10.15](https://github.com/vector-im/element-desktop/releases/tag/v1.10.15) (2022-06-14)
|
||||
=====================================================================================================
|
||||
|
||||
|
16
package.json
16
package.json
@ -2,7 +2,7 @@
|
||||
"name": "element-desktop",
|
||||
"productName": "Element",
|
||||
"main": "lib/electron-main.js",
|
||||
"version": "1.10.15",
|
||||
"version": "1.11.0",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "Element",
|
||||
"repository": {
|
||||
@ -42,7 +42,7 @@
|
||||
"dependencies": {
|
||||
"auto-launch": "^5.0.5",
|
||||
"counterpart": "^0.18.6",
|
||||
"electron-store": "^6.0.1",
|
||||
"electron-store": "^8.0.2",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"minimist": "^1.2.6",
|
||||
"png-to-ico": "^2.1.1",
|
||||
@ -74,7 +74,7 @@
|
||||
"find-npm-prefix": "^1.0.2",
|
||||
"fs-extra": "^8.1.0",
|
||||
"glob": "^7.1.6",
|
||||
"matrix-web-i18n": "^1.2.0",
|
||||
"matrix-web-i18n": "^1.3.0",
|
||||
"mkdirp": "^1.0.3",
|
||||
"needle": "^2.5.0",
|
||||
"node-pre-gyp": "^0.15.0",
|
||||
@ -88,6 +88,9 @@
|
||||
"matrix-seshat": "^2.3.3",
|
||||
"keytar": "^7.9.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "16.11.38"
|
||||
},
|
||||
"build": {
|
||||
"appId": "im.riot.app",
|
||||
"asarUnpack": "**/*.node",
|
||||
@ -119,11 +122,12 @@
|
||||
"darkModeSupport": true
|
||||
},
|
||||
"win": {
|
||||
"target": {
|
||||
"target": "squirrel"
|
||||
},
|
||||
"target": ["squirrel", "msi"],
|
||||
"sign": "scripts/electron_winSign"
|
||||
},
|
||||
"msi": {
|
||||
"perMachine": true
|
||||
},
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
|
22
src/@types/global.d.ts
vendored
22
src/@types/global.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
Copyright 2021 - 2022 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.
|
||||
@ -15,12 +15,32 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
import Store from "electron-store";
|
||||
import AutoLaunch from "auto-launch";
|
||||
|
||||
import { AppLocalization } from "../language-helper";
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
mainWindow: BrowserWindow;
|
||||
appQuitting: boolean;
|
||||
appLocalization: AppLocalization;
|
||||
launcher: AutoLaunch;
|
||||
vectorConfig: Record<string, any>;
|
||||
trayConfig: {
|
||||
// eslint-disable-next-line camelcase
|
||||
icon_path: string;
|
||||
brand: string;
|
||||
};
|
||||
store: Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration?: boolean;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,90 +21,42 @@ limitations under the License.
|
||||
import "./squirrelhooks";
|
||||
import {
|
||||
app,
|
||||
ipcMain,
|
||||
powerSaveBlocker,
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
autoUpdater,
|
||||
protocol,
|
||||
dialog,
|
||||
desktopCapturer,
|
||||
} from "electron";
|
||||
import AutoLaunch from "auto-launch";
|
||||
import path from "path";
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
import Store from 'electron-store';
|
||||
import fs, { promises as afs } from "fs";
|
||||
import crypto from "crypto";
|
||||
import { URL } from "url";
|
||||
import minimist from "minimist";
|
||||
|
||||
import type * as Keytar from "keytar"; // Hak dependency type
|
||||
import type {
|
||||
Seshat as SeshatType,
|
||||
SeshatRecovery as SeshatRecoveryType,
|
||||
ReindexError as ReindexErrorType,
|
||||
} from "matrix-seshat"; // Hak dependency type
|
||||
import "./ipc";
|
||||
import "./keytar";
|
||||
import "./seshat";
|
||||
import "./settings";
|
||||
import * as tray from "./tray";
|
||||
import { buildMenuTemplate } from './vectormenu';
|
||||
import webContentsHandler from './webcontents-handler';
|
||||
import * as updater from './updater';
|
||||
import { getProfileFromDeeplink, protocolInit, recordSSOSession } from './protocol';
|
||||
import { getProfileFromDeeplink, protocolInit } from './protocol';
|
||||
import { _t, AppLocalization } from './language-helper';
|
||||
import Input = Electron.Input;
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
|
||||
const argv = minimist(process.argv, {
|
||||
alias: { help: "h" },
|
||||
});
|
||||
|
||||
let keytar: typeof Keytar;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
keytar = require('keytar');
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
console.log("Keytar isn't installed; secure key storage is disabled.");
|
||||
} else {
|
||||
console.warn("Keytar unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
let seshatSupported = false;
|
||||
let Seshat: typeof SeshatType;
|
||||
let SeshatRecovery: typeof SeshatRecoveryType;
|
||||
let ReindexError: typeof ReindexErrorType;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const seshatModule = require('matrix-seshat');
|
||||
Seshat = seshatModule.Seshat;
|
||||
SeshatRecovery = seshatModule.SeshatRecovery;
|
||||
ReindexError = seshatModule.ReindexError;
|
||||
seshatSupported = true;
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
console.log("Seshat isn't installed, event indexing is disabled.");
|
||||
} else {
|
||||
console.warn("Seshat unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Things we need throughout the file but need to be created
|
||||
// async to are initialised in setupGlobals()
|
||||
let asarPath: string;
|
||||
let resPath: string;
|
||||
let iconPath: string;
|
||||
|
||||
let vectorConfig: Record<string, any>;
|
||||
let trayConfig: {
|
||||
// eslint-disable-next-line camelcase
|
||||
icon_path: string;
|
||||
brand: string;
|
||||
};
|
||||
let launcher: AutoLaunch;
|
||||
let appLocalization: AppLocalization;
|
||||
|
||||
if (argv["help"]) {
|
||||
console.log("Options:");
|
||||
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
||||
@ -199,13 +151,13 @@ async function setupGlobals(): Promise<void> {
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
vectorConfig = require(asarPath + 'config.json');
|
||||
global.vectorConfig = require(asarPath + 'config.json');
|
||||
} catch (e) {
|
||||
// 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
|
||||
// file or invalid json, so node is just very unhelpful.
|
||||
// Continue with the defaults (ie. an empty config)
|
||||
vectorConfig = {};
|
||||
global.vectorConfig = {};
|
||||
}
|
||||
|
||||
try {
|
||||
@ -219,19 +171,19 @@ async function setupGlobals(): Promise<void> {
|
||||
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'];
|
||||
if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) {
|
||||
// Rip out all the homeserver options from the vector config
|
||||
vectorConfig = Object.keys(vectorConfig)
|
||||
global.vectorConfig = Object.keys(global.vectorConfig)
|
||||
.filter(k => !homeserverProps.includes(k))
|
||||
.reduce((obj, key) => {obj[key] = vectorConfig[key]; return obj;}, {});
|
||||
.reduce((obj, key) => {obj[key] = global.vectorConfig[key]; return obj;}, {});
|
||||
}
|
||||
|
||||
vectorConfig = Object.assign(vectorConfig, localConfig);
|
||||
global.vectorConfig = Object.assign(global.vectorConfig, localConfig);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: `Your ${vectorConfig.brand || 'Element'} is misconfigured`,
|
||||
message: `Your custom ${vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||
`Please correct the problem and reopen ${vectorConfig.brand || 'Element'}.`,
|
||||
title: `Your ${global.vectorConfig.brand || 'Element'} is misconfigured`,
|
||||
message: `Your custom ${global.vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||
`Please correct the problem and reopen ${global.vectorConfig.brand || 'Element'}.`,
|
||||
detail: e.message || "",
|
||||
});
|
||||
}
|
||||
@ -243,14 +195,14 @@ async function setupGlobals(): Promise<void> {
|
||||
// It's important to call `path.join` so we don't end up with the packaged asar in the final path.
|
||||
const iconFile = `element.${process.platform === 'win32' ? 'ico' : 'png'}`;
|
||||
iconPath = path.join(resPath, "img", iconFile);
|
||||
trayConfig = {
|
||||
global.trayConfig = {
|
||||
icon_path: iconPath,
|
||||
brand: vectorConfig.brand || 'Element',
|
||||
brand: global.vectorConfig.brand || 'Element',
|
||||
};
|
||||
|
||||
// launcher
|
||||
launcher = new AutoLaunch({
|
||||
name: vectorConfig.brand || 'Element',
|
||||
global.launcher = new AutoLaunch({
|
||||
name: global.vectorConfig.brand || 'Element',
|
||||
isHidden: true,
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
@ -261,7 +213,7 @@ async function setupGlobals(): Promise<void> {
|
||||
async function moveAutoLauncher(): Promise<void> {
|
||||
// Look for an auto-launcher under 'Riot' and if we find one, port it's
|
||||
// enabled/disabled-ness over to the new 'Element' launcher
|
||||
if (!vectorConfig.brand || vectorConfig.brand === 'Element') {
|
||||
if (!global.vectorConfig.brand || global.vectorConfig.brand === 'Element') {
|
||||
const oldLauncher = new AutoLaunch({
|
||||
name: 'Riot',
|
||||
isHidden: true,
|
||||
@ -272,24 +224,13 @@ async function moveAutoLauncher(): Promise<void> {
|
||||
const wasEnabled = await oldLauncher.isEnabled();
|
||||
if (wasEnabled) {
|
||||
await oldLauncher.disable();
|
||||
await launcher.enable();
|
||||
await global.launcher.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
||||
const store = new Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration?: boolean;
|
||||
}>({ name: "electron-config" });
|
||||
global.store = new Store({ name: "electron-config" });
|
||||
|
||||
let eventIndex: SeshatType = null;
|
||||
|
||||
let mainWindow: BrowserWindow = null;
|
||||
global.appQuitting = false;
|
||||
|
||||
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
@ -299,12 +240,12 @@ const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
];
|
||||
|
||||
const warnBeforeExit = (event: Event, input: Input): void => {
|
||||
const shouldWarnBeforeExit = store.get('warnBeforeExit', true);
|
||||
const shouldWarnBeforeExit = global.store.get('warnBeforeExit', true);
|
||||
const exitShortcutPressed =
|
||||
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed) {
|
||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(mainWindow, {
|
||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(global.mainWindow, {
|
||||
type: "question",
|
||||
buttons: [_t("Cancel"), _t("Close Element")],
|
||||
message: _t("Are you sure you want to quit?"),
|
||||
@ -318,25 +259,6 @@ const warnBeforeExit = (event: Event, input: Input): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteContents = async (p: string): Promise<void> => {
|
||||
for (const entry of await afs.readdir(p)) {
|
||||
const curPath = path.join(p, entry);
|
||||
await afs.unlink(curPath);
|
||||
}
|
||||
};
|
||||
|
||||
async function randomArray(size: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(size, (err, buf) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(buf.toString("base64").replace(/=+$/g, ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// handle uncaught errors otherwise it displays
|
||||
// stack traces in popup dialogs, which is terrible (which
|
||||
// it will do any time the auto update poke fails, and there's
|
||||
@ -347,512 +269,6 @@ process.on('uncaughtException', function(error: Error): void {
|
||||
console.log('Unhandled exception', error);
|
||||
});
|
||||
|
||||
let focusHandlerAttached = false;
|
||||
ipcMain.on('setBadgeCount', function(_ev: IpcMainEvent, count: number): void {
|
||||
if (process.platform !== 'win32') {
|
||||
// only set badgeCount on Mac/Linux, the docs say that only those platforms support it but turns out Electron
|
||||
// has some Windows support too, and in some Windows environments this leads to two badges rendering atop
|
||||
// each other. See https://github.com/vector-im/element-web/issues/16942
|
||||
app.badgeCount = count;
|
||||
}
|
||||
if (count === 0 && mainWindow) {
|
||||
mainWindow.flashFrame(false);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('loudNotification', function(): void {
|
||||
if (process.platform === 'win32' && mainWindow && !mainWindow.isFocused() && !focusHandlerAttached) {
|
||||
mainWindow.flashFrame(true);
|
||||
mainWindow.once('focus', () => {
|
||||
mainWindow.flashFrame(false);
|
||||
focusHandlerAttached = false;
|
||||
});
|
||||
focusHandlerAttached = true;
|
||||
}
|
||||
});
|
||||
|
||||
let powerSaveBlockerId: number = null;
|
||||
ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) {
|
||||
switch (payload.action) {
|
||||
case 'call_state':
|
||||
if (powerSaveBlockerId !== null && powerSaveBlocker.isStarted(powerSaveBlockerId)) {
|
||||
if (payload.state === 'ended') {
|
||||
powerSaveBlocker.stop(powerSaveBlockerId);
|
||||
powerSaveBlockerId = null;
|
||||
}
|
||||
} else {
|
||||
if (powerSaveBlockerId === null && payload.state === 'connected') {
|
||||
powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
interface Setting {
|
||||
read(): Promise<any>;
|
||||
write(value: any): Promise<void>;
|
||||
}
|
||||
|
||||
const settings: Record<string, Setting> = {
|
||||
"Electron.autoLaunch": {
|
||||
async read(): Promise<any> {
|
||||
return launcher.isEnabled();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
if (value) {
|
||||
return launcher.enable();
|
||||
} else {
|
||||
return launcher.disable();
|
||||
}
|
||||
},
|
||||
},
|
||||
"Electron.warnBeforeExit": {
|
||||
async read(): Promise<any> {
|
||||
return store.get("warnBeforeExit", true);
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
store.set("warnBeforeExit", value);
|
||||
},
|
||||
},
|
||||
"Electron.alwaysShowMenuBar": { // not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return !global.mainWindow.autoHideMenuBar;
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
store.set('autoHideMenuBar', !value);
|
||||
global.mainWindow.autoHideMenuBar = !value;
|
||||
global.mainWindow.setMenuBarVisibility(value);
|
||||
},
|
||||
},
|
||||
"Electron.showTrayIcon": { // not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return tray.hasTray();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
if (value) {
|
||||
// Create trayIcon icon
|
||||
tray.create(trayConfig);
|
||||
} else {
|
||||
tray.destroy();
|
||||
}
|
||||
store.set('minimizeToTray', value);
|
||||
},
|
||||
},
|
||||
"Electron.enableHardwareAcceleration": {
|
||||
async read(): Promise<any> {
|
||||
return !store.get('disableHardwareAcceleration', false);
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
store.set('disableHardwareAcceleration', !value);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
if (!mainWindow) return;
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
|
||||
switch (payload.name) {
|
||||
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':
|
||||
appLocalization.setAppLocale(args[0]);
|
||||
break;
|
||||
case 'getAppVersion':
|
||||
ret = app.getVersion();
|
||||
break;
|
||||
case 'focusWindow':
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
} else if (!mainWindow.isVisible()) {
|
||||
mainWindow.show();
|
||||
} else {
|
||||
mainWindow.focus();
|
||||
}
|
||||
break;
|
||||
case 'getConfig':
|
||||
ret = vectorConfig;
|
||||
break;
|
||||
case 'navigateBack':
|
||||
if (mainWindow.webContents.canGoBack()) {
|
||||
mainWindow.webContents.goBack();
|
||||
}
|
||||
break;
|
||||
case 'navigateForward':
|
||||
if (mainWindow.webContents.canGoForward()) {
|
||||
mainWindow.webContents.goForward();
|
||||
}
|
||||
break;
|
||||
case 'setSpellCheckEnabled':
|
||||
if (typeof args[0] !== 'boolean') return;
|
||||
|
||||
mainWindow.webContents.session.setSpellCheckerEnabled(args[0]);
|
||||
store.set("spellCheckerEnabled", args[0]);
|
||||
|
||||
break;
|
||||
|
||||
case 'getSpellCheckEnabled':
|
||||
ret = store.get("spellCheckerEnabled", true);
|
||||
break;
|
||||
|
||||
case 'setSpellCheckLanguages':
|
||||
try {
|
||||
mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
|
||||
} catch (er) {
|
||||
console.log("There were problems setting the spellcheck languages", er);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getSpellCheckLanguages':
|
||||
ret = mainWindow.webContents.session.getSpellCheckerLanguages();
|
||||
break;
|
||||
|
||||
case 'getAvailableSpellCheckLanguages':
|
||||
ret = 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]}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// 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
|
||||
ret = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'createPickleKey':
|
||||
try {
|
||||
const pickleKey = await randomArray(32);
|
||||
await keytar.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
ret = pickleKey;
|
||||
} catch (e) {
|
||||
ret = null;
|
||||
}
|
||||
break;
|
||||
|
||||
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 (e) {}
|
||||
break;
|
||||
case 'getDesktopCapturerSources':
|
||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
thumbnailURL: source.thumbnail.toDataURL(),
|
||||
}));
|
||||
break;
|
||||
|
||||
default:
|
||||
mainWindow.webContents.send('ipcReply', {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
mainWindow.webContents.send('ipcReply', {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
return seshatDefaultPassphrase;
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
if (!mainWindow) return;
|
||||
|
||||
const sendError = (id, e) => {
|
||||
const error = {
|
||||
message: e.message,
|
||||
};
|
||||
|
||||
mainWindow.webContents.send('seshatReply', {
|
||||
id: id,
|
||||
error: error,
|
||||
});
|
||||
};
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
|
||||
switch (payload.name) {
|
||||
case 'supportsEventIndexing':
|
||||
ret = seshatSupported;
|
||||
break;
|
||||
|
||||
case 'initEventIndex':
|
||||
if (eventIndex === null) {
|
||||
const userId = args[0];
|
||||
const deviceId = args[1];
|
||||
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
||||
|
||||
const passphrase = await getOrCreatePassphrase(passphraseKey);
|
||||
|
||||
try {
|
||||
await afs.mkdir(eventStorePath, { recursive: true });
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} catch (e) {
|
||||
if (e instanceof ReindexError) {
|
||||
// If this is a reindex error, the index schema
|
||||
// changed. Try to open the database in recovery mode,
|
||||
// reindex the database and finally try to open the
|
||||
// database again.
|
||||
const recoveryIndex = new SeshatRecovery(eventStorePath, {
|
||||
passphrase,
|
||||
});
|
||||
|
||||
const userVersion = await recoveryIndex.getUserVersion();
|
||||
|
||||
// If our user version is 0 we'll delete the db
|
||||
// anyways so reindexing it is a waste of time.
|
||||
if (userVersion === 0) {
|
||||
await recoveryIndex.shutdown();
|
||||
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
}
|
||||
} else {
|
||||
await recoveryIndex.reindex();
|
||||
}
|
||||
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} else {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'closeEventIndex':
|
||||
if (eventIndex !== null) {
|
||||
const index = eventIndex;
|
||||
eventIndex = null;
|
||||
|
||||
try {
|
||||
await index.shutdown();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEventIndex':
|
||||
{
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'isEventIndexEmpty':
|
||||
if (eventIndex === null) ret = true;
|
||||
else ret = await eventIndex.isEmpty();
|
||||
break;
|
||||
|
||||
case 'isRoomIndexed':
|
||||
if (eventIndex === null) ret = false;
|
||||
else ret = await eventIndex.isRoomIndexed(args[0]);
|
||||
break;
|
||||
|
||||
case 'addEventToIndex':
|
||||
try {
|
||||
eventIndex.addEvent(args[0], args[1]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEvent':
|
||||
try {
|
||||
ret = await eventIndex.deleteEvent(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'commitLiveEvents':
|
||||
try {
|
||||
ret = await eventIndex.commit();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'searchEventIndex':
|
||||
try {
|
||||
ret = await eventIndex.search(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addHistoricEvents':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.addHistoricEvents(
|
||||
args[0], args[1], args[2]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getStats':
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.getStats();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'removeCrawlerCheckpoint':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.removeCrawlerCheckpoint(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addCrawlerCheckpoint':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.addCrawlerCheckpoint(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadFileEvents':
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.loadFileEvents(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadCheckpoints':
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.loadCheckpoints();
|
||||
} catch (e) {
|
||||
ret = [];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'setUserVersion':
|
||||
if (eventIndex === null) break;
|
||||
else {
|
||||
try {
|
||||
await eventIndex.setUserVersion(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getUserVersion':
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.getUserVersion();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
mainWindow.webContents.send('seshatReply', {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
mainWindow.webContents.send('seshatReply', {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
|
||||
app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
||||
if (!app.commandLine.hasSwitch('enable-features')) {
|
||||
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
|
||||
@ -896,7 +312,7 @@ app.enableSandbox();
|
||||
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
||||
|
||||
// Disable hardware acceleration if the setting has been set.
|
||||
if (store.get('disableHardwareAcceleration', false) === true) {
|
||||
if (global.store.get('disableHardwareAcceleration', false) === true) {
|
||||
console.log("Disabling hardware acceleration.");
|
||||
app.disableHardwareAcceleration();
|
||||
}
|
||||
@ -984,9 +400,9 @@ app.on('ready', async () => {
|
||||
|
||||
if (argv['no-update']) {
|
||||
console.log('Auto update disabled via command line flag "--no-update"');
|
||||
} else if (vectorConfig['update_base_url']) {
|
||||
console.log(`Starting auto update with base URL: ${vectorConfig['update_base_url']}`);
|
||||
updater.start(vectorConfig['update_base_url']);
|
||||
} else if (global.vectorConfig['update_base_url']) {
|
||||
console.log(`Starting auto update with base URL: ${global.vectorConfig['update_base_url']}`);
|
||||
updater.start(global.vectorConfig['update_base_url']);
|
||||
} else {
|
||||
console.log('No update_base_url is defined: auto update is disabled');
|
||||
}
|
||||
@ -998,13 +414,13 @@ app.on('ready', async () => {
|
||||
});
|
||||
|
||||
const preloadScript = path.normalize(`${__dirname}/preload.js`);
|
||||
mainWindow = global.mainWindow = new BrowserWindow({
|
||||
global.mainWindow = new BrowserWindow({
|
||||
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
backgroundColor: '#fff',
|
||||
|
||||
icon: iconPath,
|
||||
show: false,
|
||||
autoHideMenuBar: store.get('autoHideMenuBar', true),
|
||||
autoHideMenuBar: global.store.get('autoHideMenuBar', true),
|
||||
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
@ -1018,32 +434,32 @@ app.on('ready', async () => {
|
||||
webgl: true,
|
||||
},
|
||||
});
|
||||
mainWindow.loadURL('vector://vector/webapp/');
|
||||
global.mainWindow.loadURL('vector://vector/webapp/');
|
||||
|
||||
// Handle spellchecker
|
||||
// For some reason spellCheckerEnabled isn't persisted so we have to use the store here
|
||||
mainWindow.webContents.session.setSpellCheckerEnabled(store.get("spellCheckerEnabled", true));
|
||||
// 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));
|
||||
|
||||
// Create trayIcon icon
|
||||
if (store.get('minimizeToTray', true)) tray.create(trayConfig);
|
||||
if (global.store.get('minimizeToTray', true)) tray.create(global.trayConfig);
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindowState.manage(mainWindow);
|
||||
global.mainWindow.once('ready-to-show', () => {
|
||||
mainWindowState.manage(global.mainWindow);
|
||||
|
||||
if (!argv['hidden']) {
|
||||
mainWindow.show();
|
||||
global.mainWindow.show();
|
||||
} else {
|
||||
// hide here explicitly because window manage above sometimes shows it
|
||||
mainWindow.hide();
|
||||
global.mainWindow.hide();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
||||
global.mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = global.mainWindow = null;
|
||||
global.mainWindow.on('closed', () => {
|
||||
global.mainWindow = null;
|
||||
});
|
||||
mainWindow.on('close', async (e) => {
|
||||
global.mainWindow.on('close', async (e) => {
|
||||
// If we are not quitting and have a tray icon then minimize to tray
|
||||
if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
|
||||
// On Mac, closing the window just hides it
|
||||
@ -1051,12 +467,12 @@ app.on('ready', async () => {
|
||||
// behave, eg. Mail.app)
|
||||
e.preventDefault();
|
||||
|
||||
if (mainWindow.isFullScreen()) {
|
||||
mainWindow.once('leave-full-screen', () => mainWindow.hide());
|
||||
if (global.mainWindow.isFullScreen()) {
|
||||
global.mainWindow.once('leave-full-screen', () => global.mainWindow.hide());
|
||||
|
||||
mainWindow.setFullScreen(false);
|
||||
global.mainWindow.setFullScreen(false);
|
||||
} else {
|
||||
mainWindow.hide();
|
||||
global.mainWindow.hide();
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -1065,19 +481,19 @@ app.on('ready', async () => {
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Handle forward/backward mouse buttons in Windows
|
||||
mainWindow.on('app-command', (e, cmd) => {
|
||||
if (cmd === 'browser-backward' && mainWindow.webContents.canGoBack()) {
|
||||
mainWindow.webContents.goBack();
|
||||
} else if (cmd === 'browser-forward' && mainWindow.webContents.canGoForward()) {
|
||||
mainWindow.webContents.goForward();
|
||||
global.mainWindow.on('app-command', (e, cmd) => {
|
||||
if (cmd === 'browser-backward' && global.mainWindow.webContents.canGoBack()) {
|
||||
global.mainWindow.webContents.goBack();
|
||||
} else if (cmd === 'browser-forward' && global.mainWindow.webContents.canGoForward()) {
|
||||
global.mainWindow.webContents.goForward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
webContentsHandler(mainWindow.webContents);
|
||||
webContentsHandler(global.mainWindow.webContents);
|
||||
|
||||
appLocalization = new AppLocalization({
|
||||
store,
|
||||
global.appLocalization = new AppLocalization({
|
||||
store: global.store,
|
||||
components: [
|
||||
() => tray.initApplicationMenu(),
|
||||
() => Menu.setApplicationMenu(buildMenuTemplate()),
|
||||
@ -1090,14 +506,12 @@ app.on('window-all-closed', () => {
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
mainWindow.show();
|
||||
global.mainWindow.show();
|
||||
});
|
||||
|
||||
function beforeQuit(): void {
|
||||
global.appQuitting = true;
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('before-quit');
|
||||
}
|
||||
global.mainWindow?.webContents.send('before-quit');
|
||||
}
|
||||
|
||||
app.on('before-quit', beforeQuit);
|
||||
@ -1108,10 +522,10 @@ app.on('second-instance', (ev, commandLine, workingDirectory) => {
|
||||
if (commandLine.includes('--hidden')) return;
|
||||
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (mainWindow) {
|
||||
if (!mainWindow.isVisible()) mainWindow.show();
|
||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||
mainWindow.focus();
|
||||
if (global.mainWindow) {
|
||||
if (!global.mainWindow.isVisible()) global.mainWindow.show();
|
||||
if (global.mainWindow.isMinimized()) global.mainWindow.restore();
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
});
|
||||
|
||||
|
47
src/i18n/strings/bg.json
Normal file
47
src/i18n/strings/bg.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"Add to dictionary": "Добави към речника",
|
||||
"The image failed to save": "Изображението не успя да се запази",
|
||||
"Failed to save image": "Неуспешно запазване на изображението",
|
||||
"Save image as...": "Запази изображението като...",
|
||||
"Copy link address": "Копирай линка",
|
||||
"Copy image address": "Копирай адреса на изображението",
|
||||
"Copy email address": "Копирай имейл адрес",
|
||||
"Copy image": "Копирай изображение",
|
||||
"File": "Файл",
|
||||
"Bring All to Front": "Покажи всички най-отгоре",
|
||||
"Zoom": "Мащабирай",
|
||||
"Stop Speaking": "Спри да говориш",
|
||||
"Start Speaking": "Започни да говориш",
|
||||
"Speech": "Говор",
|
||||
"Unhide": "Покажи",
|
||||
"Hide Others": "Скрий Останалите",
|
||||
"Hide": "Скрий",
|
||||
"Services": "Услуги",
|
||||
"About": "Относно",
|
||||
"Element Help": "Помощ за Елемент",
|
||||
"Help": "Помощ",
|
||||
"Close": "Затвори",
|
||||
"Minimize": "Минимизирай",
|
||||
"Window": "Прозорец",
|
||||
"Toggle Developer Tools": "Превключи инструментите за разработчици",
|
||||
"Toggle Full Screen": "Превключи на Цял екран",
|
||||
"Preferences": "Предпочитания",
|
||||
"Zoom Out": "Намали",
|
||||
"Zoom In": "Увеличи",
|
||||
"Actual Size": "Действителен Размер",
|
||||
"View": "Преглед",
|
||||
"Select All": "Избери Всичко",
|
||||
"Delete": "Изтрий",
|
||||
"Paste and Match Style": "Постави и Използвай текущия стил",
|
||||
"Paste": "Постави",
|
||||
"Copy": "Копирай",
|
||||
"Cut": "Изрежи",
|
||||
"Redo": "Върни",
|
||||
"Undo": "Отмени",
|
||||
"Edit": "Редактирай",
|
||||
"Quit": "Напусни",
|
||||
"Show/Hide": "Покажи/Скрий",
|
||||
"Are you sure you want to quit?": "Сигурен ли си че искаш да напуснеш?",
|
||||
"Close Element": "Затвори Елемент",
|
||||
"Cancel": "Отказ"
|
||||
}
|
@ -41,5 +41,7 @@
|
||||
"Bring All to Front": "Tout amener au premier plan",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Arrêter la dictée",
|
||||
"Start Speaking": "Commencer la dictée"
|
||||
"Start Speaking": "Commencer la dictée",
|
||||
"Copy image address": "Copier l'adresse de l'image",
|
||||
"Redo": "Refaire"
|
||||
}
|
||||
|
@ -42,5 +42,6 @@
|
||||
"Are you sure you want to quit?": "האם אתה בטוח שברצונך לצאת?",
|
||||
"Close Element": "סגור את אלמנט",
|
||||
"Cancel": "ביטול",
|
||||
"Paste and Match Style": "הדבק והתאם סגנון"
|
||||
"Paste and Match Style": "הדבק והתאם סגנון",
|
||||
"Copy image address": "העתקת כתובת התמונה"
|
||||
}
|
||||
|
@ -42,5 +42,6 @@
|
||||
"Show/Hide": "Pokaż/Ukryj",
|
||||
"Are you sure you want to quit?": "Czy na pewno chcesz zamknąć?",
|
||||
"Close Element": "Zamknij Elementa",
|
||||
"Cancel": "Anuluj"
|
||||
"Cancel": "Anuluj",
|
||||
"Copy image address": "Skopiuj adres obrazu"
|
||||
}
|
||||
|
@ -27,5 +27,21 @@
|
||||
"Redo": "පසුසේ",
|
||||
"Undo": "පෙරසේ",
|
||||
"Edit": "සංස්කරණය",
|
||||
"Quit": "ඉවත් වන්න"
|
||||
"Quit": "ඉවත් වන්න",
|
||||
"Paste and Match Style": "අලවා ශෛලිය ගැළපුම",
|
||||
"Delete": "මකන්න",
|
||||
"The image failed to save": "රූපය සුරැකීමට අසමත්",
|
||||
"Failed to save image": "රූපය සුරැකීමට අසමත්",
|
||||
"Save image as...": "...ලෙස රූපය සුරකින්න",
|
||||
"Copy image address": "රූපයේ ලිපිනයේ පිටපතක්",
|
||||
"Copy image": "රූපයෙහි පිටපතක්",
|
||||
"Bring All to Front": "සියල්ල ඉදිරිපසට",
|
||||
"Stop Speaking": "කථාව නිමාව",
|
||||
"Start Speaking": "කථාව ආරම්භය",
|
||||
"Speech": "කථාව",
|
||||
"Unhide": "නොසඟවන්න",
|
||||
"Toggle Developer Tools": "සංවර්ධක මෙවලම්",
|
||||
"Toggle Full Screen": "පූර්ණ තිරයට",
|
||||
"Preferences": "පෙනුම",
|
||||
"View": "දකින්න"
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"Zoom": "பெரிதாக்குதல்",
|
||||
"Minimize": "சிறிதாக்கு",
|
||||
"Toggle Developer Tools": "படைப்பாளர் கருவிகளை நிலைமாற்று",
|
||||
"Toggle Developer Tools": "உருவாக்குநர் கருவிகளை நிலைமாற்று",
|
||||
"Toggle Full Screen": "முழு திரையை நிலைமாற்று",
|
||||
"Paste and Match Style": "ஒட்டு மற்றும் நடையை பொறுத்து",
|
||||
"Paste and Match Style": "ஒட்டு மற்றும் நடையை பொருத்து",
|
||||
"Add to dictionary": "அகராதியில் சேர்",
|
||||
"The image failed to save": "படம் சேமிக்கத் தவறிவிட்டது",
|
||||
"Failed to save image": "படத்தைச் சேமிப்பதில் தோல்வி",
|
||||
@ -16,8 +16,8 @@
|
||||
"Stop Speaking": "பேசுவதை நிறுத்து",
|
||||
"Start Speaking": "பேசத் துவங்கு",
|
||||
"Speech": "பேச்சு",
|
||||
"Unhide": "காட்டு",
|
||||
"Hide Others": "மற்றதை மறை",
|
||||
"Unhide": "மறைநீக்கு",
|
||||
"Hide Others": "மற்றவற்றை மறை",
|
||||
"Hide": "மறை",
|
||||
"Services": "சேவைகள்",
|
||||
"About": "இதனைப் பற்றி",
|
||||
@ -29,8 +29,8 @@
|
||||
"Zoom Out": "சிறிதாக்கு",
|
||||
"Zoom In": "பெரிதாக்கு",
|
||||
"Actual Size": "உண்மையான அளவு",
|
||||
"View": "காட்சி",
|
||||
"Select All": "அனைத்தையும் தெரிவுசெய்",
|
||||
"View": "காட்டு",
|
||||
"Select All": "அனைத்தையும் தேர்ந்தெடு",
|
||||
"Delete": "அழி",
|
||||
"Paste": "ஒட்டு",
|
||||
"Copy": "நகலெடு",
|
||||
@ -42,5 +42,6 @@
|
||||
"Show/Hide": "காட்டு/மறை",
|
||||
"Are you sure you want to quit?": "நீங்கள் நிச்சயம் வெளியேற விரும்புகிறீர்களா?",
|
||||
"Close Element": "எலிமெண்ட் ஐ மூடு",
|
||||
"Cancel": "ரத்துசெய்"
|
||||
"Cancel": "விலக்கிக்கொள்",
|
||||
"Copy image address": "பட முகவரியை நகலெடு"
|
||||
}
|
||||
|
@ -42,5 +42,6 @@
|
||||
"Show/Hide": "Hiển thị/Ẩn",
|
||||
"Are you sure you want to quit?": "Bạn có chắc chắn muốn thoát?",
|
||||
"Close Element": "Đóng Element",
|
||||
"Cancel": "Hủy bỏ"
|
||||
"Cancel": "Hủy bỏ",
|
||||
"Copy image address": "Sao chép địa chỉ ảnh"
|
||||
}
|
||||
|
@ -42,5 +42,6 @@
|
||||
"Show/Hide": "顯示/隱藏",
|
||||
"Are you sure you want to quit?": "您確定要退出嗎?",
|
||||
"Close Element": "關閉 Element",
|
||||
"Cancel": "取消"
|
||||
"Cancel": "取消",
|
||||
"Copy image address": "複製圖片地址"
|
||||
}
|
||||
|
203
src/ipc.ts
Normal file
203
src/ipc.ts
Normal file
@ -0,0 +1,203 @@
|
||||
/*
|
||||
Copyright 2022 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 { app, autoUpdater, desktopCapturer, ipcMain, powerSaveBlocker } from "electron";
|
||||
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
import { recordSSOSession } from "./protocol";
|
||||
import { randomArray } from "./utils";
|
||||
import { Settings } from "./settings";
|
||||
import { keytar } from "./keytar";
|
||||
|
||||
ipcMain.on('setBadgeCount', function(_ev: IpcMainEvent, count: number): void {
|
||||
if (process.platform !== 'win32') {
|
||||
// only set badgeCount on Mac/Linux, the docs say that only those platforms support it but turns out Electron
|
||||
// has some Windows support too, and in some Windows environments this leads to two badges rendering atop
|
||||
// each other. See https://github.com/vector-im/element-web/issues/16942
|
||||
app.badgeCount = count;
|
||||
}
|
||||
if (count === 0) {
|
||||
global.mainWindow?.flashFrame(false);
|
||||
}
|
||||
});
|
||||
|
||||
let focusHandlerAttached = false;
|
||||
ipcMain.on('loudNotification', function(): void {
|
||||
if (process.platform === 'win32' && global.mainWindow && !global.mainWindow.isFocused() && !focusHandlerAttached) {
|
||||
global.mainWindow.flashFrame(true);
|
||||
global.mainWindow.once('focus', () => {
|
||||
global.mainWindow.flashFrame(false);
|
||||
focusHandlerAttached = false;
|
||||
});
|
||||
focusHandlerAttached = true;
|
||||
}
|
||||
});
|
||||
|
||||
let powerSaveBlockerId: number = null;
|
||||
ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) {
|
||||
switch (payload.action) {
|
||||
case 'call_state': {
|
||||
if (powerSaveBlockerId !== null && powerSaveBlocker.isStarted(powerSaveBlockerId)) {
|
||||
if (payload.state === 'ended') {
|
||||
powerSaveBlocker.stop(powerSaveBlockerId);
|
||||
powerSaveBlockerId = null;
|
||||
}
|
||||
} else {
|
||||
if (powerSaveBlockerId === null && payload.state === 'connected') {
|
||||
powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
if (!global.mainWindow) return;
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
|
||||
switch (payload.name) {
|
||||
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;
|
||||
case 'getAppVersion':
|
||||
ret = app.getVersion();
|
||||
break;
|
||||
case 'focusWindow':
|
||||
if (global.mainWindow.isMinimized()) {
|
||||
global.mainWindow.restore();
|
||||
} else if (!global.mainWindow.isVisible()) {
|
||||
global.mainWindow.show();
|
||||
} else {
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
break;
|
||||
case 'getConfig':
|
||||
ret = global.vectorConfig;
|
||||
break;
|
||||
case 'navigateBack':
|
||||
if (global.mainWindow.webContents.canGoBack()) {
|
||||
global.mainWindow.webContents.goBack();
|
||||
}
|
||||
break;
|
||||
case 'navigateForward':
|
||||
if (global.mainWindow.webContents.canGoForward()) {
|
||||
global.mainWindow.webContents.goForward();
|
||||
}
|
||||
break;
|
||||
case 'setSpellCheckEnabled':
|
||||
if (typeof args[0] !== 'boolean') return;
|
||||
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(args[0]);
|
||||
global.store.set("spellCheckerEnabled", args[0]);
|
||||
|
||||
break;
|
||||
|
||||
case 'getSpellCheckEnabled':
|
||||
ret = global.store.get("spellCheckerEnabled", true);
|
||||
break;
|
||||
|
||||
case 'setSpellCheckLanguages':
|
||||
try {
|
||||
global.mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
|
||||
} catch (er) {
|
||||
console.log("There were problems setting the spellcheck languages", er);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getSpellCheckLanguages':
|
||||
ret = global.mainWindow.webContents.session.getSpellCheckerLanguages();
|
||||
break;
|
||||
case 'getAvailableSpellCheckLanguages':
|
||||
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]}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// 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
|
||||
ret = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'createPickleKey':
|
||||
try {
|
||||
const pickleKey = await randomArray(32);
|
||||
await keytar.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
ret = pickleKey;
|
||||
} catch (e) {
|
||||
ret = null;
|
||||
}
|
||||
break;
|
||||
|
||||
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 (e) {}
|
||||
break;
|
||||
case 'getDesktopCapturerSources':
|
||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
thumbnailURL: source.thumbnail.toDataURL(),
|
||||
}));
|
||||
break;
|
||||
|
||||
default:
|
||||
global.mainWindow.webContents.send('ipcReply', {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
global.mainWindow.webContents.send('ipcReply', {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
|
31
src/keytar.ts
Normal file
31
src/keytar.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2022 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 type * as Keytar from "keytar"; // Hak dependency type
|
||||
|
||||
let keytar: typeof Keytar | undefined;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
keytar = require('keytar');
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
console.log("Keytar isn't installed; secure key storage is disabled.");
|
||||
} else {
|
||||
console.warn("Keytar unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
export { keytar };
|
325
src/seshat.ts
Normal file
325
src/seshat.ts
Normal file
@ -0,0 +1,325 @@
|
||||
/*
|
||||
Copyright 2022 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 { app, ipcMain } from "electron";
|
||||
import { promises as afs } from "fs";
|
||||
import path from "path";
|
||||
|
||||
import type {
|
||||
Seshat as SeshatType,
|
||||
SeshatRecovery as SeshatRecoveryType,
|
||||
ReindexError as ReindexErrorType,
|
||||
} from "matrix-seshat"; // Hak dependency type
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
import { randomArray } from "./utils";
|
||||
import { keytar } from "./keytar";
|
||||
|
||||
let seshatSupported = false;
|
||||
let Seshat: typeof SeshatType;
|
||||
let SeshatRecovery: typeof SeshatRecoveryType;
|
||||
let ReindexError: typeof ReindexErrorType;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const seshatModule = require('matrix-seshat');
|
||||
Seshat = seshatModule.Seshat;
|
||||
SeshatRecovery = seshatModule.SeshatRecovery;
|
||||
ReindexError = seshatModule.ReindexError;
|
||||
seshatSupported = true;
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
console.log("Seshat isn't installed, event indexing is disabled.");
|
||||
} else {
|
||||
console.warn("Seshat unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
||||
|
||||
let eventIndex: SeshatType = 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);
|
||||
}
|
||||
} else {
|
||||
return seshatDefaultPassphrase;
|
||||
}
|
||||
}
|
||||
|
||||
const deleteContents = async (p: string): Promise<void> => {
|
||||
for (const entry of await afs.readdir(p)) {
|
||||
const curPath = path.join(p, entry);
|
||||
await afs.unlink(curPath);
|
||||
}
|
||||
};
|
||||
|
||||
ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
if (!global.mainWindow) return;
|
||||
|
||||
const sendError = (id, e) => {
|
||||
const error = {
|
||||
message: e.message,
|
||||
};
|
||||
|
||||
global.mainWindow.webContents.send('seshatReply', {
|
||||
id: id,
|
||||
error: error,
|
||||
});
|
||||
};
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
|
||||
switch (payload.name) {
|
||||
case 'supportsEventIndexing':
|
||||
ret = seshatSupported;
|
||||
break;
|
||||
|
||||
case 'initEventIndex':
|
||||
if (eventIndex === null) {
|
||||
const userId = args[0];
|
||||
const deviceId = args[1];
|
||||
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
||||
|
||||
const passphrase = await getOrCreatePassphrase(passphraseKey);
|
||||
|
||||
try {
|
||||
await afs.mkdir(eventStorePath, { recursive: true });
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} catch (e) {
|
||||
if (e instanceof ReindexError) {
|
||||
// If this is a reindex error, the index schema
|
||||
// changed. Try to open the database in recovery mode,
|
||||
// reindex the database and finally try to open the
|
||||
// database again.
|
||||
const recoveryIndex = new SeshatRecovery(eventStorePath, {
|
||||
passphrase,
|
||||
});
|
||||
|
||||
const userVersion = await recoveryIndex.getUserVersion();
|
||||
|
||||
// If our user version is 0 we'll delete the db
|
||||
// anyways so reindexing it is a waste of time.
|
||||
if (userVersion === 0) {
|
||||
await recoveryIndex.shutdown();
|
||||
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
}
|
||||
} else {
|
||||
await recoveryIndex.reindex();
|
||||
}
|
||||
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} else {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'closeEventIndex':
|
||||
if (eventIndex !== null) {
|
||||
const index = eventIndex;
|
||||
eventIndex = null;
|
||||
|
||||
try {
|
||||
await index.shutdown();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEventIndex': {
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'isEventIndexEmpty':
|
||||
if (eventIndex === null) ret = true;
|
||||
else ret = await eventIndex.isEmpty();
|
||||
break;
|
||||
|
||||
case 'isRoomIndexed':
|
||||
if (eventIndex === null) ret = false;
|
||||
else ret = await eventIndex.isRoomIndexed(args[0]);
|
||||
break;
|
||||
|
||||
case 'addEventToIndex':
|
||||
try {
|
||||
eventIndex.addEvent(args[0], args[1]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEvent':
|
||||
try {
|
||||
ret = await eventIndex.deleteEvent(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'commitLiveEvents':
|
||||
try {
|
||||
ret = await eventIndex.commit();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'searchEventIndex':
|
||||
try {
|
||||
ret = await eventIndex.search(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addHistoricEvents':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.addHistoricEvents(
|
||||
args[0], args[1], args[2]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getStats':
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.getStats();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'removeCrawlerCheckpoint':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.removeCrawlerCheckpoint(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addCrawlerCheckpoint':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.addCrawlerCheckpoint(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadFileEvents':
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.loadFileEvents(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadCheckpoints':
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.loadCheckpoints();
|
||||
} catch (e) {
|
||||
ret = [];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'setUserVersion':
|
||||
if (eventIndex === null) break;
|
||||
else {
|
||||
try {
|
||||
await eventIndex.setUserVersion(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getUserVersion':
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.getUserVersion();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
global.mainWindow.webContents.send('seshatReply', {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
global.mainWindow.webContents.send('seshatReply', {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
});
|
77
src/settings.ts
Normal file
77
src/settings.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2022 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 * as tray from "./tray";
|
||||
|
||||
interface Setting {
|
||||
read(): Promise<any>;
|
||||
write(value: any): Promise<void>;
|
||||
}
|
||||
|
||||
export const Settings: Record<string, Setting> = {
|
||||
"Electron.autoLaunch": {
|
||||
async read(): Promise<any> {
|
||||
return global.launcher.isEnabled();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
if (value) {
|
||||
return global.launcher.enable();
|
||||
} else {
|
||||
return global.launcher.disable();
|
||||
}
|
||||
},
|
||||
},
|
||||
"Electron.warnBeforeExit": {
|
||||
async read(): Promise<any> {
|
||||
return global.store.get("warnBeforeExit", true);
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("warnBeforeExit", value);
|
||||
},
|
||||
},
|
||||
"Electron.alwaysShowMenuBar": { // not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return !global.mainWindow.autoHideMenuBar;
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set('autoHideMenuBar', !value);
|
||||
global.mainWindow.autoHideMenuBar = !value;
|
||||
global.mainWindow.setMenuBarVisibility(value);
|
||||
},
|
||||
},
|
||||
"Electron.showTrayIcon": { // not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return tray.hasTray();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
if (value) {
|
||||
// Create trayIcon icon
|
||||
tray.create(global.trayConfig);
|
||||
} else {
|
||||
tray.destroy();
|
||||
}
|
||||
global.store.set('minimizeToTray', value);
|
||||
},
|
||||
},
|
||||
"Electron.enableHardwareAcceleration": {
|
||||
async read(): Promise<any> {
|
||||
return !global.store.get('disableHardwareAcceleration', false);
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set('disableHardwareAcceleration', !value);
|
||||
},
|
||||
},
|
||||
};
|
@ -28,7 +28,17 @@ function installUpdate(): void {
|
||||
|
||||
function pollForUpdates(): void {
|
||||
try {
|
||||
// If we've already got a new update downloaded, then stop trying to check for new ones, as according to the doc
|
||||
// at https://github.com/electron/electron/blob/main/docs/api/auto-updater.md#autoupdatercheckforupdates
|
||||
// we'll just keep re-downloading the same update.
|
||||
// As a hunch, this might also be causing https://github.com/vector-im/element-web/issues/12433
|
||||
// due to the update checks colliding with the pending install somehow
|
||||
if (!latestUpdateDownloaded) {
|
||||
autoUpdater.checkForUpdates();
|
||||
} else {
|
||||
console.log("Skipping update check as download already present");
|
||||
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Couldn\'t check for update', e);
|
||||
}
|
||||
@ -39,7 +49,7 @@ export function start(updateBaseUrl: string): void {
|
||||
updateBaseUrl = updateBaseUrl + '/';
|
||||
}
|
||||
try {
|
||||
let url;
|
||||
let url: string;
|
||||
// For reasons best known to Squirrel, the way it checks for updates
|
||||
// is completely different between macOS and windows. On macOS, it
|
||||
// hits a URL that either gives it a 200 with some json or
|
||||
@ -64,7 +74,7 @@ export function start(updateBaseUrl: string): void {
|
||||
|
||||
if (url) {
|
||||
console.log(`Update URL: ${url}`);
|
||||
autoUpdater.setFeedURL(url);
|
||||
autoUpdater.setFeedURL({ url });
|
||||
// We check for updates ourselves rather than using 'updater' because we need to
|
||||
// do it in the main process (and we don't really need to check every 10 minutes:
|
||||
// every hour should be just fine for a desktop app)
|
||||
@ -85,8 +95,7 @@ ipcMain.on('install_update', installUpdate);
|
||||
ipcMain.on('check_updates', pollForUpdates);
|
||||
|
||||
function ipcChannelSendUpdateStatus(status: boolean | string): void {
|
||||
if (!global.mainWindow) return;
|
||||
global.mainWindow.webContents.send('check_updates', status);
|
||||
global.mainWindow?.webContents.send('check_updates', status);
|
||||
}
|
||||
|
||||
interface ICachedUpdate {
|
||||
@ -105,8 +114,7 @@ autoUpdater.on('update-available', function() {
|
||||
// the only time we will get `update-not-available` if `latestUpdateDownloaded` is already set
|
||||
// is if the user used the Manual Update check and there is no update newer than the one we
|
||||
// have downloaded, so show it to them as the latest again.
|
||||
if (!global.mainWindow) return;
|
||||
global.mainWindow.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
} else {
|
||||
ipcChannelSendUpdateStatus(false);
|
||||
}
|
||||
@ -115,8 +123,7 @@ autoUpdater.on('update-available', function() {
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName, releaseDate, updateURL) => {
|
||||
if (!global.mainWindow) return;
|
||||
// forward to renderer
|
||||
latestUpdateDownloaded = { releaseNotes, releaseName, releaseDate, updateURL };
|
||||
global.mainWindow.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
});
|
||||
|
29
src/utils.ts
Normal file
29
src/utils.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2022 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 crypto from "crypto";
|
||||
|
||||
export async function randomArray(size: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(size, (err, buf) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(buf.toString("base64").replace(/=+$/g, ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -143,7 +143,7 @@ function onLinkContextMenu(ev: Event, params: ContextMenuParams, webContents: We
|
||||
label: _t('Save image as...'),
|
||||
accelerator: 's',
|
||||
async click() {
|
||||
const targetFileName = params.titleText || "image.png";
|
||||
const targetFileName = params.suggestedFilename || params.altText || "image.png";
|
||||
const { filePath } = await dialog.showSaveDialog({
|
||||
defaultPath: targetFileName,
|
||||
});
|
||||
|
@ -10,6 +10,7 @@
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"typeRoots": ["src/@types"],
|
||||
"lib": [
|
||||
"es2019",
|
||||
"dom"
|
||||
|
Loading…
Reference in New Issue
Block a user