mirror of
https://github.com/element-hq/element-desktop.git
synced 2026-01-05 14:10:28 -05:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cbb0b1640 | ||
|
|
b94140523e | ||
|
|
b9cc2ace60 | ||
|
|
dfdc9fb12a | ||
|
|
d871d9c672 | ||
|
|
776b0cfff2 | ||
|
|
6913fcce84 | ||
|
|
c2ca03e00c | ||
|
|
cba2f88353 | ||
|
|
af2fea8780 | ||
|
|
616263919d | ||
|
|
1a40ead8af | ||
|
|
4f11398539 | ||
|
|
62046fadcb | ||
|
|
275936cf7e | ||
|
|
ce78c292a7 | ||
|
|
9bd927fbb2 | ||
|
|
c23c3bdf03 | ||
|
|
45a9156127 | ||
|
|
389f6f4334 | ||
|
|
b7a0402de5 | ||
|
|
b27f72e3a3 | ||
|
|
a07e298971 |
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
|
||||
-->
|
||||
45
.github/workflows/build.yaml
vendored
45
.github/workflows/build.yaml
vendored
@@ -7,7 +7,32 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
fetch:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Fetch Element Web
|
||||
run: yarn run fetch --noverify --cfgdir element.io/nightly
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
retention-days: 1
|
||||
path: |
|
||||
webapp.asar
|
||||
package.json
|
||||
|
||||
windows:
|
||||
needs: fetch
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -21,6 +46,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@@ -68,7 +97,7 @@ jobs:
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build --publish never -w ${{ matrix.build-args }}"
|
||||
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -77,6 +106,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
linux:
|
||||
needs: fetch
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -88,6 +118,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@@ -119,7 +153,7 @@ jobs:
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build --publish never"
|
||||
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -128,11 +162,16 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
macos:
|
||||
needs: fetch
|
||||
name: macOS (universal)
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@@ -159,7 +198,7 @@ jobs:
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build:universal --publish never"
|
||||
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
111
CHANGELOG.md
111
CHANGELOG.md
@@ -1,3 +1,114 @@
|
||||
Changes in [1.11.2](https://github.com/vector-im/element-desktop/releases/tag/v1.11.2) (2022-08-03)
|
||||
===================================================================================================
|
||||
|
||||
## ✨ Features
|
||||
* Live location share - focus on user location on list item click ([\#9051](https://github.com/matrix-org/matrix-react-sdk/pull/9051)). Contributed by @kerryarchibald.
|
||||
* Live location sharing - don't trigger unread counts for beacon location events ([\#9071](https://github.com/matrix-org/matrix-react-sdk/pull/9071)). Contributed by @kerryarchibald.
|
||||
* Support for sending voice messages as replies and in threads ([\#9097](https://github.com/matrix-org/matrix-react-sdk/pull/9097)). Fixes vector-im/element-web#22031.
|
||||
* Add `Reply in thread` button to the right-click message context-menu ([\#9004](https://github.com/matrix-org/matrix-react-sdk/pull/9004)). Fixes vector-im/element-web#22745.
|
||||
* Starred_Messages_Feature_Contd_II/Outreachy ([\#9086](https://github.com/matrix-org/matrix-react-sdk/pull/9086)).
|
||||
* Use "frequently used emojis" for autocompletion in composer ([\#8998](https://github.com/matrix-org/matrix-react-sdk/pull/8998)). Fixes vector-im/element-web#18978. Contributed by @grimhilt.
|
||||
* Improve clickability of view source event toggle button ([\#9068](https://github.com/matrix-org/matrix-react-sdk/pull/9068)). Fixes vector-im/element-web#21856. Contributed by @luixxiul.
|
||||
* Improve clickability of "collapse" link button on bubble layout ([\#9037](https://github.com/matrix-org/matrix-react-sdk/pull/9037)). Fixes vector-im/element-web#22864. Contributed by @luixxiul.
|
||||
* Starred_Messages_Feature/Outreachy ([\#8842](https://github.com/matrix-org/matrix-react-sdk/pull/8842)).
|
||||
* Implement Use Case Selection screen ([\#8984](https://github.com/matrix-org/matrix-react-sdk/pull/8984)). Contributed by @justjanne.
|
||||
* Live location share - handle insufficient permissions in location sharing ([\#9047](https://github.com/matrix-org/matrix-react-sdk/pull/9047)). Contributed by @kerryarchibald.
|
||||
* Improve _FilePanel.scss ([\#9031](https://github.com/matrix-org/matrix-react-sdk/pull/9031)). Contributed by @luixxiul.
|
||||
* Improve spotlight accessibility by adding context menus ([\#8907](https://github.com/matrix-org/matrix-react-sdk/pull/8907)). Fixes vector-im/element-web#20875 and vector-im/element-web#22675. Contributed by @justjanne.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
* Replace mask-images with svg components in MessageActionBar ([\#9088](https://github.com/matrix-org/matrix-react-sdk/pull/9088)). Fixes vector-im/element-web#22912. Contributed by @kerryarchibald.
|
||||
* Unbreak in-app permalink tooltips ([\#9087](https://github.com/matrix-org/matrix-react-sdk/pull/9087)). Fixes vector-im/element-web#22874.
|
||||
* Show a back button when viewing a space member ([\#9095](https://github.com/matrix-org/matrix-react-sdk/pull/9095)). Fixes vector-im/element-web#22898.
|
||||
* Align the right edge of info tile lines with normal ones on IRC layout ([\#9058](https://github.com/matrix-org/matrix-react-sdk/pull/9058)). Fixes vector-im/element-web#22871. Contributed by @luixxiul.
|
||||
* Prevent email verification from overriding existing sessions ([\#9075](https://github.com/matrix-org/matrix-react-sdk/pull/9075)). Fixes vector-im/element-web#22881. Contributed by @justjanne.
|
||||
* Fix wrong buttons being used when exploring public rooms ([\#9062](https://github.com/matrix-org/matrix-react-sdk/pull/9062)). Fixes vector-im/element-web#22862.
|
||||
* Re-add padding to generic event list summary on IRC layout ([\#9063](https://github.com/matrix-org/matrix-react-sdk/pull/9063)). Fixes vector-im/element-web#22869. Contributed by @luixxiul.
|
||||
* Joining federated rooms via the spotlight search should no longer cause a "No known servers" error. ([\#9055](https://github.com/matrix-org/matrix-react-sdk/pull/9055)). Fixes vector-im/element-web#22845. Contributed by @Half-Shot.
|
||||
|
||||
Changes in [1.11.1](https://github.com/vector-im/element-desktop/releases/tag/v1.11.1) (2022-07-26)
|
||||
===================================================================================================
|
||||
|
||||
## ✨ Features
|
||||
* Enable URL tooltips on hover for Element Desktop ([\#22286](https://github.com/vector-im/element-web/pull/22286)). Fixes undefined/element-web#6532.
|
||||
* Hide screenshare button in video rooms on Desktop ([\#9045](https://github.com/matrix-org/matrix-react-sdk/pull/9045)).
|
||||
* Add a developer command to reset Megolm and Olm sessions ([\#9044](https://github.com/matrix-org/matrix-react-sdk/pull/9044)).
|
||||
* add spaces to TileErrorBoundary ([\#9012](https://github.com/matrix-org/matrix-react-sdk/pull/9012)). Contributed by @HarHarLinks.
|
||||
* Location sharing - add localised strings to map ([\#9025](https://github.com/matrix-org/matrix-react-sdk/pull/9025)). Fixes vector-im/element-web#21443. Contributed by @kerryarchibald.
|
||||
* Added trim to ignore whitespaces in email check ([\#9027](https://github.com/matrix-org/matrix-react-sdk/pull/9027)). Contributed by @ankur12-1610.
|
||||
* Improve _GenericEventListSummary.scss ([\#9005](https://github.com/matrix-org/matrix-react-sdk/pull/9005)). Contributed by @luixxiul.
|
||||
* Live location share - tiles without tile server (PSG-591) ([\#8962](https://github.com/matrix-org/matrix-react-sdk/pull/8962)). Contributed by @kerryarchibald.
|
||||
* Add option to display tooltip on link hover ([\#8394](https://github.com/matrix-org/matrix-react-sdk/pull/8394)). Fixes vector-im/element-web#21907.
|
||||
* Support a module API surface for custom functionality ([\#8246](https://github.com/matrix-org/matrix-react-sdk/pull/8246)).
|
||||
* Adjust encryption copy when creating a video room ([\#8989](https://github.com/matrix-org/matrix-react-sdk/pull/8989)). Fixes vector-im/element-web#22737.
|
||||
* Add bidirectonal isolation for pills ([\#8985](https://github.com/matrix-org/matrix-react-sdk/pull/8985)). Contributed by @sha-265.
|
||||
* Delabs `Show current avatar and name for users in message history` ([\#8764](https://github.com/matrix-org/matrix-react-sdk/pull/8764)). Fixes vector-im/element-web#22336.
|
||||
* Live location share - open latest location in map site ([\#8981](https://github.com/matrix-org/matrix-react-sdk/pull/8981)). Contributed by @kerryarchibald.
|
||||
* Improve LinkPreviewWidget ([\#8881](https://github.com/matrix-org/matrix-react-sdk/pull/8881)). Fixes vector-im/element-web#22634. Contributed by @luixxiul.
|
||||
* Render HTML topics in rooms on space home ([\#8939](https://github.com/matrix-org/matrix-react-sdk/pull/8939)).
|
||||
* Hide timestamp on event tiles being edited on every layout ([\#8956](https://github.com/matrix-org/matrix-react-sdk/pull/8956)). Contributed by @luixxiul.
|
||||
* Introduce new copy icon ([\#8942](https://github.com/matrix-org/matrix-react-sdk/pull/8942)).
|
||||
* Allow finding group DMs by members in spotlight ([\#8922](https://github.com/matrix-org/matrix-react-sdk/pull/8922)). Fixes vector-im/element-web#22564. Contributed by @justjanne.
|
||||
* Live location share - explicitly stop beacons replaced beacons ([\#8933](https://github.com/matrix-org/matrix-react-sdk/pull/8933)). Contributed by @kerryarchibald.
|
||||
* Remove unpin from widget kebab menu ([\#8924](https://github.com/matrix-org/matrix-react-sdk/pull/8924)).
|
||||
* Live location share - redact related locations on beacon redaction ([\#8926](https://github.com/matrix-org/matrix-react-sdk/pull/8926)). Contributed by @kerryarchibald.
|
||||
* Live location share - disallow message pinning ([\#8928](https://github.com/matrix-org/matrix-react-sdk/pull/8928)). Contributed by @kerryarchibald.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
* Fix manual update checks not working after being dismissed ([\#388](https://github.com/vector-im/element-desktop/pull/388)). Fixes vector-im/element-web#22795.
|
||||
* Don't check for updates if we already have one downloaded and queued ([\#386](https://github.com/vector-im/element-desktop/pull/386)).
|
||||
* Fix default file name in save-image-as ([\#385](https://github.com/vector-im/element-desktop/pull/385)). Fixes vector-im/element-web#20838.
|
||||
* Remove the ability to hide yourself in video rooms ([\#22806](https://github.com/vector-im/element-web/pull/22806)). Fixes vector-im/element-web#22805.
|
||||
* Unbreak in-app permalink tooltips ([\#9100](https://github.com/matrix-org/matrix-react-sdk/pull/9100)).
|
||||
* Add space for the stroke on message editor on IRC layout ([\#9030](https://github.com/matrix-org/matrix-react-sdk/pull/9030)). Fixes vector-im/element-web#22785. Contributed by @luixxiul.
|
||||
* Fix pinned messages not re-linkifying on edit ([\#9042](https://github.com/matrix-org/matrix-react-sdk/pull/9042)). Fixes vector-im/element-web#22726.
|
||||
* Don't unnecessarily persist the host signup dialog ([\#9043](https://github.com/matrix-org/matrix-react-sdk/pull/9043)). Fixes vector-im/element-web#22778.
|
||||
* Fix URL previews causing messages to become unrenderable ([\#9028](https://github.com/matrix-org/matrix-react-sdk/pull/9028)). Fixes vector-im/element-web#22766.
|
||||
* Fix event list summaries including invalid events ([\#9041](https://github.com/matrix-org/matrix-react-sdk/pull/9041)). Fixes vector-im/element-web#22790.
|
||||
* Correct accessibility labels for unread rooms in spotlight ([\#9003](https://github.com/matrix-org/matrix-react-sdk/pull/9003)). Contributed by @justjanne.
|
||||
* Enable search strings highlight on bubble layout ([\#9032](https://github.com/matrix-org/matrix-react-sdk/pull/9032)). Fixes vector-im/element-web#22786. Contributed by @luixxiul.
|
||||
* Unbreak URL preview for formatted links with tooltips ([\#9022](https://github.com/matrix-org/matrix-react-sdk/pull/9022)). Fixes vector-im/element-web#22764.
|
||||
* Re-add margin to tiles based on EventTileBubble ([\#9015](https://github.com/matrix-org/matrix-react-sdk/pull/9015)). Fixes vector-im/element-web#22772. Contributed by @luixxiul.
|
||||
* Fix Shortcut prompt for Search showing in minimized Roomlist ([\#9014](https://github.com/matrix-org/matrix-react-sdk/pull/9014)). Fixes vector-im/element-web#22739. Contributed by @justjanne.
|
||||
* Fix avatar position on event info line for hidden events on a thread ([\#9019](https://github.com/matrix-org/matrix-react-sdk/pull/9019)). Fixes vector-im/element-web#22777. Contributed by @luixxiul.
|
||||
* Fix lost padding of event tile info line ([\#9009](https://github.com/matrix-org/matrix-react-sdk/pull/9009)). Fixes vector-im/element-web#22754 and vector-im/element-web#22759. Contributed by @luixxiul.
|
||||
* Align verification bubble with normal event tiles on IRC layout ([\#9001](https://github.com/matrix-org/matrix-react-sdk/pull/9001)). Fixes vector-im/element-web#22758. Contributed by @luixxiul.
|
||||
* Ensure timestamp on generic event list summary is not hidden from TimelineCard ([\#9000](https://github.com/matrix-org/matrix-react-sdk/pull/9000)). Fixes vector-im/element-web#22755. Contributed by @luixxiul.
|
||||
* Fix headings margin on security user settings tab ([\#8826](https://github.com/matrix-org/matrix-react-sdk/pull/8826)). Contributed by @luixxiul.
|
||||
* Fix timestamp position on file panel ([\#8976](https://github.com/matrix-org/matrix-react-sdk/pull/8976)). Fixes vector-im/element-web#22718. Contributed by @luixxiul.
|
||||
* Stop using :not() pseudo class for mx_GenericEventListSummary ([\#8944](https://github.com/matrix-org/matrix-react-sdk/pull/8944)). Fixes vector-im/element-web#22602. Contributed by @luixxiul.
|
||||
* Don't show the same user twice in Spotlight ([\#8978](https://github.com/matrix-org/matrix-react-sdk/pull/8978)). Fixes vector-im/element-web#22697.
|
||||
* Align the right edge of expand / collapse link buttons of generic event list summary in bubble layout with a variable ([\#8992](https://github.com/matrix-org/matrix-react-sdk/pull/8992)). Fixes vector-im/element-web#22743. Contributed by @luixxiul.
|
||||
* Display own avatars on search results panel in bubble layout ([\#8990](https://github.com/matrix-org/matrix-react-sdk/pull/8990)). Contributed by @luixxiul.
|
||||
* Fix text flow of thread summary content on threads list ([\#8991](https://github.com/matrix-org/matrix-react-sdk/pull/8991)). Fixes vector-im/element-web#22738. Contributed by @luixxiul.
|
||||
* Fix the size of the clickable area of images ([\#8987](https://github.com/matrix-org/matrix-react-sdk/pull/8987)). Fixes vector-im/element-web#22282.
|
||||
* Fix font size of MessageTimestamp on TimelineCard ([\#8950](https://github.com/matrix-org/matrix-react-sdk/pull/8950)). Contributed by @luixxiul.
|
||||
* Improve security room settings tab style rules ([\#8844](https://github.com/matrix-org/matrix-react-sdk/pull/8844)). Fixes vector-im/element-web#22575. Contributed by @luixxiul.
|
||||
* Align E2E icon and avatar of info tile in compact modern layout ([\#8965](https://github.com/matrix-org/matrix-react-sdk/pull/8965)). Fixes vector-im/element-web#22652. Contributed by @luixxiul.
|
||||
* Fix clickable area of general event list summary toggle ([\#8979](https://github.com/matrix-org/matrix-react-sdk/pull/8979)). Fixes vector-im/element-web#22722. Contributed by @luixxiul.
|
||||
* Fix resizing room topic ([\#8966](https://github.com/matrix-org/matrix-react-sdk/pull/8966)). Fixes vector-im/element-web#22689.
|
||||
* Dismiss the search dialogue when starting a DM ([\#8967](https://github.com/matrix-org/matrix-react-sdk/pull/8967)). Fixes vector-im/element-web#22700.
|
||||
* Fix "greyed out" text style inconsistency on search result panel ([\#8974](https://github.com/matrix-org/matrix-react-sdk/pull/8974)). Contributed by @luixxiul.
|
||||
* Add top padding to EventTilePreview loader ([\#8977](https://github.com/matrix-org/matrix-react-sdk/pull/8977)). Fixes vector-im/element-web#22719. Contributed by @luixxiul.
|
||||
* Fix read receipts group position on TimelineCard in compact modern/group layout ([\#8971](https://github.com/matrix-org/matrix-react-sdk/pull/8971)). Fixes vector-im/element-web#22715. Contributed by @luixxiul.
|
||||
* Fix calls on homeservers without the unstable thirdparty endpoints. ([\#8931](https://github.com/matrix-org/matrix-react-sdk/pull/8931)). Fixes vector-im/element-web#21680. Contributed by @deepbluev7.
|
||||
* Enable ReplyChain text to be expanded on IRC layout ([\#8959](https://github.com/matrix-org/matrix-react-sdk/pull/8959)). Fixes vector-im/element-web#22709. Contributed by @luixxiul.
|
||||
* Fix hidden timestamp on message edit history dialog ([\#8955](https://github.com/matrix-org/matrix-react-sdk/pull/8955)). Fixes vector-im/element-web#22701. Contributed by @luixxiul.
|
||||
* Enable ReplyChain text to be expanded on bubble layout ([\#8958](https://github.com/matrix-org/matrix-react-sdk/pull/8958)). Fixes vector-im/element-web#22709. Contributed by @luixxiul.
|
||||
* Fix expand/collapse state wrong in metaspaces ([\#8952](https://github.com/matrix-org/matrix-react-sdk/pull/8952)). Fixes vector-im/element-web#22632.
|
||||
* Location (live) share replies now provide a fallback content ([\#8949](https://github.com/matrix-org/matrix-react-sdk/pull/8949)).
|
||||
* Fix space settings not opening for script-created spaces ([\#8957](https://github.com/matrix-org/matrix-react-sdk/pull/8957)). Fixes vector-im/element-web#22703.
|
||||
* Respect `filename` field on `m.file` events ([\#8951](https://github.com/matrix-org/matrix-react-sdk/pull/8951)).
|
||||
* Fix PlatformSettingsHandler always returning true due to returning a Promise ([\#8954](https://github.com/matrix-org/matrix-react-sdk/pull/8954)). Fixes vector-im/element-web#22616.
|
||||
* Improve high-contrast support for spotlight ([\#8948](https://github.com/matrix-org/matrix-react-sdk/pull/8948)). Fixes vector-im/element-web#22481. Contributed by @justjanne.
|
||||
* Fix wrong assertions that all media events have a mimetype ([\#8946](https://github.com/matrix-org/matrix-react-sdk/pull/8946)). Fixes matrix-org/element-web-rageshakes#13727.
|
||||
* Make invite dialogue fixed height ([\#8934](https://github.com/matrix-org/matrix-react-sdk/pull/8934)). Fixes vector-im/element-web#22659.
|
||||
* Fix all megolm error reported as unknown ([\#8916](https://github.com/matrix-org/matrix-react-sdk/pull/8916)).
|
||||
* Remove line-height declarations from _ReplyTile.scss ([\#8932](https://github.com/matrix-org/matrix-react-sdk/pull/8932)). Fixes vector-im/element-web#22687. Contributed by @luixxiul.
|
||||
* Reduce video rooms log spam ([\#8913](https://github.com/matrix-org/matrix-react-sdk/pull/8913)).
|
||||
* Correct new search input’s rounded corners ([\#8921](https://github.com/matrix-org/matrix-react-sdk/pull/8921)). Fixes vector-im/element-web#22576. Contributed by @justjanne.
|
||||
* Align unread notification dot on threads list in compact modern=group layout ([\#8911](https://github.com/matrix-org/matrix-react-sdk/pull/8911)). Fixes vector-im/element-web#22677. Contributed by @luixxiul.
|
||||
|
||||
Changes in [1.11.0](https://github.com/vector-im/element-desktop/releases/tag/v1.11.0) (2022-07-05)
|
||||
===================================================================================================
|
||||
|
||||
|
||||
17
package.json
17
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "element-desktop",
|
||||
"productName": "Element",
|
||||
"main": "lib/electron-main.js",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.2",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "Element",
|
||||
"repository": {
|
||||
@@ -42,7 +42,7 @@
|
||||
"dependencies": {
|
||||
"auto-launch": "^5.0.5",
|
||||
"counterpart": "^0.18.6",
|
||||
"electron-store": "^6.0.1",
|
||||
"electron-store": "^8.0.2",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"minimist": "^1.2.6",
|
||||
"png-to-ico": "^2.1.1",
|
||||
@@ -88,6 +88,9 @@
|
||||
"matrix-seshat": "^2.3.3",
|
||||
"keytar": "^7.9.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "16.11.38"
|
||||
},
|
||||
"build": {
|
||||
"appId": "im.riot.app",
|
||||
"asarUnpack": "**/*.node",
|
||||
@@ -119,11 +122,15 @@
|
||||
"darkModeSupport": true
|
||||
},
|
||||
"win": {
|
||||
"target": {
|
||||
"target": "squirrel"
|
||||
},
|
||||
"target": [
|
||||
"squirrel",
|
||||
"msi"
|
||||
],
|
||||
"sign": "scripts/electron_winSign"
|
||||
},
|
||||
"msi": {
|
||||
"perMachine": true
|
||||
},
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
|
||||
22
src/@types/global.d.ts
vendored
22
src/@types/global.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
Copyright 2021 - 2022 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -15,12 +15,32 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
import Store from "electron-store";
|
||||
import AutoLaunch from "auto-launch";
|
||||
|
||||
import { AppLocalization } from "../language-helper";
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
mainWindow: BrowserWindow;
|
||||
appQuitting: boolean;
|
||||
appLocalization: AppLocalization;
|
||||
launcher: AutoLaunch;
|
||||
vectorConfig: Record<string, any>;
|
||||
trayConfig: {
|
||||
// eslint-disable-next-line camelcase
|
||||
icon_path: string;
|
||||
brand: string;
|
||||
};
|
||||
store: Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration?: boolean;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,90 +21,42 @@ limitations under the License.
|
||||
import "./squirrelhooks";
|
||||
import {
|
||||
app,
|
||||
ipcMain,
|
||||
powerSaveBlocker,
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
autoUpdater,
|
||||
protocol,
|
||||
dialog,
|
||||
desktopCapturer,
|
||||
} from "electron";
|
||||
import AutoLaunch from "auto-launch";
|
||||
import path from "path";
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
import Store from 'electron-store';
|
||||
import fs, { promises as afs } from "fs";
|
||||
import crypto from "crypto";
|
||||
import { URL } from "url";
|
||||
import minimist from "minimist";
|
||||
|
||||
import type * as Keytar from "keytar"; // Hak dependency type
|
||||
import type {
|
||||
Seshat as SeshatType,
|
||||
SeshatRecovery as SeshatRecoveryType,
|
||||
ReindexError as ReindexErrorType,
|
||||
} from "matrix-seshat"; // Hak dependency type
|
||||
import "./ipc";
|
||||
import "./keytar";
|
||||
import "./seshat";
|
||||
import "./settings";
|
||||
import * as tray from "./tray";
|
||||
import { buildMenuTemplate } from './vectormenu';
|
||||
import webContentsHandler from './webcontents-handler';
|
||||
import * as updater from './updater';
|
||||
import { getProfileFromDeeplink, protocolInit, recordSSOSession } from './protocol';
|
||||
import { getProfileFromDeeplink, protocolInit } from './protocol';
|
||||
import { _t, AppLocalization } from './language-helper';
|
||||
import Input = Electron.Input;
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
|
||||
const argv = minimist(process.argv, {
|
||||
alias: { help: "h" },
|
||||
});
|
||||
|
||||
let keytar: typeof Keytar;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
keytar = require('keytar');
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
console.log("Keytar isn't installed; secure key storage is disabled.");
|
||||
} else {
|
||||
console.warn("Keytar unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
let seshatSupported = false;
|
||||
let Seshat: typeof SeshatType;
|
||||
let SeshatRecovery: typeof SeshatRecoveryType;
|
||||
let ReindexError: typeof ReindexErrorType;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const seshatModule = require('matrix-seshat');
|
||||
Seshat = seshatModule.Seshat;
|
||||
SeshatRecovery = seshatModule.SeshatRecovery;
|
||||
ReindexError = seshatModule.ReindexError;
|
||||
seshatSupported = true;
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
console.log("Seshat isn't installed, event indexing is disabled.");
|
||||
} else {
|
||||
console.warn("Seshat unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Things we need throughout the file but need to be created
|
||||
// async to are initialised in setupGlobals()
|
||||
let asarPath: string;
|
||||
let resPath: string;
|
||||
let iconPath: string;
|
||||
|
||||
let vectorConfig: Record<string, any>;
|
||||
let trayConfig: {
|
||||
// eslint-disable-next-line camelcase
|
||||
icon_path: string;
|
||||
brand: string;
|
||||
};
|
||||
let launcher: AutoLaunch;
|
||||
let appLocalization: AppLocalization;
|
||||
|
||||
if (argv["help"]) {
|
||||
console.log("Options:");
|
||||
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
||||
@@ -199,13 +151,13 @@ async function setupGlobals(): Promise<void> {
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
vectorConfig = require(asarPath + 'config.json');
|
||||
global.vectorConfig = require(asarPath + 'config.json');
|
||||
} catch (e) {
|
||||
// it would be nice to check the error code here and bail if the config
|
||||
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||
// file or invalid json, so node is just very unhelpful.
|
||||
// Continue with the defaults (ie. an empty config)
|
||||
vectorConfig = {};
|
||||
global.vectorConfig = {};
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -219,19 +171,19 @@ async function setupGlobals(): Promise<void> {
|
||||
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'];
|
||||
if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) {
|
||||
// Rip out all the homeserver options from the vector config
|
||||
vectorConfig = Object.keys(vectorConfig)
|
||||
global.vectorConfig = Object.keys(global.vectorConfig)
|
||||
.filter(k => !homeserverProps.includes(k))
|
||||
.reduce((obj, key) => {obj[key] = vectorConfig[key]; return obj;}, {});
|
||||
.reduce((obj, key) => {obj[key] = global.vectorConfig[key]; return obj;}, {});
|
||||
}
|
||||
|
||||
vectorConfig = Object.assign(vectorConfig, localConfig);
|
||||
global.vectorConfig = Object.assign(global.vectorConfig, localConfig);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: `Your ${vectorConfig.brand || 'Element'} is misconfigured`,
|
||||
message: `Your custom ${vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||
`Please correct the problem and reopen ${vectorConfig.brand || 'Element'}.`,
|
||||
title: `Your ${global.vectorConfig.brand || 'Element'} is misconfigured`,
|
||||
message: `Your custom ${global.vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||
`Please correct the problem and reopen ${global.vectorConfig.brand || 'Element'}.`,
|
||||
detail: e.message || "",
|
||||
});
|
||||
}
|
||||
@@ -243,14 +195,14 @@ async function setupGlobals(): Promise<void> {
|
||||
// It's important to call `path.join` so we don't end up with the packaged asar in the final path.
|
||||
const iconFile = `element.${process.platform === 'win32' ? 'ico' : 'png'}`;
|
||||
iconPath = path.join(resPath, "img", iconFile);
|
||||
trayConfig = {
|
||||
global.trayConfig = {
|
||||
icon_path: iconPath,
|
||||
brand: vectorConfig.brand || 'Element',
|
||||
brand: global.vectorConfig.brand || 'Element',
|
||||
};
|
||||
|
||||
// launcher
|
||||
launcher = new AutoLaunch({
|
||||
name: vectorConfig.brand || 'Element',
|
||||
global.launcher = new AutoLaunch({
|
||||
name: global.vectorConfig.brand || 'Element',
|
||||
isHidden: true,
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
@@ -261,7 +213,7 @@ async function setupGlobals(): Promise<void> {
|
||||
async function moveAutoLauncher(): Promise<void> {
|
||||
// Look for an auto-launcher under 'Riot' and if we find one, port it's
|
||||
// enabled/disabled-ness over to the new 'Element' launcher
|
||||
if (!vectorConfig.brand || vectorConfig.brand === 'Element') {
|
||||
if (!global.vectorConfig.brand || global.vectorConfig.brand === 'Element') {
|
||||
const oldLauncher = new AutoLaunch({
|
||||
name: 'Riot',
|
||||
isHidden: true,
|
||||
@@ -272,24 +224,13 @@ async function moveAutoLauncher(): Promise<void> {
|
||||
const wasEnabled = await oldLauncher.isEnabled();
|
||||
if (wasEnabled) {
|
||||
await oldLauncher.disable();
|
||||
await launcher.enable();
|
||||
await global.launcher.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
||||
const store = new Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration?: boolean;
|
||||
}>({ name: "electron-config" });
|
||||
global.store = new Store({ name: "electron-config" });
|
||||
|
||||
let eventIndex: SeshatType = null;
|
||||
|
||||
let mainWindow: BrowserWindow = null;
|
||||
global.appQuitting = false;
|
||||
|
||||
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
@@ -299,12 +240,12 @@ const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
];
|
||||
|
||||
const warnBeforeExit = (event: Event, input: Input): void => {
|
||||
const shouldWarnBeforeExit = store.get('warnBeforeExit', true);
|
||||
const shouldWarnBeforeExit = global.store.get('warnBeforeExit', true);
|
||||
const exitShortcutPressed =
|
||||
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed) {
|
||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(mainWindow, {
|
||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(global.mainWindow, {
|
||||
type: "question",
|
||||
buttons: [_t("Cancel"), _t("Close Element")],
|
||||
message: _t("Are you sure you want to quit?"),
|
||||
@@ -318,25 +259,6 @@ const warnBeforeExit = (event: Event, input: Input): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteContents = async (p: string): Promise<void> => {
|
||||
for (const entry of await afs.readdir(p)) {
|
||||
const curPath = path.join(p, entry);
|
||||
await afs.unlink(curPath);
|
||||
}
|
||||
};
|
||||
|
||||
async function randomArray(size: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(size, (err, buf) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(buf.toString("base64").replace(/=+$/g, ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// handle uncaught errors otherwise it displays
|
||||
// stack traces in popup dialogs, which is terrible (which
|
||||
// it will do any time the auto update poke fails, and there's
|
||||
@@ -347,510 +269,6 @@ process.on('uncaughtException', function(error: Error): void {
|
||||
console.log('Unhandled exception', error);
|
||||
});
|
||||
|
||||
let focusHandlerAttached = false;
|
||||
ipcMain.on('setBadgeCount', function(_ev: IpcMainEvent, count: number): void {
|
||||
if (process.platform !== 'win32') {
|
||||
// only set badgeCount on Mac/Linux, the docs say that only those platforms support it but turns out Electron
|
||||
// has some Windows support too, and in some Windows environments this leads to two badges rendering atop
|
||||
// each other. See https://github.com/vector-im/element-web/issues/16942
|
||||
app.badgeCount = count;
|
||||
}
|
||||
if (count === 0 && mainWindow) {
|
||||
mainWindow.flashFrame(false);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('loudNotification', function(): void {
|
||||
if (process.platform === 'win32' && mainWindow && !mainWindow.isFocused() && !focusHandlerAttached) {
|
||||
mainWindow.flashFrame(true);
|
||||
mainWindow.once('focus', () => {
|
||||
mainWindow.flashFrame(false);
|
||||
focusHandlerAttached = false;
|
||||
});
|
||||
focusHandlerAttached = true;
|
||||
}
|
||||
});
|
||||
|
||||
let powerSaveBlockerId: number = null;
|
||||
ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) {
|
||||
switch (payload.action) {
|
||||
case 'call_state':
|
||||
if (powerSaveBlockerId !== null && powerSaveBlocker.isStarted(powerSaveBlockerId)) {
|
||||
if (payload.state === 'ended') {
|
||||
powerSaveBlocker.stop(powerSaveBlockerId);
|
||||
powerSaveBlockerId = null;
|
||||
}
|
||||
} else {
|
||||
if (powerSaveBlockerId === null && payload.state === 'connected') {
|
||||
powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
interface Setting {
|
||||
read(): Promise<any>;
|
||||
write(value: any): Promise<void>;
|
||||
}
|
||||
|
||||
const settings: Record<string, Setting> = {
|
||||
"Electron.autoLaunch": {
|
||||
async read(): Promise<any> {
|
||||
return launcher.isEnabled();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
if (value) {
|
||||
return launcher.enable();
|
||||
} else {
|
||||
return launcher.disable();
|
||||
}
|
||||
},
|
||||
},
|
||||
"Electron.warnBeforeExit": {
|
||||
async read(): Promise<any> {
|
||||
return store.get("warnBeforeExit", true);
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
store.set("warnBeforeExit", value);
|
||||
},
|
||||
},
|
||||
"Electron.alwaysShowMenuBar": { // not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return !global.mainWindow.autoHideMenuBar;
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
store.set('autoHideMenuBar', !value);
|
||||
global.mainWindow.autoHideMenuBar = !value;
|
||||
global.mainWindow.setMenuBarVisibility(value);
|
||||
},
|
||||
},
|
||||
"Electron.showTrayIcon": { // not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return tray.hasTray();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
if (value) {
|
||||
// Create trayIcon icon
|
||||
tray.create(trayConfig);
|
||||
} else {
|
||||
tray.destroy();
|
||||
}
|
||||
store.set('minimizeToTray', value);
|
||||
},
|
||||
},
|
||||
"Electron.enableHardwareAcceleration": {
|
||||
async read(): Promise<any> {
|
||||
return !store.get('disableHardwareAcceleration', false);
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
store.set('disableHardwareAcceleration', !value);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
if (!mainWindow) return;
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
|
||||
switch (payload.name) {
|
||||
case 'getUpdateFeedUrl':
|
||||
ret = autoUpdater.getFeedURL();
|
||||
break;
|
||||
case 'getSettingValue': {
|
||||
const [settingName] = args;
|
||||
const setting = settings[settingName];
|
||||
ret = await setting.read();
|
||||
break;
|
||||
}
|
||||
case 'setSettingValue': {
|
||||
const [settingName, value] = args;
|
||||
const setting = settings[settingName];
|
||||
await setting.write(value);
|
||||
break;
|
||||
}
|
||||
case 'setLanguage':
|
||||
appLocalization.setAppLocale(args[0]);
|
||||
break;
|
||||
case 'getAppVersion':
|
||||
ret = app.getVersion();
|
||||
break;
|
||||
case 'focusWindow':
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
} else if (!mainWindow.isVisible()) {
|
||||
mainWindow.show();
|
||||
} else {
|
||||
mainWindow.focus();
|
||||
}
|
||||
break;
|
||||
case 'getConfig':
|
||||
ret = vectorConfig;
|
||||
break;
|
||||
case 'navigateBack':
|
||||
if (mainWindow.webContents.canGoBack()) {
|
||||
mainWindow.webContents.goBack();
|
||||
}
|
||||
break;
|
||||
case 'navigateForward':
|
||||
if (mainWindow.webContents.canGoForward()) {
|
||||
mainWindow.webContents.goForward();
|
||||
}
|
||||
break;
|
||||
case 'setSpellCheckLanguages':
|
||||
if (args[0] && args[0].length > 0) {
|
||||
mainWindow.webContents.session.setSpellCheckerEnabled(true);
|
||||
store.set("spellCheckerEnabled", true);
|
||||
|
||||
try {
|
||||
mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
|
||||
} catch (er) {
|
||||
console.log("There were problems setting the spellcheck languages", er);
|
||||
}
|
||||
} else {
|
||||
mainWindow.webContents.session.setSpellCheckerEnabled(false);
|
||||
store.set("spellCheckerEnabled", false);
|
||||
}
|
||||
break;
|
||||
case 'getSpellCheckLanguages':
|
||||
if (store.get("spellCheckerEnabled", true)) {
|
||||
ret = mainWindow.webContents.session.getSpellCheckerLanguages();
|
||||
} else {
|
||||
ret = [];
|
||||
}
|
||||
break;
|
||||
case 'getAvailableSpellCheckLanguages':
|
||||
ret = mainWindow.webContents.session.availableSpellCheckerLanguages;
|
||||
break;
|
||||
|
||||
case 'startSSOFlow':
|
||||
recordSSOSession(args[0]);
|
||||
break;
|
||||
|
||||
case 'getPickleKey':
|
||||
try {
|
||||
ret = await keytar.getPassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
if (ret === null) {
|
||||
ret = await keytar.getPassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// if an error is thrown (e.g. keytar can't connect to the keychain),
|
||||
// then return null, which means the default pickle key will be used
|
||||
ret = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'createPickleKey':
|
||||
try {
|
||||
const pickleKey = await randomArray(32);
|
||||
await keytar.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
ret = pickleKey;
|
||||
} catch (e) {
|
||||
ret = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'destroyPickleKey':
|
||||
try {
|
||||
await keytar.deletePassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
await keytar.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
} catch (e) {}
|
||||
break;
|
||||
case 'getDesktopCapturerSources':
|
||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
thumbnailURL: source.thumbnail.toDataURL(),
|
||||
}));
|
||||
break;
|
||||
|
||||
default:
|
||||
mainWindow.webContents.send('ipcReply', {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
mainWindow.webContents.send('ipcReply', {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
|
||||
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
|
||||
async function getOrCreatePassphrase(key: string): Promise<string> {
|
||||
if (keytar) {
|
||||
try {
|
||||
const storedPassphrase = await keytar.getPassword("element.io", key);
|
||||
if (storedPassphrase !== null) {
|
||||
return storedPassphrase;
|
||||
} else {
|
||||
const newPassphrase = await randomArray(32);
|
||||
await keytar.setPassword("element.io", key, newPassphrase);
|
||||
return newPassphrase;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error getting the event index passphrase out of the secret store", e);
|
||||
}
|
||||
} else {
|
||||
return seshatDefaultPassphrase;
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
if (!mainWindow) return;
|
||||
|
||||
const sendError = (id, e) => {
|
||||
const error = {
|
||||
message: e.message,
|
||||
};
|
||||
|
||||
mainWindow.webContents.send('seshatReply', {
|
||||
id: id,
|
||||
error: error,
|
||||
});
|
||||
};
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
|
||||
switch (payload.name) {
|
||||
case 'supportsEventIndexing':
|
||||
ret = seshatSupported;
|
||||
break;
|
||||
|
||||
case 'initEventIndex':
|
||||
if (eventIndex === null) {
|
||||
const userId = args[0];
|
||||
const deviceId = args[1];
|
||||
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
||||
|
||||
const passphrase = await getOrCreatePassphrase(passphraseKey);
|
||||
|
||||
try {
|
||||
await afs.mkdir(eventStorePath, { recursive: true });
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} catch (e) {
|
||||
if (e instanceof ReindexError) {
|
||||
// If this is a reindex error, the index schema
|
||||
// changed. Try to open the database in recovery mode,
|
||||
// reindex the database and finally try to open the
|
||||
// database again.
|
||||
const recoveryIndex = new SeshatRecovery(eventStorePath, {
|
||||
passphrase,
|
||||
});
|
||||
|
||||
const userVersion = await recoveryIndex.getUserVersion();
|
||||
|
||||
// If our user version is 0 we'll delete the db
|
||||
// anyways so reindexing it is a waste of time.
|
||||
if (userVersion === 0) {
|
||||
await recoveryIndex.shutdown();
|
||||
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
}
|
||||
} else {
|
||||
await recoveryIndex.reindex();
|
||||
}
|
||||
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} else {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'closeEventIndex':
|
||||
if (eventIndex !== null) {
|
||||
const index = eventIndex;
|
||||
eventIndex = null;
|
||||
|
||||
try {
|
||||
await index.shutdown();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEventIndex':
|
||||
{
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'isEventIndexEmpty':
|
||||
if (eventIndex === null) ret = true;
|
||||
else ret = await eventIndex.isEmpty();
|
||||
break;
|
||||
|
||||
case 'isRoomIndexed':
|
||||
if (eventIndex === null) ret = false;
|
||||
else ret = await eventIndex.isRoomIndexed(args[0]);
|
||||
break;
|
||||
|
||||
case 'addEventToIndex':
|
||||
try {
|
||||
eventIndex.addEvent(args[0], args[1]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEvent':
|
||||
try {
|
||||
ret = await eventIndex.deleteEvent(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'commitLiveEvents':
|
||||
try {
|
||||
ret = await eventIndex.commit();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'searchEventIndex':
|
||||
try {
|
||||
ret = await eventIndex.search(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addHistoricEvents':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.addHistoricEvents(
|
||||
args[0], args[1], args[2]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getStats':
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.getStats();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'removeCrawlerCheckpoint':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.removeCrawlerCheckpoint(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addCrawlerCheckpoint':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.addCrawlerCheckpoint(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadFileEvents':
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.loadFileEvents(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadCheckpoints':
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.loadCheckpoints();
|
||||
} catch (e) {
|
||||
ret = [];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'setUserVersion':
|
||||
if (eventIndex === null) break;
|
||||
else {
|
||||
try {
|
||||
await eventIndex.setUserVersion(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getUserVersion':
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.getUserVersion();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
mainWindow.webContents.send('seshatReply', {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
mainWindow.webContents.send('seshatReply', {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
|
||||
app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
||||
if (!app.commandLine.hasSwitch('enable-features')) {
|
||||
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
|
||||
@@ -894,7 +312,7 @@ app.enableSandbox();
|
||||
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
||||
|
||||
// Disable hardware acceleration if the setting has been set.
|
||||
if (store.get('disableHardwareAcceleration', false) === true) {
|
||||
if (global.store.get('disableHardwareAcceleration', false) === true) {
|
||||
console.log("Disabling hardware acceleration.");
|
||||
app.disableHardwareAcceleration();
|
||||
}
|
||||
@@ -982,9 +400,9 @@ app.on('ready', async () => {
|
||||
|
||||
if (argv['no-update']) {
|
||||
console.log('Auto update disabled via command line flag "--no-update"');
|
||||
} else if (vectorConfig['update_base_url']) {
|
||||
console.log(`Starting auto update with base URL: ${vectorConfig['update_base_url']}`);
|
||||
updater.start(vectorConfig['update_base_url']);
|
||||
} else if (global.vectorConfig['update_base_url']) {
|
||||
console.log(`Starting auto update with base URL: ${global.vectorConfig['update_base_url']}`);
|
||||
updater.start(global.vectorConfig['update_base_url']);
|
||||
} else {
|
||||
console.log('No update_base_url is defined: auto update is disabled');
|
||||
}
|
||||
@@ -996,13 +414,13 @@ app.on('ready', async () => {
|
||||
});
|
||||
|
||||
const preloadScript = path.normalize(`${__dirname}/preload.js`);
|
||||
mainWindow = global.mainWindow = new BrowserWindow({
|
||||
global.mainWindow = new BrowserWindow({
|
||||
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
backgroundColor: '#fff',
|
||||
|
||||
icon: iconPath,
|
||||
show: false,
|
||||
autoHideMenuBar: store.get('autoHideMenuBar', true),
|
||||
autoHideMenuBar: global.store.get('autoHideMenuBar', true),
|
||||
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
@@ -1016,32 +434,32 @@ app.on('ready', async () => {
|
||||
webgl: true,
|
||||
},
|
||||
});
|
||||
mainWindow.loadURL('vector://vector/webapp/');
|
||||
global.mainWindow.loadURL('vector://vector/webapp/');
|
||||
|
||||
// Handle spellchecker
|
||||
// For some reason spellCheckerEnabled isn't persisted so we have to use the store here
|
||||
mainWindow.webContents.session.setSpellCheckerEnabled(store.get("spellCheckerEnabled", true));
|
||||
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true));
|
||||
|
||||
// Create trayIcon icon
|
||||
if (store.get('minimizeToTray', true)) tray.create(trayConfig);
|
||||
if (global.store.get('minimizeToTray', true)) tray.create(global.trayConfig);
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindowState.manage(mainWindow);
|
||||
global.mainWindow.once('ready-to-show', () => {
|
||||
mainWindowState.manage(global.mainWindow);
|
||||
|
||||
if (!argv['hidden']) {
|
||||
mainWindow.show();
|
||||
global.mainWindow.show();
|
||||
} else {
|
||||
// hide here explicitly because window manage above sometimes shows it
|
||||
mainWindow.hide();
|
||||
global.mainWindow.hide();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
||||
global.mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = global.mainWindow = null;
|
||||
global.mainWindow.on('closed', () => {
|
||||
global.mainWindow = null;
|
||||
});
|
||||
mainWindow.on('close', async (e) => {
|
||||
global.mainWindow.on('close', async (e) => {
|
||||
// If we are not quitting and have a tray icon then minimize to tray
|
||||
if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
|
||||
// On Mac, closing the window just hides it
|
||||
@@ -1049,12 +467,12 @@ app.on('ready', async () => {
|
||||
// behave, eg. Mail.app)
|
||||
e.preventDefault();
|
||||
|
||||
if (mainWindow.isFullScreen()) {
|
||||
mainWindow.once('leave-full-screen', () => mainWindow.hide());
|
||||
if (global.mainWindow.isFullScreen()) {
|
||||
global.mainWindow.once('leave-full-screen', () => global.mainWindow.hide());
|
||||
|
||||
mainWindow.setFullScreen(false);
|
||||
global.mainWindow.setFullScreen(false);
|
||||
} else {
|
||||
mainWindow.hide();
|
||||
global.mainWindow.hide();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1063,19 +481,19 @@ app.on('ready', async () => {
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Handle forward/backward mouse buttons in Windows
|
||||
mainWindow.on('app-command', (e, cmd) => {
|
||||
if (cmd === 'browser-backward' && mainWindow.webContents.canGoBack()) {
|
||||
mainWindow.webContents.goBack();
|
||||
} else if (cmd === 'browser-forward' && mainWindow.webContents.canGoForward()) {
|
||||
mainWindow.webContents.goForward();
|
||||
global.mainWindow.on('app-command', (e, cmd) => {
|
||||
if (cmd === 'browser-backward' && global.mainWindow.webContents.canGoBack()) {
|
||||
global.mainWindow.webContents.goBack();
|
||||
} else if (cmd === 'browser-forward' && global.mainWindow.webContents.canGoForward()) {
|
||||
global.mainWindow.webContents.goForward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
webContentsHandler(mainWindow.webContents);
|
||||
webContentsHandler(global.mainWindow.webContents);
|
||||
|
||||
appLocalization = new AppLocalization({
|
||||
store,
|
||||
global.appLocalization = new AppLocalization({
|
||||
store: global.store,
|
||||
components: [
|
||||
() => tray.initApplicationMenu(),
|
||||
() => Menu.setApplicationMenu(buildMenuTemplate()),
|
||||
@@ -1088,14 +506,12 @@ app.on('window-all-closed', () => {
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
mainWindow.show();
|
||||
global.mainWindow.show();
|
||||
});
|
||||
|
||||
function beforeQuit(): void {
|
||||
global.appQuitting = true;
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('before-quit');
|
||||
}
|
||||
global.mainWindow?.webContents.send('before-quit');
|
||||
}
|
||||
|
||||
app.on('before-quit', beforeQuit);
|
||||
@@ -1106,10 +522,10 @@ app.on('second-instance', (ev, commandLine, workingDirectory) => {
|
||||
if (commandLine.includes('--hidden')) return;
|
||||
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (mainWindow) {
|
||||
if (!mainWindow.isVisible()) mainWindow.show();
|
||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||
mainWindow.focus();
|
||||
if (global.mainWindow) {
|
||||
if (!global.mainWindow.isVisible()) global.mainWindow.show();
|
||||
if (global.mainWindow.isMinimized()) global.mainWindow.restore();
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
47
src/i18n/strings/bg.json
Normal file
47
src/i18n/strings/bg.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Add to dictionary": "Добави към речника",
|
||||
"The image failed to save": "Изображението не успя да се запази",
|
||||
"Failed to save image": "Неуспешно запазване на изображението",
|
||||
"Save image as...": "Запази изображението като...",
|
||||
"Copy link address": "Копирай линка",
|
||||
"Copy image address": "Копирай адреса на изображението",
|
||||
"Copy email address": "Копирай имейл адрес",
|
||||
"Copy image": "Копирай изображение",
|
||||
"File": "Файл",
|
||||
"Bring All to Front": "Покажи всички най-отгоре",
|
||||
"Zoom": "Мащабирай",
|
||||
"Stop Speaking": "Спри да говориш",
|
||||
"Start Speaking": "Започни да говориш",
|
||||
"Speech": "Говор",
|
||||
"Unhide": "Покажи",
|
||||
"Hide Others": "Скрий Останалите",
|
||||
"Hide": "Скрий",
|
||||
"Services": "Услуги",
|
||||
"About": "Относно",
|
||||
"Element Help": "Помощ за Елемент",
|
||||
"Help": "Помощ",
|
||||
"Close": "Затвори",
|
||||
"Minimize": "Минимизирай",
|
||||
"Window": "Прозорец",
|
||||
"Toggle Developer Tools": "Превключи инструментите за разработчици",
|
||||
"Toggle Full Screen": "Превключи на Цял екран",
|
||||
"Preferences": "Предпочитания",
|
||||
"Zoom Out": "Намали",
|
||||
"Zoom In": "Увеличи",
|
||||
"Actual Size": "Действителен Размер",
|
||||
"View": "Преглед",
|
||||
"Select All": "Избери Всичко",
|
||||
"Delete": "Изтрий",
|
||||
"Paste and Match Style": "Постави и Използвай текущия стил",
|
||||
"Paste": "Постави",
|
||||
"Copy": "Копирай",
|
||||
"Cut": "Изрежи",
|
||||
"Redo": "Върни",
|
||||
"Undo": "Отмени",
|
||||
"Edit": "Редактирай",
|
||||
"Quit": "Напусни",
|
||||
"Show/Hide": "Покажи/Скрий",
|
||||
"Are you sure you want to quit?": "Сигурен ли си че искаш да напуснеш?",
|
||||
"Close Element": "Затвори Елемент",
|
||||
"Cancel": "Отказ"
|
||||
}
|
||||
@@ -27,5 +27,21 @@
|
||||
"Redo": "පසුසේ",
|
||||
"Undo": "පෙරසේ",
|
||||
"Edit": "සංස්කරණය",
|
||||
"Quit": "ඉවත් වන්න"
|
||||
"Quit": "ඉවත් වන්න",
|
||||
"Paste and Match Style": "අලවා ශෛලිය ගැළපුම",
|
||||
"Delete": "මකන්න",
|
||||
"The image failed to save": "රූපය සුරැකීමට අසමත්",
|
||||
"Failed to save image": "රූපය සුරැකීමට අසමත්",
|
||||
"Save image as...": "...ලෙස රූපය සුරකින්න",
|
||||
"Copy image address": "රූපයේ ලිපිනයේ පිටපතක්",
|
||||
"Copy image": "රූපයෙහි පිටපතක්",
|
||||
"Bring All to Front": "සියල්ල ඉදිරිපසට",
|
||||
"Stop Speaking": "කථාව නිමාව",
|
||||
"Start Speaking": "කථාව ආරම්භය",
|
||||
"Speech": "කථාව",
|
||||
"Unhide": "නොසඟවන්න",
|
||||
"Toggle Developer Tools": "සංවර්ධක මෙවලම්",
|
||||
"Toggle Full Screen": "පූර්ණ තිරයට",
|
||||
"Preferences": "පෙනුම",
|
||||
"View": "දකින්න"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"Copy email address": "复制邮箱地址",
|
||||
"Copy image": "复制图片",
|
||||
"File": "文件",
|
||||
"Bring All to Front": "置前",
|
||||
"Bring All to Front": "全部置前",
|
||||
"Zoom": "放大",
|
||||
"Stop Speaking": "停止讲话",
|
||||
"Start Speaking": "开始讲话",
|
||||
@@ -22,7 +22,7 @@
|
||||
"Close": "关闭",
|
||||
"Minimize": "最小化",
|
||||
"Window": "窗口",
|
||||
"Toggle Developer Tools": "切换开发工具",
|
||||
"Toggle Developer Tools": "切换开发者工具",
|
||||
"Toggle Full Screen": "切换全屏",
|
||||
"Preferences": "外观",
|
||||
"Zoom Out": "缩小",
|
||||
@@ -34,7 +34,7 @@
|
||||
"Paste and Match Style": "粘贴并匹配样式",
|
||||
"Paste": "粘贴",
|
||||
"Copy": "复制",
|
||||
"Cut": "剪贴",
|
||||
"Cut": "剪切",
|
||||
"Redo": "重做",
|
||||
"Undo": "撤销",
|
||||
"Edit": "编辑",
|
||||
@@ -42,5 +42,6 @@
|
||||
"Show/Hide": "显示/隐藏",
|
||||
"Are you sure you want to quit?": "你确定要退出吗?",
|
||||
"Close Element": "关闭 Element",
|
||||
"Cancel": "取消"
|
||||
"Cancel": "取消",
|
||||
"Copy image address": "复制图片地址"
|
||||
}
|
||||
|
||||
@@ -42,5 +42,6 @@
|
||||
"Show/Hide": "顯示/隱藏",
|
||||
"Are you sure you want to quit?": "您確定要退出嗎?",
|
||||
"Close Element": "關閉 Element",
|
||||
"Cancel": "取消"
|
||||
"Cancel": "取消",
|
||||
"Copy image address": "複製圖片地址"
|
||||
}
|
||||
|
||||
202
src/ipc.ts
Normal file
202
src/ipc.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
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 'setSpellCheckLanguages':
|
||||
if (args[0] && args[0].length > 0) {
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(true);
|
||||
global.store.set("spellCheckerEnabled", true);
|
||||
|
||||
try {
|
||||
global.mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
|
||||
} catch (er) {
|
||||
console.log("There were problems setting the spellcheck languages", er);
|
||||
}
|
||||
} else {
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(false);
|
||||
global.store.set("spellCheckerEnabled", false);
|
||||
}
|
||||
break;
|
||||
case 'getSpellCheckLanguages':
|
||||
if (global.store.get("spellCheckerEnabled", true)) {
|
||||
ret = global.mainWindow.webContents.session.getSpellCheckerLanguages();
|
||||
} else {
|
||||
ret = [];
|
||||
}
|
||||
break;
|
||||
case 'getAvailableSpellCheckLanguages':
|
||||
ret = global.mainWindow.webContents.session.availableSpellCheckerLanguages;
|
||||
break;
|
||||
|
||||
case 'startSSOFlow':
|
||||
recordSSOSession(args[0]);
|
||||
break;
|
||||
|
||||
case 'getPickleKey':
|
||||
try {
|
||||
ret = await keytar.getPassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
if (ret === null) {
|
||||
ret = await keytar.getPassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// if an error is thrown (e.g. keytar can't connect to the keychain),
|
||||
// then return null, which means the default pickle key will be used
|
||||
ret = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'createPickleKey':
|
||||
try {
|
||||
const pickleKey = await randomArray(32);
|
||||
await keytar.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
ret = pickleKey;
|
||||
} catch (e) {
|
||||
ret = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'destroyPickleKey':
|
||||
try {
|
||||
await keytar.deletePassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
await keytar.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
} catch (e) {}
|
||||
break;
|
||||
case 'getDesktopCapturerSources':
|
||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
thumbnailURL: source.thumbnail.toDataURL(),
|
||||
}));
|
||||
break;
|
||||
|
||||
default:
|
||||
global.mainWindow.webContents.send('ipcReply', {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
global.mainWindow.webContents.send('ipcReply', {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
|
||||
31
src/keytar.ts
Normal file
31
src/keytar.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import type * as Keytar from "keytar"; // Hak dependency type
|
||||
|
||||
let keytar: typeof Keytar | undefined;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
keytar = require('keytar');
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
console.log("Keytar isn't installed; secure key storage is disabled.");
|
||||
} else {
|
||||
console.warn("Keytar unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
export { keytar };
|
||||
325
src/seshat.ts
Normal file
325
src/seshat.ts
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { app, ipcMain } from "electron";
|
||||
import { promises as afs } from "fs";
|
||||
import path from "path";
|
||||
|
||||
import type {
|
||||
Seshat as SeshatType,
|
||||
SeshatRecovery as SeshatRecoveryType,
|
||||
ReindexError as ReindexErrorType,
|
||||
} from "matrix-seshat"; // Hak dependency type
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
import { randomArray } from "./utils";
|
||||
import { keytar } from "./keytar";
|
||||
|
||||
let seshatSupported = false;
|
||||
let Seshat: typeof SeshatType;
|
||||
let SeshatRecovery: typeof SeshatRecoveryType;
|
||||
let ReindexError: typeof ReindexErrorType;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const seshatModule = require('matrix-seshat');
|
||||
Seshat = seshatModule.Seshat;
|
||||
SeshatRecovery = seshatModule.SeshatRecovery;
|
||||
ReindexError = seshatModule.ReindexError;
|
||||
seshatSupported = true;
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
console.log("Seshat isn't installed, event indexing is disabled.");
|
||||
} else {
|
||||
console.warn("Seshat unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
||||
|
||||
let eventIndex: SeshatType = null;
|
||||
|
||||
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
|
||||
async function getOrCreatePassphrase(key: string): Promise<string> {
|
||||
if (keytar) {
|
||||
try {
|
||||
const storedPassphrase = await keytar.getPassword("element.io", key);
|
||||
if (storedPassphrase !== null) {
|
||||
return storedPassphrase;
|
||||
} else {
|
||||
const newPassphrase = await randomArray(32);
|
||||
await keytar.setPassword("element.io", key, newPassphrase);
|
||||
return newPassphrase;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error getting the event index passphrase out of the secret store", e);
|
||||
}
|
||||
} else {
|
||||
return seshatDefaultPassphrase;
|
||||
}
|
||||
}
|
||||
|
||||
const deleteContents = async (p: string): Promise<void> => {
|
||||
for (const entry of await afs.readdir(p)) {
|
||||
const curPath = path.join(p, entry);
|
||||
await afs.unlink(curPath);
|
||||
}
|
||||
};
|
||||
|
||||
ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
if (!global.mainWindow) return;
|
||||
|
||||
const sendError = (id, e) => {
|
||||
const error = {
|
||||
message: e.message,
|
||||
};
|
||||
|
||||
global.mainWindow.webContents.send('seshatReply', {
|
||||
id: id,
|
||||
error: error,
|
||||
});
|
||||
};
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
|
||||
switch (payload.name) {
|
||||
case 'supportsEventIndexing':
|
||||
ret = seshatSupported;
|
||||
break;
|
||||
|
||||
case 'initEventIndex':
|
||||
if (eventIndex === null) {
|
||||
const userId = args[0];
|
||||
const deviceId = args[1];
|
||||
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
||||
|
||||
const passphrase = await getOrCreatePassphrase(passphraseKey);
|
||||
|
||||
try {
|
||||
await afs.mkdir(eventStorePath, { recursive: true });
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} catch (e) {
|
||||
if (e instanceof ReindexError) {
|
||||
// If this is a reindex error, the index schema
|
||||
// changed. Try to open the database in recovery mode,
|
||||
// reindex the database and finally try to open the
|
||||
// database again.
|
||||
const recoveryIndex = new SeshatRecovery(eventStorePath, {
|
||||
passphrase,
|
||||
});
|
||||
|
||||
const userVersion = await recoveryIndex.getUserVersion();
|
||||
|
||||
// If our user version is 0 we'll delete the db
|
||||
// anyways so reindexing it is a waste of time.
|
||||
if (userVersion === 0) {
|
||||
await recoveryIndex.shutdown();
|
||||
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
}
|
||||
} else {
|
||||
await recoveryIndex.reindex();
|
||||
}
|
||||
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} else {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'closeEventIndex':
|
||||
if (eventIndex !== null) {
|
||||
const index = eventIndex;
|
||||
eventIndex = null;
|
||||
|
||||
try {
|
||||
await index.shutdown();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEventIndex': {
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'isEventIndexEmpty':
|
||||
if (eventIndex === null) ret = true;
|
||||
else ret = await eventIndex.isEmpty();
|
||||
break;
|
||||
|
||||
case 'isRoomIndexed':
|
||||
if (eventIndex === null) ret = false;
|
||||
else ret = await eventIndex.isRoomIndexed(args[0]);
|
||||
break;
|
||||
|
||||
case 'addEventToIndex':
|
||||
try {
|
||||
eventIndex.addEvent(args[0], args[1]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEvent':
|
||||
try {
|
||||
ret = await eventIndex.deleteEvent(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'commitLiveEvents':
|
||||
try {
|
||||
ret = await eventIndex.commit();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'searchEventIndex':
|
||||
try {
|
||||
ret = await eventIndex.search(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addHistoricEvents':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.addHistoricEvents(
|
||||
args[0], args[1], args[2]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getStats':
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.getStats();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'removeCrawlerCheckpoint':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.removeCrawlerCheckpoint(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addCrawlerCheckpoint':
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.addCrawlerCheckpoint(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadFileEvents':
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.loadFileEvents(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadCheckpoints':
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.loadCheckpoints();
|
||||
} catch (e) {
|
||||
ret = [];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'setUserVersion':
|
||||
if (eventIndex === null) break;
|
||||
else {
|
||||
try {
|
||||
await eventIndex.setUserVersion(args[0]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getUserVersion':
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.getUserVersion();
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
global.mainWindow.webContents.send('seshatReply', {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
global.mainWindow.webContents.send('seshatReply', {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
77
src/settings.ts
Normal file
77
src/settings.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import * as tray from "./tray";
|
||||
|
||||
interface Setting {
|
||||
read(): Promise<any>;
|
||||
write(value: any): Promise<void>;
|
||||
}
|
||||
|
||||
export const Settings: Record<string, Setting> = {
|
||||
"Electron.autoLaunch": {
|
||||
async read(): Promise<any> {
|
||||
return global.launcher.isEnabled();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
if (value) {
|
||||
return global.launcher.enable();
|
||||
} else {
|
||||
return global.launcher.disable();
|
||||
}
|
||||
},
|
||||
},
|
||||
"Electron.warnBeforeExit": {
|
||||
async read(): Promise<any> {
|
||||
return global.store.get("warnBeforeExit", true);
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("warnBeforeExit", value);
|
||||
},
|
||||
},
|
||||
"Electron.alwaysShowMenuBar": { // not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return !global.mainWindow.autoHideMenuBar;
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set('autoHideMenuBar', !value);
|
||||
global.mainWindow.autoHideMenuBar = !value;
|
||||
global.mainWindow.setMenuBarVisibility(value);
|
||||
},
|
||||
},
|
||||
"Electron.showTrayIcon": { // not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return tray.hasTray();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
if (value) {
|
||||
// Create trayIcon icon
|
||||
tray.create(global.trayConfig);
|
||||
} else {
|
||||
tray.destroy();
|
||||
}
|
||||
global.store.set('minimizeToTray', value);
|
||||
},
|
||||
},
|
||||
"Electron.enableHardwareAcceleration": {
|
||||
async read(): Promise<any> {
|
||||
return !global.store.get('disableHardwareAcceleration', false);
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set('disableHardwareAcceleration', !value);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -28,7 +28,17 @@ function installUpdate(): void {
|
||||
|
||||
function pollForUpdates(): void {
|
||||
try {
|
||||
autoUpdater.checkForUpdates();
|
||||
// If we've already got a new update downloaded, then stop trying to check for new ones, as according to the doc
|
||||
// at https://github.com/electron/electron/blob/main/docs/api/auto-updater.md#autoupdatercheckforupdates
|
||||
// we'll just keep re-downloading the same update.
|
||||
// As a hunch, this might also be causing https://github.com/vector-im/element-web/issues/12433
|
||||
// due to the update checks colliding with the pending install somehow
|
||||
if (!latestUpdateDownloaded) {
|
||||
autoUpdater.checkForUpdates();
|
||||
} else {
|
||||
console.log("Skipping update check as download already present");
|
||||
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Couldn\'t check for update', e);
|
||||
}
|
||||
@@ -39,7 +49,7 @@ export function start(updateBaseUrl: string): void {
|
||||
updateBaseUrl = updateBaseUrl + '/';
|
||||
}
|
||||
try {
|
||||
let url;
|
||||
let url: string;
|
||||
// For reasons best known to Squirrel, the way it checks for updates
|
||||
// is completely different between macOS and windows. On macOS, it
|
||||
// hits a URL that either gives it a 200 with some json or
|
||||
@@ -64,7 +74,7 @@ export function start(updateBaseUrl: string): void {
|
||||
|
||||
if (url) {
|
||||
console.log(`Update URL: ${url}`);
|
||||
autoUpdater.setFeedURL(url);
|
||||
autoUpdater.setFeedURL({ url });
|
||||
// We check for updates ourselves rather than using 'updater' because we need to
|
||||
// do it in the main process (and we don't really need to check every 10 minutes:
|
||||
// every hour should be just fine for a desktop app)
|
||||
@@ -85,8 +95,7 @@ ipcMain.on('install_update', installUpdate);
|
||||
ipcMain.on('check_updates', pollForUpdates);
|
||||
|
||||
function ipcChannelSendUpdateStatus(status: boolean | string): void {
|
||||
if (!global.mainWindow) return;
|
||||
global.mainWindow.webContents.send('check_updates', status);
|
||||
global.mainWindow?.webContents.send('check_updates', status);
|
||||
}
|
||||
|
||||
interface ICachedUpdate {
|
||||
@@ -105,8 +114,7 @@ autoUpdater.on('update-available', function() {
|
||||
// the only time we will get `update-not-available` if `latestUpdateDownloaded` is already set
|
||||
// is if the user used the Manual Update check and there is no update newer than the one we
|
||||
// have downloaded, so show it to them as the latest again.
|
||||
if (!global.mainWindow) return;
|
||||
global.mainWindow.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
} else {
|
||||
ipcChannelSendUpdateStatus(false);
|
||||
}
|
||||
@@ -115,8 +123,7 @@ autoUpdater.on('update-available', function() {
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName, releaseDate, updateURL) => {
|
||||
if (!global.mainWindow) return;
|
||||
// forward to renderer
|
||||
latestUpdateDownloaded = { releaseNotes, releaseName, releaseDate, updateURL };
|
||||
global.mainWindow.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
});
|
||||
|
||||
29
src/utils.ts
Normal file
29
src/utils.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import crypto from "crypto";
|
||||
|
||||
export async function randomArray(size: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(size, (err, buf) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(buf.toString("base64").replace(/=+$/g, ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -143,7 +143,7 @@ function onLinkContextMenu(ev: Event, params: ContextMenuParams, webContents: We
|
||||
label: _t('Save image as...'),
|
||||
accelerator: 's',
|
||||
async click() {
|
||||
const targetFileName = params.titleText || "image.png";
|
||||
const targetFileName = params.suggestedFilename || params.altText || "image.png";
|
||||
const { filePath } = await dialog.showSaveDialog({
|
||||
defaultPath: targetFileName,
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"typeRoots": ["src/@types"],
|
||||
"lib": [
|
||||
"es2019",
|
||||
"dom"
|
||||
|
||||
Reference in New Issue
Block a user