Compare commits
517 Commits
t3chguy/bu
...
b17a15882b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b17a15882b | ||
|
|
2a6fdd843f | ||
|
|
624dad601e | ||
|
|
3aa30ec18c | ||
|
|
aaf1ace5f3 | ||
|
|
4419f209d5 | ||
|
|
45c99ff0d4 | ||
|
|
3d078d5389 | ||
|
|
5db23f3a3a | ||
|
|
24425c039b | ||
|
|
cf94b619b7 | ||
|
|
d3935d45ce | ||
|
|
784f1556f4 | ||
|
|
25667d7786 | ||
|
|
3ad56f27de | ||
|
|
b49e7dfecc | ||
|
|
a4099168ac | ||
|
|
686a56d564 | ||
|
|
f12d414b1b | ||
|
|
98f6cbd8a7 | ||
|
|
71a6e77cf1 | ||
|
|
7e723e2e47 | ||
|
|
a0b40b2047 | ||
|
|
741bdb6244 | ||
|
|
5bc7434f50 | ||
|
|
ed741394bc | ||
|
|
588b709035 | ||
|
|
bbe493a696 | ||
|
|
7e3508e5fb | ||
|
|
9c69d07eb6 | ||
|
|
73b83d055c | ||
|
|
820c699fb9 | ||
|
|
ac3ccf47a3 | ||
|
|
3c3b1d9ad3 | ||
|
|
ff6dd5e90b | ||
|
|
74748cf205 | ||
|
|
55e3fa1652 | ||
|
|
1240eb83ec | ||
|
|
25d754db0f | ||
|
|
7a0a7497b2 | ||
|
|
2004171d3d | ||
|
|
250bad90e9 | ||
|
|
5c69abb799 | ||
|
|
868d873183 | ||
|
|
339b1c01cc | ||
|
|
ae4ce43204 | ||
|
|
91490db890 | ||
|
|
9fe021a327 | ||
|
|
a106663750 | ||
|
|
9434f2adf9 | ||
|
|
97cb39829a | ||
|
|
e55f3cfb69 | ||
|
|
f21767d423 | ||
|
|
86891dcdd4 | ||
|
|
55ee9c0053 | ||
|
|
1fd2214951 | ||
|
|
fe253172b3 | ||
|
|
c7cdbcf07b | ||
|
|
5e882f8e08 | ||
|
|
e5f6bd882f | ||
|
|
e4f75de04c | ||
|
|
1c55aa780f | ||
|
|
5dd1984896 | ||
|
|
3c78634cd5 | ||
|
|
155bbe3634 | ||
|
|
3287fec669 | ||
|
|
a64ed5428c | ||
|
|
d0a735b25c | ||
|
|
522c6f95ab | ||
|
|
155b6f284a | ||
|
|
8376cef25a | ||
|
|
15f23d4cf9 | ||
|
|
ea2b160dcc | ||
|
|
bc0a8f03db | ||
|
|
39eddfdc4b | ||
|
|
e668edbcec | ||
|
|
763ebc4ca8 | ||
|
|
cfff1c7640 | ||
|
|
ec1971366a | ||
|
|
1f803f7051 | ||
|
|
c90c79e3ae | ||
|
|
22100bff80 | ||
|
|
a4b622dd5e | ||
|
|
c9a03859bd | ||
|
|
2385c4dd9b | ||
|
|
9d2b1621d6 | ||
|
|
35db60092b | ||
|
|
0a97f12323 | ||
|
|
cd671919e2 | ||
|
|
8e964cd782 | ||
|
|
bec7a9be19 | ||
|
|
64d8341deb | ||
|
|
0e22e7dd38 | ||
|
|
7937f4b0ba | ||
|
|
9493680a42 | ||
|
|
20f8a32ed4 | ||
|
|
44b685a272 | ||
|
|
b84b82f583 | ||
|
|
13e036ee4c | ||
|
|
edb79cba9d | ||
|
|
0b8071cdc3 | ||
|
|
54dd920d34 | ||
|
|
65b22386fa | ||
|
|
2785778fb0 | ||
|
|
9f6928679a | ||
|
|
2e531da3ae | ||
|
|
8c4a57dbff | ||
|
|
ac098fd045 | ||
|
|
acb03b6abe | ||
|
|
cf9bf306cf | ||
|
|
46e1a866a3 | ||
|
|
fd1d792589 | ||
|
|
31c5fcff9b | ||
|
|
bea98eaff7 | ||
|
|
23cc2b3884 | ||
|
|
a330b64fa6 | ||
|
|
87d2f64555 | ||
|
|
a17abce33b | ||
|
|
c20cf1b8bc | ||
|
|
78aed022f5 | ||
|
|
2b0a9b83b6 | ||
|
|
15455ad4be | ||
|
|
87c22acaae | ||
|
|
3961e3d7bb | ||
|
|
b0a3b9e484 | ||
|
|
7c5eb799d0 | ||
|
|
2bf3436a5b | ||
|
|
5682c38944 | ||
|
|
f5558add0f | ||
|
|
29dbe3284f | ||
|
|
56e3e8389d | ||
|
|
f1039d3fc2 | ||
|
|
15f944581b | ||
|
|
b8bedd9c22 | ||
|
|
5561e2efa3 | ||
|
|
46192bd10e | ||
|
|
74444237d9 | ||
|
|
9b8ebd97ed | ||
|
|
c770ea5e2c | ||
|
|
942c4eabc3 | ||
|
|
d725093b10 | ||
|
|
a0185ecf3c | ||
|
|
a969bde5fd | ||
|
|
bcde9afce9 | ||
|
|
a50957973b | ||
|
|
08fc8722f1 | ||
|
|
79f917792a | ||
|
|
954e11947e | ||
|
|
58caf2d337 | ||
|
|
da87f2e5be | ||
|
|
3a780766de | ||
|
|
4762bc137f | ||
|
|
e13c6a4797 | ||
|
|
a7ecf597dd | ||
|
|
d261f95d86 | ||
|
|
108b8574f0 | ||
|
|
cf2081effe | ||
|
|
dd73fcc82e | ||
|
|
3d39d6a1f1 | ||
|
|
9b7bca653b | ||
|
|
25e8bd4906 | ||
|
|
6f6923b9fa | ||
|
|
c58bedb312 | ||
|
|
479b823fdb | ||
|
|
2ddced50e2 | ||
|
|
679b6146dd | ||
|
|
3d0d131733 | ||
|
|
062f7ea977 | ||
|
|
38e3d473e6 | ||
|
|
5450102047 | ||
|
|
5fd8cf189a | ||
|
|
77962c402b | ||
|
|
31bef3860e | ||
|
|
81458c29ed | ||
|
|
c61771de38 | ||
|
|
2c577bcf84 | ||
|
|
3360b069d8 | ||
|
|
bb951131d1 | ||
|
|
ada93d84e7 | ||
|
|
8cf16397a9 | ||
|
|
8bbbdcc2af | ||
|
|
73f5aa0ffb | ||
|
|
60c2ebfb19 | ||
|
|
8d39941559 | ||
|
|
4fc2099693 | ||
|
|
05321d2e94 | ||
|
|
54a88af28c | ||
|
|
e8b4fc7660 | ||
|
|
85e1e83d1d | ||
|
|
1d09bb0016 | ||
|
|
bff219808e | ||
|
|
00475e4bfa | ||
|
|
9b50bdb9a0 | ||
|
|
d97a6a743e | ||
|
|
5daf37de3e | ||
|
|
2cbb6041c8 | ||
|
|
0dc2cafb26 | ||
|
|
45a7d1564a | ||
|
|
003d813967 | ||
|
|
cdb0a051e6 | ||
|
|
cad16c7e3b | ||
|
|
6a61d24fb8 | ||
|
|
0bb49702ba | ||
|
|
d1a0196d44 | ||
|
|
229e52d809 | ||
|
|
0c16caf519 | ||
|
|
def0864df3 | ||
|
|
2cb99596d2 | ||
|
|
812dd20b39 | ||
|
|
db78dbec09 | ||
|
|
31be440d37 | ||
|
|
b13f3ba1bd | ||
|
|
82852a0d0b | ||
|
|
dd45096808 | ||
|
|
88829a1726 | ||
|
|
205d1609cd | ||
|
|
c5a5dab318 | ||
|
|
27582b5d8c | ||
|
|
92a7da38ea | ||
|
|
7c19e5f0a8 | ||
|
|
f91c07f929 | ||
|
|
2d21835588 | ||
|
|
79b9ac896a | ||
|
|
7d488cc06d | ||
|
|
ad8287112b | ||
|
|
4629eccb5a | ||
|
|
8aba763e3e | ||
|
|
ec23373969 | ||
|
|
c9ec2d2a84 | ||
|
|
e02e0b86e5 | ||
|
|
5dc47b9e77 | ||
|
|
267e85cd4e | ||
|
|
17b57d10f5 | ||
|
|
2debea1f53 | ||
|
|
08d0e076fa | ||
|
|
b08adafb62 | ||
|
|
c2bb7ebb6d | ||
|
|
3f94cb2619 | ||
|
|
48a138102b | ||
|
|
93a72b3d72 | ||
|
|
343b7d75d0 | ||
|
|
804933023a | ||
|
|
5149911e38 | ||
|
|
c9a53ba25d | ||
|
|
abf15ef471 | ||
|
|
4d0add6309 | ||
|
|
74f2ecb4a9 | ||
|
|
8ce5cf0f10 | ||
|
|
b92d31e7a4 | ||
|
|
fd29e65112 | ||
|
|
75f93ab631 | ||
|
|
4b56f3b1ca | ||
|
|
a1b1245c77 | ||
|
|
053bcd10e1 | ||
|
|
91036dcd22 | ||
|
|
c0f3703c5a | ||
|
|
960a413c8a | ||
|
|
5e8373d6cd | ||
|
|
858e4fdbde | ||
|
|
8e5761be2d | ||
|
|
be6565656d | ||
|
|
02f2274765 | ||
|
|
0d04e3d2ac | ||
|
|
f4b3816888 | ||
|
|
db153374f0 | ||
|
|
0d4f02cde6 | ||
|
|
0061966718 | ||
|
|
9fc64550dd | ||
|
|
51d2f6a29e | ||
|
|
58ef3d277f | ||
|
|
fd62231856 | ||
|
|
0510fa4ee4 | ||
|
|
58d129f565 | ||
|
|
c72c18e998 | ||
|
|
906125c738 | ||
|
|
bf97ef4904 | ||
|
|
273afae84e | ||
|
|
e18ddee37e | ||
|
|
c43e8d684f | ||
|
|
75463f0296 | ||
|
|
2401c1f7ef | ||
|
|
fe7d10539b | ||
|
|
cb5f5bc94a | ||
|
|
b123e0f60c | ||
|
|
39f460b636 | ||
|
|
86f6136257 | ||
|
|
2e039f4bab | ||
|
|
8f24f45090 | ||
|
|
6901bff548 | ||
|
|
cf88e520a0 | ||
|
|
3bc59fb6ce | ||
|
|
f136c7aaad | ||
|
|
5b33e2866d | ||
|
|
c0ca58e930 | ||
|
|
77038a5a86 | ||
|
|
b22d2480ae | ||
|
|
7142b6fe57 | ||
|
|
b4c2bc7165 | ||
|
|
4ea7b679e9 | ||
|
|
89ead2e56f | ||
|
|
360665cd41 | ||
|
|
93c0b81cc4 | ||
|
|
bd194306dd | ||
|
|
b4a23ff505 | ||
|
|
8559a740f4 | ||
|
|
dea64f7e38 | ||
|
|
044de246a1 | ||
|
|
2465607ac5 | ||
|
|
d9ad2faac6 | ||
|
|
0c4c950fc7 | ||
|
|
9660c4b2be | ||
|
|
73ddf2a19b | ||
|
|
cfea34766a | ||
|
|
77bea6db7e | ||
|
|
abd508fc0d | ||
|
|
8e4826b4e9 | ||
|
|
3cd88352b6 | ||
|
|
1d79fa633a | ||
|
|
ec4c610158 | ||
|
|
468d2249d1 | ||
|
|
63e1e0d894 | ||
|
|
e326246669 | ||
|
|
53672fadd7 | ||
|
|
20f28abb47 | ||
|
|
a0db96d50d | ||
|
|
2bb1544064 | ||
|
|
827d514628 | ||
|
|
28e558162a | ||
|
|
85556ecd74 | ||
|
|
46080c66d0 | ||
|
|
80b88583b8 | ||
|
|
2aaf42b8e8 | ||
|
|
660ccd414a | ||
|
|
32cf6b2ebf | ||
|
|
5507f2859f | ||
|
|
689179c5ae | ||
|
|
3116f596f2 | ||
|
|
69cfe0bda6 | ||
|
|
194798497f | ||
|
|
68216568b0 | ||
|
|
9cf81e4484 | ||
|
|
bb2435b529 | ||
|
|
c0efe2334b | ||
|
|
0c2c26af75 | ||
|
|
3c9b229664 | ||
|
|
e5815f3e3c | ||
|
|
b5febd1a07 | ||
|
|
747245e63f | ||
|
|
a1bba6f8d3 | ||
|
|
3c39998b0a | ||
|
|
10f09c90c7 | ||
|
|
cd9b48d87f | ||
|
|
10e2c5c1e4 | ||
|
|
0ad720454c | ||
|
|
ccb1c59076 | ||
|
|
9e315e9b05 | ||
|
|
3a8726f953 | ||
|
|
abaaadd2eb | ||
|
|
2699d04fd1 | ||
|
|
00f3777fcf | ||
|
|
2b928c3f50 | ||
|
|
0b167fe278 | ||
|
|
8db9c620df | ||
|
|
2c60929139 | ||
|
|
9a8ca9980f | ||
|
|
451129d468 | ||
|
|
42a3b8b68c | ||
|
|
56bbea995f | ||
|
|
c72d23f995 | ||
|
|
570a8bdefe | ||
|
|
1d3408bb37 | ||
|
|
eef66d9795 | ||
|
|
a70c5e0215 | ||
|
|
d388b1e64a | ||
|
|
216b1c2157 | ||
|
|
72da1a90db | ||
|
|
37bc884d77 | ||
|
|
a43d087c02 | ||
|
|
906b5bc93c | ||
|
|
41ff61421d | ||
|
|
65cdeaa9de | ||
|
|
262abf5847 | ||
|
|
4aa0454442 | ||
|
|
681365fd2f | ||
|
|
f566c83c81 | ||
|
|
1aab67e874 | ||
|
|
4ded16f12e | ||
|
|
12ea5fa91a | ||
|
|
83775c9104 | ||
|
|
1817e7b216 | ||
|
|
31868f833a | ||
|
|
e02bbf80ff | ||
|
|
42ac29359c | ||
|
|
db4c544b72 | ||
|
|
fca69978aa | ||
|
|
0559886b77 | ||
|
|
f59c38a07e | ||
|
|
bff9c344b6 | ||
|
|
30a464fcdc | ||
|
|
2cdf1cf3b6 | ||
|
|
0e8785e7ac | ||
|
|
7ea9bf1944 | ||
|
|
4fe0384523 | ||
|
|
aabbb8772d | ||
|
|
dde93f30f4 | ||
|
|
4486f6046e | ||
|
|
b2fff67cad | ||
|
|
575a9fa564 | ||
|
|
8d78eedb34 | ||
|
|
791bfc63b3 | ||
|
|
c1b2139ca2 | ||
|
|
559bb5b672 | ||
|
|
98e865ebae | ||
|
|
df576b8dc5 | ||
|
|
76e55014e0 | ||
|
|
6911eb203b | ||
|
|
74aebb59e1 | ||
|
|
e05afba0b5 | ||
|
|
0bb6c47e6f | ||
|
|
bbfb8d3f47 | ||
|
|
789798e595 | ||
|
|
a8a5a955e5 | ||
|
|
07c01e7117 | ||
|
|
e6e46aa45f | ||
|
|
11a76d60b1 | ||
|
|
585b5ea5cb | ||
|
|
4181c6f95a | ||
|
|
20d3e0af9a | ||
|
|
4fd75b608a | ||
|
|
1d7a2e632d | ||
|
|
6be83576eb | ||
|
|
443be796ee | ||
|
|
1e0f25725e | ||
|
|
1fef39fcf1 | ||
|
|
2f872c3548 | ||
|
|
424bb8172a | ||
|
|
0212b77b20 | ||
|
|
536918fb1c | ||
|
|
e4d5863606 | ||
|
|
21244d454f | ||
|
|
f38e76db01 | ||
|
|
50277e00a5 | ||
|
|
4979c8fbed | ||
|
|
610cd5d370 | ||
|
|
7c858d358b | ||
|
|
7098789689 | ||
|
|
618c04d613 | ||
|
|
702c9d2b60 | ||
|
|
75b77274f5 | ||
|
|
fa9f0f4474 | ||
|
|
11c58a90ca | ||
|
|
ef6c8871a2 | ||
|
|
bc49c1d58b | ||
|
|
e275f9cccb | ||
|
|
b83d666b4c | ||
|
|
987ce7dde4 | ||
|
|
a013504e17 | ||
|
|
79abfcfddc | ||
|
|
d8f4644ffc | ||
|
|
f1d9b0a847 | ||
|
|
960ff865fb | ||
|
|
bbcb7c0438 | ||
|
|
e36b3b3ce1 | ||
|
|
566b32041d | ||
|
|
d57ac6f016 | ||
|
|
76a5dcf5c8 | ||
|
|
2fdd591e09 | ||
|
|
50b0a757a8 | ||
|
|
2669b2adeb | ||
|
|
e6ab8743d1 | ||
|
|
f733c2e942 | ||
|
|
5b0ab72745 | ||
|
|
783d0f15b8 | ||
|
|
0fc37c929a | ||
|
|
f2319fc173 | ||
|
|
50836358dc | ||
|
|
d61c284d8e | ||
|
|
ae17facfe0 | ||
|
|
63f29006fc | ||
|
|
0bb7ccb054 | ||
|
|
3c4791f7ce | ||
|
|
782ba453cf | ||
|
|
7c17bdcafe | ||
|
|
46e81cd8f8 | ||
|
|
1496f3d64c | ||
|
|
7847e53adc | ||
|
|
9a1d2291c1 | ||
|
|
ff7f53fb35 | ||
|
|
bce251b35b | ||
|
|
a46985d91e | ||
|
|
a546b28c62 | ||
|
|
9a785738df | ||
|
|
6be60b69e6 | ||
|
|
ea8bd4e062 | ||
|
|
37ec2b9f34 | ||
|
|
8ee1a04592 | ||
|
|
573c7d4522 | ||
|
|
64e5a424ca | ||
|
|
a02fad52bb | ||
|
|
eb3ae80142 | ||
|
|
12b287d639 | ||
|
|
8f31f2ff57 | ||
|
|
8f464b9450 | ||
|
|
b8bb4d3316 | ||
|
|
00f08cb443 | ||
|
|
6fb5202e86 | ||
|
|
e7cda8ee4e | ||
|
|
c4cddd3591 | ||
|
|
f23b279633 | ||
|
|
4302d6106e | ||
|
|
3c4138326b | ||
|
|
bf141856d7 | ||
|
|
da3698f6d5 | ||
|
|
c1f55a64a8 | ||
|
|
57aa266320 | ||
|
|
81f76fe574 | ||
|
|
af7e8a7e0f |
35
.github/SSLcom-sandbox.crt
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGBzCCA++gAwIBAgIIaI6ivggL++4wDQYJKoZIhvcNAQELBQAwgZAxCzAJBgNV
|
||||
BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE
|
||||
CgwPU1NMIENvcnBvcmF0aW9uMUUwQwYDVQQDDDxTU0wuY29tIEVWIFJvb3QgQ2Vy
|
||||
dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyIC0gRGV2ZWxvcG1lbnQwHhcNMTgw
|
||||
MTE2MTIxNjM2WhcNNDMwMTE1MTIxNjM2WjCBkDELMAkGA1UEBhMCVVMxDjAMBgNV
|
||||
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9y
|
||||
YXRpb24xRTBDBgNVBAMMPFNTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1
|
||||
dGhvcml0eSBSU0EgUjIgLSBEZXZlbG9wbWVudDCCAiIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggIPADCCAgoCggIBAK/qcD65JCkueKp0+KXG2kAw8euDHuraLR3lJoUFz4ilGK1M
|
||||
t+RjSuY6dHQw8ku7TnW9ejWoSFjCBSDx7tP/fzOwOxmBW6+F1NDuV/IaUtn3G2lk
|
||||
CZglVk9z3n1HuWDN10xNiLoo5nzeIlvNAoDbXDGhI4Y6Z0qouAIS607JpJMWHOqZ
|
||||
OUiiOuM11gI5Kz9GtVttXCjRmwlkU8WiJVIUuVedQAQt2FChrzNQewGFFi0uIau/
|
||||
wFRclx6hd4JRIImC6VMJd9lcitWsqMcM94pD3fX2ozNgWX+MVlmcDYFSN9Sv8tG4
|
||||
yCj4ONS8HZGzbxeyQXJhEJSi2FnBi0j6MD/d4DNFj0hCg9wz3fgVLDGCO0pNMO0Y
|
||||
oXdrzfoj1/zEv0Ibgh7zKG2JHkPfapn3ExFI5d6xi66u5tPVI8cvLxqrgybRPs7Z
|
||||
y1dQA7ew3LyTPAHoGtbTMvewtx1TkTtRxxhRRm0l58owqSVbSYrixFtosNobCERo
|
||||
uiknaQqoY1ZDsdKsaqFoZDbntNRYhN3Ea4OPWVqDUU5ZPz9MTIRAi3MIq854yyQo
|
||||
BjX9nv+kYa+Esr19pxUW0z7BWFhbXsMVpt0QMVyhwgzXvEreaZHFwHHaGb9d5x5P
|
||||
VBDhsigMmtzBk9NlbCsy+uGXWHgZA/DVefueEq0sv38VoU30uYa5Tj0FLm09AgMB
|
||||
AAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUI9PCucv3G9fRoTDu
|
||||
ZQ4Hw6g4PkIwHQYDVR0OBBYEFCPTwrnL9xvX0aEw7mUOB8OoOD5CMA4GA1UdDwEB
|
||||
/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAk43CCrC0Zbvi7YUsSePKi+KzvyQ9
|
||||
mjKa5NBU/A5/sLeZS3R+wqCX7l5euYVDsUuNgNVD/QL9jNIonuHBrvKaxkmqxE1r
|
||||
IcDEaUdjy2lQ0uqD7UDoS3ctrjGkPpUahrTdr3gaKcQBtUhn9v4Y2OBm6J1hDVwI
|
||||
CIKcxIzRv6AUpApOtk+++m5tzDU48t8+GzrVl1hkspSYcumA+zuHllbPDL1ADdo5
|
||||
kK/bBQtZrGqzPqKzeqaB1A5Wm0Igwf++7nyzdKNdjxtv907D9vg8EB4Swavuv/Ne
|
||||
5/jbpI32pz0NIzzSl5ARAHuFhILsO/cEAlloDoTHzibHqFDIeU9/59HMUsJYMOtD
|
||||
Ii0/LmQ6dBE4TeukCCLJwtkFYZ2eBgDjF/LHBB+z/UBs4milRgwx+Pe5UDUEjtGe
|
||||
G/XMVnTSKZTy9jMaXJD5EmfP+Cfh8EEgFgjg4AmLUbEo9gXzPxyXSLgd8JGSsjg8
|
||||
EV/Ri4Mmmt4XUwlSVvEOezxxDGd17gwbottCIC+rqPHonHkGmKpLMH80Bk0uOOCs
|
||||
ui1oVwSifMyIcudgCcOfRLUf/f2j2NW7N7E7Vw/Zqfn+pqp/EG0KCqOM2vfJAc0s
|
||||
u3rSrOJZGtB6txgtmTjoadxApWf4U/FCi3uArt6gS5MJqZjuiRNXs/K3SlSAqLGl
|
||||
5UiG52ew+VdBHzE=
|
||||
-----END CERTIFICATE-----
|
||||
43
.github/workflows/build_and_deploy.yaml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
name: Windows ${{ matrix.arch }}
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ia32, x64]
|
||||
arch: [x64, arm64]
|
||||
uses: ./.github/workflows/build_windows.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -92,7 +92,6 @@ jobs:
|
||||
uses: ./.github/workflows/build_linux.yaml
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
config: ${{ needs.prepare.outputs.config }}
|
||||
sqlcipher: ${{ matrix.sqlcipher }}
|
||||
version: ${{ needs.prepare.outputs.nightly-version }}
|
||||
|
||||
@@ -108,12 +107,14 @@ jobs:
|
||||
environment: ${{ needs.prepare.outputs.deploy == 'true' && 'packages.element.io' || '' }}
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
|
||||
- name: Prepare artifacts for deployment
|
||||
run: |
|
||||
set -x
|
||||
|
||||
# Windows
|
||||
for arch in x64 ia32 arm64
|
||||
for arch in x64 arm64
|
||||
do
|
||||
if [ -d "win-$arch" ]; then
|
||||
mkdir -p packages.element.io/{install,update}/win32/$arch
|
||||
@@ -145,8 +146,10 @@ jobs:
|
||||
- name: "[Nightly] Strip version from installer file"
|
||||
if: needs.prepare.outputs.nightly-version != ''
|
||||
run: |
|
||||
set -x
|
||||
|
||||
# Windows
|
||||
for arch in x64 ia32 arm64
|
||||
for arch in x64 arm64
|
||||
do
|
||||
[ -d "win-$arch" ] && mv packages.element.io/install/win32/$arch/{*,"Element Nightly Setup"}.exe
|
||||
done
|
||||
@@ -161,8 +164,10 @@ jobs:
|
||||
- name: "[Release] Prepare release latest symlink"
|
||||
if: needs.prepare.outputs.nightly-version == ''
|
||||
run: |
|
||||
set -x
|
||||
|
||||
# Windows
|
||||
for arch in x64 ia32 arm64
|
||||
for arch in x64 arm64
|
||||
do
|
||||
if [ -d "win-$arch" ]; then
|
||||
pushd packages.element.io/install/win32/$arch
|
||||
@@ -192,23 +197,17 @@ jobs:
|
||||
|
||||
- name: Stash packages.element.io
|
||||
if: needs.prepare.outputs.deploy == 'false'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
with:
|
||||
name: packages.element.io
|
||||
path: packages.element.io
|
||||
|
||||
# Workaround for https://www.cloudflarestatus.com/incidents/t5nrjmpxc1cj
|
||||
- uses: unfor19/install-aws-cli-action@e8b481e524a99f37fbd39fdc1dcb3341ab091367 # v1
|
||||
if: needs.prepare.outputs.deploy == 'true'
|
||||
with:
|
||||
version: 2.22.35
|
||||
verbose: false
|
||||
arch: amd64
|
||||
|
||||
# Checksum algorithm specified as per https://developers.cloudflare.com/r2/examples/aws/aws-cli/
|
||||
- name: Deploy artifacts
|
||||
if: needs.prepare.outputs.deploy == 'true'
|
||||
run: |
|
||||
aws s3 cp --recursive packages.element.io/ s3://$R2_BUCKET/$DEPLOYMENT_DIR --endpoint-url $R2_URL --region auto
|
||||
set -x
|
||||
aws s3 cp --recursive packages.element.io/ s3://$R2_BUCKET/$DEPLOYMENT_DIR --endpoint-url $R2_URL --region auto --checksum-algorithm CRC32
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
|
||||
@@ -217,7 +216,7 @@ jobs:
|
||||
|
||||
- name: Notify packages.element.io of new files
|
||||
if: needs.prepare.outputs.deploy == 'true'
|
||||
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
|
||||
uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
repository: element-hq/packages.element.io
|
||||
@@ -227,6 +226,8 @@ jobs:
|
||||
id: deb
|
||||
if: needs.linux.result == 'success'
|
||||
run: |
|
||||
set -x
|
||||
|
||||
for arch in amd64 arm64
|
||||
do
|
||||
echo "$arch=$(ls linux-$arch-sqlcipher-static/*.deb | tail -n1)" >> $GITHUB_OUTPUT
|
||||
@@ -234,7 +235,7 @@ jobs:
|
||||
|
||||
- name: Stash debs
|
||||
if: needs.prepare.outputs.deploy == 'false' && needs.linux.result == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
with:
|
||||
name: debs
|
||||
path: |
|
||||
@@ -273,19 +274,21 @@ jobs:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::264135176173:role/Push-ElementDesktop-MSI
|
||||
role-session-name: githubaction-run-${{ github.run_id }}
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
with:
|
||||
pattern: win-*
|
||||
|
||||
- name: Copy files to S3
|
||||
run: |
|
||||
set -x
|
||||
|
||||
PREFIX="${VERSION%.*}"
|
||||
for file in win-*/*.msi; do
|
||||
filename=$(basename "$file")
|
||||
|
||||
119
.github/workflows/build_and_test.yaml
vendored
@@ -13,8 +13,9 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
config: ${{ github.event.pull_request.base.ref == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
version: ${{ github.event.pull_request.base.ref == 'develop' && 'develop' || '' }}
|
||||
config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
version: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'develop' || '' }}
|
||||
branch-matching: true
|
||||
|
||||
windows:
|
||||
needs: fetch
|
||||
@@ -22,9 +23,10 @@ jobs:
|
||||
uses: ./.github/workflows/build_windows.yaml
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x64, ia32]
|
||||
arch: [x64, ia32, arm64]
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
blob_report: true
|
||||
|
||||
linux:
|
||||
needs: fetch
|
||||
@@ -35,110 +37,49 @@ jobs:
|
||||
sqlcipher: [system, static]
|
||||
arch: [amd64, arm64]
|
||||
with:
|
||||
config: ${{ github.event.pull_request.base.ref == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
sqlcipher: ${{ matrix.sqlcipher }}
|
||||
arch: ${{ matrix.arch }}
|
||||
blob_report: true
|
||||
|
||||
macos:
|
||||
needs: fetch
|
||||
name: macOS
|
||||
uses: ./.github/workflows/build_macos.yaml
|
||||
with:
|
||||
blob_report: true
|
||||
|
||||
test:
|
||||
needs:
|
||||
- macos
|
||||
- linux
|
||||
- windows
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: macOS Universal
|
||||
os: macos-14
|
||||
artifact: macos
|
||||
executable: "/Users/runner/Applications/Element.app/Contents/MacOS/Element"
|
||||
# We need to mount the DMG and copy the app to the Applications folder as a mounted DMG is
|
||||
# read-only and thus would not allow us to override the fuses as is required for Playwright.
|
||||
prepare_cmd: |
|
||||
hdiutil attach ./dist/*.dmg -mountpoint /Volumes/Element &&
|
||||
rsync -a /Volumes/Element/Element.app ~/Applications/ &&
|
||||
hdiutil detach /Volumes/Element
|
||||
- name: "Linux (amd64) (sqlcipher: system)"
|
||||
os: ubuntu-22.04
|
||||
artifact: linux-amd64-sqlcipher-system
|
||||
executable: "/opt/Element/element-desktop"
|
||||
prepare_cmd: "sudo apt-get -qq update && sudo apt install ./dist/*.deb"
|
||||
- name: "Linux (amd64) (sqlcipher: static)"
|
||||
os: ubuntu-22.04
|
||||
artifact: linux-amd64-sqlcipher-static
|
||||
executable: "/opt/Element/element-desktop"
|
||||
prepare_cmd: "sudo apt-get -qq update && sudo apt install ./dist/*.deb"
|
||||
- name: "Linux (arm64) (sqlcipher: system)"
|
||||
os: ubuntu-22.04-arm
|
||||
artifact: linux-arm64-sqlcipher-system
|
||||
executable: "/opt/Element/element-desktop"
|
||||
prepare_cmd: "sudo apt-get -qq update && sudo apt install -y ./dist/*.deb"
|
||||
- name: "Linux (arm64) (sqlcipher: static)"
|
||||
os: ubuntu-22.04-arm
|
||||
artifact: linux-arm64-sqlcipher-static
|
||||
executable: "/opt/Element/element-desktop"
|
||||
prepare_cmd: "sudo apt-get -qq update && sudo apt install -y ./dist/*.deb"
|
||||
- name: Windows (x86)
|
||||
os: windows-2022
|
||||
artifact: win-ia32
|
||||
executable: "./dist/win-ia32-unpacked/Element.exe"
|
||||
- name: Windows (x64)
|
||||
os: windows-2022
|
||||
artifact: win-x64
|
||||
executable: "./dist/win-unpacked/Element.exe"
|
||||
name: Test ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
tests-done:
|
||||
needs: [windows, linux, macos]
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: dist
|
||||
pattern: blob-report-*
|
||||
path: all-blob-reports
|
||||
merge-multiple: true
|
||||
|
||||
- name: Prepare for tests
|
||||
run: ${{ matrix.prepare_cmd }}
|
||||
if: matrix.prepare_cmd
|
||||
|
||||
# We previously disabled the `EnableNodeCliInspectArguments` fuse, but Playwright requires
|
||||
# it to be enabled to test Electron apps, so turn it back on.
|
||||
- name: Set EnableNodeCliInspectArguments fuse enabled
|
||||
run: $RUN_AS npx @electron/fuses write --app ${{ matrix.executable }} EnableNodeCliInspectArguments=on
|
||||
shell: bash
|
||||
env:
|
||||
# We need sudo on Linux as it is installed in /opt/
|
||||
RUN_AS: ${{ runner.os == 'Linux' && 'sudo' || '' }}
|
||||
|
||||
- name: Workaround macOS GHA permission issues
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159,NULL,NULL,'UNUSED',1687786159);"
|
||||
sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159,NULL,NULL,'UNUSED',1687786159);"
|
||||
|
||||
- name: Run tests
|
||||
uses: coactions/setup-xvfb@6b00cf1889f4e1d5a48635647013c0508128ee1a
|
||||
timeout-minutes: 5
|
||||
with:
|
||||
run: "yarn test ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }}"
|
||||
env:
|
||||
ELEMENT_DESKTOP_EXECUTABLE: ${{ matrix.executable }}
|
||||
- name: Merge into HTML Report
|
||||
run: yarn playwright merge-reports -c ./playwright.config.ts --reporter=html ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
with:
|
||||
name: ${{ matrix.artifact }}-test
|
||||
path: playwright/html-report
|
||||
name: html-report
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
|
||||
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
||||
run: exit 1
|
||||
|
||||
151
.github/workflows/build_linux.yaml
vendored
@@ -4,14 +4,14 @@
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
type: string
|
||||
required: false
|
||||
description: "The git ref to checkout, defaults to the default branch"
|
||||
arch:
|
||||
type: string
|
||||
required: true
|
||||
description: "The architecture to build for, one of 'amd64' | 'arm64'"
|
||||
config:
|
||||
type: string
|
||||
required: true
|
||||
description: "The config directory to use"
|
||||
version:
|
||||
type: string
|
||||
required: false
|
||||
@@ -20,21 +20,66 @@ on:
|
||||
type: string
|
||||
required: true
|
||||
description: "How to link sqlcipher, one of 'system' | 'static'"
|
||||
blob_report:
|
||||
type: boolean
|
||||
required: false
|
||||
description: "Whether to run the blob report"
|
||||
prepare-artifact-name:
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
The name of the prepare artifact to use, defaults to 'webapp'.
|
||||
The artifact must contain the following:
|
||||
+ webapp.asar - the asar archive of the webapp to embed in the desktop app
|
||||
+ electronVersion - the version of electron to use for cache keying
|
||||
+ hakHash - the hash of the .hak directory to use for cache keying
|
||||
+ changelog.Debian - the changelog file to embed in the Debian package
|
||||
+ variant.json - the variant configuration to use for the build
|
||||
|
||||
The artifact can also contain any additional files which will be applied as overrides to the checkout root before building,
|
||||
for example icons in the `build/` directory to override the app icons.
|
||||
default: "webapp"
|
||||
test:
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
description: "Whether to run the test stage after building"
|
||||
test-args:
|
||||
type: string
|
||||
required: false
|
||||
description: "Additional arguments to pass to playwright"
|
||||
runs-on:
|
||||
type: string
|
||||
required: false
|
||||
description: "The runner image to use, normally set for you, may be needed for running in private repos."
|
||||
artifact-prefix:
|
||||
type: string
|
||||
required: false
|
||||
description: "An optional prefix to add to the artifact name, useful for distinguishing builds in private repos."
|
||||
default: ""
|
||||
targets:
|
||||
type: string
|
||||
required: false
|
||||
description: "List of targets to build"
|
||||
default: "tar.gz deb"
|
||||
env:
|
||||
SQLCIPHER_BUNDLED: ${{ inputs.sqlcipher == 'static' && '1' || '' }}
|
||||
MAX_GLIBC: 2.31 # bullseye-era glibc, used by glibc-check.sh
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
build:
|
||||
name: Build Linux ${{ inputs.arch }} SQLCipher ${{ inputs.sqlcipher }}
|
||||
# We build on native infrastructure as matrix-seshat fails to cross-compile properly
|
||||
# https://github.com/matrix-org/seshat/issues/135
|
||||
runs-on: ${{ inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04' }}
|
||||
runs-on: ${{ inputs.runs-on || (inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04') }}
|
||||
env:
|
||||
HAK_DOCKER_IMAGE: ghcr.io/element-hq/element-desktop-dockerbuild
|
||||
steps:
|
||||
- name: Resolve docker image tag for push
|
||||
if: github.event_name == 'push'
|
||||
run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:$GITHUB_REF_NAME" >> $GITHUB_ENV
|
||||
run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:$REF" >> $GITHUB_ENV
|
||||
env:
|
||||
REF: ${{ inputs.ref || github.ref_name }}
|
||||
- name: Resolve docker image tag for release
|
||||
if: github.event_name == 'release'
|
||||
run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:staging" >> $GITHUB_ENV
|
||||
@@ -60,23 +105,26 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
name: webapp
|
||||
repository: element-hq/element-desktop
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
with:
|
||||
name: ${{ inputs.prepare-artifact-name }}
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion', 'dockerbuild/*') }}
|
||||
path: |
|
||||
./.hak
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
env:
|
||||
# Workaround for https://github.com/actions/setup-node/issues/317
|
||||
@@ -87,18 +135,18 @@ jobs:
|
||||
|
||||
- name: "Get modified files"
|
||||
id: changed_files
|
||||
if: steps.cache.outputs.cache-hit != 'true' && github.event_name == 'pull_request'
|
||||
uses: tj-actions/changed-files@d6e91a2266cdb9d62096cebf1e8546899c6aa18f # v45
|
||||
if: steps.cache.outputs.cache-hit != 'true' && github.event_name == 'pull_request' && github.repository == 'element-hq/element-desktop'
|
||||
uses: tj-actions/changed-files@823fcebdb31bb35fdf2229d9f769b400309430d0 # v46
|
||||
with:
|
||||
files: |
|
||||
dockerbuild/**
|
||||
|
||||
# This allows contributors to test changes to the dockerbuild image within a pull request
|
||||
- name: Build docker image
|
||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
if: steps.changed_files.outputs.any_modified == 'true'
|
||||
with:
|
||||
context: dockerbuild
|
||||
file: dockerbuild/Dockerfile
|
||||
load: true
|
||||
platforms: linux/${{ inputs.arch }}
|
||||
tags: ${{ env.HAK_DOCKER_IMAGE }}
|
||||
@@ -123,27 +171,20 @@ jobs:
|
||||
./scripts/glibc-check.sh $filename
|
||||
done
|
||||
|
||||
- name: "[Nightly] Resolve version"
|
||||
if: inputs.version != ''
|
||||
run: |
|
||||
echo "ED_NIGHTLY=${{ inputs.version }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate debian files and arguments
|
||||
run: |
|
||||
if [ -f changelog.Debian ]; then
|
||||
echo "ED_DEBIAN_CHANGELOG=changelog.Debian" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# Workaround for https://github.com/electron-userland/electron-builder/issues/6116
|
||||
- name: Install fpm
|
||||
if: inputs.arch == 'arm64'
|
||||
run: |
|
||||
sudo apt-get install ruby-dev build-essential
|
||||
sudo gem install fpm
|
||||
echo "USE_SYSTEM_FPM=true" >> $GITHUB_ENV
|
||||
|
||||
- name: Build App
|
||||
run: yarn build --publish never -l ${{ steps.config.outputs.build-args }}
|
||||
run: yarn build --publish never ${{ steps.config.outputs.build-args }} -l ${{ inputs.targets }}
|
||||
env:
|
||||
VARIANT_PATH: variant.json
|
||||
# Only set for Nightly builds
|
||||
VERSION: ${{ inputs.version }}
|
||||
# Workaround for https://github.com/electron-userland/electron-builder/issues/5721
|
||||
USE_HARD_LINKS: false
|
||||
|
||||
- name: Check native libraries
|
||||
run: |
|
||||
@@ -151,11 +192,9 @@ jobs:
|
||||
shopt -s globstar
|
||||
|
||||
FILES=$(file dist/**/*.node)
|
||||
echo "$FILES"
|
||||
echo $FILES
|
||||
|
||||
if [ grep -v "$ARCH" ]; then
|
||||
exit 1
|
||||
fi
|
||||
! echo "$FILES" | grep -v "$ARCH"
|
||||
|
||||
LIBS=$(readelf -d dist/**/*.node | grep NEEDED)
|
||||
echo "$LIBS"
|
||||
@@ -175,10 +214,48 @@ jobs:
|
||||
|
||||
# We exclude *-unpacked as it loses permissions and the tarball contains it with correct permissions
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
with:
|
||||
name: linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
|
||||
name: ${{ inputs.artifact-prefix }}linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
|
||||
path: |
|
||||
dist
|
||||
!dist/*-unpacked/**
|
||||
retention-days: 1
|
||||
|
||||
- name: Assert deb is present and valid
|
||||
if: contains(inputs.targets, 'deb')
|
||||
run: |
|
||||
test -f ./dist/element-desktop*$ARCH.deb
|
||||
|
||||
DEB_LISTING=$(dpkg-deb --fsys-tarfile ./dist/element-desktop*.deb | tar -tv)
|
||||
echo "deb listing: "
|
||||
echo "$DEB_LISTING"
|
||||
! echo "$DEB_LISTING" | grep '^h'
|
||||
env:
|
||||
ARCH: ${{ inputs.arch }}
|
||||
|
||||
- name: Assert tar.gz is present
|
||||
if: contains(inputs.targets, 'tar.gz')
|
||||
run: |
|
||||
test -f ./dist/element-desktop*.tar.gz
|
||||
|
||||
TAR_GZ_LISTING=$(tar -tvf ./dist/element-desktop*.tar.gz)
|
||||
echo "tar.gz listing: "
|
||||
echo "$TAR_GZ_LISTING"
|
||||
! echo "$TAR_GZ_LISTING" | grep '^h'
|
||||
|
||||
test:
|
||||
name: Test Linux ${{ inputs.arch }} SQLCipher ${{ inputs.sqlcipher }}
|
||||
needs: build
|
||||
if: inputs.test && contains(inputs.targets, 'deb')
|
||||
uses: ./.github/workflows/build_test.yaml
|
||||
with:
|
||||
project: linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
|
||||
artifact: ${{ inputs.artifact-prefix }}linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
|
||||
runs-on: ${{ inputs.runs-on || (inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04') }}
|
||||
executable: /opt/Element*/element-desktop*
|
||||
prepare_cmd: |
|
||||
sudo apt-get -qq update
|
||||
sudo apt install ./dist/*.deb
|
||||
blob_report: ${{ inputs.blob_report }}
|
||||
args: ${{ inputs.test-args }}
|
||||
|
||||
119
.github/workflows/build_macos.yaml
vendored
@@ -15,6 +15,10 @@ on:
|
||||
APPLE_CSC_LINK:
|
||||
required: false
|
||||
inputs:
|
||||
ref:
|
||||
type: string
|
||||
required: false
|
||||
description: "The git ref to checkout, defaults to the default branch"
|
||||
version:
|
||||
type: string
|
||||
required: false
|
||||
@@ -27,21 +31,62 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
description: "The URL to which the output will be deployed."
|
||||
blob_report:
|
||||
type: boolean
|
||||
required: false
|
||||
description: "Whether to run the blob report"
|
||||
prepare-artifact-name:
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
The name of the prepare artifact to use, defaults to 'webapp'.
|
||||
The artifact must contain the following:
|
||||
+ webapp.asar - the asar archive of the webapp to embed in the desktop app
|
||||
+ electronVersion - the version of electron to use for cache keying
|
||||
+ hakHash - the hash of the .hak directory to use for cache keying
|
||||
+ variant.json - the variant configuration to use for the build
|
||||
|
||||
The artifact can also contain any additional files which will be applied as overrides to the checkout root before building,
|
||||
for example icons in the `build/` directory to override the app icons.
|
||||
default: "webapp"
|
||||
test:
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
description: "Whether to run the test stage after building"
|
||||
test-args:
|
||||
type: string
|
||||
required: false
|
||||
description: "Additional arguments to pass to playwright"
|
||||
artifact-prefix:
|
||||
type: string
|
||||
required: false
|
||||
description: "An optional prefix to add to the artifact name, useful for distinguishing builds in private repos."
|
||||
default: ""
|
||||
targets:
|
||||
type: string
|
||||
required: false
|
||||
description: "List of targets to build"
|
||||
default: "dmg zip"
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
build:
|
||||
name: Build macOS Universal
|
||||
runs-on: macos-14 # M1
|
||||
environment: ${{ inputs.sign && 'packages.element.io' || '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
name: webapp
|
||||
repository: element-hq/element-desktop
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
with:
|
||||
name: ${{ inputs.prepare-artifact-name }}
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakHash', 'electronVersion') }}
|
||||
path: |
|
||||
@@ -56,42 +101,40 @@ jobs:
|
||||
rustup target add x86_64-apple-darwin
|
||||
|
||||
# M1 macos-14 comes without Python preinstalled
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.13"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
|
||||
# Python 3.12 drops distutils which keytar relies on
|
||||
- name: Install setuptools
|
||||
run: pip3 install setuptools
|
||||
|
||||
- name: Build Natives
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
# Python 3.12 drops distutils which keytar relies on
|
||||
pip3 install setuptools
|
||||
yarn build:native:universal
|
||||
|
||||
- name: "[Nightly] Resolve version"
|
||||
if: inputs.version != ''
|
||||
run: |
|
||||
echo "ED_NIGHTLY=${{ inputs.version }}" >> $GITHUB_ENV
|
||||
run: yarn build:native:universal
|
||||
|
||||
# We split these because electron-builder gets upset if we set CSC_LINK even to an empty string
|
||||
- name: "[Signed] Build App"
|
||||
if: inputs.sign != ''
|
||||
run: |
|
||||
yarn build:universal --publish never
|
||||
yarn build:universal --publish never -m ${{ inputs.targets }}
|
||||
env:
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.APPLE_CSC_LINK }}
|
||||
VARIANT_PATH: variant.json
|
||||
# Only set for Nightly builds
|
||||
VERSION: ${{ inputs.version }}
|
||||
|
||||
- name: Check app was signed & notarised successfully
|
||||
if: inputs.sign != ''
|
||||
@@ -104,9 +147,10 @@ jobs:
|
||||
- name: "[Unsigned] Build App"
|
||||
if: inputs.sign == ''
|
||||
run: |
|
||||
yarn build:universal --publish never
|
||||
yarn build:universal --publish never -m ${{ inputs.targets }}
|
||||
env:
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: false
|
||||
VARIANT_PATH: variant.json
|
||||
|
||||
- name: Generate releases.json
|
||||
if: inputs.base-url
|
||||
@@ -136,10 +180,39 @@ jobs:
|
||||
|
||||
# We exclude mac-universal as the unpacked app takes forever to upload and zip and dmg already contains it
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
with:
|
||||
name: macos
|
||||
name: ${{ inputs.artifact-prefix }}macos
|
||||
path: |
|
||||
dist
|
||||
!dist/mac-universal/**
|
||||
retention-days: 1
|
||||
|
||||
- name: Assert zip is present
|
||||
if: contains(inputs.targets, 'zip')
|
||||
run: |
|
||||
test -f ./dist/Element*-mac.zip
|
||||
|
||||
- name: Assert dmg is present
|
||||
if: contains(inputs.targets, 'dmg')
|
||||
run: |
|
||||
test -f ./dist/Element*.dmg
|
||||
|
||||
test:
|
||||
name: Test macOS Universal
|
||||
needs: build
|
||||
if: inputs.test && contains(inputs.targets, 'dmg')
|
||||
uses: ./.github/workflows/build_test.yaml
|
||||
with:
|
||||
project: macos
|
||||
artifact: ${{ inputs.artifact-prefix }}macos
|
||||
runs-on: macos-14
|
||||
executable: /Users/runner/Applications/Element*.app/Contents/MacOS/Element*
|
||||
# We need to mount the DMG and copy the app to the Applications folder as a mounted DMG is
|
||||
# read-only and thus would not allow us to override the fuses as is required for Playwright.
|
||||
prepare_cmd: |
|
||||
hdiutil attach ./dist/*.dmg -mountpoint /Volumes/Element &&
|
||||
rsync -a /Volumes/Element/Element*.app ~/Applications/ &&
|
||||
hdiutil detach /Volumes/Element
|
||||
blob_report: ${{ inputs.blob_report }}
|
||||
args: ${{ inputs.test-args }}
|
||||
|
||||
51
.github/workflows/build_prepare.yaml
vendored
@@ -20,6 +20,11 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
description: "Whether the build should be deployed to production"
|
||||
branch-matching:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
description: "Whether the branch name should be matched to find the element-web commit"
|
||||
secrets:
|
||||
# Required if `nightly` is set
|
||||
CF_R2_ACCESS_KEY_ID:
|
||||
@@ -34,12 +39,9 @@ on:
|
||||
packages-dir:
|
||||
description: "The directory non-deb packages for this run should live in within packages.element.io"
|
||||
value: ${{ inputs.nightly && 'nightly' || 'desktop' }}
|
||||
# These are just simple pass-throughs of the input to simplify reuse of complex inline conditions
|
||||
config:
|
||||
description: "The relative path to the config file for this run"
|
||||
value: ${{ inputs.config }}
|
||||
# This is just a simple pass-through of the input to simplify reuse of complex inline conditions
|
||||
deploy:
|
||||
description: "The relative path to the config file for this run"
|
||||
description: "Whether the build should be deployed to production"
|
||||
value: ${{ inputs.deploy }}
|
||||
permissions: {}
|
||||
jobs:
|
||||
@@ -52,19 +54,42 @@ jobs:
|
||||
outputs:
|
||||
nightly-version: ${{ steps.versions.outputs.nightly }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
|
||||
- name: Fetch Element Web
|
||||
- name: Fetch Element Web (matching branch)
|
||||
id: branch-matching
|
||||
if: inputs.branch-matching
|
||||
continue-on-error: true
|
||||
run: |
|
||||
scripts/branch-match.sh
|
||||
cp "$CONFIG_DIR/config.json" element-web/
|
||||
yarn --cwd element-web install --frozen-lockfile
|
||||
yarn --cwd element-web run build
|
||||
mv element-web/webapp .
|
||||
yarn asar-webapp
|
||||
env:
|
||||
# These must be set for branch-match.sh to get the right branch
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
CONFIG_DIR: ${{ inputs.config }}
|
||||
|
||||
- name: Fetch Element Web (${{ inputs.version }})
|
||||
if: steps.branch-matching.outcome == 'failure' || steps.branch-matching.outcome == 'skipped'
|
||||
run: yarn run fetch --noverify -d ${{ inputs.config }} ${{ inputs.version }}
|
||||
|
||||
- name: Copy variant config
|
||||
run: cp "$CONFIG_DIR/build.json" variant.json
|
||||
env:
|
||||
CONFIG_DIR: ${{ inputs.config }}
|
||||
|
||||
# We split this out to save the build_* scripts having to do it to make use of `hashFiles` in the cache action
|
||||
- name: Generate cache hash files
|
||||
run: |
|
||||
@@ -83,7 +108,7 @@ jobs:
|
||||
aws s3 cp s3://$R2_BUCKET/debian/dists/default/main/binary-amd64/Packages - --endpoint-url $R2_URL --region auto | grep "Package: element-nightly" -A 50 | grep Version -m1 | sed -n 's/Version: //p' >> VERSIONS
|
||||
aws s3 cp s3://$R2_BUCKET/debian/dists/default/main/binary-arm64/Packages - --endpoint-url $R2_URL --region auto | grep "Package: element-nightly" -A 50 | grep Version -m1 | sed -n 's/Version: //p' >> VERSIONS
|
||||
aws s3 cp s3://$R2_BUCKET/nightly/update/win32/x64/RELEASES - --endpoint-url $R2_URL --region auto | awk '{print $2}' | cut -d "-" -f 5 | cut -c 8- >> VERSIONS
|
||||
aws s3 cp s3://$R2_BUCKET/nightly/update/win32/ia32/RELEASES - --endpoint-url $R2_URL --region auto | awk '{print $2}' | cut -d "-" -f 5 | cut -c 8- >> VERSIONS
|
||||
aws s3 cp s3://$R2_BUCKET/nightly/update/win32/arm64/RELEASES - --endpoint-url $R2_URL --region auto | awk '{print $2}' | cut -d "-" -f 5 | cut -c 8- >> VERSIONS
|
||||
|
||||
# Pick the greatest one
|
||||
VERSION=$(cat VERSIONS | sort -uf | tail -n1)
|
||||
@@ -93,8 +118,6 @@ jobs:
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
|
||||
# Workaround for https://www.cloudflarestatus.com/incidents/t5nrjmpxc1cj
|
||||
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
|
||||
R2_BUCKET: ${{ vars.R2_BUCKET }}
|
||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||
|
||||
@@ -142,13 +165,13 @@ jobs:
|
||||
echo "| Element Web | [$WEB_VERSION](https://github.com/element-hq/element-web/commit/$WEB_VERSION) |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| JS SDK | [$JS_VERSION](https://github.com/matrix-org/matrix-js-sdk/commit/$JS_VERSION) |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
with:
|
||||
name: webapp
|
||||
retention-days: 1
|
||||
path: |
|
||||
webapp.asar
|
||||
package.json
|
||||
electronVersion
|
||||
hakHash
|
||||
changelog.Debian
|
||||
variant.json
|
||||
|
||||
101
.github/workflows/build_test.yaml
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# This action helps run Playwright tests within one of the build_* stages.
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
runs-on:
|
||||
type: string
|
||||
required: true
|
||||
description: "The runner image to use"
|
||||
artifact:
|
||||
type: string
|
||||
required: true
|
||||
description: "The name of the artifact to download"
|
||||
project:
|
||||
type: string
|
||||
required: true
|
||||
description: "The Playwright project to use for testing"
|
||||
executable:
|
||||
type: string
|
||||
required: true
|
||||
description: "Path to the executable to test"
|
||||
prepare_cmd:
|
||||
type: string
|
||||
required: false
|
||||
description: "Command to run to prepare the executable or environment for testing"
|
||||
blob_report:
|
||||
type: boolean
|
||||
default: false
|
||||
description: "Whether to upload a blob report instead of the HTML report"
|
||||
args:
|
||||
type: string
|
||||
required: false
|
||||
description: "Additional arguments to pass to playwright, for e.g. skipping specific tests"
|
||||
permissions: {}
|
||||
jobs:
|
||||
test:
|
||||
name: Test ${{ inputs.project }}
|
||||
runs-on: ${{ inputs.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
repository: ${{ github.repository == 'element-hq/element-web-pro' && 'element-hq/element-desktop' || github.repository }}
|
||||
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
with:
|
||||
name: ${{ inputs.artifact }}
|
||||
path: dist
|
||||
|
||||
- name: Prepare for tests
|
||||
run: ${{ inputs.prepare_cmd }}
|
||||
if: inputs.prepare_cmd
|
||||
|
||||
- name: Expand executable path
|
||||
id: executable
|
||||
shell: bash
|
||||
env:
|
||||
EXECUTABLE: ${{ inputs.executable }}
|
||||
run: |
|
||||
FILES=($EXECUTABLE)
|
||||
echo "path=${FILES[0]}" >> $GITHUB_OUTPUT
|
||||
|
||||
# We previously disabled the `EnableNodeCliInspectArguments` fuse, but Playwright requires
|
||||
# it to be enabled to test Electron apps, so turn it back on.
|
||||
- name: Set EnableNodeCliInspectArguments fuse enabled
|
||||
run: $RUN_AS npx @electron/fuses write --app "$EXECUTABLE" EnableNodeCliInspectArguments=on
|
||||
shell: bash
|
||||
env:
|
||||
# We need sudo on Linux as it is installed in /opt/
|
||||
RUN_AS: ${{ runner.os == 'Linux' && 'sudo' || '' }}
|
||||
EXECUTABLE: ${{ steps.executable.outputs.path }}
|
||||
|
||||
- name: Run tests
|
||||
uses: coactions/setup-xvfb@6b00cf1889f4e1d5a48635647013c0508128ee1a
|
||||
timeout-minutes: 20
|
||||
with:
|
||||
run: yarn test --project=${{ inputs.project }} ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} ${{ inputs.blob_report == false && '--reporter=html' || '' }} ${{ inputs.args }}
|
||||
env:
|
||||
ELEMENT_DESKTOP_EXECUTABLE: ${{ steps.executable.outputs.path }}
|
||||
|
||||
- name: Upload blob report
|
||||
if: always() && inputs.blob_report
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
with:
|
||||
name: blob-report-${{ inputs.artifact }}
|
||||
path: blob-report
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload HTML report
|
||||
if: always() && inputs.blob_report == false
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
with:
|
||||
name: ${{ inputs.artifact }}-test
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
169
.github/workflows/build_windows.yaml
vendored
@@ -18,6 +18,10 @@ on:
|
||||
ESIGNER_USER_TOTP:
|
||||
required: false
|
||||
inputs:
|
||||
ref:
|
||||
type: string
|
||||
required: false
|
||||
description: "The git ref to checkout, defaults to the default branch"
|
||||
arch:
|
||||
type: string
|
||||
required: true
|
||||
@@ -30,13 +34,56 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
description: "Whether to sign & notarise the build, requires 'packages.element.io' environment"
|
||||
blob_report:
|
||||
type: boolean
|
||||
required: false
|
||||
description: "Whether to run the blob report"
|
||||
prepare-artifact-name:
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
The name of the prepare artifact to use, defaults to 'webapp'.
|
||||
The artifact must contain the following:
|
||||
+ webapp.asar - the asar archive of the webapp to embed in the desktop app
|
||||
+ electronVersion - the version of electron to use for cache keying
|
||||
+ hakHash - the hash of the .hak directory to use for cache keying
|
||||
+ variant.json - the variant configuration to use for the build
|
||||
|
||||
|
||||
The artifact can also contain any additional files which will be applied as overrides to the checkout root before building,
|
||||
for example icons in the `build/` directory to override the app icons.
|
||||
default: "webapp"
|
||||
test:
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
description: "Whether to run the test stage after building"
|
||||
test-runs-on:
|
||||
type: string
|
||||
required: false
|
||||
description: "The runner image to use for testing, normally set for you, may be needed for running in private repos."
|
||||
test-args:
|
||||
type: string
|
||||
required: false
|
||||
description: "Additional arguments to pass to playwright"
|
||||
artifact-prefix:
|
||||
type: string
|
||||
required: false
|
||||
description: "An optional prefix to add to the artifact name, useful for distinguishing builds in private repos."
|
||||
default: ""
|
||||
targets:
|
||||
type: string
|
||||
required: false
|
||||
description: "List of targets to build"
|
||||
default: "squirrel msi"
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
name: Build Windows ${{ inputs.arch }}
|
||||
runs-on: windows-2025
|
||||
environment: ${{ inputs.sign && 'packages.element.io' || '' }}
|
||||
env:
|
||||
SIGNTOOL_PATH: "C:/Program Files (x86)/Windows Kits/10/bin/10.0.22000.0/x86/signtool.exe"
|
||||
SIGNTOOL_PATH: "C:/Program Files (x86)/Windows Kits/10/bin/10.0.26100.0/x86/signtool.exe"
|
||||
steps:
|
||||
- uses: nbucic/variable-mapper@0673f6891a0619ba7c002ecfed0f9f4f39017b6f
|
||||
id: config
|
||||
@@ -56,29 +103,28 @@ jobs:
|
||||
"ia32": {
|
||||
"target": "i686-pc-windows-msvc",
|
||||
"build-args": "--ia32",
|
||||
"arch": "x86"
|
||||
"arch": "x86",
|
||||
"extra_config": "{\"user_notice\": {\"title\": \"Your desktop environment is unsupported.\",\"description\": \"Support for 32-bit Windows installations has ended. Transition to the web or mobile app for continued access.\"}}"
|
||||
}
|
||||
}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
name: webapp
|
||||
repository: element-hq/element-desktop
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
with:
|
||||
name: ${{ inputs.prepare-artifact-name }}
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion') }}
|
||||
path: |
|
||||
./.hak
|
||||
|
||||
- name: Set up build tools
|
||||
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
|
||||
with:
|
||||
arch: ${{ steps.config.outputs.arch || inputs.arch }}
|
||||
|
||||
# ActiveTCL package on choco is from 2015,
|
||||
# this one is newer but includes more than we need
|
||||
- name: Choco install tclsh
|
||||
@@ -102,15 +148,40 @@ jobs:
|
||||
rustup default stable
|
||||
rustup target add ${{ steps.config.outputs.target }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
node-version-file: .node-version
|
||||
cache: "yarn"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
|
||||
- name: Insert config snippet
|
||||
if: steps.config.outputs.extra_config != ''
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir config-edit
|
||||
yarn asar extract webapp.asar config-edit
|
||||
cd config-edit
|
||||
mv config.json old-config.json
|
||||
echo '${{ steps.config.outputs.extra_config }}' | jq -s '.[0] * .[1]' old-config.json - > config.json
|
||||
rm old-config.json
|
||||
cd ..
|
||||
rm webapp.asar
|
||||
yarn asar pack config-edit/ webapp.asar
|
||||
|
||||
- name: Set up sqlcipher macros
|
||||
if: steps.cache.outputs.cache-hit != 'true' && contains(inputs.arch, 'arm')
|
||||
shell: pwsh
|
||||
run: |
|
||||
echo "NCC=${{ github.workspace }}\scripts\cl.bat" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Set up build tools
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
|
||||
with:
|
||||
arch: ${{ steps.config.outputs.arch || inputs.arch }}
|
||||
|
||||
- name: Build Natives
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@@ -160,29 +231,65 @@ jobs:
|
||||
INSTALL_DIR: C:\Users\runneradmin\eSignerCKA
|
||||
MASTER_KEY_FILE: C:\Users\runneradmin\eSignerCKA\master.key
|
||||
|
||||
- name: "[Nightly] Resolve version"
|
||||
if: inputs.version != ''
|
||||
shell: bash
|
||||
run: |
|
||||
echo "ED_NIGHTLY=${{ inputs.version }}" >> $GITHUB_ENV
|
||||
|
||||
# XXX: For whatever reason if we use `yarn build ...` it freezes, but splitting it into parts it is fine
|
||||
- run: yarn run build:ts
|
||||
- run: yarn run build:res
|
||||
|
||||
- name: Build App
|
||||
run: yarn build --publish never ${{ steps.config.outputs.build-args }} -w ${{ inputs.targets }}
|
||||
env:
|
||||
VARIANT_PATH: variant.json
|
||||
# Only set for Nightly builds
|
||||
# The windows packager relies on parsing this as semver, so we have to make it look like one.
|
||||
# This will give our update packages really stupid names, but we probably can't change that either
|
||||
# because squirrel windows parses them for the version too. We don't really care: nobody sees them.
|
||||
# We just give the installer a static name, so you'll just see this in the 'about' dialog.
|
||||
# Turns out if you use 0.0.0 here it makes Squirrel windows crash, so we use 0.0.1.
|
||||
VERSION: ${{ inputs.version && format('0.0.1-nightly.{0}', inputs.version) || '' }}
|
||||
|
||||
- name: Trust eSigner sandbox cert
|
||||
if: inputs.sign == ''
|
||||
run: |
|
||||
yarn electron-builder --publish never -w ${{ steps.config.outputs.build-args }}
|
||||
Set-StrictMode -Version 'Latest'
|
||||
Import-Certificate -CertStoreLocation Cert:\LocalMachine\Root -FilePath .github/SSLcom-sandbox.crt
|
||||
|
||||
- name: Check app was signed successfully
|
||||
if: inputs.sign
|
||||
run: |
|
||||
. "$env:SIGNTOOL_PATH" verify /pa (get-item ./dist/squirrel-windows*/*.exe)
|
||||
Set-StrictMode -Version 'Latest'
|
||||
Get-ChildItem `
|
||||
-Recurse dist `
|
||||
-Include *.exe, *.msi `
|
||||
| ForEach-Object -Process {. $env:SIGNTOOL_PATH verify /pa $_.FullName; if(!$?) { throw }}
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
with:
|
||||
name: win-${{ inputs.arch }}
|
||||
name: ${{ inputs.artifact-prefix }}win-${{ inputs.arch }}
|
||||
path: |
|
||||
dist
|
||||
retention-days: 1
|
||||
|
||||
- name: Assert executable is present
|
||||
run: |
|
||||
Test-Path './dist/win-*unpacked/Element*.exe'
|
||||
|
||||
- name: Assert all Squirrel files are present
|
||||
if: contains(inputs.targets, 'squirrel')
|
||||
run: |
|
||||
Test-Path './dist/squirrel-windows*/Element Setup*.exe'
|
||||
Test-Path './dist/squirrel-windows*/element-desktop-*-full.nupkg'
|
||||
Test-Path './dist/squirrel-windows*/RELEASES'
|
||||
|
||||
- name: Assert MSI is present
|
||||
if: contains(inputs.targets, 'msi')
|
||||
run: |
|
||||
Test-Path './dist/Element*.msi'
|
||||
|
||||
test:
|
||||
name: Test Windows ${{ inputs.arch }}
|
||||
needs: build
|
||||
if: inputs.test
|
||||
uses: ./.github/workflows/build_test.yaml
|
||||
with:
|
||||
project: win-${{ inputs.arch }}
|
||||
artifact: ${{ inputs.artifact-prefix }}win-${{ inputs.arch }}
|
||||
runs-on: ${{ inputs.test-runs-on || (inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2022') }}
|
||||
executable: ./dist/win*-unpacked/Element*.exe
|
||||
blob_report: ${{ inputs.blob_report }}
|
||||
args: ${{ inputs.test-args }}
|
||||
|
||||
18
.github/workflows/dockerbuild.yaml
vendored
@@ -19,20 +19,20 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Build test image
|
||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
with:
|
||||
context: dockerbuild
|
||||
file: dockerbuild/Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
tags: element-desktop-dockerbuild
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: docker run -v $PWD:/project element-desktop-dockerbuild yarn install
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@327cd5a69de6c009b9ce71bce8395f28e651bf99
|
||||
uses: docker/login-action@28fdb31ff34708d19615a74d67103ddc2ea9725c
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5
|
||||
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
@@ -61,9 +61,9 @@ jobs:
|
||||
|
||||
- name: Build and push Docker image
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
with:
|
||||
context: dockerbuild
|
||||
file: dockerbuild/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
1
.github/workflows/release.yml
vendored
@@ -19,6 +19,7 @@ jobs:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: read
|
||||
id-token: write
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
16
.github/workflows/static_analysis.yaml
vendored
@@ -9,9 +9,9 @@ jobs:
|
||||
name: "Typescript Syntax Check"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "yarn"
|
||||
@@ -35,9 +35,9 @@ jobs:
|
||||
name: "ESLint"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "yarn"
|
||||
@@ -53,9 +53,9 @@ jobs:
|
||||
name: "Workflow Lint"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "yarn"
|
||||
@@ -71,9 +71,9 @@ jobs:
|
||||
name: "Analyse Dead Code"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "yarn"
|
||||
|
||||
6
.github/workflows/triage-stale.yml
vendored
@@ -12,9 +12,11 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10
|
||||
with:
|
||||
operations-per-run: 100
|
||||
operations-per-run: 250
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
days-before-pr-stale: 180
|
||||
days-before-pr-close: 0
|
||||
close-pr-message: "This PR has been automatically closed because it has been stale for 180 days. If you wish to continue working on this PR, please ping a maintainer to reopen it."
|
||||
|
||||
1
.node-version
Normal file
@@ -0,0 +1 @@
|
||||
24.11.1
|
||||
648
CHANGELOG.md
@@ -1,3 +1,651 @@
|
||||
Changes in [1.12.4](https://github.com/element-hq/element-desktop/releases/tag/v1.12.4) (2025-11-18)
|
||||
====================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Update nightly and release builds to use the dedicated subdomain for 'bug\_report\_endpoint\_url' ([#2677](https://github.com/element-hq/element-desktop/pull/2677)). Contributed by @benbz.
|
||||
* Apply aria-hidden to emoji in SAS verification ([#31204](https://github.com/element-hq/element-web/pull/31204)). Contributed by @t3chguy.
|
||||
* Add options to hide header and composer of room view for the module api ([#31095](https://github.com/element-hq/element-web/pull/31095)). Contributed by @florianduros.
|
||||
* Experimental Module API Additions ([#30863](https://github.com/element-hq/element-web/pull/30863)). Contributed by @dbkr.
|
||||
* Change polls to use fieldset/legend markup ([#31160](https://github.com/element-hq/element-web/pull/31160)). Contributed by @langleyd.
|
||||
* Use compound Button styles for Jitsi button ([#31159](https://github.com/element-hq/element-web/pull/31159)). Contributed by @Half-Shot.
|
||||
* Add FocusLock to emoji picker ([#31146](https://github.com/element-hq/element-web/pull/31146)). Contributed by @langleyd.
|
||||
* Move room name, avatar, and topic to IOpts. ([#30981](https://github.com/element-hq/element-web/pull/30981)). Contributed by @kaylendog.
|
||||
* Add a devtool for looking at users and their devices ([#30983](https://github.com/element-hq/element-web/pull/30983)). Contributed by @uhoreg.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix room list handling of membership changes ([#31197](https://github.com/element-hq/element-web/pull/31197)). Contributed by @t3chguy.
|
||||
* Fix room list unable to be resized when displayed after a module ([#31186](https://github.com/element-hq/element-web/pull/31186)). Contributed by @florianduros.
|
||||
* Inhibit keyboard highlights in dialogs when effector is not in focus ([#31181](https://github.com/element-hq/element-web/pull/31181)). Contributed by @t3chguy.
|
||||
* Strip mentions from forwarded messages ([#30884](https://github.com/element-hq/element-web/pull/30884)). Contributed by @twassman.
|
||||
* Don't allow pin or edit of messages with a send status ([#31158](https://github.com/element-hq/element-web/pull/31158)). Contributed by @langleyd.
|
||||
* Hide room header buttons if the room hasn't been created yet. ([#31092](https://github.com/element-hq/element-web/pull/31092)). Contributed by @Half-Shot.
|
||||
* Fix screen readers not indicating the emoji picker search field is focused. ([#31128](https://github.com/element-hq/element-web/pull/31128)). Contributed by @langleyd.
|
||||
* Fix emoji picker highlight missing when not active element ([#31148](https://github.com/element-hq/element-web/pull/31148)). Contributed by @t3chguy.
|
||||
* Add relevant aria attribute for selected emoji in the emoji picker ([#31125](https://github.com/element-hq/element-web/pull/31125)). Contributed by @t3chguy.
|
||||
* Fix tooltips within context menu portals being unreliable ([#31129](https://github.com/element-hq/element-web/pull/31129)). Contributed by @t3chguy.
|
||||
* Avoid excessive re-render of room list and member list ([#31131](https://github.com/element-hq/element-web/pull/31131)). Contributed by @florianduros.
|
||||
* Make emoji picker height responsive. ([#31130](https://github.com/element-hq/element-web/pull/31130)). Contributed by @langleyd.
|
||||
* Emoji Picker: Focused emoji does not move with the arrow keys ([#30893](https://github.com/element-hq/element-web/pull/30893)). Contributed by @langleyd.
|
||||
* Fix audio player seek bar position ([#31127](https://github.com/element-hq/element-web/pull/31127)). Contributed by @florianduros.
|
||||
* Add aria label to emoji picker search ([#31126](https://github.com/element-hq/element-web/pull/31126)). Contributed by @langleyd.
|
||||
|
||||
|
||||
|
||||
Changes in [1.12.3](https://github.com/element-hq/element-desktop/releases/tag/v1.12.3) (2025-11-04)
|
||||
====================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Remove allowVoipWithNoMedia feature flag ([#31087](https://github.com/element-hq/element-web/pull/31087)). Contributed by @Half-Shot.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* Change module API to be an instance getter ([#31025](https://github.com/element-hq/element-web/pull/31025)). Contributed by @dbkr.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Wait for Electron to be ready before we fire syntax error dialog ([#2659](https://github.com/element-hq/element-desktop/pull/2659)). Contributed by @t3chguy.
|
||||
* Show hover elements when keyboard focus is within an event tile ([#31078](https://github.com/element-hq/element-web/pull/31078)). Contributed by @t3chguy.
|
||||
* Ensure toolbar navigation pattern works in MessageActionBar ([#31080](https://github.com/element-hq/element-web/pull/31080)). Contributed by @t3chguy.
|
||||
* Ensure sent markers are hidden when showing thread summary. ([#31076](https://github.com/element-hq/element-web/pull/31076)). Contributed by @Half-Shot.
|
||||
* Fix translation in dev mode ([#31045](https://github.com/element-hq/element-web/pull/31045)). Contributed by @florianduros.
|
||||
* Fix sort order in space hierarchy ([#30975](https://github.com/element-hq/element-web/pull/30975)). Contributed by @t3chguy.
|
||||
* New Room list: don't display message preview of thread ([#31043](https://github.com/element-hq/element-web/pull/31043)). Contributed by @florianduros.
|
||||
* Revert "A11y: move focus to right panel when opened" ([#30999](https://github.com/element-hq/element-web/pull/30999)). Contributed by @florianduros.
|
||||
* Fix highlights in messages (or search results) breaking links ([#30264](https://github.com/element-hq/element-web/pull/30264)). Contributed by @bojidar-bg.
|
||||
* Add prepare script ([#31030](https://github.com/element-hq/element-web/pull/31030)). Contributed by @dbkr.
|
||||
* Fix html exports by adding SDKContext ([#30987](https://github.com/element-hq/element-web/pull/30987)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
|
||||
Changes in [1.12.2](https://github.com/element-hq/element-desktop/releases/tag/v1.12.2) (2025-10-21)
|
||||
====================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Allow Desktop app to be auto-started minimised or focused ([#2622](https://github.com/element-hq/element-desktop/pull/2622)). Contributed by @t3chguy.
|
||||
* Room List: Extend the viewport to avoid so many black spots when scrolling the room list ([#30867](https://github.com/element-hq/element-web/pull/30867)). Contributed by @langleyd.
|
||||
* Hide calling buttons in room header before a room is created ([#30816](https://github.com/element-hq/element-web/pull/30816)). Contributed by @Half-Shot.
|
||||
* Improve invite dialog ui - Part 2 ([#30836](https://github.com/element-hq/element-web/pull/30836)). Contributed by @florianduros.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix hardlinks appearing in and breaking deb packages ([#2609](https://github.com/element-hq/element-desktop/pull/2609)). Contributed by @t3chguy.
|
||||
* Fix platform settings race condition and make auto-launch tri-state ([#30977](https://github.com/element-hq/element-web/pull/30977)). Contributed by @t3chguy.
|
||||
* Fix: member count in header and member list ([#30982](https://github.com/element-hq/element-web/pull/30982)). Contributed by @florianduros.
|
||||
* Fix duration of voice message in timeline ([#30973](https://github.com/element-hq/element-web/pull/30973)). Contributed by @florianduros.
|
||||
* Fix voice notes rendering at 00:00 when playback had not begun. ([#30961](https://github.com/element-hq/element-web/pull/30961)). Contributed by @Half-Shot.
|
||||
* Improve handling of animated images, add support for AVIF animations ([#30932](https://github.com/element-hq/element-web/pull/30932)). Contributed by @t3chguy.
|
||||
* Update key storage toggle when key storage status changes ([#30934](https://github.com/element-hq/element-web/pull/30934)). Contributed by @uhoreg.
|
||||
* Fix jitsi widget popout ([#30908](https://github.com/element-hq/element-web/pull/30908)). Contributed by @dbkr.
|
||||
* Improve keyboard navigation on invite dialog ([#30930](https://github.com/element-hq/element-web/pull/30930)). Contributed by @florianduros.
|
||||
* Prefer UIA flows with supported UIA stages ([#30926](https://github.com/element-hq/element-web/pull/30926)). Contributed by @richvdh.
|
||||
* Enhance accessibility of dropdown ([#30928](https://github.com/element-hq/element-web/pull/30928)). Contributed by @florianduros.
|
||||
* Improve accessibility of the `\<AvatarSetting> component ([#30907](https://github.com/element-hq/element-web/pull/30907)). Contributed by @MidhunSureshR.
|
||||
|
||||
|
||||
|
||||
Changes in [1.12.1](https://github.com/element-hq/element-desktop/releases/tag/v1.12.1) (2025-10-07)
|
||||
====================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* New Room List: Change the order of filters to match those on mobile ([#30905](https://github.com/element-hq/element-web/pull/30905)). Contributed by @langleyd.
|
||||
* New Room List: Don't clear filters on space change ([#30903](https://github.com/element-hq/element-web/pull/30903)). Contributed by @langleyd.
|
||||
* Add release announcement for the sounds ([#30900](https://github.com/element-hq/element-web/pull/30900)). Contributed by @langleyd.
|
||||
* Rich Text Editor: Add emoji suggestion support ([#30873](https://github.com/element-hq/element-web/pull/30873)). Contributed by @langleyd.
|
||||
* feat: Disable session lock when running in element-desktop ([#30643](https://github.com/element-hq/element-web/pull/30643)). Contributed by @kaylendog.
|
||||
* Improve invite dialog ui - Part 1 ([#30764](https://github.com/element-hq/element-web/pull/30764)). Contributed by @florianduros.
|
||||
* Update Message Sound for Element ([#30804](https://github.com/element-hq/element-web/pull/30804)). Contributed by @beatdemon.
|
||||
* Add new and improved ringtone ([#30761](https://github.com/element-hq/element-web/pull/30761)). Contributed by @Half-Shot.
|
||||
* Disable RTE formatting buttons when the content contains a slash command ([#30802](https://github.com/element-hq/element-web/pull/30802)). Contributed by @langleyd.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* New Room List: Improve robustness of keyboard navigation ([#30888](https://github.com/element-hq/element-web/pull/30888)). Contributed by @langleyd.
|
||||
* Fix a11y issue on list in invite dialog ([#30878](https://github.com/element-hq/element-web/pull/30878)). Contributed by @florianduros.
|
||||
* Switch Export and Import Icons to match intuition ([#30805](https://github.com/element-hq/element-web/pull/30805)). Contributed by @micartey.
|
||||
* Hide breadcrumb option when new room list is enabled ([#30869](https://github.com/element-hq/element-web/pull/30869)). Contributed by @florianduros.
|
||||
* Avoid creating multiple call objects for the same widget ([#30839](https://github.com/element-hq/element-web/pull/30839)). Contributed by @robintown.
|
||||
* Add a test for #29882, which is fixed by matrix-org/matrix-js-sdk#5016 ([#30835](https://github.com/element-hq/element-web/pull/30835)). Contributed by @andybalaam.
|
||||
* fix: use `help_encryption_url` of config instead of hardcoded `https://element.io/help#encryption5` ([#30746](https://github.com/element-hq/element-web/pull/30746)). Contributed by @florianduros.
|
||||
* Fix html export when feature\_jump\_to\_date is enabled ([#30828](https://github.com/element-hq/element-web/pull/30828)). Contributed by @langleyd.
|
||||
* Fix #30439: "Forgot recovery key" should go to "reset" ([#30771](https://github.com/element-hq/element-web/pull/30771)). Contributed by @andybalaam.
|
||||
|
||||
|
||||
|
||||
Changes in [1.12.0](https://github.com/element-hq/element-desktop/releases/tag/v1.12.0) (2025-09-23)
|
||||
====================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Remove remaining support for outdated .well-known settings ([#30702](https://github.com/element-hq/element-web/pull/30702)). Contributed by @richvdh.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* Automatically select first source for desktop capture under Wayland ([#2526](https://github.com/element-hq/element-desktop/pull/2526)). Contributed by @byquanton.
|
||||
* Add decline button to call notification toast (use new notification event) ([#30729](https://github.com/element-hq/element-web/pull/30729)). Contributed by @toger5.
|
||||
* Use the new room list by default ([#30640](https://github.com/element-hq/element-web/pull/30640)). Contributed by @langleyd.
|
||||
* "Verify this device" redesign ([#30596](https://github.com/element-hq/element-web/pull/30596)). Contributed by @uhoreg.
|
||||
* Set Element Call "intents" when starting and answering DM calls. ([#30730](https://github.com/element-hq/element-web/pull/30730)). Contributed by @Half-Shot.
|
||||
* Add axe compliance for new room list ([#30700](https://github.com/element-hq/element-web/pull/30700)). Contributed by @langleyd.
|
||||
* Stop ringing and remove toast if another device answers a RTC call. ([#30728](https://github.com/element-hq/element-web/pull/30728)). Contributed by @Half-Shot.
|
||||
* Automatically adjust history visibility when making a room private ([#30713](https://github.com/element-hq/element-web/pull/30713)). Contributed by @Half-Shot.
|
||||
* Release announcement for new room list ([#30675](https://github.com/element-hq/element-web/pull/30675)). Contributed by @dbkr.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Update Electron to v38.1.0 to fix Kernel crash on multi-GPU systems ([#2544](https://github.com/element-hq/element-desktop/pull/2544)). Contributed by @Arcitec.
|
||||
* Fix Confirm your identity buttons being unclickable ([#2554](https://github.com/element-hq/element-desktop/pull/2554)). Contributed by @dbkr.
|
||||
* Ensure dropdown is not a drag element on macOS ([#2540](https://github.com/element-hq/element-desktop/pull/2540)). Contributed by @t3chguy.
|
||||
* [Backport staging] Room list: make the filter resize correctly ([#30795](https://github.com/element-hq/element-web/pull/30795)). Contributed by @RiotRobot.
|
||||
* [Backport staging] Avoid flicker of the room list filter on resize ([#30794](https://github.com/element-hq/element-web/pull/30794)). Contributed by @RiotRobot.
|
||||
* Don't show release announcements while toasts are displayed ([#30770](https://github.com/element-hq/element-web/pull/30770)). Contributed by @dbkr.
|
||||
* Fix enabling key backup not working if there is an untrusted key backup ([#30707](https://github.com/element-hq/element-web/pull/30707)). Contributed by @Half-Shot.
|
||||
* Force `preload` to be false when setting an intent on an Element Call. ([#30759](https://github.com/element-hq/element-web/pull/30759)). Contributed by @Half-Shot.
|
||||
* Fix handling of 413 server response when uploading media ([#30737](https://github.com/element-hq/element-web/pull/30737)). Contributed by @hughns.
|
||||
* Make landmark navigation work with new room list ([#30747](https://github.com/element-hq/element-web/pull/30747)). Contributed by @dbkr.
|
||||
* Prevent voice message from displaying spurious errors ([#30736](https://github.com/element-hq/element-web/pull/30736)). Contributed by @florianduros.
|
||||
* Align default avatar and fix colors in composer pills ([#30739](https://github.com/element-hq/element-web/pull/30739)). Contributed by @florianduros.
|
||||
* Use configured URL for link to desktop app in message search settings ([#30742](https://github.com/element-hq/element-web/pull/30742)). Contributed by @t3chguy.
|
||||
* Fix history visibility when creating space rooms ([#30745](https://github.com/element-hq/element-web/pull/30745)). Contributed by @dbkr.
|
||||
* Check HTML-encoded quotes when handling translations for embedded pages (such as welcome.html) ([#30743](https://github.com/element-hq/element-web/pull/30743)). Contributed by @Half-Shot.
|
||||
* Fix local room encryption status always not enabled ([#30461](https://github.com/element-hq/element-web/pull/30461)). Contributed by @BillCarsonFr.
|
||||
* fix: make url in topic in room intro clickable ([#30686](https://github.com/element-hq/element-web/pull/30686)). Contributed by @florianduros.
|
||||
* Block change recovery key button while a change is ongoing. ([#30664](https://github.com/element-hq/element-web/pull/30664)). Contributed by @Half-Shot.
|
||||
* Hide advanced settings during room creation when `UIFeature.advancedSettings=false` ([#30684](https://github.com/element-hq/element-web/pull/30684)). Contributed by @florianduros.
|
||||
* A11y: improve accessibility of pinned messages ([#30558](https://github.com/element-hq/element-web/pull/30558)). Contributed by @florianduros.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.112](https://github.com/element-hq/element-desktop/releases/tag/v1.11.112) (2025-09-16)
|
||||
========================================================================================================
|
||||
Fix [CVE-2025-59161](https://www.cve.org/CVERecord?id=CVE-2025-59161) / [GHSA-m6c8-98f4-75rr](https://github.com/element-hq/element-web/security/advisories/GHSA-m6c8-98f4-75rr)
|
||||
|
||||
## This is the last release compatible with macOS Big Sur. It will not update further. Big Sur is End of Life for almost 2 years and the next version of Electron crashes upon startup on Big Sur.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* [Backport staging] Handle unsupported macOS versions better ([#2555](https://github.com/element-hq/element-desktop/pull/2555)). Contributed by @RiotRobot.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.111](https://github.com/element-hq/element-desktop/releases/tag/v1.11.111) (2025-09-10)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Do not hide media from your own user by default ([#29797](https://github.com/element-hq/element-web/pull/29797)). Contributed by @Half-Shot.
|
||||
* Remember whether sidebar is shown for calls when switching rooms ([#30262](https://github.com/element-hq/element-web/pull/30262)). Contributed by @bojidar-bg.
|
||||
* Open the proper integration settings on integrations disabled error ([#30538](https://github.com/element-hq/element-web/pull/30538)). Contributed by @Half-Shot.
|
||||
* Show a "progress" dialog while invites are being sent ([#30561](https://github.com/element-hq/element-web/pull/30561)). Contributed by @richvdh.
|
||||
* Move the room list to the new ListView(backed by react-virtuoso) ([#30515](https://github.com/element-hq/element-web/pull/30515)). Contributed by @langleyd.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Ensure container starts if it is mounted with an empty /modules directory. ([#30705](https://github.com/element-hq/element-web/pull/30705)). Contributed by @RiotRobot.
|
||||
* Fix room joining over federation not specifying vias or using aliases ([#30641](https://github.com/element-hq/element-web/pull/30641)). Contributed by @t3chguy.
|
||||
* Fix stable-suffixed MSC4133 support ([#30649](https://github.com/element-hq/element-web/pull/30649)). Contributed by @dbkr.
|
||||
* Fix i18n of message when a setting is disabled ([#30646](https://github.com/element-hq/element-web/pull/30646)). Contributed by @dbkr.
|
||||
* ListView should not handle the arrow keys if there is a modifier applied ([#30633](https://github.com/element-hq/element-web/pull/30633)). Contributed by @langleyd.
|
||||
* Make BaseDialog's div keyboard focusable and fix test. ([#30631](https://github.com/element-hq/element-web/pull/30631)). Contributed by @langleyd.
|
||||
* Fix: Allow triple-click text selection to flow around pills ([#30349](https://github.com/element-hq/element-web/pull/30349)). Contributed by @AlirezaMrtz.
|
||||
* Watch for a 'join' action to know when the call is connected ([#29492](https://github.com/element-hq/element-web/pull/29492)). Contributed by @robintown.
|
||||
* Fix: add missing tooltip and aria-label to lock icon next to composer ([#30623](https://github.com/element-hq/element-web/pull/30623)). Contributed by @florianduros.
|
||||
* Don't render context menu when scrolling ([#30613](https://github.com/element-hq/element-web/pull/30613)). Contributed by @langleyd.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.110](https://github.com/element-hq/element-desktop/releases/tag/v1.11.110) (2025-08-27)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Hide recovery key when re-entering it while creating or changing it ([#30499](https://github.com/element-hq/element-web/pull/30499)). Contributed by @andybalaam.
|
||||
* Add `?no_universal_links=true` to OIDC url so EX doesn't try to handle it ([#29439](https://github.com/element-hq/element-web/pull/29439)). Contributed by @t3chguy.
|
||||
* Show a blue lock for unencrypted rooms and hide the grey shield for encrypted rooms ([#30440](https://github.com/element-hq/element-web/pull/30440)). Contributed by @langleyd.
|
||||
* Add support for Module API 1.4 ([#30185](https://github.com/element-hq/element-web/pull/30185)). Contributed by @t3chguy.
|
||||
* MVVM - Introduce some helpers for snapshot management ([#30398](https://github.com/element-hq/element-web/pull/30398)). Contributed by @MidhunSureshR.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* A11y: move focus to right panel when opened ([#30553](https://github.com/element-hq/element-web/pull/30553)). Contributed by @florianduros.
|
||||
* Fix e2e warning icon should be white ([#30539](https://github.com/element-hq/element-web/pull/30539)). Contributed by @florianduros.
|
||||
* Remove NoOneHere disabled reason. ([#30524](https://github.com/element-hq/element-web/pull/30524)). Contributed by @toger5.
|
||||
* Fix downloading files with authenticated media API ([#30520](https://github.com/element-hq/element-web/pull/30520)). Contributed by @t3chguy.
|
||||
* Fix call permissions check confusion around element call ([#30521](https://github.com/element-hq/element-web/pull/30521)). Contributed by @t3chguy.
|
||||
* Fix line wrap around emoji verification ([#30523](https://github.com/element-hq/element-web/pull/30523)). Contributed by @t3chguy.
|
||||
* Don't highlight redacted events ([#30519](https://github.com/element-hq/element-web/pull/30519)). Contributed by @t3chguy.
|
||||
* Fix matrix.to links not being handled in the app ([#30522](https://github.com/element-hq/element-web/pull/30522)). Contributed by @t3chguy.
|
||||
* Fix issue of new room list taking up the full width ([#30459](https://github.com/element-hq/element-web/pull/30459)). Contributed by @langleyd.
|
||||
* Fix widget persistence in React development mode ([#30509](https://github.com/element-hq/element-web/pull/30509)). Contributed by @robintown.
|
||||
* Fix widget initialization in React development mode ([#30463](https://github.com/element-hq/element-web/pull/30463)). Contributed by @robintown.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.109](https://github.com/element-hq/element-desktop/releases/tag/v1.11.109) (2025-08-11)
|
||||
========================================================================================================
|
||||
This release supports the upcoming v12 ("hydra") Matrix room version and is necessary to view and participate in these rooms.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* [Backport staging] Allow /upgraderoom command without developer mode enabled ([#30529](https://github.com/element-hq/element-web/pull/30529)). Contributed by @RiotRobot.
|
||||
* [Backport staging] Support for creator/owner power level ([#30526](https://github.com/element-hq/element-web/pull/30526)). Contributed by @RiotRobot.
|
||||
* New room list: change icon and label of menu item for to start a DM ([#30470](https://github.com/element-hq/element-web/pull/30470)). Contributed by @florianduros.
|
||||
* Implement the member list with virtuoso ([#29869](https://github.com/element-hq/element-web/pull/29869)). Contributed by @langleyd.
|
||||
* Add labs option for history sharing on invite ([#30313](https://github.com/element-hq/element-web/pull/30313)). Contributed by @richvdh.
|
||||
* Bump wysiwyg to 2.39.0 adding support for pasting rich text content in the Rich Text Edtior ([#30421](https://github.com/element-hq/element-web/pull/30421)). Contributed by @langleyd.
|
||||
* Support `EventShieldReason.MISMATCHED_SENDER` ([#30403](https://github.com/element-hq/element-web/pull/30403)). Contributed by @richvdh.
|
||||
* Change unencrypted and public pills to blue ([#30399](https://github.com/element-hq/element-web/pull/30399)). Contributed by @florianduros.
|
||||
* Change color of public room icon ([#30390](https://github.com/element-hq/element-web/pull/30390)). Contributed by @florianduros.
|
||||
* Script for updating storybook screenshots ([#30340](https://github.com/element-hq/element-web/pull/30340)). Contributed by @dbkr.
|
||||
* Add toggle to hide empty state in devtools ([#30352](https://github.com/element-hq/element-web/pull/30352)). Contributed by @toger5.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Use userId to filter users in non-federated rooms when showing the InviteDialog ([#30537](https://github.com/element-hq/element-web/pull/30537)). Contributed by @RiotRobot.
|
||||
* [Backport staging] Catch error when encountering invalid m.room.pinned\_events event ([#30536](https://github.com/element-hq/element-web/pull/30536)). Contributed by @RiotRobot.
|
||||
* Update for compatibility with v12 rooms ([#30452](https://github.com/element-hq/element-web/pull/30452)). Contributed by @dbkr.
|
||||
* New room list: fix tooltip on presence ([#30474](https://github.com/element-hq/element-web/pull/30474)). Contributed by @florianduros.
|
||||
* New room list: add tooltip for presence and room status ([#30472](https://github.com/element-hq/element-web/pull/30472)). Contributed by @florianduros.
|
||||
* Fix: Clicking on an item in the member list causes it to scroll to the top rather than show the profile view ([#30455](https://github.com/element-hq/element-web/pull/30455)). Contributed by @langleyd.
|
||||
* Put the 'decrypting' tooltip back ([#30446](https://github.com/element-hq/element-web/pull/30446)). Contributed by @dbkr.
|
||||
* Use server name explicitly for via. ([#30362](https://github.com/element-hq/element-web/pull/30362)). Contributed by @Half-Shot.
|
||||
* fix: replace hardcoded string in poll history dialog ([#30402](https://github.com/element-hq/element-web/pull/30402)). Contributed by @florianduros.
|
||||
* fix: replace hardcoded string on qr code back button ([#30401](https://github.com/element-hq/element-web/pull/30401)). Contributed by @florianduros.
|
||||
* Fix color of icon button with outline ([#30361](https://github.com/element-hq/element-web/pull/30361)). Contributed by @florianduros.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.108](https://github.com/element-hq/element-desktop/releases/tag/v1.11.108) (2025-07-30)
|
||||
========================================================================================================
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Fix downloaded attachments not being decrypted ([#30434](https://github.com/element-hq/element-web/pull/30434)). Contributed by @RiotRobot.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.107](https://github.com/element-hq/element-desktop/releases/tag/v1.11.107) (2025-07-29)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Add support for overlaying notification badges on the Windows Taskbar icon. ([#2443](https://github.com/element-hq/element-desktop/pull/2443)). Contributed by @Half-Shot.
|
||||
* Reduce macos titlebar height with the new room list and expand the existing border ([#2446](https://github.com/element-hq/element-desktop/pull/2446)). Contributed by @florianduros.
|
||||
* Message preview should show tooltip with the full message on hover ([#30265](https://github.com/element-hq/element-web/pull/30265)). Contributed by @MidhunSureshR.
|
||||
* Support rendering notification badges on platforms that do their own icon overlays ([#30315](https://github.com/element-hq/element-web/pull/30315)). Contributed by @Half-Shot.
|
||||
* Add SubscriptionViewModel base class ([#30297](https://github.com/element-hq/element-web/pull/30297)). Contributed by @dbkr.
|
||||
* Enhancement: Save image on CTRL+S ([#30330](https://github.com/element-hq/element-web/pull/30330)). Contributed by @ioalexander.
|
||||
* Add quote functionality to MessageContextMenu (#29893) ([#30323](https://github.com/element-hq/element-web/pull/30323)). Contributed by @AlirezaMrtz.
|
||||
* Initial structure for shared component views ([#30216](https://github.com/element-hq/element-web/pull/30216)). Contributed by @dbkr.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Reduce macos titlebar height with the new room list and expand the existing border ([#2446](https://github.com/element-hq/element-desktop/pull/2446)). Contributed by @florianduros.
|
||||
* [Backport staging] Fix e2e shield being invisible in white mode for encrypted room ([#30411](https://github.com/element-hq/element-web/pull/30411)). Contributed by @RiotRobot.
|
||||
* Force ED titlebar color for new room list ([#30332](https://github.com/element-hq/element-web/pull/30332)). Contributed by @florianduros.
|
||||
* Add a background color to left panel for macos titlebar in element desktop ([#30328](https://github.com/element-hq/element-web/pull/30328)). Contributed by @florianduros.
|
||||
* Fix: Prevent page refresh on Enter key in right panel member search ([#30312](https://github.com/element-hq/element-web/pull/30312)). Contributed by @AlirezaMrtz.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.106](https://github.com/element-hq/element-desktop/releases/tag/v1.11.106) (2025-07-15)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* [Backport staging] Fix e2e icon colour ([#30304](https://github.com/element-hq/element-web/pull/30304)). Contributed by @RiotRobot.
|
||||
* Add support for module message hint `allowDownloadingMedia` ([#30252](https://github.com/element-hq/element-web/pull/30252)). Contributed by @Half-Shot.
|
||||
* Update the mobile\_guide page to the new design and link out to Element X by default. ([#30172](https://github.com/element-hq/element-web/pull/30172)). Contributed by @pixlwave.
|
||||
* Filter settings exported when rageshaking ([#30236](https://github.com/element-hq/element-web/pull/30236)). Contributed by @Half-Shot.
|
||||
* Allow Element Call to learn the room name ([#30213](https://github.com/element-hq/element-web/pull/30213)). Contributed by @robintown.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Fix missing image download button ([#30322](https://github.com/element-hq/element-web/pull/30322)). Contributed by @RiotRobot.
|
||||
* Fix transparent verification checkmark in dark mode ([#30235](https://github.com/element-hq/element-web/pull/30235)). Contributed by @Banbuii.
|
||||
* Fix logic in DeviceListener ([#30230](https://github.com/element-hq/element-web/pull/30230)). Contributed by @uhoreg.
|
||||
* Disable file drag-and-drop if insufficient permissions ([#30186](https://github.com/element-hq/element-web/pull/30186)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.105](https://github.com/element-hq/element-desktop/releases/tag/v1.11.105) (2025-07-01)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Add support for migrating to kwallet6 ([#2390](https://github.com/element-hq/element-desktop/pull/2390)). Contributed by @t3chguy.
|
||||
* New room list: add context menu to room list item ([#29952](https://github.com/element-hq/element-web/pull/29952)). Contributed by @florianduros.
|
||||
* Support for custom message components via Module API ([#30074](https://github.com/element-hq/element-web/pull/30074)). Contributed by @Half-Shot.
|
||||
* Prompt users to set up recovery ([#30075](https://github.com/element-hq/element-web/pull/30075)). Contributed by @uhoreg.
|
||||
* Update `IconButton` colors ([#30124](https://github.com/element-hq/element-web/pull/30124)). Contributed by @florianduros.
|
||||
* New room list: filter list can be collapsed ([#29992](https://github.com/element-hq/element-web/pull/29992)). Contributed by @florianduros.
|
||||
* Show `EmptyRoomListView` when low priority filter matches zero rooms ([#30122](https://github.com/element-hq/element-web/pull/30122)). Contributed by @MidhunSureshR.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix element-desktop-ssoid profile deeplinking for OIDC ([#2396](https://github.com/element-hq/element-desktop/pull/2396)). Contributed by @t3chguy.
|
||||
* Add support for migrating to kwallet6 ([#2390](https://github.com/element-hq/element-desktop/pull/2390)). Contributed by @t3chguy.
|
||||
* Fix untranslatable string "People" in notifications beta ([#30165](https://github.com/element-hq/element-web/pull/30165)). Contributed by @t3chguy.
|
||||
* Force verification even after logging in via delegate ([#30141](https://github.com/element-hq/element-web/pull/30141)). Contributed by @andybalaam.
|
||||
* Hide add integrations button based on UIComponent.AddIntegrations ([#30140](https://github.com/element-hq/element-web/pull/30140)). Contributed by @t3chguy.
|
||||
* Use nav for new room list and label sections ([#30134](https://github.com/element-hq/element-web/pull/30134)). Contributed by @dbkr.
|
||||
* Spacestore should emit event after rebuilding home space ([#30132](https://github.com/element-hq/element-web/pull/30132)). Contributed by @MidhunSureshR.
|
||||
* Handle m.room.pinned\_events being invalid ([#30129](https://github.com/element-hq/element-web/pull/30129)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.104](https://github.com/element-hq/element-desktop/releases/tag/v1.11.104) (2025-06-17)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Update the mobile\_guide page to the new design. ([#30006](https://github.com/element-hq/element-web/pull/30006)). Contributed by @pixlwave.
|
||||
* Provide a devtool for manually verifying other devices ([#30094](https://github.com/element-hq/element-web/pull/30094)). Contributed by @andybalaam.
|
||||
* Implement MSC4155: Invite filtering ([#29603](https://github.com/element-hq/element-web/pull/29603)). Contributed by @Half-Shot.
|
||||
* Add low priority avatar decoration to room tile ([#30065](https://github.com/element-hq/element-web/pull/30065)). Contributed by @MidhunSureshR.
|
||||
* Add ability to prevent window content being captured by other apps (Desktop) ([#30098](https://github.com/element-hq/element-web/pull/30098)). Contributed by @t3chguy.
|
||||
* New room list: move message preview in user settings ([#30023](https://github.com/element-hq/element-web/pull/30023)). Contributed by @florianduros.
|
||||
* New room list: change room options icon ([#30029](https://github.com/element-hq/element-web/pull/30029)). Contributed by @florianduros.
|
||||
* RoomListStore: Sort low priority rooms to the bottom of the list ([#30070](https://github.com/element-hq/element-web/pull/30070)). Contributed by @MidhunSureshR.
|
||||
* Add low priority filter pill to the room list UI ([#30060](https://github.com/element-hq/element-web/pull/30060)). Contributed by @MidhunSureshR.
|
||||
* New room list: remove color gradient in space panel ([#29721](https://github.com/element-hq/element-web/pull/29721)). Contributed by @florianduros.
|
||||
* /share?msg=foo endpoint using forward message dialog ([#29874](https://github.com/element-hq/element-web/pull/29874)). Contributed by @ara4n.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix restart loop in safeStorage ([#2373](https://github.com/element-hq/element-desktop/pull/2373)). Contributed by @dbkr.
|
||||
* Do not send empty auth when setting up cross-signing keys ([#29914](https://github.com/element-hq/element-web/pull/29914)). Contributed by @gnieto.
|
||||
* Settings: flip local video feed by default ([#29501](https://github.com/element-hq/element-web/pull/29501)). Contributed by @jbtrystram.
|
||||
* AccessSecretStorageDialog: various fixes ([#30093](https://github.com/element-hq/element-web/pull/30093)). Contributed by @richvdh.
|
||||
* AccessSecretStorageDialog: fix inability to enter recovery key ([#30090](https://github.com/element-hq/element-web/pull/30090)). Contributed by @richvdh.
|
||||
* Fix failure to upload thumbnail causing image to send as file ([#30086](https://github.com/element-hq/element-web/pull/30086)). Contributed by @t3chguy.
|
||||
* Low priority menu item should be a toggle ([#30071](https://github.com/element-hq/element-web/pull/30071)). Contributed by @MidhunSureshR.
|
||||
* Add sanity checks to prevent users from ignoring themselves ([#30079](https://github.com/element-hq/element-web/pull/30079)). Contributed by @MidhunSureshR.
|
||||
* Fix issue with duplicate images ([#30073](https://github.com/element-hq/element-web/pull/30073)). Contributed by @fatlewis.
|
||||
* Handle errors returned from Seshat ([#30083](https://github.com/element-hq/element-web/pull/30083)). Contributed by @richvdh.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.103](https://github.com/element-hq/element-desktop/releases/tag/v1.11.103) (2025-06-10)
|
||||
========================================================================================================
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
+ Check the sender of an event matches owner of session, preventing sender spoofing by homeserver owners.
|
||||
[13c1d20](https://github.com/matrix-org/matrix-rust-sdk/commit/13c1d2048286bbabf5e7bc6b015aafee98f04d55) (High, [GHSA-x958-rvg6-956w](https://github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-x958-rvg6-956w)).
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.102](https://github.com/element-hq/element-desktop/releases/tag/v1.11.102) (2025-06-03)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Support build-time specified protocol scheme for oidc callback ([#2285](https://github.com/element-hq/element-desktop/pull/2285)). Contributed by @t3chguy.
|
||||
* EW: Modernize the recovery key input modal ([#29819](https://github.com/element-hq/element-web/pull/29819)). Contributed by @uhoreg.
|
||||
* New room list: move secondary filters into primary filters ([#29972](https://github.com/element-hq/element-web/pull/29972)). Contributed by @florianduros.
|
||||
* Prompt the user when key storage is unexpectedly off ([#29912](https://github.com/element-hq/element-web/pull/29912)). Contributed by @andybalaam.
|
||||
* New room list: move sort menu in room list header ([#29983](https://github.com/element-hq/element-web/pull/29983)). Contributed by @florianduros.
|
||||
* New room list: rework spacing of room list item ([#29965](https://github.com/element-hq/element-web/pull/29965)). Contributed by @florianduros.
|
||||
* RLS: Remove forgotten room from skiplist ([#29933](https://github.com/element-hq/element-web/pull/29933)). Contributed by @MidhunSureshR.
|
||||
* Add room list sorting ([#29951](https://github.com/element-hq/element-web/pull/29951)). Contributed by @dbkr.
|
||||
* Don't use the minimised width(68px) on the new room list ([#29778](https://github.com/element-hq/element-web/pull/29778)). Contributed by @langleyd.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Enable plain text encryption before checking if encryption is available ([#2343](https://github.com/element-hq/element-desktop/pull/2343)). Contributed by @MidhunSureshR.
|
||||
* Enable plain text encryption early if we actually mean to use `basic_text` as backend ([#2341](https://github.com/element-hq/element-desktop/pull/2341)). Contributed by @MidhunSureshR.
|
||||
* [Backport staging] Close call options popup menu when option has been selected ([#30054](https://github.com/element-hq/element-web/pull/30054)). Contributed by @RiotRobot.
|
||||
* RoomListStoreV3: Only add new rooms that pass `VisibilityProvider` check ([#29974](https://github.com/element-hq/element-web/pull/29974)). Contributed by @MidhunSureshR.
|
||||
* Re-order primary filters ([#29957](https://github.com/element-hq/element-web/pull/29957)). Contributed by @dbkr.
|
||||
* Fix leaky CSS adding `!` to all H1 elements ([#29964](https://github.com/element-hq/element-web/pull/29964)). Contributed by @t3chguy.
|
||||
* Fix extensions panel style ([#29273](https://github.com/element-hq/element-web/pull/29273)). Contributed by @langleyd.
|
||||
* Fix state events being hidden from widgets in read\_events actions ([#29954](https://github.com/element-hq/element-web/pull/29954)). Contributed by @robintown.
|
||||
* Remove old filter test ([#29963](https://github.com/element-hq/element-web/pull/29963)). Contributed by @dbkr.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.101](https://github.com/element-hq/element-desktop/releases/tag/v1.11.101) (2025-05-20)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Migrate from keytar to safeStorage ([#2227](https://github.com/element-hq/element-desktop/pull/2227)). Contributed by @t3chguy.
|
||||
* New room list: add keyboard navigation support ([#29805](https://github.com/element-hq/element-web/pull/29805)). Contributed by @florianduros.
|
||||
* Use the JoinRuleSettings component for the guest link access prompt. ([#28614](https://github.com/element-hq/element-web/pull/28614)). Contributed by @toger5.
|
||||
* Add loading state to the new room list view ([#29725](https://github.com/element-hq/element-web/pull/29725)). Contributed by @langleyd.
|
||||
* Make OIDC identity reset consistent with EX ([#29854](https://github.com/element-hq/element-web/pull/29854)). Contributed by @andybalaam.
|
||||
* Support error code for email / phone adding unsupported (MSC4178) ([#29855](https://github.com/element-hq/element-web/pull/29855)). Contributed by @dbkr.
|
||||
* Update identity reset UI (Make consistent with EX) ([#29701](https://github.com/element-hq/element-web/pull/29701)). Contributed by @andybalaam.
|
||||
* Add secondary filters to the new room list ([#29818](https://github.com/element-hq/element-web/pull/29818)). Contributed by @dbkr.
|
||||
* Fix battery drain from Web Audio ([#29203](https://github.com/element-hq/element-web/pull/29203)). Contributed by @mbachry.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix go home shortcut on macos and change toggle action events shortcut ([#29929](https://github.com/element-hq/element-web/pull/29929)). Contributed by @florianduros.
|
||||
* New room list: fix outdated message preview when space or filter change ([#29925](https://github.com/element-hq/element-web/pull/29925)). Contributed by @florianduros.
|
||||
* Stop migrating to MSC4278 if the config exists. ([#29924](https://github.com/element-hq/element-web/pull/29924)). Contributed by @Half-Shot.
|
||||
* Ensure consistent download file name on download from ImageView ([#29913](https://github.com/element-hq/element-web/pull/29913)). Contributed by @t3chguy.
|
||||
* Add error toast when service worker registration fails ([#29895](https://github.com/element-hq/element-web/pull/29895)). Contributed by @t3chguy.
|
||||
* New Room List: Prevent old tombstoned rooms from appearing in the list ([#29881](https://github.com/element-hq/element-web/pull/29881)). Contributed by @MidhunSureshR.
|
||||
* Remove lag in search field ([#29885](https://github.com/element-hq/element-web/pull/29885)). Contributed by @florianduros.
|
||||
* Respect UIFeature.Voip ([#29873](https://github.com/element-hq/element-web/pull/29873)). Contributed by @langleyd.
|
||||
* Allow jumping to message search from spotlight ([#29850](https://github.com/element-hq/element-web/pull/29850)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.100](https://github.com/element-hq/element-desktop/releases/tag/v1.11.100) (2025-05-06)
|
||||
========================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Move rich topics out of labs / stabilise MSC3765 ([#29817](https://github.com/element-hq/element-web/pull/29817)). Contributed by @Johennes.
|
||||
* Spell out that Element Web does \*not\* work on mobile. ([#29211](https://github.com/element-hq/element-web/pull/29211)). Contributed by @ara4n.
|
||||
* Add message preview support to the new room list ([#29784](https://github.com/element-hq/element-web/pull/29784)). Contributed by @dbkr.
|
||||
* Global configuration flag for media previews ([#29582](https://github.com/element-hq/element-web/pull/29582)). Contributed by @Half-Shot.
|
||||
* New room list: add partial keyboard shortcuts support ([#29783](https://github.com/element-hq/element-web/pull/29783)). Contributed by @florianduros.
|
||||
* MVVM RoomSummaryCard Topic ([#29710](https://github.com/element-hq/element-web/pull/29710)). Contributed by @MarcWadai.
|
||||
* Warn on self change from settings > roles ([#28926](https://github.com/element-hq/element-web/pull/28926)). Contributed by @MarcWadai.
|
||||
* New room list: new visual for invitation ([#29773](https://github.com/element-hq/element-web/pull/29773)). Contributed by @florianduros.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Apply workaround to fix app launching on Linux ([#2308](https://github.com/element-hq/element-desktop/pull/2308)). Contributed by @dbkr.
|
||||
* Notification fixes for Windows - AppID name was messing up handler ([#2275](https://github.com/element-hq/element-desktop/pull/2275)). Contributed by @Fusseldieb.
|
||||
* Fix incorrect display of the user info display name ([#29826](https://github.com/element-hq/element-web/pull/29826)). Contributed by @langleyd.
|
||||
* RoomListStore: Remove invite rooms on decline ([#29804](https://github.com/element-hq/element-web/pull/29804)). Contributed by @MidhunSureshR.
|
||||
* Fix the buttons not being displayed with long preview text ([#29811](https://github.com/element-hq/element-web/pull/29811)). Contributed by @dbkr.
|
||||
* New room list: fix missing/incorrect notification decoration ([#29796](https://github.com/element-hq/element-web/pull/29796)). Contributed by @florianduros.
|
||||
* New Room List: Prevent potential scroll jump/flicker when switching spaces ([#29781](https://github.com/element-hq/element-web/pull/29781)). Contributed by @MidhunSureshR.
|
||||
* New room list: fix incorrect decoration ([#29770](https://github.com/element-hq/element-web/pull/29770)). Contributed by @florianduros.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.99](https://github.com/element-hq/element-desktop/releases/tag/v1.11.99) (2025-04-23)
|
||||
======================================================================================================
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Fix `io.element.desktop` protocol handler ([#2281](https://github.com/element-hq/element-desktop/pull/2281)). Contributed by @RiotRobot.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.98](https://github.com/element-hq/element-desktop/releases/tag/v1.11.98) (2025-04-22)
|
||||
======================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Remove support for 32 bit / ia32 Windows. ([#2225](https://github.com/element-hq/element-desktop/pull/2225)). Contributed by @Half-Shot.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* Update config logging to specify config file path ([#2231](https://github.com/element-hq/element-desktop/pull/2231)). Contributed by @nbolton.
|
||||
* Support specifying the profile dir path via env var (#2226) ([#2246](https://github.com/element-hq/element-desktop/pull/2246)). Contributed by @schuhj.
|
||||
* print better errors in the search view instead of a blocking modal ([#29724](https://github.com/element-hq/element-web/pull/29724)). Contributed by @Jujure.
|
||||
* New room list: video room and video call decoration ([#29693](https://github.com/element-hq/element-web/pull/29693)). Contributed by @florianduros.
|
||||
* Remove Secure Backup, Cross-signing and Cryptography sections in `Security & Privacy` user settings ([#29088](https://github.com/element-hq/element-web/pull/29088)). Contributed by @florianduros.
|
||||
* Allow reporting a room when rejecting an invite. ([#29570](https://github.com/element-hq/element-web/pull/29570)). Contributed by @Half-Shot.
|
||||
* RoomListViewModel: Reset primary and secondary filters on space change ([#29672](https://github.com/element-hq/element-web/pull/29672)). Contributed by @MidhunSureshR.
|
||||
* RoomListStore: Support specific sorting requirements for muted rooms ([#29665](https://github.com/element-hq/element-web/pull/29665)). Contributed by @MidhunSureshR.
|
||||
* New room list: add notification options menu ([#29639](https://github.com/element-hq/element-web/pull/29639)). Contributed by @florianduros.
|
||||
* Room List: Scroll to top of the list when active room is not in the list ([#29650](https://github.com/element-hq/element-web/pull/29650)). Contributed by @MidhunSureshR.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix unwanted form submit behaviour in memberlist ([#29747](https://github.com/element-hq/element-web/pull/29747)). Contributed by @MidhunSureshR.
|
||||
* New room list: fix public room icon visibility when filter change ([#29737](https://github.com/element-hq/element-web/pull/29737)). Contributed by @florianduros.
|
||||
* Fix custom theme support for short hex \& rgba hex strings ([#29726](https://github.com/element-hq/element-web/pull/29726)). Contributed by @t3chguy.
|
||||
* New room list: minor visual fixes ([#29723](https://github.com/element-hq/element-web/pull/29723)). Contributed by @florianduros.
|
||||
* Fix getOidcCallbackUrl for Element Desktop ([#29711](https://github.com/element-hq/element-web/pull/29711)). Contributed by @t3chguy.
|
||||
* Fix some webp images improperly marked as animated ([#29713](https://github.com/element-hq/element-web/pull/29713)). Contributed by @Petersmit27.
|
||||
* Revert deletion of hydrateSession ([#29703](https://github.com/element-hq/element-web/pull/29703)). Contributed by @Jujure.
|
||||
* Fix converttoroom \& converttodm not working ([#29705](https://github.com/element-hq/element-web/pull/29705)). Contributed by @t3chguy.
|
||||
* Ensure forceCloseAllModals also closes priority/static modals ([#29706](https://github.com/element-hq/element-web/pull/29706)). Contributed by @t3chguy.
|
||||
* Continue button is disabled when uploading a recovery key file ([#29695](https://github.com/element-hq/element-web/pull/29695)). Contributed by @Giwayume.
|
||||
* Catch errors after syncing recovery ([#29691](https://github.com/element-hq/element-web/pull/29691)). Contributed by @andybalaam.
|
||||
* New room list: fix multiple visual issues ([#29673](https://github.com/element-hq/element-web/pull/29673)). Contributed by @florianduros.
|
||||
* New Room List: Fix mentions filter matching rooms with any highlight ([#29668](https://github.com/element-hq/element-web/pull/29668)). Contributed by @MidhunSureshR.
|
||||
* Fix truncated emoji label during emoji SAS ([#29643](https://github.com/element-hq/element-web/pull/29643)). Contributed by @florianduros.
|
||||
* Remove duplicate jitsi link ([#29642](https://github.com/element-hq/element-web/pull/29642)). Contributed by @dbkr.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.97](https://github.com/element-hq/element-desktop/releases/tag/v1.11.97) (2025-04-08)
|
||||
======================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* New room list: reduce padding between avatar and room list border ([#29634](https://github.com/element-hq/element-web/pull/29634)). Contributed by @florianduros.
|
||||
* Bundle Element Call with Element Web packages ([#29309](https://github.com/element-hq/element-web/pull/29309)). Contributed by @t3chguy.
|
||||
* Hide an event notification if it is redacted ([#29605](https://github.com/element-hq/element-web/pull/29605)). Contributed by @Half-Shot.
|
||||
* Docker: Use nginx-unprivileged as base image ([#29353](https://github.com/element-hq/element-web/pull/29353)). Contributed by @AndrewFerr.
|
||||
* Switch away from nesting React trees and mangling the DOM ([#29586](https://github.com/element-hq/element-web/pull/29586)). Contributed by @t3chguy.
|
||||
* New room list: add notification decoration ([#29552](https://github.com/element-hq/element-web/pull/29552)). Contributed by @florianduros.
|
||||
* RoomListStore: Unread filter should match rooms that were marked as unread ([#29580](https://github.com/element-hq/element-web/pull/29580)). Contributed by @MidhunSureshR.
|
||||
* Add support for hiding videos ([#29496](https://github.com/element-hq/element-web/pull/29496)). Contributed by @Half-Shot.
|
||||
* Use an outline icon for the report room button ([#29573](https://github.com/element-hq/element-web/pull/29573)). Contributed by @robintown.
|
||||
* Generate/load pickle key on SSO ([#29568](https://github.com/element-hq/element-web/pull/29568)). Contributed by @Jujure.
|
||||
* Add report room dialog button/dialog. ([#29513](https://github.com/element-hq/element-web/pull/29513)). Contributed by @Half-Shot.
|
||||
* RoomListViewModel: Make the active room sticky in the list ([#29551](https://github.com/element-hq/element-web/pull/29551)). Contributed by @MidhunSureshR.
|
||||
* Replace checkboxes with Compound checkboxes, and appropriately label each checkbox. ([#29363](https://github.com/element-hq/element-web/pull/29363)). Contributed by @Half-Shot.
|
||||
* New room list: add selection decoration ([#29531](https://github.com/element-hq/element-web/pull/29531)). Contributed by @florianduros.
|
||||
* Simplified Sliding Sync ([#28515](https://github.com/element-hq/element-web/pull/28515)). Contributed by @dbkr.
|
||||
* Add ability to hide images after clicking "show image" ([#29467](https://github.com/element-hq/element-web/pull/29467)). Contributed by @Half-Shot.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix scroll issues in memberlist ([#29392](https://github.com/element-hq/element-web/pull/29392)). Contributed by @MidhunSureshR.
|
||||
* Ensure clicks on spoilers do not get handled by the hidden content ([#29618](https://github.com/element-hq/element-web/pull/29618)). Contributed by @t3chguy.
|
||||
* New room list: add cursor pointer on room list item ([#29627](https://github.com/element-hq/element-web/pull/29627)). Contributed by @florianduros.
|
||||
* Fix missing ambiguous url tooltips on Element Desktop ([#29619](https://github.com/element-hq/element-web/pull/29619)). Contributed by @t3chguy.
|
||||
* New room list: fix spacing and padding ([#29607](https://github.com/element-hq/element-web/pull/29607)). Contributed by @florianduros.
|
||||
* Make fetchdep check out matching branch name ([#29601](https://github.com/element-hq/element-web/pull/29601)). Contributed by @dbkr.
|
||||
* Fix MFileBody fileName not considering `filename` ([#29589](https://github.com/element-hq/element-web/pull/29589)). Contributed by @t3chguy.
|
||||
* Fix token expiry racing with login causing wrong error to be shown ([#29566](https://github.com/element-hq/element-web/pull/29566)). Contributed by @t3chguy.
|
||||
* Fix bug which caused startup to hang if the clock was wound back since a previous session ([#29558](https://github.com/element-hq/element-web/pull/29558)). Contributed by @richvdh.
|
||||
* RoomListViewModel: Reset any primary filter on secondary filter change ([#29562](https://github.com/element-hq/element-web/pull/29562)). Contributed by @MidhunSureshR.
|
||||
* RoomListStore: Unread filter should only filter rooms having unread counts ([#29555](https://github.com/element-hq/element-web/pull/29555)). Contributed by @MidhunSureshR.
|
||||
* In force-verify mode, prevent bypassing by cancelling device verification ([#29487](https://github.com/element-hq/element-web/pull/29487)). Contributed by @andybalaam.
|
||||
* Add title attribute to user identifier ([#29547](https://github.com/element-hq/element-web/pull/29547)). Contributed by @arpitbatra123.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.96](https://github.com/element-hq/element-desktop/releases/tag/v1.11.96) (2025-03-25)
|
||||
======================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* RoomListViewModel: Track the index of the active room in the list ([#29519](https://github.com/element-hq/element-web/pull/29519)). Contributed by @MidhunSureshR.
|
||||
* New room list: add empty state ([#29512](https://github.com/element-hq/element-web/pull/29512)). Contributed by @florianduros.
|
||||
* Implement `MessagePreviewViewModel` ([#29514](https://github.com/element-hq/element-web/pull/29514)). Contributed by @MidhunSureshR.
|
||||
* RoomListViewModel: Add functionality to toggle message preview setting ([#29511](https://github.com/element-hq/element-web/pull/29511)). Contributed by @MidhunSureshR.
|
||||
* New room list: add more options menu on room list item ([#29445](https://github.com/element-hq/element-web/pull/29445)). Contributed by @florianduros.
|
||||
* RoomListViewModel: Provide a way to resort the room list and track the active sort method ([#29499](https://github.com/element-hq/element-web/pull/29499)). Contributed by @MidhunSureshR.
|
||||
* Change \*All rooms\* meta space name to \*All Chats\* ([#29498](https://github.com/element-hq/element-web/pull/29498)). Contributed by @florianduros.
|
||||
* Add setting to hide avatars of rooms you have been invited to. ([#29497](https://github.com/element-hq/element-web/pull/29497)). Contributed by @Half-Shot.
|
||||
* Room List Store: Save preferred sorting algorithm and use that on app launch ([#29493](https://github.com/element-hq/element-web/pull/29493)). Contributed by @MidhunSureshR.
|
||||
* Add key storage toggle to Encryption settings ([#29310](https://github.com/element-hq/element-web/pull/29310)). Contributed by @dbkr.
|
||||
* New room list: add primary filters ([#29481](https://github.com/element-hq/element-web/pull/29481)). Contributed by @florianduros.
|
||||
* Implement MSC4142: Remove unintentional intentional mentions in replies ([#28209](https://github.com/element-hq/element-web/pull/28209)). Contributed by @tulir.
|
||||
* White background for 'They do not match' button ([#29470](https://github.com/element-hq/element-web/pull/29470)). Contributed by @andybalaam.
|
||||
* RoomListViewModel: Support secondary filters in the view model ([#29465](https://github.com/element-hq/element-web/pull/29465)). Contributed by @MidhunSureshR.
|
||||
* RoomListViewModel: Support primary filters in the view model ([#29454](https://github.com/element-hq/element-web/pull/29454)). Contributed by @MidhunSureshR.
|
||||
* Room List Store: Implement secondary filters ([#29458](https://github.com/element-hq/element-web/pull/29458)). Contributed by @MidhunSureshR.
|
||||
* Room List Store: Implement rest of the primary filters ([#29444](https://github.com/element-hq/element-web/pull/29444)). Contributed by @MidhunSureshR.
|
||||
* Room List Store: Support filters by implementing just the favourite filter ([#29433](https://github.com/element-hq/element-web/pull/29433)). Contributed by @MidhunSureshR.
|
||||
* Move toggle switch for integration manager for a11y ([#29436](https://github.com/element-hq/element-web/pull/29436)). Contributed by @Half-Shot.
|
||||
* New room list: basic flat list ([#29368](https://github.com/element-hq/element-web/pull/29368)). Contributed by @florianduros.
|
||||
* Improve rageshake upload experience by providing useful error information ([#29378](https://github.com/element-hq/element-web/pull/29378)). Contributed by @Half-Shot.
|
||||
* Add more functionality to the room list vm ([#29402](https://github.com/element-hq/element-web/pull/29402)). Contributed by @MidhunSureshR.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix `--no-update` command line flag ([#2210](https://github.com/element-hq/element-desktop/pull/2210)). Contributed by @t3chguy.
|
||||
* New room list: fix compose menu action in space ([#29500](https://github.com/element-hq/element-web/pull/29500)). Contributed by @florianduros.
|
||||
* Change ToggleHiddenEventVisibility \& GoToHome KeyBindingActions ([#29374](https://github.com/element-hq/element-web/pull/29374)). Contributed by @gy-mate.
|
||||
* Fix Docker Healthcheck ([#29471](https://github.com/element-hq/element-web/pull/29471)). Contributed by @benbz.
|
||||
* Room List Store: Fetch rooms after space store is ready + attach store to window ([#29453](https://github.com/element-hq/element-web/pull/29453)). Contributed by @MidhunSureshR.
|
||||
* Room List Store: Fix bug where left rooms appear in room list ([#29452](https://github.com/element-hq/element-web/pull/29452)). Contributed by @MidhunSureshR.
|
||||
* Add space to the bottom of the room summary actions below leave room ([#29270](https://github.com/element-hq/element-web/pull/29270)). Contributed by @langleyd.
|
||||
* Show error screens in group calls ([#29254](https://github.com/element-hq/element-web/pull/29254)). Contributed by @robintown.
|
||||
* Prevent user from accidentally triggering multiple identity resets ([#29388](https://github.com/element-hq/element-web/pull/29388)). Contributed by @uhoreg.
|
||||
* Remove buggy tooltip on room intro \& homepage ([#29406](https://github.com/element-hq/element-web/pull/29406)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.95](https://github.com/element-hq/element-desktop/releases/tag/v1.11.95) (2025-03-11)
|
||||
======================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Switch to shiftkey/node-keytar as it has NAPI 10 updates ([#2172](https://github.com/element-hq/element-desktop/pull/2172)). Contributed by @t3chguy.
|
||||
* Add support for Windows arm64 ([#624](https://github.com/element-hq/element-desktop/pull/624)). Contributed by @t3chguy.
|
||||
* Room List Store: Filter rooms by active space ([#29399](https://github.com/element-hq/element-web/pull/29399)). Contributed by @MidhunSureshR.
|
||||
* Room List - Update the room list store on actions from the dispatcher ([#29397](https://github.com/element-hq/element-web/pull/29397)). Contributed by @MidhunSureshR.
|
||||
* Room List - Implement a minimal view model ([#29357](https://github.com/element-hq/element-web/pull/29357)). Contributed by @MidhunSureshR.
|
||||
* New room list: add space menu in room header ([#29352](https://github.com/element-hq/element-web/pull/29352)). Contributed by @florianduros.
|
||||
* Room List - Store sorted rooms in skip list ([#29345](https://github.com/element-hq/element-web/pull/29345)). Contributed by @MidhunSureshR.
|
||||
* New room list: add dial to search section ([#29359](https://github.com/element-hq/element-web/pull/29359)). Contributed by @florianduros.
|
||||
* New room list: add compose menu for spaces in header ([#29347](https://github.com/element-hq/element-web/pull/29347)). Contributed by @florianduros.
|
||||
* Use EditInPlace control for Identity Server picker to improve a11y ([#29280](https://github.com/element-hq/element-web/pull/29280)). Contributed by @Half-Shot.
|
||||
* First step to add header to new room list ([#29320](https://github.com/element-hq/element-web/pull/29320)). Contributed by @florianduros.
|
||||
* Add Windows 64-bit arm link and remove 32-bit link on compatibility page ([#29312](https://github.com/element-hq/element-web/pull/29312)). Contributed by @t3chguy.
|
||||
* Honour the backup disable flag from Element X ([#29290](https://github.com/element-hq/element-web/pull/29290)). Contributed by @dbkr.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix edited code block width ([#29394](https://github.com/element-hq/element-web/pull/29394)). Contributed by @florianduros.
|
||||
* new room list: keep space name in one line in header ([#29369](https://github.com/element-hq/element-web/pull/29369)). Contributed by @florianduros.
|
||||
* Dismiss "Key storage out of sync" toast when secrets received ([#29348](https://github.com/element-hq/element-web/pull/29348)). Contributed by @richvdh.
|
||||
* Minor CSS fixes for the new room list ([#29334](https://github.com/element-hq/element-web/pull/29334)). Contributed by @florianduros.
|
||||
* Add padding to room header icon ([#29271](https://github.com/element-hq/element-web/pull/29271)). Contributed by @langleyd.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.94](https://github.com/element-hq/element-desktop/releases/tag/v1.11.94) (2025-02-27)
|
||||
======================================================================================================
|
||||
* No changes
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] fix: /tmp/element-web-config may already exist preventing the container from booting up ([#29377](https://github.com/element-hq/element-web/pull/29377)). Contributed by @RiotRobot.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.93](https://github.com/element-hq/element-desktop/releases/tag/v1.11.93) (2025-02-25)
|
||||
======================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* [backport] Dynamically load Element Web modules in Docker entrypoint ([#29358](https://github.com/element-hq/element-web/pull/29358)). Contributed by @t3chguy.
|
||||
* ChangeRecoveryKey: error handling ([#29262](https://github.com/element-hq/element-web/pull/29262)). Contributed by @richvdh.
|
||||
* Dehydration: enable dehydrated device on "Set up recovery" ([#29265](https://github.com/element-hq/element-web/pull/29265)). Contributed by @richvdh.
|
||||
* Render reason for invite rejection. ([#29257](https://github.com/element-hq/element-web/pull/29257)). Contributed by @Half-Shot.
|
||||
* New room list: add search section ([#29251](https://github.com/element-hq/element-web/pull/29251)). Contributed by @florianduros.
|
||||
* New room list: hide favourites and people meta spaces ([#29241](https://github.com/element-hq/element-web/pull/29241)). Contributed by @florianduros.
|
||||
* New Room List: Create new labs flag ([#29239](https://github.com/element-hq/element-web/pull/29239)). Contributed by @MidhunSureshR.
|
||||
* Stop URl preview from covering message box ([#29215](https://github.com/element-hq/element-web/pull/29215)). Contributed by @edent.
|
||||
* Rename "security key" into "recovery key" ([#29217](https://github.com/element-hq/element-web/pull/29217)). Contributed by @florianduros.
|
||||
* Add new verification section to user profile ([#29200](https://github.com/element-hq/element-web/pull/29200)). Contributed by @MidhunSureshR.
|
||||
* Initial support for runtime modules ([#29104](https://github.com/element-hq/element-web/pull/29104)). Contributed by @t3chguy.
|
||||
* Add `Forgot recovery key?` button to encryption tab ([#29202](https://github.com/element-hq/element-web/pull/29202)). Contributed by @florianduros.
|
||||
* Add KeyIcon to key storage out of sync toast ([#29201](https://github.com/element-hq/element-web/pull/29201)). Contributed by @florianduros.
|
||||
* Improve rendering of empty topics in the timeline ([#29152](https://github.com/element-hq/element-web/pull/29152)). Contributed by @Half-Shot.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix font scaling in member list ([#29285](https://github.com/element-hq/element-web/pull/29285)). Contributed by @florianduros.
|
||||
* Grow member list search field when resizing the right panel ([#29267](https://github.com/element-hq/element-web/pull/29267)). Contributed by @langleyd.
|
||||
* Don't reload roomview on offline connectivity check ([#29243](https://github.com/element-hq/element-web/pull/29243)). Contributed by @dbkr.
|
||||
* Respect user's 12/24 hour preference consistently ([#29237](https://github.com/element-hq/element-web/pull/29237)). Contributed by @t3chguy.
|
||||
* Restore the accessibility role on call views ([#29225](https://github.com/element-hq/element-web/pull/29225)). Contributed by @robintown.
|
||||
* Revert `GoToHome` keyboard shortcut to `Ctrl`–`Shift`–`H` on macOS ([#28577](https://github.com/element-hq/element-web/pull/28577)). Contributed by @gy-mate.
|
||||
* Encryption tab: display correct encryption panel when user cancels the reset identity flow ([#29216](https://github.com/element-hq/element-web/pull/29216)). Contributed by @florianduros.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.92](https://github.com/element-hq/element-desktop/releases/tag/v1.11.92) (2025-02-11)
|
||||
======================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,8 +1,8 @@
|
||||
# Docker image to facilitate building Element Desktop's native bits using a glibc version (2.31)
|
||||
# with broader compatibility, down to Debian bullseye & Ubuntu focal.
|
||||
FROM rust:bullseye
|
||||
FROM rust:bullseye@sha256:8d9b5e80062dcdb7f037b4eef92a6649632034d6811652e028706ea93db64159
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN curl --proto "=https" -L https://yarnpkg.com/latest.tar.gz | tar xvz && mv yarn-* /yarn && ln -s /yarn/bin/yarn /usr/bin/yarn
|
||||
RUN apt-get -qq update && apt-get -y -qq dist-upgrade && \
|
||||
@@ -16,13 +16,12 @@ RUN apt-get -qq update && apt-get -y -qq dist-upgrade && \
|
||||
apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/*
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python & ln -s /usr/bin/pip3 /usr/bin/pip
|
||||
|
||||
ENV DEBUG_COLORS true
|
||||
ENV FORCE_COLOR true
|
||||
ENV DEBUG_COLORS=true
|
||||
ENV FORCE_COLOR=true
|
||||
|
||||
WORKDIR /project
|
||||
|
||||
ENV NODE_VERSION 20.18.2
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
COPY setup.sh /setup.sh
|
||||
COPY .node-version dockerbuild/setup.sh /
|
||||
RUN /setup.sh
|
||||
|
||||
@@ -3,5 +3,9 @@
|
||||
set -x
|
||||
declare -A archMap=(["amd64"]="x64" ["arm64"]="arm64")
|
||||
ARCH="${archMap["$TARGETARCH"]}"
|
||||
# The .node-version file generally doesn't have the 'v' (renovate does not put the 'v' and will
|
||||
# strip it on upgrade if it's there) but the 'v' is also widely supported so we probably ought
|
||||
# to just work either way.
|
||||
NODE_VERSION=$(cat /.node-version | sed -e 's/^v//')
|
||||
curl --proto "=https" -L "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-$TARGETOS-$ARCH.tar.gz" | tar xz -C /usr/local --strip-components=1 && \
|
||||
unlink /usr/local/CHANGELOG.md && unlink /usr/local/LICENSE && unlink /usr/local/README.md
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
- [Introduction](../README.md)
|
||||
|
||||
# Build
|
||||
# Build/Debug
|
||||
|
||||
- [Native Node modules](native-node-modules.md)
|
||||
- [Windows requirements](windows-requirements.md)
|
||||
- [Debugging](debugging.md)
|
||||
- [Using gdb](gdb.md)
|
||||
|
||||
# Distribution
|
||||
|
||||
|
||||
28
docs/debugging.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Debugging Element-Desktop
|
||||
|
||||
There are two parts of the desktop app that you might want to debug.
|
||||
|
||||
## The renderer process
|
||||
|
||||
This is the regular element-web codeand can be debugged by just selecting 'toggle developer tools'
|
||||
from the menu, even on ppackaged builds. This then works the same as chrome dev tools for element web.
|
||||
|
||||
## The main process
|
||||
|
||||
This is debugged as a node app, so:
|
||||
|
||||
1. Open any chrome dev tools window
|
||||
1. Start element with the `--inspect-brk` flag
|
||||
1. Notice that you now have a little green icon in the top left of your chrome devtools window, click it.
|
||||
|
||||
You are now debugging the code of the desktop app itself.
|
||||
|
||||
## The main process of a package app
|
||||
|
||||
When the app is shipped, electron's "fuses" are flipped, editing the electron binary itself to prevent certain features from being usable, one of which is debugging using `--inspect-brk` as above. You can flip the fuse back on Linux as follows:
|
||||
|
||||
```
|
||||
sudo npx @electron/fuses write --app /opt/Element/element-desktop EnableNodeCliInspectArguments=on
|
||||
```
|
||||
|
||||
A similar command will work, in theory, on mac and windows, except that this will break code signing (which is the point of fuses) so you would have to re-sign the app or somesuch.
|
||||
46
docs/gdb.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Using gdb against Element-Desktop
|
||||
|
||||
Occasionally it is useful to be able to connect to a running Element-Desktop
|
||||
with [`gdb`](https://sourceware.org/gdb/), or to analayze a coredump. For this,
|
||||
you will need debug symbols.
|
||||
|
||||
1. If you don't already have the right version of Element-Desktop (eg because
|
||||
you are analyzing someone else's coredump), download and unpack the tarball
|
||||
from https://packages.element.io/desktop/install/linux/. If it was a
|
||||
nightly, your best bet may be to download the deb from
|
||||
https://packages.element.io/debian/pool/main/e/element-nightly/ and unpack
|
||||
it.
|
||||
2. Figure out which version of Electron your Element-Desktop is based on. The
|
||||
best way to do this is to figure out the version of Element-Desktop, then
|
||||
look at
|
||||
[`yarn.lock`](https://github.com/element-hq/element-desktop/blob/develop/yarn.lock)
|
||||
for the corresponding version. There should be an entry starting
|
||||
`electron@`, and under it a `version` line: this will tell you the version
|
||||
of Electron that was used for that version of Element-Desktop.
|
||||
|
||||
3. Go to [Electron's releases page](https://github.com/electron/electron/releases/)
|
||||
and find the version you just identified. Under "Assets", download
|
||||
`electron-v<version>-linux-x64-debug.zip` (or, the -debug zip corresponding to your
|
||||
architecture).
|
||||
|
||||
4. The debug zip has a structure like:
|
||||
|
||||
```
|
||||
.
|
||||
├── debug
|
||||
│ ├── chrome_crashpad_handler.debug
|
||||
│ ├── electron.debug
|
||||
│ ├── libEGL.so.debug
|
||||
│ ├── libffmpeg.so.debug
|
||||
│ ├── libGLESv2.so.debug
|
||||
│ └── libvk_swiftshader.so.debug
|
||||
├── LICENSE
|
||||
├── LICENSES.chromium.html
|
||||
└── version
|
||||
```
|
||||
|
||||
Take all the contents of `debug`, and copy them into the Element-Desktop directory,
|
||||
so that `electron.debug` is alongside the `element-desktop-nightly` executable.
|
||||
|
||||
5. You now have a thing you can gdb as normal, either as `gdb --args element-desktop-nightly`, or
|
||||
`gdb element-desktop-nightly core`.
|
||||
@@ -12,6 +12,13 @@ If you want to build native modules, make sure that the following tools are inst
|
||||
- [Strawberry Perl](https://strawberryperl.com/)
|
||||
- [Rustup](https://rustup.rs/)
|
||||
- [NASM](https://www.nasm.us/)
|
||||
|
||||
You can install the above tools using [Chocolatey](https://chocolatey.org/install):
|
||||
|
||||
```cmd
|
||||
choco install --no-progress -y git nodejs-lts yarn python StrawberryPerl rustup.install nasm magicsplat-tcl-tk
|
||||
```
|
||||
|
||||
- [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019) with the following configuration:
|
||||
- On the Workloads tab:
|
||||
- Desktop & Mobile -> C++ build tools
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import * as os from "node:os";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as plist from "plist";
|
||||
import { AfterPackContext, Arch, Configuration as BaseConfiguration, Platform } from "electron-builder";
|
||||
import { computeData } from "app-builder-lib/out/asar/integrity";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { type Configuration as BaseConfiguration, type Protocol } from "electron-builder";
|
||||
|
||||
/**
|
||||
* This script has different outputs depending on your os platform.
|
||||
*
|
||||
* On Windows:
|
||||
* Prefixes the nightly version with `0.0.1-nightly.` as it breaks if it is not semver
|
||||
* Passes $ED_SIGNTOOL_THUMBPRINT and $ED_SIGNTOOL_SUBJECT_NAME to
|
||||
* build.win.signtoolOptions.signingHashAlgorithms and build.win.signtoolOptions.certificateSubjectName respectively if specified.
|
||||
*
|
||||
@@ -20,24 +16,68 @@ import { readFile, writeFile } from "node:fs/promises";
|
||||
* Passes $ED_DEBIAN_CHANGELOG to build.deb.fpm if specified
|
||||
*/
|
||||
|
||||
const NIGHTLY_APP_ID = "im.riot.nightly";
|
||||
const NIGHTLY_DEB_NAME = "element-nightly";
|
||||
|
||||
/**
|
||||
* Interface describing relevant fields of the package.json file.
|
||||
*/
|
||||
interface Pkg {
|
||||
version: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base metadata fields, used in both package.json and the variant configuration.
|
||||
*/
|
||||
interface Metadata {
|
||||
name: string;
|
||||
productName: string;
|
||||
description: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra metadata fields that are injected into the build to pass to the app at runtime.
|
||||
*/
|
||||
interface ExtraMetadata extends Metadata {
|
||||
electron_appId: string;
|
||||
electron_protocol: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface describing the variant configuration format.
|
||||
*/
|
||||
interface Variant extends Metadata {
|
||||
"appId": string;
|
||||
"linux.executableName"?: string;
|
||||
"linux.deb.name"?: string;
|
||||
"protocols": string[];
|
||||
}
|
||||
|
||||
type Writable<T> = NonNullable<
|
||||
T extends Function ? T : T extends object ? { -readonly [K in keyof T]: Writable<T[K]> } : T
|
||||
>;
|
||||
|
||||
const pkg: Pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||
// Load the default variant as a base configuration
|
||||
const DEFAULT_VARIANT = path.join("element.io", "release", "build.json");
|
||||
let variant: Variant = JSON.parse(fs.readFileSync(DEFAULT_VARIANT, "utf8"));
|
||||
|
||||
/**
|
||||
* If a variant is specified, we will use it to override the build-specific values.
|
||||
* This allows us to have different builds for different purposes (e.g. stable, nightly).
|
||||
*/
|
||||
if (process.env.VARIANT_PATH) {
|
||||
console.log(`Using variant configuration from '${process.env.VARIANT_PATH}':`);
|
||||
variant = {
|
||||
...variant,
|
||||
...JSON.parse(fs.readFileSync(`${process.env.VARIANT_PATH}`, "utf8")),
|
||||
};
|
||||
} else {
|
||||
console.warn(`No VARIANT_PATH specified, using default variant configuration '${DEFAULT_VARIANT}':`);
|
||||
}
|
||||
|
||||
for (const key in variant) {
|
||||
console.log(`${key}: ${variant[key]}`);
|
||||
}
|
||||
|
||||
interface Configuration extends BaseConfiguration {
|
||||
extraMetadata: Partial<Pick<Pkg, "version">> & Omit<Pkg, "version">;
|
||||
extraMetadata: Partial<Pick<Pkg, "version">> & ExtraMetadata;
|
||||
linux: BaseConfiguration["linux"];
|
||||
win: BaseConfiguration["win"];
|
||||
mac: BaseConfiguration["mac"];
|
||||
@@ -46,26 +86,6 @@ interface Configuration extends BaseConfiguration {
|
||||
} & BaseConfiguration["deb"];
|
||||
}
|
||||
|
||||
async function injectAsarIntegrity(context: AfterPackContext) {
|
||||
const packager = context.packager;
|
||||
|
||||
// We only need to re-generate asar on universal Mac builds, due to https://github.com/electron/universal/issues/116
|
||||
if (packager.platform !== Platform.MAC || context.arch !== Arch.universal) return;
|
||||
|
||||
const resourcesPath = packager.getResourcesDir(context.appOutDir);
|
||||
const asarIntegrity = await computeData({
|
||||
resourcesPath,
|
||||
resourcesRelativePath: "Resources",
|
||||
resourcesDestinationPath: resourcesPath,
|
||||
extraResourceMatchers: [],
|
||||
});
|
||||
|
||||
const plistPath = path.join(resourcesPath, "..", "Info.plist");
|
||||
const data = plist.parse(await readFile(plistPath, "utf8")) as unknown as Writable<plist.PlistObject>;
|
||||
data["ElectronAsarIntegrity"] = asarIntegrity as unknown as Writable<plist.PlistValue>;
|
||||
await writeFile(plistPath, plist.build(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import('electron-builder').Configuration}
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
@@ -74,7 +94,7 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
// Make all fuses required to ensure they are all explicitly specified
|
||||
electronFuses: Required<Configuration["electronFuses"]>;
|
||||
} = {
|
||||
appId: "im.riot.app",
|
||||
appId: variant.appId,
|
||||
asarUnpack: "**/*.node",
|
||||
electronFuses: {
|
||||
enableCookieEncryption: true,
|
||||
@@ -90,9 +110,6 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
loadBrowserProcessSpecificV8Snapshot: false,
|
||||
enableEmbeddedAsarIntegrityValidation: true,
|
||||
},
|
||||
afterPack: async (context: AfterPackContext) => {
|
||||
await injectAsarIntegrity(context);
|
||||
},
|
||||
files: [
|
||||
"package.json",
|
||||
{
|
||||
@@ -101,23 +118,19 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
},
|
||||
"lib/**",
|
||||
],
|
||||
extraResources: [
|
||||
{
|
||||
from: "res/img",
|
||||
to: "img",
|
||||
},
|
||||
"webapp.asar",
|
||||
],
|
||||
extraResources: ["build/icon.*", "webapp.asar"],
|
||||
extraMetadata: {
|
||||
name: pkg.name,
|
||||
productName: pkg.productName,
|
||||
description: pkg.description,
|
||||
name: variant.name,
|
||||
productName: variant.productName,
|
||||
description: variant.description,
|
||||
electron_appId: variant.appId,
|
||||
electron_protocol: variant.protocols[0],
|
||||
},
|
||||
linux: {
|
||||
target: ["tar.gz", "deb"],
|
||||
category: "Network;InstantMessaging;Chat",
|
||||
maintainer: "support@element.io",
|
||||
icon: "build/icons",
|
||||
icon: "icon.png",
|
||||
executableName: variant.name, // element-desktop or element-desktop-nightly
|
||||
},
|
||||
deb: {
|
||||
packageCategory: "net",
|
||||
@@ -135,32 +148,26 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
"libgbm1",
|
||||
],
|
||||
recommends: ["libsqlcipher0", "element-io-archive-keyring"],
|
||||
fpm: [
|
||||
"--deb-field",
|
||||
"Replaces: riot-desktop (<< 1.7.0), riot-web (<< 1.7.0)",
|
||||
"--deb-field",
|
||||
"Breaks: riot-desktop (<< 1.7.0), riot-web (<< 1.7.0)",
|
||||
"--deb-pre-depends",
|
||||
"libc6 (>= 2.31)",
|
||||
],
|
||||
fpm: ["--deb-pre-depends", "libc6 (>= 2.31)"],
|
||||
},
|
||||
mac: {
|
||||
target: ["dmg", "zip"],
|
||||
category: "public.app-category.social-networking",
|
||||
darkModeSupport: true,
|
||||
hardenedRuntime: true,
|
||||
gatekeeperAssess: true,
|
||||
// XXX: we cannot specify this due to https://github.com/electron/osx-sign/issues/344
|
||||
// strictVerify: true,
|
||||
strictVerify: true,
|
||||
entitlements: "./build/entitlements.mac.plist",
|
||||
icon: "build/icons/icon.icns",
|
||||
icon: "build/icon.icns",
|
||||
mergeASARs: true,
|
||||
x64ArchFiles: "**/matrix-seshat/*.node", // hak already runs lipo
|
||||
},
|
||||
win: {
|
||||
target: ["squirrel", "msi"],
|
||||
signtoolOptions: {
|
||||
signingHashAlgorithms: ["sha256"],
|
||||
},
|
||||
icon: "build/icons/icon.ico",
|
||||
icon: "build/icon.ico",
|
||||
},
|
||||
msi: {
|
||||
perMachine: true,
|
||||
@@ -168,14 +175,28 @@ const config: Omit<Writable<Configuration>, "electronFuses"> & {
|
||||
directories: {
|
||||
output: "dist",
|
||||
},
|
||||
protocols: [
|
||||
{
|
||||
name: "element",
|
||||
schemes: ["io.element.desktop", "element"],
|
||||
},
|
||||
],
|
||||
protocols: {
|
||||
name: variant.productName,
|
||||
schemes: variant.protocols,
|
||||
},
|
||||
nativeRebuilder: "sequential",
|
||||
nodeGypRebuild: false,
|
||||
npmRebuild: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Allow specifying the version via env var.
|
||||
* If unspecified, it will default to the version in package.json.
|
||||
* @param {string} process.env.VERSION
|
||||
*/
|
||||
if (process.env.VERSION) {
|
||||
config.extraMetadata.version = process.env.VERSION;
|
||||
}
|
||||
|
||||
if (variant["linux.deb.name"]) {
|
||||
config.deb.fpm.push("--name", variant["linux.deb.name"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow specifying windows signing cert via env vars
|
||||
* @param {string} process.env.ED_SIGNTOOL_SUBJECT_NAME
|
||||
@@ -186,31 +207,6 @@ if (process.env.ED_SIGNTOOL_SUBJECT_NAME && process.env.ED_SIGNTOOL_THUMBPRINT)
|
||||
config.win.signtoolOptions!.certificateSha1 = process.env.ED_SIGNTOOL_THUMBPRINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow specifying nightly version via env var
|
||||
* @param {string} process.env.ED_NIGHTLY
|
||||
*/
|
||||
if (process.env.ED_NIGHTLY) {
|
||||
config.deb.fpm = []; // Clear the fpm as the breaks deb fields don't apply to nightly
|
||||
|
||||
config.appId = NIGHTLY_APP_ID;
|
||||
config.extraMetadata.productName += " Nightly";
|
||||
config.extraMetadata.name += "-nightly";
|
||||
config.extraMetadata.description += " (nightly unstable build)";
|
||||
config.deb.fpm.push("--name", NIGHTLY_DEB_NAME);
|
||||
|
||||
let version = process.env.ED_NIGHTLY;
|
||||
if (os.platform() === "win32") {
|
||||
// The windows packager relies on parsing this as semver, so we have to make it look like one.
|
||||
// This will give our update packages really stupid names, but we probably can't change that either
|
||||
// because squirrel windows parses them for the version too. We don't really care: nobody sees them.
|
||||
// We just give the installer a static name, so you'll just see this in the 'about' dialog.
|
||||
// Turns out if you use 0.0.0 here it makes Squirrel windows crash, so we use 0.0.1.
|
||||
version = "0.0.1-nightly." + version;
|
||||
}
|
||||
config.extraMetadata.version = version;
|
||||
}
|
||||
|
||||
if (os.platform() === "linux") {
|
||||
// Electron crashes on debian if there's a space in the path.
|
||||
// https://github.com/vector-im/element-web/issues/13171
|
||||
|
||||
9
element.io/nightly/build.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"appId": "im.riot.nightly",
|
||||
"name": "element-desktop-nightly",
|
||||
"productName": "Element Nightly",
|
||||
"description": "Element: the future of secure communication (nightly unstable build)",
|
||||
"protocols": ["io.element.nightly", "element"],
|
||||
"linux.executableName": "element-desktop-nightly",
|
||||
"linux.deb.name": "element-nightly"
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
"https://scalar-staging.vector.im/api",
|
||||
"https://scalar-staging.riot.im/scalar/api"
|
||||
],
|
||||
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
|
||||
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"show_labs_settings": true,
|
||||
"room_directory": {
|
||||
|
||||
7
element.io/release/build.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"appId": "im.riot.app",
|
||||
"name": "element-desktop",
|
||||
"productName": "Element",
|
||||
"description": "Element: the future of secure communication",
|
||||
"protocols": ["io.element.desktop", "element"]
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
"https://scalar-staging.vector.im/api",
|
||||
"https://scalar-staging.riot.im/scalar/api"
|
||||
],
|
||||
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
|
||||
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"room_directory": {
|
||||
"servers": ["matrix.org", "gitter.im"]
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import path from "node:path";
|
||||
|
||||
import type HakEnv from "../../scripts/hak/hakEnv.js";
|
||||
import type { DependencyInfo } from "../../scripts/hak/dep.js";
|
||||
|
||||
export default async function buildKeytar(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const env = hakEnv.makeGypEnv();
|
||||
|
||||
console.log("Running yarn with env", env);
|
||||
await hakEnv.spawn(
|
||||
path.join(moduleInfo.nodeModuleBinDir, "node-gyp"),
|
||||
["rebuild", "--arch", hakEnv.getTargetArch()],
|
||||
{
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
env,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type HakEnv from "../../scripts/hak/hakEnv.js";
|
||||
import type { DependencyInfo } from "../../scripts/hak/dep.js";
|
||||
|
||||
export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
// node-gyp uses python for reasons beyond comprehension
|
||||
await hakEnv.checkTools([["python", "--version"]]);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"scripts": {
|
||||
"check": "check.ts",
|
||||
"build": "build.ts"
|
||||
},
|
||||
"copy": "build/Release/keytar.node",
|
||||
"dependencies": {
|
||||
"libsecret": "0.20.3"
|
||||
}
|
||||
}
|
||||
5
knip.ts
@@ -1,16 +1,17 @@
|
||||
import { KnipConfig } from "knip";
|
||||
|
||||
export default {
|
||||
entry: ["src/electron-main.ts", "src/preload.ts", "electron-builder.ts", ".eslintrc-*.js", "scripts/**", "hak/**"],
|
||||
entry: ["src/preload.cts", "electron-builder.ts", "scripts/**", "hak/**"],
|
||||
project: ["**/*.{js,ts}"],
|
||||
ignoreDependencies: [
|
||||
// Brought in via hak scripts
|
||||
"keytar",
|
||||
"matrix-seshat",
|
||||
// Required for `action-validator`
|
||||
"@action-validator/*",
|
||||
// Used for git pre-commit hooks
|
||||
"husky",
|
||||
// Required for `patch-package`
|
||||
"postinstall-postinstall",
|
||||
],
|
||||
ignoreBinaries: ["jq", "scripts/in-docker.sh"],
|
||||
} satisfies KnipConfig;
|
||||
|
||||
55
package.json
@@ -3,9 +3,12 @@
|
||||
"productName": "Element",
|
||||
"main": "lib/electron-main.js",
|
||||
"exports": "./lib/electron-main.js",
|
||||
"version": "1.11.91",
|
||||
"version": "1.12.4",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": "Element",
|
||||
"author": {
|
||||
"name": "Element",
|
||||
"email": "support@element.io"
|
||||
},
|
||||
"homepage": "https://element.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -44,7 +47,7 @@
|
||||
"build": "yarn run build:ts && yarn run build:res && electron-builder",
|
||||
"build:ts": "tsc",
|
||||
"build:res": "tsx scripts/copy-res.ts",
|
||||
"docker:setup": "docker build --platform linux/amd64 -t element-desktop-dockerbuild dockerbuild",
|
||||
"docker:setup": "docker build --platform linux/amd64 -t element-desktop-dockerbuild -f dockerbuild/Dockerfile .",
|
||||
"docker:build:native": "scripts/in-docker.sh yarn run hak",
|
||||
"docker:build": "scripts/in-docker.sh yarn run build",
|
||||
"docker:install": "scripts/in-docker.sh yarn install",
|
||||
@@ -53,18 +56,19 @@
|
||||
"test": "playwright test",
|
||||
"test:open": "yarn test --ui",
|
||||
"test:screenshots:build": "docker build playwright -t element-desktop-playwright --platform linux/amd64",
|
||||
"test:screenshots:run": "docker run --rm --network host -v $(pwd):/work/element-desktop -v /var/run/docker.sock:/var/run/docker.sock --platform linux/amd64 -it element-desktop-playwright"
|
||||
"test:screenshots:run": "docker run --rm --network host -v $(pwd):/work/element-desktop -v /var/run/docker.sock:/var/run/docker.sock --platform linux/amd64 -it element-desktop-playwright",
|
||||
"postinstall": "patch-package && electron-builder install-app-deps"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/electron": "^5.0.0",
|
||||
"@standardnotes/electron-clear-data": "^1.0.5",
|
||||
"@sentry/electron": "^7.0.0",
|
||||
"auto-launch": "^5.0.5",
|
||||
"counterpart": "^0.18.6",
|
||||
"electron-store": "^10.0.0",
|
||||
"electron-store": "^11.0.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"keytar-forked": "7.10.0",
|
||||
"minimist": "^1.2.6",
|
||||
"png-to-ico": "^2.1.1",
|
||||
"uuid": "^11.0.0"
|
||||
"png-to-ico": "^3.0.0",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@action-validator/cli": "^0.6.0",
|
||||
@@ -72,50 +76,51 @@
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@electron/asar": "3.2.18",
|
||||
"@playwright/test": "1.50.1",
|
||||
"@stylistic/eslint-plugin": "^3.0.0",
|
||||
"@electron/asar": "4.0.1",
|
||||
"@playwright/test": "1.56.1",
|
||||
"@stylistic/eslint-plugin": "^5.0.0",
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
"@types/counterpart": "^0.18.1",
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@types/node": "18.19.74",
|
||||
"@types/node": "18.19.130",
|
||||
"@types/pacote": "^11.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"app-builder-lib": "26.0.1",
|
||||
"app-builder-lib": "26.1.0",
|
||||
"chokidar": "^4.0.0",
|
||||
"detect-libc": "^2.0.0",
|
||||
"electron": "34.0.2",
|
||||
"electron-builder": "26.0.1",
|
||||
"electron-builder-squirrel-windows": "26.0.1",
|
||||
"electron": "39.1.1",
|
||||
"electron-builder": "26.1.0",
|
||||
"electron-builder-squirrel-windows": "26.1.0",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-matrix-org": "^2.0.1",
|
||||
"eslint-plugin-matrix-org": "^3.0.0",
|
||||
"eslint-plugin-n": "^17.12.0",
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
"glob": "^11.0.0",
|
||||
"husky": "^9.1.6",
|
||||
"knip": "^5.0.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"lint-staged": "^16.0.0",
|
||||
"matrix-web-i18n": "^3.2.1",
|
||||
"mkdirp": "^3.0.0",
|
||||
"pacote": "^21.0.0",
|
||||
"plist": "^3.1.0",
|
||||
"patch-package": "^8.0.1",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^3.0.0",
|
||||
"rimraf": "^6.0.0",
|
||||
"tar": "^7.0.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "5.7.3"
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"hakDependencies": {
|
||||
"matrix-seshat": "^4.0.0",
|
||||
"keytar": "^7.9.0"
|
||||
"matrix-seshat": "^4.0.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "18.19.74",
|
||||
"config-file-ts": "0.2.8-rc1"
|
||||
"@types/node": "18.19.130",
|
||||
"config-file-ts": "0.2.8-rc1",
|
||||
"node-abi": "4.17.0"
|
||||
}
|
||||
}
|
||||
|
||||
18
patches/@types+auto-launch+5.0.5.patch
Normal file
@@ -0,0 +1,18 @@
|
||||
diff --git a/node_modules/@types/auto-launch/index.d.ts b/node_modules/@types/auto-launch/index.d.ts
|
||||
index a30a77c..e512ce1 100644
|
||||
--- a/node_modules/@types/auto-launch/index.d.ts
|
||||
+++ b/node_modules/@types/auto-launch/index.d.ts
|
||||
@@ -25,6 +25,13 @@ interface AutoLaunchOptions {
|
||||
declare class AutoLaunch {
|
||||
constructor(options: AutoLaunchOptions);
|
||||
|
||||
+ /**
|
||||
+ * This type describes the internal options of the `auto-launch` package which allows us to update options after initialization.
|
||||
+ */
|
||||
+ readonly opts: {
|
||||
+ isHiddenOnLaunch: boolean;
|
||||
+ };
|
||||
+
|
||||
/**
|
||||
* Enables auto-launch at start up.
|
||||
*/
|
||||
@@ -8,7 +8,25 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { defineConfig } from "@playwright/test";
|
||||
|
||||
const projects = [
|
||||
"macos",
|
||||
"win-x64",
|
||||
"win-ia32",
|
||||
"win-arm64",
|
||||
"linux-amd64-sqlcipher-system",
|
||||
"linux-amd64-sqlcipher-static",
|
||||
"linux-arm64-sqlcipher-system",
|
||||
"linux-arm64-sqlcipher-static",
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
// Allows the GitHub action to specify a project name (OS + arch) for the combined report to make sense
|
||||
// workaround for https://github.com/microsoft/playwright/issues/33521
|
||||
projects: process.env.CI
|
||||
? projects.map((name) => ({
|
||||
name,
|
||||
}))
|
||||
: undefined,
|
||||
use: {
|
||||
viewport: { width: 1280, height: 720 },
|
||||
video: "retain-on-failure",
|
||||
@@ -18,7 +36,7 @@ export default defineConfig({
|
||||
outputDir: "playwright/test-results",
|
||||
workers: 1,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reporter: [["html", { outputFolder: "playwright/html-report" }]],
|
||||
reporter: process.env.CI ? [["blob"], ["github"]] : [["html", { outputFolder: "playwright/html-report" }]],
|
||||
snapshotDir: "playwright/snapshots",
|
||||
snapshotPathTemplate: "{snapshotDir}/{testFilePath}/{arg}-{platform}{ext}",
|
||||
timeout: 30 * 1000,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/playwright:v1.50.1-jammy
|
||||
FROM mcr.microsoft.com/playwright:v1.56.1-jammy@sha256:d518367161e599b64e4e8b83ff180be45bfe22efb78dde77fc4c2942340fe8ca
|
||||
|
||||
WORKDIR /work/element-desktop
|
||||
|
||||
|
||||
@@ -6,21 +6,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { platform } from "node:os";
|
||||
import keytar from "keytar-forked";
|
||||
|
||||
import { test, expect } from "../../element-desktop-test.js";
|
||||
|
||||
declare global {
|
||||
interface ElectronPlatform {
|
||||
getEventIndexingManager():
|
||||
| {
|
||||
supportsEventIndexing(): Promise<boolean>;
|
||||
}
|
||||
| undefined;
|
||||
getPickleKey(userId: string, deviceId: string): Promise<string | null>;
|
||||
createPickleKey(userId: string, deviceId: string): Promise<string | null>;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
mxPlatformPeg: {
|
||||
get(): {
|
||||
getEventIndexingManager():
|
||||
| {
|
||||
supportsEventIndexing(): Promise<boolean>;
|
||||
}
|
||||
| undefined;
|
||||
createPickleKey(userId: string, deviceId: string): Promise<string | null>;
|
||||
};
|
||||
get(): ElectronPlatform;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -46,13 +49,57 @@ test.describe("App launch", () => {
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
test("should launch and render the welcome view successfully and support keytar", async ({ page }) => {
|
||||
test.skip(platform() === "linux", "This test does not yet support Linux");
|
||||
test.describe("safeStorage", () => {
|
||||
const userId = "@user:server";
|
||||
const deviceId = "ABCDEF";
|
||||
|
||||
await expect(
|
||||
page.evaluate<string | null>(async () => {
|
||||
return await window.mxPlatformPeg.get().createPickleKey("@user:server", "ABCDEF");
|
||||
}),
|
||||
).resolves.not.toBeNull();
|
||||
test("should be supported", async ({ page }) => {
|
||||
await expect(
|
||||
page.evaluate(
|
||||
([userId, deviceId]) => window.mxPlatformPeg.get().createPickleKey(userId, deviceId),
|
||||
[userId, deviceId],
|
||||
),
|
||||
).resolves.not.toBeNull();
|
||||
});
|
||||
|
||||
test.describe("migrate from keytar", () => {
|
||||
test.skip(
|
||||
process.env.GITHUB_ACTIONS && ["linux", "darwin"].includes(process.platform),
|
||||
"GitHub Actions hosted runner are not a compatible environment for this test",
|
||||
);
|
||||
|
||||
const pickleKey = "DEADBEEF1234";
|
||||
const keytarService = "element.io";
|
||||
const keytarKey = `${userId}|${deviceId}`;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await keytar.setPassword(keytarService, keytarKey, pickleKey);
|
||||
await expect(keytar.getPassword(keytarService, keytarKey)).resolves.toBe(pickleKey);
|
||||
});
|
||||
test.afterAll(async () => {
|
||||
await keytar.deletePassword(keytarService, keytarKey);
|
||||
});
|
||||
|
||||
test("should migrate successfully", async ({ page }) => {
|
||||
await expect(
|
||||
page.evaluate(
|
||||
([userId, deviceId]) => window.mxPlatformPeg.get().getPickleKey(userId, deviceId),
|
||||
[userId, deviceId],
|
||||
),
|
||||
).resolves.toBe(pickleKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("--no-update", () => {
|
||||
test.use({
|
||||
extraArgs: ["--no-update"],
|
||||
});
|
||||
|
||||
// XXX: this test works fine locally but in CI the app start races with the test plumbing up the stdout/stderr pipes
|
||||
// which means the logs are missed, disabling for now.
|
||||
test.skip("should respect option", async ({ page, stdout }) => {
|
||||
expect(stdout.data.toString()).toContain("Auto update disabled via command line flag");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
36
playwright/e2e/launch/oidc.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { test, expect } from "../../element-desktop-test.js";
|
||||
|
||||
declare global {
|
||||
interface ElectronPlatform {
|
||||
getOidcCallbackUrl(): URL;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
mxPlatformPeg: {
|
||||
get(): ElectronPlatform;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
test.describe("OIDC Native", () => {
|
||||
test.slow();
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.locator(".mx_Welcome").waitFor();
|
||||
});
|
||||
|
||||
test("should use OIDC callback URL without authority component", async ({ page }) => {
|
||||
await expect(
|
||||
page.evaluate<string>(() => {
|
||||
return window.mxPlatformPeg.get().getOidcCallbackUrl().toString();
|
||||
}),
|
||||
).resolves.toMatch(/io\.element\.(desktop|nightly):\/vector\/webapp\//);
|
||||
});
|
||||
});
|
||||
@@ -11,12 +11,37 @@ import fs from "node:fs/promises";
|
||||
import path, { dirname } from "node:path";
|
||||
import os from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { PassThrough } from "node:stream";
|
||||
|
||||
/**
|
||||
* A PassThrough stream that captures all data written to it.
|
||||
*/
|
||||
class CapturedPassThrough extends PassThrough {
|
||||
private _chunks = [];
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
super.on("data", this.onData);
|
||||
}
|
||||
|
||||
private onData = (chunk): void => {
|
||||
this._chunks.push(chunk);
|
||||
};
|
||||
|
||||
public get data(): Buffer {
|
||||
return Buffer.concat(this._chunks);
|
||||
}
|
||||
}
|
||||
|
||||
interface Fixtures {
|
||||
app: ElectronApplication;
|
||||
tmpDir: string;
|
||||
extraEnv: Record<string, string>;
|
||||
extraArgs: string[];
|
||||
|
||||
// Utilities to capture stdout and stderr for tests to make assertions against
|
||||
stdout: CapturedPassThrough;
|
||||
stderr: CapturedPassThrough;
|
||||
}
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -24,15 +49,34 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
export const test = base.extend<Fixtures>({
|
||||
extraEnv: {},
|
||||
extraArgs: [],
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
stdout: async ({}, use) => {
|
||||
await use(new CapturedPassThrough());
|
||||
},
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
stderr: async ({}, use) => {
|
||||
await use(new CapturedPassThrough());
|
||||
},
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
tmpDir: async ({}, use) => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "element-desktop-tests-"));
|
||||
console.log("Using temp profile directory: ", tmpDir);
|
||||
await use(tmpDir);
|
||||
await fs.rm(tmpDir, { recursive: true });
|
||||
},
|
||||
app: async ({ tmpDir, extraEnv, extraArgs }, use) => {
|
||||
const args = ["--profile-dir", tmpDir];
|
||||
app: async ({ tmpDir, extraEnv, extraArgs, stdout, stderr }, use) => {
|
||||
const args = ["--profile-dir", tmpDir, ...extraArgs];
|
||||
|
||||
if (process.env.GITHUB_ACTIONS) {
|
||||
if (process.platform === "linux") {
|
||||
// GitHub Actions hosted runner lacks dbus and a compatible keyring, so we need to force plaintext storage
|
||||
args.push("--storage-mode", "force-plaintext");
|
||||
} else if (process.platform === "darwin") {
|
||||
// GitHub Actions hosted runner has no working default keychain, so allow plaintext storage
|
||||
args.push("--storage-mode", "allow-plaintext");
|
||||
}
|
||||
}
|
||||
|
||||
const executablePath = process.env["ELEMENT_DESKTOP_EXECUTABLE"];
|
||||
if (!executablePath) {
|
||||
@@ -40,17 +84,19 @@ export const test = base.extend<Fixtures>({
|
||||
args.unshift(path.join(__dirname, "..", "lib", "electron-main.js"));
|
||||
}
|
||||
|
||||
console.log(`Launching '${executablePath}' with args ${args.join(" ")}`);
|
||||
|
||||
const app = await electron.launch({
|
||||
env: {
|
||||
...process.env,
|
||||
...extraEnv,
|
||||
},
|
||||
executablePath,
|
||||
args: [...args, ...extraArgs],
|
||||
args,
|
||||
});
|
||||
|
||||
app.process().stdout.pipe(process.stdout);
|
||||
app.process().stderr.pipe(process.stderr);
|
||||
app.process().stdout.pipe(stdout).pipe(process.stdout);
|
||||
app.process().stderr.pipe(stderr).pipe(process.stderr);
|
||||
|
||||
await app.firstWindow();
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
48
scripts/branch-match.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script for downloading a branch of element-web matching the branch a PR is contributed from
|
||||
|
||||
set -x
|
||||
|
||||
deforg="element-hq"
|
||||
defrepo="element-web"
|
||||
|
||||
# The PR_NUMBER variable must be set explicitly.
|
||||
default_org_repo=${GITHUB_REPOSITORY:-"$deforg/$defrepo"}
|
||||
PR_ORG=${PR_ORG:-${default_org_repo%%/*}}
|
||||
PR_REPO=${PR_REPO:-${default_org_repo##*/}}
|
||||
|
||||
# A function that clones a branch of a repo based on the org, repo and branch
|
||||
clone() {
|
||||
org=$1
|
||||
repo=$2
|
||||
branch=$3
|
||||
if [ -n "$branch" ]
|
||||
then
|
||||
echo "Trying to use $org/$repo#$branch"
|
||||
# Disable auth prompts: https://serverfault.com/a/665959
|
||||
GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Getting info about a PR with number $PR_NUMBER"
|
||||
apiEndpoint="https://api.github.com/repos/$PR_ORG/$PR_REPO/pulls/$PR_NUMBER"
|
||||
head=$(curl "$apiEndpoint" | jq -r '.head.label')
|
||||
|
||||
# for forks, $head will be in the format "fork:branch", so we split it by ":"
|
||||
# into an array. On non-forks, this has the effect of splitting into a single
|
||||
# element array given ":" shouldn't appear in the head - it'll just be the
|
||||
# branch name. Based on the results, we clone.
|
||||
BRANCH_ARRAY=(${head//:/ })
|
||||
TRY_ORG=$deforg
|
||||
TRY_BRANCH=${BRANCH_ARRAY[0]}
|
||||
if [[ "$head" == *":"* ]]; then
|
||||
# ... but only match that fork if it's a real fork
|
||||
if [ "${BRANCH_ARRAY[0]}" != "$PR_ORG" ]; then
|
||||
TRY_ORG=${BRANCH_ARRAY[0]}
|
||||
fi
|
||||
TRY_BRANCH=${BRANCH_ARRAY[1]}
|
||||
fi
|
||||
clone "$TRY_ORG" "$defrepo" "$TRY_BRANCH"
|
||||
|
||||
exit 1
|
||||
7
scripts/cl.bat
Normal file
@@ -0,0 +1,7 @@
|
||||
REM Batch file to aid in cross-compiling sqlcipher for Windows ARM64
|
||||
REM Full path should be passed to Makefile.msc as NCC env var
|
||||
|
||||
setlocal
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" %VSCMD_ARG_HOST_ARCH%
|
||||
cl.exe %*
|
||||
endlocal
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
|
||||
deforg="$1"
|
||||
defrepo="$2"
|
||||
defbranch="$3"
|
||||
|
||||
[ -z "$defbranch" ] && defbranch="develop"
|
||||
|
||||
rm -r "$defrepo" || true
|
||||
|
||||
clone() {
|
||||
org=$1
|
||||
repo=$2
|
||||
branch=$3
|
||||
if [ -n "$branch" ]
|
||||
then
|
||||
echo "Trying to use $org/$repo#$branch"
|
||||
# Disable auth prompts: https://serverfault.com/a/665959
|
||||
GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Try the PR author's branch in case it exists on the deps as well.
|
||||
# If BUILDKITE_BRANCH is set, it will contain either:
|
||||
# * "branch" when the author's branch and target branch are in the same repo
|
||||
# * "author:branch" when the author's branch is in their fork
|
||||
# We can split on `:` into an array to check.
|
||||
BUILDKITE_BRANCH_ARRAY=(${BUILDKITE_BRANCH//:/ })
|
||||
if [[ "${#BUILDKITE_BRANCH_ARRAY[@]}" == "1" ]]; then
|
||||
clone $deforg $defrepo $BUILDKITE_BRANCH
|
||||
elif [[ "${#BUILDKITE_BRANCH_ARRAY[@]}" == "2" ]]; then
|
||||
clone ${BUILDKITE_BRANCH_ARRAY[0]} $defrepo ${BUILDKITE_BRANCH_ARRAY[1]}
|
||||
fi
|
||||
# Try the target branch of the push or PR.
|
||||
clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
|
||||
# Try the current branch from Jenkins.
|
||||
clone $deforg $defrepo `"echo $GIT_BRANCH" | sed -e 's/^origin\///'`
|
||||
# Use the default branch as the last resort.
|
||||
clone $deforg $defrepo $defbranch
|
||||
@@ -67,14 +67,14 @@ Hak is divided into lifecycle stages, in order:
|
||||
|
||||
# hak.json
|
||||
|
||||
The scripts section contains scripts used for lifecycle stages that need them (fetch, fetchDeps, build).
|
||||
The scripts section contains scripts used for lifecycle stages that need them (fetch, build).
|
||||
It also contains 'prune' and 'copy' which are globs of files to delete from the output module directory
|
||||
and copy over from the module build directory to the output module directory, respectively.
|
||||
|
||||
# Shortcomings
|
||||
|
||||
Hak doesn't know about dependencies between lifecycle stages, ie. it doesn't know that you need to
|
||||
'fetch' and 'fetchDeps' before you can 'build', etc. You get to run each individually, and remember
|
||||
'fetch' before you can 'build', etc. You get to run each individually, and remember
|
||||
the right order.
|
||||
|
||||
There is also a _lot_ of duplication in the command execution: we should abstract away
|
||||
|
||||
@@ -10,7 +10,5 @@ import type { DependencyInfo } from "./dep.js";
|
||||
import type HakEnv from "./hakEnv.js";
|
||||
|
||||
export default async function check(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (moduleInfo.scripts.check) {
|
||||
await moduleInfo.scripts.check(hakEnv, moduleInfo);
|
||||
}
|
||||
await moduleInfo.scripts.check?.(hakEnv, moduleInfo);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
import path from "node:path";
|
||||
import fsProm from "node:fs/promises";
|
||||
import childProcess from "node:child_process";
|
||||
import { rimraf } from "rimraf";
|
||||
import { glob } from "glob";
|
||||
import { mkdirp } from "mkdirp";
|
||||
|
||||
@@ -17,20 +16,6 @@ import type HakEnv from "./hakEnv.js";
|
||||
import type { DependencyInfo } from "./dep.js";
|
||||
|
||||
export default async function copy(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (moduleInfo.cfg.prune) {
|
||||
console.log("Removing " + moduleInfo.cfg.prune + " from " + moduleInfo.moduleOutDir);
|
||||
// rimraf doesn't have a 'cwd' option: it always uses process.cwd()
|
||||
// (and if you set glob.cwd it just breaks because it can't find the files)
|
||||
const oldCwd = process.cwd();
|
||||
try {
|
||||
await mkdirp(moduleInfo.moduleOutDir);
|
||||
process.chdir(moduleInfo.moduleOutDir);
|
||||
await rimraf(moduleInfo.cfg.prune);
|
||||
} finally {
|
||||
process.chdir(oldCwd);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleInfo.cfg.copy) {
|
||||
// If there are multiple moduleBuildDirs, singular moduleBuildDir
|
||||
// is the same as moduleBuildDirs[0], so we're just listing the contents
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mkdirp } from "mkdirp";
|
||||
|
||||
import type { DependencyInfo } from "./dep.js";
|
||||
import type HakEnv from "./hakEnv.js";
|
||||
|
||||
export default async function fetchDeps(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
await mkdirp(moduleInfo.moduleDotHakDir);
|
||||
if (moduleInfo.scripts.fetchDeps) {
|
||||
await moduleInfo.scripts.fetchDeps(hakEnv, moduleInfo);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import packageJson from "../../package.json";
|
||||
const MODULECOMMANDS = ["check", "fetch", "link", "build", "copy", "clean"];
|
||||
|
||||
// Shortcuts for multiple commands at once (useful for building universal binaries
|
||||
// because you can run the fetch/fetchDeps/build for each arch and then copy/link once)
|
||||
// because you can run the fetch/build for each arch and then copy/link once)
|
||||
const METACOMMANDS: Record<string, string[]> = {
|
||||
fetchandbuild: ["check", "fetch", "build"],
|
||||
copyandlink: ["copy", "link"],
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"module": "node16",
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"lib": ["es2020"]
|
||||
"lib": ["es2021"]
|
||||
},
|
||||
"include": ["../src/@types", "./**/*.ts"]
|
||||
}
|
||||
|
||||
15
src/@types/global.d.ts
vendored
@@ -7,30 +7,21 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { type BrowserWindow } from "electron";
|
||||
|
||||
import type Store from "electron-store";
|
||||
import type AutoLaunch from "auto-launch";
|
||||
import { type AppLocalization } from "../language-helper.js";
|
||||
|
||||
// global type extensions need to use var for whatever reason
|
||||
/* eslint-disable no-var */
|
||||
declare global {
|
||||
type IConfigOptions = Record<string, any>;
|
||||
|
||||
var mainWindow: BrowserWindow | null;
|
||||
var appQuitting: boolean;
|
||||
var appLocalization: AppLocalization;
|
||||
var launcher: AutoLaunch;
|
||||
var vectorConfig: Record<string, any>;
|
||||
var vectorConfig: IConfigOptions;
|
||||
var trayConfig: {
|
||||
// eslint-disable-next-line camelcase
|
||||
icon_path: string;
|
||||
brand: string;
|
||||
};
|
||||
var store: Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration?: boolean;
|
||||
}>;
|
||||
}
|
||||
/* eslint-enable no-var */
|
||||
|
||||
54
src/@types/keytar.d.ts
vendored
@@ -1,54 +0,0 @@
|
||||
// Based on https://github.com/atom/node-keytar/blob/master/keytar.d.ts because keytar is a hak-dependency and not a normal one
|
||||
// Definitions by: Milan Burda <https://github.com/miniak>, Brendan Forster <https://github.com/shiftkey>, Hari Juturu <https://github.com/juturu>
|
||||
// Adapted from DefinitelyTyped: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/keytar/index.d.ts
|
||||
|
||||
declare module "keytar" {
|
||||
/**
|
||||
* Get the stored password for the service and account.
|
||||
*
|
||||
* @param service The string service name.
|
||||
* @param account The string account name.
|
||||
*
|
||||
* @returns A promise for the password string.
|
||||
*/
|
||||
export function getPassword(service: string, account: string): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Add the password for the service and account to the keychain.
|
||||
*
|
||||
* @param service The string service name.
|
||||
* @param account The string account name.
|
||||
* @param password The string password.
|
||||
*
|
||||
* @returns A promise for the set password completion.
|
||||
*/
|
||||
export function setPassword(service: string, account: string, password: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Delete the stored password for the service and account.
|
||||
*
|
||||
* @param service The string service name.
|
||||
* @param account The string account name.
|
||||
*
|
||||
* @returns A promise for the deletion status. True on success.
|
||||
*/
|
||||
export function deletePassword(service: string, account: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Find a password for the service in the keychain.
|
||||
*
|
||||
* @param service The string service name.
|
||||
*
|
||||
* @returns A promise for the password string.
|
||||
*/
|
||||
export function findPassword(service: string): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Find all accounts and passwords for `service` in the keychain.
|
||||
*
|
||||
* @param service The string service name.
|
||||
*
|
||||
* @returns A promise for the array of found credentials.
|
||||
*/
|
||||
export function findCredentials(service: string): Promise<Array<{ account: string; password: string }>>;
|
||||
}
|
||||
50
src/auto-launch.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import BaseAutoLaunch from "auto-launch";
|
||||
|
||||
import Store from "./store.js";
|
||||
|
||||
export type AutoLaunchState = "enabled" | "minimised" | "disabled";
|
||||
|
||||
// Wrapper around auto-launch to get/set the `isHidden` option
|
||||
export class AutoLaunch extends BaseAutoLaunch {
|
||||
private static internalInstance?: AutoLaunch;
|
||||
|
||||
public static get instance(): AutoLaunch {
|
||||
if (!AutoLaunch.internalInstance) {
|
||||
if (!Store.instance) throw new Error("Store not initialized");
|
||||
AutoLaunch.internalInstance = new AutoLaunch({
|
||||
name: global.vectorConfig.brand || "Element",
|
||||
isHidden: Store.instance.get("openAtLoginMinimised"),
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
return AutoLaunch.internalInstance;
|
||||
}
|
||||
|
||||
public async getState(): Promise<AutoLaunchState> {
|
||||
if (!(await this.isEnabled())) {
|
||||
return "disabled";
|
||||
}
|
||||
return this.opts.isHiddenOnLaunch ? "minimised" : "enabled";
|
||||
}
|
||||
|
||||
public async setState(state: AutoLaunchState): Promise<void> {
|
||||
const openAtLoginMinimised = state === "minimised";
|
||||
Store.instance?.set("openAtLoginMinimised", openAtLoginMinimised);
|
||||
this.opts.isHiddenOnLaunch = openAtLoginMinimised;
|
||||
|
||||
if (state !== "disabled") {
|
||||
return this.enable();
|
||||
} else {
|
||||
return this.disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/badge.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { app, ipcMain, type IpcMainEvent, nativeImage } from "electron";
|
||||
|
||||
import { _t } from "./language-helper.js";
|
||||
|
||||
// Handles calculating the correct "badge" for the window, for notifications and error states.
|
||||
// Tray icon updates are handled in tray.ts
|
||||
|
||||
if (process.platform === "win32") {
|
||||
// We only use setOverlayIcon on Windows as it's only supported on that platform, but has good support
|
||||
// from all the Windows variants we support.
|
||||
// https://www.electronjs.org/docs/latest/api/browser-window#winsetoverlayiconoverlay-description-windows
|
||||
ipcMain.on(
|
||||
"setBadgeCount",
|
||||
function (_ev: IpcMainEvent, count: number, imageBuffer?: Buffer, isError?: boolean): void {
|
||||
if (count === 0) {
|
||||
// Flash frame is set to true in ipc.ts "loudNotification"
|
||||
global.mainWindow?.flashFrame(false);
|
||||
}
|
||||
if (imageBuffer) {
|
||||
global.mainWindow?.setOverlayIcon(
|
||||
nativeImage.createFromBuffer(Buffer.from(imageBuffer)),
|
||||
isError
|
||||
? _t("icon_overlay|description_error")
|
||||
: _t("icon_overlay|description_notifications", { count }),
|
||||
);
|
||||
} else {
|
||||
global.mainWindow?.setOverlayIcon(null, "");
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// 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
|
||||
ipcMain.on("setBadgeCount", function (_ev: IpcMainEvent, count: number): void {
|
||||
if (count === 0) {
|
||||
// Flash frame is set to true in ipc.ts "loudNotification"
|
||||
global.mainWindow?.flashFrame(false);
|
||||
}
|
||||
app.badgeCount = count;
|
||||
});
|
||||
}
|
||||
26
src/build-config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import path, { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { type JsonObject, loadJsonFile } from "./utils.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
interface BuildConfig {
|
||||
appId: string;
|
||||
protocol: string;
|
||||
}
|
||||
|
||||
export function readBuildConfig(): BuildConfig {
|
||||
const packageJson = loadJsonFile(path.join(__dirname, "..", "package.json")) as JsonObject;
|
||||
return {
|
||||
appId: (packageJson["electron_appId"] as string) || "im.riot.app",
|
||||
protocol: (packageJson["electron_protocol"] as string) || "io.element.desktop",
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2018-2024 New Vector Ltd.
|
||||
Copyright 2018-2025 New Vector Ltd.
|
||||
Copyright 2017-2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
@@ -10,31 +10,42 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
// Squirrel on windows starts the app with various flags as hooks to tell us when we've been installed/uninstalled etc.
|
||||
import "./squirrelhooks.js";
|
||||
import { app, BrowserWindow, Menu, autoUpdater, protocol, dialog, type Input, type Event, session } from "electron";
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
autoUpdater,
|
||||
dialog,
|
||||
type Input,
|
||||
type Event,
|
||||
session,
|
||||
protocol,
|
||||
desktopCapturer,
|
||||
} from "electron";
|
||||
// eslint-disable-next-line n/file-extension-in-import
|
||||
import * as Sentry from "@sentry/electron/main";
|
||||
import AutoLaunch from "auto-launch";
|
||||
import path, { dirname } from "node:path";
|
||||
import windowStateKeeper from "electron-window-state";
|
||||
import Store from "electron-store";
|
||||
import fs, { promises as afs } from "node:fs";
|
||||
import { URL, fileURLToPath } from "node:url";
|
||||
import minimist from "minimist";
|
||||
|
||||
import "./ipc.js";
|
||||
import "./keytar.js";
|
||||
import "./seshat.js";
|
||||
import "./settings.js";
|
||||
import "./badge.js";
|
||||
import * as tray from "./tray.js";
|
||||
import Store from "./store.js";
|
||||
import { buildMenuTemplate } from "./vectormenu.js";
|
||||
import webContentsHandler from "./webcontents-handler.js";
|
||||
import * as updater from "./updater.js";
|
||||
import { getProfileFromDeeplink, protocolInit } from "./protocol.js";
|
||||
import ProtocolHandler from "./protocol.js";
|
||||
import { _t, AppLocalization } from "./language-helper.js";
|
||||
import { setDisplayMediaCallback } from "./displayMediaCallback.js";
|
||||
import { setupMacosTitleBar } from "./macos-titlebar.js";
|
||||
import { loadJsonFile } from "./utils.js";
|
||||
import { type Json, loadJsonFile } from "./utils.js";
|
||||
import { setupMediaAuth } from "./media-auth.js";
|
||||
import { readBuildConfig } from "./build-config.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -45,7 +56,12 @@ const argv = minimist(process.argv, {
|
||||
if (argv["help"]) {
|
||||
console.log("Options:");
|
||||
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
||||
console.log(" --profile {name}: Name of alternate profile to use, allows for running multiple accounts.");
|
||||
console.log(
|
||||
` --profile {name}: Name of alternate profile to use, allows for running multiple accounts.\n` +
|
||||
` Ignored if --profile-dir is specified.\n` +
|
||||
` The ELEMENT_PROFILE_DIR environment variable may be used to change the default profile path.\n` +
|
||||
` It is overridden by --profile-dir, but can be combined with --profile.`,
|
||||
);
|
||||
console.log(" --devtools: Install and use react-devtools and react-perf.");
|
||||
console.log(
|
||||
` --config: Path to the config.json file. May also be specified via the ELEMENT_DESKTOP_CONFIG_JSON environment variable.\n` +
|
||||
@@ -59,6 +75,7 @@ if (argv["help"]) {
|
||||
}
|
||||
|
||||
const LocalConfigLocation = process.env.ELEMENT_DESKTOP_CONFIG_JSON ?? argv["config"];
|
||||
const LocalConfigFilename = "config.json";
|
||||
|
||||
// Electron creates the user data directory (with just an empty 'Dictionaries' directory...)
|
||||
// as soon as the app path is set, so pick a random path in it that must exist if it's a
|
||||
@@ -67,16 +84,19 @@ function isRealUserDataDir(d: string): boolean {
|
||||
return fs.existsSync(path.join(d, "IndexedDB"));
|
||||
}
|
||||
|
||||
const buildConfig = readBuildConfig();
|
||||
const protocolHandler = new ProtocolHandler(buildConfig.protocol);
|
||||
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
let userDataPath: string;
|
||||
|
||||
const userDataPathInProtocol = getProfileFromDeeplink(argv["_"]);
|
||||
const userDataPathInProtocol = protocolHandler.getProfileFromDeeplink(argv["_"]);
|
||||
if (userDataPathInProtocol) {
|
||||
userDataPath = userDataPathInProtocol;
|
||||
} else if (argv["profile-dir"]) {
|
||||
userDataPath = argv["profile-dir"];
|
||||
} else {
|
||||
let newUserDataPath = app.getPath("userData");
|
||||
let newUserDataPath = process.env.ELEMENT_PROFILE_DIR ?? app.getPath("userData");
|
||||
if (argv["profile"]) {
|
||||
newUserDataPath += "-" + argv["profile"];
|
||||
}
|
||||
@@ -137,60 +157,86 @@ function getAsarPath(): Promise<string> {
|
||||
return asarPathPromise;
|
||||
}
|
||||
|
||||
// Loads the config from asar, and applies a config.json from userData atop if one exists
|
||||
// Writes config to `global.vectorConfig`. Does nothing if `global.vectorConfig` is already set.
|
||||
async function loadConfig(): Promise<void> {
|
||||
if (global.vectorConfig) return;
|
||||
|
||||
const asarPath = await getAsarPath();
|
||||
|
||||
try {
|
||||
global.vectorConfig = loadJsonFile(asarPath, "config.json");
|
||||
} catch {
|
||||
// it would be nice to check the error code here and bail if the config
|
||||
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||
// file or invalid json, so node is just very unhelpful.
|
||||
// Continue with the defaults (ie. an empty config)
|
||||
global.vectorConfig = {};
|
||||
function loadLocalConfigFile(): Json {
|
||||
if (LocalConfigLocation) {
|
||||
console.log("Loading local config: " + LocalConfigLocation);
|
||||
return loadJsonFile(LocalConfigLocation);
|
||||
} else {
|
||||
const configDir = app.getPath("userData");
|
||||
console.log(`Loading local config: ${path.join(configDir, LocalConfigFilename)}`);
|
||||
return loadJsonFile(configDir, LocalConfigFilename);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Load local config and use it to override values from the one baked with the build
|
||||
const localConfig = LocalConfigLocation
|
||||
? loadJsonFile(LocalConfigLocation)
|
||||
: loadJsonFile(app.getPath("userData"), "config.json");
|
||||
let loadConfigPromise: Promise<void> | undefined;
|
||||
// Loads the config from asar, and applies a config.json from userData atop if one exists
|
||||
// Writes config to `global.vectorConfig`. Idempotent, returns the same promise on subsequent calls.
|
||||
function loadConfig(): Promise<void> {
|
||||
if (loadConfigPromise) return loadConfigPromise;
|
||||
|
||||
// If the local config has a homeserver defined, don't use the homeserver from the build
|
||||
// config. This is to avoid a problem where Riot thinks there are multiple homeservers
|
||||
// defined, and panics as a result.
|
||||
if (Object.keys(localConfig).find((k) => homeserverProps.includes(<any>k))) {
|
||||
// Rip out all the homeserver options from the vector config
|
||||
global.vectorConfig = Object.keys(global.vectorConfig)
|
||||
.filter((k) => !homeserverProps.includes(<any>k))
|
||||
.reduce(
|
||||
(obj, key) => {
|
||||
obj[key] = global.vectorConfig[key];
|
||||
return obj;
|
||||
},
|
||||
{} as Omit<Partial<(typeof global)["vectorConfig"]>, keyof typeof homeserverProps>,
|
||||
);
|
||||
async function actuallyLoadConfig(): Promise<void> {
|
||||
const asarPath = await getAsarPath();
|
||||
|
||||
try {
|
||||
console.log(`Loading app config: ${path.join(asarPath, LocalConfigFilename)}`);
|
||||
global.vectorConfig = loadJsonFile(asarPath, LocalConfigFilename);
|
||||
} catch {
|
||||
// it would be nice to check the error code here and bail if the config
|
||||
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||
// file or invalid json, so node is just very unhelpful.
|
||||
// Continue with the defaults (ie. an empty config)
|
||||
global.vectorConfig = {};
|
||||
}
|
||||
|
||||
global.vectorConfig = Object.assign(global.vectorConfig, localConfig);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
void dialog.showMessageBox({
|
||||
type: "error",
|
||||
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 || "",
|
||||
try {
|
||||
// Load local config and use it to override values from the one baked with the build
|
||||
const localConfig = loadLocalConfigFile();
|
||||
|
||||
// If the local config has a homeserver defined, don't use the homeserver from the build
|
||||
// config. This is to avoid a problem where Riot thinks there are multiple homeservers
|
||||
// defined, and panics as a result.
|
||||
if (Object.keys(localConfig).find((k) => homeserverProps.includes(<any>k))) {
|
||||
// Rip out all the homeserver options from the vector config
|
||||
global.vectorConfig = Object.keys(global.vectorConfig)
|
||||
.filter((k) => !homeserverProps.includes(<any>k))
|
||||
.reduce(
|
||||
(obj, key) => {
|
||||
obj[key] = global.vectorConfig[key];
|
||||
return obj;
|
||||
},
|
||||
{} as Omit<Partial<(typeof global)["vectorConfig"]>, keyof typeof homeserverProps>,
|
||||
);
|
||||
}
|
||||
|
||||
global.vectorConfig = Object.assign(global.vectorConfig, localConfig);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
await app.whenReady();
|
||||
void dialog.showMessageBox({
|
||||
type: "error",
|
||||
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 || "",
|
||||
});
|
||||
}
|
||||
|
||||
// Could not load local config, this is expected in most cases.
|
||||
}
|
||||
|
||||
// Tweak modules paths as they assume the root is at the same level as webapp, but for `vector://vector/webapp` it is not.
|
||||
if (Array.isArray(global.vectorConfig.modules)) {
|
||||
global.vectorConfig.modules = global.vectorConfig.modules.map((m) => {
|
||||
if (m.startsWith("/")) {
|
||||
return "/webapp" + m;
|
||||
}
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
||||
// Could not load local config, this is expected in most cases.
|
||||
}
|
||||
loadConfigPromise = actuallyLoadConfig();
|
||||
return loadConfigPromise;
|
||||
}
|
||||
|
||||
// Configure Electron Sentry and crashReporter using sentry.dsn in config.json if one is present.
|
||||
@@ -208,58 +254,19 @@ async function configureSentry(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Set up globals for Tray and AutoLaunch
|
||||
// Set up globals for Tray
|
||||
async function setupGlobals(): Promise<void> {
|
||||
const asarPath = await getAsarPath();
|
||||
await loadConfig();
|
||||
|
||||
// we assume the resources path is in the same place as the asar
|
||||
const resPath = await tryPaths("res", path.dirname(asarPath), [
|
||||
// If run from the source checkout
|
||||
"res",
|
||||
// if run from packaged application
|
||||
"",
|
||||
]);
|
||||
|
||||
// The tray icon
|
||||
// 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"}`;
|
||||
// Figure out the tray icon path & brand name
|
||||
const iconFile = `icon.${process.platform === "win32" ? "ico" : "png"}`;
|
||||
global.trayConfig = {
|
||||
icon_path: path.join(resPath, "img", iconFile),
|
||||
icon_path: path.join(path.dirname(asarPath), "build", iconFile),
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
};
|
||||
|
||||
// launcher
|
||||
global.launcher = new AutoLaunch({
|
||||
name: global.vectorConfig.brand || "Element",
|
||||
isHidden: true,
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Look for an auto-launcher under 'Riot' and if we find one,
|
||||
// port its enabled/disabled-ness over to the new 'Element' launcher
|
||||
async function moveAutoLauncher(): Promise<void> {
|
||||
if (!global.vectorConfig.brand || global.vectorConfig.brand === "Element") {
|
||||
const oldLauncher = new AutoLaunch({
|
||||
name: "Riot",
|
||||
isHidden: true,
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
},
|
||||
});
|
||||
const wasEnabled = await oldLauncher.isEnabled();
|
||||
if (wasEnabled) {
|
||||
await oldLauncher.disable();
|
||||
await global.launcher.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.store = new Store({ name: "electron-config" });
|
||||
|
||||
global.appQuitting = false;
|
||||
|
||||
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
@@ -269,32 +276,6 @@ const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
platform === "darwin" && input.meta && !input.control && input.key.toUpperCase() === "Q",
|
||||
];
|
||||
|
||||
const warnBeforeExit = (event: Event, input: Input): void => {
|
||||
const shouldWarnBeforeExit = global.store.get("warnBeforeExit", true);
|
||||
const exitShortcutPressed =
|
||||
input.type === "keyDown" && exitShortcuts.some((shortcutFn) => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed && global.mainWindow) {
|
||||
const shouldCancelCloseRequest =
|
||||
dialog.showMessageBoxSync(global.mainWindow, {
|
||||
type: "question",
|
||||
buttons: [
|
||||
_t("action|cancel"),
|
||||
_t("action|close_brand", {
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
}),
|
||||
],
|
||||
message: _t("confirm_quit"),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
}) === 0;
|
||||
|
||||
if (shouldCancelCloseRequest) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void configureSentry();
|
||||
|
||||
// handle uncaught errors otherwise it displays
|
||||
@@ -319,7 +300,7 @@ if (!gotLock) {
|
||||
}
|
||||
|
||||
// do this after we know we are the primary instance of the app
|
||||
protocolInit();
|
||||
protocolHandler.initialise(userDataPath);
|
||||
|
||||
// Register the scheme the app is served from as 'standard'
|
||||
// which allows things like relative URLs and IndexedDB to
|
||||
@@ -351,19 +332,22 @@ app.enableSandbox();
|
||||
// We disable media controls here. We do this because calls use audio and video elements and they sometimes capture the media keys. See https://github.com/vector-im/element-web/issues/15704
|
||||
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling,MediaSessionService");
|
||||
|
||||
const store = Store.initialize(argv["storage-mode"]); // must be called before any async actions
|
||||
|
||||
// Disable hardware acceleration if the setting has been set.
|
||||
if (global.store.get("disableHardwareAcceleration", false) === true) {
|
||||
if (store.get("disableHardwareAcceleration")) {
|
||||
console.log("Disabling hardware acceleration.");
|
||||
app.disableHardwareAcceleration();
|
||||
}
|
||||
|
||||
app.on("ready", async () => {
|
||||
console.debug("Reached Electron ready state");
|
||||
|
||||
let asarPath: string;
|
||||
|
||||
try {
|
||||
asarPath = await getAsarPath();
|
||||
await setupGlobals();
|
||||
await moveAutoLauncher();
|
||||
} catch (e) {
|
||||
console.log("App setup failed: exiting", e);
|
||||
process.exit(1);
|
||||
@@ -437,21 +421,28 @@ app.on("ready", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (argv["no-update"]) {
|
||||
console.log('Auto update disabled via command line flag "--no-update"');
|
||||
// Minimist parses `--no-`-prefixed arguments as booleans with value `false` rather than verbatim.
|
||||
if (argv["update"] === false) {
|
||||
console.log("Auto update disabled via command line flag");
|
||||
} else if (global.vectorConfig["update_base_url"]) {
|
||||
console.log(`Starting auto update with base URL: ${global.vectorConfig["update_base_url"]}`);
|
||||
void updater.start(global.vectorConfig["update_base_url"]);
|
||||
} else {
|
||||
console.log("No update_base_url is defined: auto update is disabled");
|
||||
}
|
||||
|
||||
// Set up i18n before loading storage as we need translations for dialogs
|
||||
global.appLocalization = new AppLocalization({
|
||||
components: [(): void => tray.initApplicationMenu(), (): void => Menu.setApplicationMenu(buildMenuTemplate())],
|
||||
store,
|
||||
});
|
||||
|
||||
// Load the previous window state with fallback to defaults
|
||||
const mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 1024,
|
||||
defaultHeight: 768,
|
||||
});
|
||||
|
||||
console.debug("Opening main window");
|
||||
const preloadScript = path.normalize(`${__dirname}/preload.cjs`);
|
||||
global.mainWindow = new BrowserWindow({
|
||||
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
@@ -462,7 +453,7 @@ app.on("ready", async () => {
|
||||
|
||||
icon: global.trayConfig.icon_path,
|
||||
show: false,
|
||||
autoHideMenuBar: global.store.get("autoHideMenuBar", true),
|
||||
autoHideMenuBar: store.get("autoHideMenuBar"),
|
||||
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
@@ -476,6 +467,17 @@ app.on("ready", async () => {
|
||||
webgl: true,
|
||||
},
|
||||
});
|
||||
|
||||
global.mainWindow.setContentProtection(store.get("enableContentProtection"));
|
||||
|
||||
try {
|
||||
console.debug("Ensuring storage is ready");
|
||||
if (!(await store.prepareSafeStorage(global.mainWindow.webContents.session))) return;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
app.exit(1);
|
||||
}
|
||||
|
||||
void global.mainWindow.loadURL("vector://vector/webapp/");
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
@@ -484,10 +486,10 @@ app.on("ready", async () => {
|
||||
|
||||
// Handle spellchecker
|
||||
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true));
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(store.get("spellCheckerEnabled", true));
|
||||
|
||||
// Create trayIcon icon
|
||||
if (global.store.get("minimizeToTray", true)) tray.create(global.trayConfig);
|
||||
if (store.get("minimizeToTray")) tray.create(global.trayConfig);
|
||||
|
||||
global.mainWindow.once("ready-to-show", () => {
|
||||
if (!global.mainWindow) return;
|
||||
@@ -501,7 +503,31 @@ app.on("ready", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
global.mainWindow.webContents.on("before-input-event", warnBeforeExit);
|
||||
global.mainWindow.webContents.on("before-input-event", (event: Event, input: Input): void => {
|
||||
const shouldWarnBeforeExit = store.get("warnBeforeExit", true);
|
||||
const exitShortcutPressed =
|
||||
input.type === "keyDown" && exitShortcuts.some((shortcutFn) => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed && global.mainWindow) {
|
||||
const shouldCancelCloseRequest =
|
||||
dialog.showMessageBoxSync(global.mainWindow, {
|
||||
type: "question",
|
||||
buttons: [
|
||||
_t("action|cancel"),
|
||||
_t("action|close_brand", {
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
}),
|
||||
],
|
||||
message: _t("confirm_quit"),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
}) === 0;
|
||||
|
||||
if (shouldCancelCloseRequest) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
global.mainWindow.on("closed", () => {
|
||||
global.mainWindow = null;
|
||||
@@ -539,15 +565,28 @@ app.on("ready", async () => {
|
||||
|
||||
webContentsHandler(global.mainWindow.webContents);
|
||||
|
||||
global.appLocalization = new AppLocalization({
|
||||
store: global.store,
|
||||
components: [(): void => tray.initApplicationMenu(), (): void => Menu.setApplicationMenu(buildMenuTemplate())],
|
||||
});
|
||||
|
||||
session.defaultSession.setDisplayMediaRequestHandler((_, callback) => {
|
||||
global.mainWindow?.webContents.send("openDesktopCapturerSourcePicker");
|
||||
setDisplayMediaCallback(callback);
|
||||
});
|
||||
session.defaultSession.setDisplayMediaRequestHandler(
|
||||
(_, callback) => {
|
||||
if (process.env.XDG_SESSION_TYPE === "wayland") {
|
||||
// On Wayland, calling getSources() opens the xdg-desktop-portal picker.
|
||||
// The user can only select a single source there, so Electron will return an array with exactly one entry.
|
||||
desktopCapturer
|
||||
.getSources({ types: ["screen", "window"] })
|
||||
.then((sources) => {
|
||||
callback({ video: sources[0] });
|
||||
})
|
||||
.catch((err) => {
|
||||
// If the user cancels the dialog an error occurs "Failed to get sources"
|
||||
console.error("Wayland: failed to get user-selected source:", err);
|
||||
callback({ video: { id: "", name: "" } }); // The promise does not return if no dummy is passed here as source
|
||||
});
|
||||
} else {
|
||||
global.mainWindow?.webContents.send("openDesktopCapturerSourcePicker");
|
||||
}
|
||||
setDisplayMediaCallback(callback);
|
||||
},
|
||||
{ useSystemPicker: true },
|
||||
); // Use Mac OS 15+ native picker
|
||||
|
||||
setupMediaAuth(global.mainWindow);
|
||||
});
|
||||
@@ -580,8 +619,9 @@ app.on("second-instance", (ev, commandLine, workingDirectory) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Set the App User Model ID to match what the squirrel
|
||||
// installer uses for the shortcut icon.
|
||||
// This makes notifications work on windows 8.1 (and is
|
||||
// a noop on other platforms).
|
||||
app.setAppUserModelId("com.squirrel.element-desktop.Element");
|
||||
// This is required to make notification handlers work
|
||||
// on Windows 8.1/10/11 (and is a noop on other platforms);
|
||||
// It must also match the ID found in 'electron-builder'
|
||||
// in order to get the title and icon to show up correctly.
|
||||
// Ref: https://stackoverflow.com/a/77314604/3525780
|
||||
app.setAppUserModelId(buildConfig.appId);
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "O",
|
||||
"brand_help": "%(brand)s nápověda",
|
||||
"help": "Nápověda",
|
||||
"preferences": "Předvolby"
|
||||
"no": "Ne",
|
||||
"preferences": "Předvolby",
|
||||
"yes": "Ano"
|
||||
},
|
||||
"confirm_quit": "Opravdu chcete ukončit aplikaci?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,22 @@
|
||||
"speech_start_speaking": "Spustit nahrávání hlasu",
|
||||
"speech_stop_speaking": "Zastavit nahrávání hlasu"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Používáte nepodporovanou verzi systému macOS. Prosím upgradujte %(brand)s pro získání aktualizací.",
|
||||
"title": "Systém není podporován",
|
||||
"warning": "Používáte nepodporovanou verzi systému macOS. Proveďte prosím upgrade %(brand)s , aby byl stále funkční."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Soubor"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Chyba",
|
||||
"description_notifications": {
|
||||
"one": "Máte %(count)s nepřečtené oznámení.",
|
||||
"few": "Máte %(count)s nepřečtená oznámení.",
|
||||
"other": "Máte %(count)s nepřečtených oznámení."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Skrýt",
|
||||
"hide_others": "Skrýt ostatní",
|
||||
@@ -49,6 +64,21 @@
|
||||
"save_image_as_error_description": "Obrázek se nepodařilo uložit",
|
||||
"save_image_as_error_title": "Chyba při ukládání obrázku"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Vymazat data a znovu načíst?",
|
||||
"backend_changed_detail": "Nelze získat přístup k tajnému klíči ze systémové klíčenky, zdá se, že se změnil.",
|
||||
"backend_changed_title": "Nepodařilo se načíst databázi",
|
||||
"backend_no_encryption": "Váš systém má podporovanou klíčenku, ale šifrování není k dispozici.",
|
||||
"backend_no_encryption_detail": "Electron zjistil, že pro vaši klíčenku %(backend)s není k dispozici šifrování. Ujistěte se, že máte nainstalovanou klíčenku. Pokud ji máte, restartujte počítač a zkuste to znovu. Volitelně můžete povolit %(brand)s použít slabší formu šifrování.",
|
||||
"backend_no_encryption_title": "Bez podpory šifrování",
|
||||
"unsupported_keyring": "Váš systém má nepodporovanou klíčenku, což znamená, že databázi nelze otevřít.",
|
||||
"unsupported_keyring_detail": "Detekce klíčenky Electronu nenalezla podporovaný backend. Můžete se pokusit ručně nakonfigurovat backend spuštěním %(brand)s s argumentem příkazového řádku, jednorázovou operací. Viz %(link)s.",
|
||||
"unsupported_keyring_title": "Systém není podporován",
|
||||
"unsupported_keyring_use_basic_text": "Používat slabší šifrování",
|
||||
"unsupported_keyring_use_plaintext": "Nepoužívat žádné šifrování"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Aktuální velikost",
|
||||
"toggle_developer_tools": "Přepnout zobrazení nástrojů pro vývojáře",
|
||||
|
||||
93
src/i18n/strings/cy.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"action": {
|
||||
"cancel": "Diddymu",
|
||||
"close": "Cau",
|
||||
"close_brand": "Cau %(brand)s",
|
||||
"copy": "Copïo",
|
||||
"cut": "Torri",
|
||||
"delete": "Dileu",
|
||||
"edit": "Golygu",
|
||||
"minimise": "Lleihau",
|
||||
"paste": "Gludo",
|
||||
"paste_match_style": "Arddull Gludo a Chyfateb",
|
||||
"quit": "Gadael",
|
||||
"redo": "Ail-wneud",
|
||||
"select_all": "Dewis y Cyfan",
|
||||
"show_hide": "Dangos/Cuddio",
|
||||
"undo": "Dadwneud",
|
||||
"zoom_in": "Chwyddo i Mewn",
|
||||
"zoom_out": "Chwyddo Allan"
|
||||
},
|
||||
"common": {
|
||||
"about": "Ynghylch",
|
||||
"brand_help": "Cymorth %(brand)s",
|
||||
"help": "Cymorth",
|
||||
"no": "Na",
|
||||
"preferences": "Dewisiadau",
|
||||
"yes": "Iawn"
|
||||
},
|
||||
"confirm_quit": "Ydych chi'n siŵr eich bod am roi'r gorau iddi?",
|
||||
"edit_menu": {
|
||||
"speech": "Lleferydd",
|
||||
"speech_start_speaking": "Cychwyn Llefaru",
|
||||
"speech_stop_speaking": "Peidio Llefaru"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Rydych chi'n rhedeg fersiwn o macOS nad yw'n cael ei chefnogi. Uwchraddiwch i dderbyn diweddariadau %(brand)s.",
|
||||
"title": "System heb ei chefnogi",
|
||||
"warning": "Rydych chi'n rhedeg fersiwn o macOS nad yw'n cael ei chefnogi. Uwchraddiwch i sicrhau bod %(brand)s yn parhau i weithio."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Ffeil"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Gwall",
|
||||
"description_notifications": {
|
||||
"Mae gennych chi %(count)s hysbysiadau heb eu darllen.": "zero",
|
||||
"Mae gennych chi %(count)s hysbysiad heb ei ddarllen.": "one",
|
||||
"Mae gennych chi %(count)s hysbysiad heb eu darllen.": "other"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Cuddio",
|
||||
"hide_others": "Cuddio'r Gweddill",
|
||||
"services": "Gwasanaethau",
|
||||
"unhide": "Datguddio"
|
||||
},
|
||||
"right_click_menu": {
|
||||
"add_to_dictionary": "Ychwanegu at y geiriadur",
|
||||
"copy_email": "Copïo cyfeiriad e-bost",
|
||||
"copy_image": "Copïo delwedd",
|
||||
"copy_image_url": "Copïo cyfeiriad delwedd",
|
||||
"copy_link_url": "Copïo cyfeiriad y ddolen",
|
||||
"save_image_as": "Cadw delwedd fel...",
|
||||
"save_image_as_error_description": "Methodd cadw'r ddelwedd",
|
||||
"save_image_as_error_title": "Methodd cadw'r ddelwedd"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Clirio data ac ail-lwytho?",
|
||||
"backend_changed_detail": "Methu cael mynediad at y gyfrinach o allweddi'r system, mae'n ymddangos ei fod wedi newid.",
|
||||
"backend_changed_title": "Methwyd llwytho'r gronfa ddata",
|
||||
"backend_no_encryption": "Mae gan eich system cylch allwedd sy'n cael ei gefnogi ond nid yw amgryptio ar gael.",
|
||||
"backend_no_encryption_detail": "Mae Electron wedi canfod nad yw amgryptio ar gael ar eich cylch allweddi %(backend)s. Gwnewch yn siŵr bod y cylch allweddi wedi'i osod. Os oes y cylch allweddi wedi'i osod, ail gychwynnwch a cheisiwch eto. Yn ddewisol, gallwch ganiatáu i %(brand)s ddefnyddio ffurf wannach o amgryptio.",
|
||||
"backend_no_encryption_title": "Dim cefnogaeth amgryptio",
|
||||
"unsupported_keyring": "Mae gan eich system allweddell nad yw'n cael ei chefnogi sy'n golygu nad oes modd agor y gronfa ddata.",
|
||||
"unsupported_keyring_detail": "Heb ganfod allweddell Electron gefn. Gallwch geisio ffurfweddu'r gefn â llaw trwy gychwyn %(brand)s gyda dadl llinell orchymyn, gweithrediad untro. Gweler %(link)s.",
|
||||
"unsupported_keyring_title": "System heb ei chefnogi",
|
||||
"unsupported_keyring_use_basic_text": "Defnyddiwch amgryptio gwannach",
|
||||
"unsupported_keyring_use_plaintext": "Peidiwch â defnyddio amgryptio"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Maint Gwirioneddol",
|
||||
"toggle_developer_tools": "Toglo Offer Datblygwyr",
|
||||
"toggle_full_screen": "Toglo Sgrin Lawn",
|
||||
"view": "Golwg"
|
||||
},
|
||||
"window_menu": {
|
||||
"bring_all_to_front": "Popeth i'r Blaen",
|
||||
"label": "Ffenestr",
|
||||
"zoom": "Chwyddo"
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Über",
|
||||
"brand_help": "%(brand)s Hilfe",
|
||||
"help": "Hilfe",
|
||||
"preferences": "Präferenzen"
|
||||
"no": "Nein",
|
||||
"preferences": "Präferenzen",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"confirm_quit": "Wirklich beenden?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,21 @@
|
||||
"speech_start_speaking": "Aufnahme starten",
|
||||
"speech_stop_speaking": "Aufnahme beenden"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Du benutzt eine nicht unterstützte Version von macOS. Bitte aktualisiere, um Updates für %(brand)s zu erhalten.",
|
||||
"title": "System nicht unterstützt",
|
||||
"warning": "Du benutzt eine nicht unterstützte Version von macOS. Bitte aktualisiere, damit %(brand)s weiter funktioniert."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Datei"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Fehler",
|
||||
"description_notifications": {
|
||||
"one": "Du hast %(count)s ungelesene Benachrichtigung.",
|
||||
"other": "Du hast %(count)s ungelesene Benachrichtigungen."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Verstecken",
|
||||
"hide_others": "Andere verstecken",
|
||||
@@ -49,6 +63,21 @@
|
||||
"save_image_as_error_description": "Das Bild konnte nicht gespeichert werden",
|
||||
"save_image_as_error_title": "Bild kann nicht gespeichert werden"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Daten löschen und neu laden?",
|
||||
"backend_changed_detail": "Zugriff auf Schlüssel im Systemschlüsselbund nicht möglich, er scheint sich geändert zu haben.",
|
||||
"backend_changed_title": "Datenbank konnte nicht geladen werden",
|
||||
"backend_no_encryption": "Ihr System verfügt über einen unterstützten Keyring, aber die Verschlüsselung ist nicht verfügbar.",
|
||||
"backend_no_encryption_detail": "Electron hat festgestellt, dass der Keyring von %(backend)s keine Verschlüsselung bietet. Wenn du den Keyring installiert hast, starte den Rechner bitte neu und versuche es erneut. Optional kann %(brand)s auch eine abgeschwächte Verschlüsselung nutzen.",
|
||||
"backend_no_encryption_title": "Keine Verschlüsselungsunterstützung",
|
||||
"unsupported_keyring": "Der Keyring des Systems wird nicht unterstützt. Daher kann die Datenbank nicht geöffnet werden kann.",
|
||||
"unsupported_keyring_detail": "Die Keyring-Erkennung von Electron hat kein unterstütztes Backend gefunden. Du kannst einmalig versuchen, eine manuelle Konfiguration von %(brand)s über die Kommandozeile vorzunehmen. Infos unter %(link)s.",
|
||||
"unsupported_keyring_title": "System nicht unterstützt",
|
||||
"unsupported_keyring_use_basic_text": "Schwächere Verschlüsselung verwenden",
|
||||
"unsupported_keyring_use_plaintext": "Verwende keine Verschlüsselung"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Tatsächliche Größe",
|
||||
"toggle_developer_tools": "Developer-Tools an/aus",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "About",
|
||||
"brand_help": "%(brand)s Help",
|
||||
"help": "Help",
|
||||
"preferences": "Preferences"
|
||||
"no": "No",
|
||||
"preferences": "Preferences",
|
||||
"yes": "Yes"
|
||||
},
|
||||
"confirm_quit": "Are you sure you want to quit?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,21 @@
|
||||
"speech_start_speaking": "Start Speaking",
|
||||
"speech_stop_speaking": "Stop Speaking"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "You are running an unsupported version of macOS. Please upgrade to receive %(brand)s updates.",
|
||||
"title": "System unsupported",
|
||||
"warning": "You are running an unsupported version of macOS. Please upgrade to ensure %(brand)s keeps working."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "File"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Error",
|
||||
"description_notifications": {
|
||||
"one": "You have %(count)s unread notification.",
|
||||
"other": "You have %(count)s unread notifications."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Hide",
|
||||
"hide_others": "Hide Others",
|
||||
@@ -49,6 +63,21 @@
|
||||
"save_image_as_error_description": "The image failed to save",
|
||||
"save_image_as_error_title": "Failed to save image"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Clear data and reload?",
|
||||
"backend_changed_detail": "Unable to access secret from system keyring, it appears to have changed.",
|
||||
"backend_changed_title": "Failed to load database",
|
||||
"backend_no_encryption": "Your system has a supported keyring but encryption is not available.",
|
||||
"backend_no_encryption_detail": "Electron has detected that encryption is not available on your keyring %(backend)s. Please ensure that you have the keyring installed. If you do have the keyring installed, please reboot and try again. Optionally, you can allow %(brand)s to use a weaker form of encryption.",
|
||||
"backend_no_encryption_title": "No encryption support",
|
||||
"unsupported_keyring": "Your system has an unsupported keyring meaning the database cannot be opened.",
|
||||
"unsupported_keyring_detail": "Electron's keyring detection did not find a supported backend. You can attempt to manually configure the backend by starting %(brand)s with a command-line argument, a one-time operation. See %(link)s.",
|
||||
"unsupported_keyring_title": "System unsupported",
|
||||
"unsupported_keyring_use_basic_text": "Use weaker encryption",
|
||||
"unsupported_keyring_use_plaintext": "Use no encryption"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Actual Size",
|
||||
"toggle_developer_tools": "Toggle Developer Tools",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Rakenduse teave",
|
||||
"brand_help": "%(brand)s abiteave",
|
||||
"help": "Abiteave",
|
||||
"preferences": "Eelistused"
|
||||
"no": "Ei",
|
||||
"preferences": "Eelistused",
|
||||
"yes": "Jah"
|
||||
},
|
||||
"confirm_quit": "Kas sa kindlasti soovid rakendusest väljuda?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,21 @@
|
||||
"speech_start_speaking": "Alusta rääkimist",
|
||||
"speech_stop_speaking": "Lõpeta rääkimine"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Sa kasutad macOS-i toetamata versiooni. %(brand)s rakenduse tulevaste versioonide kasutamiseks palun uuenda operatsioonisüsteemi.",
|
||||
"title": "Süsteem pole toetatud",
|
||||
"warning": "Sa kasutad macOS-i toetamata versiooni. Et %(brand)s toimiks ka edaspidi, palun uuenda operatsioonisüsteemi."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Fail"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Viga",
|
||||
"description_notifications": {
|
||||
"one": "Sul on %(count)s lugemata teavitus",
|
||||
"other": "Sul on %(count)s lugemata teavitust"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Peida",
|
||||
"hide_others": "Peida muud",
|
||||
@@ -49,6 +63,21 @@
|
||||
"save_image_as_error_description": "Seda pilti ei õnnestunud salvestada",
|
||||
"save_image_as_error_title": "Pildi salvestamine ei õnnestunud"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Kas kustutame andmed ja laadime uuesti?",
|
||||
"backend_changed_detail": "Süsteemsest võtmerõngast ei õnnestu laadida vajalikku saladust, tundub et ta on muutunud.",
|
||||
"backend_changed_title": "Andmebaasi ei õnnestunud laadida",
|
||||
"backend_no_encryption": "Sinu süsteem kasutab toetatud võtmerõngast, kuid krüptimist pole saadaval.",
|
||||
"backend_no_encryption_detail": "Electron on tuvastanud, et krüptimine pole sinu %(backend)s võtmerõnga jaoks saadaval. Palun kontrolli, et võtmerõngas oleks korrektselt paigaldatud. Kui sul on võtmerõngas paigaldatud, siis palun taaskäivita ta ja proovi uuesti. Lisavõimalusena saad lubada, et %(brand)s kasutab nõrgemat krüptimislahendust.",
|
||||
"backend_no_encryption_title": "Krüptimise tugi puudub",
|
||||
"unsupported_keyring": "Sinu süsteemis on kasutusel mittetoetatud võtmerõnga versioon ning see tähendab, et andmebaasi ei saa avada.",
|
||||
"unsupported_keyring_detail": "Electroni võtmerõnga tuvastamine ei leidnud toetatud taustateenust. Kui käivitad rakenduse %(brand)s käsurealt õigete argumentidega, siis võib taustateenuse käsitsi seadistamine õnnestuda ning seda tegevust peaksid vaid üks kord tegema. Lisateave: %(link)s.",
|
||||
"unsupported_keyring_title": "Süsteem pole toetatud",
|
||||
"unsupported_keyring_use_basic_text": "Kasuta nõrgemat krüptimist",
|
||||
"unsupported_keyring_use_plaintext": "Ära üldse kasuta krüptimist"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Näita tavasuuruses",
|
||||
"toggle_developer_tools": "Arendaja töövahendid sisse/välja",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Tietoa",
|
||||
"brand_help": "%(brand)s-tuki",
|
||||
"help": "Ohje",
|
||||
"preferences": "Valinnat"
|
||||
"no": "Ei",
|
||||
"preferences": "Valinnat",
|
||||
"yes": "Kyllä"
|
||||
},
|
||||
"confirm_quit": "Haluatko varmasti poistua?",
|
||||
"edit_menu": {
|
||||
@@ -49,6 +51,12 @@
|
||||
"save_image_as_error_description": "Kuvan tallennus epäonnistui",
|
||||
"save_image_as_error_title": "Kuvan tallennus epäonnistui"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed_title": "Tietokannan lataaminen epäonnistui",
|
||||
"unsupported_keyring_title": "Järjestelmä ei ole tuettu"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Alkuperäinen koko",
|
||||
"toggle_developer_tools": "Näytä tai piilota kehittäjätyökalut",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "À propos",
|
||||
"brand_help": "Aide de %(brand)s",
|
||||
"help": "Aide",
|
||||
"preferences": "Préférences"
|
||||
"no": "Non",
|
||||
"preferences": "Préférences",
|
||||
"yes": "Oui"
|
||||
},
|
||||
"confirm_quit": "Êtes-vous sûr de vouloir quitter ?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,21 @@
|
||||
"speech_start_speaking": "Commencer la dictée",
|
||||
"speech_stop_speaking": "Arrêter la dictée"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Vous utilisez une version de macOS non prise en charge. Veuillez mettre à jour macOS pour recevoir les mises à jour de %(brand)s.",
|
||||
"title": "Système non pris en charge",
|
||||
"warning": "Vous utilisez une version de macOS non prise en charge. Veuillez mettre à jour macOS afin que %(brand)s continue de fonctionner."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Fichier"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Erreur",
|
||||
"description_notifications": {
|
||||
"one": "Vous avez %(count)s notification non lue.",
|
||||
"other": "Vous avez %(count)s notifications non lues."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Masquer",
|
||||
"hide_others": "Masquer les autres",
|
||||
@@ -46,9 +60,24 @@
|
||||
"copy_image_url": "Copier l'adresse de l'image",
|
||||
"copy_link_url": "Copier l’adresse du lien",
|
||||
"save_image_as": "Enregistrer l’image sous…",
|
||||
"save_image_as_error_description": "L’image n’a pas pu être sauvegardée",
|
||||
"save_image_as_error_description": "L’image n’a pas pu être enregistrée",
|
||||
"save_image_as_error_title": "Échec de la sauvegarde de l’image"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Effacer les données et recharger ?",
|
||||
"backend_changed_detail": "Impossible d'accéder aux secrets depuis le trousseau de clés du système, il semble avoir changé.",
|
||||
"backend_changed_title": "Impossible de charger la base de données",
|
||||
"backend_no_encryption": "Votre système dispose d'un trousseau de clés compatible mais le chiffrement est indisponible.",
|
||||
"backend_no_encryption_detail": "Electron a détecté que le chiffrement n'est pas disponible pour le trousseau %(backend)s. Veuillez vérifier que votre trousseau est installé. Si c'est le cas, redémarrez et réessayez. Comme alternative, vous pouvez autoriser %(brand)s à utiliser une option de chiffrement moins sécurisée.",
|
||||
"backend_no_encryption_title": "Aucune prise en charge du chiffrement",
|
||||
"unsupported_keyring": "Votre système possède un trousseau de clés non pris en charge, la base de données ne peut pas être ouverte.",
|
||||
"unsupported_keyring_detail": "La détection du porte-clés par Electron n'a pas permis de trouver de backend compatible. Vous pouvez essayer de configurer manuellement le backend en utilisant %(brand)s avec un argument de ligne de commande. Cette opération doit être effectuer une seule fois. Voir%(link)s.",
|
||||
"unsupported_keyring_title": "Système non pris en charge",
|
||||
"unsupported_keyring_use_basic_text": "Utiliser un chiffrement plus faible",
|
||||
"unsupported_keyring_use_plaintext": "N'utilise pas de chiffrement"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Taille réelle",
|
||||
"toggle_developer_tools": "Basculer les outils de développement",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"action": {
|
||||
"cancel": "Mégse",
|
||||
"close": "Bezárás",
|
||||
"close_brand": "%(brand)s bezárása",
|
||||
"close_brand": "Az %(brand)s bezárása",
|
||||
"copy": "Másolás",
|
||||
"cut": "Kivágás",
|
||||
"delete": "Törlés",
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Névjegy",
|
||||
"brand_help": "%(brand)s Súgó",
|
||||
"help": "Súgó",
|
||||
"preferences": "Beállítások"
|
||||
"no": "Nem",
|
||||
"preferences": "Beállítások",
|
||||
"yes": "Igen"
|
||||
},
|
||||
"confirm_quit": "Biztos, hogy kilép?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,21 @@
|
||||
"speech_start_speaking": "Kezdjen beszélni",
|
||||
"speech_stop_speaking": "Fejezze be a beszédet"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "A macOS egy nem támogatott verzióját futtatja. Frissítse, hogy megkapja az %(brand)s új frissítéseit.",
|
||||
"title": "A rendszer nem támogatott",
|
||||
"warning": "A macOS egy nem támogatott verzióját futtatja. Frissítse a rendszert, hogy biztosítsa az %(brand)s további működését."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Fájl"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Hiba",
|
||||
"description_notifications": {
|
||||
"one": "%(count)s olvasatlan értesítése van.",
|
||||
"other": "%(count)s olvasatlan értesítése van."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Elrejtés",
|
||||
"hide_others": "Mások elrejtése",
|
||||
@@ -49,6 +63,21 @@
|
||||
"save_image_as_error_description": "A kép mentése sikertelen",
|
||||
"save_image_as_error_title": "Kép mentése sikertelen"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Adatok törlése és újratöltés?",
|
||||
"backend_changed_detail": "Nem sikerült hozzáférni a rendszerkulcstartó titkos kódjához, úgy tűnik, megváltozott.",
|
||||
"backend_changed_title": "Nem sikerült betölteni az adatbázist",
|
||||
"backend_no_encryption": "A rendszer támogatott kulcstartóval rendelkezik, de a titkosítás nem érhető el.",
|
||||
"backend_no_encryption_detail": "Az Electron észlelte, hogy a titkosítás nem érhető el a(z) %(backend)s kulcstartóján. Győződjön meg róla, hogy telepítve van-e a kulcstartó. Ha telepítve van, indítsa újra és próbálja újra. Esetleg engedélyezheti a gyengébb titkosítást az %(brand)s számára.",
|
||||
"backend_no_encryption_title": "Nincs titkosítási támogatás",
|
||||
"unsupported_keyring": "A rendszer nem támogatott kulcstartóval rendelkezik, ami azt jelenti, hogy az adatbázis nem nyitható meg.",
|
||||
"unsupported_keyring_detail": "Az Electron kulcstartóészlelése nem talált támogatott háttérrendszert. Megpróbálhatja kézileg beállítani a háttérrendszert az %(brand)s egyszeri, parancssori argumentummal való indításával. Lásd: %(link)s.",
|
||||
"unsupported_keyring_title": "A rendszer nem támogatott",
|
||||
"unsupported_keyring_use_basic_text": "Gyengébb titkosítás használata",
|
||||
"unsupported_keyring_use_plaintext": "Ne használjon titkosítást"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Jelenlegi méret",
|
||||
"toggle_developer_tools": "Fejlesztői eszközök",
|
||||
@@ -56,7 +85,7 @@
|
||||
"view": "Megtekintés"
|
||||
},
|
||||
"window_menu": {
|
||||
"bring_all_to_front": "Mindent előtérbe hoz",
|
||||
"bring_all_to_front": "Minden előtérbe hozása",
|
||||
"label": "Ablak",
|
||||
"zoom": "Nagyítás"
|
||||
}
|
||||
|
||||
87
src/i18n/strings/hy.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"action": {
|
||||
"cancel": "Չեղարկել",
|
||||
"close": "Փակել",
|
||||
"close_brand": "Փակել %(brand)s",
|
||||
"copy": "Պատճենել",
|
||||
"cut": "Կտրել",
|
||||
"delete": "Ջնջել",
|
||||
"edit": "Խմբագրել",
|
||||
"minimise": "Նվազագույնի հասցնել",
|
||||
"paste": "Տեղադրել",
|
||||
"paste_match_style": "Տեղադրել և համապատասխանեցնել ոճը",
|
||||
"quit": "Դուրս գալ",
|
||||
"redo": "Կրկնել",
|
||||
"select_all": "Ընտրել բոլորը",
|
||||
"show_hide": "Ցուցադրել/Թաքցնել",
|
||||
"undo": "Հետարկել",
|
||||
"zoom_in": "Մեծացնել",
|
||||
"zoom_out": "Փոքրացնել"
|
||||
},
|
||||
"common": {
|
||||
"about": "Կենսագրություն",
|
||||
"brand_help": "%(brand)s Օգնություն",
|
||||
"help": "Օգնություն",
|
||||
"no": "Ոչ",
|
||||
"preferences": "Նախապատվություններ",
|
||||
"yes": "Այո"
|
||||
},
|
||||
"confirm_quit": "Վստա՞հ եք, որ ուզում եք դուրս գալ։",
|
||||
"edit_menu": {
|
||||
"speech": "Խոսք/Ելույթ",
|
||||
"speech_start_speaking": "Սկսեք խոսել",
|
||||
"speech_stop_speaking": "Դադարեցրեք խոսելը"
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Ֆայլ"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Սխալ",
|
||||
"description_notifications": {
|
||||
"one": "Դուք ունեք %(count)s չկարդացված ծանուցում։",
|
||||
"other": "Դուք ունեք %(count)s չկարդացված ծանուցումներ։"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Թաքցնել",
|
||||
"hide_others": "Թաքցնել մյուսները",
|
||||
"services": "Ծառայություններ",
|
||||
"unhide": "Ապաթաքցնել"
|
||||
},
|
||||
"right_click_menu": {
|
||||
"add_to_dictionary": "Ավելացնել բառարանում",
|
||||
"copy_email": "Պատճենել էլ․ հասցեն",
|
||||
"copy_image": "Պատճենել պատկերը",
|
||||
"copy_image_url": "Պատճենել պատկերի հասցեն",
|
||||
"copy_link_url": "Պատճենել հղման հասցեն",
|
||||
"save_image_as": "Պահպանել պատկերը որպես...",
|
||||
"save_image_as_error_description": "Պատկերը չհաջողվեց պահպանել",
|
||||
"save_image_as_error_title": "Չհաջողվեց պահպանել պատկերը"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Մաքրե՞լ տվյալները և վերաբեռնե՞լ",
|
||||
"backend_changed_detail": "Գաղտնի տվյալները հասանելի չեն համակարգի բանալիների պահոցից(keyring), կարծես թե այն փոխվել է։",
|
||||
"backend_changed_title": "Չհաջողվեց բեռնել տվյալների բազան",
|
||||
"backend_no_encryption": "Ձեր համակարգը ունի աջակցվող բանալիների պահոց, բայց գաղտնագրումը հասանելի չէ։",
|
||||
"backend_no_encryption_detail": "Electron-ը հայտնաբերել է, որ ձեր բանալիների պահոցում %(backend)s գաղտնագրումը հասանելի չէ։ Խնդրում ենք համոզվել, որ բանալիների պահոցը տեղադրված է։ Եթե այն արդեն տեղադրված է, վերագործարկեք համակարգը և փորձեք կրկին։ Ցանկության դեպքում կարող եք թույլատրել %(brand)s-ին կիրառել ավելի թույլ գաղտնագրման տարբերակ։",
|
||||
"backend_no_encryption_title": "Գաղտնագրման աջակցություն չկա",
|
||||
"unsupported_keyring": "Համակարգում օգտագործվող բանալիների պահոցը(keyring) չի աջակցվում, ինչը նշանակում է, որ տվյալների բազան հնարավոր չէ բացել։",
|
||||
"unsupported_keyring_detail": "Electron-ի բանալիների պահոցի ստուգումը չգտավ համատեղելի backend։ Կարող եք փորձել ձեռքով կարգավորել backend-ը` մեկնարկելով %(brand)s-ը command-line արգումենտով, մեկանգամյա գործողությամբ։ Տես %(link)s։",
|
||||
"unsupported_keyring_title": "Համակարգը չի աջակցվում",
|
||||
"unsupported_keyring_use_basic_text": "Օգտագործել ավելի թույլ գաղտնագրում",
|
||||
"unsupported_keyring_use_plaintext": "Չօգտագործել գաղտնագրում"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Իրական չափս",
|
||||
"toggle_developer_tools": "Միացնել/անջատել ծրագրավորողի գործիքները",
|
||||
"toggle_full_screen": "Միացնել/անջատել ամբողջական էկրանը",
|
||||
"view": "Դիտել"
|
||||
},
|
||||
"window_menu": {
|
||||
"bring_all_to_front": "Բերեք բոլորին առջևի պլան",
|
||||
"label": "Պատուհան",
|
||||
"zoom": "Մեծացնել"
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Tentang",
|
||||
"brand_help": "Bantuan %(brand)s",
|
||||
"help": "Bantuan",
|
||||
"preferences": "Preferensi"
|
||||
"no": "Tidak",
|
||||
"preferences": "Preferensi",
|
||||
"yes": "Ya"
|
||||
},
|
||||
"confirm_quit": "Apakah Anda yakin ingin keluar?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,20 @@
|
||||
"speech_start_speaking": "Mulai Berbicara",
|
||||
"speech_stop_speaking": "Berhenti Berbicara"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Anda menggunakan versi macOS yang tidak didukung. Harap tingkatkan untuk menerima pembaruan %(brand)s.",
|
||||
"title": "Sistem tidak didukung",
|
||||
"warning": "Anda menggunakan versi macOS yang tidak didukung. Harap perbarui untuk memastikan%(brand)s terus bekerja."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Berkas"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Kesalahan",
|
||||
"description_notifications": {
|
||||
"other": "Anda memiliki %(count)s notifikasi yang belum dibaca."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Sembunyikan",
|
||||
"hide_others": "Sembunyikan yang Lain",
|
||||
@@ -49,6 +62,21 @@
|
||||
"save_image_as_error_description": "Gambar gagal disimpan",
|
||||
"save_image_as_error_title": "Gagal menyimpan gambar"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Hapus data dan muat ulang?",
|
||||
"backend_changed_detail": "Tidak dapat mengakses rahasia dari keyring sistem, tampaknya telah berubah.",
|
||||
"backend_changed_title": "Gagal memuat basis data",
|
||||
"backend_no_encryption": "Sistem Anda memiliki keyring yang didukung tetapi enkripsi tidak tersedia.",
|
||||
"backend_no_encryption_detail": "Electron telah mendeteksi bahwa enkripsi tidak tersedia pada keyring %(backend)s Anda. Harap pastikan bahwa Anda telah memasang keyring. Jika Anda telah memasang keyring, silakan mulai ulang dan coba lagi. Secara opsional, Anda dapat mengizinkan %(brand)s untuk menggunakan bentuk enkripsi yang lebih lemah.",
|
||||
"backend_no_encryption_title": "Tidak ada dukungan enkripsi",
|
||||
"unsupported_keyring": "Sistem Anda memiliki keyring yang tidak didukung yang berarti basis data tidak dapat dibuka.",
|
||||
"unsupported_keyring_detail": "Deteksi keyring Electron tidak menemukan backend yang didukung. Anda dapat mencoba mengonfigurasi backend secara manual dengan memulai %(brand)s dengan argumen baris perintah, operasi satu kali. Lihat %(link)s.",
|
||||
"unsupported_keyring_title": "Sistem tidak didukung",
|
||||
"unsupported_keyring_use_basic_text": "Gunakan enkripsi yang lebih lemah",
|
||||
"unsupported_keyring_use_plaintext": "Jangan gunakan enkripsi"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Ukuran Sebenarnya",
|
||||
"toggle_developer_tools": "Beralih Alat Pengembang",
|
||||
|
||||
63
src/i18n/strings/ka.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"action": {
|
||||
"cancel": "გაუქმება",
|
||||
"close": "დახურვა",
|
||||
"close_brand": "დახურვა %(brand)s",
|
||||
"copy": "კოპირება",
|
||||
"cut": "მოჭრა",
|
||||
"delete": "წაშალეთ",
|
||||
"edit": "რედაქტირება",
|
||||
"minimise": "შეამცირეთ",
|
||||
"paste": "პასტა",
|
||||
"paste_match_style": "ჩასვით და მატჩის სტილი",
|
||||
"quit": "თავი დაანებე",
|
||||
"redo": "რედო",
|
||||
"select_all": "აირჩიეთ ყველა",
|
||||
"show_hide": "ჩვენება/დამალვა",
|
||||
"undo": "გაუქმება",
|
||||
"zoom_in": "გაზარდოთ",
|
||||
"zoom_out": "გაფართოება"
|
||||
},
|
||||
"common": {
|
||||
"about": "შესახებ",
|
||||
"brand_help": "%(brand)sდახმარება",
|
||||
"help": "დახმარება",
|
||||
"preferences": "პრეფერენციები"
|
||||
},
|
||||
"confirm_quit": "დარწმუნებული ხართ, რომ გსურთ დატოვება?",
|
||||
"edit_menu": {
|
||||
"speech": "გამოსვლა",
|
||||
"speech_start_speaking": "დაიწყეთ საუბარი",
|
||||
"speech_stop_speaking": "შეწყვიტე ლაპარ"
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "ფაილი"
|
||||
},
|
||||
"menu": {
|
||||
"hide": "დამალვა",
|
||||
"hide_others": "სხვების დამალვა",
|
||||
"services": "მომსახურება",
|
||||
"unhide": "გამოხატე"
|
||||
},
|
||||
"right_click_menu": {
|
||||
"add_to_dictionary": "ლექსიკონში დამატება",
|
||||
"copy_email": "ელ. ფოსტის მისამართის",
|
||||
"copy_image": "სურათის დაკოპირება",
|
||||
"copy_image_url": "გამოსახულების მისამართის კოპირ",
|
||||
"copy_link_url": "ბმულის მისამართის კოპირება",
|
||||
"save_image_as": "შეინახეთ სურათი როგორც...",
|
||||
"save_image_as_error_description": "სურათის შენახვა ვერ შეძლო",
|
||||
"save_image_as_error_title": "სურათის შენახვა ვერ შეძლ"
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "რეალური ზომა",
|
||||
"toggle_developer_tools": "დეველოპერის ინსტრუმენტების",
|
||||
"toggle_full_screen": "სრული ეკრანის გადართვა",
|
||||
"view": "ნახვა"
|
||||
},
|
||||
"window_menu": {
|
||||
"bring_all_to_front": "ყველაფერი წინ წამოიყვანეთ",
|
||||
"label": "ფანჯარა",
|
||||
"zoom": "გაზუსტება"
|
||||
}
|
||||
}
|
||||
63
src/i18n/strings/lv.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"action": {
|
||||
"cancel": "Atcelt",
|
||||
"close": "Aizvērt",
|
||||
"close_brand": "Aizvērt %(brand)s",
|
||||
"copy": "Kopēt",
|
||||
"cut": "Izgriezt",
|
||||
"delete": "Dzēst",
|
||||
"edit": "Labot",
|
||||
"minimise": "Samazināt",
|
||||
"paste": "Ielīmēt",
|
||||
"paste_match_style": "Ielīmēt un pielāgot stilu",
|
||||
"quit": "Iziet",
|
||||
"redo": "Atatsaukt",
|
||||
"select_all": "Atzīmēt visu",
|
||||
"show_hide": "Parādīt/paslēpt",
|
||||
"undo": "Atsaukt",
|
||||
"zoom_in": "Tuvināt",
|
||||
"zoom_out": "Tālināt"
|
||||
},
|
||||
"common": {
|
||||
"about": "Par",
|
||||
"brand_help": "%(brand)s palīdzība",
|
||||
"help": "Palīdzība",
|
||||
"preferences": "Iestatījumi"
|
||||
},
|
||||
"confirm_quit": "Vai tiešām iziet?",
|
||||
"edit_menu": {
|
||||
"speech": "Runa",
|
||||
"speech_start_speaking": "Uzsākt runāšanu",
|
||||
"speech_stop_speaking": "Pārtraukt runāšanu"
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Datne"
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Paslēpt",
|
||||
"hide_others": "Paslēpt citus",
|
||||
"services": "Pakalpojumi",
|
||||
"unhide": "Rādīt"
|
||||
},
|
||||
"right_click_menu": {
|
||||
"add_to_dictionary": "Pievienot vārdnīcai",
|
||||
"copy_email": "Ievietot e-pasta adresi starpliktuvē",
|
||||
"copy_image": "Ievietot attēlu starpliktuvē",
|
||||
"copy_image_url": "Ievietot attēla adresi starpliktuvē",
|
||||
"copy_link_url": "Ievietot saites adresi starpliktuvē",
|
||||
"save_image_as": "Saglabāt attēlu kā...",
|
||||
"save_image_as_error_description": "Attēlu neizdevās saglabāt",
|
||||
"save_image_as_error_title": "Neizdevās saglabāt attēlu"
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Īstais izmērs",
|
||||
"toggle_developer_tools": "Pārslēgt izstrādātāja rīkus",
|
||||
"toggle_full_screen": "Pārslēgt pilnekrānu",
|
||||
"view": "Skats"
|
||||
},
|
||||
"window_menu": {
|
||||
"bring_all_to_front": "Iznest visu priekšplānā",
|
||||
"label": "Logs",
|
||||
"zoom": "Tālummaiņa"
|
||||
}
|
||||
}
|
||||
63
src/i18n/strings/mg_MG.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"action": {
|
||||
"cancel": "Hanafoana",
|
||||
"close": "Akatona",
|
||||
"close_brand": "Anakatona%(brand)s",
|
||||
"copy": "Dika Mitovy",
|
||||
"cut": "Tapaina",
|
||||
"delete": "Fafaina",
|
||||
"edit": "Anova",
|
||||
"minimise": "Manamaivana",
|
||||
"paste": "Koba",
|
||||
"paste_match_style": "Mametaka sy Mampifanandrify ny fomba",
|
||||
"quit": "Mialà",
|
||||
"redo": "Averina atao",
|
||||
"select_all": "Isafidy ny rehetra",
|
||||
"show_hide": "Aneho/Anafina",
|
||||
"undo": "Ravao",
|
||||
"zoom_in": "Angedao",
|
||||
"zoom_out": "Hahelezo"
|
||||
},
|
||||
"common": {
|
||||
"about": "Mombamomba",
|
||||
"brand_help": "%(marques)Fanampiana",
|
||||
"help": "Fanampiana",
|
||||
"preferences": "Safidy manokana"
|
||||
},
|
||||
"confirm_quit": "Azo Antoka ve fa tena hiala ianao",
|
||||
"edit_menu": {
|
||||
"speech": "Fitenenana",
|
||||
"speech_start_speaking": "Atomboy ny resaka/Manomboha fitenenena",
|
||||
"speech_stop_speaking": "Atsaharo ny fitenenana"
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Manapetraka/apetrao"
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Afeno",
|
||||
"hide_others": "Afeno ny hafa",
|
||||
"services": "Tolotra",
|
||||
"unhide": "Asehoy"
|
||||
},
|
||||
"right_click_menu": {
|
||||
"add_to_dictionary": "Ampio ao amin'ny rakibolana",
|
||||
"copy_email": "Adikao ny adiresy imailaka",
|
||||
"copy_image": "Andika ny sary",
|
||||
"copy_image_url": "Adikao ny adiresin'ny sary",
|
||||
"copy_link_url": "Adikao ny adiresy rohy",
|
||||
"save_image_as": "Hitahiry ny sary ho",
|
||||
"save_image_as_error_description": "Tsy voatahiry ilay sary",
|
||||
"save_image_as_error_title": "Tsy nahahomby ny fitahirizana an'ilay sary"
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Habe Ankehitriny",
|
||||
"toggle_developer_tools": "Amadika fitaovana fampandrosoana",
|
||||
"toggle_full_screen": "Hamadika amin'ny efijery feno",
|
||||
"view": "Hijery"
|
||||
},
|
||||
"window_menu": {
|
||||
"bring_all_to_front": "Ataovy aloha ny zava-drehetra",
|
||||
"label": "Varavarankely",
|
||||
"zoom": "Anakaiky fahitana"
|
||||
}
|
||||
}
|
||||
92
src/i18n/strings/nb_NO.json
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"action": {
|
||||
"cancel": "Avbryt",
|
||||
"close": "Lukk",
|
||||
"close_brand": "Avslutt %(brand)s",
|
||||
"copy": "Kopier",
|
||||
"cut": "Klipp",
|
||||
"delete": "Slett",
|
||||
"edit": "Rediger",
|
||||
"minimise": "Minimere",
|
||||
"paste": "Lim inn",
|
||||
"paste_match_style": "Lim inn og match stil",
|
||||
"quit": "Avslutt",
|
||||
"redo": "Gjør om",
|
||||
"select_all": "Velg alle",
|
||||
"show_hide": "Vis/Skjul",
|
||||
"undo": "Angre",
|
||||
"zoom_in": "Zoom inn",
|
||||
"zoom_out": "Zoom ut"
|
||||
},
|
||||
"common": {
|
||||
"about": "Om",
|
||||
"brand_help": "%(brand)s Hjelp",
|
||||
"help": "Hjelp",
|
||||
"no": "Nei",
|
||||
"preferences": "Innstillinger",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"confirm_quit": "Er du sikker på at du vil slutte?",
|
||||
"edit_menu": {
|
||||
"speech": "Tale",
|
||||
"speech_start_speaking": "Begynn å snakke",
|
||||
"speech_stop_speaking": "Slutt å snakke"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Du kjører en versjon av macOS som ikke støttes. Oppgrader for å motta oppdateringer fr %(brand)s.",
|
||||
"title": "Systemet støttes ikke",
|
||||
"warning": "Du bruker en versjon av macOS som ikke støttes. Oppgrader for å sikre at %(brand)s fortsetter å fungere."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Fil"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Feil",
|
||||
"description_notifications": {
|
||||
"one": "Du har %(count)s ulest varsel.",
|
||||
"other": "Du har %(count)s uleste varsler."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Skjul",
|
||||
"hide_others": "Skjul andre",
|
||||
"services": "Tjenester",
|
||||
"unhide": "Slutt å skjule"
|
||||
},
|
||||
"right_click_menu": {
|
||||
"add_to_dictionary": "Legg til i ordbok",
|
||||
"copy_email": "Kopier e-postadressen",
|
||||
"copy_image": "Kopier bildet",
|
||||
"copy_image_url": "Kopier bildeadresse",
|
||||
"copy_link_url": "Kopier link adresse",
|
||||
"save_image_as": "Lagre bildet som...",
|
||||
"save_image_as_error_description": "Bildet kunne ikke lagres",
|
||||
"save_image_as_error_title": "Kunne ikke lagre bildet"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Tøm data og last inn på nytt?",
|
||||
"backend_changed_detail": "Kan ikke få tilgang til hemmeligheten fra systemnøkkelringen, den ser ut til å ha blitt endret.",
|
||||
"backend_changed_title": "Kunne ikke laste inn databasen",
|
||||
"backend_no_encryption": "Systemet ditt har en støttet nøkkelring, men kryptering er ikke tilgjengelig.",
|
||||
"backend_no_encryption_detail": "Electron har oppdaget at kryptering ikke er tilgjengelig på nøkkelringen %(backend)s din. Forsikre deg om at du har nøkkelringen installert. Hvis du har nøkkelringen installert, vennligst start på nytt og prøv igjen. Eventuelt kan du tillate %(brand)s å bruke en svakere form for kryptering.",
|
||||
"backend_no_encryption_title": "Ingen støtte for kryptering",
|
||||
"unsupported_keyring": "Systemet ditt har en nøkkelring som ikke støttes, noe som betyr at databasen ikke kan åpnes.",
|
||||
"unsupported_keyring_detail": "Electrons nøkkelringdeteksjon fant ikke en støttet backend. Du kan prøve å konfigurere backend manuelt ved å starte %(brand)s med et kommandolinjeargument, en engangsoperasjon. Se%(link)s.",
|
||||
"unsupported_keyring_title": "Systemet støttes ikke",
|
||||
"unsupported_keyring_use_basic_text": "Bruk svakere kryptering",
|
||||
"unsupported_keyring_use_plaintext": "Ikke bruk kryptering"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Faktisk størrelse",
|
||||
"toggle_developer_tools": "Veksle Utvikleralternativer",
|
||||
"toggle_full_screen": "Veksle Fullskjerm",
|
||||
"view": "Vis"
|
||||
},
|
||||
"window_menu": {
|
||||
"bring_all_to_front": "Flytt Alt Frem",
|
||||
"label": "Vindu",
|
||||
"zoom": "Forstørr"
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Informacje",
|
||||
"brand_help": "Pomoc %(brand)s",
|
||||
"help": "Pomoc",
|
||||
"preferences": "Preferencje"
|
||||
"no": "Nie",
|
||||
"preferences": "Preferencje",
|
||||
"yes": "Tak"
|
||||
},
|
||||
"confirm_quit": "Czy na pewno chcesz zamknąć?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,22 @@
|
||||
"speech_start_speaking": "Zacznij mówić",
|
||||
"speech_stop_speaking": "Przestań mówić"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Korzystasz z nieobsługiwanej wersji systemu macOS. Zaktualizuj system, aby uzyskać aktualizacje %(brand)s.",
|
||||
"title": "System nie jest obsługiwany",
|
||||
"warning": "Korzystasz z nieobsługiwanej wersji systemu macOS. Zaktualizuj system, aby dalej korzystać z %(brand)s."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Plik"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Błąd",
|
||||
"description_notifications": {
|
||||
"one": "Masz %(count)s nieprzeczytane powiadomienie.",
|
||||
"few": "Masz %(count)s nieprzeczytane powiadomienia.",
|
||||
"many": "Masz %(count)s nieprzeczytanych powiadomień."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Ukryj",
|
||||
"hide_others": "Ukryj inne",
|
||||
@@ -49,6 +64,21 @@
|
||||
"save_image_as_error_description": "Obraz nie został zapisany",
|
||||
"save_image_as_error_title": "Nie udało się zapisać obrazu"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Wyczyścić dane i przeładować?",
|
||||
"backend_changed_detail": "Nie można uzyskać dostępu do sekretnego magazynu, wygląda na to, że uległ zmianie.",
|
||||
"backend_changed_title": "Nie udało się załadować bazy danych",
|
||||
"backend_no_encryption": "Twój system posiada wspierany keyring, ale szyfrowanie nie jest dostępne.",
|
||||
"backend_no_encryption_detail": "Elektron wykrył, że szyfrowanie nie jest dostępne w twoim keyring'u %(backend)s. Upewnij się, że keyring został zainstalowany. Jeśli tak, uruchom ponownie urządzenie i spróbuj ponownie. Opcjonalnie, zezwól %(brand)s, aby korzystał ze słabszego szyfrowania.",
|
||||
"backend_no_encryption_title": "Szyfrowanie nie jest obsługiwane",
|
||||
"unsupported_keyring": "System zawiera niewspierany keyring, nie można otworzyć bazy danych.",
|
||||
"unsupported_keyring_detail": "Wykrywanie keyringu Electron nie znalazł wspieranego backendu. Możesz spróbować ręcznie ustawić backed, uruchamiając %(brand)s za pomocą wiesza poleceń. Zobacz %(link)s.",
|
||||
"unsupported_keyring_title": "System niewspierany",
|
||||
"unsupported_keyring_use_basic_text": "Użyj słabszego szyfrowania",
|
||||
"unsupported_keyring_use_plaintext": "Nie używaj szyfrowania"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Rozmiar rzeczywisty",
|
||||
"toggle_developer_tools": "Przełącz narzędzia deweloperskie",
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
},
|
||||
"common": {
|
||||
"about": "Sobre",
|
||||
"brand_help": "%(brand)s Ajuda",
|
||||
"help": "Ajuda",
|
||||
"preferences": "Preferências"
|
||||
"no": "Não",
|
||||
"preferences": "Preferências",
|
||||
"yes": "Sim"
|
||||
},
|
||||
"confirm_quit": "Você tem certeza que você quer sair?",
|
||||
"edit_menu": {
|
||||
@@ -29,9 +32,21 @@
|
||||
"speech_start_speaking": "Começar a Falar",
|
||||
"speech_stop_speaking": "Parar de Falar"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Você está usando uma versão não suportada do macOS. Atualize para receber as atualizações d %(brand)s.",
|
||||
"title": "Sistema não suportado",
|
||||
"warning": "Você está usando uma versão não compatível do macOS. Faça a atualização para garantir que o %(brand)s continue funcionando."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Arquivo"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Erro",
|
||||
"description_notifications": {
|
||||
"one": "Você tem %(count)s notificação não lida.",
|
||||
"other": "Você tem %(count)s notificações não lidas."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Esconder",
|
||||
"hide_others": "Esconder Outras(os)",
|
||||
@@ -48,6 +63,21 @@
|
||||
"save_image_as_error_description": "A imagem falhou para salvar",
|
||||
"save_image_as_error_title": "Falha para salvar imagem"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Limpar dados e recarregar?",
|
||||
"backend_changed_detail": "Não foi possível acessar o segredo no cofre do sistema, parece que ele foi alterado.",
|
||||
"backend_changed_title": "Falha ao carregar o banco de dados",
|
||||
"backend_no_encryption": "Seu sistema tem um cofre compatível, mas a criptografia não está disponível.",
|
||||
"backend_no_encryption_detail": "O Electron detetou que a encriptação não está disponível no seu cofre %(backend)s. Certifique-se de que tem o cofre instalado. Se tiver o cofre instalado, reinicie e tente novamente. Opcionalmente, você pode permitir que %(brand)s use uma forma mais fraca de criptografia.",
|
||||
"backend_no_encryption_title": "Sem suporte para criptografia",
|
||||
"unsupported_keyring": "Seu sistema possui um cofre não compatível, o que impede a abertura do banco de dados.",
|
||||
"unsupported_keyring_detail": "A detecção de cofre do Electron não encontrou um backend compatível. Você pode tentar configurar manualmente o backend iniciando %(brand)s com um argumento de linha de comando, uma operação única. Consulte %(link)s.",
|
||||
"unsupported_keyring_title": "Sistema não suportado",
|
||||
"unsupported_keyring_use_basic_text": "Use criptografia mais fraca",
|
||||
"unsupported_keyring_use_plaintext": "Não usar criptografia"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Tamanho de Verdade",
|
||||
"toggle_developer_tools": "Ativar/Desativar Ferramentas de Desenvolvimento",
|
||||
@@ -56,6 +86,7 @@
|
||||
},
|
||||
"window_menu": {
|
||||
"bring_all_to_front": "Trazer Todas Para Frente",
|
||||
"label": "Janela"
|
||||
"label": "Janela",
|
||||
"zoom": "Zoom"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "О программе",
|
||||
"brand_help": "Помощь %(brand)s",
|
||||
"help": "Помощь",
|
||||
"preferences": "Предпочтения"
|
||||
"no": "Нет",
|
||||
"preferences": "Предпочтения",
|
||||
"yes": "Да"
|
||||
},
|
||||
"confirm_quit": "Вы уверены, что хотите выйти?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,22 @@
|
||||
"speech_start_speaking": "Говорите",
|
||||
"speech_stop_speaking": "Перестаньте говорить"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Вы используете неподдерживаемую версию macOS. Пожалуйста обновите систему чтобы получать обновления %(brand)s.",
|
||||
"title": "Система не поддерживается",
|
||||
"warning": "Вы используете неподдерживаемую версию macOS. Пожалуйста обновите её, чтобы %(brand)s продолжал работать."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Файл"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Ошибка",
|
||||
"description_notifications": {
|
||||
"one": "У вас есть %(count)s непрочитанное уведомление.",
|
||||
"few": "У вас есть %(count)s непрочитанных уведомления.",
|
||||
"many": "У вас есть %(count)s непрочитанных уведомлений."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Скрыть",
|
||||
"hide_others": "Скрыть прочие",
|
||||
@@ -49,6 +64,21 @@
|
||||
"save_image_as_error_description": "Не удалось сохранить изображение",
|
||||
"save_image_as_error_title": "Не удалось сохранить изображение"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Очистить данные и перезагрузить?",
|
||||
"backend_changed_detail": "Не удалось получить доступ к секрету из системной связки ключей. Похоже, что она изменилась.",
|
||||
"backend_changed_title": "Не удалось загрузить базу данных",
|
||||
"backend_no_encryption": "В вашей системе имеется поддерживаемая связка ключей, но шифрование недоступно.",
|
||||
"backend_no_encryption_detail": "Electron обнаружил, что шифрование недоступно в вашем хранилище %(backend)s Убедитесь, что у вас установлено связное устройство. Если связное устройство установлено, перезагрузите компьютер и повторите попытку. При желании вы можете разрешить %(brand)s использовать более слабую форму шифрования.",
|
||||
"backend_no_encryption_title": "Нет поддержки шифрования",
|
||||
"unsupported_keyring": "Невозможно открыть базу данных, так как в вашей системе установлена неподдерживаемая связка ключей",
|
||||
"unsupported_keyring_detail": "Функция обнаружения ключей Electron не нашла поддерживаемый сервер. Можно попробовать настроить сервер вручную, запустив %(brand)s с аргументом командной строки, это нужно сделать только один раз. Смотри %(link)s.",
|
||||
"unsupported_keyring_title": "Система не поддерживается",
|
||||
"unsupported_keyring_use_basic_text": "Использовать более слабое шифрование",
|
||||
"unsupported_keyring_use_plaintext": "Не использовать шифрование"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Фактический размер",
|
||||
"toggle_developer_tools": "Переключить инструменты разработчика",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Informácie",
|
||||
"brand_help": "%(brand)s Pomoc",
|
||||
"help": "Pomocník",
|
||||
"preferences": "Predvoľby"
|
||||
"no": "Nie",
|
||||
"preferences": "Predvoľby",
|
||||
"yes": "Áno"
|
||||
},
|
||||
"confirm_quit": "Naozaj chcete zavrieť aplikáciu?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,22 @@
|
||||
"speech_start_speaking": "Spustiť nahrávanie hlasu",
|
||||
"speech_stop_speaking": "Zastaviť nahrávanie hlasu"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "Používate nepodporovanú verziu systému macOS. Prosím, aktualizujte systém, aby ste mohli dostávať aktualizácie aplikácie %(brand)s.",
|
||||
"title": "Systém nie je podporovaný",
|
||||
"warning": "Používate nepodporovanú verziu systému macOS. Vykonajte prosím aktualizáciu, aby aplikácia %(brand)s mohla správne fungovať."
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Súbor"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Chyba",
|
||||
"description_notifications": {
|
||||
"one": "Máte %(count)s neprečítané oznámenie.",
|
||||
"few": "Máte %(count)s neprečítané oznámenia.",
|
||||
"other": "Máte %(count)s neprečítaných oznámení."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Skryť",
|
||||
"hide_others": "Skryť ostatné",
|
||||
@@ -49,6 +64,21 @@
|
||||
"save_image_as_error_description": "Obrázok sa nepodarilo uložiť",
|
||||
"save_image_as_error_title": "Chyba pri ukladaní obrázka"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Vymazať údaje a znova načítať?",
|
||||
"backend_changed_detail": "Nepodarilo sa získať prístup k tajnému kľúču zo systémového zväzku kľúčov, zdá sa, že sa zmenil.",
|
||||
"backend_changed_title": "Nepodarilo sa načítať databázu",
|
||||
"backend_no_encryption": "Váš systém má podporovaný zväzok kľúčov, ale šifrovanie nie je k dispozícii.",
|
||||
"backend_no_encryption_detail": "Electron zistil, že šifrovanie nie je k dispozícii na vašom zväzku kľúčov %(backend)s. Uistite sa, že máte nainštalovaný zväzok kľúčov. Ak máte zväzok kľúčov nainštalovaný, reštartujte počítač a skúste to znova. Voliteľne môžete povoliť aplikácii %(brand)s používať slabšiu formu šifrovania.",
|
||||
"backend_no_encryption_title": "Žiadna podpora šifrovania",
|
||||
"unsupported_keyring": "Váš systém má nepodporovaný zväzok kľúčov, čo znamená, že databázu nemožno otvoriť.",
|
||||
"unsupported_keyring_detail": "Detekcia zväzku kľúčov aplikácie Electron nenašla podporovaný backend. Môžete sa pokúsiť manuálne nastaviť backend spustením aplikácie %(brand)s s argumentom príkazového riadka, je to jednorazová operácia. Pozrite si %(link)s .",
|
||||
"unsupported_keyring_title": "Systém nie je podporovaný",
|
||||
"unsupported_keyring_use_basic_text": "Použiť slabšie šifrovanie",
|
||||
"unsupported_keyring_use_plaintext": "Nepoužiť žiadne šifrovanie"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Aktuálna veľkosť",
|
||||
"toggle_developer_tools": "Nástroje pre vývojárov",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Om",
|
||||
"brand_help": "%(brand)s-hjälp",
|
||||
"help": "Hjälp",
|
||||
"preferences": "Inställningar"
|
||||
"no": "Nej",
|
||||
"preferences": "Inställningar",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"confirm_quit": "Är du säker att du vill avsluta?",
|
||||
"edit_menu": {
|
||||
@@ -33,6 +35,13 @@
|
||||
"file_menu": {
|
||||
"label": "Arkiv"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Fel",
|
||||
"description_notifications": {
|
||||
"one": "Du har %(count)s oläst avisering.",
|
||||
"other": "Du har %(count)s olästa aviseringar."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Göm",
|
||||
"hide_others": "Göm övriga",
|
||||
@@ -49,6 +58,21 @@
|
||||
"save_image_as_error_description": "Bilden sparades inte",
|
||||
"save_image_as_error_title": "Misslyckades med att spara bilden"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Rensa data och ladda om?",
|
||||
"backend_changed_detail": "Kunde inte komma åt hemligheten från systemnyckelringen, det verkar ha ändrats.",
|
||||
"backend_changed_title": "Misslyckades att ladda databasen",
|
||||
"backend_no_encryption": "Ditt system har en nyckelring som stöds men kryptering är inte tillgänglig.",
|
||||
"backend_no_encryption_detail": "Electron har upptäckt att kryptering inte är tillgänglig på din nyckelring %(backend)s. Se till att du har nyckelringen installerad. Om du har nyckelringen installerad, starta om och försök igen. Alternativt kan du tillåta %(brand)s att använda en svagare form av kryptering.",
|
||||
"backend_no_encryption_title": "Inget krypteringsstöd",
|
||||
"unsupported_keyring": "Ditt system har en nyckelring som inte stöds, vilket innebär att databasen inte kan öppnas.",
|
||||
"unsupported_keyring_detail": "Electrons nyckelringsdetektering hittade inte en backend som stöds. Du kan försöka konfigurera backend manuellt genom att starta %(brand)s med ett kommandoradsargument, en engångsåtgärd. Se %(link)s.",
|
||||
"unsupported_keyring_title": "Systemet stöds inte",
|
||||
"unsupported_keyring_use_basic_text": "Använd svagare kryptering",
|
||||
"unsupported_keyring_use_plaintext": "Använd ingen kryptering"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Verklig storlek",
|
||||
"toggle_developer_tools": "Växla utvecklarverktyg",
|
||||
|
||||
63
src/i18n/strings/tr.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"action": {
|
||||
"cancel": "İptal",
|
||||
"close": "Kapat",
|
||||
"close_brand": "Kapat %(brand)s",
|
||||
"copy": "Kopyala",
|
||||
"cut": "Kes",
|
||||
"delete": "Sil",
|
||||
"edit": "Düzenle",
|
||||
"minimise": "Küçült",
|
||||
"paste": "Yapıştır",
|
||||
"paste_match_style": "Stili Yapıştır ve Eşleştir",
|
||||
"quit": "Çık",
|
||||
"redo": "Yeniden yap",
|
||||
"select_all": "Tümünü seç",
|
||||
"show_hide": "Göster/Gizle",
|
||||
"undo": "Geri al",
|
||||
"zoom_in": "Yakınlaştır",
|
||||
"zoom_out": "Uzaklaştır"
|
||||
},
|
||||
"common": {
|
||||
"about": "Hakkında",
|
||||
"brand_help": "%(brand)s Yardım",
|
||||
"help": "Yardım",
|
||||
"preferences": "Tercihler"
|
||||
},
|
||||
"confirm_quit": "Çıkmak istediğinizden emin misiniz?",
|
||||
"edit_menu": {
|
||||
"speech": "Konuşma",
|
||||
"speech_start_speaking": "Konuşmaya başla",
|
||||
"speech_stop_speaking": "Konuşmayı durdur"
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "Dosya"
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Gizle",
|
||||
"hide_others": "Diğerlerini gizle",
|
||||
"services": "Hizmetler",
|
||||
"unhide": "Göster"
|
||||
},
|
||||
"right_click_menu": {
|
||||
"add_to_dictionary": "Sözlüğe ekle",
|
||||
"copy_email": "E-posta adresini kopyala",
|
||||
"copy_image": "Resmi kopyala",
|
||||
"copy_image_url": "Görsel adresini kopyala",
|
||||
"copy_link_url": "Bağlantılı adresi kopyala",
|
||||
"save_image_as": "Resmi farklı kaydet...",
|
||||
"save_image_as_error_description": "Görüntü kaydedilemedi",
|
||||
"save_image_as_error_title": "Resim kaydedilemedi"
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Gerçek boyut",
|
||||
"toggle_developer_tools": "Geliştirici araçları",
|
||||
"toggle_full_screen": "Tam ekran",
|
||||
"view": "Görüntüle"
|
||||
},
|
||||
"window_menu": {
|
||||
"bring_all_to_front": "Hepsini öne getir",
|
||||
"label": "Pencere",
|
||||
"zoom": "Yaklaştır"
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "Про застосунок",
|
||||
"brand_help": "Довідка %(brand)s",
|
||||
"help": "Довідка",
|
||||
"preferences": "Параметри"
|
||||
"no": "Ні",
|
||||
"preferences": "Параметри",
|
||||
"yes": "Так"
|
||||
},
|
||||
"confirm_quit": "Ви впевнені, що хочете вийти?",
|
||||
"edit_menu": {
|
||||
@@ -33,6 +35,14 @@
|
||||
"file_menu": {
|
||||
"label": "Файл"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "Помилка",
|
||||
"description_notifications": {
|
||||
"one": "У вас є %(count)s непрочитане сповіщення.",
|
||||
"few": "У вас є %(count)s непрочитані сповіщення.",
|
||||
"many": "У вас є %(count)s непрочитаних сповіщень."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "Сховати",
|
||||
"hide_others": "Сховати інші",
|
||||
@@ -49,6 +59,21 @@
|
||||
"save_image_as_error_description": "Не вдалося зберегти зображення",
|
||||
"save_image_as_error_title": "Не вдалося зберегти зображення"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "Очистити дані та перезавантажити?",
|
||||
"backend_changed_detail": "Не вдається отримати доступ до таємного ключа з системного набору ключів, видається, він змінився.",
|
||||
"backend_changed_title": "Не вдалося завантажити базу даних",
|
||||
"backend_no_encryption": "Ваша система підтримує сховище ключів, але шифрування недоступне.",
|
||||
"backend_no_encryption_detail": "Electron виявив, що шифрування недоступне у вашому сховищі ключів %(backend)s. Переконайтеся, що у вас встановлено сховище ключів. Якщо так, тоді перезавантажте комп'ютер і повторіть спробу. За бажанням, ви можете дозволити %(brand)s використовувати слабшу форму шифрування.",
|
||||
"backend_no_encryption_title": "Шифрування не підтримується",
|
||||
"unsupported_keyring": "Ваша система має непідтримуваний набір ключів. Це означає, що базу даних неможливо відкрити.",
|
||||
"unsupported_keyring_detail": "Electron не виявив підтримуваного бекенда для роботи зі сховищем паролів. Ви можете вручну налаштувати його, запустивши %(brand)s з відповідним аргументом у командному рядку. Цю дію потрібно виконати лише один раз. Докладніше – %(link)s.",
|
||||
"unsupported_keyring_title": "Система не підтримується",
|
||||
"unsupported_keyring_use_basic_text": "Використовувати слабше шифрування",
|
||||
"unsupported_keyring_use_plaintext": "Не використовувати шифрування"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "Фактичний розмір",
|
||||
"toggle_developer_tools": "Перемкнути інструменти розробника",
|
||||
|
||||
83
src/ipc.ts
@@ -1,31 +1,16 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
Copyright 2022-2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { app, autoUpdater, desktopCapturer, ipcMain, powerSaveBlocker, TouchBar, nativeImage } from "electron";
|
||||
import { relaunchApp } from "@standardnotes/electron-clear-data";
|
||||
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
import { recordSSOSession } from "./protocol.js";
|
||||
import { randomArray } from "./utils.js";
|
||||
import { Settings } from "./settings.js";
|
||||
import { keytar } from "./keytar.js";
|
||||
import { getDisplayMediaCallback, setDisplayMediaCallback } from "./displayMediaCallback.js";
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
import Store, { clearDataAndRelaunch } from "./store.js";
|
||||
|
||||
let focusHandlerAttached = false;
|
||||
ipcMain.on("loudNotification", function (): void {
|
||||
@@ -61,7 +46,8 @@ ipcMain.on("app_onAction", function (_ev: IpcMainEvent, payload) {
|
||||
});
|
||||
|
||||
ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
if (!global.mainWindow) return;
|
||||
const store = Store.instance;
|
||||
if (!global.mainWindow || !store) return;
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
@@ -70,18 +56,6 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
case "getUpdateFeedUrl":
|
||||
ret = autoUpdater.getFeedURL();
|
||||
break;
|
||||
case "getSettingValue": {
|
||||
const [settingName] = args;
|
||||
const setting = Settings[settingName];
|
||||
ret = await setting.read();
|
||||
break;
|
||||
}
|
||||
case "setSettingValue": {
|
||||
const [settingName, value] = args;
|
||||
const setting = Settings[settingName];
|
||||
await setting.write(value);
|
||||
break;
|
||||
}
|
||||
case "setLanguage":
|
||||
global.appLocalization.setAppLocale(args[0]);
|
||||
break;
|
||||
@@ -96,9 +70,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
break;
|
||||
case "getConfig":
|
||||
ret = global.vectorConfig;
|
||||
break;
|
||||
|
||||
case "navigateBack":
|
||||
if (global.mainWindow.webContents.canGoBack()) {
|
||||
global.mainWindow.webContents.goBack();
|
||||
@@ -113,11 +85,11 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
if (typeof args[0] !== "boolean") return;
|
||||
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(args[0]);
|
||||
global.store.set("spellCheckerEnabled", args[0]);
|
||||
store.set("spellCheckerEnabled", args[0]);
|
||||
break;
|
||||
|
||||
case "getSpellCheckEnabled":
|
||||
ret = global.store.get("spellCheckerEnabled", true);
|
||||
ret = store.get("spellCheckerEnabled");
|
||||
break;
|
||||
|
||||
case "setSpellCheckLanguages":
|
||||
@@ -135,18 +107,9 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
ret = global.mainWindow.webContents.session.availableSpellCheckerLanguages;
|
||||
break;
|
||||
|
||||
case "startSSOFlow":
|
||||
recordSSOSession(args[0]);
|
||||
break;
|
||||
|
||||
case "getPickleKey":
|
||||
try {
|
||||
ret = await keytar?.getPassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
if (ret === null) {
|
||||
ret = await keytar?.getPassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
}
|
||||
ret = await store.getSecret(`${args[0]}|${args[1]}`);
|
||||
} catch {
|
||||
// if an error is thrown (e.g. keytar can't connect to the keychain),
|
||||
// then return null, which means the default pickle key will be used
|
||||
@@ -157,22 +120,20 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
case "createPickleKey":
|
||||
try {
|
||||
const pickleKey = await randomArray(32);
|
||||
// We purposefully throw if keytar is not available so the caller can handle it
|
||||
// rather than sending them a pickle key we did not store on their behalf.
|
||||
await keytar!.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
await store.setSecret(`${args[0]}|${args[1]}`, pickleKey);
|
||||
ret = pickleKey;
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error("Failed to create pickle key", 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 {}
|
||||
await store.deleteSecret(`${args[0]}|${args[1]}`);
|
||||
} catch (e) {
|
||||
console.error("Failed to destroy pickle key", e);
|
||||
}
|
||||
break;
|
||||
case "getDesktopCapturerSources":
|
||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||
@@ -188,10 +149,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
break;
|
||||
|
||||
case "clearStorage":
|
||||
global.store.clear();
|
||||
global.mainWindow.webContents.session.flushStorageData();
|
||||
await global.mainWindow.webContents.session.clearStorageData();
|
||||
relaunchApp();
|
||||
await clearDataAndRelaunch(global.mainWindow.webContents.session);
|
||||
return; // the app is about to stop, we don't need to reply to the IPC
|
||||
|
||||
case "breadcrumbs": {
|
||||
@@ -258,3 +216,12 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle("getConfig", () => global.vectorConfig);
|
||||
|
||||
const initialisePromiseWithResolvers = Promise.withResolvers<void>();
|
||||
export const initialisePromise = initialisePromiseWithResolvers.promise;
|
||||
|
||||
ipcMain.once("initialise", () => {
|
||||
initialisePromiseWithResolvers.resolve();
|
||||
});
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type * as Keytar from "keytar"; // Hak dependency type
|
||||
|
||||
let keytar: typeof Keytar | undefined;
|
||||
try {
|
||||
({ default: keytar } = await import("keytar"));
|
||||
} catch (e) {
|
||||
if ((<NodeJS.ErrnoException>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 };
|
||||
@@ -10,9 +10,9 @@ import { type TranslationKey as TKey } from "matrix-web-i18n";
|
||||
import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import type Store from "electron-store";
|
||||
import type EN from "./i18n/strings/en_EN.json";
|
||||
import { loadJsonFile } from "./utils.js";
|
||||
import type Store from "./store.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -59,26 +59,24 @@ export function _t(text: TranslationKey, variables: Variables = {}): string {
|
||||
|
||||
type Component = () => void;
|
||||
|
||||
type TypedStore = Store<{ locale?: string | string[] }>;
|
||||
|
||||
export class AppLocalization {
|
||||
private static readonly STORE_KEY = "locale";
|
||||
|
||||
private readonly store: TypedStore;
|
||||
private readonly localizedComponents?: Set<Component>;
|
||||
private readonly store: Store;
|
||||
|
||||
public constructor({ store, components = [] }: { store: TypedStore; components: Component[] }) {
|
||||
public constructor({ components = [], store }: { components: Component[]; store: Store }) {
|
||||
counterpart.registerTranslations(FALLBACK_LOCALE, this.fetchTranslationJson("en_EN"));
|
||||
counterpart.setFallbackLocale(FALLBACK_LOCALE);
|
||||
counterpart.setSeparator("|");
|
||||
|
||||
this.store = store;
|
||||
if (Array.isArray(components)) {
|
||||
this.localizedComponents = new Set(components);
|
||||
}
|
||||
|
||||
this.store = store;
|
||||
if (this.store.has(AppLocalization.STORE_KEY)) {
|
||||
const locales = this.store.get(AppLocalization.STORE_KEY);
|
||||
if (store.has(AppLocalization.STORE_KEY)) {
|
||||
const locales = store.get(AppLocalization.STORE_KEY);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.setAppLocale(locales!);
|
||||
}
|
||||
|
||||
@@ -52,8 +52,10 @@ export function setupMacosTitleBar(window: BrowserWindow): void {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
/* Exclude the main content elements from being drag handles */
|
||||
.mx_AuthPage .mx_AuthPage_modalContent,
|
||||
.mx_AuthPage .mx_AuthPage_modalBlur,
|
||||
.mx_AuthPage .mx_AuthFooter > * {
|
||||
.mx_AuthPage .mx_AuthFooter > *,
|
||||
.mx_AuthPage .mx_Dropdown_menu {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
@@ -117,6 +119,12 @@ export function setupMacosTitleBar(window: BrowserWindow): void {
|
||||
height: 20px;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_newRoomList::before {
|
||||
/* Aligned with the room header */
|
||||
height: 13px;
|
||||
border-right: 1px solid var(--cpd-color-bg-subtle-primary);
|
||||
}
|
||||
|
||||
.mx_RoomView::before,
|
||||
.mx_SpaceRoomView::before {
|
||||
|
||||
@@ -32,6 +32,7 @@ const CHANNELS = [
|
||||
"userAccessToken",
|
||||
"homeserverUrl",
|
||||
"serverSupportedVersions",
|
||||
"showToast",
|
||||
];
|
||||
|
||||
contextBridge.exposeInMainWorld("electron", {
|
||||
@@ -49,4 +50,30 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
}
|
||||
ipcRenderer.send(channel, ...args);
|
||||
},
|
||||
|
||||
async initialise(): Promise<{
|
||||
protocol: string;
|
||||
sessionId: string;
|
||||
config: IConfigOptions;
|
||||
supportedSettings: Record<string, boolean>;
|
||||
/**
|
||||
* Do we need to render badge overlays for new notifications?
|
||||
*/
|
||||
supportsBadgeOverlay: boolean;
|
||||
}> {
|
||||
ipcRenderer.emit("initialise");
|
||||
const [{ protocol, sessionId }, config, supportedSettings] = await Promise.all([
|
||||
ipcRenderer.invoke("getProtocol"),
|
||||
ipcRenderer.invoke("getConfig"),
|
||||
ipcRenderer.invoke("getSupportedSettings"),
|
||||
]);
|
||||
return { protocol, sessionId, config, supportedSettings, supportsBadgeOverlay: process.platform === "win32" };
|
||||
},
|
||||
|
||||
async setSettingValue(settingName: string, value: any): Promise<void> {
|
||||
return ipcRenderer.invoke("setSettingValue", settingName, value);
|
||||
},
|
||||
async getSettingValue(settingName: string): Promise<any> {
|
||||
return ipcRenderer.invoke("getSettingValue", settingName);
|
||||
},
|
||||
});
|
||||
|
||||
199
src/protocol.ts
@@ -6,119 +6,136 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { app, ipcMain } from "electron";
|
||||
import { URL } from "node:url";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
const LEGACY_PROTOCOL = "element";
|
||||
const PROTOCOL = "io.element.desktop";
|
||||
const SEARCH_PARAM = "element-desktop-ssoid";
|
||||
const STORE_FILE_NAME = "sso-sessions.json";
|
||||
|
||||
// we getPath userData before electron-main changes it, so this is the default value
|
||||
const storePath = path.join(app.getPath("userData"), STORE_FILE_NAME);
|
||||
|
||||
function processUrl(url: string): void {
|
||||
if (!global.mainWindow) return;
|
||||
export default class ProtocolHandler {
|
||||
private readonly store: Record<string, string> = {};
|
||||
private readonly sessionId: string;
|
||||
|
||||
const parsed = new URL(url);
|
||||
// sanity check: we only register for the one protocol, so we shouldn't
|
||||
// be getting anything else unless the user is forcing a URL to open
|
||||
// with the Element app.
|
||||
if (parsed.protocol !== `${PROTOCOL}:` && parsed.protocol !== `${LEGACY_PROTOCOL}:`) {
|
||||
console.log("Ignoring unexpected protocol: ", parsed.protocol);
|
||||
return;
|
||||
public constructor(private readonly protocol: string) {
|
||||
// get all args except `hidden` as it'd mean the app would not get focused
|
||||
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
|
||||
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
|
||||
const args = process.argv.slice(1).filter((arg) => arg !== "--hidden" && arg !== "-hidden");
|
||||
if (app.isPackaged) {
|
||||
app.setAsDefaultProtocolClient(this.protocol, process.execPath, args);
|
||||
app.setAsDefaultProtocolClient(LEGACY_PROTOCOL, process.execPath, args);
|
||||
} else if (process.platform === "win32") {
|
||||
// on Mac/Linux this would just cause the electron binary to open
|
||||
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
|
||||
app.setAsDefaultProtocolClient(this.protocol, process.execPath, [app.getAppPath(), ...args]);
|
||||
app.setAsDefaultProtocolClient(LEGACY_PROTOCOL, process.execPath, [app.getAppPath(), ...args]);
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
// Protocol handler for macos
|
||||
app.on("open-url", (ev, url) => {
|
||||
ev.preventDefault();
|
||||
this.processUrl(url);
|
||||
});
|
||||
} else {
|
||||
// Protocol handler for win32/Linux
|
||||
app.on("second-instance", (ev, commandLine) => {
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
if (!url.startsWith(`${this.protocol}:/`) && !url.startsWith(`${LEGACY_PROTOCOL}://`)) return;
|
||||
this.processUrl(url);
|
||||
});
|
||||
}
|
||||
|
||||
this.store = this.readStore();
|
||||
this.sessionId = randomUUID();
|
||||
|
||||
ipcMain.handle("getProtocol", this.onGetProtocol);
|
||||
}
|
||||
|
||||
const urlToLoad = new URL("vector://vector/webapp/");
|
||||
// ignore anything other than the search (used for SSO login redirect)
|
||||
// and the hash (for general element deep links)
|
||||
// There's no reason to allow anything else, particularly other paths,
|
||||
// since this would allow things like the internal jitsi wrapper to
|
||||
// be loaded, which would get the app stuck on that page and generally
|
||||
// be a bit strange and confusing.
|
||||
urlToLoad.search = parsed.search;
|
||||
urlToLoad.hash = parsed.hash;
|
||||
private readonly onGetProtocol = (): { protocol: string; sessionId: string } => {
|
||||
return {
|
||||
protocol: this.protocol,
|
||||
sessionId: this.sessionId,
|
||||
};
|
||||
};
|
||||
|
||||
console.log("Opening URL: ", urlToLoad.href);
|
||||
void global.mainWindow.loadURL(urlToLoad.href);
|
||||
}
|
||||
private processUrl(url: string): void {
|
||||
if (!global.mainWindow) return;
|
||||
|
||||
function readStore(): Record<string, string> {
|
||||
try {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch {
|
||||
return {};
|
||||
const parsed = new URL(url);
|
||||
// sanity check: we only register for the one protocol, so we shouldn't
|
||||
// be getting anything else unless the user is forcing a URL to open
|
||||
// with the Element app.
|
||||
if (parsed.protocol !== `${this.protocol}:` && parsed.protocol !== `${LEGACY_PROTOCOL}:`) {
|
||||
console.log("Ignoring unexpected protocol: ", parsed.protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
const urlToLoad = new URL("vector://vector/webapp/");
|
||||
// ignore anything other than the search (used for SSO login redirect)
|
||||
// and the hash (for general element deep links)
|
||||
// There's no reason to allow anything else, particularly other paths,
|
||||
// since this would allow things like the internal jitsi wrapper to
|
||||
// be loaded, which would get the app stuck on that page and generally
|
||||
// be a bit strange and confusing.
|
||||
urlToLoad.search = parsed.search;
|
||||
urlToLoad.hash = parsed.hash;
|
||||
|
||||
console.log("Opening URL: ", urlToLoad.href);
|
||||
void global.mainWindow.loadURL(urlToLoad.href);
|
||||
}
|
||||
}
|
||||
|
||||
function writeStore(data: Record<string, string>): void {
|
||||
fs.writeFileSync(storePath, JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function recordSSOSession(sessionID: string): void {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const store = readStore();
|
||||
for (const key in store) {
|
||||
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
|
||||
if (store[key] === userDataPath) {
|
||||
delete store[key];
|
||||
break;
|
||||
private readStore(): Record<string, string> {
|
||||
try {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
store[sessionID] = userDataPath;
|
||||
writeStore(store);
|
||||
}
|
||||
|
||||
export function getProfileFromDeeplink(args: string[]): string | undefined {
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
const deeplinkUrl = args.find((arg) => arg.startsWith(`${PROTOCOL}://`) || arg.startsWith(`${LEGACY_PROTOCOL}://`));
|
||||
if (deeplinkUrl?.includes(SEARCH_PARAM)) {
|
||||
const parsedUrl = new URL(deeplinkUrl);
|
||||
if (parsedUrl.protocol === `${PROTOCOL}:` || parsedUrl.protocol === `${LEGACY_PROTOCOL}:`) {
|
||||
const store = readStore();
|
||||
let ssoID = parsedUrl.searchParams.get(SEARCH_PARAM);
|
||||
if (!ssoID) {
|
||||
// In OIDC, we must shuttle the value in the `state` param rather than `element-desktop-ssoid`
|
||||
// We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ`
|
||||
ssoID = parsedUrl.searchParams.get("state")!.split(`:${SEARCH_PARAM}:`)[1];
|
||||
private writeStore(): void {
|
||||
fs.writeFileSync(storePath, JSON.stringify(this.store));
|
||||
}
|
||||
|
||||
public initialise(userDataPath: string): void {
|
||||
for (const key in this.store) {
|
||||
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
|
||||
if (this.store[key] === userDataPath) {
|
||||
delete this.store[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.store[this.sessionId] = userDataPath;
|
||||
this.writeStore();
|
||||
}
|
||||
|
||||
public getProfileFromDeeplink(args: string[]): string | undefined {
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
const deeplinkUrl = args.find(
|
||||
(arg) => arg.startsWith(`${this.protocol}:/`) || arg.startsWith(`${LEGACY_PROTOCOL}://`),
|
||||
);
|
||||
if (deeplinkUrl?.includes(SEARCH_PARAM)) {
|
||||
const parsedUrl = new URL(deeplinkUrl);
|
||||
if (parsedUrl.protocol === `${this.protocol}:` || parsedUrl.protocol === `${LEGACY_PROTOCOL}:`) {
|
||||
const store = this.readStore();
|
||||
let sessionId = parsedUrl.searchParams.get(SEARCH_PARAM);
|
||||
if (!sessionId) {
|
||||
// In OIDC, we must shuttle the value in the `state` param rather than `element-desktop-ssoid`
|
||||
// We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ`
|
||||
sessionId = parsedUrl.searchParams.get("state")!.split(`:${SEARCH_PARAM}:`)[1];
|
||||
}
|
||||
console.log("Forwarding to profile: ", store[sessionId]);
|
||||
return store[sessionId];
|
||||
}
|
||||
console.log("Forwarding to profile: ", store[ssoID]);
|
||||
return store[ssoID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function protocolInit(): void {
|
||||
// get all args except `hidden` as it'd mean the app would not get focused
|
||||
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
|
||||
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
|
||||
const args = process.argv.slice(1).filter((arg) => arg !== "--hidden" && arg !== "-hidden");
|
||||
if (app.isPackaged) {
|
||||
app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, args);
|
||||
app.setAsDefaultProtocolClient(LEGACY_PROTOCOL, process.execPath, args);
|
||||
} else if (process.platform === "win32") {
|
||||
// on Mac/Linux this would just cause the electron binary to open
|
||||
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
|
||||
app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [app.getAppPath(), ...args]);
|
||||
app.setAsDefaultProtocolClient(LEGACY_PROTOCOL, process.execPath, [app.getAppPath(), ...args]);
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
// Protocol handler for macos
|
||||
app.on("open-url", function (ev, url) {
|
||||
ev.preventDefault();
|
||||
processUrl(url);
|
||||
});
|
||||
} else {
|
||||
// Protocol handler for win32/Linux
|
||||
app.on("second-instance", (ev, commandLine) => {
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
if (!url.startsWith(`${PROTOCOL}://`) && !url.startsWith(`${LEGACY_PROTOCOL}://`)) return;
|
||||
processUrl(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
} from "matrix-seshat"; // Hak dependency type
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
import { randomArray } from "./utils.js";
|
||||
import { keytar } from "./keytar.js";
|
||||
import Store from "./store.js";
|
||||
|
||||
let seshatSupported = false;
|
||||
let Seshat: typeof SeshatType;
|
||||
@@ -40,21 +40,24 @@ try {
|
||||
let eventIndex: SeshatType | null = null;
|
||||
|
||||
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
|
||||
async function getOrCreatePassphrase(key: string): Promise<string> {
|
||||
if (keytar) {
|
||||
try {
|
||||
const storedPassphrase = await keytar.getPassword("element.io", key);
|
||||
if (storedPassphrase !== null) {
|
||||
return storedPassphrase;
|
||||
} else {
|
||||
const newPassphrase = await randomArray(32);
|
||||
await keytar.setPassword("element.io", key, newPassphrase);
|
||||
return newPassphrase;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error getting the event index passphrase out of the secret store", e);
|
||||
async function getOrCreatePassphrase(store: Store, key: string): Promise<string> {
|
||||
try {
|
||||
const storedPassphrase = await store.getSecret(key);
|
||||
if (storedPassphrase !== undefined) {
|
||||
return storedPassphrase;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error getting the event index passphrase out of the secret store", e);
|
||||
}
|
||||
|
||||
try {
|
||||
const newPassphrase = await randomArray(32);
|
||||
await store.setSecret(key, newPassphrase);
|
||||
return newPassphrase;
|
||||
} catch (e) {
|
||||
console.error("Error creating new event index passphrase, using default", e);
|
||||
}
|
||||
|
||||
return seshatDefaultPassphrase;
|
||||
}
|
||||
|
||||
@@ -74,7 +77,8 @@ const deleteContents = async (p: string): Promise<void> => {
|
||||
};
|
||||
|
||||
ipcMain.on("seshat", async function (_ev: IpcMainEvent, payload): Promise<void> {
|
||||
if (!global.mainWindow) return;
|
||||
const store = Store.instance;
|
||||
if (!global.mainWindow || !store) return;
|
||||
|
||||
// We do this here to ensure we get the path after --profile has been resolved
|
||||
const eventStorePath = path.join(app.getPath("userData"), "EventStore");
|
||||
@@ -101,7 +105,7 @@ ipcMain.on("seshat", async function (_ev: IpcMainEvent, payload): Promise<void>
|
||||
const deviceId = args[1];
|
||||
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
||||
|
||||
const passphrase = await getOrCreatePassphrase(passphraseKey);
|
||||
const passphrase = await getOrCreatePassphrase(store, passphraseKey);
|
||||
|
||||
try {
|
||||
await afs.mkdir(eventStorePath, { recursive: true });
|
||||
|
||||
@@ -5,47 +5,54 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ipcMain } from "electron";
|
||||
|
||||
import * as tray from "./tray.js";
|
||||
import Store from "./store.js";
|
||||
import { AutoLaunch, type AutoLaunchState } from "./auto-launch.js";
|
||||
|
||||
interface Setting {
|
||||
read(): Promise<any>;
|
||||
write(value: any): Promise<void>;
|
||||
supported?(): boolean; // if undefined, the setting is always supported
|
||||
}
|
||||
|
||||
export const Settings: Record<string, Setting> = {
|
||||
const Settings: Record<string, Setting> = {
|
||||
"Electron.autoLaunch": {
|
||||
async read(): Promise<any> {
|
||||
return global.launcher.isEnabled();
|
||||
async read(): Promise<AutoLaunchState> {
|
||||
return AutoLaunch.instance.getState();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
if (value) {
|
||||
return global.launcher.enable();
|
||||
} else {
|
||||
return global.launcher.disable();
|
||||
}
|
||||
async write(value: AutoLaunchState): Promise<void> {
|
||||
return AutoLaunch.instance.setState(value);
|
||||
},
|
||||
},
|
||||
"Electron.warnBeforeExit": {
|
||||
async read(): Promise<any> {
|
||||
return global.store.get("warnBeforeExit", true);
|
||||
return Store.instance?.get("warnBeforeExit");
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("warnBeforeExit", value);
|
||||
Store.instance?.set("warnBeforeExit", value);
|
||||
},
|
||||
},
|
||||
"Electron.alwaysShowMenuBar": {
|
||||
// not supported on macOS
|
||||
// This isn't relevant on Mac as Menu bars don't live in the app window
|
||||
supported(): boolean {
|
||||
return process.platform !== "darwin";
|
||||
},
|
||||
async read(): Promise<any> {
|
||||
return !global.mainWindow!.autoHideMenuBar;
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("autoHideMenuBar", !value);
|
||||
Store.instance?.set("autoHideMenuBar", !value);
|
||||
global.mainWindow!.autoHideMenuBar = !value;
|
||||
global.mainWindow!.setMenuBarVisibility(value);
|
||||
},
|
||||
},
|
||||
"Electron.showTrayIcon": {
|
||||
// not supported on macOS
|
||||
// Things other than Mac support tray icons
|
||||
supported(): boolean {
|
||||
return process.platform !== "darwin";
|
||||
},
|
||||
async read(): Promise<any> {
|
||||
return tray.hasTray();
|
||||
},
|
||||
@@ -56,15 +63,54 @@ export const Settings: Record<string, Setting> = {
|
||||
} else {
|
||||
tray.destroy();
|
||||
}
|
||||
global.store.set("minimizeToTray", value);
|
||||
Store.instance?.set("minimizeToTray", value);
|
||||
},
|
||||
},
|
||||
"Electron.enableHardwareAcceleration": {
|
||||
async read(): Promise<any> {
|
||||
return !global.store.get("disableHardwareAcceleration", false);
|
||||
return !Store.instance?.get("disableHardwareAcceleration");
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("disableHardwareAcceleration", !value);
|
||||
Store.instance?.set("disableHardwareAcceleration", !value);
|
||||
},
|
||||
},
|
||||
"Electron.enableContentProtection": {
|
||||
// Unsupported on Linux https://www.electronjs.org/docs/latest/api/browser-window#winsetcontentprotectionenable-macos-windows
|
||||
// Broken on macOS https://github.com/electron/electron/issues/19880
|
||||
supported(): boolean {
|
||||
return process.platform === "win32";
|
||||
},
|
||||
async read(): Promise<any> {
|
||||
return Store.instance?.get("enableContentProtection");
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.mainWindow?.setContentProtection(value);
|
||||
Store.instance?.set("enableContentProtection", value);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
ipcMain.handle("getSupportedSettings", async () => {
|
||||
const supportedSettings: Record<string, boolean> = {};
|
||||
for (const [key, setting] of Object.entries(Settings)) {
|
||||
supportedSettings[key] = setting.supported?.() ?? true;
|
||||
}
|
||||
return supportedSettings;
|
||||
});
|
||||
ipcMain.handle("setSettingValue", async (_ev, settingName: string, value: any) => {
|
||||
const setting = Settings[settingName];
|
||||
if (!setting) {
|
||||
throw new Error(`Unknown setting: ${settingName}`);
|
||||
}
|
||||
console.debug(`Writing setting value for: ${settingName} = ${value}`);
|
||||
await setting.write(value);
|
||||
});
|
||||
ipcMain.handle("getSettingValue", async (_ev, settingName: string) => {
|
||||
const setting = Settings[settingName];
|
||||
if (!setting) {
|
||||
throw new Error(`Unknown setting: ${settingName}`);
|
||||
}
|
||||
const value = await setting.read();
|
||||
console.debug(`Reading setting value for: ${settingName} = ${value}`);
|
||||
return value;
|
||||
});
|
||||
|
||||
544
src/store.ts
Normal file
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
Copyright 2022-2025 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ElectronStore from "electron-store";
|
||||
import keytar from "keytar-forked";
|
||||
import { app, safeStorage, dialog, type SafeStorage, type Session } from "electron";
|
||||
|
||||
import { _t } from "./language-helper.js";
|
||||
|
||||
/**
|
||||
* Legacy keytar service name for storing secrets.
|
||||
* @deprecated
|
||||
*/
|
||||
const KEYTAR_SERVICE = "element.io";
|
||||
/**
|
||||
* Super legacy keytar service name for reading secrets.
|
||||
* @deprecated
|
||||
*/
|
||||
const LEGACY_KEYTAR_SERVICE = "riot.im";
|
||||
|
||||
/**
|
||||
* String union type representing all the safeStorage backends.
|
||||
* + The "unknown" backend shouldn't exist in practice once the app is ready
|
||||
* + The "plaintext" is the temporarily-unencrypted backend for migration, data is wholly unencrypted - uses PlaintextStorageWriter
|
||||
* + The "basic_text" backend is the 'plaintext' backend on Linux, data is encrypted but not using the keychain
|
||||
* + The "system" backend is the encrypted backend on Windows & macOS, data is encrypted using system keychain
|
||||
* + All other backends are linux-specific and are encrypted using the keychain
|
||||
*/
|
||||
type SafeStorageBackend = ReturnType<SafeStorage["getSelectedStorageBackend"]> | "system" | "plaintext";
|
||||
/**
|
||||
* The "unknown" backend is not a valid backend, so we exclude it from the type.
|
||||
*/
|
||||
type SaneSafeStorageBackend = Exclude<SafeStorageBackend, "unknown">;
|
||||
|
||||
/**
|
||||
* Map of safeStorage backends to their command line arguments.
|
||||
* kwallet6 cannot be specified via command line
|
||||
* https://www.electronjs.org/docs/latest/api/safe-storage#safestoragegetselectedstoragebackend-linux
|
||||
*/
|
||||
const safeStorageBackendMap: Omit<Record<SaneSafeStorageBackend, string>, "system" | "plaintext"> = {
|
||||
basic_text: "basic",
|
||||
gnome_libsecret: "gnome-libsecret",
|
||||
kwallet: "kwallet",
|
||||
kwallet5: "kwallet5",
|
||||
kwallet6: "kwallet6",
|
||||
};
|
||||
|
||||
function relaunchApp(): void {
|
||||
console.info("Relaunching app...");
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data and relaunch the app.
|
||||
*/
|
||||
export async function clearDataAndRelaunch(electronSession: Session): Promise<void> {
|
||||
Store.instance?.clear();
|
||||
electronSession.flushStorageData();
|
||||
await electronSession.clearStorageData();
|
||||
relaunchApp();
|
||||
}
|
||||
|
||||
interface StoreData {
|
||||
warnBeforeExit: boolean;
|
||||
minimizeToTray: boolean;
|
||||
spellCheckerEnabled: boolean;
|
||||
autoHideMenuBar: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration: boolean;
|
||||
enableContentProtection: boolean;
|
||||
safeStorage?: Record<string, string>;
|
||||
/** the safeStorage backend used for the safeStorage data as written */
|
||||
safeStorageBackend?: SafeStorageBackend;
|
||||
/** whether to explicitly override the safeStorage backend, used for migration */
|
||||
safeStorageBackendOverride?: boolean;
|
||||
/** whether to perform a migration of the safeStorage data */
|
||||
safeStorageBackendMigrate?: boolean;
|
||||
/** whether to open the app at login minimised, only valid when app.openAtLogin is true */
|
||||
openAtLoginMinimised: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback storage writer for secrets, mainly used for automated tests and systems without any safeStorage support.
|
||||
*/
|
||||
class StorageWriter {
|
||||
public constructor(protected readonly store: ElectronStore<StoreData>) {}
|
||||
|
||||
public getKey(key: string): `safeStorage.${string}` {
|
||||
return `safeStorage.${key.replaceAll(".", "-")}`;
|
||||
}
|
||||
|
||||
public set(key: string, secret: string): void {
|
||||
this.store.set(this.getKey(key), secret);
|
||||
}
|
||||
|
||||
public get(key: string): string | undefined {
|
||||
return this.store.get(this.getKey(key));
|
||||
}
|
||||
|
||||
public delete(key: string): void {
|
||||
this.store.delete(this.getKey(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage writer for secrets using safeStorage.
|
||||
*/
|
||||
class SafeStorageWriter extends StorageWriter {
|
||||
public set(key: string, secret: string): void {
|
||||
this.store.set(this.getKey(key), safeStorage.encryptString(secret).toString("base64"));
|
||||
}
|
||||
|
||||
public get(key: string): string | undefined {
|
||||
const ciphertext = this.store.get<string, string | undefined>(this.getKey(key));
|
||||
if (ciphertext) {
|
||||
try {
|
||||
return safeStorage.decryptString(Buffer.from(ciphertext, "base64"));
|
||||
} catch (e) {
|
||||
console.error("Failed to decrypt secret", e);
|
||||
console.error("...ciphertext:", JSON.stringify(ciphertext));
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const enum Mode {
|
||||
Encrypted = "encrypted", // default
|
||||
AllowPlaintext = "allow-plaintext",
|
||||
ForcePlaintext = "force-plaintext",
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON-backed store for settings which need to be accessible by the main process.
|
||||
* Secrets are stored within the `safeStorage` object, encrypted with safeStorage.
|
||||
* Any secrets operations are blocked on Electron app ready emit, and keytar migration if still needed.
|
||||
*/
|
||||
class Store extends ElectronStore<StoreData> {
|
||||
private static internalInstance?: Store;
|
||||
|
||||
public static get instance(): Store | undefined {
|
||||
return Store.internalInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the store, does not prepare safeStorage, which needs to be done after the app is ready.
|
||||
* Must be executed in the first tick of the event loop so that it can call Electron APIs before ready state.
|
||||
*/
|
||||
public static initialize(mode: Mode | undefined): Store {
|
||||
if (Store.internalInstance) {
|
||||
throw new Error("Store already initialized");
|
||||
}
|
||||
|
||||
const store = new Store(mode ?? Mode.Encrypted);
|
||||
Store.internalInstance = store;
|
||||
|
||||
if (
|
||||
process.platform === "linux" &&
|
||||
(store.get("safeStorageBackendOverride") || store.get("safeStorageBackendMigrate"))
|
||||
) {
|
||||
const backend = store.get("safeStorageBackend")!;
|
||||
if (backend in safeStorageBackendMap) {
|
||||
// If the safeStorage backend which was used to write the data is one we can specify via the commandLine
|
||||
// then do so to ensure we use the same backend for reading the data.
|
||||
app.commandLine.appendSwitch(
|
||||
"password-store",
|
||||
safeStorageBackendMap[backend as keyof typeof safeStorageBackendMap],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
// Provides "raw" access to the underlying secrets storage,
|
||||
// should be avoided in favour of the getSecret/setSecret/deleteSecret methods.
|
||||
private secrets?: StorageWriter;
|
||||
|
||||
private constructor(private mode: Mode) {
|
||||
super({
|
||||
name: "electron-config",
|
||||
clearInvalidConfig: false,
|
||||
schema: {
|
||||
warnBeforeExit: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
minimizeToTray: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
spellCheckerEnabled: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
autoHideMenuBar: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
locale: {
|
||||
anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
|
||||
},
|
||||
disableHardwareAcceleration: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
enableContentProtection: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
safeStorage: {
|
||||
type: "object",
|
||||
},
|
||||
safeStorageBackend: {
|
||||
type: "string",
|
||||
},
|
||||
safeStorageBackendOverride: {
|
||||
type: "boolean",
|
||||
},
|
||||
safeStorageBackendMigrate: {
|
||||
type: "boolean",
|
||||
},
|
||||
openAtLoginMinimised: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private safeStorageReadyPromise?: Promise<boolean>;
|
||||
public async safeStorageReady(): Promise<void> {
|
||||
if (!this.safeStorageReadyPromise) {
|
||||
throw new Error("prepareSafeStorage must be called before using storage methods");
|
||||
}
|
||||
await this.safeStorageReadyPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the backend to a sane value (exclude `unknown`), respect forcePlaintext mode,
|
||||
* and ensure that if an encrypted backend is picked that encryption is available, falling back to plaintext if not.
|
||||
* @param forcePlaintext - whether to force plaintext mode
|
||||
* @private
|
||||
*/
|
||||
private chooseBackend(forcePlaintext: boolean): SaneSafeStorageBackend {
|
||||
if (forcePlaintext) {
|
||||
return "plaintext";
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
// The following enables plain text encryption if the backend used is basic_text.
|
||||
// It has no significance for any other backend.
|
||||
// We do this early so that in case we end up using the basic_text backend (either because that's the only one available
|
||||
// or as a fallback when the configured backend lacks encryption support), encryption is already turned on.
|
||||
safeStorage.setUsePlainTextEncryption(true);
|
||||
|
||||
// Linux safeStorage support is hellish, the support varies on the Desktop Environment used rather than the store itself.
|
||||
// https://github.com/electron/electron/issues/39789 https://github.com/microsoft/vscode/issues/185212
|
||||
const selectedBackend = safeStorage.getSelectedStorageBackend();
|
||||
|
||||
if (selectedBackend === "unknown" || !safeStorage.isEncryptionAvailable()) {
|
||||
return "plaintext";
|
||||
}
|
||||
return selectedBackend;
|
||||
}
|
||||
|
||||
return safeStorage.isEncryptionAvailable() ? "system" : "plaintext";
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the safeStorage backend for use.
|
||||
* We don't eagerly import from keytar as that would bring in data for all Element profiles and not just the current one,
|
||||
* so we import lazily in getSecret.
|
||||
*
|
||||
* This will relaunch the app in some cases, in which case it will return false and the caller should abort startup.
|
||||
*
|
||||
* @param electronSession - The Electron session to use for storage (will be used to clear storage if necessary).
|
||||
* @returns true if safeStorage was initialised successfully or false if the app will be relaunched
|
||||
*/
|
||||
public async prepareSafeStorage(electronSession: Session): Promise<boolean> {
|
||||
this.safeStorageReadyPromise = this.reallyPrepareSafeStorage(electronSession);
|
||||
return this.safeStorageReadyPromise;
|
||||
}
|
||||
|
||||
private async reallyPrepareSafeStorage(electronSession: Session): Promise<boolean> {
|
||||
await app.whenReady();
|
||||
|
||||
// The backend the existing data is written with if any
|
||||
let existingSafeStorageBackend = this.get("safeStorageBackend");
|
||||
// The backend and encryption status of the currently loaded backend
|
||||
const backend = this.chooseBackend(this.mode === Mode.ForcePlaintext);
|
||||
|
||||
// Handle migrations
|
||||
if (existingSafeStorageBackend) {
|
||||
if (existingSafeStorageBackend === "basic_text" && backend !== "plaintext" && backend !== "basic_text") {
|
||||
this.prepareMigrateBasicTextToPlaintext();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.get("safeStorageBackendMigrate") && backend === "basic_text") {
|
||||
this.migrateBasicTextToPlaintext();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (existingSafeStorageBackend === "plaintext" && backend !== "plaintext") {
|
||||
this.migratePlaintextToEncrypted();
|
||||
// Ensure we update existingSafeStorageBackend so we don't fall into the "backend changed" clause below
|
||||
existingSafeStorageBackend = this.get("safeStorageBackend");
|
||||
}
|
||||
}
|
||||
|
||||
if (!existingSafeStorageBackend) {
|
||||
// First launch of the app or first launch since the update
|
||||
if (this.mode === Mode.Encrypted && (backend === "plaintext" || backend === "basic_text")) {
|
||||
// Ask the user for consent to use a degraded mode
|
||||
await this.consultUserConsentDegradedMode(backend);
|
||||
}
|
||||
// Store the backend used for the safeStorage data so we can detect if it changes, and we know how the data is encoded
|
||||
this.recordSafeStorageBackend(backend);
|
||||
} else if (existingSafeStorageBackend !== backend) {
|
||||
// We already appear to have started using a backend other than the one that we picked, so
|
||||
// set the override flag and relaunch with the backend we were previously using, unless we
|
||||
// already have the override flag, in which case we must assume the previous backend is no
|
||||
// longer usable, in which case we should fall into the next block and warn the user we can't
|
||||
// migrate.
|
||||
console.warn(`safeStorage backend changed from ${existingSafeStorageBackend} to ${backend}`);
|
||||
|
||||
if (existingSafeStorageBackend in safeStorageBackendMap && !this.get("safeStorageBackendOverride")) {
|
||||
this.set("safeStorageBackendOverride", true);
|
||||
relaunchApp();
|
||||
return false;
|
||||
} else {
|
||||
// This will either relaunch the app or throw an execption
|
||||
await this.consultUserBackendChangedUnableToMigrate(electronSession);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
console.info(`Using storage mode '${this.mode}' with backend '${backend}'`);
|
||||
if (backend !== "plaintext") {
|
||||
this.secrets = new SafeStorageWriter(this);
|
||||
} else {
|
||||
this.secrets = new StorageWriter(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async consultUserBackendChangedUnableToMigrate(electronSession: Session): Promise<void> {
|
||||
const { response } = await dialog.showMessageBox({
|
||||
title: _t("store|error|backend_changed_title"),
|
||||
message: _t("store|error|backend_changed"),
|
||||
detail: _t("store|error|backend_changed_detail"),
|
||||
type: "question",
|
||||
buttons: [_t("common|no"), _t("common|yes")],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
});
|
||||
if (response === 0) {
|
||||
throw new Error("safeStorage backend changed and cannot migrate");
|
||||
}
|
||||
return clearDataAndRelaunch(electronSession);
|
||||
}
|
||||
|
||||
private async consultUserConsentDegradedMode(backend: "plaintext" | "basic_text"): Promise<void> {
|
||||
if (backend === "plaintext") {
|
||||
// Sometimes we may have a working backend that for some reason does not support encryption at the moment.
|
||||
// This may be because electron reported an incorrect backend or because of some known issues with the keyring itself.
|
||||
// Or the environment specified `--storage-mode=force-plaintext`.
|
||||
// In any case, when this happens, we give the user an option to use a weaker form of encryption.
|
||||
const { response } = await dialog.showMessageBox({
|
||||
title: _t("store|error|backend_no_encryption_title"),
|
||||
message: _t("store|error|backend_no_encryption"),
|
||||
detail: _t("store|error|backend_no_encryption_detail", {
|
||||
backend: safeStorage.getSelectedStorageBackend(),
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
}),
|
||||
type: "error",
|
||||
buttons: [_t("action|cancel"), _t("store|error|unsupported_keyring_use_plaintext")],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
});
|
||||
if (response === 0) {
|
||||
throw new Error("isEncryptionAvailable=false and user rejected plaintext");
|
||||
}
|
||||
} else {
|
||||
// Electron did not identify a compatible encrypted backend, ask user for consent to degraded mode
|
||||
const { response } = await dialog.showMessageBox({
|
||||
title: _t("store|error|unsupported_keyring_title"),
|
||||
message: _t("store|error|unsupported_keyring"),
|
||||
detail: _t("store|error|unsupported_keyring_detail", {
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
link: "https://www.electronjs.org/docs/latest/api/safe-storage#safestoragegetselectedstoragebackend-linux",
|
||||
}),
|
||||
type: "error",
|
||||
buttons: [_t("action|cancel"), _t("store|error|unsupported_keyring_use_basic_text")],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
});
|
||||
if (response === 0) {
|
||||
throw new Error("safeStorage backend basic_text and user rejected it");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private recordSafeStorageBackend(backend: SafeStorageBackend): void {
|
||||
this.set("safeStorageBackend", backend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Linux support for upgrading the backend from basic_text to one of the encrypted backends,
|
||||
* this is quite a tricky process as the backend is not known until the app is ready & cannot be changed once it is.
|
||||
* 1. We restart the app in safeStorageBackendMigrate mode
|
||||
* 2. Now that we are in the mode which our data is written in we decrypt the data, write it back in plaintext
|
||||
* & restart back in default backend mode,
|
||||
* 3. Finally, we load the plaintext data & encrypt it.
|
||||
*/
|
||||
private prepareMigrateBasicTextToPlaintext(): void {
|
||||
console.info(`Starting safeStorage migration to ${safeStorage.getSelectedStorageBackend()}`);
|
||||
this.set("safeStorageBackendMigrate", true);
|
||||
relaunchApp();
|
||||
}
|
||||
private migrateBasicTextToPlaintext(): void {
|
||||
const secrets = new SafeStorageWriter(this);
|
||||
console.info("Performing safeStorage migration");
|
||||
const data = this.get("safeStorage");
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
this.set(secrets.getKey(key), secrets.get(key));
|
||||
}
|
||||
this.recordSafeStorageBackend("plaintext");
|
||||
}
|
||||
this.delete("safeStorageBackendMigrate");
|
||||
relaunchApp();
|
||||
}
|
||||
private migratePlaintextToEncrypted(): void {
|
||||
const secrets = new SafeStorageWriter(this);
|
||||
const selectedSafeStorageBackend = safeStorage.getSelectedStorageBackend();
|
||||
console.info(`Finishing safeStorage migration to ${selectedSafeStorageBackend}`);
|
||||
const data = this.get("safeStorage");
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
secrets.set(key, data[key]);
|
||||
}
|
||||
}
|
||||
this.recordSafeStorageBackend(selectedSafeStorageBackend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored secret for the key.
|
||||
* Lazily migrates keys from keytar if they are not yet in the store.
|
||||
*
|
||||
* @param key The string key name.
|
||||
*
|
||||
* @returns A promise for the secret string.
|
||||
*/
|
||||
public async getSecret(key: string): Promise<string | undefined> {
|
||||
await this.safeStorageReady();
|
||||
let secret = this.secrets!.get(key);
|
||||
if (secret) return secret;
|
||||
|
||||
try {
|
||||
secret = await this.getSecretKeytar(key);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to read data from keytar with key='${key}'`, e);
|
||||
}
|
||||
if (secret) {
|
||||
console.debug("Migrating secret from keytar", key);
|
||||
this.secrets!.set(key, secret);
|
||||
}
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the secret for the key to the keychain.
|
||||
* We write to both safeStorage & keytar to support downgrading the application.
|
||||
*
|
||||
* @param key The string key name.
|
||||
* @param secret The string password.
|
||||
*
|
||||
* @returns A promise for the set password completion.
|
||||
*/
|
||||
public async setSecret(key: string, secret: string): Promise<void> {
|
||||
await this.safeStorageReady();
|
||||
this.secrets!.set(key, secret);
|
||||
try {
|
||||
await keytar.setPassword(KEYTAR_SERVICE, key, secret);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to write safeStorage backwards-compatibility key='${key}' data to keytar`, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the stored password for the key.
|
||||
* Removes from safeStorage, keytar & keytar legacy.
|
||||
*
|
||||
* @param key The string key name.
|
||||
*/
|
||||
public async deleteSecret(key: string): Promise<void> {
|
||||
await this.safeStorageReady();
|
||||
this.secrets!.delete(key);
|
||||
try {
|
||||
await this.deleteSecretKeytar(key);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to delete secret with key='${key}' from keytar`, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated will be removed in the near future
|
||||
*/
|
||||
private async getSecretKeytar(key: string): Promise<string | undefined> {
|
||||
return (
|
||||
(await keytar.getPassword(KEYTAR_SERVICE, key)) ??
|
||||
(await keytar.getPassword(LEGACY_KEYTAR_SERVICE, key)) ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated will be removed in the near future
|
||||
*/
|
||||
private async deleteSecretKeytar(key: string): Promise<void> {
|
||||
await keytar.deletePassword(LEGACY_KEYTAR_SERVICE, key);
|
||||
await keytar.deletePassword(KEYTAR_SERVICE, key);
|
||||
}
|
||||
}
|
||||
|
||||
export default Store;
|
||||
24
src/tray.ts
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2024-2025 New Vector Ltd.
|
||||
Copyright 2017 Karl Glatz <karl@glatz.biz>
|
||||
Copyright 2017 OpenMarket Ltd
|
||||
|
||||
@@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { app, Tray, Menu, nativeImage } from "electron";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import pngToIco from "png-to-ico";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
import { _t } from "./language-helper.js";
|
||||
|
||||
@@ -38,18 +38,13 @@ function toggleWin(): void {
|
||||
}
|
||||
}
|
||||
|
||||
interface IConfig {
|
||||
icon_path: string; // eslint-disable-line camelcase
|
||||
brand: string;
|
||||
}
|
||||
|
||||
function getUuid(): string {
|
||||
// The uuid field is optional and only needed on unsigned Windows packages where the executable path changes
|
||||
// The hardcoded uuid is an arbitrary v4 uuid generated on https://www.uuidgenerator.net/version4
|
||||
return global.vectorConfig["uuid"] || "eba84003-e499-4563-8e9d-166e34b5cc25";
|
||||
}
|
||||
|
||||
export function create(config: IConfig): void {
|
||||
export function create(config: (typeof global)["trayConfig"]): void {
|
||||
// no trays on darwin
|
||||
if (process.platform === "darwin" || trayIcon) return;
|
||||
const defaultIcon = nativeImage.createFromPath(config.icon_path);
|
||||
@@ -71,6 +66,7 @@ export function create(config: IConfig): void {
|
||||
initApplicationMenu();
|
||||
trayIcon.on("click", toggleWin);
|
||||
|
||||
// See also, badge.ts
|
||||
let lastFavicon: string | null = null;
|
||||
global.mainWindow?.webContents.on("page-favicon-updated", async function (ev, favicons) {
|
||||
if (!favicons || favicons.length <= 0 || !favicons[0].startsWith("data:")) {
|
||||
@@ -92,15 +88,17 @@ export function create(config: IConfig): void {
|
||||
if (process.platform === "win32") {
|
||||
try {
|
||||
const icoPath = path.join(app.getPath("temp"), "win32_element_icon.ico");
|
||||
fs.writeFileSync(icoPath, await pngToIco(newFavicon.toPNG()));
|
||||
await writeFile(icoPath, await pngToIco(newFavicon.toPNG()));
|
||||
newFavicon = nativeImage.createFromPath(icoPath);
|
||||
} catch (e) {
|
||||
console.error("Failed to make win32 ico", e);
|
||||
}
|
||||
// Always update the tray icon for Windows.
|
||||
trayIcon?.setImage(newFavicon);
|
||||
} else {
|
||||
trayIcon?.setImage(newFavicon);
|
||||
global.mainWindow?.setIcon(newFavicon);
|
||||
}
|
||||
|
||||
trayIcon?.setImage(newFavicon);
|
||||
global.mainWindow?.setIcon(newFavicon);
|
||||
});
|
||||
|
||||
global.mainWindow?.webContents.on("page-title-updated", function (ev, title) {
|
||||
|
||||
@@ -7,8 +7,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { app, autoUpdater, ipcMain } from "electron";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
|
||||
import { getSquirrelExecutable } from "./squirrelhooks.js";
|
||||
import { _t } from "./language-helper.js";
|
||||
import { initialisePromise } from "./ipc.js";
|
||||
|
||||
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
|
||||
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
|
||||
@@ -69,7 +72,8 @@ async function pollForUpdates(): Promise<void> {
|
||||
}
|
||||
|
||||
export async function start(updateBaseUrl: string): Promise<void> {
|
||||
if (!(await available(updateBaseUrl))) return;
|
||||
if (!(await available())) return;
|
||||
console.log(`Starting auto update with base URL: ${updateBaseUrl}`);
|
||||
if (!updateBaseUrl.endsWith("/")) {
|
||||
updateBaseUrl = updateBaseUrl + "/";
|
||||
}
|
||||
@@ -111,10 +115,15 @@ export async function start(updateBaseUrl: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function available(updateBaseUrl?: string): Promise<boolean> {
|
||||
/**
|
||||
* Check if auto update is available on this platform.
|
||||
* Has a side effect of firing showToast on EOL platforms so must only be called once!
|
||||
* @returns True if auto update is available
|
||||
*/
|
||||
async function available(): Promise<boolean> {
|
||||
if (process.platform === "linux") {
|
||||
// Auto update is not supported on Linux
|
||||
console.log("Auto update not supported on this platform");
|
||||
console.warn("Auto update not supported on this platform");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -122,13 +131,42 @@ async function available(updateBaseUrl?: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(getSquirrelExecutable());
|
||||
} catch {
|
||||
console.log("Squirrel not found, auto update not supported");
|
||||
console.warn("Squirrel not found, auto update not supported");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise we're either on macOS or Windows with Squirrel
|
||||
return !!updateBaseUrl;
|
||||
if (process.platform === "darwin") {
|
||||
// OS release returns the Darwin kernel version, not the macOS version, see
|
||||
// https://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history to interpret it
|
||||
const release = os.release();
|
||||
const major = parseInt(release.split(".")[0], 10);
|
||||
|
||||
if (major < 21) {
|
||||
// If the macOS version is too old for modern Electron support then disable auto update to prevent the app updating and bricking itself.
|
||||
// The oldest macOS version supported by Chromium/Electron 38 is Monterey (12.x) which started with Darwin 21.0
|
||||
initialisePromise.then(() => {
|
||||
ipcMain.emit("showToast", {
|
||||
title: _t("eol|title"),
|
||||
description: _t("eol|no_more_updates", { brand: global.trayConfig.brand }),
|
||||
});
|
||||
});
|
||||
console.warn("Auto update not supported, macOS version too old");
|
||||
return false;
|
||||
} else if (major < 22) {
|
||||
// If the macOS version is EOL then show a warning message.
|
||||
// The oldest macOS version still supported by Apple is Ventura (13.x) which started with Darwin 22.0
|
||||
initialisePromise.then(() => {
|
||||
ipcMain.emit("showToast", {
|
||||
title: _t("eol|title"),
|
||||
description: _t("eol|warning", { brand: global.trayConfig.brand }),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ipcMain.on("install_update", installUpdate);
|
||||
|
||||
13
src/utils.ts
@@ -23,10 +23,10 @@ export async function randomArray(size: number): Promise<string> {
|
||||
|
||||
type JsonValue = null | string | number;
|
||||
type JsonArray = Array<JsonValue | JsonObject | JsonArray>;
|
||||
interface JsonObject {
|
||||
export interface JsonObject {
|
||||
[key: string]: JsonObject | JsonArray | JsonValue;
|
||||
}
|
||||
type Json = JsonArray | JsonObject;
|
||||
export type Json = JsonArray | JsonObject;
|
||||
|
||||
/**
|
||||
* Synchronously load a JSON file from the local filesystem.
|
||||
@@ -34,6 +34,13 @@ type Json = JsonArray | JsonObject;
|
||||
* @param paths - An array of path segments which will be joined using the system's path delimiter.
|
||||
*/
|
||||
export function loadJsonFile<T extends Json>(...paths: string[]): T {
|
||||
const file = fs.readFileSync(path.join(...paths), { encoding: "utf-8" });
|
||||
const joinedPaths = path.join(...paths);
|
||||
|
||||
if (!fs.existsSync(joinedPaths)) {
|
||||
console.log(`Skipping nonexistent file: ${joinedPaths}`);
|
||||
return {} as T;
|
||||
}
|
||||
|
||||
const file = fs.readFileSync(joinedPaths, { encoding: "utf-8" });
|
||||
return JSON.parse(file);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"typeRoots": ["src/@types", "node_modules/@types"],
|
||||
"lib": ["es2022"],
|
||||
"lib": ["es2022", "es2024.promise"],
|
||||
"types": ["node"],
|
||||
"strict": true
|
||||
},
|
||||
|
||||