Compare commits
672 Commits
rav/bump_p
...
v1.11.72
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79b6a6fb69 | ||
|
|
181f91a7e8 | ||
|
|
9a3f00a621 | ||
|
|
5bd9529d37 | ||
|
|
630be696b1 | ||
|
|
7f66d65796 | ||
|
|
03e6280b6a | ||
|
|
88b3b922f9 | ||
|
|
d3f0af6fd1 | ||
|
|
9f27685a54 | ||
|
|
1f4006ac13 | ||
|
|
36ff766be3 | ||
|
|
ab54cbc329 | ||
|
|
f8701ee1fc | ||
|
|
1eea7fffbe | ||
|
|
8ecf47bce8 | ||
|
|
bc805d2989 | ||
|
|
4728503f66 | ||
|
|
419f37b31e | ||
|
|
a32aca434c | ||
|
|
73a8aad088 | ||
|
|
0273909a43 | ||
|
|
dd685934a3 | ||
|
|
5eebd54baf | ||
|
|
764654fffb | ||
|
|
a238990c25 | ||
|
|
655a1ae065 | ||
|
|
41d6fe3b23 | ||
|
|
a10d2b1828 | ||
|
|
af3fb76ecf | ||
|
|
d030a7d86f | ||
|
|
79163a1f16 | ||
|
|
9d1c724402 | ||
|
|
493e7f7f3e | ||
|
|
39bdcafbe2 | ||
|
|
df92294056 | ||
|
|
7a45ca2614 | ||
|
|
6d84073bec | ||
|
|
8e1561b1c7 | ||
|
|
0f01d7adbe | ||
|
|
579e9a3d3f | ||
|
|
167bcdf9d9 | ||
|
|
118f71ea3b | ||
|
|
7b4e00c72e | ||
|
|
35fdffb245 | ||
|
|
67fd02dc4a | ||
|
|
b1d94de413 | ||
|
|
a2589cab2e | ||
|
|
cfb800d916 | ||
|
|
4a25cc4b43 | ||
|
|
fd65226b1a | ||
|
|
722923befd | ||
|
|
fdefe624d2 | ||
|
|
2443ae118a | ||
|
|
bc27c1dcc3 | ||
|
|
be4f779150 | ||
|
|
d584dc0d52 | ||
|
|
e6a60e0770 | ||
|
|
31032525c9 | ||
|
|
42f1ae1dde | ||
|
|
fbd1e4f731 | ||
|
|
3091db7bb1 | ||
|
|
99553210be | ||
|
|
8fc13ba4b6 | ||
|
|
5722f074d1 | ||
|
|
4f718cb7d3 | ||
|
|
6452f27cb6 | ||
|
|
700886c0e2 | ||
|
|
668450325c | ||
|
|
b4aa375619 | ||
|
|
be92e64458 | ||
|
|
4d0c740ad2 | ||
|
|
438d07bc75 | ||
|
|
b4d343053d | ||
|
|
88d507f4f9 | ||
|
|
8e03b273ae | ||
|
|
4ffc77dfef | ||
|
|
e4d138f4d1 | ||
|
|
746c20bdba | ||
|
|
cd2bd81268 | ||
|
|
561868f12a | ||
|
|
833072f753 | ||
|
|
d1f0cbea27 | ||
|
|
9353b27ce7 | ||
|
|
60b3b95603 | ||
|
|
944516c2f7 | ||
|
|
e80ab738ce | ||
|
|
d60768daec | ||
|
|
a7ace95eac | ||
|
|
cdb65233b0 | ||
|
|
83186c0403 | ||
|
|
8776371ed4 | ||
|
|
5f9637b14e | ||
|
|
c924f59f41 | ||
|
|
c6134b1849 | ||
|
|
818ec7529c | ||
|
|
7c7cbe655a | ||
|
|
d379103352 | ||
|
|
844a318eb2 | ||
|
|
205f0f9f30 | ||
|
|
703ad190f8 | ||
|
|
1237fbf74c | ||
|
|
137b20b784 | ||
|
|
5de07e091f | ||
|
|
f3237fb0b0 | ||
|
|
e41b476be3 | ||
|
|
4506988ad6 | ||
|
|
734e698c7e | ||
|
|
ba9f962570 | ||
|
|
1dca6b60c3 | ||
|
|
4e47b75f0f | ||
|
|
55634304b0 | ||
|
|
eb2ff67e7b | ||
|
|
c5d04b3649 | ||
|
|
0845c515c4 | ||
|
|
76416ec7b9 | ||
|
|
b02a418cbb | ||
|
|
039b2d421f | ||
|
|
a26818ee3b | ||
|
|
e3aee58a6d | ||
|
|
ea158ed63f | ||
|
|
38c0bf3b62 | ||
|
|
6821a35444 | ||
|
|
9f9cd6ff08 | ||
|
|
f8b3be682a | ||
|
|
ab5ff85bff | ||
|
|
aa9851dc4e | ||
|
|
437b5241b9 | ||
|
|
42943f3ffc | ||
|
|
2fd4e474cb | ||
|
|
cf1eddf0ea | ||
|
|
cee4a754d7 | ||
|
|
72354161e2 | ||
|
|
fa1d2c7dfb | ||
|
|
42c0c19556 | ||
|
|
d3a0925e42 | ||
|
|
351a31c9ee | ||
|
|
2614cab64b | ||
|
|
15db387425 | ||
|
|
53cc1c78b1 | ||
|
|
be6528de26 | ||
|
|
087f1bc948 | ||
|
|
4360f5a63e | ||
|
|
65a87f1a53 | ||
|
|
b0c04834b6 | ||
|
|
b61d5b4475 | ||
|
|
6c3817fa34 | ||
|
|
e151d9ab53 | ||
|
|
f15b188297 | ||
|
|
ea16928d35 | ||
|
|
4651bf4eb4 | ||
|
|
5442d4050d | ||
|
|
695cc1b28b | ||
|
|
c94e3f00ed | ||
|
|
79e0661be3 | ||
|
|
6cf0809064 | ||
|
|
a0eb94704e | ||
|
|
e1e0d37478 | ||
|
|
9039e70990 | ||
|
|
596f297094 | ||
|
|
4f92927a3d | ||
|
|
b77218e65a | ||
|
|
c21e9f6383 | ||
|
|
4f8a8a2543 | ||
|
|
d615e69a86 | ||
|
|
db1d440e3b | ||
|
|
24fafe2588 | ||
|
|
1e06e3083f | ||
|
|
8e9f3a786c | ||
|
|
95f134a7c5 | ||
|
|
50f5c15027 | ||
|
|
65b43d8dc7 | ||
|
|
8dad4f5f10 | ||
|
|
a3a436ad64 | ||
|
|
ffb69d4c47 | ||
|
|
369ae1208e | ||
|
|
050dae7a9f | ||
|
|
a9cd567fc9 | ||
|
|
82073aea3e | ||
|
|
9cf7b8b6f6 | ||
|
|
fa5bd19368 | ||
|
|
fb72597fcf | ||
|
|
3ce0d78961 | ||
|
|
361636a3d1 | ||
|
|
0b49a7724b | ||
|
|
2db2cf2b1b | ||
|
|
c38505598f | ||
|
|
7e21f12678 | ||
|
|
4f43c57a96 | ||
|
|
60d3b59076 | ||
|
|
5a7956fd90 | ||
|
|
e193f57ac7 | ||
|
|
30f964447f | ||
|
|
cb9eccebed | ||
|
|
a0ae97c2e3 | ||
|
|
fd62e04bc0 | ||
|
|
e6c0b8d8e5 | ||
|
|
67e78a56a6 | ||
|
|
153d0c36b8 | ||
|
|
18bfe3cb43 | ||
|
|
61ba9b48f9 | ||
|
|
5e02a42ca0 | ||
|
|
89622749e2 | ||
|
|
eced524029 | ||
|
|
c4263b3330 | ||
|
|
68f4a7d4b0 | ||
|
|
7df5028c51 | ||
|
|
4de49c76d0 | ||
|
|
165cbcda89 | ||
|
|
c11bfaf07e | ||
|
|
98babd140e | ||
|
|
575a726361 | ||
|
|
ed2440f8cf | ||
|
|
59592f84fb | ||
|
|
ab690372d2 | ||
|
|
571028f124 | ||
|
|
36391187da | ||
|
|
35f2f5055a | ||
|
|
e2c974f53b | ||
|
|
74172969c1 | ||
|
|
6db392025c | ||
|
|
a61de47bb6 | ||
|
|
bcd5c838e8 | ||
|
|
482b81b0ed | ||
|
|
095c613b69 | ||
|
|
5eed034389 | ||
|
|
7a3bc83a55 | ||
|
|
706f4c6e20 | ||
|
|
f43bb0a9f7 | ||
|
|
64f0bb5460 | ||
|
|
e651330ff1 | ||
|
|
6a6a362a8d | ||
|
|
35046bb7b2 | ||
|
|
a10f2e1258 | ||
|
|
a0d7805484 | ||
|
|
ae77dac66d | ||
|
|
5f1f018a30 | ||
|
|
f2da5fcf2c | ||
|
|
9cce1e060a | ||
|
|
50c8cb7bf0 | ||
|
|
58e5b3959d | ||
|
|
84b6bf9789 | ||
|
|
9efa04da7a | ||
|
|
8ba3f53d2c | ||
|
|
36571edb95 | ||
|
|
3a159beb21 | ||
|
|
6ebe844fc0 | ||
|
|
7359c9bf93 | ||
|
|
02fd35dad0 | ||
|
|
a71b7f0e66 | ||
|
|
f0672082b3 | ||
|
|
2ee54dd4bc | ||
|
|
e852282270 | ||
|
|
80a7ea7649 | ||
|
|
9b988f70de | ||
|
|
27d4e1d990 | ||
|
|
f7132ff139 | ||
|
|
b82100ede8 | ||
|
|
729534401f | ||
|
|
dc85c9fb13 | ||
|
|
2c8b32d2c9 | ||
|
|
011f7465e7 | ||
|
|
4561b2d35d | ||
|
|
5dcaf8ab80 | ||
|
|
ac20fffb40 | ||
|
|
aeef12ef8b | ||
|
|
73f2dba0c3 | ||
|
|
ea56d6774c | ||
|
|
0937c161f4 | ||
|
|
ad31575ddc | ||
|
|
3fc2cdfbab | ||
|
|
ba186a22d0 | ||
|
|
8449bdc5c1 | ||
|
|
686816b8a4 | ||
|
|
6d457e3559 | ||
|
|
91e9891892 | ||
|
|
c8e866e384 | ||
|
|
f5997aaf7c | ||
|
|
9a2066d98a | ||
|
|
6e59f6f413 | ||
|
|
7e8177edb3 | ||
|
|
d3deda62a8 | ||
|
|
f990918e49 | ||
|
|
9ce7b19147 | ||
|
|
786c590dcf | ||
|
|
bbf37ba7cf | ||
|
|
c50556a957 | ||
|
|
4a895f6daf | ||
|
|
7f5af1ccdf | ||
|
|
eac221c2d3 | ||
|
|
c5b98a5e93 | ||
|
|
88c49e7c50 | ||
|
|
3cb5b840ce | ||
|
|
e0deabceb3 | ||
|
|
2e15e5e3c7 | ||
|
|
050e9126d8 | ||
|
|
3b52d375e7 | ||
|
|
a1c6f0a1d2 | ||
|
|
4bd351f167 | ||
|
|
5bed03f37e | ||
|
|
315ee8a64d | ||
|
|
f51622a507 | ||
|
|
ce95e1abcd | ||
|
|
70e480a3e9 | ||
|
|
6ca67ff1ef | ||
|
|
ff63c92c2c | ||
|
|
36ff005d11 | ||
|
|
38ea3312f6 | ||
|
|
8c61026761 | ||
|
|
5d32728f3e | ||
|
|
159ef4cc15 | ||
|
|
8baab4ce8e | ||
|
|
491ad6de17 | ||
|
|
f62076cdc1 | ||
|
|
674650f24d | ||
|
|
9f5bd7bf66 | ||
|
|
fa5a842257 | ||
|
|
a2f1c917bc | ||
|
|
b4bff9f3b2 | ||
|
|
8ce46d3de8 | ||
|
|
2cad731f23 | ||
|
|
1cf08423ad | ||
|
|
b64ac0593e | ||
|
|
f64759e42e | ||
|
|
d0b192dbc8 | ||
|
|
fc90392982 | ||
|
|
180a1a243b | ||
|
|
5246a7824b | ||
|
|
2dffd41c6f | ||
|
|
2f72796ae7 | ||
|
|
364ad021e4 | ||
|
|
a187b54707 | ||
|
|
15c177d8b3 | ||
|
|
773e9bb015 | ||
|
|
dbf2e8c927 | ||
|
|
e794ac5ecb | ||
|
|
f4d64dcb39 | ||
|
|
a1404722ba | ||
|
|
9167d4ac56 | ||
|
|
c3376efa8d | ||
|
|
2ad9c5685e | ||
|
|
0565e614d3 | ||
|
|
1126956d56 | ||
|
|
aec32bbc35 | ||
|
|
424243c064 | ||
|
|
84f293fee2 | ||
|
|
39f51612f5 | ||
|
|
f4155fa7c4 | ||
|
|
c2fdd04681 | ||
|
|
c5da65ba60 | ||
|
|
65165247c0 | ||
|
|
e89ba979cf | ||
|
|
cf605b5e6f | ||
|
|
1984ba3b33 | ||
|
|
43db5a8731 | ||
|
|
7c9f521f65 | ||
|
|
9848bf8246 | ||
|
|
b0d8d4bbdb | ||
|
|
4cd14b58e1 | ||
|
|
2853a23679 | ||
|
|
bd931ebd74 | ||
|
|
e2b5cea7df | ||
|
|
2fb41f19a7 | ||
|
|
0cd36558c5 | ||
|
|
f835c678bf | ||
|
|
44a2666183 | ||
|
|
1b81c276fc | ||
|
|
309bb7eb47 | ||
|
|
44fff3b45c | ||
|
|
7498afcb33 | ||
|
|
9091c074a8 | ||
|
|
50a62f6105 | ||
|
|
ef29e98bdc | ||
|
|
814aa778ea | ||
|
|
2723a23ea7 | ||
|
|
23bc0d7f3d | ||
|
|
573478a630 | ||
|
|
271524e264 | ||
|
|
18446de298 | ||
|
|
a6a901109b | ||
|
|
7effb12bbe | ||
|
|
b625a8f41f | ||
|
|
786cd12636 | ||
|
|
122ecb05c9 | ||
|
|
f07ea3d2cc | ||
|
|
37612eb41a | ||
|
|
24e17182b3 | ||
|
|
7a8a72b1e8 | ||
|
|
926d944c2a | ||
|
|
ddc22578f6 | ||
|
|
8f24d68b53 | ||
|
|
5a97ad33a2 | ||
|
|
954589df1b | ||
|
|
7c9e37a2c6 | ||
|
|
913be315f3 | ||
|
|
830e7c5c45 | ||
|
|
d6671cca75 | ||
|
|
38dbedcd85 | ||
|
|
4e492da1e4 | ||
|
|
5cfacc799d | ||
|
|
31d44b4c46 | ||
|
|
ba2336ac5c | ||
|
|
33ea45301e | ||
|
|
02a315814f | ||
|
|
5fe009d094 | ||
|
|
8082d07e7b | ||
|
|
559c3788ac | ||
|
|
54ba0873da | ||
|
|
28038cc53e | ||
|
|
97c3da3789 | ||
|
|
40a6a4296b | ||
|
|
c5168ad100 | ||
|
|
89e455e9a7 | ||
|
|
b68ff2fd88 | ||
|
|
00abe771bf | ||
|
|
1a2e1474f4 | ||
|
|
d0b87400d1 | ||
|
|
0977c3839a | ||
|
|
cd7bd1b253 | ||
|
|
11b7c72e7d | ||
|
|
ddec6aed59 | ||
|
|
3f8ce55e8b | ||
|
|
43467fc7c5 | ||
|
|
7b9d7e37e4 | ||
|
|
072bc7fc8c | ||
|
|
8d04ce4255 | ||
|
|
b97f4b31fa | ||
|
|
a0c90f9657 | ||
|
|
9b01f375e9 | ||
|
|
0539c99cf8 | ||
|
|
842fa767b8 | ||
|
|
aa427ecaf3 | ||
|
|
5e72da399e | ||
|
|
1c562a0d2d | ||
|
|
f01d69f90b | ||
|
|
c9d40992a1 | ||
|
|
986ea30ef5 | ||
|
|
110e5163e0 | ||
|
|
26999ee978 | ||
|
|
ed2d6d68bb | ||
|
|
f530e7e4b4 | ||
|
|
b428f4f483 | ||
|
|
4a6ac5f8d2 | ||
|
|
5360dfa9a4 | ||
|
|
ca93577cd1 | ||
|
|
e5e536a279 | ||
|
|
1f181f1c0a | ||
|
|
66ba7054f9 | ||
|
|
5ee2f5c9c0 | ||
|
|
a7e3cb8c2c | ||
|
|
a8e9746968 | ||
|
|
91d3d9f910 | ||
|
|
3c41b70932 | ||
|
|
d1d909105f | ||
|
|
831eddfbd4 | ||
|
|
16f197eda8 | ||
|
|
946f35577e | ||
|
|
7856a2aac3 | ||
|
|
617dd777a0 | ||
|
|
4d52fe0334 | ||
|
|
8b2cd6caa8 | ||
|
|
f3f249194b | ||
|
|
4a014ea056 | ||
|
|
494f9eccf3 | ||
|
|
f8a2e41df3 | ||
|
|
f601f0b889 | ||
|
|
67898d8ba7 | ||
|
|
a440a57945 | ||
|
|
276afcd02f | ||
|
|
e876c45581 | ||
|
|
4a77466cae | ||
|
|
f41d46866d | ||
|
|
282d71730b | ||
|
|
465131a836 | ||
|
|
432bd3421d | ||
|
|
64cec89085 | ||
|
|
f502a4049a | ||
|
|
ade8f795b2 | ||
|
|
c022ae2853 | ||
|
|
e35d040cc6 | ||
|
|
b44719aa67 | ||
|
|
03b7aeee75 | ||
|
|
ad696bceb7 | ||
|
|
e03b52b946 | ||
|
|
1ead4e1f4c | ||
|
|
4f5e1bb5ee | ||
|
|
3e38fd749d | ||
|
|
4b093489dd | ||
|
|
1d429cca6b | ||
|
|
6e5f2ad66e | ||
|
|
31a0dcab4d | ||
|
|
8a71a98119 | ||
|
|
ab0c396705 | ||
|
|
0788253645 | ||
|
|
3b4bc60041 | ||
|
|
0bf0794956 | ||
|
|
4126d6807f | ||
|
|
4fb185ad70 | ||
|
|
8559fb3eaa | ||
|
|
36002f6d76 | ||
|
|
fa1ff8f566 | ||
|
|
0e7ee62872 | ||
|
|
bf99ab262f | ||
|
|
f372a924b5 | ||
|
|
854a81056b | ||
|
|
3ac096619b | ||
|
|
fea4ae5939 | ||
|
|
4bd9f41f9e | ||
|
|
77b9cc18cc | ||
|
|
35fe447aae | ||
|
|
7da5e48d89 | ||
|
|
88d6340b37 | ||
|
|
588094339a | ||
|
|
18b9052c57 | ||
|
|
9f450c877d | ||
|
|
de46ba01e3 | ||
|
|
c47f8c8fa8 | ||
|
|
c73548ccbd | ||
|
|
b40db8a89b | ||
|
|
aa5024adae | ||
|
|
c19737b387 | ||
|
|
92c831094d | ||
|
|
bb337a1cd8 | ||
|
|
6ca50e537e | ||
|
|
3e84bb3a6e | ||
|
|
2a8b00f928 | ||
|
|
3e5c054243 | ||
|
|
fab68c1c5d | ||
|
|
ee72d0de75 | ||
|
|
09dff6ab05 | ||
|
|
7622dd35c5 | ||
|
|
c64af5671a | ||
|
|
313753483a | ||
|
|
502a00dba1 | ||
|
|
fa8ae283eb | ||
|
|
1d8fb46580 | ||
|
|
3b7fd88ef0 | ||
|
|
def12eb7d5 | ||
|
|
ddae8f0b6d | ||
|
|
2e882b138a | ||
|
|
c1ff276828 | ||
|
|
d248fa9ea0 | ||
|
|
b0bb33cccf | ||
|
|
fa7ff50482 | ||
|
|
8892bd659c | ||
|
|
ca2016d041 | ||
|
|
f4f78af477 | ||
|
|
d658103530 | ||
|
|
32d4d33e3e | ||
|
|
9144983794 | ||
|
|
a80895074b | ||
|
|
48b5eddd79 | ||
|
|
a21a1cad1f | ||
|
|
e46c4d06c3 | ||
|
|
92b3fb1e5e | ||
|
|
a41cf2140c | ||
|
|
037996711e | ||
|
|
8f9cf862fe | ||
|
|
0266f73965 | ||
|
|
01c64013c2 | ||
|
|
fc04258231 | ||
|
|
010d835d2c | ||
|
|
fcec34f4e7 | ||
|
|
1162820af9 | ||
|
|
9511d46a83 | ||
|
|
145f551cd3 | ||
|
|
7c584df3e8 | ||
|
|
46415c3439 | ||
|
|
c512e49639 | ||
|
|
0cd4a4faac | ||
|
|
e0abf63d87 | ||
|
|
e1715e2c00 | ||
|
|
e6543bbde6 | ||
|
|
96bcde4e35 | ||
|
|
285ba42316 | ||
|
|
aaeb1081c7 | ||
|
|
8af9fe2e27 | ||
|
|
6e1f842351 | ||
|
|
b90966fd1c | ||
|
|
eb93fb6ab5 | ||
|
|
4c4331193e | ||
|
|
cee3bfc031 | ||
|
|
7f0434523f | ||
|
|
1f46d61c1b | ||
|
|
7285773737 | ||
|
|
3ebe9f4196 | ||
|
|
58059d1f0a | ||
|
|
3000a48907 | ||
|
|
c02f153c2a | ||
|
|
feba18abd4 | ||
|
|
41576e41ca | ||
|
|
8ab44c98b2 | ||
|
|
e7ce9b4751 | ||
|
|
db78839e86 | ||
|
|
0aed37a2e5 | ||
|
|
19ed4a86c6 | ||
|
|
71840ee63c | ||
|
|
cde1e1ecb8 | ||
|
|
3fe0325eab | ||
|
|
5dd1ed9aac | ||
|
|
3867576137 | ||
|
|
e2371c0244 | ||
|
|
bfe40d6a1b | ||
|
|
927b3165ab | ||
|
|
5b6f5e8c4c | ||
|
|
c5bf7f405c | ||
|
|
bb3954c235 | ||
|
|
1669477d87 | ||
|
|
9ca55b76b0 | ||
|
|
243515a323 | ||
|
|
feab5057bd | ||
|
|
f10214a9dd | ||
|
|
e5d0c2ae7b | ||
|
|
62b65744e5 | ||
|
|
74d52f535a | ||
|
|
a410f96802 | ||
|
|
6d9efbab7a | ||
|
|
0afcc88763 | ||
|
|
8afab3c3f6 | ||
|
|
872b7f5110 | ||
|
|
9a1956c187 | ||
|
|
1ee644aab1 | ||
|
|
c2fa3d8f04 | ||
|
|
d7ab867af3 | ||
|
|
500f3485e0 | ||
|
|
97412e5e12 | ||
|
|
03cf7ca698 | ||
|
|
cc6b519d98 | ||
|
|
4b36e06cfe | ||
|
|
3f578ad80a | ||
|
|
7cd2f78e3a | ||
|
|
d6437b2db0 | ||
|
|
e938d8d0f4 | ||
|
|
3582e2a3ae | ||
|
|
3c31f55e87 | ||
|
|
9526a94ffc | ||
|
|
3a97c74074 | ||
|
|
9ea88438f9 | ||
|
|
d5047b708f | ||
|
|
fa3d8341a9 | ||
|
|
b91d0a6a5a | ||
|
|
04a2d3c9ba | ||
|
|
75aefff539 | ||
|
|
18fee2e650 | ||
|
|
ee5a3ce4a9 | ||
|
|
dee2a8ca29 | ||
|
|
e5a7bb89e2 | ||
|
|
c02af77655 | ||
|
|
d3ff627134 | ||
|
|
e73eba5b28 | ||
|
|
96bbe07e22 | ||
|
|
136a41bf7b | ||
|
|
0e0f5f06c2 | ||
|
|
4101d36bfb | ||
|
|
5c66bc860e | ||
|
|
91a09d97b1 | ||
|
|
ded9d6b55d | ||
|
|
4032304f01 | ||
|
|
a5caa5055e | ||
|
|
3f159934e4 | ||
|
|
436790cfd8 | ||
|
|
a012fb282f | ||
|
|
68fec606f2 | ||
|
|
03a624f788 | ||
|
|
922f16812c | ||
|
|
9ee7440dfd | ||
|
|
2d26c46c1c | ||
|
|
e3522d221a | ||
|
|
c6182b263d | ||
|
|
b98289c660 | ||
|
|
85dbaaa5ff | ||
|
|
29c176b521 |
@@ -88,6 +88,7 @@ module.exports = {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,17 +2,7 @@
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Tests written for new code (and old code if feasible)
|
||||
- [ ] Linter and other CI checks pass
|
||||
- [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/element-hq/element-web/blob/develop/CONTRIBUTING.md))
|
||||
|
||||
<!--
|
||||
If you would like to specify text for the changelog entry other than your PR title, add the following:
|
||||
|
||||
Notes: Add super cool feature
|
||||
|
||||
For PRs which *only* affect the desktop version, please use:
|
||||
|
||||
Notes: none
|
||||
element-desktop notes: Add super cool feature
|
||||
-->
|
||||
- [ ] Tests written for new code (and old code if feasible).
|
||||
- [ ] New or updated `public`/`exported` symbols have accurate [TSDoc](https://tsdoc.org/) documentation.
|
||||
- [ ] Linter and other CI checks pass.
|
||||
- [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/element-hq/element-web/blob/develop/CONTRIBUTING.md)).
|
||||
|
||||
3
.github/cfp_headers
vendored
3
.github/cfp_headers
vendored
@@ -11,3 +11,6 @@
|
||||
|
||||
/apple-app-site-association
|
||||
Content-Type: application/json
|
||||
|
||||
/.well-known/assetlinks.json
|
||||
Content-Type: application/json
|
||||
|
||||
6
.github/labels.yml
vendored
6
.github/labels.yml
vendored
@@ -226,6 +226,9 @@
|
||||
- name: "Z-Fixed by Element Call"
|
||||
description: "Issues which can be closed when we move to Element Call"
|
||||
color: "ededed"
|
||||
- name: "Z-Fixed-By-OIDC"
|
||||
description: "Issues which can be closed when we move to OIDC"
|
||||
color: "ededed"
|
||||
- name: "Z-Flaky-Test"
|
||||
description: "A test is raising false alarms"
|
||||
color: "ededed"
|
||||
@@ -258,3 +261,6 @@
|
||||
color: "ededed"
|
||||
- name: "Z-t3chguy"
|
||||
color: "ededed"
|
||||
- name: "Z-Flaky-Test-Disabled"
|
||||
description: "The flaking test has been disabled"
|
||||
color: "ededed"
|
||||
|
||||
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -3,6 +3,8 @@ on:
|
||||
pull_request: {}
|
||||
push:
|
||||
branches: [develop, master]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
# develop pushes and repository_dispatch handled in build_develop.yaml
|
||||
env:
|
||||
# These must be set for fetchdep.sh to get the right branch
|
||||
@@ -35,6 +37,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
# Workaround for yarn install timeouts, especially on Windows
|
||||
- run: yarn config set network-timeout 300000
|
||||
|
||||
2
.github/workflows/build_debian.yaml
vendored
2
.github/workflows/build_debian.yaml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog
|
||||
dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: element-web.deb
|
||||
path: element-web.deb
|
||||
|
||||
14
.github/workflows/build_develop.yml
vendored
14
.github/workflows/build_develop.yml
vendored
@@ -26,6 +26,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: "./scripts/layered.sh"
|
||||
@@ -47,7 +48,7 @@ jobs:
|
||||
|
||||
- run: mv dist/element-*.tar.gz dist/develop.tar.gz
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: webapp
|
||||
path: dist/develop.tar.gz
|
||||
@@ -84,14 +85,21 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
|
||||
|
||||
# We may be trying to deploy the same webapp bundles again, we need to ensure that the live bundles
|
||||
# are not present in the _redirects file and instead accessed directly from Cloudflare Pages.
|
||||
- name: Trim _redirects
|
||||
working-directory: _deploy
|
||||
run: |
|
||||
find bundles -type d -mindepth 1 -maxdepth 1 -exec sed -i "\:{}:d" _redirects \;
|
||||
|
||||
- name: Wait for other steps to succeed
|
||||
uses: t3chguy/wait-on-check-action@05861d3a448898eb33dfce34153bd1ecb9422fb9 # fork
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
running-workflow-name: "Build & Deploy develop.element.io"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release).)*$
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages).)*$
|
||||
|
||||
# We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier
|
||||
# as the expires after 24h and requires auth to download.
|
||||
|
||||
30
.github/workflows/dockerhub.yaml
vendored
30
.github/workflows/dockerhub.yaml
vendored
@@ -7,6 +7,9 @@ on:
|
||||
# This job can take a while, and we have usage limits, so just publish develop only twice a day
|
||||
- cron: "0 7/12 * * *"
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
|
||||
permissions:
|
||||
id-token: write # needed for signing the images with GitHub OIDC Token
|
||||
jobs:
|
||||
buildx:
|
||||
name: Docker Buildx
|
||||
@@ -26,27 +29,30 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # needed for docker-package to be able to calculate the version
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3
|
||||
|
||||
- name: Prepare
|
||||
if: matrix.prepare
|
||||
run: ${{ matrix.prepare }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3
|
||||
uses: docker/setup-qemu-action@5927c834f5b4fdf503fca6f4c7eccda82949e1ee # v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3
|
||||
uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3
|
||||
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@31cebacef4805868f9ce9a0cb03ee36c32df2ac4 # v5
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5
|
||||
with:
|
||||
images: |
|
||||
vectorim/element-web
|
||||
@@ -58,7 +64,8 @@ jobs:
|
||||
${{ matrix.flavor }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -66,9 +73,20 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Sign the images with GitHub OIDC Token
|
||||
env:
|
||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
||||
TAGS: ${{ steps.meta.outputs.tags }}
|
||||
run: |
|
||||
images=""
|
||||
for tag in ${TAGS}; do
|
||||
images+="${tag}@${DIGEST} "
|
||||
done
|
||||
cosign sign --yes ${images}
|
||||
|
||||
- name: Update repo description
|
||||
if: matrix.variant == 'vanilla'
|
||||
uses: peter-evans/dockerhub-description@dc67fad7001ef9e8e3c124cb7a64e16d0a63d864 # v3
|
||||
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
|
||||
7
.github/workflows/docs.yml
vendored
7
.github/workflows/docs.yml
vendored
@@ -46,6 +46,7 @@ jobs:
|
||||
with:
|
||||
cache: "yarn"
|
||||
cache-dependency-path: element-web/yarn.lock
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Generate automations docs
|
||||
working-directory: element-web
|
||||
@@ -55,7 +56,7 @@ jobs:
|
||||
echo "- [Automations](automations.md)" >> docs/SUMMARY.md
|
||||
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
uses: peaceiris/actions-mdbook@v2
|
||||
with:
|
||||
mdbook-version: "0.4.10"
|
||||
|
||||
@@ -99,7 +100,7 @@ jobs:
|
||||
run: mdbook build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v2
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./book
|
||||
|
||||
@@ -112,4 +113,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v3
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
29
.github/workflows/end-to-end-tests.yaml
vendored
Normal file
29
.github/workflows/end-to-end-tests.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# Triggers after the "Downstream artifacts" build has finished, to run the
|
||||
# matrix-react-sdk playwright tests (with access to repo secrets)
|
||||
|
||||
name: matrix-react-sdk End to End Tests
|
||||
on:
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
pull_request: {}
|
||||
push:
|
||||
branches: [develop, master]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event.workflow_run.event == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
playwright:
|
||||
name: Playwright
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/end-to-end-tests.yaml@develop
|
||||
permissions:
|
||||
actions: read
|
||||
issues: read
|
||||
pull-requests: read
|
||||
with:
|
||||
element-web-sha: ${{ github.sha }}
|
||||
react-sdk-repository: matrix-org/matrix-react-sdk
|
||||
# We only want to run the playwright tests on merge queue to prevent regressions
|
||||
# from creeping in. They take a long time to run and consume multiple concurrent runners.
|
||||
skip: ${{ github.event_name != 'merge_group' }}
|
||||
2
.github/workflows/pull_request.yaml
vendored
2
.github/workflows/pull_request.yaml
vendored
@@ -2,6 +2,8 @@ name: Pull Request
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, edited, labeled, unlabeled, synchronize]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
jobs:
|
||||
action:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
|
||||
|
||||
18
.github/workflows/release-drafter.yml
vendored
18
.github/workflows/release-drafter.yml
vendored
@@ -2,20 +2,10 @@ name: Release Drafter
|
||||
on:
|
||||
push:
|
||||
branches: [staging]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
previous-version:
|
||||
description: What release to use as a base for release note purposes
|
||||
required: false
|
||||
type: string
|
||||
workflow_dispatch: {}
|
||||
concurrency: ${{ github.workflow }}
|
||||
jobs:
|
||||
draft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@e64b19c4c46173209ed9f2e5a2f4ca7de89a0e86 # v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
disable-autolabeler: true
|
||||
previous-version: ${{ inputs.previous-version }}
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/release-drafter-workflow.yml@develop
|
||||
with:
|
||||
include-changes: matrix-react-sdk
|
||||
|
||||
51
.github/workflows/release.yml
vendored
51
.github/workflows/release.yml
vendored
@@ -10,16 +10,6 @@ on:
|
||||
options:
|
||||
- rc
|
||||
- final
|
||||
matrix-react-sdk:
|
||||
description: React SDK version to use (current|X.Y.Z)
|
||||
required: false
|
||||
default: current
|
||||
type: string
|
||||
matrix-js-sdk:
|
||||
description: JS SDK version to use (current|X.Y.Z)
|
||||
required: false
|
||||
default: current
|
||||
type: string
|
||||
concurrency: ${{ github.workflow }}
|
||||
jobs:
|
||||
release:
|
||||
@@ -30,10 +20,43 @@ jobs:
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
with:
|
||||
final: ${{ inputs.mode == 'final' }}
|
||||
include-changes: matrix-react-sdk
|
||||
gpg-fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
||||
asset-path: dist/*.tar.gz
|
||||
expected-asset-count: 3
|
||||
dependencies: |
|
||||
matrix-react-sdk=${{ inputs.matrix-react-sdk }}
|
||||
matrix-js-sdk=${{ inputs.matrix-js-sdk }}
|
||||
|
||||
notify-downstream:
|
||||
name: Trigger release drafter downstream
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify element-desktop repo that element-web release has completed to re-trigger release-drafter
|
||||
uses: benc-uk/workflow-dispatch@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1
|
||||
with:
|
||||
workflow: release-drafter.yml
|
||||
repo: element-hq/element-desktop
|
||||
ref: staging
|
||||
# Required when using the `repo` option. Either a PAT or a token generated from the GitHub app or CLI
|
||||
token: "${{ secrets.ELEMENT_BOT_TOKEN }}"
|
||||
|
||||
check:
|
||||
name: Post release checks
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Wait for dockerhub
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
with:
|
||||
ref: master
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-name: "Docker Buildx (vanilla)"
|
||||
allowed-conclusions: success
|
||||
|
||||
- name: Wait for debian package
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
with:
|
||||
ref: master
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-name: Build package
|
||||
allowed-conclusions: success
|
||||
|
||||
55
.github/workflows/release_prepare.yml
vendored
55
.github/workflows/release_prepare.yml
vendored
@@ -25,6 +25,9 @@ on:
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# The order is specified bottom-up to avoid any races for allchange
|
||||
REPOS: matrix-js-sdk matrix-react-sdk element-web element-desktop
|
||||
steps:
|
||||
- name: Checkout Element Desktop
|
||||
uses: actions/checkout@v4
|
||||
@@ -67,15 +70,55 @@ jobs:
|
||||
fetch-tags: true
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
- name: Resolve repos
|
||||
run: |
|
||||
echo "REPOS=$(ls . | tr '\n' ' ')" >> $GITHUB_ENV
|
||||
|
||||
- name: Merge develop
|
||||
run: |
|
||||
git config --global user.email "releases@riot.im"
|
||||
git config --global user.name "RiotRobot"
|
||||
for REPO in $REPOS; do git -C "$REPO" merge origin/develop; done
|
||||
for REPO in $REPOS; do [ -d "$REPO" ] && git -C "$REPO" merge origin/develop; done
|
||||
|
||||
- name: Push staging
|
||||
run: for REPO in $REPOS; do git -C "$REPO" push origin staging; done
|
||||
run: for REPO in $REPOS; do [ -d "$REPO" ] && git -C "$REPO" push origin staging; done
|
||||
|
||||
- name: Wait for matrix-js-sdk draft
|
||||
if: inputs.matrix-js-sdk
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
with:
|
||||
ref: staging
|
||||
repo: matrix-org/matrix-js-sdk
|
||||
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-name: draft
|
||||
allowed-conclusions: success
|
||||
|
||||
- name: Wait for matrix-react-sdk draft
|
||||
if: inputs.matrix-react-sdk
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
with:
|
||||
ref: staging
|
||||
repo: matrix-org/matrix-react-sdk
|
||||
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-name: draft
|
||||
allowed-conclusions: success
|
||||
|
||||
- name: Wait for element-web draft
|
||||
if: inputs.element-web
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
with:
|
||||
ref: staging
|
||||
repo: element-hq/element-web
|
||||
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-name: draft
|
||||
allowed-conclusions: success
|
||||
|
||||
- name: Wait for element-desktop draft
|
||||
if: inputs.element-desktop
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
with:
|
||||
ref: staging
|
||||
repo: element-hq/element-desktop
|
||||
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-name: draft
|
||||
allowed-conclusions: success
|
||||
|
||||
1
.github/workflows/sonarqube.yml
vendored
1
.github/workflows/sonarqube.yml
vendored
@@ -13,3 +13,4 @@ jobs:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop
|
||||
secrets:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
9
.github/workflows/static_analysis.yaml
vendored
9
.github/workflows/static_analysis.yaml
vendored
@@ -3,6 +3,8 @@ on:
|
||||
pull_request: {}
|
||||
push:
|
||||
branches: [develop, master]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
repository_dispatch:
|
||||
types: [element-web-notify]
|
||||
env:
|
||||
@@ -19,6 +21,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: "./scripts/layered.sh"
|
||||
@@ -29,6 +32,8 @@ jobs:
|
||||
i18n_lint:
|
||||
name: "i18n Check"
|
||||
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
|
||||
with:
|
||||
hardcoded-words: "Element"
|
||||
|
||||
js_lint:
|
||||
name: "ESLint"
|
||||
@@ -39,6 +44,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
@@ -56,6 +62,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
# Needs branch matching as it inherits .stylelintrc.js from matrix-react-sdk
|
||||
- name: Install Dependencies
|
||||
@@ -73,6 +80,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
@@ -90,6 +98,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Deps
|
||||
run: "scripts/layered.sh"
|
||||
|
||||
21
.github/workflows/tests.yaml
vendored
21
.github/workflows/tests.yaml
vendored
@@ -3,6 +3,8 @@ on:
|
||||
pull_request: {}
|
||||
push:
|
||||
branches: [develop, master]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
repository_dispatch:
|
||||
types: [element-web-notify]
|
||||
env:
|
||||
@@ -21,6 +23,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: "./scripts/layered.sh"
|
||||
@@ -33,9 +36,25 @@ jobs:
|
||||
run: "yarn coverage --ci --max-workers ${{ steps.cpu-cores.outputs.count }}"
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage
|
||||
path: |
|
||||
coverage
|
||||
!coverage/lcov-report
|
||||
|
||||
skip_sonar:
|
||||
name: Skip SonarCloud in merge queue
|
||||
if: github.event_name == 'merge_group'
|
||||
runs-on: ubuntu-latest
|
||||
needs: jest
|
||||
steps:
|
||||
- name: Skip SonarCloud
|
||||
uses: Sibz/github-status-action@faaa4d96fecf273bd762985e0e7f9f933c774918 # v1
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
state: success
|
||||
description: SonarCloud skipped
|
||||
context: SonarCloud Code Analysis
|
||||
sha: ${{ github.sha }}
|
||||
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
7
.github/workflows/triage-incoming.yml
vendored
7
.github/workflows/triage-incoming.yml
vendored
@@ -8,8 +8,7 @@ jobs:
|
||||
automate-project-columns:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43
|
||||
- uses: actions/add-to-project@main
|
||||
with:
|
||||
project: Issue triage
|
||||
column: Incoming
|
||||
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
project-url: https://github.com/orgs/element-hq/projects/120
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
50
.github/workflows/triage-labelled.yml
vendored
50
.github/workflows/triage-labelled.yml
vendored
@@ -24,8 +24,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Video-Rooms') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Message-Starring') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-R')
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
@@ -57,13 +56,48 @@ jobs:
|
||||
move_needs_info_issues:
|
||||
name: X-Needs-Info issues to Need info column on triage board
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'X-Needs-Info')
|
||||
steps:
|
||||
- uses: konradpabjan/move-labeled-or-milestoned-issue@190352295fe309fcb113b49193bc81d9aaa9cb01
|
||||
- id: add_to_project
|
||||
uses: actions/add-to-project@v1.0.1
|
||||
with:
|
||||
action-token: "${{ secrets.ELEMENT_BOT_TOKEN }}"
|
||||
project-url: "https://github.com/element-hq/element-web/projects/27"
|
||||
column-name: "Need info"
|
||||
label-name: "X-Needs-Info"
|
||||
project-url: ${{ env.PROJECT_URL }}
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
- id: set_fields
|
||||
uses: titoportas/update-project-fields@421a54430b3cdc9eefd8f14f9ce0142ab7678751 # v0.1.0
|
||||
with:
|
||||
project-url: ${{ env.PROJECT_URL }}
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
item-id: ${{ steps.add_to_project.outputs.itemId }} # Use the item-id output of the previous step
|
||||
field-keys: Status
|
||||
field-values: "Needs info"
|
||||
env:
|
||||
PROJECT_URL: https://github.com/orgs/element-hq/projects/120
|
||||
|
||||
move_flakey_test_issues:
|
||||
name: Z-Flaky-Test issues to Sized for maintainer column on triage board
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
|
||||
steps:
|
||||
- id: add_to_project
|
||||
uses: actions/add-to-project@v1.0.1
|
||||
with:
|
||||
project-url: ${{ env.PROJECT_URL }}
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
- id: set_fields
|
||||
uses: titoportas/update-project-fields@421a54430b3cdc9eefd8f14f9ce0142ab7678751 # v0.1.0
|
||||
with:
|
||||
project-url: ${{ env.PROJECT_URL }}
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
item-id: ${{ steps.add_to_project.outputs.itemId }} # Use the item-id output of the previous step
|
||||
field-keys: Status
|
||||
field-values: "Sized for maintainer"
|
||||
env:
|
||||
PROJECT_URL: https://github.com/orgs/element-hq/projects/120
|
||||
|
||||
add_priority_design_issues_to_project:
|
||||
name: P1 X-Needs-Design to Design project board
|
||||
@@ -149,7 +183,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-R')
|
||||
steps:
|
||||
- id: add_to_project
|
||||
uses: actions/add-to-project@v0.5.0
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: ${{ env.PROJECT_URL }}
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
17
.github/workflows/triage-stale-flaky-tests.yml
vendored
Normal file
17
.github/workflows/triage-stale-flaky-tests.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Close stale flaky issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
jobs:
|
||||
close:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
only-labels: "Z-Flaky-Test"
|
||||
days-before-stale: 14
|
||||
days-before-close: 0
|
||||
close-issue-message: "This flaky test issue has not been updated in 14 days. It is being closed as presumed resolved."
|
||||
exempt-issue-labels: "Z-Flaky-Test-Disabled"
|
||||
2
.github/workflows/triage-unlabelled.yml
vendored
2
.github/workflows/triage-unlabelled.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
fi
|
||||
fi
|
||||
- name: Move issue
|
||||
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43
|
||||
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36
|
||||
if: ${{ env.ALREADY_IN_BOARD == 'true' && env.SKIP_ACTION != 'true' }}
|
||||
with:
|
||||
project: Issue triage
|
||||
|
||||
3
.github/workflows/update-jitsi.yml
vendored
3
.github/workflows/update-jitsi.yml
vendored
@@ -13,6 +13,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
@@ -21,7 +22,7 @@ jobs:
|
||||
run: "yarn update:jitsi"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5
|
||||
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
branch: actions/jitsi-update
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"minify": true,
|
||||
"minify": false,
|
||||
"enableClasses": false,
|
||||
"feature-detects": [
|
||||
"test/css/animations",
|
||||
@@ -31,6 +31,7 @@
|
||||
"test/json",
|
||||
"test/network/fetch",
|
||||
"test/storage/localstorage",
|
||||
"test/window/resizeobserver"
|
||||
"test/window/resizeobserver",
|
||||
"test/audio/webaudio"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ src/vector/modernizr.js
|
||||
|
||||
# This file is owned, parsed, and generated by allchange, which doesn't comply with prettier
|
||||
/CHANGELOG.md
|
||||
/docs/changelogs
|
||||
|
||||
# Downloaded and already minified
|
||||
res/jitsi_external_api.min.js
|
||||
|
||||
9385
CHANGELOG.md
9385
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -124,7 +124,7 @@ must include:
|
||||
|
||||
1. Comprehensive unit tests written in Jest. These are located in `/test`.
|
||||
2. "happy path" end-to-end tests.
|
||||
These are located in `/cypress/e2e` in `matrix-react-sdk`, and
|
||||
These are located in `/playwright/e2e` in `matrix-react-sdk`, and
|
||||
are run using `element-web`. Ideally, you would also include tests for edge
|
||||
and error cases.
|
||||
|
||||
|
||||
@@ -17,17 +17,17 @@ module.exports = {
|
||||
],
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
"@babel/plugin-transform-numeric-separator",
|
||||
"@babel/plugin-transform-class-properties",
|
||||
"@babel/plugin-transform-object-rest-spread",
|
||||
"@babel/plugin-transform-optional-chaining",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator",
|
||||
|
||||
// transform logical assignment (??=, ||=, &&=). preset-env doesn't
|
||||
// normally bother with these (presumably because all the target
|
||||
// browsers support it natively), but they make our webpack version (or
|
||||
// something downstream of babel, at least) fall over.
|
||||
"@babel/plugin-proposal-logical-assignment-operators",
|
||||
"@babel/plugin-transform-logical-assignment-operators",
|
||||
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-transform-runtime",
|
||||
|
||||
@@ -225,6 +225,12 @@ Unless otherwise specified, the following applies to all code:
|
||||
}
|
||||
```
|
||||
|
||||
37. Avoid functions whose fundamental behaviour varies with different parameter types.
|
||||
Multiple return types are fine, but if the function's behaviour is going to change significantly,
|
||||
have two separate functions. For example, `SDKConfig.get()` with a string param which returns the
|
||||
type according to the param given is ok, but `SDKConfig.get()` with no args returning the whole
|
||||
config object would not be: this should just be a separate function.
|
||||
|
||||
## React
|
||||
|
||||
Inheriting all the rules of TypeScript, the following additionally apply:
|
||||
|
||||
1867
docs/changelogs/CHANGELOG-2022.md
Normal file
1867
docs/changelogs/CHANGELOG-2022.md
Normal file
File diff suppressed because it is too large
Load Diff
1017
docs/changelogs/CHANGELOG-2023.md
Normal file
1017
docs/changelogs/CHANGELOG-2023.md
Normal file
File diff suppressed because it is too large
Load Diff
6035
docs/changelogs/CHANGELOG-pre-2022.md
Normal file
6035
docs/changelogs/CHANGELOG-pre-2022.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -137,7 +137,7 @@ complete re-branding/private labeling, a more personalised experience can be ach
|
||||
This setting is ignored if your homeserver provides `/.well-known/matrix/client` in its well-known location, and the JSON file
|
||||
at that location has a key `m.tile_server` (or the unstable version `org.matrix.msc3488.tile_server`). In this case, the
|
||||
configuration found in the well-known location is used instead.
|
||||
10. `welcome_user_id`: An optional user ID to start a DM with after creating an account. Defaults to nothing (no DM created).
|
||||
10. `welcome_user_id`: **DEPRECATED** An optional user ID to start a DM with after creating an account. Defaults to nothing (no DM created).
|
||||
11. `custom_translations_url`: An optional URL to allow overriding of translatable strings. The JSON file must be in a format of
|
||||
`{"affected|translation|key": {"languageCode": "new string"}}`. See https://github.com/matrix-org/matrix-react-sdk/pull/7886 for details.
|
||||
12. `branding`: Options for configuring various assets used within the app. Described in more detail down below.
|
||||
@@ -250,17 +250,60 @@ When Element is deployed alongside a homeserver with SSO-only login, some option
|
||||
user can be sent to in order to log them out of that system too, making logout symmetric between Element and the SSO system.
|
||||
2. `sso_redirect_options`: Options to define how to handle unauthenticated users. If the object contains `"immediate": true`, then
|
||||
all unauthenticated users will be automatically redirected to the SSO system to start their login. If instead you'd only like to
|
||||
have users which land on the welcome page to be redirected, use `"on_welcome_page": true`. As an example:
|
||||
have users which land on the welcome page to be redirected, use `"on_welcome_page": true`. Additionally, there is an option to
|
||||
redirect anyone landing on the login page, by using `"on_login_page": true`. As an example:
|
||||
```json
|
||||
{
|
||||
"sso_redirect_options": {
|
||||
"immediate": false,
|
||||
"on_welcome_page": true
|
||||
"on_welcome_page": true,
|
||||
"on_login_page": true
|
||||
}
|
||||
}
|
||||
```
|
||||
It is most common to use the `immediate` flag instead of `on_welcome_page`.
|
||||
|
||||
## Native OIDC
|
||||
|
||||
Native OIDC support is currently in labs and is subject to change.
|
||||
|
||||
Static OIDC Client IDs are preferred and can be specified under `oidc_static_clients` as a mapping from `issuer` to configuration object containing `client_id`.
|
||||
Issuer must have a trailing forward slash. As an example:
|
||||
|
||||
```json
|
||||
{
|
||||
"oidc_static_clients": {
|
||||
"https://auth.example.com/": {
|
||||
"client_id": "example-client-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If a matching static client is not found, the app will attempt to dynamically register a client using metadata specified under `oidc_metadata`.
|
||||
The app has sane defaults for the metadata properties below but on stricter policy identity providers they may not pass muster, e.g. `contacts` may be required.
|
||||
The following subproperties are available:
|
||||
|
||||
1. `client_uri`: This is the base URI for the OIDC client registration, typically `logo_uri`, `tos_uri`, and `policy_uri` must be either on the same domain or a subdomain of this URI.
|
||||
2. `logo_uri`: Optional URI for the client logo.
|
||||
3. `tos_uri`: Optional URI for the client's terms of service.
|
||||
4. `policy_uri`: Optional URI for the client's privacy policy.
|
||||
5. `contacts`: Optional list of contact emails for the client.
|
||||
|
||||
As an example:
|
||||
|
||||
```json
|
||||
{
|
||||
"oidc_metadata": {
|
||||
"client_uri": "https://example.com",
|
||||
"logo_uri": "https://example.com/logo.png",
|
||||
"tos_uri": "https://example.com/tos",
|
||||
"policy_uri": "https://example.com/policy",
|
||||
"contacts": ["support@example.com"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## VoIP / Jitsi calls
|
||||
|
||||
Currently, Element uses Jitsi to offer conference calls in rooms, with an experimental Element Call implementation in the works.
|
||||
@@ -331,8 +374,8 @@ The VoIP and Jitsi options are:
|
||||
}
|
||||
```
|
||||
The `widget` is the `content` of a normal widget state event. The `layout` is the layout specifier for the widget being created,
|
||||
as defined by the `io.element.widgets.layout` state event. By default this applies to all rooms, but the behaviour can be skipped for DMs
|
||||
by setting the option `widget_build_url_ignore_dm` to `true`.
|
||||
as defined by the `io.element.widgets.layout` state event. By default this applies to all rooms, but the behaviour can be skipped for
|
||||
2-person rooms, causing Element to fall back to 1:1 VoIP, by setting the option `widget_build_url_ignore_dm` to `true`.
|
||||
5. `audio_stream_url`: Optional URL to pass to Jitsi to enable live streaming. This option is considered experimental and may be removed
|
||||
at any time without notice.
|
||||
6. `element_call`: Optional configuration for native group calls using Element Call, with the following subkeys:
|
||||
@@ -344,6 +387,12 @@ The VoIP and Jitsi options are:
|
||||
this number is exceeded, the user will not be able to join a given call.
|
||||
- `brand`: Optional name for the app. Defaults to `Element Call`. This is
|
||||
used throughout the application in various strings/locations.
|
||||
- `guest_spa_url`: Optional URL for an Element Call single-page app (SPA),
|
||||
for guest links. If this is set, Element Web will expose a "join" link
|
||||
for public video rooms, which can then be shared to non-matrix users.
|
||||
The target Element Call SPA is typically set up to use a homeserver that
|
||||
allows users to register without email ("passwordless guest users") and to
|
||||
federate.
|
||||
|
||||
## Bug reporting
|
||||
|
||||
|
||||
@@ -110,12 +110,6 @@ This is useful while we experiment with encryption and to make calling compatibl
|
||||
|
||||
Enables rendering of MD / HTML in room topics.
|
||||
|
||||
## Use the Rust cryptography implementation (`feature_rust_crypto`) [In Development]
|
||||
|
||||
Configures Element to use a new cryptography implementation based on the [matrix-rust-sdk](https://github.com/matrix-org/matrix-rust-sdk).
|
||||
|
||||
This setting is (currently) _sticky_ to a user's session: it only takes effect when the user logs in to a new session. Likewise, even after disabling the setting in `config.json`, the Rust implementation will remain in use until users log out.
|
||||
|
||||
## New room header & details (`feature_new_room_decoration_ui`) [In Development]
|
||||
|
||||
Refactors visually the room header and room sidebar
|
||||
|
||||
@@ -186,33 +186,35 @@ this means that any commit which goes to `staging` will eventually make its way
|
||||
Shortly after concluding the preparation stage (or pushing any changes to `staging` in general);
|
||||
a draft release will be automatically made on the 4 project repositories with suggested changelogs and version numbers.
|
||||
|
||||
Review the draft releases created, check the version number makes sense and that the changelog contains everything you'd expect to.
|
||||
|
||||
_Note: we should add a step here to write summaries atop the changelogs manually, or via AI_
|
||||
|
||||
Publishing the SDKs to npm also commits a dependency upgrade to the relevant downstream projects,
|
||||
if you skip a layer of this release (e.g. for a hotfix) then the dependency will remain on `#develop` which will be
|
||||
switched back to the version of the dependency from the master branch to not leak develop code into a release.
|
||||
|
||||
### Matrix JS SDK
|
||||
|
||||
The first stop is the matrix-js-sdk; kick off a release using [the automation](https://github.com/matrix-org/matrix-js-sdk/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options.
|
||||
|
||||
- [ ] matrix-js-sdk has been released & published to npm
|
||||
- [ ] Check the draft release which has been generated by [the automation](https://github.com/matrix-org/matrix-js-sdk/actions/workflows/release-drafter.yml)
|
||||
- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft**
|
||||
- [ ] Kick off a release using [the automation](https://github.com/matrix-org/matrix-js-sdk/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options.
|
||||
|
||||
### Matrix React SDK
|
||||
|
||||
The next stop is matrix-react-sdk; kick off a release using [the automation](https://github.com/matrix-org/matrix-react-sdk/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. In the JS SDK version field enter the version of the JS SDK you wish to use, for typical releases including all the layers this would be the version released in the stage above.
|
||||
|
||||
- [ ] matrix-react-sdk has been released & published to npm
|
||||
- [ ] Check the draft release which has been generated by [the automation](https://github.com/matrix-org/matrix-react-sdk/actions/workflows/release-drafter.yml)
|
||||
- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft**
|
||||
- [ ] Kick off a release using [the automation](https://github.com/matrix-org/matrix-react-sdk/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options.
|
||||
|
||||
### Element Web
|
||||
|
||||
The next stop is element-web; kick off a release using [the automation](https://github.com/element-hq/element-web/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. In the SDK version fields enter the versions you wish to use, for typical releases including all the layers this would be the versions released in the stages above.
|
||||
|
||||
- [ ] Element Web has been released
|
||||
- [ ] Check the draft release which has been generated by [the automation](https://github.com/element-hq/element-web/actions/workflows/release-drafter.yml)
|
||||
- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft**
|
||||
- [ ] Kick off a release using [the automation](https://github.com/element-hq/element-web/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options.
|
||||
|
||||
### Element Desktop
|
||||
|
||||
The next stop is element-desktop; kick off a release using [the automation](https://github.com/element-hq/element-desktop/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. In the JS SDK version field enter the version of the JS SDK you wish to use, for typical releases including all the layers this would be the version released in the stage above.
|
||||
|
||||
- [ ] Element Desktop has been released
|
||||
- [ ] Check the draft release which has been generated by [the automation](https://github.com/element-hq/element-desktop/actions/workflows/release-drafter.yml)
|
||||
- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft**
|
||||
- [ ] Kick off a release using [the automation](https://github.com/element-hq/element-desktop/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options.
|
||||
|
||||
# Deploying
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ To avoid name collisions, the internal name of a theme is
|
||||
`custom-${theme.name}`. So if you want to set the custom theme below as the
|
||||
default theme, you would use `default_theme: "custom-Electric Blue"`.
|
||||
|
||||
eg. in config.json:
|
||||
e.g. in config.json:
|
||||
|
||||
```
|
||||
"setting_defaults": {
|
||||
@@ -63,8 +63,10 @@ eg. in config.json:
|
||||
"timeline-text-color": "#2e2f32",
|
||||
"timeline-text-secondary-color": "#61708b",
|
||||
"timeline-highlights-color": "#f3f8fd",
|
||||
"username-colors": ["#ff0000", ...]
|
||||
"avatar-background-colors": ["#cc0000", ...]
|
||||
},
|
||||
"compound": {
|
||||
"--cpd-color-icon-accent-tertiary": "var(--cpd-color-blue-800)",
|
||||
"--cpd-color-text-action-accent": "var(--cpd-color-blue-900)"
|
||||
}
|
||||
}, {
|
||||
"name": "Deep Purple",
|
||||
@@ -89,8 +91,6 @@ eg. in config.json:
|
||||
}
|
||||
```
|
||||
|
||||
`username-colors` is expected to contain 8 colors. `avatar-background-colors` is expected to contain 3 colors. Both values are optional and have fallbacks from the built-in theme.
|
||||
|
||||
These are exposed as `--username-colors_0`, ... and `--avatar-background-colors_0`, ... respectively in CSS.
|
||||
`compound` may contain overrides for any [semantic design token](https://compound.element.io/?path=/docs/tokens-semantic-colors--docs) belonging to our design system. The above example shows how you might change the accent color to blue by setting the relevant semantic tokens to refer to blue [base tokens](https://compound.element.io/?path=/docs/tokens-color-palettes--docs).
|
||||
|
||||
All properties in `fonts` are optional, and will default to the standard Riot fonts.
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
|
||||
## How to check if your language already is being translated
|
||||
|
||||
Go to https://localazy.com/p/element-web
|
||||
If your language is listed then you can get started, have a read of https://localazy.com/docs/general/translating-strings
|
||||
if you need help getting started. If your language is not yet listed please express your wishes to start translating it in
|
||||
the general discussion room linked above.
|
||||
Go to https://localazy.com/p/element-web. If your language is listed then you can get started. Have a read
|
||||
of https://localazy.com/docs/general/translating-strings if you need help getting started. If your language is not yet
|
||||
listed please express your wishes to start translating it in the general discussion room linked above.
|
||||
|
||||
### What are `%(something)s`?
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"show_labs_settings": false,
|
||||
"room_directory": {
|
||||
"servers": ["matrix.org", "gitter.im", "libera.chat"]
|
||||
"servers": ["matrix.org", "gitter.im"]
|
||||
},
|
||||
"enable_presence_by_hs_url": {
|
||||
"https://matrix.org": false,
|
||||
@@ -43,5 +43,8 @@
|
||||
"api_host": "https://posthog.element.io"
|
||||
},
|
||||
"privacy_policy_url": "https://element.io/cookie-policy",
|
||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
|
||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
|
||||
"setting_defaults": {
|
||||
"RustCrypto.staged_rollout_percent": 60
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"show_labs_settings": true,
|
||||
"room_directory": {
|
||||
"servers": ["matrix.org", "gitter.im", "libera.chat"]
|
||||
"servers": ["matrix.org", "gitter.im"]
|
||||
},
|
||||
"enable_presence_by_hs_url": {
|
||||
"https://matrix.org": false,
|
||||
@@ -48,9 +48,13 @@
|
||||
},
|
||||
"privacy_policy_url": "https://element.io/cookie-policy",
|
||||
"features": {
|
||||
"threadsActivityCentre": true,
|
||||
"feature_video_rooms": true,
|
||||
"feature_rust_crypto": true,
|
||||
"feature_new_room_decoration_ui": true
|
||||
"feature_new_room_decoration_ui": true,
|
||||
"feature_element_call_video_rooms": true
|
||||
},
|
||||
"setting_defaults": {
|
||||
"RustCrypto.staged_rollout_percent": 100
|
||||
},
|
||||
"element_call": {
|
||||
"url": "https://call.element.dev"
|
||||
|
||||
103
package.json
103
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "element-web",
|
||||
"version": "1.11.53",
|
||||
"version": "1.11.72",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
@@ -67,37 +67,36 @@
|
||||
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react-dom": "17.0.21",
|
||||
"@types/react": "17.0.68"
|
||||
"@types/react-dom": "17.0.25",
|
||||
"@types/react": "17.0.80"
|
||||
},
|
||||
"dependencies": {
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"@matrix-org/react-sdk-module-api": "^2.2.1",
|
||||
"gfm.css": "^1.1.2",
|
||||
"jsrsasign": "^10.5.25",
|
||||
"@formatjs/intl-segmenter": "^11.5.7",
|
||||
"@matrix-org/react-sdk-module-api": "^2.3.0",
|
||||
"jsrsasign": "^11.0.0",
|
||||
"katex": "^0.16.0",
|
||||
"lodash": "^4.17.21",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"matrix-react-sdk": "github:matrix-org/matrix-react-sdk#develop",
|
||||
"matrix-js-sdk": "34.2.0",
|
||||
"matrix-react-sdk": "3.104.0",
|
||||
"matrix-widget-api": "^1.3.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"ua-parser-js": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@action-validator/cli": "^0.5.3",
|
||||
"@action-validator/core": "^0.5.3",
|
||||
"@action-validator/cli": "^0.6.0",
|
||||
"@action-validator/core": "^0.6.0",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/eslint-parser": "^7.12.10",
|
||||
"@babel/eslint-plugin": "^7.12.10",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.12.1",
|
||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.20.7",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-class-properties": "^7.12.1",
|
||||
"@babel/plugin-transform-logical-assignment-operators": "^7.20.7",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.12.1",
|
||||
"@babel/plugin-transform-numeric-separator": "^7.12.7",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.12.1",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.12.7",
|
||||
"@babel/plugin-transform-runtime": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/preset-react": "^7.12.10",
|
||||
@@ -107,7 +106,7 @@
|
||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
||||
"@sentry/webpack-plugin": "^2.7.1",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@svgr/webpack": "^8.0.0",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@types/commonmark": "^0.27.9",
|
||||
"@types/content-type": "^1.1.8",
|
||||
@@ -127,38 +126,39 @@
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
"@types/pako": "^2.0.3",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "17.0.68",
|
||||
"@types/react": "17.0.80",
|
||||
"@types/react-beautiful-dnd": "^13.1.7",
|
||||
"@types/react-dom": "17.0.21",
|
||||
"@types/react-dom": "17.0.25",
|
||||
"@types/react-transition-group": "^4.4.9",
|
||||
"@types/sanitize-html": "^2.9.5",
|
||||
"@types/sdp-transform": "^2.4.9",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/tar-js": "^0.3.5",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"allchange": "^1.0.6",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"babel-jest": "^29.0.0",
|
||||
"babel-loader": "^9.0.0",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"chokidar": "^3.5.1",
|
||||
"concurrently": "^8.0.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"copy-webpack-plugin": "^12.0.0",
|
||||
"cronstrue": "^2.41.0",
|
||||
"css-loader": "^5.2.7",
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"css-loader": "^7.0.0",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"dotenv": "^16.0.2",
|
||||
"eslint": "8.55.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-deprecate": "0.8.4",
|
||||
"eslint-plugin-deprecate": "0.8.5",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-matrix-org": "^1.0.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-unicorn": "^49.0.0",
|
||||
"fake-indexeddb": "^5.0.0",
|
||||
"eslint-plugin-unicorn": "^54.0.0",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
"fetch-mock": "9.11.0",
|
||||
"fetch-mock-jest": "^1.5.1",
|
||||
"file-loader": "^6.0.0",
|
||||
@@ -169,48 +169,49 @@
|
||||
"jest-mock": "^29.0.0",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"matrix-mock-request": "^2.5.0",
|
||||
"matrix-web-i18n": "^3.1.3",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"matrix-web-i18n": "^3.2.1",
|
||||
"mini-css-extract-plugin": "2.8.0",
|
||||
"minimist": "^1.2.6",
|
||||
"mkdirp": "^3.0.0",
|
||||
"modernizr": "^3.12.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-easings": "^2.0.0",
|
||||
"postcss-hexrgba": "2.0.1",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-loader": "^4.0.0",
|
||||
"postcss-mixins": "^6.2.3",
|
||||
"postcss-nested": "^4.2.3",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"postcss": "8.4.33",
|
||||
"postcss-easings": "^4.0.0",
|
||||
"postcss-hexrgba": "2.1.0",
|
||||
"postcss-import": "16.0.0",
|
||||
"postcss-loader": "8.1.0",
|
||||
"postcss-mixins": "^10.0.0",
|
||||
"postcss-nested": "^6.0.0",
|
||||
"postcss-preset-env": "^9.5.14",
|
||||
"postcss-scss": "^4.0.4",
|
||||
"postcss-simple-vars": "^5.0.2",
|
||||
"prettier": "3.1.1",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"prettier": "3.3.2",
|
||||
"process": "^0.11.10",
|
||||
"proxy-agent": "^6.3.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^5.0.0",
|
||||
"rimraf": "^6.0.0",
|
||||
"semver": "^7.5.2",
|
||||
"setimmediate": "^1.0.5",
|
||||
"string-replace-loader": "3",
|
||||
"style-loader": "3",
|
||||
"stylelint": "^16.0.0",
|
||||
"stylelint-config-standard": "^35.0.0",
|
||||
"style-loader": "4",
|
||||
"stylelint": "^16.1.0",
|
||||
"stylelint-config-standard": "^36.0.0",
|
||||
"stylelint-scss": "^6.0.0",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"ts-node": "^10.9.1",
|
||||
"ts-prune": "^0.10.3",
|
||||
"typescript": "5.3.3",
|
||||
"typescript": "5.5.3",
|
||||
"util": "^0.12.5",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
"webpack-cli": "^5.0.0",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-dev-server": "^5.0.0",
|
||||
"yaml": "^2.3.3"
|
||||
},
|
||||
"@casualbot/jest-sonar-reporter": {
|
||||
"outputDirectory": "coverage",
|
||||
"outputName": "jest-sonar-report.xml",
|
||||
"relativePaths": true
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ module.exports.pitch = function pitch(request) {
|
||||
return cb(err);
|
||||
}
|
||||
if (entries[0]) {
|
||||
return cb(null, `module.exports = __webpack_public_path__ + ${JSON.stringify(entries[0].files[0])};`);
|
||||
return cb(null, `module.exports = __webpack_public_path__ + ${JSON.stringify([...entries[0].files][0])};`);
|
||||
}
|
||||
return cb(null, null);
|
||||
});
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Script to perform a release of element-web.
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
./node_modules/matrix-js-sdk/release.sh "$@"
|
||||
62
res/.well-known/assetlinks.json
Normal file
62
res/.well-known/assetlinks.json
Normal file
@@ -0,0 +1,62 @@
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "im.vector.app.debug",
|
||||
"sha256_cert_fingerprints": [
|
||||
"B0:B0:51:DC:56:5C:81:2F:E1:7F:6F:3E:94:5B:4D:79:04:71:23:AB:0D:A6:12:86:76:9E:B2:94:91:97:13:0E"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "im.vector.app.nightly",
|
||||
"sha256_cert_fingerprints": [
|
||||
"CA:D3:85:16:84:3A:05:CC:EB:00:AB:7B:D3:80:0F:01:BA:8F:E0:4B:38:86:F3:97:D8:F7:9A:1B:C4:54:E4:0F"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "im.vector.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"F3:FF:38:D2:E5:A6:38:84:86:4A:4E:0D:45:C5:3B:19:8E:7E:39:C0:50:5B:D9:63:F5:55:D6:53:2D:EA:BF:5F"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.element.android.x.debug",
|
||||
"sha256_cert_fingerprints": [
|
||||
"B0:B0:51:DC:56:5C:81:2F:E1:7F:6F:3E:94:5B:4D:79:04:71:23:AB:0D:A6:12:86:76:9E:B2:94:91:97:13:0E"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.element.android.x.nightly",
|
||||
"sha256_cert_fingerprints": [
|
||||
"CA:D3:85:16:84:3A:05:CC:EB:00:AB:7B:D3:80:0F:01:BA:8F:E0:4B:38:86:F3:97:D8:F7:9A:1B:C4:54:E4:0F"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.element.android.x",
|
||||
"sha256_cert_fingerprints": [
|
||||
"C6:DB:9B:9C:8C:BD:D6:5D:16:E8:EC:8C:8B:91:C8:31:B9:EF:C9:5C:BF:98:AE:41:F6:A9:D8:35:15:1A:7E:16"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
3
res/jitsi_external_api.min.js
vendored
3
res/jitsi_external_api.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -10,5 +10,5 @@ sonar.exclusions=__mocks__,docs,element.io,nginx
|
||||
|
||||
sonar.typescript.tsconfigPath=./tsconfig.json
|
||||
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
||||
sonar.coverage.exclusions=test/**/*,res/**/*
|
||||
sonar.coverage.exclusions=test/**/*,res/**/*,src/vector/modernizr.js
|
||||
sonar.testExecutionReportPaths=coverage/jest-sonar-report.xml
|
||||
|
||||
4
src/@types/global.d.ts
vendored
4
src/@types/global.d.ts
vendored
@@ -33,7 +33,9 @@ type ElectronChannel =
|
||||
| "update-downloaded"
|
||||
| "userDownloadCompleted"
|
||||
| "userDownloadAction"
|
||||
| "openDesktopCapturerSourcePicker";
|
||||
| "openDesktopCapturerSourcePicker"
|
||||
| "userAccessToken"
|
||||
| "serverSupportedVersions";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
||||
@@ -194,9 +194,9 @@ export default class Favicon {
|
||||
}
|
||||
|
||||
private setIcon(canvas: HTMLCanvasElement): void {
|
||||
setImmediate(() => {
|
||||
setTimeout(() => {
|
||||
this.setIconSrc(canvas.toDataURL("image/png"));
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private setIconSrc(url: string): void {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"error": {
|
||||
"app_launch_unexpected_error": "שגיאה לא צפויה במהלך טעינת האפליקציה. ראו קונסול לפרטים נוספים.",
|
||||
"cannot_load_config": "לא ניתן לטעון את קובץ ההגדרות: יש לרענן את הדף כדי לנסות שנית.",
|
||||
"invalid_configuration_mixed_server": "Invalid configuration: a default_hs_url can't be specified along with default_server_name or default_server_config",
|
||||
"invalid_configuration_no_server": "תצורה שגויה: לא צוין שרת ברירת מחדל.",
|
||||
"invalid_json": "האלמנט מכיל הגדרת JSON שגויה, אנא תקנו את הבעיה ואתחלו את הדף.",
|
||||
"invalid_json_detail": "ההודעה מהמנתח היא: %(message)s",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"title": "Niewspierana przeglądarka"
|
||||
},
|
||||
"powered_by_matrix": "Zasilane przez Matrix",
|
||||
"powered_by_matrix_with_logo": "Zdecentralizowany, szyfrowany czat i współpraca oparte na $matrixLogo",
|
||||
"powered_by_matrix_with_logo": "Zdecentralizowany czat szyfrowany i współpraca oparta na $matrixLogo",
|
||||
"unknown_device": "Nieznane urządzenie",
|
||||
"use_brand_on_mobile": "Użyj %(brand)s w telefonie",
|
||||
"web_default_device_name": "%(appName)s: %(browserName)s na %(osName)s",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"summary": "您的瀏覽器無法執行 %(brand)s",
|
||||
"title": "不支援的瀏覽器"
|
||||
},
|
||||
"powered_by_matrix": "由Matrix支持",
|
||||
"powered_by_matrix_with_logo": "由 $matrixLogo 驅動的去中心化、加密的聊天與協作工具",
|
||||
"unknown_device": "未知裝置",
|
||||
"use_brand_on_mobile": "在行動裝置上使用 %(brand)s",
|
||||
|
||||
188
src/serviceworker/index.ts
Normal file
188
src/serviceworker/index.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
Copyright 2024 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 { idbLoad } from "matrix-react-sdk/src/utils/StorageAccess";
|
||||
import { ACCESS_TOKEN_IV, tryDecryptToken } from "matrix-react-sdk/src/utils/tokens/tokens";
|
||||
import { buildAndEncodePickleKey } from "matrix-react-sdk/src/utils/tokens/pickling";
|
||||
|
||||
const serverSupportMap: {
|
||||
[serverUrl: string]: {
|
||||
supportsAuthedMedia: boolean;
|
||||
cacheExpiryTimeMs: number;
|
||||
};
|
||||
} = {};
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
// We skipWaiting() to update the service worker more frequently, particularly in development environments.
|
||||
// @ts-expect-error - service worker types are not available. See 'fetch' event handler.
|
||||
event.waitUntil(skipWaiting());
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
// We force all clients to be under our control, immediately. This could be old tabs.
|
||||
// @ts-expect-error - service worker types are not available. See 'fetch' event handler.
|
||||
event.waitUntil(clients.claim());
|
||||
});
|
||||
|
||||
// @ts-expect-error - the service worker types conflict with the DOM types available through TypeScript. Many hours
|
||||
// have been spent trying to convince the type system that there's no actual conflict, but it has yet to work. Instead
|
||||
// of trying to make it do the thing, we force-cast to something close enough where we can (and ignore errors otherwise).
|
||||
self.addEventListener("fetch", (event: FetchEvent) => {
|
||||
// This is the authenticated media (MSC3916) check, proxying what was unauthenticated to the authenticated variants.
|
||||
|
||||
if (event.request.method !== "GET") {
|
||||
return; // not important to us
|
||||
}
|
||||
|
||||
// Note: ideally we'd keep the request headers etc, but in practice we can't even see those details.
|
||||
// See https://stackoverflow.com/a/59152482
|
||||
let url = event.request.url;
|
||||
|
||||
// We only intercept v3 download and thumbnail requests as presumably everything else is deliberate.
|
||||
// For example, `/_matrix/media/unstable` or `/_matrix/media/v3/preview_url` are something well within
|
||||
// the control of the application, and appear to be choices made at a higher level than us.
|
||||
if (!url.includes("/_matrix/media/v3/download") && !url.includes("/_matrix/media/v3/thumbnail")) {
|
||||
return; // not a URL we care about
|
||||
}
|
||||
|
||||
// We need to call respondWith synchronously, otherwise we may never execute properly. This means
|
||||
// later on we need to proxy the request through if it turns out the server doesn't support authentication.
|
||||
event.respondWith(
|
||||
(async (): Promise<Response> => {
|
||||
let accessToken: string | undefined;
|
||||
try {
|
||||
// Figure out which homeserver we're communicating with
|
||||
const csApi = url.substring(0, url.indexOf("/_matrix/media/v3"));
|
||||
|
||||
// Add jitter to reduce request spam, particularly to `/versions` on initial page load
|
||||
await new Promise<void>((resolve) => setTimeout(() => resolve(), Math.random() * 10));
|
||||
|
||||
// Locate our access token, and populate the fetchConfig with the authentication header.
|
||||
// @ts-expect-error - service worker types are not available. See 'fetch' event handler.
|
||||
const client = await self.clients.get(event.clientId);
|
||||
accessToken = await getAccessToken(client);
|
||||
|
||||
// Update or populate the server support map using a (usually) authenticated `/versions` call.
|
||||
await tryUpdateServerSupportMap(csApi, accessToken);
|
||||
|
||||
// If we have server support (and a means of authentication), rewrite the URL to use MSC3916 endpoints.
|
||||
if (serverSupportMap[csApi].supportsAuthedMedia && accessToken) {
|
||||
url = url.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
|
||||
} // else by default we make no changes
|
||||
} catch (err) {
|
||||
console.error("SW: Error in request rewrite.", err);
|
||||
}
|
||||
|
||||
// Add authentication and send the request. We add authentication even if MSC3916 endpoints aren't
|
||||
// being used to ensure patches like this work:
|
||||
// https://github.com/matrix-org/synapse/commit/2390b66bf0ec3ff5ffb0c7333f3c9b239eeb92bb
|
||||
return fetch(url, fetchConfigForToken(accessToken));
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
async function tryUpdateServerSupportMap(clientApiUrl: string, accessToken?: string): Promise<void> {
|
||||
// only update if we don't know about it, or if the data is stale
|
||||
if (serverSupportMap[clientApiUrl]?.cacheExpiryTimeMs > new Date().getTime()) {
|
||||
return; // up to date
|
||||
}
|
||||
|
||||
const config = fetchConfigForToken(accessToken);
|
||||
const versions = await (await fetch(`${clientApiUrl}/_matrix/client/versions`, config)).json();
|
||||
console.log(`[ServiceWorker] /versions response for '${clientApiUrl}': ${JSON.stringify(versions)}`);
|
||||
|
||||
serverSupportMap[clientApiUrl] = {
|
||||
supportsAuthedMedia: Boolean(versions?.versions?.includes("v1.11")),
|
||||
cacheExpiryTimeMs: new Date().getTime() + 2 * 60 * 60 * 1000, // 2 hours from now
|
||||
};
|
||||
console.log(
|
||||
`[ServiceWorker] serverSupportMap update for '${clientApiUrl}': ${JSON.stringify(serverSupportMap[clientApiUrl])}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Ideally we'd use the `Client` interface for `client`, but since it's not available (see 'fetch' listener), we use
|
||||
// unknown for now and force-cast it to something close enough later.
|
||||
async function getAccessToken(client: unknown): Promise<string | undefined> {
|
||||
// Access tokens are encrypted at rest, so while we can grab the "access token", we'll need to do work to get the
|
||||
// real thing.
|
||||
const encryptedAccessToken = await idbLoad("account", "mx_access_token");
|
||||
|
||||
// We need to extract a user ID and device ID from localstorage, which means calling WebPlatform for the
|
||||
// read operation. Service workers can't access localstorage.
|
||||
const { userId, deviceId } = await askClientForUserIdParams(client);
|
||||
|
||||
// ... and this is why we need the user ID and device ID: they're index keys for the pickle key table.
|
||||
const pickleKeyData = await idbLoad("pickleKey", [userId, deviceId]);
|
||||
if (pickleKeyData && (!pickleKeyData.encrypted || !pickleKeyData.iv || !pickleKeyData.cryptoKey)) {
|
||||
console.error("SW: Invalid pickle key loaded - ignoring");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Finally, try decrypting the thing and return that. This may fail, but that's okay.
|
||||
try {
|
||||
const pickleKey = await buildAndEncodePickleKey(pickleKeyData, userId, deviceId);
|
||||
return tryDecryptToken(pickleKey, encryptedAccessToken, ACCESS_TOKEN_IV);
|
||||
} catch (e) {
|
||||
console.error("SW: Error decrypting access token.", e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Ideally we'd use the `Client` interface for `client`, but since it's not available (see 'fetch' listener), we use
|
||||
// unknown for now and force-cast it to something close enough inside the function.
|
||||
async function askClientForUserIdParams(client: unknown): Promise<{ userId: string; deviceId: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Dev note: this uses postMessage, which is a highly insecure channel. postMessage is typically visible to other
|
||||
// tabs, windows, browser extensions, etc, making it far from ideal for sharing sensitive information. This is
|
||||
// why our service worker calculates/decrypts the access token manually: we don't want the user's access token
|
||||
// to be available to (potentially) malicious listeners. We do require some information for that decryption to
|
||||
// work though, and request that in the least sensitive way possible.
|
||||
//
|
||||
// We could also potentially use some version of TLS to encrypt postMessage, though that feels way more involved
|
||||
// than just reading IndexedDB ourselves.
|
||||
|
||||
// Avoid stalling the tab in case something goes wrong.
|
||||
const timeoutId = setTimeout(() => reject(new Error("timeout in postMessage")), 1000);
|
||||
|
||||
// We don't need particularly good randomness here - we just use this to generate a request ID, so we know
|
||||
// which postMessage reply is for our active request.
|
||||
const responseKey = Math.random().toString(36);
|
||||
|
||||
// Add the listener first, just in case the tab is *really* fast.
|
||||
const listener = (event: MessageEvent): void => {
|
||||
if (event.data?.responseKey !== responseKey) return; // not for us
|
||||
clearTimeout(timeoutId); // do this as soon as possible, avoiding a race between resolve and reject.
|
||||
resolve(event.data); // "unblock" the remainder of the thread, if that were such a thing in JavaScript.
|
||||
self.removeEventListener("message", listener); // cleanup, since we're not going to do anything else.
|
||||
};
|
||||
self.addEventListener("message", listener);
|
||||
|
||||
// Ask the tab for the information we need. This is handled by WebPlatform.
|
||||
(client as Window).postMessage({ responseKey, type: "userinfo" });
|
||||
});
|
||||
}
|
||||
|
||||
function fetchConfigForToken(accessToken?: string): RequestInit | undefined {
|
||||
if (!accessToken) {
|
||||
return undefined; // no headers/config to specify
|
||||
}
|
||||
|
||||
return {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -89,9 +89,14 @@ export async function loadApp(fragParams: {}, matrixChatRef: React.Ref<MatrixCha
|
||||
// XXX: This path matching is a bit brittle, but better to do it early instead of in the app code.
|
||||
const isWelcomeOrLanding =
|
||||
window.location.hash === "#/welcome" || window.location.hash === "#" || window.location.hash === "";
|
||||
const isLoginPage = window.location.hash === "#/login";
|
||||
|
||||
if (!autoRedirect && ssoRedirects.on_welcome_page && isWelcomeOrLanding) {
|
||||
autoRedirect = true;
|
||||
}
|
||||
if (!autoRedirect && ssoRedirects.on_login_page && isLoginPage) {
|
||||
autoRedirect = true;
|
||||
}
|
||||
if (!hasPossibleToken && !isReturningFromSso && autoRedirect) {
|
||||
logger.log("Bypassing app load to redirect to SSO");
|
||||
const tempCli = createClient({
|
||||
@@ -199,7 +204,7 @@ async function verifyServerConfig(): Promise<IConfigOptions> {
|
||||
}
|
||||
}
|
||||
|
||||
validatedConfig = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, true);
|
||||
validatedConfig = await AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, true);
|
||||
} catch (e) {
|
||||
const { hsUrl, isUrl, userId } = await Lifecycle.getStoredSessionVars();
|
||||
if (hsUrl && userId) {
|
||||
|
||||
@@ -81,32 +81,13 @@
|
||||
<img src="<%= require('matrix-react-sdk/res/img/format/quote.svg').default %>" aria-hidden alt="" width="25" height="22" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/>
|
||||
<img src="<%= require('matrix-react-sdk/res/img/format/strikethrough.svg').default %>" aria-hidden alt="" width="25" height="22" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/>
|
||||
|
||||
<audio id="messageAudio">
|
||||
<source src="media/message.ogg" type="audio/ogg" />
|
||||
<source src="media/message.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="ringAudio" loop>
|
||||
<source src="media/ring.ogg" type="audio/ogg" />
|
||||
<source src="media/ring.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="ringbackAudio" loop>
|
||||
<source src="media/ringback.ogg" type="audio/ogg" />
|
||||
<source src="media/ringback.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="callendAudio">
|
||||
<source src="media/callend.ogg" type="audio/ogg" />
|
||||
<source src="media/callend.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="busyAudio">
|
||||
<source src="media/busy.ogg" type="audio/ogg" />
|
||||
<source src="media/busy.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="errorAudio">
|
||||
<source src="media/error.ogg" type="audio/ogg" />
|
||||
<source src="media/error.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="remoteAudio"></audio>
|
||||
<!-- let CSS themes pass constants to the app -->
|
||||
<div id="mx_theme_accentColor"></div><div id="mx_theme_secondaryAccentColor"></div><div id="mx_theme_tertiaryAccentColor"></div>
|
||||
|
||||
<!-- We eagerly create these containers to ensure their CSS stacking context order is sensible -->
|
||||
<div id="mx_PersistedElement_container"></div>
|
||||
<div id="mx_Dialog_StaticContainer"></div>
|
||||
<div id="mx_Dialog_Container"></div>
|
||||
<div id="mx_ContextualMenu_Container"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -19,19 +19,15 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { extractErrorMessageFromError } from "matrix-react-sdk/src/components/views/dialogs/ErrorDialog";
|
||||
import { shouldPolyfill as shouldPolyFillIntlSegmenter } from "@formatjs/intl-segmenter/should-polyfill";
|
||||
|
||||
// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
|
||||
import { parseQsFromFragment } from "./url_utils";
|
||||
import "./modernizr";
|
||||
|
||||
// Make setImmediate available in bundle
|
||||
import "setimmediate";
|
||||
|
||||
// Require common CSS here; this will make webpack process it into bundle.css.
|
||||
// Our own CSS (which is themed) is imported via separate webpack entry points
|
||||
// in webpack.config.js
|
||||
require("gfm.css/gfm.css");
|
||||
require("katex/dist/katex.css");
|
||||
|
||||
/**
|
||||
@@ -60,8 +56,8 @@ function checkBrowserFeatures(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks
|
||||
// in it for some features we depend on.
|
||||
// Custom checks atop Modernizr because it doesn't have checks in it for
|
||||
// some features we depend on.
|
||||
// Modernizr requires rules to be lowercase with no punctuation.
|
||||
// ES2018: http://262.ecma-international.org/9.0/#sec-promise.prototype.finally
|
||||
window.Modernizr.addTest("promiseprototypefinally", () => typeof window.Promise?.prototype?.finally === "function");
|
||||
@@ -74,6 +70,21 @@ function checkBrowserFeatures(): boolean {
|
||||
);
|
||||
// ES2019: http://262.ecma-international.org/10.0/#sec-object.fromentries
|
||||
window.Modernizr.addTest("objectfromentries", () => typeof window.Object?.fromEntries === "function");
|
||||
// ES2024: https://tc39.es/ecma262/2024/#sec-get-regexp.prototype.unicodesets
|
||||
window.Modernizr.addTest(
|
||||
"regexpunicodesets",
|
||||
() => window.RegExp?.prototype && "unicodeSets" in window.RegExp.prototype,
|
||||
);
|
||||
// ES2024: https://402.ecma-international.org/9.0/#sec-intl.segmenter
|
||||
// The built-in modernizer 'intl' check only checks for the presence of the Intl object, not the Segmenter,
|
||||
// and older Firefox has the former but not the latter, so we add our own.
|
||||
// This is polyfilled now, but we still want to show the warning because we want to remove the polyfill
|
||||
// at some point.
|
||||
window.Modernizr.addTest("intlsegmenter", () => typeof window.Intl?.Segmenter === "function");
|
||||
|
||||
// Basic test for WebAssembly support. We could also try instantiating a simple module,
|
||||
// although this would start to make (more) assumptions about how rust-crypto loads its wasm.
|
||||
window.Modernizr.addTest("wasm", () => typeof WebAssembly === "object" && typeof WebAssembly.Module === "function");
|
||||
|
||||
const featureList = Object.keys(window.Modernizr) as Array<keyof ModernizrStatic>;
|
||||
|
||||
@@ -104,12 +115,15 @@ const supportedBrowser = checkBrowserFeatures();
|
||||
// the browser to use as much parallelism as it can.
|
||||
// Load parallelism is based on research in https://github.com/element-hq/element-web/issues/12253
|
||||
async function start(): Promise<void> {
|
||||
if (shouldPolyFillIntlSegmenter()) {
|
||||
await import(/* webpackChunkName: "intl-segmenter-polyfill" */ "@formatjs/intl-segmenter/polyfill-force");
|
||||
}
|
||||
|
||||
// load init.ts async so that its code is not executed immediately and we can catch any exceptions
|
||||
const {
|
||||
rageshakePromise,
|
||||
setupLogStorage,
|
||||
preparePlatform,
|
||||
loadOlm,
|
||||
loadConfig,
|
||||
loadLanguage,
|
||||
loadTheme,
|
||||
@@ -118,12 +132,15 @@ async function start(): Promise<void> {
|
||||
showError,
|
||||
showIncompatibleBrowser,
|
||||
_t,
|
||||
extractErrorMessageFromError,
|
||||
} = await import(
|
||||
/* webpackChunkName: "init" */
|
||||
/* webpackPreload: true */
|
||||
"./init"
|
||||
);
|
||||
|
||||
// Now perform the next stage of initialisation. This has its own try/catch in which we render
|
||||
// a react error page on failure.
|
||||
try {
|
||||
// give rageshake a chance to load/fail, we don't actually assert rageshake loads, we allow it to fail if no IDB
|
||||
await settled(rageshakePromise);
|
||||
@@ -147,7 +164,6 @@ async function start(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
const loadOlmPromise = loadOlm();
|
||||
// set the platform for react sdk
|
||||
preparePlatform();
|
||||
// load config requires the platform to be ready
|
||||
@@ -180,7 +196,7 @@ async function start(): Promise<void> {
|
||||
// error handling begins here
|
||||
// ##########################
|
||||
if (!acceptBrowser) {
|
||||
await new Promise<void>((resolve) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
logger.error("Browser is missing required features.");
|
||||
// take to a different landing page to AWOOOOOGA at the user
|
||||
showIncompatibleBrowser(() => {
|
||||
@@ -189,7 +205,7 @@ async function start(): Promise<void> {
|
||||
}
|
||||
logger.log("User accepts the compatibility risks.");
|
||||
resolve();
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -214,7 +230,6 @@ async function start(): Promise<void> {
|
||||
// app load critical path starts here
|
||||
// assert things started successfully
|
||||
// ##################################
|
||||
await loadOlmPromise;
|
||||
await loadModulesPromise;
|
||||
await loadThemePromise;
|
||||
await loadLanguagePromise;
|
||||
@@ -238,6 +253,10 @@ async function start(): Promise<void> {
|
||||
}
|
||||
|
||||
start().catch((err) => {
|
||||
// If we get here, things have gone terribly wrong and we cannot load the app javascript at all.
|
||||
// Show a different, very simple iframed-static error page. Or actually, one of two different ones
|
||||
// depending on whether the browser is supported (ie. we think we should be able to load but
|
||||
// failed) or unsupported (where we tried anyway and, lo and behold, we failed).
|
||||
logger.error(err);
|
||||
// show the static error in an iframe to not lose any context / console data
|
||||
// with some basic styling to make the iframe full page
|
||||
|
||||
@@ -17,10 +17,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import olmWasmPath from "@matrix-org/olm/olm.wasm";
|
||||
import Olm from "@matrix-org/olm";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import * as React from "react";
|
||||
import * as languageHandler from "matrix-react-sdk/src/languageHandler";
|
||||
@@ -76,48 +72,6 @@ export async function loadConfig(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export function loadOlm(): Promise<void> {
|
||||
/* Load Olm. We try the WebAssembly version first, and then the legacy,
|
||||
* asm.js version if that fails. For this reason we need to wait for this
|
||||
* to finish before continuing to load the rest of the app. In future
|
||||
* we could somehow pass a promise down to react-sdk and have it wait on
|
||||
* that so olm can be loading in parallel with the rest of the app.
|
||||
*
|
||||
* We also need to tell the Olm js to look for its wasm file at the same
|
||||
* level as index.html. It really should be in the same place as the js,
|
||||
* ie. in the bundle directory, but as far as I can tell this is
|
||||
* completely impossible with webpack. We do, however, use a hashed
|
||||
* filename to avoid caching issues.
|
||||
*/
|
||||
return Olm.init({
|
||||
locateFile: () => olmWasmPath,
|
||||
})
|
||||
.then(() => {
|
||||
logger.log("Using WebAssembly Olm");
|
||||
})
|
||||
.catch((wasmLoadError) => {
|
||||
logger.log("Failed to load Olm: trying legacy version", wasmLoadError);
|
||||
return new Promise((resolve, reject) => {
|
||||
const s = document.createElement("script");
|
||||
s.src = "olm_legacy.js"; // XXX: This should be cache-busted too
|
||||
s.onload = resolve;
|
||||
s.onerror = reject;
|
||||
document.body.appendChild(s);
|
||||
})
|
||||
.then(() => {
|
||||
// Init window.Olm, ie. the one just loaded by the script tag,
|
||||
// not 'Olm' which is still the failed wasm version.
|
||||
return window.Olm.init();
|
||||
})
|
||||
.then(() => {
|
||||
logger.log("Using legacy Olm");
|
||||
})
|
||||
.catch((legacyLoadError) => {
|
||||
logger.log("Both WebAssembly and asm.js Olm failed!", legacyLoadError);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadLanguage(): Promise<void> {
|
||||
const prefLang = SettingsStore.getValue("language", null, /*excludeDefault=*/ true);
|
||||
let langs: string[] = [];
|
||||
@@ -138,7 +92,7 @@ export async function loadLanguage(): Promise<void> {
|
||||
}
|
||||
|
||||
export async function loadTheme(): Promise<void> {
|
||||
setTheme();
|
||||
return setTheme();
|
||||
}
|
||||
|
||||
export async function loadApp(fragParams: {}): Promise<void> {
|
||||
@@ -189,3 +143,5 @@ export async function loadModules(): Promise<void> {
|
||||
}
|
||||
|
||||
export { _t } from "../languageHandler";
|
||||
|
||||
export { extractErrorMessageFromError } from "matrix-react-sdk/src/components/views/dialogs/ErrorDialog";
|
||||
|
||||
@@ -177,17 +177,17 @@ const setupCompleted = (async (): Promise<string | void> => {
|
||||
}
|
||||
}
|
||||
|
||||
await widgetApi!.transport.reply(ev.detail, response);
|
||||
widgetApi!.transport.reply(ev.detail, response);
|
||||
});
|
||||
};
|
||||
|
||||
handleAction(ElementWidgetActions.JoinCall, async ({ audioInput, videoInput }) => {
|
||||
joinConference(audioInput as string | null, videoInput as string | null);
|
||||
void joinConference(audioInput as string | null, videoInput as string | null);
|
||||
});
|
||||
handleAction(ElementWidgetActions.HangupCall, async ({ force }) => {
|
||||
if (force === true) {
|
||||
meetApi?.dispose();
|
||||
notifyHangup();
|
||||
void notifyHangup();
|
||||
meetApi = undefined;
|
||||
closeConference();
|
||||
} else {
|
||||
@@ -292,14 +292,12 @@ function switchVisibleContainers(): void {
|
||||
|
||||
function toggleConferenceVisibility(inConference: boolean): void {
|
||||
document.getElementById("jitsiContainer")!.style.visibility = inConference ? "unset" : "hidden";
|
||||
// Video rooms have a separate UI for joining, so they should never show our join button
|
||||
document.getElementById("joinButtonContainer")!.style.visibility =
|
||||
inConference || isVideoChannel ? "hidden" : "unset";
|
||||
document.getElementById("joinButtonContainer")!.style.visibility = inConference ? "hidden" : "unset";
|
||||
}
|
||||
|
||||
function skipToJitsiSplashScreen(): void {
|
||||
// really just a function alias for self-documenting code
|
||||
joinConference();
|
||||
void joinConference();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,9 +448,8 @@ async function joinConference(audioInput?: string | null, videoInput?: string |
|
||||
|
||||
// Video channel widgets need some more tailored config options
|
||||
if (isVideoChannel) {
|
||||
// Ensure that we skip Jitsi Meet's native prejoin screen, for
|
||||
// deployments that have it enabled
|
||||
options.configOverwrite!.prejoinConfig = { enabled: false };
|
||||
// We don't skip jitsi's prejoin screen for video rooms.
|
||||
options.configOverwrite!.prejoinConfig = { enabled: true };
|
||||
// Use a simplified set of toolbar buttons
|
||||
options.configOverwrite!.toolbarButtons = ["microphone", "camera", "tileview", "hangup"];
|
||||
// Note: We can hide the screenshare button in video rooms but not in
|
||||
@@ -503,8 +500,8 @@ const onVideoConferenceJoined = (): void => {
|
||||
if (widgetApi) {
|
||||
// ignored promise because we don't care if it works
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
widgetApi.setAlwaysOnScreen(true);
|
||||
widgetApi.transport.send(ElementWidgetActions.JoinCall, {});
|
||||
void widgetApi.setAlwaysOnScreen(true);
|
||||
void widgetApi.transport.send(ElementWidgetActions.JoinCall, {});
|
||||
}
|
||||
|
||||
// Video rooms should start in tile mode
|
||||
@@ -512,7 +509,7 @@ const onVideoConferenceJoined = (): void => {
|
||||
};
|
||||
|
||||
const onVideoConferenceLeft = (): void => {
|
||||
notifyHangup();
|
||||
void notifyHangup();
|
||||
meetApi = undefined;
|
||||
};
|
||||
|
||||
@@ -520,7 +517,7 @@ const onErrorOccurred = ({ error }: Parameters<ExternalAPIEventCallbacks["errorO
|
||||
if (error.isFatal) {
|
||||
// We got disconnected. Since Jitsi Meet might send us back to the
|
||||
// prejoin screen, we're forced to act as if we hung up entirely.
|
||||
notifyHangup(error.message);
|
||||
void notifyHangup(error.message);
|
||||
meetApi = undefined;
|
||||
closeConference();
|
||||
}
|
||||
@@ -528,7 +525,7 @@ const onErrorOccurred = ({ error }: Parameters<ExternalAPIEventCallbacks["errorO
|
||||
|
||||
const onAudioMuteStatusChanged = ({ muted }: AudioMuteStatusChangedEvent): void => {
|
||||
const action = muted ? ElementWidgetActions.MuteAudio : ElementWidgetActions.UnmuteAudio;
|
||||
widgetApi?.transport.send(action, {});
|
||||
void widgetApi?.transport.send(action, {});
|
||||
};
|
||||
|
||||
const onVideoMuteStatusChanged = ({ muted }: VideoMuteStatusChangedEvent): void => {
|
||||
@@ -538,15 +535,15 @@ const onVideoMuteStatusChanged = ({ muted }: VideoMuteStatusChangedEvent): void
|
||||
// otherwise the React SDK will mistakenly think the user turned off
|
||||
// their video by hand
|
||||
setTimeout(() => {
|
||||
if (meetApi) widgetApi?.transport.send(ElementWidgetActions.MuteVideo, {});
|
||||
if (meetApi) void widgetApi?.transport.send(ElementWidgetActions.MuteVideo, {});
|
||||
}, 200);
|
||||
} else {
|
||||
widgetApi?.transport.send(ElementWidgetActions.UnmuteVideo, {});
|
||||
void widgetApi?.transport.send(ElementWidgetActions.UnmuteVideo, {});
|
||||
}
|
||||
};
|
||||
|
||||
const updateParticipants = (): void => {
|
||||
widgetApi?.transport.send(ElementWidgetActions.CallParticipants, {
|
||||
void widgetApi?.transport.send(ElementWidgetActions.CallParticipants, {
|
||||
participants: meetApi?.getParticipantsInfo(),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -120,4 +120,4 @@ async function initPage(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
initPage();
|
||||
void initPage();
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -43,6 +43,8 @@ import { BreadcrumbsStore } from "matrix-react-sdk/src/stores/BreadcrumbsStore";
|
||||
import { UPDATE_EVENT } from "matrix-react-sdk/src/stores/AsyncStore";
|
||||
import { avatarUrlForRoom, getInitialLetter } from "matrix-react-sdk/src/Avatar";
|
||||
import DesktopCapturerSourcePicker from "matrix-react-sdk/src/components/views/elements/DesktopCapturerSourcePicker";
|
||||
import { OidcRegistrationClientMetadata } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
|
||||
|
||||
import VectorBasePlatform from "./VectorBasePlatform";
|
||||
import { SeshatIndexManager } from "./SeshatIndexManager";
|
||||
@@ -56,6 +58,8 @@ interface SquirrelUpdate {
|
||||
updateURL: string;
|
||||
}
|
||||
|
||||
const SSO_ID_KEY = "element-desktop-ssoid";
|
||||
|
||||
const isMac = navigator.platform.toUpperCase().includes("MAC");
|
||||
|
||||
function platformFriendlyName(): string {
|
||||
@@ -124,6 +128,19 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
});
|
||||
});
|
||||
|
||||
// `userAccessToken` (IPC) is requested by the main process when appending authentication
|
||||
// to media downloads. A reply is sent over the same channel.
|
||||
window.electron.on("userAccessToken", () => {
|
||||
window.electron!.send("userAccessToken", MatrixClientPeg.get()?.getAccessToken());
|
||||
});
|
||||
|
||||
// `serverSupportedVersions` is requested by the main process when it needs to know if the
|
||||
// server supports a particular version. This is primarily used to detect authenticated media
|
||||
// support. A reply is sent over the same channel.
|
||||
window.electron.on("serverSupportedVersions", async () => {
|
||||
window.electron!.send("serverSupportedVersions", await MatrixClientPeg.get()?.getVersions());
|
||||
});
|
||||
|
||||
// try to flush the rageshake logs to indexeddb before quit.
|
||||
window.electron.on("before-quit", function () {
|
||||
logger.log("element-desktop closing");
|
||||
@@ -164,15 +181,14 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
});
|
||||
});
|
||||
|
||||
window.electron.on("openDesktopCapturerSourcePicker", () => {
|
||||
window.electron.on("openDesktopCapturerSourcePicker", async () => {
|
||||
const { finished } = Modal.createDialog(DesktopCapturerSourcePicker);
|
||||
finished.then(([source]) => {
|
||||
if (!source) return;
|
||||
this.ipc.call("callDisplayMediaCallback", source);
|
||||
});
|
||||
const [source] = await finished;
|
||||
// getDisplayMedia promise does not return if no dummy is passed here as source
|
||||
await this.ipc.call("callDisplayMediaCallback", source ?? { id: "", name: "", thumbnailURL: "" });
|
||||
});
|
||||
|
||||
this.ipc.call("startSSOFlow", this.ssoID);
|
||||
void this.ipc.call("startSSOFlow", this.ssoID);
|
||||
|
||||
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
}
|
||||
@@ -192,7 +208,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
),
|
||||
initial: getInitialLetter(r.name),
|
||||
}));
|
||||
this.ipc.call("breadcrumbs", rooms);
|
||||
void this.ipc.call("breadcrumbs", rooms);
|
||||
};
|
||||
|
||||
private onUpdateDownloaded = async (ev: Event, { releaseNotes, releaseName }: SquirrelUpdate): Promise<void> => {
|
||||
@@ -258,7 +274,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
const handler = notification.onclick as Function;
|
||||
notification.onclick = (): void => {
|
||||
handler?.();
|
||||
this.ipc.call("focusWindow");
|
||||
void this.ipc.call("focusWindow");
|
||||
};
|
||||
|
||||
return notification;
|
||||
@@ -374,10 +390,10 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
return this.ipc.call("getAvailableSpellCheckLanguages");
|
||||
}
|
||||
|
||||
public getSSOCallbackUrl(fragmentAfterLogin: string): URL {
|
||||
public getSSOCallbackUrl(fragmentAfterLogin?: string): URL {
|
||||
const url = super.getSSOCallbackUrl(fragmentAfterLogin);
|
||||
url.protocol = "element";
|
||||
url.searchParams.set("element-desktop-ssoid", this.ssoID);
|
||||
url.searchParams.set(SSO_ID_KEY, this.ssoID);
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -396,7 +412,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
}
|
||||
|
||||
public navigateForwardBack(back: boolean): void {
|
||||
this.ipc.call(back ? "navigateBack" : "navigateForward");
|
||||
void this.ipc.call(back ? "navigateBack" : "navigateForward");
|
||||
}
|
||||
|
||||
public overrideBrowserShortcuts(): boolean {
|
||||
@@ -435,4 +451,35 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
await this.ipc.call("clearStorage");
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
public get baseUrl(): string {
|
||||
// This configuration is element-desktop specific so the types here do not know about it
|
||||
return (SdkConfig.get() as unknown as Record<string, string>)["web_base_url"] ?? "https://app.element.io";
|
||||
}
|
||||
|
||||
public get defaultOidcClientUri(): string {
|
||||
// Default to element.io as our scheme `io.element.desktop` is within its scope on default MAS policies
|
||||
return "https://element.io";
|
||||
}
|
||||
|
||||
public async getOidcClientMetadata(): Promise<OidcRegistrationClientMetadata> {
|
||||
const baseMetadata = await super.getOidcClientMetadata();
|
||||
return {
|
||||
...baseMetadata,
|
||||
applicationType: "native",
|
||||
};
|
||||
}
|
||||
|
||||
public getOidcClientState(): string {
|
||||
return `:${SSO_ID_KEY}:${this.ssoID}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL to return to after a successful OIDC authentication
|
||||
*/
|
||||
public getOidcCallbackUrl(): URL {
|
||||
const url = super.getOidcCallbackUrl();
|
||||
url.protocol = "io.element.desktop";
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017-2020 New Vector Ltd
|
||||
Copyright 2017-2020, 2024 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.
|
||||
@@ -44,9 +44,41 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
// Register service worker if available on this platform
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("sw.js");
|
||||
|
||||
// Register the service worker in the background
|
||||
this.tryRegisterServiceWorker().catch((e) => console.error("Error registering/updating service worker:", e));
|
||||
}
|
||||
|
||||
private async tryRegisterServiceWorker(): Promise<void> {
|
||||
if (!("serviceWorker" in navigator)) {
|
||||
return; // not available on this platform - don't try to register the service worker
|
||||
}
|
||||
|
||||
// sw.js is exported by webpack, sourced from `/src/serviceworker/index.ts`
|
||||
const registration = await navigator.serviceWorker.register("sw.js");
|
||||
if (!registration) {
|
||||
// Registration didn't work for some reason - assume failed and ignore.
|
||||
// This typically happens in Jest.
|
||||
return;
|
||||
}
|
||||
|
||||
await registration.update();
|
||||
navigator.serviceWorker.addEventListener("message", this.onServiceWorkerPostMessage.bind(this));
|
||||
}
|
||||
|
||||
private onServiceWorkerPostMessage(event: MessageEvent): void {
|
||||
try {
|
||||
if (event.data?.["type"] === "userinfo" && event.data?.["responseKey"]) {
|
||||
const userId = localStorage.getItem("mx_user_id");
|
||||
const deviceId = localStorage.getItem("mx_device_id");
|
||||
event.source!.postMessage({
|
||||
responseKey: event.data["responseKey"],
|
||||
userId,
|
||||
deviceId,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error responding to service worker: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +113,10 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||
// annoyingly, the latest spec says this returns a
|
||||
// promise, but this is only supported in Chrome 46
|
||||
// and Firefox 47, so adapt the callback API.
|
||||
return new Promise(function (resolve) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
window.Notification.requestPermission((result) => {
|
||||
resolve(result);
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -116,7 +148,7 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||
// Ideally, loading an old copy would be impossible with the
|
||||
// cache-control: nocache HTTP header set, but Firefox doesn't always obey it :/
|
||||
console.log("startUpdater, current version is " + getNormalizedAppVersion(WebPlatform.VERSION));
|
||||
this.pollForUpdate((version: string, newVersion: string) => {
|
||||
void this.pollForUpdate((version: string, newVersion: string) => {
|
||||
const query = parseQs(location);
|
||||
if (query.updated) {
|
||||
console.log("Update reloaded but still on an old version, stopping");
|
||||
@@ -175,7 +207,7 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||
|
||||
public startUpdateCheck(): void {
|
||||
super.startUpdateCheck();
|
||||
this.pollForUpdate(showUpdateToast, hideUpdateToast).then((updateState) => {
|
||||
void this.pollForUpdate(showUpdateToast, hideUpdateToast).then((updateState) => {
|
||||
dis.dispatch<CheckUpdatesPayload>({
|
||||
action: Action.CheckUpdates,
|
||||
...updateState,
|
||||
|
||||
@@ -35,7 +35,7 @@ export function initRageshake(): Promise<void> {
|
||||
// we manually check persistence for rageshakes ourselves
|
||||
const prom = rageshake.init(/*setUpPersistence=*/ false);
|
||||
prom.then(
|
||||
() => {
|
||||
async () => {
|
||||
logger.log("Initialised rageshake.");
|
||||
logger.log(
|
||||
"To fix line numbers in Chrome: " +
|
||||
@@ -48,7 +48,7 @@ export function initRageshake(): Promise<void> {
|
||||
rageshake.flush();
|
||||
});
|
||||
|
||||
rageshake.cleanup();
|
||||
await rageshake.cleanup();
|
||||
},
|
||||
(err) => {
|
||||
logger.error("Failed to initialise rageshake: " + err);
|
||||
|
||||
@@ -1,684 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/* loading.js: test the myriad paths we have for loading the application */
|
||||
|
||||
import "fake-indexeddb/auto";
|
||||
import React from "react";
|
||||
import { render, screen, fireEvent, waitFor, RenderResult, waitForElementToBeRemoved } from "@testing-library/react";
|
||||
import PlatformPeg from "matrix-react-sdk/src/PlatformPeg";
|
||||
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
|
||||
import MatrixChat from "matrix-react-sdk/src/components/structures/MatrixChat";
|
||||
import dis from "matrix-react-sdk/src/dispatcher/dispatcher";
|
||||
import MockHttpBackend from "matrix-mock-request";
|
||||
import { ValidatedServerConfig } from "matrix-react-sdk/src/utils/ValidatedServerConfig";
|
||||
import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
|
||||
import { QueryDict, sleep } from "matrix-js-sdk/src/utils";
|
||||
import { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions";
|
||||
import { ActionPayload } from "matrix-react-sdk/src/dispatcher/payloads";
|
||||
|
||||
import "../jest-mocks";
|
||||
import WebPlatform from "../../src/vector/platform/WebPlatform";
|
||||
import { parseQs, parseQsFromFragment } from "../../src/vector/url_utils";
|
||||
import { cleanLocalstorage, deleteIndexedDB, waitForLoadingSpinner, waitForWelcomeComponent } from "../test-utils";
|
||||
|
||||
const DEFAULT_HS_URL = "http://my_server";
|
||||
const DEFAULT_IS_URL = "http://my_is";
|
||||
|
||||
/** The matrix versions our mock server claims to support */
|
||||
const SERVER_SUPPORTED_MATRIX_VERSIONS = ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9"];
|
||||
|
||||
describe("loading:", function () {
|
||||
let httpBackend: MockHttpBackend;
|
||||
|
||||
// an Object simulating the window.location
|
||||
let windowLocation: Location | undefined;
|
||||
|
||||
// the mounted MatrixChat
|
||||
let matrixChat: RenderResult | undefined;
|
||||
|
||||
// a promise which resolves when the MatrixChat calls onTokenLoginCompleted
|
||||
let tokenLoginCompletePromise: Promise<void> | undefined;
|
||||
|
||||
beforeEach(function () {
|
||||
httpBackend = new MockHttpBackend();
|
||||
// @ts-ignore
|
||||
window.fetch = httpBackend.fetchFn;
|
||||
|
||||
windowLocation = undefined;
|
||||
matrixChat = undefined;
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
console.log(`${Date.now()}: loading: afterEach`);
|
||||
matrixChat?.unmount();
|
||||
// unmounting should have cleared the MatrixClientPeg
|
||||
expect(MatrixClientPeg.get()).toBe(null);
|
||||
|
||||
// clear the indexeddbs so we can start from a clean slate next time.
|
||||
await Promise.all([deleteIndexedDB("matrix-js-sdk:crypto"), deleteIndexedDB("matrix-js-sdk:riot-web-sync")]);
|
||||
cleanLocalstorage();
|
||||
console.log(`${Date.now()}: loading: afterEach complete`);
|
||||
});
|
||||
|
||||
/* simulate the load process done by index.js
|
||||
*
|
||||
* TODO: it would be nice to factor some of this stuff out of index.js so
|
||||
* that we can test it rather than our own implementation of it.
|
||||
*/
|
||||
function loadApp(
|
||||
opts: {
|
||||
queryString?: string;
|
||||
uriFragment?: string;
|
||||
config?: IConfigOptions;
|
||||
} = {},
|
||||
): void {
|
||||
const queryString = opts.queryString || "";
|
||||
const uriFragment = opts.uriFragment || "";
|
||||
|
||||
windowLocation = {
|
||||
search: queryString,
|
||||
hash: uriFragment,
|
||||
toString: function (): string {
|
||||
return this.search + this.hash;
|
||||
},
|
||||
} as Location;
|
||||
|
||||
function onNewScreen(screen: string): void {
|
||||
console.log(Date.now() + " newscreen " + screen);
|
||||
const hash = "#/" + screen;
|
||||
windowLocation!.hash = hash;
|
||||
console.log(Date.now() + " browser URI now " + windowLocation);
|
||||
}
|
||||
|
||||
// Parse the given window.location and return parameters that can be used when calling
|
||||
// MatrixChat.showScreen(screen, params)
|
||||
function getScreenFromLocation(location: Location): { screen: string; params: QueryDict } {
|
||||
const fragparts = parseQsFromFragment(location);
|
||||
return {
|
||||
screen: fragparts.location.substring(1),
|
||||
params: fragparts.params,
|
||||
};
|
||||
}
|
||||
|
||||
const fragParts = parseQsFromFragment(windowLocation);
|
||||
|
||||
const config = {
|
||||
default_hs_url: DEFAULT_HS_URL,
|
||||
default_is_url: DEFAULT_IS_URL,
|
||||
validated_server_config: {
|
||||
hsUrl: DEFAULT_HS_URL,
|
||||
hsName: "TEST_ENVIRONMENT",
|
||||
hsNameIsDifferent: false, // yes, we lie
|
||||
isUrl: DEFAULT_IS_URL,
|
||||
} as ValidatedServerConfig,
|
||||
embedded_pages: {
|
||||
home_url: "data:text/html;charset=utf-8;base64,PGh0bWw+PC9odG1sPg==",
|
||||
},
|
||||
...(opts.config ?? {}),
|
||||
} as IConfigOptions;
|
||||
|
||||
PlatformPeg.set(new WebPlatform());
|
||||
|
||||
const params = parseQs(windowLocation);
|
||||
|
||||
tokenLoginCompletePromise = new Promise<void>((resolve) => {
|
||||
matrixChat = render(
|
||||
<MatrixChat
|
||||
onNewScreen={onNewScreen}
|
||||
config={config!}
|
||||
realQueryParams={params}
|
||||
startingFragmentQueryParams={fragParts.params}
|
||||
enableGuest={true}
|
||||
onTokenLoginCompleted={resolve}
|
||||
initialScreenAfterLogin={getScreenFromLocation(windowLocation!)}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// set an expectation that we will get a call to /sync, then flush
|
||||
// http requests until we do.
|
||||
//
|
||||
// returns a promise resolving to the received request
|
||||
async function expectAndAwaitSync(opts?: { isGuest?: boolean }): Promise<any> {
|
||||
let syncRequest: (typeof MockHttpBackend.prototype.requests)[number] | null = null;
|
||||
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
|
||||
versions: SERVER_SUPPORTED_MATRIX_VERSIONS,
|
||||
unstable_features: {},
|
||||
});
|
||||
const isGuest = opts?.isGuest;
|
||||
if (!isGuest) {
|
||||
// the call to create the LL filter
|
||||
httpBackend.when("POST", "/filter").respond(200, { filter_id: "llfid" });
|
||||
httpBackend.when("GET", "/pushrules").respond(200, {});
|
||||
}
|
||||
httpBackend
|
||||
.when("GET", "/sync")
|
||||
.check((r) => {
|
||||
syncRequest = r;
|
||||
})
|
||||
.respond(200, {});
|
||||
|
||||
for (let attempts = 10; attempts > 0; attempts--) {
|
||||
console.log(Date.now() + " waiting for /sync");
|
||||
if (syncRequest) {
|
||||
return syncRequest;
|
||||
}
|
||||
await httpBackend.flush(undefined);
|
||||
}
|
||||
throw new Error("Gave up waiting for /sync");
|
||||
}
|
||||
|
||||
describe("Clean load with no stored credentials:", function () {
|
||||
it("gives a welcome page by default", function () {
|
||||
loadApp();
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(403, "Guest access is disabled");
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return waitForWelcomeComponent(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
return waitFor(() => expect(windowLocation?.hash).toEqual("#/welcome"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should follow the original link after successful login", function () {
|
||||
loadApp({
|
||||
uriFragment: "#/room/!room:id",
|
||||
});
|
||||
|
||||
// Pass the liveliness checks
|
||||
httpBackend.when("GET", "/versions").respond(200, { versions: SERVER_SUPPORTED_MATRIX_VERSIONS });
|
||||
httpBackend.when("GET", "/_matrix/identity/v2").respond(200, {});
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(403, "Guest access is disabled");
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return sleep(10);
|
||||
})
|
||||
.then(() => {
|
||||
return moveFromWelcomeToLogin(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
return completeLogin(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
return awaitRoomView(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(windowLocation?.hash).toEqual("#/room/!room:id");
|
||||
|
||||
// and the localstorage should have been updated
|
||||
expect(localStorage.getItem("mx_user_id")).toEqual("@user:id");
|
||||
expect(localStorage.getItem("mx_access_token")).toEqual("access_token");
|
||||
expect(localStorage.getItem("mx_hs_url")).toEqual(DEFAULT_HS_URL);
|
||||
expect(localStorage.getItem("mx_is_url")).toEqual(DEFAULT_IS_URL);
|
||||
});
|
||||
});
|
||||
|
||||
it.skip("should not register as a guest when using a #/login link", function () {
|
||||
loadApp({
|
||||
uriFragment: "#/login",
|
||||
});
|
||||
|
||||
// Pass the liveliness checks
|
||||
httpBackend.when("GET", "/versions").respond(200, { versions: SERVER_SUPPORTED_MATRIX_VERSIONS });
|
||||
httpBackend.when("GET", "/_matrix/identity/v2").respond(200, {});
|
||||
|
||||
return awaitLoginComponent(matrixChat)
|
||||
.then(async () => {
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||
// we expect a single <Login> component
|
||||
await screen.findByRole("main");
|
||||
screen.getAllByText("Sign in");
|
||||
|
||||
// the only outstanding request should be a GET /login
|
||||
// (in particular there should be no /register request for
|
||||
// guest registration).
|
||||
const allowedRequests = ["/_matrix/client/v3/login", "/versions", "/_matrix/identity/v2"];
|
||||
for (const req of httpBackend.requests) {
|
||||
if (req.method === "GET" && allowedRequests.find((p) => req.path.endsWith(p))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected HTTP request to ${req}`);
|
||||
}
|
||||
return completeLogin(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
expect(matrixChat?.container.querySelector(".mx_HomePage")).toBeTruthy();
|
||||
expect(windowLocation?.hash).toEqual("#/home");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("MatrixClient rehydrated from stored credentials:", function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.setItem("mx_hs_url", "http://localhost");
|
||||
localStorage.setItem("mx_is_url", "http://localhost");
|
||||
localStorage.setItem("mx_access_token", "access_token");
|
||||
localStorage.setItem("mx_user_id", "@me:localhost");
|
||||
localStorage.setItem("mx_last_room_id", "!last_room:id");
|
||||
|
||||
// Create a crypto store as well to satisfy storage consistency checks
|
||||
const cryptoStore = new IndexedDBCryptoStore(indexedDB, "matrix-js-sdk:crypto");
|
||||
await cryptoStore.startup();
|
||||
});
|
||||
|
||||
it("shows the last known room by default", function () {
|
||||
loadApp();
|
||||
|
||||
return awaitLoggedIn(matrixChat!)
|
||||
.then(() => {
|
||||
// we are logged in - let the sync complete
|
||||
return expectAndAwaitSync();
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
return awaitRoomView(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(windowLocation?.hash).toEqual("#/room/!last_room:id");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a home page by default if we have no joined rooms", function () {
|
||||
localStorage.removeItem("mx_last_room_id");
|
||||
|
||||
loadApp();
|
||||
|
||||
return awaitLoggedIn(matrixChat!)
|
||||
.then(() => {
|
||||
// we are logged in - let the sync complete
|
||||
return expectAndAwaitSync();
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a home page
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(matrixChat?.container.querySelector(".mx_HomePage")).toBeTruthy();
|
||||
expect(windowLocation?.hash).toEqual("#/home");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a room view if we followed a room link", function () {
|
||||
loadApp({
|
||||
uriFragment: "#/room/!room:id",
|
||||
});
|
||||
|
||||
return awaitLoggedIn(matrixChat!)
|
||||
.then(() => {
|
||||
// we are logged in - let the sync complete
|
||||
return expectAndAwaitSync();
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
return awaitRoomView(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(windowLocation?.hash).toEqual("#/room/!room:id");
|
||||
});
|
||||
});
|
||||
|
||||
describe("/#/login link:", function () {
|
||||
beforeEach(function () {
|
||||
loadApp({
|
||||
uriFragment: "#/login",
|
||||
});
|
||||
|
||||
// give the UI a chance to display
|
||||
return expectAndAwaitSync();
|
||||
});
|
||||
|
||||
it("does not show a login view", async function () {
|
||||
await awaitRoomView(matrixChat);
|
||||
|
||||
await screen.getByRole("tree", { name: "Spaces" });
|
||||
expect(screen.queryAllByText("Sign in")).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Guest auto-registration:", function () {
|
||||
it("shows a welcome page by default", function () {
|
||||
loadApp();
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@guest:localhost",
|
||||
access_token: "secret_token",
|
||||
});
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
return awaitLoggedIn(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
// we are logged in - let the sync complete
|
||||
return expectAndAwaitSync({ isGuest: true });
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a welcome page
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(matrixChat?.container.querySelector(".mx_Welcome")).toBeTruthy();
|
||||
expect(windowLocation?.hash).toEqual("#/welcome");
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the default homeserver to register with", function () {
|
||||
loadApp();
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.path.startsWith(DEFAULT_HS_URL)).toBe(true);
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@guest:localhost",
|
||||
access_token: "secret_token",
|
||||
});
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
return awaitLoggedIn(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
return expectAndAwaitSync({ isGuest: true });
|
||||
})
|
||||
.then((req) => {
|
||||
expect(req.path.startsWith(DEFAULT_HS_URL)).toBe(true);
|
||||
|
||||
// once the sync completes, we should have a welcome page
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(matrixChat?.container.querySelector(".mx_Welcome")).toBeTruthy();
|
||||
expect(windowLocation?.hash).toEqual("#/welcome");
|
||||
expect(MatrixClientPeg.safeGet().baseUrl).toEqual(DEFAULT_HS_URL);
|
||||
expect(MatrixClientPeg.safeGet().idBaseUrl).toEqual(DEFAULT_IS_URL);
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a room view if we followed a room link", function () {
|
||||
loadApp({
|
||||
uriFragment: "#/room/!room:id",
|
||||
});
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@guest:localhost",
|
||||
access_token: "secret_token",
|
||||
});
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
return awaitLoggedIn(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
return expectAndAwaitSync({ isGuest: true });
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
return awaitRoomView(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(windowLocation?.hash).toEqual("#/room/!room:id");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Login as user", function () {
|
||||
beforeEach(function () {
|
||||
// first we have to load the homepage
|
||||
loadApp();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@guest:localhost",
|
||||
access_token: "secret_token",
|
||||
});
|
||||
|
||||
return httpBackend
|
||||
.flush(undefined)
|
||||
.then(() => {
|
||||
return awaitLoggedIn(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
// we got a sync spinner - let the sync complete
|
||||
return expectAndAwaitSync();
|
||||
})
|
||||
.then(async () => {
|
||||
// once the sync completes, we should have a home page
|
||||
await waitFor(() => matrixChat?.container.querySelector(".mx_HomePage"));
|
||||
|
||||
// we simulate a click on the 'login' button by firing off
|
||||
// the relevant dispatch.
|
||||
//
|
||||
// XXX: is it an anti-pattern to access the react-sdk's
|
||||
// dispatcher in this way? Is it better to find the login
|
||||
// button and simulate a click? (we might have to arrange
|
||||
// for it to be shown - it's not always, due to the
|
||||
// collapsing left panel
|
||||
|
||||
dis.dispatch({ action: "start_login" });
|
||||
|
||||
return awaitLoginComponent(matrixChat);
|
||||
});
|
||||
});
|
||||
|
||||
it("should give us a login page", async function () {
|
||||
// we expect a single <Login> component
|
||||
await screen.findByRole("main");
|
||||
screen.getAllByText("Sign in");
|
||||
|
||||
expect(windowLocation?.hash).toEqual("#/login");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Token login:", function () {
|
||||
it("logs in successfully", function () {
|
||||
localStorage.setItem("mx_sso_hs_url", "https://homeserver");
|
||||
localStorage.setItem("mx_sso_is_url", "https://idserver");
|
||||
loadApp({
|
||||
queryString: "?loginToken=secretToken",
|
||||
});
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// we expect a spinner while we're logging in
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/login")
|
||||
.check(function (req) {
|
||||
expect(req.path).toMatch(new RegExp("^https://homeserver/"));
|
||||
expect(req.data.type).toEqual("m.login.token");
|
||||
expect(req.data.token).toEqual("secretToken");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@user:localhost",
|
||||
device_id: "DEVICE_ID",
|
||||
access_token: "access_token",
|
||||
});
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
// at this point, MatrixChat should fire onTokenLoginCompleted, which
|
||||
// makes index.js reload the app. We're not going to attempt to
|
||||
// simulate the reload - just check that things are left in the
|
||||
// right state for the reloaded app.
|
||||
|
||||
return tokenLoginCompletePromise;
|
||||
})
|
||||
.then(() => {
|
||||
return expectAndAwaitSync().catch((e) => {
|
||||
throw new Error("Never got /sync after login: did the client start?");
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// check that the localstorage has been set up in such a way that
|
||||
// the reloaded app can pick up where we leave off.
|
||||
expect(localStorage.getItem("mx_user_id")).toEqual("@user:localhost");
|
||||
expect(localStorage.getItem("mx_access_token")).toEqual("access_token");
|
||||
expect(localStorage.getItem("mx_hs_url")).toEqual("https://homeserver");
|
||||
expect(localStorage.getItem("mx_is_url")).toEqual("https://idserver");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// check that we have a Login component, send a 'user:pass' login,
|
||||
// and await the HTTP requests.
|
||||
async function completeLogin(matrixChat: RenderResult): Promise<void> {
|
||||
// When we switch to the login component, it'll hit the login endpoint
|
||||
// for proof of life and to get flows. We'll only give it one option.
|
||||
httpBackend.when("GET", "/login").respond(200, { flows: [{ type: "m.login.password" }] });
|
||||
httpBackend.flush(undefined); // We already would have tried the GET /login request
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/login")
|
||||
.check(function (req) {
|
||||
expect(req.data.type).toEqual("m.login.password");
|
||||
expect(req.data.identifier.type).toEqual("m.id.user");
|
||||
expect(req.data.identifier.user).toEqual("user");
|
||||
expect(req.data.password).toEqual("pass");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@user:id",
|
||||
device_id: "DEVICE_ID",
|
||||
access_token: "access_token",
|
||||
});
|
||||
|
||||
// Give the component some time to finish processing the login flows before continuing.
|
||||
await waitFor(() => expect(matrixChat?.container.querySelector("#mx_LoginForm_username")).toBeTruthy());
|
||||
|
||||
// Enter login details
|
||||
fireEvent.change(matrixChat.container.querySelector("#mx_LoginForm_username")!, { target: { value: "user" } });
|
||||
fireEvent.change(matrixChat.container.querySelector("#mx_LoginForm_password")!, { target: { value: "pass" } });
|
||||
fireEvent.click(screen.getByText("Sign in", { selector: ".mx_Login_submit" }));
|
||||
|
||||
return httpBackend
|
||||
.flush(undefined)
|
||||
.then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return sleep(1);
|
||||
})
|
||||
.then(() => {
|
||||
return expectAndAwaitSync().catch((e) => {
|
||||
throw new Error("Never got /sync after login: did the client start?");
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function awaitLoggedIn(matrixChat: RenderResult): Promise<void> {
|
||||
if (matrixChat.container.querySelector(".mx_MatrixChat_wrapper")) return; // already logged in
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const onAction = ({ action }: ActionPayload): void => {
|
||||
if (action !== "on_logged_in") {
|
||||
return;
|
||||
}
|
||||
console.log(Date.now() + ": Received on_logged_in action");
|
||||
dis.unregister(dispatcherRef);
|
||||
resolve(sleep(1));
|
||||
};
|
||||
const dispatcherRef = dis.register(onAction);
|
||||
console.log(Date.now() + ": Waiting for on_logged_in action");
|
||||
});
|
||||
}
|
||||
|
||||
async function awaitRoomView(matrixChat?: RenderResult): Promise<void> {
|
||||
await waitFor(() => matrixChat?.container.querySelector(".mx_RoomView"));
|
||||
}
|
||||
|
||||
async function awaitLoginComponent(matrixChat?: RenderResult): Promise<void> {
|
||||
await waitFor(() => matrixChat?.container.querySelector(".mx_AuthPage"));
|
||||
}
|
||||
|
||||
function moveFromWelcomeToLogin(matrixChat?: RenderResult): Promise<void> {
|
||||
dis.dispatch({ action: "start_login" });
|
||||
return awaitLoginComponent(matrixChat);
|
||||
}
|
||||
@@ -16,40 +16,6 @@ limitations under the License.
|
||||
|
||||
import { RenderResult, screen, waitFor } from "@testing-library/react";
|
||||
|
||||
export function cleanLocalstorage(): void {
|
||||
window.localStorage.clear();
|
||||
}
|
||||
|
||||
export function deleteIndexedDB(dbName: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!window.indexedDB) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
console.log(`${startTime}: Removing indexeddb instance: ${dbName}`);
|
||||
const req = window.indexedDB.deleteDatabase(dbName);
|
||||
|
||||
req.onblocked = (): void => {
|
||||
console.log(`${Date.now()}: can't yet delete indexeddb ${dbName} because it is open elsewhere`);
|
||||
};
|
||||
|
||||
req.onerror = (ev): void => {
|
||||
reject(new Error(`${Date.now()}: unable to delete indexeddb ${dbName}: ${req.error?.message}`));
|
||||
};
|
||||
|
||||
req.onsuccess = (): void => {
|
||||
const now = Date.now();
|
||||
console.log(`${now}: Removed indexeddb instance: ${dbName} in ${now - startTime} ms`);
|
||||
resolve();
|
||||
};
|
||||
}).catch((e) => {
|
||||
console.error(`${Date.now()}: Error removing indexeddb instance ${dbName}: ${e}`);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
// wait for loading page
|
||||
export async function waitForLoadingSpinner(): Promise<void> {
|
||||
await screen.findByRole("progressbar");
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
"esModuleInterop": true,
|
||||
"module": "es2022",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2016",
|
||||
"target": "es2018",
|
||||
"noUnusedLocals": true,
|
||||
"sourceMap": false,
|
||||
"outDir": "./lib",
|
||||
"declaration": true,
|
||||
"jsx": "react",
|
||||
"lib": ["es2021", "dom", "dom.iterable"],
|
||||
"lib": ["es2022", "dom", "dom.iterable"],
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
@@ -22,6 +22,7 @@
|
||||
"./node_modules/matrix-react-sdk/src/@types/opus-recorder.d.ts",
|
||||
"./node_modules/matrix-react-sdk/src/@types/png-chunks-extract.d.ts",
|
||||
"./node_modules/matrix-react-sdk/src/@types/sanitize-html.d.ts",
|
||||
"./node_modules/matrix-react-sdk/src/@types/matrix-js-sdk.d.ts",
|
||||
"./src/**/*.ts",
|
||||
"./src/**/*.tsx",
|
||||
"./test/**/*.ts",
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
# The values of this are provided to `yarn add` for inclusion.
|
||||
modules:
|
||||
- "@nordeck/element-web-guest-module@1.0.0"
|
||||
- "@nordeck/element-web-opendesk-module@0.3.0"
|
||||
- "@nordeck/element-web-opendesk-module@0.4.0"
|
||||
- "@nordeck/element-web-widget-lifecycle-module@1.0.1"
|
||||
- "@nordeck/element-web-widget-toggles-module@0.1.0"
|
||||
|
||||
@@ -8,15 +8,8 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
||||
const HtmlWebpackInjectPreload = require("@principalstudio/html-webpack-inject-preload");
|
||||
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");
|
||||
const crypto = require("crypto");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
|
||||
// XXX: mangle Crypto::createHash to replace md4 with sha256, output.hashFunction is insufficient as multiple bits
|
||||
// of webpack hardcode md4. The proper fix it to upgrade to webpack 5.
|
||||
const createHash = crypto.createHash;
|
||||
crypto.createHash = (algorithm, options) => createHash(algorithm === "md4" ? "sha256" : algorithm, options);
|
||||
|
||||
// Environment variables
|
||||
// RIOT_OG_IMAGE_URL: specifies the URL to the image which should be used for the opengraph logo.
|
||||
// CSP_EXTRA_SOURCE: specifies a URL which should be appended to each CSP directive which uses 'self',
|
||||
@@ -160,6 +153,10 @@ module.exports = (env, argv) => {
|
||||
mobileguide: "./src/vector/mobile_guide/index.ts",
|
||||
jitsi: "./src/vector/jitsi/index.ts",
|
||||
usercontent: "./node_modules/matrix-react-sdk/src/usercontent/index.ts",
|
||||
serviceworker: {
|
||||
import: "./src/serviceworker/index.ts",
|
||||
filename: "sw.js", // update WebPlatform if this changes
|
||||
},
|
||||
...(useHMR ? {} : cssThemes),
|
||||
},
|
||||
|
||||
@@ -277,10 +274,6 @@ module.exports = (env, argv) => {
|
||||
// there is no need for webpack to parse them - they can just be
|
||||
// included as-is.
|
||||
/highlight\.js[\\/]lib[\\/]languages/,
|
||||
|
||||
// olm takes ages for webpack to process, and it's already heavily
|
||||
// optimised, so there is little to gain by us uglifying it.
|
||||
/olm[\\/](javascript[\\/])?olm\.js$/,
|
||||
],
|
||||
rules: [
|
||||
useHMR && {
|
||||
@@ -319,6 +312,7 @@ module.exports = (env, argv) => {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
plugins: enableMinification ? ["babel-plugin-jsx-remove-data-test-id"] : [],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -330,15 +324,16 @@ module.exports = (env, argv) => {
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
esModule: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "postcss-loader",
|
||||
ident: "postcss",
|
||||
options: {
|
||||
"sourceMap": true,
|
||||
"postcssOptions": {
|
||||
"plugins": () => [
|
||||
sourceMap: true,
|
||||
postcssOptions: () => ({
|
||||
"plugins": [
|
||||
// Note that we use significantly fewer plugins on the plain
|
||||
// CSS parser. If we start to parse plain CSS, we end with all
|
||||
// kinds of nasty problems (like stylesheets not loading).
|
||||
@@ -365,7 +360,8 @@ module.exports = (env, argv) => {
|
||||
require("postcss-preset-env")({ stage: 3, browsers: "last 2 versions" }),
|
||||
],
|
||||
"parser": "postcss-scss",
|
||||
},
|
||||
"local-plugins": true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -414,15 +410,16 @@ module.exports = (env, argv) => {
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
esModule: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "postcss-loader",
|
||||
ident: "postcss",
|
||||
options: {
|
||||
"sourceMap": true,
|
||||
"postcssOptions": {
|
||||
"plugins": () => [
|
||||
sourceMap: true,
|
||||
postcssOptions: () => ({
|
||||
"plugins": [
|
||||
// Note that we use slightly different plugins for PostCSS.
|
||||
require("postcss-import")(),
|
||||
require("postcss-mixins")(),
|
||||
@@ -436,25 +433,12 @@ module.exports = (env, argv) => {
|
||||
require("postcss-preset-env")({ stage: 3, browsers: "last 2 versions" }),
|
||||
],
|
||||
"parser": "postcss-scss",
|
||||
},
|
||||
"local-plugins": true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// the olm library wants to load its own wasm, rather than have webpack do it.
|
||||
// We therefore use the `file-loader` to tell webpack to dump the contents to
|
||||
// a separate file and return the name, and override the default `type` for `.wasm` files
|
||||
// (which is `webassembly/experimental` under webpack 4) to stop webpack trying to interpret
|
||||
// the filename as webassembly. (see also https://github.com/webpack/webpack/issues/6725)
|
||||
test: /olm\.wasm$/,
|
||||
loader: "file-loader",
|
||||
type: "javascript/auto",
|
||||
options: {
|
||||
name: "[name].[hash:7].[ext]",
|
||||
outputPath: ".",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Fix up the name of the opus-recorder worker (react-sdk dependency).
|
||||
// We more or less just want it to be clear it's for opus and not something else.
|
||||
@@ -496,8 +480,11 @@ module.exports = (env, argv) => {
|
||||
},
|
||||
},
|
||||
{
|
||||
// Same deal as olm.wasm: the decoderWorker wants to load the wasm artifact
|
||||
// itself.
|
||||
// The decoderWorker wants to load its own wasm, rather than have webpack do it.
|
||||
// We therefore use the `file-loader` to tell webpack to dump the contents to
|
||||
// a separate file and return the name, and override the default `type` for `.wasm` files
|
||||
// (which is `webassembly/experimental` under webpack 4) to stop webpack trying to interpret
|
||||
// the filename as webassembly. (see also https://github.com/webpack/webpack/issues/6725)
|
||||
test: /decoderWorker\.min\.wasm$/,
|
||||
loader: "file-loader",
|
||||
type: "javascript/auto",
|
||||
@@ -545,10 +532,20 @@ module.exports = (env, argv) => {
|
||||
// props set on the svg will override defaults
|
||||
expandProps: "end",
|
||||
svgoConfig: {
|
||||
plugins: {
|
||||
plugins: [
|
||||
{
|
||||
name: "preset-default",
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
// generates a viewbox if missing
|
||||
removeDimensions: true,
|
||||
},
|
||||
{ name: "removeDimensions" },
|
||||
// https://github.com/facebook/docusaurus/issues/8297
|
||||
{ name: "prefixIds" },
|
||||
],
|
||||
},
|
||||
/**
|
||||
* Forwards the React ref to the root SVG element
|
||||
@@ -645,8 +642,8 @@ module.exports = (env, argv) => {
|
||||
|
||||
// This exports our CSS using the splitChunks and loaders above.
|
||||
new MiniCssExtractPlugin({
|
||||
filename: useHMR ? "bundles/[name].css" : "bundles/[hash]/[name].css",
|
||||
chunkFilename: useHMR ? "bundles/[name].css" : "bundles/[hash]/[name].css",
|
||||
filename: useHMR ? "bundles/[name].css" : "bundles/[fullhash]/[name].css",
|
||||
chunkFilename: useHMR ? "bundles/[name].css" : "bundles/[fullhash]/[name].css",
|
||||
ignoreOrder: false, // Enable to remove warnings about conflicting order
|
||||
}),
|
||||
|
||||
@@ -658,7 +655,7 @@ module.exports = (env, argv) => {
|
||||
// HtmlWebpackPlugin will screw up our formatting like the names
|
||||
// of the themes and which chunks we actually care about.
|
||||
inject: false,
|
||||
excludeChunks: ["mobileguide", "usercontent", "jitsi"],
|
||||
excludeChunks: ["mobileguide", "usercontent", "jitsi", "serviceworker"],
|
||||
minify: false,
|
||||
templateParameters: {
|
||||
og_image_url: ogImageUrl,
|
||||
@@ -708,9 +705,11 @@ module.exports = (env, argv) => {
|
||||
files: [{ match: /.*Inter.*\.woff2$/ }],
|
||||
}),
|
||||
|
||||
// upload to sentry if sentry env is present
|
||||
// Upload to sentry if sentry env is present
|
||||
// This plugin throws an error on import on some platforms like ppc64le & s390x even if the plugin isn't called,
|
||||
// so we require it conditionally.
|
||||
process.env.SENTRY_DSN &&
|
||||
sentryWebpackPlugin({
|
||||
require("@sentry/webpack-plugin").sentryWebpackPlugin({
|
||||
release: process.env.VERSION,
|
||||
sourcemaps: {
|
||||
paths: "./webapp/bundles/**",
|
||||
@@ -726,17 +725,16 @@ module.exports = (env, argv) => {
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
"res/apple-app-site-association",
|
||||
{ from: ".well-known/**", context: path.resolve(__dirname, "res") },
|
||||
"res/jitsi_external_api.min.js",
|
||||
"res/jitsi_external_api.min.js.LICENSE.txt",
|
||||
"res/manifest.json",
|
||||
"res/sw.js",
|
||||
"res/welcome.html",
|
||||
{ from: "welcome/**", context: path.resolve(__dirname, "res") },
|
||||
{ from: "themes/**", context: path.resolve(__dirname, "res") },
|
||||
{ from: "vector-icons/**", context: path.resolve(__dirname, "res") },
|
||||
{ from: "decoder-ring/**", context: path.resolve(__dirname, "res") },
|
||||
{ from: "media/**", context: path.resolve(__dirname, "node_modules/matrix-react-sdk/res/") },
|
||||
"node_modules/@matrix-org/olm/olm_legacy.js",
|
||||
{ from: "config.json", noErrorOnMissing: true },
|
||||
"contribute.json",
|
||||
],
|
||||
@@ -760,9 +758,9 @@ module.exports = (env, argv) => {
|
||||
// directory and symlink it into place - this allows users who loaded
|
||||
// an older version of the application to continue to access webpack
|
||||
// chunks even after the app is redeployed.
|
||||
filename: "bundles/[hash]/[name].js",
|
||||
chunkFilename: "bundles/[hash]/[name].js",
|
||||
webassemblyModuleFilename: "bundles/[hash]/[modulehash].wasm",
|
||||
filename: "bundles/[fullhash]/[name].js",
|
||||
chunkFilename: "bundles/[fullhash]/[name].js",
|
||||
webassemblyModuleFilename: "bundles/[fullhash]/[modulehash].wasm",
|
||||
},
|
||||
|
||||
// configuration for the webpack-dev-server
|
||||
|
||||
Reference in New Issue
Block a user