mirror of
https://github.com/CringeStudios/element-desktop.git
synced 2025-01-19 07:54: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 }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
jobs:
|
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:
|
windows:
|
||||||
|
needs: fetch
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
@ -21,6 +46,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: webapp
|
||||||
|
|
||||||
- name: Cache .hak
|
- name: Cache .hak
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@ -69,7 +98,15 @@ jobs:
|
|||||||
- name: Build App
|
- name: Build App
|
||||||
run: "yarn build --publish never -w ${{ matrix.build-args }}"
|
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:
|
linux:
|
||||||
|
needs: fetch
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
@ -81,6 +118,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: webapp
|
||||||
|
|
||||||
- name: Cache .hak
|
- name: Cache .hak
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@ -113,12 +154,24 @@ jobs:
|
|||||||
- name: Build App
|
- name: Build App
|
||||||
run: "yarn build --publish never"
|
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:
|
macos:
|
||||||
|
needs: fetch
|
||||||
name: macOS (universal)
|
name: macOS (universal)
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: webapp
|
||||||
|
|
||||||
- name: Cache .hak
|
- name: Cache .hak
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@ -145,3 +198,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Build App
|
- name: Build App
|
||||||
run: "yarn build:universal --publish never"
|
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)
|
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",
|
"name": "element-desktop",
|
||||||
"productName": "Element",
|
"productName": "Element",
|
||||||
"main": "lib/electron-main.js",
|
"main": "lib/electron-main.js",
|
||||||
"version": "1.10.15",
|
"version": "1.11.0",
|
||||||
"description": "A feature-rich client for Matrix.org",
|
"description": "A feature-rich client for Matrix.org",
|
||||||
"author": "Element",
|
"author": "Element",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -42,7 +42,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"auto-launch": "^5.0.5",
|
"auto-launch": "^5.0.5",
|
||||||
"counterpart": "^0.18.6",
|
"counterpart": "^0.18.6",
|
||||||
"electron-store": "^6.0.1",
|
"electron-store": "^8.0.2",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
"png-to-ico": "^2.1.1",
|
"png-to-ico": "^2.1.1",
|
||||||
@ -74,7 +74,7 @@
|
|||||||
"find-npm-prefix": "^1.0.2",
|
"find-npm-prefix": "^1.0.2",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
"matrix-web-i18n": "^1.2.0",
|
"matrix-web-i18n": "^1.3.0",
|
||||||
"mkdirp": "^1.0.3",
|
"mkdirp": "^1.0.3",
|
||||||
"needle": "^2.5.0",
|
"needle": "^2.5.0",
|
||||||
"node-pre-gyp": "^0.15.0",
|
"node-pre-gyp": "^0.15.0",
|
||||||
@ -88,6 +88,9 @@
|
|||||||
"matrix-seshat": "^2.3.3",
|
"matrix-seshat": "^2.3.3",
|
||||||
"keytar": "^7.9.0"
|
"keytar": "^7.9.0"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@types/node": "16.11.38"
|
||||||
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "im.riot.app",
|
"appId": "im.riot.app",
|
||||||
"asarUnpack": "**/*.node",
|
"asarUnpack": "**/*.node",
|
||||||
@ -119,11 +122,12 @@
|
|||||||
"darkModeSupport": true
|
"darkModeSupport": true
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": {
|
"target": ["squirrel", "msi"],
|
||||||
"target": "squirrel"
|
|
||||||
},
|
|
||||||
"sign": "scripts/electron_winSign"
|
"sign": "scripts/electron_winSign"
|
||||||
},
|
},
|
||||||
|
"msi": {
|
||||||
|
"perMachine": true
|
||||||
|
},
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist"
|
"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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 { BrowserWindow } from "electron";
|
||||||
|
import Store from "electron-store";
|
||||||
|
import AutoLaunch from "auto-launch";
|
||||||
|
|
||||||
|
import { AppLocalization } from "../language-helper";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface Global {
|
interface Global {
|
||||||
mainWindow: BrowserWindow;
|
mainWindow: BrowserWindow;
|
||||||
appQuitting: boolean;
|
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 "./squirrelhooks";
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
ipcMain,
|
|
||||||
powerSaveBlocker,
|
|
||||||
BrowserWindow,
|
BrowserWindow,
|
||||||
Menu,
|
Menu,
|
||||||
autoUpdater,
|
autoUpdater,
|
||||||
protocol,
|
protocol,
|
||||||
dialog,
|
dialog,
|
||||||
desktopCapturer,
|
|
||||||
} from "electron";
|
} from "electron";
|
||||||
import AutoLaunch from "auto-launch";
|
import AutoLaunch from "auto-launch";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import windowStateKeeper from 'electron-window-state';
|
import windowStateKeeper from 'electron-window-state';
|
||||||
import Store from 'electron-store';
|
import Store from 'electron-store';
|
||||||
import fs, { promises as afs } from "fs";
|
import fs, { promises as afs } from "fs";
|
||||||
import crypto from "crypto";
|
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
import minimist from "minimist";
|
import minimist from "minimist";
|
||||||
|
|
||||||
import type * as Keytar from "keytar"; // Hak dependency type
|
import "./ipc";
|
||||||
import type {
|
import "./keytar";
|
||||||
Seshat as SeshatType,
|
import "./seshat";
|
||||||
SeshatRecovery as SeshatRecoveryType,
|
import "./settings";
|
||||||
ReindexError as ReindexErrorType,
|
|
||||||
} from "matrix-seshat"; // Hak dependency type
|
|
||||||
import * as tray from "./tray";
|
import * as tray from "./tray";
|
||||||
import { buildMenuTemplate } from './vectormenu';
|
import { buildMenuTemplate } from './vectormenu';
|
||||||
import webContentsHandler from './webcontents-handler';
|
import webContentsHandler from './webcontents-handler';
|
||||||
import * as updater from './updater';
|
import * as updater from './updater';
|
||||||
import { getProfileFromDeeplink, protocolInit, recordSSOSession } from './protocol';
|
import { getProfileFromDeeplink, protocolInit } from './protocol';
|
||||||
import { _t, AppLocalization } from './language-helper';
|
import { _t, AppLocalization } from './language-helper';
|
||||||
import Input = Electron.Input;
|
import Input = Electron.Input;
|
||||||
import IpcMainEvent = Electron.IpcMainEvent;
|
|
||||||
|
|
||||||
const argv = minimist(process.argv, {
|
const argv = minimist(process.argv, {
|
||||||
alias: { help: "h" },
|
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
|
// Things we need throughout the file but need to be created
|
||||||
// async to are initialised in setupGlobals()
|
// async to are initialised in setupGlobals()
|
||||||
let asarPath: string;
|
let asarPath: string;
|
||||||
let resPath: string;
|
let resPath: string;
|
||||||
let iconPath: 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"]) {
|
if (argv["help"]) {
|
||||||
console.log("Options:");
|
console.log("Options:");
|
||||||
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
||||||
@ -199,13 +151,13 @@ async function setupGlobals(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
vectorConfig = require(asarPath + 'config.json');
|
global.vectorConfig = require(asarPath + 'config.json');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// it would be nice to check the error code here and bail if the config
|
// 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
|
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||||
// file or invalid json, so node is just very unhelpful.
|
// file or invalid json, so node is just very unhelpful.
|
||||||
// Continue with the defaults (ie. an empty config)
|
// Continue with the defaults (ie. an empty config)
|
||||||
vectorConfig = {};
|
global.vectorConfig = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -219,19 +171,19 @@ async function setupGlobals(): Promise<void> {
|
|||||||
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'];
|
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'];
|
||||||
if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) {
|
if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) {
|
||||||
// Rip out all the homeserver options from the vector config
|
// 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))
|
.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) {
|
} catch (e) {
|
||||||
if (e instanceof SyntaxError) {
|
if (e instanceof SyntaxError) {
|
||||||
dialog.showMessageBox({
|
dialog.showMessageBox({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: `Your ${vectorConfig.brand || 'Element'} is misconfigured`,
|
title: `Your ${global.vectorConfig.brand || 'Element'} is misconfigured`,
|
||||||
message: `Your custom ${vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
message: `Your custom ${global.vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||||
`Please correct the problem and reopen ${vectorConfig.brand || 'Element'}.`,
|
`Please correct the problem and reopen ${global.vectorConfig.brand || 'Element'}.`,
|
||||||
detail: e.message || "",
|
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.
|
// 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'}`;
|
const iconFile = `element.${process.platform === 'win32' ? 'ico' : 'png'}`;
|
||||||
iconPath = path.join(resPath, "img", iconFile);
|
iconPath = path.join(resPath, "img", iconFile);
|
||||||
trayConfig = {
|
global.trayConfig = {
|
||||||
icon_path: iconPath,
|
icon_path: iconPath,
|
||||||
brand: vectorConfig.brand || 'Element',
|
brand: global.vectorConfig.brand || 'Element',
|
||||||
};
|
};
|
||||||
|
|
||||||
// launcher
|
// launcher
|
||||||
launcher = new AutoLaunch({
|
global.launcher = new AutoLaunch({
|
||||||
name: vectorConfig.brand || 'Element',
|
name: global.vectorConfig.brand || 'Element',
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
mac: {
|
mac: {
|
||||||
useLaunchAgent: true,
|
useLaunchAgent: true,
|
||||||
@ -261,7 +213,7 @@ async function setupGlobals(): Promise<void> {
|
|||||||
async function moveAutoLauncher(): Promise<void> {
|
async function moveAutoLauncher(): Promise<void> {
|
||||||
// Look for an auto-launcher under 'Riot' and if we find one, port it's
|
// Look for an auto-launcher under 'Riot' and if we find one, port it's
|
||||||
// enabled/disabled-ness over to the new 'Element' launcher
|
// 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({
|
const oldLauncher = new AutoLaunch({
|
||||||
name: 'Riot',
|
name: 'Riot',
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
@ -272,24 +224,13 @@ async function moveAutoLauncher(): Promise<void> {
|
|||||||
const wasEnabled = await oldLauncher.isEnabled();
|
const wasEnabled = await oldLauncher.isEnabled();
|
||||||
if (wasEnabled) {
|
if (wasEnabled) {
|
||||||
await oldLauncher.disable();
|
await oldLauncher.disable();
|
||||||
await launcher.enable();
|
await global.launcher.enable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
global.store = new Store({ name: "electron-config" });
|
||||||
const store = new Store<{
|
|
||||||
warnBeforeExit?: boolean;
|
|
||||||
minimizeToTray?: boolean;
|
|
||||||
spellCheckerEnabled?: boolean;
|
|
||||||
autoHideMenuBar?: boolean;
|
|
||||||
locale?: string | string[];
|
|
||||||
disableHardwareAcceleration?: boolean;
|
|
||||||
}>({ name: "electron-config" });
|
|
||||||
|
|
||||||
let eventIndex: SeshatType = null;
|
|
||||||
|
|
||||||
let mainWindow: BrowserWindow = null;
|
|
||||||
global.appQuitting = false;
|
global.appQuitting = false;
|
||||||
|
|
||||||
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
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 warnBeforeExit = (event: Event, input: Input): void => {
|
||||||
const shouldWarnBeforeExit = store.get('warnBeforeExit', true);
|
const shouldWarnBeforeExit = global.store.get('warnBeforeExit', true);
|
||||||
const exitShortcutPressed =
|
const exitShortcutPressed =
|
||||||
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
||||||
|
|
||||||
if (shouldWarnBeforeExit && exitShortcutPressed) {
|
if (shouldWarnBeforeExit && exitShortcutPressed) {
|
||||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(mainWindow, {
|
const shouldCancelCloseRequest = dialog.showMessageBoxSync(global.mainWindow, {
|
||||||
type: "question",
|
type: "question",
|
||||||
buttons: [_t("Cancel"), _t("Close Element")],
|
buttons: [_t("Cancel"), _t("Close Element")],
|
||||||
message: _t("Are you sure you want to quit?"),
|
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
|
// handle uncaught errors otherwise it displays
|
||||||
// stack traces in popup dialogs, which is terrible (which
|
// stack traces in popup dialogs, which is terrible (which
|
||||||
// it will do any time the auto update poke fails, and there's
|
// 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);
|
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');
|
app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
||||||
if (!app.commandLine.hasSwitch('enable-features')) {
|
if (!app.commandLine.hasSwitch('enable-features')) {
|
||||||
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
|
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
|
||||||
@ -896,7 +312,7 @@ app.enableSandbox();
|
|||||||
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
||||||
|
|
||||||
// Disable hardware acceleration if the setting has been set.
|
// 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.");
|
console.log("Disabling hardware acceleration.");
|
||||||
app.disableHardwareAcceleration();
|
app.disableHardwareAcceleration();
|
||||||
}
|
}
|
||||||
@ -984,9 +400,9 @@ app.on('ready', async () => {
|
|||||||
|
|
||||||
if (argv['no-update']) {
|
if (argv['no-update']) {
|
||||||
console.log('Auto update disabled via command line flag "--no-update"');
|
console.log('Auto update disabled via command line flag "--no-update"');
|
||||||
} else if (vectorConfig['update_base_url']) {
|
} else if (global.vectorConfig['update_base_url']) {
|
||||||
console.log(`Starting auto update with base URL: ${vectorConfig['update_base_url']}`);
|
console.log(`Starting auto update with base URL: ${global.vectorConfig['update_base_url']}`);
|
||||||
updater.start(vectorConfig['update_base_url']);
|
updater.start(global.vectorConfig['update_base_url']);
|
||||||
} else {
|
} else {
|
||||||
console.log('No update_base_url is defined: auto update is disabled');
|
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`);
|
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
|
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
|
|
||||||
icon: iconPath,
|
icon: iconPath,
|
||||||
show: false,
|
show: false,
|
||||||
autoHideMenuBar: store.get('autoHideMenuBar', true),
|
autoHideMenuBar: global.store.get('autoHideMenuBar', true),
|
||||||
|
|
||||||
x: mainWindowState.x,
|
x: mainWindowState.x,
|
||||||
y: mainWindowState.y,
|
y: mainWindowState.y,
|
||||||
@ -1018,32 +434,32 @@ app.on('ready', async () => {
|
|||||||
webgl: true,
|
webgl: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
mainWindow.loadURL('vector://vector/webapp/');
|
global.mainWindow.loadURL('vector://vector/webapp/');
|
||||||
|
|
||||||
// Handle spellchecker
|
// Handle spellchecker
|
||||||
// For some reason spellCheckerEnabled isn't persisted so we have to use the store here
|
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
|
||||||
mainWindow.webContents.session.setSpellCheckerEnabled(store.get("spellCheckerEnabled", true));
|
global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true));
|
||||||
|
|
||||||
// Create trayIcon icon
|
// 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', () => {
|
global.mainWindow.once('ready-to-show', () => {
|
||||||
mainWindowState.manage(mainWindow);
|
mainWindowState.manage(global.mainWindow);
|
||||||
|
|
||||||
if (!argv['hidden']) {
|
if (!argv['hidden']) {
|
||||||
mainWindow.show();
|
global.mainWindow.show();
|
||||||
} else {
|
} else {
|
||||||
// hide here explicitly because window manage above sometimes shows it
|
// 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', () => {
|
global.mainWindow.on('closed', () => {
|
||||||
mainWindow = global.mainWindow = null;
|
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 we are not quitting and have a tray icon then minimize to tray
|
||||||
if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
|
if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
|
||||||
// On Mac, closing the window just hides it
|
// On Mac, closing the window just hides it
|
||||||
@ -1051,12 +467,12 @@ app.on('ready', async () => {
|
|||||||
// behave, eg. Mail.app)
|
// behave, eg. Mail.app)
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (mainWindow.isFullScreen()) {
|
if (global.mainWindow.isFullScreen()) {
|
||||||
mainWindow.once('leave-full-screen', () => mainWindow.hide());
|
global.mainWindow.once('leave-full-screen', () => global.mainWindow.hide());
|
||||||
|
|
||||||
mainWindow.setFullScreen(false);
|
global.mainWindow.setFullScreen(false);
|
||||||
} else {
|
} else {
|
||||||
mainWindow.hide();
|
global.mainWindow.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -1065,19 +481,19 @@ app.on('ready', async () => {
|
|||||||
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
// Handle forward/backward mouse buttons in Windows
|
// Handle forward/backward mouse buttons in Windows
|
||||||
mainWindow.on('app-command', (e, cmd) => {
|
global.mainWindow.on('app-command', (e, cmd) => {
|
||||||
if (cmd === 'browser-backward' && mainWindow.webContents.canGoBack()) {
|
if (cmd === 'browser-backward' && global.mainWindow.webContents.canGoBack()) {
|
||||||
mainWindow.webContents.goBack();
|
global.mainWindow.webContents.goBack();
|
||||||
} else if (cmd === 'browser-forward' && mainWindow.webContents.canGoForward()) {
|
} else if (cmd === 'browser-forward' && global.mainWindow.webContents.canGoForward()) {
|
||||||
mainWindow.webContents.goForward();
|
global.mainWindow.webContents.goForward();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
webContentsHandler(mainWindow.webContents);
|
webContentsHandler(global.mainWindow.webContents);
|
||||||
|
|
||||||
appLocalization = new AppLocalization({
|
global.appLocalization = new AppLocalization({
|
||||||
store,
|
store: global.store,
|
||||||
components: [
|
components: [
|
||||||
() => tray.initApplicationMenu(),
|
() => tray.initApplicationMenu(),
|
||||||
() => Menu.setApplicationMenu(buildMenuTemplate()),
|
() => Menu.setApplicationMenu(buildMenuTemplate()),
|
||||||
@ -1090,14 +506,12 @@ app.on('window-all-closed', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
mainWindow.show();
|
global.mainWindow.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
function beforeQuit(): void {
|
function beforeQuit(): void {
|
||||||
global.appQuitting = true;
|
global.appQuitting = true;
|
||||||
if (mainWindow) {
|
global.mainWindow?.webContents.send('before-quit');
|
||||||
mainWindow.webContents.send('before-quit');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on('before-quit', beforeQuit);
|
app.on('before-quit', beforeQuit);
|
||||||
@ -1108,10 +522,10 @@ app.on('second-instance', (ev, commandLine, workingDirectory) => {
|
|||||||
if (commandLine.includes('--hidden')) return;
|
if (commandLine.includes('--hidden')) return;
|
||||||
|
|
||||||
// Someone tried to run a second instance, we should focus our window.
|
// Someone tried to run a second instance, we should focus our window.
|
||||||
if (mainWindow) {
|
if (global.mainWindow) {
|
||||||
if (!mainWindow.isVisible()) mainWindow.show();
|
if (!global.mainWindow.isVisible()) global.mainWindow.show();
|
||||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
if (global.mainWindow.isMinimized()) global.mainWindow.restore();
|
||||||
mainWindow.focus();
|
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",
|
"Bring All to Front": "Tout amener au premier plan",
|
||||||
"Zoom": "Zoom",
|
"Zoom": "Zoom",
|
||||||
"Stop Speaking": "Arrêter la dictée",
|
"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?": "האם אתה בטוח שברצונך לצאת?",
|
"Are you sure you want to quit?": "האם אתה בטוח שברצונך לצאת?",
|
||||||
"Close Element": "סגור את אלמנט",
|
"Close Element": "סגור את אלמנט",
|
||||||
"Cancel": "ביטול",
|
"Cancel": "ביטול",
|
||||||
"Paste and Match Style": "הדבק והתאם סגנון"
|
"Paste and Match Style": "הדבק והתאם סגנון",
|
||||||
|
"Copy image address": "העתקת כתובת התמונה"
|
||||||
}
|
}
|
||||||
|
@ -42,5 +42,6 @@
|
|||||||
"Show/Hide": "Pokaż/Ukryj",
|
"Show/Hide": "Pokaż/Ukryj",
|
||||||
"Are you sure you want to quit?": "Czy na pewno chcesz zamknąć?",
|
"Are you sure you want to quit?": "Czy na pewno chcesz zamknąć?",
|
||||||
"Close Element": "Zamknij Elementa",
|
"Close Element": "Zamknij Elementa",
|
||||||
"Cancel": "Anuluj"
|
"Cancel": "Anuluj",
|
||||||
|
"Copy image address": "Skopiuj adres obrazu"
|
||||||
}
|
}
|
||||||
|
@ -27,5 +27,21 @@
|
|||||||
"Redo": "පසුසේ",
|
"Redo": "පසුසේ",
|
||||||
"Undo": "පෙරසේ",
|
"Undo": "පෙරසේ",
|
||||||
"Edit": "සංස්කරණය",
|
"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": "பெரிதாக்குதல்",
|
"Zoom": "பெரிதாக்குதல்",
|
||||||
"Minimize": "சிறிதாக்கு",
|
"Minimize": "சிறிதாக்கு",
|
||||||
"Toggle Developer Tools": "படைப்பாளர் கருவிகளை நிலைமாற்று",
|
"Toggle Developer Tools": "உருவாக்குநர் கருவிகளை நிலைமாற்று",
|
||||||
"Toggle Full Screen": "முழு திரையை நிலைமாற்று",
|
"Toggle Full Screen": "முழு திரையை நிலைமாற்று",
|
||||||
"Paste and Match Style": "ஒட்டு மற்றும் நடையை பொறுத்து",
|
"Paste and Match Style": "ஒட்டு மற்றும் நடையை பொருத்து",
|
||||||
"Add to dictionary": "அகராதியில் சேர்",
|
"Add to dictionary": "அகராதியில் சேர்",
|
||||||
"The image failed to save": "படம் சேமிக்கத் தவறிவிட்டது",
|
"The image failed to save": "படம் சேமிக்கத் தவறிவிட்டது",
|
||||||
"Failed to save image": "படத்தைச் சேமிப்பதில் தோல்வி",
|
"Failed to save image": "படத்தைச் சேமிப்பதில் தோல்வி",
|
||||||
@ -16,8 +16,8 @@
|
|||||||
"Stop Speaking": "பேசுவதை நிறுத்து",
|
"Stop Speaking": "பேசுவதை நிறுத்து",
|
||||||
"Start Speaking": "பேசத் துவங்கு",
|
"Start Speaking": "பேசத் துவங்கு",
|
||||||
"Speech": "பேச்சு",
|
"Speech": "பேச்சு",
|
||||||
"Unhide": "காட்டு",
|
"Unhide": "மறைநீக்கு",
|
||||||
"Hide Others": "மற்றதை மறை",
|
"Hide Others": "மற்றவற்றை மறை",
|
||||||
"Hide": "மறை",
|
"Hide": "மறை",
|
||||||
"Services": "சேவைகள்",
|
"Services": "சேவைகள்",
|
||||||
"About": "இதனைப் பற்றி",
|
"About": "இதனைப் பற்றி",
|
||||||
@ -29,8 +29,8 @@
|
|||||||
"Zoom Out": "சிறிதாக்கு",
|
"Zoom Out": "சிறிதாக்கு",
|
||||||
"Zoom In": "பெரிதாக்கு",
|
"Zoom In": "பெரிதாக்கு",
|
||||||
"Actual Size": "உண்மையான அளவு",
|
"Actual Size": "உண்மையான அளவு",
|
||||||
"View": "காட்சி",
|
"View": "காட்டு",
|
||||||
"Select All": "அனைத்தையும் தெரிவுசெய்",
|
"Select All": "அனைத்தையும் தேர்ந்தெடு",
|
||||||
"Delete": "அழி",
|
"Delete": "அழி",
|
||||||
"Paste": "ஒட்டு",
|
"Paste": "ஒட்டு",
|
||||||
"Copy": "நகலெடு",
|
"Copy": "நகலெடு",
|
||||||
@ -42,5 +42,6 @@
|
|||||||
"Show/Hide": "காட்டு/மறை",
|
"Show/Hide": "காட்டு/மறை",
|
||||||
"Are you sure you want to quit?": "நீங்கள் நிச்சயம் வெளியேற விரும்புகிறீர்களா?",
|
"Are you sure you want to quit?": "நீங்கள் நிச்சயம் வெளியேற விரும்புகிறீர்களா?",
|
||||||
"Close Element": "எலிமெண்ட் ஐ மூடு",
|
"Close Element": "எலிமெண்ட் ஐ மூடு",
|
||||||
"Cancel": "ரத்துசெய்"
|
"Cancel": "விலக்கிக்கொள்",
|
||||||
|
"Copy image address": "பட முகவரியை நகலெடு"
|
||||||
}
|
}
|
||||||
|
@ -42,5 +42,6 @@
|
|||||||
"Show/Hide": "Hiển thị/Ẩn",
|
"Show/Hide": "Hiển thị/Ẩn",
|
||||||
"Are you sure you want to quit?": "Bạn có chắc chắn muốn thoát?",
|
"Are you sure you want to quit?": "Bạn có chắc chắn muốn thoát?",
|
||||||
"Close Element": "Đóng Element",
|
"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": "顯示/隱藏",
|
"Show/Hide": "顯示/隱藏",
|
||||||
"Are you sure you want to quit?": "您確定要退出嗎?",
|
"Are you sure you want to quit?": "您確定要退出嗎?",
|
||||||
"Close Element": "關閉 Element",
|
"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 {
|
function pollForUpdates(): void {
|
||||||
try {
|
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();
|
autoUpdater.checkForUpdates();
|
||||||
|
} else {
|
||||||
|
console.log("Skipping update check as download already present");
|
||||||
|
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Couldn\'t check for update', e);
|
console.log('Couldn\'t check for update', e);
|
||||||
}
|
}
|
||||||
@ -39,7 +49,7 @@ export function start(updateBaseUrl: string): void {
|
|||||||
updateBaseUrl = updateBaseUrl + '/';
|
updateBaseUrl = updateBaseUrl + '/';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let url;
|
let url: string;
|
||||||
// For reasons best known to Squirrel, the way it checks for updates
|
// For reasons best known to Squirrel, the way it checks for updates
|
||||||
// is completely different between macOS and windows. On macOS, it
|
// is completely different between macOS and windows. On macOS, it
|
||||||
// hits a URL that either gives it a 200 with some json or
|
// 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) {
|
if (url) {
|
||||||
console.log(`Update URL: ${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
|
// 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:
|
// 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)
|
// every hour should be just fine for a desktop app)
|
||||||
@ -85,8 +95,7 @@ ipcMain.on('install_update', installUpdate);
|
|||||||
ipcMain.on('check_updates', pollForUpdates);
|
ipcMain.on('check_updates', pollForUpdates);
|
||||||
|
|
||||||
function ipcChannelSendUpdateStatus(status: boolean | string): void {
|
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 {
|
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
|
// 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
|
// 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.
|
// 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 {
|
} else {
|
||||||
ipcChannelSendUpdateStatus(false);
|
ipcChannelSendUpdateStatus(false);
|
||||||
}
|
}
|
||||||
@ -115,8 +123,7 @@ autoUpdater.on('update-available', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName, releaseDate, updateURL) => {
|
autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName, releaseDate, updateURL) => {
|
||||||
if (!global.mainWindow) return;
|
|
||||||
// forward to renderer
|
// forward to renderer
|
||||||
latestUpdateDownloaded = { releaseNotes, releaseName, releaseDate, updateURL };
|
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...'),
|
label: _t('Save image as...'),
|
||||||
accelerator: 's',
|
accelerator: 's',
|
||||||
async click() {
|
async click() {
|
||||||
const targetFileName = params.titleText || "image.png";
|
const targetFileName = params.suggestedFilename || params.altText || "image.png";
|
||||||
const { filePath } = await dialog.showSaveDialog({
|
const { filePath } = await dialog.showSaveDialog({
|
||||||
defaultPath: targetFileName,
|
defaultPath: targetFileName,
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
"typeRoots": ["src/@types"],
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2019",
|
"es2019",
|
||||||
"dom"
|
"dom"
|
||||||
|
Loading…
Reference in New Issue
Block a user