Compare commits
624 Commits
0f
...
matrix-sdk-0.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
| fefd2a6b68 | |||
| 269bf3a706 | |||
| 91d1ee9f09 | |||
| b22fe63476 | |||
| 92467af4a0 | |||
| f1f1c1bba6 | |||
| ff82d6420d | |||
| b9409b7c0f | |||
| ea51935601 | |||
| 2fff5d916b | |||
| d16faee68b | |||
| df9b60a5bc | |||
| 1a57170970 | |||
| 41449d2cc3 | |||
| 093fb5d0aa | |||
| 513afa57ef | |||
| 25632f04d4 | |||
| 7922786e0c | |||
| 9a006c58f5 | |||
| 9f4385e639 | |||
| e79b683560 | |||
| 27e4675df0 | |||
| b5bd6dfee9 | |||
| f8b02d1a11 | |||
| 0a5ebe4dce | |||
| e8278b5d68 | |||
| f054141e66 | |||
| 1a6d5e7b26 | |||
| d3e5a3b7db | |||
| 92809590b1 | |||
| 00d388f3d3 | |||
| 59901b6349 | |||
| 914ac5279e | |||
| c23976f975 | |||
| cb94d60e0e | |||
| df50a5446f | |||
| 62ab263d0e | |||
| 2818551d62 | |||
| c29b0f154a | |||
| 6fa04a0ba1 | |||
| dee14c4ee4 | |||
| 5b919dd840 | |||
| 406fc58720 | |||
| b4cec6e1bb | |||
| 6be0fc1fae | |||
| 782b256ea9 | |||
| 82670022f9 | |||
| b8c41e805b | |||
| e3d63c5593 | |||
| 0222a8fec2 | |||
| b442247946 | |||
| c0ebeee730 | |||
| a46b8f392a | |||
| b12da9d4db | |||
| 6f5681f7c2 | |||
| fee0db03c8 | |||
| b2ce906bce | |||
| e45d6f45fd | |||
| 84aa85958c | |||
| b941c16b7d | |||
| 1a5379881e | |||
| 0c4b85e1f9 | |||
| 602dbe42f2 | |||
| d3d316ee6c | |||
| 494969ed49 | |||
| 34ed04958f | |||
| 8da456055d | |||
| ef462c786a | |||
| 1a50f42402 | |||
| 020a75d55c | |||
| f4e0c6e243 | |||
| 01f1b9b846 | |||
| abcd287496 | |||
| cf96a3ba2e | |||
| 9d2e0fe8ad | |||
| 83d5e567eb | |||
| a47d8669cd | |||
| 0643292d76 | |||
| 8a26ba8343 | |||
| cc1c6aedcb | |||
| 55cf573142 | |||
| c5006081e6 | |||
| 4c4fcf91c1 | |||
| 6842fb97fd | |||
| 8027a3036c | |||
| 1d9ac6e60c | |||
| 2f35b2cfc6 | |||
| 7edd6a148c | |||
| 3b526ea412 | |||
| 1c50cee5d7 | |||
| cb95c59194 | |||
| 792b4581ab | |||
| 58ea598c68 | |||
| 581c537396 | |||
| 9834b67bd5 | |||
| 6f89d02599 | |||
| cbb5080837 | |||
| 6a05f834a9 | |||
| c7e0b3ee31 | |||
| 0d57983e1b | |||
| bbfc076c7f | |||
| e367d8574d | |||
| 1be17d354d | |||
| e8331cc40c | |||
| 71a2fac46f | |||
| 8948333e1e | |||
| c471a6fb4d | |||
| 6239d31bcf | |||
| b5a8103023 | |||
| 95709bb4b3 | |||
| 14f22979c0 | |||
| e6141d8efc | |||
| 155b187d45 | |||
| e659c724cd | |||
| e00b9221b9 | |||
| b6f01b3cec | |||
| 53e21e0c26 | |||
| 9155989060 | |||
| bb62437369 | |||
| 6e6c474bcb | |||
| 8ba33f6fd3 | |||
| 2591bcbca9 | |||
| 18a8b2f275 | |||
| f4764bbd8a | |||
| e46e13d1bf | |||
| 97995b7bf6 | |||
| b4f0438c1a | |||
| 076de488e7 | |||
| d0d8e38a1d | |||
| aac41fc82a | |||
| 268ecea7fe | |||
| 24be8a8e82 | |||
| 3cc6fe5dea | |||
| d1b2bfcaa4 | |||
| a0edd2f8d8 | |||
| 478529f230 | |||
| 9411801245 | |||
| 361313fbea | |||
| 2a94f575b8 | |||
| f9d09b60d5 | |||
| 03477afb26 | |||
| 6b66a1de56 | |||
| 519a005d16 | |||
| 7831e0cd89 | |||
| 70eeffbbb0 | |||
| 831e802dd0 | |||
| ab0c144f51 | |||
| dc05c6e2b8 | |||
| 3a6397fdba | |||
| 4a481f09d1 | |||
| fa6745bb60 | |||
| cb21d89229 | |||
| 12b1ec5ef9 | |||
| aa1a47831a | |||
| cc5034f4b9 | |||
| 3be8c9585d | |||
| d810fa6883 | |||
| a744447bb5 | |||
| 9252e2c7a9 | |||
| 282072b6b0 | |||
| e997da0e72 | |||
| b3f3d0a95e | |||
| 42767968ec | |||
| 68a8a214ee | |||
| 5adec41b6b | |||
| b816307ea9 | |||
| a182578722 | |||
| 8b5368ff06 | |||
| a368caf2b0 | |||
| df4ee7db4c | |||
| 321e56cff8 | |||
| a640312503 | |||
| b2452ae92c | |||
| a25b2d4418 | |||
| 5f6775f47c | |||
| c2a222278d | |||
| 567b230cb7 | |||
| 8535c16bc8 | |||
| d009d0475e | |||
| 6b8cf3c02a | |||
| edfc0cbe20 | |||
| 91186d8a25 | |||
| 090d67b6ef | |||
| fd4957f533 | |||
| 08760bd4c0 | |||
| b769827313 | |||
| 79b5854c83 | |||
| 38324f6c60 | |||
| 33bce0b18d | |||
| 245ecea263 | |||
| d4ac1bffd0 | |||
| d6af63e37b | |||
| 57dde2c4d3 | |||
| 6f3813a65f | |||
| 433f75ae57 | |||
| 8f0fb08fe7 | |||
| 4f6ff5c0d3 | |||
| c4d46f233e | |||
| faf0ce5007 | |||
| c5e6178b70 | |||
| bf9f431e22 | |||
| 4b673cfe9c | |||
| 0079f1d569 | |||
| a71292cb16 | |||
| cc813b049e | |||
| 1b06d8ca51 | |||
| 4338d5e534 | |||
| 4c98dfb42a | |||
| dacaef3ddd | |||
| e4267cc4fd | |||
| 16ac69a967 | |||
| 193da88320 | |||
| 0d6a19e388 | |||
| f49e9be905 | |||
| a954518d73 | |||
| 16d9ed230a | |||
| 82b647a888 | |||
| 2e74983c79 | |||
| ffebc7c313 | |||
| 7d1b60a3b1 | |||
| 53c5158eca | |||
| 3eab9ca8e5 | |||
| 36b41ac1c6 | |||
| 8a8cc5f230 | |||
| 96384d9447 | |||
| bb04a1e041 | |||
| d416e64a7e | |||
| aedf807025 | |||
| 77afa26217 | |||
| f1a03ececd | |||
| f8502720c3 | |||
| 84f9414aa4 | |||
| 9cfddc4c65 | |||
| 01f8ed10aa | |||
| 6bb9a7a7d7 | |||
| 75fb3b81a7 | |||
| 504ad39c27 | |||
| d20db7c7c1 | |||
| 1bfcc52a1f | |||
| a8362e389e | |||
| fe35e7c9fa | |||
| 5768188da8 | |||
| 1e451a92e0 | |||
| 12c1b80bc9 | |||
| 7fda431d5f | |||
| 337fbb591d | |||
| 8cca01369a | |||
| 8cdc609876 | |||
| 0673ad315f | |||
| 3aaf70cb5a | |||
| b00e963a21 | |||
| 7d98d87c5a | |||
| fb840f73a3 | |||
| e935f59039 | |||
| 3df6797419 | |||
| eaf1f27831 | |||
| 748eff40f0 | |||
| 0084117de9 | |||
| a4af5bf4e1 | |||
| 27d4228269 | |||
| dc39c8d96b | |||
| d427632230 | |||
| 6ddc8ba36f | |||
| 4be2f3aa04 | |||
| f6c404cb32 | |||
| 20a6a16152 | |||
| 5a1853b0d5 | |||
| 9183f5d4ef | |||
| 973833c643 | |||
| ce4f7e2254 | |||
| 0018749e15 | |||
| 9c414bbe9f | |||
| 0b8462423a | |||
| 2313c099fa | |||
| 85795a92b6 | |||
| ef8207fbaa | |||
| f83292fc75 | |||
| 5faed2e635 | |||
| 9722a4c415 | |||
| 432449b009 | |||
| 04634d5f39 | |||
| ef56f76978 | |||
| 527c727e4a | |||
| d947448c64 | |||
| d497883669 | |||
| 7f07ac52ef | |||
| 9462061a5a | |||
| d508237078 | |||
| 331f8b381f | |||
| 2481618608 | |||
| dd78a8cecd | |||
| 39077b185b | |||
| 68107e6285 | |||
| 549c829000 | |||
| 3581d83389 | |||
| 260b604615 | |||
| a1dca23c3c | |||
| 9fd5a8b3b1 | |||
| 180822dd18 | |||
| 20477ab7da | |||
| 06f39696d3 | |||
| 6f5443f8a0 | |||
| 45a66f3321 | |||
| 7f35691236 | |||
| e8d51a4cba | |||
| 7feffec9c0 | |||
| 8e74834342 | |||
| 070637b0ef | |||
| 2dffe03c8d | |||
| 3f0509e7b1 | |||
| 2275643ea0 | |||
| 0fa6ff6955 | |||
| 997a6ed0ad | |||
| fd220f197b | |||
| 655e62814b | |||
| c2468b2f2e | |||
| e0ae4f60e3 | |||
| 2beb13cc3e | |||
| 78dcff9259 | |||
| 0bd58a7741 | |||
| 405c3938b7 | |||
| bd5ea8b0f7 | |||
| 24c042e974 | |||
| d236cde0c7 | |||
| d2f39bc18f | |||
| 96165f5602 | |||
| 4a13b8d207 | |||
| c9c09adb38 | |||
| aedbbcdde7 | |||
| 97f37acb4a | |||
| 0b5bfeadea | |||
| 6d60acfff4 | |||
| 3d56af442d | |||
| ae18b01c25 | |||
| 4692a12cd7 | |||
| 344309c1bf | |||
| 87094a9111 | |||
| adf3f9d434 | |||
| 158bd24b40 | |||
| 8e368d86b7 | |||
| 0fe714df86 | |||
| 936f0371de | |||
| ecc800a319 | |||
| febfcf9796 | |||
| 3f0a68082a | |||
| 18861bb595 | |||
| 329d461a2f | |||
| 4914c595e9 | |||
| ad80839ffd | |||
| 0701561e45 | |||
| 25030780b0 | |||
| 327a404d60 | |||
| 06e096f6cc | |||
| a025163dae | |||
| 19fcff56de | |||
| edf81fb325 | |||
| 08338acac2 | |||
| 52e70f1955 | |||
| e788ec6b07 | |||
| d3d108deb8 | |||
| bd65d9b7a6 | |||
| 230bb67763 | |||
| 10a37f6d51 | |||
| 5156fb7dd4 | |||
| 3b03ad804f | |||
| 603176f521 | |||
| dfec17e6af | |||
| 7623f93bb3 | |||
| a60620306c | |||
| 1fea48359d | |||
| 054dfa98a0 | |||
| 593d4e6062 | |||
| 5a94ba7b80 | |||
| e1b7f3be05 | |||
| 6bed51f016 | |||
| 4db162b8a2 | |||
| 0b1bdd66f9 | |||
| a4f3c3a070 | |||
| df7895d2c6 | |||
| 694c36d741 | |||
| b5329f99f1 | |||
| f055e939e7 | |||
| c4c7c2bb23 | |||
| efc0556124 | |||
| 9d588f7e00 | |||
| 323974fe4c | |||
| 3bdb2f22ea | |||
| ddf8577c84 | |||
| 90c2dcdbc0 | |||
| fe4bc0dc75 | |||
| 69c8cdf304 | |||
| daf92d7745 | |||
| ccbd9e5712 | |||
| 2430d7f267 | |||
| 3ca3016539 | |||
| bbbd7942b0 | |||
| 1a5953e01e | |||
| 3a19d8e5bf | |||
| 0b7d0aa780 | |||
| 82f4e57a2c | |||
| 9714ce9edf | |||
| 9538596fbb | |||
| 35be128139 | |||
| d54612b6e2 | |||
| a1bf53a331 | |||
| 9064e7b02d | |||
| ae261c2091 | |||
| 968792ea00 | |||
| 42c88e840f | |||
| 38a71972e5 | |||
| d8caaed1ce | |||
| 67e63c0d35 | |||
| 4c7ddd7512 | |||
| 9fca639f9b | |||
| 6cb87c64b5 | |||
| a5875ff75d | |||
| 9bb02f2419 | |||
| 65be06ebad | |||
| 154538c4c9 | |||
| f23c16cf88 | |||
| 165973121c | |||
| 31903d3cbc | |||
| 108f299e92 | |||
| eb78815be5 | |||
| 0716f6afaa | |||
| 78169d516e | |||
| 061fe104a9 | |||
| f91bdb28ba | |||
| 14429af511 | |||
| 1e6e00b0c0 | |||
| 744bd8d0d2 | |||
| 06ad079099 | |||
| 7dfadd1848 | |||
| 5089c1a7e9 | |||
| c15a4bcdd4 | |||
| 729836cf70 | |||
| 2d21c30639 | |||
| 3842426788 | |||
| c27016b2e7 | |||
| d0f58d0879 | |||
| 4cdc91844e | |||
| fe5d8fb40e | |||
| ffb4c6d6ef | |||
| 93c4c5d128 | |||
| ec3a6e4b15 | |||
| ecbcf734b9 | |||
| 75f08aed69 | |||
| 255955555d | |||
| 27b858308c | |||
| a21ac5d6e4 | |||
| 4db041ce9a | |||
| c194e08942 | |||
| 713e96d3a8 | |||
| b36f79a1bb | |||
| 504464c1e8 | |||
| b884c5baae | |||
| c15fb5e5b7 | |||
| 0555216f37 | |||
| 03a4814a1a | |||
| 80c7f057cc | |||
| 7e70997e1c | |||
| 2bb7914611 | |||
| c8c793da98 | |||
| e501979d92 | |||
| 57c1e68893 | |||
| a875c4b373 | |||
| f6e215dac3 | |||
| 4415ed9b58 | |||
| 59fc81b957 | |||
| 770ac193a7 | |||
| fe40b3753a | |||
| e871114436 | |||
| fb4dd4d875 | |||
| 5806b3fbe9 | |||
| e1be222558 | |||
| 37f0926832 | |||
| 936731065b | |||
| 088699b6a4 | |||
| b2da8b7dd7 | |||
| b80d3c2f2d | |||
| eb1dfd9690 | |||
| 54c181a9ad | |||
| 003c946581 | |||
| 82731a5e89 | |||
| 4175e6badf | |||
| 008c0f4659 | |||
| a951a273e7 | |||
| f13f590b7f | |||
| 1eadd0ba2d | |||
| 3b8a2ca2d7 | |||
| b6b1f904e0 | |||
| f31528c926 | |||
| 05fab8c394 | |||
| e37e62c92c | |||
| d162dd07d5 | |||
| 42d3ebbe87 | |||
| 4e7bd6760a | |||
| ab9476960b | |||
| b3092bd1d9 | |||
| c2ad4b7d82 | |||
| f8e729f7f3 | |||
| 5c88bd1595 | |||
| 204441f2b3 | |||
| 79d13148fb | |||
| c247de95b6 | |||
| 6d87dd8011 | |||
| 4b6a28da0d | |||
| bf6e1b594d | |||
| 59332e46b5 | |||
| c070b96a68 | |||
| d68d6ead69 | |||
| f34c2f0574 | |||
| aad167b792 | |||
| 814a30064f | |||
| 6ce5d0b507 | |||
| db7824efbd | |||
| ee4702d04a | |||
| d68b6ea64d | |||
| 49bc950fdd | |||
| 7001113877 | |||
| c0d3099e2e | |||
| 49e5d73d83 | |||
| 2ffaaeeae2 | |||
| 60eef8967f | |||
| ed893ed5b0 | |||
| 4e2bd14a27 | |||
| 0b3b757120 | |||
| 63d01dd20e | |||
| a43005dec2 | |||
| 4187aa400f | |||
| cf8f3bf7cc | |||
| 5b66bed1f0 | |||
| 1668c173e6 | |||
| 76fd9bd963 | |||
| 6f18e35b72 | |||
| 5f7e91c2e7 | |||
| 45829efa7e | |||
| a6bd7fc82f | |||
| 5c53a5f699 | |||
| 021bf55074 | |||
| 0043de4028 | |||
| f9bb86c52f | |||
| 1cd18f49aa | |||
| 8b160dfd38 | |||
| 77f7dbbbc8 | |||
| fe590735c1 | |||
| 0360b13cdb | |||
| f30419446f | |||
| 93d879f356 | |||
| c20c23bc19 | |||
| a174d0f669 | |||
| a10a26a68d | |||
| 1940ebefcb | |||
| b91217f53b | |||
| 3d3db96734 | |||
| b24f7b7ad7 | |||
| 9502d32941 | |||
| 9318c2f1e8 | |||
| 2d03cb5c79 | |||
| aa8206d6c8 | |||
| f937d82336 | |||
| 3f71818704 | |||
| 025db83af3 | |||
| 8520976417 | |||
| 06cab75df3 | |||
| 47a8c62f44 | |||
| e2f1f01cb2 | |||
| fdef2dd86d | |||
| c72ec36b3a | |||
| e3febd6f1f | |||
| b98e3d80a0 | |||
| c0a7b17324 | |||
| 66c5d5311e | |||
| f215c92d0b | |||
| d571ca718d | |||
| 37eb058dac | |||
| 45ecd89387 | |||
| 5d916e4a67 | |||
| daa0fc0206 | |||
| 3d1c96fbec | |||
| decd3fcb43 | |||
| c763ce3f41 | |||
| 09c56ea057 | |||
| 163ce94806 | |||
| ebfa235dab | |||
| 0112396c99 | |||
| 8170d2c996 | |||
| 41de3e0af8 | |||
| 81bf300000 | |||
| 594e8c04cd | |||
| b6d94ab7c6 | |||
| 283c5ff51e | |||
| bb631f2f79 | |||
| d39baf1295 | |||
| f5016dbb97 | |||
| e7d0ee4379 | |||
| 70561f9649 | |||
| 8f8bd40e8d | |||
| 04d326eec1 | |||
| 3567d19359 | |||
| 65b1dfef6f | |||
| 4f1718e587 | |||
| 3c5f30d41e | |||
| 529bdc8e0a | |||
| f55a86dd66 | |||
| f87764fabb | |||
| 65654de7eb | |||
| 399bbc25e9 | |||
| c61dbb657e | |||
| a73b104c59 | |||
| c10961f068 | |||
| 15e22cba47 | |||
| ec00af0bca | |||
| a4f1c404f6 | |||
| f69123b0d8 | |||
| c1f0c73728 | |||
| 54ed1af223 | |||
| 6a57461f74 | |||
| d7b974ac04 | |||
| aae9b5c6d8 | |||
| dc40309cbe | |||
| d465b70bea | |||
| 5316d2e6e7 | |||
| 7adef1d24e |
@@ -0,0 +1,5 @@
|
||||
|
||||
[profile.default]
|
||||
retries = 2
|
||||
# kill the slow tests if they still aren't up after 180s
|
||||
slow-timeout = { period = "60s", terminate-after = 3 }
|
||||
@@ -0,0 +1,6 @@
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
@@ -1,54 +0,0 @@
|
||||
name: AppService
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test-appservice:
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
name: ${{ matrix.os-name }} [m]-appservice
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
|
||||
- os: macos-latest
|
||||
os-name: 🍏
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Run checks
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-appservice
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest]
|
||||
node-version: [14.0, 16.0, 18.0]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
@@ -34,13 +34,14 @@ jobs:
|
||||
|
||||
- os: macos-latest
|
||||
os-name: 🍏
|
||||
node-version: 18.0
|
||||
|
||||
- node-version: 18.0
|
||||
build-doc: true
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -98,7 +99,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -113,6 +114,8 @@ jobs:
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.0
|
||||
|
||||
- name: Install NPM dependencies
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
@@ -159,15 +162,16 @@ jobs:
|
||||
with:
|
||||
command: install
|
||||
# keep in sync with uniffi dependency in Cargo.toml's
|
||||
args: uniffi_bindgen --version ^0.18
|
||||
args: uniffi_bindgen --git https://github.com/mozilla/uniffi-rs --rev 091c3561656e72e1a4160412c83b36d98e556d06
|
||||
|
||||
- name: Generate .xcframework
|
||||
run: sh bindings/apple/debug_build_xcframework.sh ci
|
||||
working-directory: bindings/apple
|
||||
run: sh ./debug_build_xcframework.sh x86_64 ci
|
||||
|
||||
- name: Run XCTests
|
||||
working-directory: bindings/apple
|
||||
run: |
|
||||
xcodebuild test \
|
||||
-project bindings/apple/MatrixRustSDK.xcodeproj \
|
||||
-scheme MatrixRustSDK \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4'
|
||||
-destination 'platform=iOS Simulator,name=iPhone 13'
|
||||
|
||||
+266
-21
@@ -16,8 +16,46 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
cancel-others:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.9.1
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
xtask:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check xtask cache
|
||||
uses: actions/cache@v3
|
||||
id: xtask-cache
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
|
||||
- name: Install rust stable toolchain
|
||||
if: steps.xtask-cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
if: steps.xtask-cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: -p xtask
|
||||
|
||||
test-matrix-sdk-features:
|
||||
name: 🐧 [m], ${{ matrix.name }}
|
||||
needs: xtask
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
@@ -51,39 +89,87 @@ jobs:
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-features ${{ matrix.name }}
|
||||
|
||||
test-matrix-sdk-crypto:
|
||||
name: 🐧 [m]-crypto
|
||||
test-matrix-sdk-examples:
|
||||
name: 🐧 [m]-examples
|
||||
needs: xtask
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-crypto
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci examples
|
||||
|
||||
test-matrix-sdk-crypto:
|
||||
name: 🐧 [m]-crypto
|
||||
needs: xtask
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-crypto
|
||||
|
||||
test-all-crates:
|
||||
name: ${{ matrix.name }}
|
||||
@@ -127,7 +213,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: nextest
|
||||
args: run --workspace
|
||||
args: run --workspace --exclude matrix-sdk-integration-testing
|
||||
|
||||
- name: Test documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -137,6 +223,7 @@ jobs:
|
||||
|
||||
test-wasm:
|
||||
name: 🕸️ ${{ matrix.name }}
|
||||
needs: xtask
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
@@ -174,7 +261,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -196,6 +283,12 @@ jobs:
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
|
||||
- name: Rust Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@@ -207,3 +300,155 @@ jobs:
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci wasm-pack ${{ matrix.cmd }}
|
||||
|
||||
test-appservice:
|
||||
name: ${{ matrix.os-name }} [m]-appservice
|
||||
needs: xtask
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
|
||||
- os: macos-latest
|
||||
os-name: 🍏
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
|
||||
- name: Run checks
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-appservice
|
||||
|
||||
formatting:
|
||||
name: Check Formatting
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: -- --check
|
||||
|
||||
typos:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check the spelling of the files in our repo
|
||||
uses: crate-ci/typos@master
|
||||
|
||||
clippy:
|
||||
name: Run clippy
|
||||
needs: xtask
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci clippy
|
||||
|
||||
integration-tests:
|
||||
name: Integration test
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- uses: michaelkaye/setup-matrix-synapse@main
|
||||
with:
|
||||
uploadLogs: true
|
||||
httpPort: 8228
|
||||
disableRateLimiting: true
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: nextest
|
||||
args: run -p matrix-sdk-integration-testing
|
||||
|
||||
@@ -17,7 +17,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -35,6 +37,18 @@ jobs:
|
||||
command: install
|
||||
args: cargo-tarpaulin
|
||||
|
||||
# set up backend for integration tests
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- uses: gnunicorn/setup-matrix-synapse@main
|
||||
with:
|
||||
uploadLogs: true
|
||||
httpPort: 8228
|
||||
disableRateLimiting: true
|
||||
serverName: "matrix-sdk.rs"
|
||||
|
||||
- name: Run tarpaulin
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -22,11 +22,16 @@ jobs:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
# Keep in sync with xtask docs
|
||||
- name: Build documentation
|
||||
- name: Build rust documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
env:
|
||||
# Work around https://github.com/rust-lang/cargo/issues/10744
|
||||
@@ -34,12 +39,33 @@ jobs:
|
||||
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings"
|
||||
with:
|
||||
command: doc
|
||||
args: --no-deps --workspace --features docsrs
|
||||
args: --no-deps --features docsrs
|
||||
|
||||
- name: Build `matrix-sdk-crypto-nodejs` doc
|
||||
run: |
|
||||
cd bindings/matrix-sdk-crypto-nodejs
|
||||
npm install
|
||||
npm run build && npm run doc
|
||||
|
||||
- name: Build `matrix-sdk-crypto-js` doc
|
||||
run: |
|
||||
cd bindings/matrix-sdk-crypto-js
|
||||
npm install
|
||||
npm run build && npm run doc
|
||||
|
||||
- name: Prepare the doc hierarchy
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p doc/bindings/matrix-sdk-crypto-nodejs/
|
||||
mkdir -p doc/bindings/matrix-sdk-crypto-js/
|
||||
mv target/doc/* doc/
|
||||
mv bindings/matrix-sdk-crypto-nodejs/docs/* doc/bindings/matrix-sdk-crypto-nodejs/
|
||||
mv bindings/matrix-sdk-crypto-js/docs/* doc/bindings/matrix-sdk-crypto-js/
|
||||
|
||||
- name: Deploy documentation
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./target/doc/
|
||||
publish_dir: ./doc/
|
||||
force_orphan: true
|
||||
|
||||
@@ -38,6 +38,8 @@ jobs:
|
||||
prepare-release:
|
||||
name: "Preparing crypto-nodejs release tag"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: "${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
@@ -98,12 +100,18 @@ jobs:
|
||||
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
body_path: "${{ env.PKG_PATH }}/CHANGELOG.md"
|
||||
|
||||
# finally, let's create a PR for all this, too
|
||||
# let's create a PR for all this, too
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
branch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
base: "main"
|
||||
title: "Preparing Release ${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
body: |
|
||||
Automatic Pull-Request to merge release ${{ env.TAG_PREFIX }}${{ inputs.version }} back into main
|
||||
Automatic Pull-Request to merge release ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
|
||||
trigger-release:
|
||||
# and trigger the tagging release workflow
|
||||
uses: matrix-org/matrix-rust-sdk/.github/workflows/release-crypto-nodejs.yml@main
|
||||
needs: ['prepare-release']
|
||||
name: "Trigger release Workflow"
|
||||
with:
|
||||
tag: ${{needs.prepare-release.outputs.tag}}
|
||||
|
||||
@@ -21,7 +21,12 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- matrix-sdk-crypto-nodejs-v[0-9]+.*
|
||||
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: "The tag to build with"
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
upload-assets:
|
||||
name: "Upload prebuilt libraries"
|
||||
@@ -57,7 +62,15 @@ jobs:
|
||||
os: windows-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
# use the given tag
|
||||
- uses: actions/checkout@v3
|
||||
name: "Checking out ${{ inputs.tag }}"
|
||||
if: "${{ inputs.tag }}"
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
# use the default
|
||||
- uses: actions/checkout@v3
|
||||
if: "${{ !inputs.tag }}"
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -90,7 +103,15 @@ jobs:
|
||||
needs:
|
||||
- upload-assets
|
||||
steps:
|
||||
# use the given tag
|
||||
- uses: actions/checkout@v3
|
||||
name: "Checking out ${{ inputs.tag }}"
|
||||
if: "${{ inputs.tag }}"
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
# use the default
|
||||
- uses: actions/checkout@v3
|
||||
if: "${{ !inputs.tag }}"
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -114,4 +135,5 @@ jobs:
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
package: ${{env.PKG_PATH}}/package.json
|
||||
access: public
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# This workflow releases the `matrix-sdk-crypto-js` project.
|
||||
#
|
||||
# It is triggered when a new tag appears that matches
|
||||
# `matrix-sdk-crypto-js-v[0-9]+.*`. This workflow builds the package
|
||||
# for the binding, run its tests to ensure everything is still
|
||||
# correct, and publish the package on NPM and on a newly created
|
||||
# Github release.
|
||||
|
||||
|
||||
name: Release `crypto-js`
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
PKG_PATH: "bindings/matrix-sdk-crypto-js"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- matrix-sdk-crypto-js-v[0-9]+.*
|
||||
|
||||
jobs:
|
||||
publish-matrix-sdk-crypto-js:
|
||||
name: Publish 🕸 [m]-crypto-js
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.0
|
||||
|
||||
- name: Install NPM dependencies
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
run: npm install
|
||||
|
||||
- name: Configure NPM auth token
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
run: npm set "//registry.npmjs.org/:_authToken" "${{ secrets.NPM_TOKEN }}"
|
||||
|
||||
- name: Publish the WebAssembly + JavaScript binding (imply building + testing)
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
run: npm run publish
|
||||
|
||||
- name: Create the Github release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
files: ${{ env.PKG_PATH }}/pkg/matrix-org-matrix-sdk-crypto-js-*.tgz
|
||||
@@ -1,105 +0,0 @@
|
||||
name: Style
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
style:
|
||||
name: Check Formatting
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: -- --check
|
||||
|
||||
pre-commit-styles:
|
||||
name: Check Style
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Setup Python Env
|
||||
uses: actions/setup-python@v2
|
||||
|
||||
- name: Check Styles with pre-commit
|
||||
uses: pre-commit/action@v2.0.3
|
||||
with:
|
||||
# only run `commit`-hooks, excludes typos, clippy, etc
|
||||
extra_args: --show-diff-on-failure --hook-stage commit
|
||||
|
||||
typos:
|
||||
name: Spell Check with Typos
|
||||
needs: [style]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check the spelling of the files in our repo
|
||||
uses: crate-ci/typos@master
|
||||
|
||||
clippy:
|
||||
name: Run clippy
|
||||
needs: [style]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci clippy
|
||||
+1
-1
@@ -1,9 +1,9 @@
|
||||
Cargo.lock
|
||||
target
|
||||
generated
|
||||
master.zip
|
||||
emsdk-*
|
||||
.idea/
|
||||
.env
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.2.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: check-toml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- id: check-merge-conflict
|
||||
- id: mixed-line-ending
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: fmt
|
||||
name: fmt
|
||||
language: system
|
||||
types: [file, rust]
|
||||
entry: cargo fmt -- --check
|
||||
|
||||
- id: clippy
|
||||
name: clippy
|
||||
stages: [push]
|
||||
language: system
|
||||
types: [file, rust]
|
||||
entry: cargo clippy --all-targets --all
|
||||
pass_filenames: false
|
||||
|
||||
- id: test
|
||||
name: test
|
||||
stages: [push]
|
||||
language: system
|
||||
files: '\.rs$'
|
||||
entry: cargo test --lib
|
||||
pass_filenames: false
|
||||
|
||||
- id: typos
|
||||
name: typos
|
||||
stages: [push]
|
||||
language: system
|
||||
entry: typos
|
||||
pass_filenames: false
|
||||
+4
-1
@@ -1,7 +1,10 @@
|
||||
edition = "2018"
|
||||
max_width = 100
|
||||
comment_width = 80
|
||||
wrap_comments = true
|
||||
imports_granularity = "Crate"
|
||||
use_small_heuristics = "Max"
|
||||
group_imports = "StdExternalCrate"
|
||||
format_code_in_doc_comments = true
|
||||
doc_comment_code_block_width = 80
|
||||
# Workaround for https://github.com/rust-lang/rust.vim/issues/464
|
||||
edition = "2021"
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
Fo = "Fo"
|
||||
BA = "BA"
|
||||
UE = "UE"
|
||||
Ure = "Ure"
|
||||
Ot = "Ot"
|
||||
# This is the thead html tag, remove this once typos is updated in the github
|
||||
# action. 1.3.1 seems to work correctly, while 1.11.0 on the CI seems to get
|
||||
# this wrong
|
||||
thead = "thead"
|
||||
|
||||
[files]
|
||||
# Our json files contain a bunch of base64 encoded ed25519 keys which aren't
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Conventional Commits
|
||||
|
||||
This project uses [Conventional
|
||||
Commits](https://www.conventionalcommits.org/). Read the
|
||||
[Summary](https://www.conventionalcommits.org/en/v1.0.0/#summary) or
|
||||
the [Full
|
||||
Specification](https://www.conventionalcommits.org/en/v1.0.0/#specification)
|
||||
to learn more.
|
||||
|
||||
## Types
|
||||
|
||||
Conventional Commits defines _type_ (as in `type(scope):
|
||||
message`). This section aims at listing the types used inside this
|
||||
project:
|
||||
|
||||
| Type | Definition |
|
||||
|-|-|
|
||||
| `feat` | About a new feature. |
|
||||
| `fix` | About a bug fix. |
|
||||
| `test` | About a test (suite, case, runner…). |
|
||||
| `doc` | About a documentation modification. |
|
||||
| `refactor` | About a refactoring. |
|
||||
| `ci` | About a Continuous Integration modification. |
|
||||
| `chore` | About some cleanup, or regular tasks. |
|
||||
|
||||
## Scopes
|
||||
|
||||
Conventional Commits defines _scope_ (as in `type(scope): message`). This
|
||||
section aims at listing all the scopes used inside this project:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group</th>
|
||||
<th>Scope</th>
|
||||
<th>Definition</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="10">Crates</td>
|
||||
<td><code>sdk</code></td>
|
||||
<td>About the <code>matrix-sdk</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>appservice</code></td>
|
||||
<td>About the <code>matrix-sdk-appservice</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>base</code></td>
|
||||
<td>About the <code>matrix-sdk-base</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>common</code></td>
|
||||
<td>About the <code>matrix-sdk-common</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>indexeddb</code></td>
|
||||
<td>About the <code>matrix-sdk-indexeddb</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>qrcode</code></td>
|
||||
<td>About the <code>matrix-sdk-qrcode</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>sled</code></td>
|
||||
<td>About the <code>matrix-sdk-sled</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>store-encryption</code></td>
|
||||
<td>About the <code>matrix-sdk-store-encryption</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>test</code></td>
|
||||
<td>About the <code>matrix-sdk-test</code> and <code>matrix-sdk-test-macros</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">Bindings</td>
|
||||
<td><code>apple</code></td>
|
||||
<td>About the <code>matrix-rust-components-swift</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto-nodejs</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto-nodejs</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto-js</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto-js</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto-ffi</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto-ffi</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Labs</td>
|
||||
<td><code>sled-state-inspector</code></td>
|
||||
<td>About the <code>sled-state-inspector</code> project.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Continuous Integration</td>
|
||||
<td><code>xtask</code></td>
|
||||
<td>About the <code>xtask</code> project.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Generating `CHANGELOG.md`
|
||||
|
||||
The [`git-cliff`](https://github.com/orhun/git-cliff) project is used
|
||||
to generate `CHANGELOG.md` automatically. Hence the various
|
||||
`cliff.toml` files that are present in this project, or the
|
||||
`package.metadata.git-cliff` sections in various `Cargo.toml` files.
|
||||
|
||||
Its companion,
|
||||
[`git-cliff-action`](https://github.com/orhun/git-cliff-action)
|
||||
project, is used inside Github Action workflows.
|
||||
Generated
+5377
File diff suppressed because it is too large
Load Diff
+9
-1
@@ -6,16 +6,24 @@ members = [
|
||||
"bindings/matrix-sdk-crypto-nodejs",
|
||||
"bindings/matrix-sdk-ffi",
|
||||
"crates/*",
|
||||
"testing/*",
|
||||
"examples/*",
|
||||
"labs/*",
|
||||
"xtask",
|
||||
]
|
||||
# xtask, labs and the bindings should only be built when invoked explicitly.
|
||||
# xtask, labs, testing and the bindings should only be built when invoked explicitly.
|
||||
default-members = ["benchmarks", "crates/*"]
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[profile.dev]
|
||||
# Copied from rust-analyzer. Saves a lot of disk space and hopefully
|
||||
# compilation time / mem usage too, at the expense of potentially having to
|
||||
# change this setting here when you want to use a debugger.
|
||||
debug = 0
|
||||
|
||||
[profile.dev.package]
|
||||
# Optimize quote even in debug mode. Speeds up proc-macros enough to account
|
||||
# for the extra time of optimizing it for a clean build of matrix-sdk-ffi.
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# SDK 0.5 - stores and native crypto
|
||||
|
||||
The @matrix-org/rust team is happy to present to the wider public version 0.5 of the [`matrix-rust-sdk`][sdk], now available on crates.io for your convenience. This marks an important milestone after over half a year of work since the latest release (0.4) and with many new team members working on it. It comes with many bug fixes, improvements and updates, but a few we'd like to highlight here:
|
||||
|
||||
## Better, safer, native-er crypto
|
||||
|
||||
One of the biggest improvements under the hood is the replacement of the former crypto core written in C, libolm, with the fully rustic, fully audited [Vodozemac][vodozemac]. [Vodozemac][vodozemac] is a much more robust new implementation of the olm crypto primitives and engines with all the learning included and pitfalls from the previous C implementation avoided in a freshly baked, very robust library. With this release both matrix-sdk-crypto and matrix-sdk (when the `e2e-encryption` is enabled, which it is by default) use vodozemac to handle all crypto needs.
|
||||
|
||||
## Stores, stores, stores
|
||||
|
||||
This release has also seen a major refactoring around the state and crypto store primitives and implementations. From this release forward, the implementation of the storage layer is truly modular and pluggable, with the default implementation (based on `sled`) even being a separate crate. Furthermore this release has also additional support for the [`indexeddb`-storage][indexeddb] layer for in-browser WASM needs. As before, the base still ships with an in-memory store, but if none of them fits your needs, you are very much invited to build your own - we recommend looking at the implementation of the existing [`matrix-sdk-sled`][sled] to understand what is needed.
|
||||
|
||||
We've further extended and cleaned up the storage API and hardened the two existing implementations: both `sled` and `indexeddb` will now encrypt _all metadata_, including all keys, if a passphrase is given. Even a core dump of a database won't show any strings or other identifiable data. Both implementation also hold a database version string now, allowing future migrations of the database schema while we move forward.
|
||||
|
||||
## WebAssembly
|
||||
|
||||
It already came through in the previous paragraph: we now fully support `wasm` as a primary target for the main sdk and its dependencies (browser for now). While it was already possible to build the SDK for wasm before, if you were lucky and had the specific emscripten setup, even crypto didn't always fail to compile, our move to vodozemac frees us from these problems and our CI now makes sure all our PRs will continue to build on WebAssembly, too. And with the `indexeddb`-store, we also have a fully persistent storage layer implementation for all your in-browser matrix needs built in.
|
||||
|
||||
## More features
|
||||
|
||||
Of course a lot more has happened over the last few months as we've ramped up our work on the SDK. Most notably, you can now check whether a room has been created as a `space`, and we offer nice automatic thumbnailing and resizing support if you enable `image`. Additionally, there is a very early event-timeline API behind the `experimental-timeline`-feature-flag. Just to name a few. We really recommend you migrate from the older version to this one.
|
||||
|
||||
## Process and Workflow
|
||||
|
||||
We've also ramped up our CI, parallelized it a lot, while adding more tests. In general we don't merge anything breaking the tests, thus our `main` can also be considered `stable` to build again, though things might be changed without prior notice. We recommend keeping an eye out in our [team chat][chat] to stay current.
|
||||
|
||||
### Versions
|
||||
|
||||
This and all future releases of matrix-sdk adhere to [semver][semver] and we do our best to stick to it for the default-feature-set. As you will see only the main crate `matrix-sdk` is actually versioned at `0.5`, while other crates - especially newer ones - have different versions attached. We recommend to sticking to the high-level matrix-sdk-crate wherever reasonable as lower level crates might change more often, also in breaking fashion.
|
||||
|
||||
With this release all our crates are build with `rust v2021` and need a rust compiler of at least `1.60` (due to our need for weak-dependencies).
|
||||
|
||||
### Conventional Commit
|
||||
|
||||
We also want to make it easier for external parties to follow changes and stay up to date with what's-what, so, with this release, we are implementing the [conventional commits][cc] standard for our commits and will start putting in automatic changelog generation. Which makes it easier for us to create comprehensive changelogs for releases, but also for anyone following `main` to figure out what changed.
|
||||
|
||||
[sdk]: https://crates.io/crates/matrix-sdk/
|
||||
[vodozemac]: https://github.com/matrix-org/vodozemac
|
||||
[sled]: https://crates.io/crates/matrix-sdk-sled/
|
||||
[indexeddb]: https://crates.io/crates/matrix-sdk-indexeddb/
|
||||
[chat]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
|
||||
[semver]: https://semver.org/
|
||||
[cc]: http://conventionalcommits.org/
|
||||
@@ -0,0 +1,121 @@
|
||||
# Upgrades 0.5 ➜ 0.6
|
||||
|
||||
This is a rough migration guide to help you upgrade your code using matrix-sdk 0.5 to the newly released matrix-sdk 0.6 . While it won't cover all edge cases and problems, we are trying to get the most common issues covered. If you experience any other difficulties in upgrade or need support with using the matrix-sdk in general, please approach us in our [matrix-sdk channel on matrix.org][matrix-channel].
|
||||
|
||||
## Minimum Supported Rust Version Update: `1.60`
|
||||
|
||||
We have updated the minimal rust version you need in order to build `matrix-sdk`, as we require some new dependency resolving features from it:
|
||||
|
||||
> These crates are built with the Rust language version 2021 and require a minimum compiler version of 1.60
|
||||
|
||||
## Dependencies
|
||||
|
||||
Many dependencies have been upgraded. Most notably, we are using `ruma` at version `0.7.0` now. It has seen some renamings and restructurings since our last release, so you might find that some Types have new names now.
|
||||
|
||||
## Repo Structure Updates
|
||||
|
||||
If you are looking at the repository itself, you will find we've rearranged the code quite a bit: we have split out any bindings-specific and testing related crates (and other things) into respective folders, and we've moved all `examples` into its own top-level-folder with each example as their own crate (rendering them easier to find and copy as starting points), all in all slimming down the `crates` folder to the core aspects.
|
||||
|
||||
|
||||
## Architecture Changes / API overall
|
||||
|
||||
### Builder Pattern
|
||||
|
||||
We are moving to the [builder pattern][] (familiar from e.g. `std::io:process:Command`) as the main configurable path for many aspects of the API, including to construct Matrix-Requests and workflows. This has been and is an on-going effort, and this release sees a lot of APIs transitioning to this pattern, you should already be familiar with from the `matrix_sdk::Client::builder()` in `0.5`. This pattern been extended onto:
|
||||
- the [login configuration][login builder] and [login with sso][ssologin builder],
|
||||
- [`SledStore` configuratiion][sled-store builder]
|
||||
- [`Indexeddb` configuration][indexeddb builder]
|
||||
|
||||
Most have fallback (though maybe with deprecation warning) support for an existing code path, but these are likely to be removed in upcoming releases.
|
||||
|
||||
### Splitting of concerns: Media
|
||||
|
||||
In an effort to declutter the `Client` API dedicated types have been created dealing with specific concerns in one place. In `0.5` we introduced `client.account()`, and `client.encryption()`, we are doing the same with `client.media()` to manage media and attachments in one place with the [`media::Media` type][media typ] now.
|
||||
|
||||
The signatures of media uploads, have also changed slightly: rather than expecting a reader `R: Read + Seek`, it now is a simple `&[u8]`. Which also means no more unnecessary `seek(0)` to reset the cursor, as we are just taking an immutable reference now.
|
||||
|
||||
### Event Handling & sync updaes
|
||||
|
||||
If you are using the `client.register_event_handler` function to receive updates on incoming sync events, you'll find yourself with a deprecation warning now. That is because we've refactored and redesigned the event handler logic to allowing `removing` of event handlers on the fly, too. For that the new `add_event_handler()` (and `add_room_event_handler`) will hand you an `EventHandlerHandle` (pardon the pun), which you can pass to `remove_event_handler`, or by using the convenient `client.event_handler_drop_guard` to create a `DropGuard` that will remove the handler when the guard is dropped. While the code still works, we recommend you switch to the new one, as we will be removing the `register_event_handler` and `register_event_handler_context` in a coming release.
|
||||
|
||||
Secondly, you will find a new [`sync_with_result_callback` sync function][sync with result]. Other than the previous sync functions, this will pass the entire `Result` to your callback, allowing you to handle errors or even raise some yourself to stop the loop. Further more, it will propagate any unhandled errors (it still handles retries as before) to the outer caller, allowing the higher level to decide how to handle that (e.g. in case of a network failure). This result-returning-behavior also punshes through the existing `sync` and `sync_with_callback`-API, allowing you to handle them on a higher level now (rather than the futures just resolving). If you find that warning, just adding a `?` to the `.await` of the call is probably the quickest way to move forward.
|
||||
|
||||
### Refresh Tokens
|
||||
|
||||
This release now [supports `refresh_token`s][refresh tokens PR] as part of the [`Session`][session]. It is implemented with a default-flag in serde so deserializing a previously serialized Session (e.g. in a store) will work as before. As part of `refresh_token` support, you can now configure the client via `ClientBuilder.request_refresh_token()` to refresh the access token automagically on certain failures or do it manually by calling `client.refresh_access_token()` yourself. Auto-refresh is _off_ by default.
|
||||
|
||||
You can stay informed about updates on the access token by listening to `client.session_tokens_signal()`.
|
||||
|
||||
### Further changes
|
||||
|
||||
- [`MessageOptions`][message options] has been updated to Matrix 1.3 by making the `from` parameter optional (and function signatures have been updated, too). You can now request the server sends you messages from the first one you are allowed to have received.
|
||||
- `client.user_id()` is not a `future` anymore. Remove any `.await` you had behind it.
|
||||
- `verified()`, `blacklisted()` and `deleted()` on `matrix_sdk::encryption::identities::Device` have been renamed with a `is_` prefix.
|
||||
- `verified()` on `matrix_sdk::encryption::identities::UserIdentity`, too has been prefixed with `is_` and thus is onw called `is_verified()`.
|
||||
- The top-level crypto and state-store types of Indexeddb and Sled have been renamed to unique types>
|
||||
- `state_store` and `crypto_store` do not need to be boxed anymore when passed to the [`StoreConfig`][store config]
|
||||
- Indexeddb's `SerializationError` is now `IndexedDBStoreError`
|
||||
- Javascript specific features are now behind the `js` feature-gate
|
||||
- The new experimental next generation of sync ("sliding sync"), with a totally revamped api, can be found behind the optional `sliding-sync`-feature-gate
|
||||
|
||||
|
||||
## Quick Troubleshooting
|
||||
|
||||
You find yourself focussed with any of these, here are the steps to follow to upgrade your code accordingly:
|
||||
|
||||
### warning: use of deprecated associated function `matrix_sdk::Client::register_event_handler`: Use [`Client::add_event_handler`](#method.add_event_handler) instead
|
||||
|
||||
As it says on the tin: we have deprecated this function in favor of the newer removable handler approach (see above). You can still continue to use this `fn` for now, but it will be removed in a future release.
|
||||
|
||||
### warning: use of deprecated associated function `matrix_sdk::Client::login`: Replaced by [`Client::login_username`](#method.login_username)
|
||||
|
||||
We have replaced the login facilities with a `LoginBuilder` and recommend you use that from now on. This isn't an error yet, but the function will be removed in a future release.
|
||||
|
||||
### expected slice `[u8]`, found struct ...
|
||||
|
||||
We've updated the `send_attachment` and `Media` signatures to use `&[u8]` rather than `reader: Read + Seek` as it is more convenient and common place for most architectures anyways. If you are using `File::open(path)?` to get that handler, you can just replace that with `std::fs::read(path)?`
|
||||
|
||||
### no method named `verified` found for struct `matrix_sdk::encryption::identities::Device` in the current scope
|
||||
|
||||
Boolean flags like `verified`, `deleted`, `blacklisted`, etc have been renamed with a `is_` prefix. So, just follow the cargo suggestion:
|
||||
```
|
||||
|
|
||||
69 | device.verified()
|
||||
| ^^^^^^^^ help: there is an associated function with a similar name: `is_verified`
|
||||
```
|
||||
|
||||
### unresolved import `matrix_sdk::ruma::events::AnySyncRoomEvent`
|
||||
|
||||
Ruma has been updated to `0.7.0`, you will find some ruma Events names have changed, most notably, the `AnySyncRoomEvent` is now named `AnySyncTimelineEvent` (and not `AnySyncStateEvent`, which cargo wrongly suggests). Just rename the import and usage of it.
|
||||
|
||||
### `std::option::Option<&matrix_sdk::ruma::UserId>` is not a future
|
||||
|
||||
You are seeing something along the lines of:
|
||||
```
|
||||
19 | if room_member.state_key != client.user_id().await.unwrap() {
|
||||
| ^^^^^^ `std::option::Option<&matrix_sdk::ruma::UserId>` is not a future
|
||||
|
|
||||
= help: the trait `Future` is not implemented for `std::option::Option<&matrix_sdk::ruma::UserId>`
|
||||
= note: std::option::Option<&matrix_sdk::ruma::UserId> must be a future or must implement `IntoFuture` to be awaited
|
||||
= note: required because of the requirements on the impl of `IntoFuture` for `std::option::Option<&matrix_sdk::ruma::UserId>`
|
||||
help: remove the `.await`
|
||||
|
|
||||
19 - if room_member.state_key != client.user_id().await.unwrap() {
|
||||
19 + if room_member.state_key != client.user_id().unwrap() {
|
||||
```
|
||||
|
||||
You are using `client.user_id().await` but `user_id()` is no longer `async`. Just follow the cargo suggestion and remove the `.await`, it is not necessary any longer.
|
||||
|
||||
|
||||
[matrix-channel]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
|
||||
[builder pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
|
||||
[login builder]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.LoginBuilder.html
|
||||
[ssologin builder]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.SsoLoginBuilder.html
|
||||
[sled-store builder]: https://docs.rs/matrix-sdk-sled/latest/matrix_sdk_sled/struct.SledStateStoreBuilder.html
|
||||
[indexeddb builder]: https://docs.rs/matrix-sdk-indexeddb/latest/matrix_sdk_indexeddb/struct.IndexeddbStateStoreBuilder.html
|
||||
[media type]: https://docs.rs/matrix-sdk/latest/matrix_sdk//media/struct.Media.html
|
||||
[sync with result]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.Client.html#method.sync_with_result_callback
|
||||
[session]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.Session.html
|
||||
[refresh tokens PR]: https://github.com/matrix-org/matrix-rust-sdk/pull/892
|
||||
[store config]: https://docs.rs/matrix-sdk-base/latest/matrix_sdk_base/store/struct.StoreConfig.html
|
||||
[message options]: https://docs.rs/matrix-sdk/latest/matrix_sdk/room/struct.MessagesOptions.html
|
||||
@@ -9,16 +9,16 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
criterion = { version = "0.3.5", features = ["async", "async_tokio", "html_reports"] }
|
||||
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.5.0" }
|
||||
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.1.0", default-features = false, features = ["crypto-store"] }
|
||||
matrix-sdk-test = { path = "../crates/matrix-sdk-test", version = "0.5.0" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" }
|
||||
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.6.0"}
|
||||
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.2.0", default-features = false, features = ["crypto-store"] }
|
||||
matrix-sdk-test = { path = "../testing/matrix-sdk-test", version = "0.6.0"}
|
||||
ruma = "0.7.0"
|
||||
serde_json = "1.0.79"
|
||||
tempfile = "3.3.0"
|
||||
tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
pprof = { version = "0.8.0", features = ["flamegraph", "criterion"] }
|
||||
pprof = { version = "0.10.0", features = ["flamegraph", "criterion"] }
|
||||
|
||||
[[bench]]
|
||||
name = "crypto_bench"
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use criterion::*;
|
||||
use matrix_sdk_crypto::{EncryptionSettings, OlmMachine};
|
||||
use matrix_sdk_sled::CryptoStore as SledCryptoStore;
|
||||
use matrix_sdk_sled::SledCryptoStore;
|
||||
use matrix_sdk_test::response_from_file;
|
||||
use ruma::{
|
||||
api::{
|
||||
@@ -63,7 +63,7 @@ pub fn keys_query(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Keys querying");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
|
||||
let name = format!("{} device and cross signing keys", count);
|
||||
let name = format!("{count} device and cross signing keys");
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
|
||||
b.to_async(&runtime)
|
||||
@@ -96,7 +96,7 @@ pub fn keys_claiming(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Olm session creation");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
|
||||
let name = format!("{} one-time keys", count);
|
||||
let name = format!("{count} one-time keys");
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
|
||||
b.iter_batched(
|
||||
@@ -158,7 +158,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
|
||||
let mut group = c.benchmark_group("Room key sharing");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
let name = format!("{} devices", count);
|
||||
let name = format!("{count} devices");
|
||||
|
||||
group.bench_function(BenchmarkId::new("memory store", &name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
@@ -225,7 +225,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Devices missing sessions collecting");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
|
||||
let name = format!("{} devices", count);
|
||||
let name = format!("{count} devices");
|
||||
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
|
||||
|
||||
|
||||
@@ -1,513 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 55;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; };
|
||||
181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189A89B927B40BBF0048B0A5 /* sdk.swift */; };
|
||||
18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */; };
|
||||
18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D927B2939900CA89E1 /* ContentView.swift */; };
|
||||
18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 18CE89DB27B2939A00CA89E1 /* Assets.xcassets */; };
|
||||
18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 18CE89CC27B2939900CA89E1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 18CE89D327B2939900CA89E1;
|
||||
remoteInfo = MatrixRustSDK;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
18CE8A1F27B2941600CA89E1 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
181AA19927B52AA60005F102 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MatrixSDKFFI.xcframework; path = ../../generated/MatrixSDKFFI.xcframework; sourceTree = "<group>"; };
|
||||
189A89B927B40BBF0048B0A5 /* sdk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = sdk.swift; path = ../../../generated/swift/sdk.swift; sourceTree = "<group>"; };
|
||||
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MatrixRustSDK-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatrixRustSDK.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKApp.swift; sourceTree = "<group>"; };
|
||||
18CE89D927B2939900CA89E1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatrixRustSDKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKTests.swift; sourceTree = "<group>"; };
|
||||
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MatrixRustSDK.entitlements; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
18CE89D127B2939900CA89E1 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
18CE89E127B2939A00CA89E1 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
189A89AB27B2E16B0048B0A5 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
189A89B827B40BB10048B0A5 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
189A89B927B40BBF0048B0A5 /* sdk.swift */,
|
||||
);
|
||||
name = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
18CE89CB27B2939900CA89E1 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
18CE89D627B2939900CA89E1 /* MatrixRustSDK */,
|
||||
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */,
|
||||
18CE89D527B2939900CA89E1 /* Products */,
|
||||
189A89AB27B2E16B0048B0A5 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
18CE89D527B2939900CA89E1 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */,
|
||||
18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
18CE89D627B2939900CA89E1 /* MatrixRustSDK */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
181AA19927B52AA60005F102 /* Info.plist */,
|
||||
189A89B827B40BB10048B0A5 /* Generated */,
|
||||
18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */,
|
||||
18CE89D927B2939900CA89E1 /* ContentView.swift */,
|
||||
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */,
|
||||
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */,
|
||||
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */,
|
||||
);
|
||||
path = MatrixRustSDK;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */,
|
||||
);
|
||||
path = MatrixRustSDKTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
18CE89D327B2939900CA89E1 /* MatrixRustSDK */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */;
|
||||
buildPhases = (
|
||||
18CE89D027B2939900CA89E1 /* Sources */,
|
||||
18CE89D127B2939900CA89E1 /* Frameworks */,
|
||||
18CE89D227B2939900CA89E1 /* Resources */,
|
||||
18CE8A1F27B2941600CA89E1 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = MatrixRustSDK;
|
||||
productName = MatrixRustSDK;
|
||||
productReference = 18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */;
|
||||
buildPhases = (
|
||||
18CE89E027B2939A00CA89E1 /* Sources */,
|
||||
18CE89E127B2939A00CA89E1 /* Frameworks */,
|
||||
18CE89E227B2939A00CA89E1 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
18CE89E627B2939A00CA89E1 /* PBXTargetDependency */,
|
||||
);
|
||||
name = MatrixRustSDKTests;
|
||||
productName = MatrixRustSDKTests;
|
||||
productReference = 18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
18CE89CC27B2939900CA89E1 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastUpgradeCheck = 1320;
|
||||
TargetAttributes = {
|
||||
18CE89D327B2939900CA89E1 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
LastSwiftMigration = 1320;
|
||||
};
|
||||
18CE89E327B2939A00CA89E1 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
TestTargetID = 18CE89D327B2939900CA89E1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */;
|
||||
compatibilityVersion = "Xcode 13.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 18CE89CB27B2939900CA89E1;
|
||||
productRefGroup = 18CE89D527B2939900CA89E1 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
18CE89D327B2939900CA89E1 /* MatrixRustSDK */,
|
||||
18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
18CE89D227B2939900CA89E1 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
18CE89E227B2939A00CA89E1 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
18CE89D027B2939900CA89E1 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */,
|
||||
189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */,
|
||||
18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
18CE89E027B2939A00CA89E1 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
18CE89E627B2939A00CA89E1 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 18CE89D327B2939900CA89E1 /* MatrixRustSDK */;
|
||||
targetProxy = 18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
18CE89F627B2939A00CA89E1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
18CE89F727B2939A00CA89E1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
18CE89F927B2939A00CA89E1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = MatrixRustSDK/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
18CE89FA27B2939A00CA89E1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = MatrixRustSDK/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
18CE89FC27B2939A00CA89E1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
18CE89FD27B2939A00CA89E1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
18CE89F627B2939A00CA89E1 /* Debug */,
|
||||
18CE89F727B2939A00CA89E1 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
18CE89F927B2939A00CA89E1 /* Debug */,
|
||||
18CE89FA27B2939A00CA89E1 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
18CE89FC27B2939A00CA89E1 /* Debug */,
|
||||
18CE89FD27B2939A00CA89E1 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 18CE89CC27B2939900CA89E1 /* Project object */;
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,98 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
|
||||
BuildableName = "MatrixRustSDK.app"
|
||||
BlueprintName = "MatrixRustSDK"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89E327B2939A00CA89E1"
|
||||
BuildableName = "MatrixRustSDKTests.xctest"
|
||||
BlueprintName = "MatrixRustSDKTests"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89ED27B2939A00CA89E1"
|
||||
BuildableName = "MatrixRustSDKUITests.xctest"
|
||||
BlueprintName = "MatrixRustSDKUITests"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
|
||||
BuildableName = "MatrixRustSDK.app"
|
||||
BlueprintName = "MatrixRustSDK"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
|
||||
BuildableName = "MatrixRustSDK.app"
|
||||
BlueprintName = "MatrixRustSDK"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// MatrixRustSDK
|
||||
//
|
||||
// Created by Stefan Ceriu on 08.02.2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
Text("Hello, Rust!")
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -1,5 +0,0 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "sdkFFI.h"
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,17 +0,0 @@
|
||||
//
|
||||
// MatrixRustSDKApp.swift
|
||||
// MatrixRustSDK
|
||||
//
|
||||
// Created by Stefan Ceriu on 08.02.2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct MatrixRustSDKApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
//
|
||||
// MatrixRustSDKTests.swift
|
||||
// MatrixRustSDKTests
|
||||
//
|
||||
// Created by Stefan Ceriu on 08.02.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import MatrixRustSDK
|
||||
|
||||
class MatrixRustSDKTests: XCTestCase {
|
||||
|
||||
func testReadOnlyFileSystemError() {
|
||||
do {
|
||||
let client = try ClientBuilder()
|
||||
.basePath(path: "")
|
||||
.username(username: "@test:domain")
|
||||
.build()
|
||||
|
||||
try client.login(username: "@test:domain", password: "test")
|
||||
} catch ClientError.Generic(let message) {
|
||||
XCTAssertNotNil(message.range(of: "Read-only file system"))
|
||||
} catch {
|
||||
XCTFail("Not expecting any other kind of exception")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
static private var basePath: String {
|
||||
guard let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
|
||||
fatalError("Should always be able to retrieve the caches directory")
|
||||
}
|
||||
|
||||
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
return url.path
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// swift-tools-version: 5.6
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MatrixRustSDK",
|
||||
platforms: [.iOS(.v15)],
|
||||
products: [
|
||||
.library(name: "MatrixRustSDK",
|
||||
targets: ["MatrixRustSDK"]),
|
||||
],
|
||||
targets: [
|
||||
.binaryTarget(name: "MatrixSDKFFI", path: "generated/MatrixSDKFFI.xcframework"),
|
||||
.target(name: "MatrixRustSDK",
|
||||
dependencies: [.target(name: "MatrixSDKFFI")],
|
||||
path: "generated/swift"),
|
||||
.testTarget(name: "MatrixRustSDKTests",
|
||||
dependencies: ["MatrixRustSDK"]),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
import XCTest
|
||||
@testable import MatrixRustSDK
|
||||
|
||||
final class ClientTests: XCTestCase {
|
||||
func testBuildingWithHomeserverURL() {
|
||||
do {
|
||||
_ = try ClientBuilder()
|
||||
.homeserverUrl(url: "https://localhost:8008")
|
||||
.build()
|
||||
} catch {
|
||||
XCTFail("The client should build successfully when given a homeserver.")
|
||||
}
|
||||
}
|
||||
|
||||
func testBuildingWithUsername() {
|
||||
do {
|
||||
_ = try ClientBuilder()
|
||||
.username(username: "@test:matrix.org")
|
||||
.build()
|
||||
} catch {
|
||||
XCTFail("The client should build successfully when given a username.")
|
||||
}
|
||||
}
|
||||
|
||||
func testBuildingWithInvalidUsername() {
|
||||
do {
|
||||
_ = try ClientBuilder()
|
||||
.username(username: "@test:invalid")
|
||||
.build()
|
||||
|
||||
XCTFail("The client should not build when given an invalid username.")
|
||||
} catch ClientError.Generic(let message) {
|
||||
XCTAssertTrue(message.contains(".well-known"), "The client should fail to do the well-known lookup.")
|
||||
} catch {
|
||||
XCTFail("Not expecting any other kind of exception")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
static private var basePath: String {
|
||||
guard let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
|
||||
fatalError("Should always be able to retrieve the caches directory")
|
||||
}
|
||||
|
||||
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
return url.path
|
||||
}
|
||||
}
|
||||
@@ -17,20 +17,22 @@ REL_TYPE_DIR="release"
|
||||
# Build static libs for all the different architectures
|
||||
|
||||
# iOS
|
||||
echo -e "Building for iOS [1/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios"
|
||||
|
||||
# MacOS
|
||||
echo -e "\nBuilding for macOS (Apple Silicon) [2/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-darwin"
|
||||
echo -e "\nBuilding for macOS (Intel) [3/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-darwin"
|
||||
|
||||
# iOS Simulator
|
||||
echo -e "\nBuilding for iOS Simulator (Apple Silicon) [4/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
echo -e "\nBuilding for iOS Simulator (Intel) [5/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
|
||||
|
||||
# Mac Catalyst
|
||||
cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-macabi"
|
||||
cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios-macabi"
|
||||
|
||||
echo -e "\nCreating XCFramework"
|
||||
# Lipo together the libraries for the same platform
|
||||
|
||||
# MacOS
|
||||
@@ -45,21 +47,23 @@ lipo -create \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
|
||||
# Mac Catalyst
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a"
|
||||
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
|
||||
# Architecture for the .a file argument doesn't matter, since the API is the same on all
|
||||
uniffi-bindgen generate \
|
||||
--language swift \
|
||||
--lib-file "${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
--out-dir ${GENERATED_DIR} \
|
||||
"${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl"
|
||||
|
||||
# Move them to the right place
|
||||
HEADERS_DIR=${GENERATED_DIR}/headers
|
||||
mkdir -p ${HEADERS_DIR}
|
||||
|
||||
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
|
||||
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
|
||||
|
||||
# Rename and move modulemap to the right place
|
||||
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
|
||||
|
||||
SWIFT_DIR="${GENERATED_DIR}/swift"
|
||||
mkdir -p ${SWIFT_DIR}
|
||||
@@ -75,8 +79,6 @@ xcodebuild -create-xcframework \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-output "${GENERATED_DIR}/MatrixSDKFFI.xcframework"
|
||||
@@ -85,5 +87,4 @@ xcodebuild -create-xcframework \
|
||||
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a"; fi
|
||||
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
|
||||
@@ -4,42 +4,63 @@ set -eEu
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
IS_CI=false
|
||||
ACTIVE_ARCH="arm64"
|
||||
|
||||
if [ $# -eq 1 ]; then
|
||||
IS_CI=true
|
||||
if [ $# -eq 2 ]; then
|
||||
echo "Running CI build"
|
||||
IS_CI=true
|
||||
ARCHS=( $1 )
|
||||
ACTIVE_ARCH=${ARCHS[0]}
|
||||
elif [ $# -eq 1 ]; then
|
||||
echo "Running debug build"
|
||||
ARCHS=( $1 )
|
||||
ACTIVE_ARCH=${ARCHS[0]}
|
||||
else
|
||||
echo "Running debug build"
|
||||
fi
|
||||
|
||||
echo "Active architecture ${ACTIVE_ARCH}"
|
||||
|
||||
# Path to the repo root
|
||||
SRC_ROOT=../..
|
||||
|
||||
TARGET_DIR="${SRC_ROOT}/target"
|
||||
|
||||
GENERATED_DIR="${SRC_ROOT}/generated"
|
||||
GENERATED_DIR="${SRC_ROOT}/bindings/apple/generated"
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
REL_FLAG=""
|
||||
REL_TYPE_DIR="debug"
|
||||
|
||||
# iOS Simulator
|
||||
cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
|
||||
# iOS Simulator arm64
|
||||
if [ "$ACTIVE_ARCH" = "arm64" ]; then
|
||||
TARGET="aarch64-apple-ios-sim"
|
||||
# iOS Simulator intel
|
||||
else
|
||||
TARGET="x86_64-apple-ios"
|
||||
fi
|
||||
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "$TARGET"
|
||||
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
"${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
|
||||
uniffi-bindgen generate \
|
||||
--language swift \
|
||||
--lib-file "${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
--out-dir ${GENERATED_DIR} \
|
||||
"${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl"
|
||||
|
||||
# Move them to the right place
|
||||
HEADERS_DIR=${GENERATED_DIR}/headers
|
||||
mkdir -p ${HEADERS_DIR}
|
||||
|
||||
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
|
||||
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
|
||||
|
||||
# Rename and move modulemap to the right place
|
||||
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
|
||||
|
||||
SWIFT_DIR="${GENERATED_DIR}/swift"
|
||||
mkdir -p ${SWIFT_DIR}
|
||||
@@ -57,15 +78,13 @@ xcodebuild -create-xcframework \
|
||||
|
||||
# Cleanup
|
||||
|
||||
# if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
|
||||
# if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
|
||||
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
|
||||
if [ "$IS_CI" = false ] ; then
|
||||
echo "Preparing matrix-rust-components-swift"
|
||||
|
||||
# Debug -> Copy generated files over to ../../../matrix-rust-components-swift
|
||||
echo "$(printf "import MatrixSDKFFIWrapper\n\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift"
|
||||
|
||||
rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/"
|
||||
rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK"
|
||||
fi
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "matrix-sdk-crypto-ffi"
|
||||
version = "0.1.0"
|
||||
authors = ["Damir Jelić <poljar@termina.org.uk>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
description = "Uniffi based bindings for the Rust SDK crypto crate"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
@@ -20,7 +20,7 @@ hmac = "0.12.1"
|
||||
http = "0.2.6"
|
||||
pbkdf2 = "0.11.0"
|
||||
rand = "0.8.5"
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] }
|
||||
ruma = { version = "0.7.0", features = ["client-api-c"] }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
sha2 = "0.10.2"
|
||||
@@ -28,7 +28,7 @@ thiserror = "1.0.30"
|
||||
tracing = "0.1.34"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
|
||||
# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job
|
||||
uniffi = "0.18.0"
|
||||
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06" }
|
||||
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
|
||||
|
||||
[dependencies.js_int]
|
||||
@@ -37,16 +37,16 @@ features = ["lax_deserialize"]
|
||||
|
||||
[dependencies.matrix-sdk-common]
|
||||
path = "../../crates/matrix-sdk-common"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
|
||||
[dependencies.matrix-sdk-crypto]
|
||||
path = "../../crates/matrix-sdk-crypto"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
features = ["qrcode", "backups_v1"]
|
||||
|
||||
[dependencies.matrix-sdk-sled]
|
||||
path = "../../crates/matrix-sdk-sled"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
default_features = false
|
||||
features = ["crypto-store"]
|
||||
|
||||
@@ -56,11 +56,10 @@ default_features = false
|
||||
features = ["rt-multi-thread"]
|
||||
|
||||
[dependencies.vodozemac]
|
||||
git = "https://github.com/matrix-org/vodozemac/"
|
||||
rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd"
|
||||
version = "0.3.0"
|
||||
|
||||
[build-dependencies]
|
||||
uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] }
|
||||
uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06", features = ["builtin-bindgen"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
|
||||
@@ -14,7 +14,7 @@ mod responses;
|
||||
mod users;
|
||||
mod verification;
|
||||
|
||||
use std::{borrow::Borrow, collections::HashMap, convert::TryFrom, str::FromStr, sync::Arc};
|
||||
use std::{borrow::Borrow, collections::HashMap, str::FromStr, sync::Arc};
|
||||
|
||||
pub use backup_recovery_key::{
|
||||
BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError,
|
||||
@@ -26,6 +26,7 @@ pub use error::{
|
||||
use js_int::UInt;
|
||||
pub use logger::{set_logger, Logger};
|
||||
pub use machine::{KeyRequestPair, OlmMachine};
|
||||
use matrix_sdk_crypto::types::{EventEncryptionAlgorithm, SigningKey};
|
||||
pub use responses::{
|
||||
BootstrapCrossSigningResult, DeviceLists, KeysImportResult, OutgoingVerificationRequest,
|
||||
Request, RequestType, SignatureUploadRequest, UploadSigningKeysRequest,
|
||||
@@ -161,7 +162,7 @@ pub fn migrate(
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
store::{Changes as RustChanges, CryptoStore, RecoveryKey},
|
||||
};
|
||||
use matrix_sdk_sled::CryptoStore as SledStore;
|
||||
use matrix_sdk_sled::SledCryptoStore;
|
||||
use tokio::runtime::Runtime;
|
||||
use vodozemac::{
|
||||
megolm::InboundGroupSession,
|
||||
@@ -184,7 +185,7 @@ pub fn migrate(
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
let store = SledStore::open_with_passphrase(path, passphrase.as_deref())?;
|
||||
let store = SledCryptoStore::open_with_passphrase(path, passphrase.as_deref())?;
|
||||
let runtime = Runtime::new()?;
|
||||
|
||||
processed_steps += 1;
|
||||
@@ -249,19 +250,26 @@ pub fn migrate(
|
||||
let pickle =
|
||||
InboundGroupSession::from_libolm_pickle(&session.pickle, &data.pickle_key)?.pickle();
|
||||
|
||||
let sender_key = Curve25519PublicKey::from_base64(&session.sender_key)?;
|
||||
|
||||
let pickle = matrix_sdk_crypto::olm::PickledInboundGroupSession {
|
||||
pickle,
|
||||
sender_key: session.sender_key,
|
||||
sender_key,
|
||||
signing_key: session
|
||||
.signing_key
|
||||
.into_iter()
|
||||
.map(|(k, v)| Ok((DeviceKeyAlgorithm::try_from(k)?, v)))
|
||||
.map(|(k, v)| {
|
||||
let algorithm = DeviceKeyAlgorithm::try_from(k)?;
|
||||
let key = SigningKey::from_parts(&algorithm, v)?;
|
||||
|
||||
Ok((algorithm, key))
|
||||
})
|
||||
.collect::<anyhow::Result<_>>()?,
|
||||
room_id: RoomId::parse(session.room_id)?,
|
||||
forwarding_chains: session.forwarding_chains,
|
||||
imported: session.imported,
|
||||
backed_up: session.backed_up,
|
||||
history_visibility: None,
|
||||
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
};
|
||||
|
||||
let session = matrix_sdk_crypto::olm::InboundGroupSession::from_pickle(pickle)?;
|
||||
@@ -575,7 +583,7 @@ mod test {
|
||||
"JGgPQRuYj3ScMdPS+A0P+k/1qS9Hr3qeKXLscI+hS78"
|
||||
);
|
||||
|
||||
let room_keys = machine.runtime.block_on(machine.inner.export_keys(|_| true))?;
|
||||
let room_keys = machine.runtime.block_on(machine.inner.export_room_keys(|_| true))?;
|
||||
assert_eq!(room_keys.len(), 2);
|
||||
|
||||
let cross_signing_status = machine.cross_signing_status();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
convert::TryInto,
|
||||
io::Cursor,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
@@ -11,7 +10,7 @@ use base64::{decode_config, encode, STANDARD_NO_PAD};
|
||||
use js_int::UInt;
|
||||
use matrix_sdk_common::deserialized_responses::AlgorithmInfo;
|
||||
use matrix_sdk_crypto::{
|
||||
backups::MegolmV1BackupKey as RustBackupKey, decrypt_key_export, encrypt_key_export,
|
||||
backups::MegolmV1BackupKey as RustBackupKey, decrypt_room_key_export, encrypt_room_key_export,
|
||||
matrix_sdk_qrcode::QrVerificationData, olm::ExportedRoomKey, store::RecoveryKey,
|
||||
EncryptionSettings, LocalTrust, OlmMachine as InnerMachine, UserIdentities,
|
||||
Verification as RustVerification,
|
||||
@@ -32,10 +31,8 @@ use ruma::{
|
||||
},
|
||||
IncomingResponse,
|
||||
},
|
||||
events::{
|
||||
key::verification::VerificationMethod, room::encrypted::OriginalSyncRoomEncryptedEvent,
|
||||
AnySyncMessageLikeEvent,
|
||||
},
|
||||
events::{key::verification::VerificationMethod, AnySyncMessageLikeEvent},
|
||||
serde::Raw,
|
||||
DeviceKeyAlgorithm, EventId, OwnedTransactionId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -95,7 +92,7 @@ impl OlmMachine {
|
||||
let runtime = Runtime::new().expect("Couldn't create a tokio runtime");
|
||||
|
||||
let store = Arc::new(
|
||||
matrix_sdk_sled::CryptoStore::open_with_passphrase(path, passphrase.as_deref())
|
||||
matrix_sdk_sled::SledCryptoStore::open_with_passphrase(path, passphrase.as_deref())
|
||||
.map_err(|e| {
|
||||
match e {
|
||||
// This is a bit of an error in the sled store, the
|
||||
@@ -176,7 +173,7 @@ impl OlmMachine {
|
||||
{
|
||||
match identity {
|
||||
UserIdentities::Own(i) => i.is_verified(),
|
||||
UserIdentities::Other(i) => i.verified(),
|
||||
UserIdentities::Other(i) => i.is_verified(),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
@@ -602,7 +599,7 @@ impl OlmMachine {
|
||||
content: &'a RawValue,
|
||||
}
|
||||
|
||||
let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?;
|
||||
let event: Raw<_> = serde_json::from_str(event)?;
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
|
||||
let decrypted = self.runtime.block_on(self.inner.decrypt_room_event(&event, &room_id))?;
|
||||
@@ -613,16 +610,16 @@ impl OlmMachine {
|
||||
let event_json: Event<'_> = serde_json::from_str(decrypted.event.json().get())?;
|
||||
|
||||
Ok(match &encryption_info.algorithm_info {
|
||||
AlgorithmInfo::MegolmV1AesSha2 {
|
||||
curve25519_key,
|
||||
sender_claimed_keys,
|
||||
forwarding_curve25519_key_chain,
|
||||
} => DecryptedEvent {
|
||||
clear_event: serde_json::to_string(&event_json)?,
|
||||
sender_curve25519_key: curve25519_key.to_owned(),
|
||||
claimed_ed25519_key: sender_claimed_keys.get(&DeviceKeyAlgorithm::Ed25519).cloned(),
|
||||
forwarding_curve25519_chain: forwarding_curve25519_key_chain.to_owned(),
|
||||
},
|
||||
AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, sender_claimed_keys } => {
|
||||
DecryptedEvent {
|
||||
clear_event: serde_json::to_string(&event_json)?,
|
||||
sender_curve25519_key: curve25519_key.to_owned(),
|
||||
claimed_ed25519_key: sender_claimed_keys
|
||||
.get(&DeviceKeyAlgorithm::Ed25519)
|
||||
.cloned(),
|
||||
forwarding_curve25519_chain: vec![],
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -640,7 +637,7 @@ impl OlmMachine {
|
||||
event: &str,
|
||||
room_id: &str,
|
||||
) -> Result<KeyRequestPair, DecryptionError> {
|
||||
let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?;
|
||||
let event: Raw<_> = serde_json::from_str(event)?;
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
|
||||
let (cancel, request) =
|
||||
@@ -661,16 +658,20 @@ impl OlmMachine {
|
||||
///
|
||||
/// * `rounds` - The number of rounds that should be used when expanding the
|
||||
/// passphrase into an key.
|
||||
pub fn export_keys(&self, passphrase: &str, rounds: i32) -> Result<String, CryptoStoreError> {
|
||||
let keys = self.runtime.block_on(self.inner.export_keys(|_| true))?;
|
||||
pub fn export_room_keys(
|
||||
&self,
|
||||
passphrase: &str,
|
||||
rounds: i32,
|
||||
) -> Result<String, CryptoStoreError> {
|
||||
let keys = self.runtime.block_on(self.inner.export_room_keys(|_| true))?;
|
||||
|
||||
let encrypted = encrypt_key_export(&keys, passphrase, rounds as u32)
|
||||
let encrypted = encrypt_room_key_export(&keys, passphrase, rounds as u32)
|
||||
.map_err(CryptoStoreError::Serialization)?;
|
||||
|
||||
Ok(encrypted)
|
||||
}
|
||||
|
||||
fn import_keys_helper(
|
||||
fn import_room_keys_helper(
|
||||
&self,
|
||||
keys: Vec<ExportedRoomKey>,
|
||||
from_backup: bool,
|
||||
@@ -680,7 +681,8 @@ impl OlmMachine {
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
let result = self.runtime.block_on(self.inner.import_keys(keys, from_backup, listener))?;
|
||||
let result =
|
||||
self.runtime.block_on(self.inner.import_room_keys(keys, from_backup, listener))?;
|
||||
|
||||
Ok(KeysImportResult {
|
||||
imported: result.imported_count as i64,
|
||||
@@ -708,20 +710,20 @@ impl OlmMachine {
|
||||
///
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the key import.
|
||||
pub fn import_keys(
|
||||
pub fn import_room_keys(
|
||||
&self,
|
||||
keys: &str,
|
||||
passphrase: &str,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<KeysImportResult, KeyImportError> {
|
||||
let keys = Cursor::new(keys);
|
||||
let keys = decrypt_key_export(keys, passphrase)?;
|
||||
self.import_keys_helper(keys, false, progress_listener)
|
||||
let keys = decrypt_room_key_export(keys, passphrase)?;
|
||||
self.import_room_keys_helper(keys, false, progress_listener)
|
||||
}
|
||||
|
||||
/// Import room keys from the given serialized unencrypted key export.
|
||||
///
|
||||
/// This method is the same as [`OlmMachine::import_keys`] but the
|
||||
/// This method is the same as [`OlmMachine::import_room_keys`] but the
|
||||
/// decryption step is skipped and should be performed by the caller. This
|
||||
/// should be used if the room keys are coming from the server-side backup,
|
||||
/// the method will mark all imported room keys as backed up.
|
||||
@@ -732,7 +734,7 @@ impl OlmMachine {
|
||||
///
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the key import.
|
||||
pub fn import_decrypted_keys(
|
||||
pub fn import_decrypted_room_keys(
|
||||
&self,
|
||||
keys: &str,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
@@ -741,7 +743,7 @@ impl OlmMachine {
|
||||
|
||||
let keys = keys.into_iter().map(serde_json::from_value).filter_map(|k| k.ok()).collect();
|
||||
|
||||
self.import_keys_helper(keys, true, progress_listener)
|
||||
self.import_room_keys_helper(keys, true, progress_listener)
|
||||
}
|
||||
|
||||
/// Discard the currently active room key for the given room if there is
|
||||
@@ -1243,10 +1245,7 @@ impl OlmMachine {
|
||||
) -> Option<OutgoingVerificationRequest> {
|
||||
let user_id = UserId::parse(user_id).ok()?;
|
||||
|
||||
self.inner
|
||||
.get_verification(&user_id, flow_id)
|
||||
.and_then(|s| s.sas_v1())
|
||||
.and_then(|s| s.accept().map(|r| r.into()))
|
||||
self.inner.get_verification(&user_id, flow_id)?.sas_v1()?.accept().map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Get a list of emoji indices of the emoji representation of the short
|
||||
|
||||
@@ -358,15 +358,15 @@ interface OlmMachine {
|
||||
KeyRequestPair request_room_key([ByRef] string event, [ByRef] string room_id);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
string export_keys([ByRef] string passphrase, i32 rounds);
|
||||
string export_room_keys([ByRef] string passphrase, i32 rounds);
|
||||
[Throws=KeyImportError]
|
||||
KeysImportResult import_keys(
|
||||
KeysImportResult import_room_keys(
|
||||
[ByRef] string keys,
|
||||
[ByRef] string passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=KeyImportError]
|
||||
KeysImportResult import_decrypted_keys(
|
||||
KeysImportResult import_decrypted_room_keys(
|
||||
[ByRef] string keys,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
[package]
|
||||
authors = ["Ivan Enderlin <ivane@element.io>"]
|
||||
name = "matrix-sdk-crypto-js"
|
||||
description = "Matrix encryption library, for JavaScript"
|
||||
authors = ["Ivan Enderlin <ivane@element.io>"]
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
|
||||
license = "Apache-2.0"
|
||||
name = "matrix-sdk-crypto-js"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
version = "0.5.0"
|
||||
version = "0.1.0-alpha.0"
|
||||
publish = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
@@ -22,18 +22,24 @@ wasm-opt = ['-Oz']
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
docsrs = []
|
||||
default = ["tracing", "qrcode"]
|
||||
qrcode = ["matrix-sdk-crypto/qrcode", "dep:matrix-sdk-qrcode"]
|
||||
tracing = []
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] }
|
||||
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
|
||||
matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] }
|
||||
matrix-sdk-indexeddb = { version = "0.2.0", path = "../../crates/matrix-sdk-indexeddb", features = ["experimental-nodejs"] }
|
||||
matrix-sdk-qrcode = { version = "0.4.0", path = "../../crates/matrix-sdk-qrcode", optional = true }
|
||||
ruma = { version = "0.7.0", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { version = "0.3.0", features = ["js"] }
|
||||
wasm-bindgen = "0.2.80"
|
||||
wasm-bindgen-futures = "0.4.30"
|
||||
js-sys = "0.3.49"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
serde_json = "1.0.79"
|
||||
http = "0.2.6"
|
||||
anyhow = "1.0"
|
||||
anyhow = "1.0.58"
|
||||
tracing = { version = "0.1.35", default-features = false, features = ["attributes"] }
|
||||
tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std"] }
|
||||
zeroize = "1.3.0"
|
||||
|
||||
@@ -37,7 +37,11 @@ TBD
|
||||
|
||||
## Documentation
|
||||
|
||||
To generate the documentation, please run the following command:
|
||||
[The documentation can be found
|
||||
online](https://matrix-org.github.io/matrix-rust-sdk/bindings/matrix-sdk-crypto-js/).
|
||||
|
||||
To generate the documentation locally, please run the following
|
||||
command:
|
||||
|
||||
```sh
|
||||
$ npm run doc
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# configuration file for git-cliff (0.1.0)
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Matrix SDK Crypto JavaScript Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://tera.netlify.app/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | filter(attribute="scope", value="crypto-js") | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
"""
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/matrix-org/matrix-rust-sdk/issues/${2}))"},
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^refactor", group = "Refactoring"},
|
||||
{ message = "^ci", group = "Continuous Integration"},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = false
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = ""
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags chronologically
|
||||
date_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@matrix-org/matrix-sdk-crypto-js",
|
||||
"version": "0.5.0",
|
||||
"version": "0.1.0-alpha.0",
|
||||
"homepage": "https://github.com/matrix-org/matrix-rust-sdk",
|
||||
"description": "Matrix encryption library, for JavaScript",
|
||||
"license": "Apache-2.0",
|
||||
@@ -26,16 +26,23 @@
|
||||
"pkg/matrix_sdk_crypto.d.ts"
|
||||
],
|
||||
"devDependencies": {
|
||||
"wasm-pack": "^0.10.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"fake-indexeddb": "^4.0",
|
||||
"jest": "^28.1.0",
|
||||
"typedoc": "^0.22.17"
|
||||
"typedoc": "^0.22.17",
|
||||
"wasm-pack": "^0.10.2",
|
||||
"yargs-parser": "~21.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./pkg",
|
||||
"build": "cross-env RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --scope matrix-org --out-dir ./pkg",
|
||||
"test": "jest --verbose",
|
||||
"doc": "typedoc --tsconfig ."
|
||||
"doc": "typedoc --tsconfig .",
|
||||
"prepack": "npm run build && npm run test",
|
||||
"pack": "wasm-pack pack",
|
||||
"prepublish": "npm run pack",
|
||||
"publish": "wasm-pack publish"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
//! Attachment API.
|
||||
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// A type to encrypt and to decrypt anything that can fit in an
|
||||
/// `Uint8Array`, usually big buffer.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct Attachment;
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Attachment {
|
||||
/// Encrypt the content of the `Uint8Array`.
|
||||
///
|
||||
/// It produces an `EncryptedAttachment`, which can be used to
|
||||
/// retrieve the media encryption information, or the encrypted
|
||||
/// data.
|
||||
#[wasm_bindgen]
|
||||
pub fn encrypt(array: &[u8]) -> Result<EncryptedAttachment, JsError> {
|
||||
let mut cursor = Cursor::new(array);
|
||||
let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
|
||||
|
||||
let mut encrypted_data = Vec::new();
|
||||
encryptor.read_to_end(&mut encrypted_data)?;
|
||||
|
||||
let media_encryption_info = Some(encryptor.finish());
|
||||
|
||||
Ok(EncryptedAttachment { encrypted_data, media_encryption_info })
|
||||
}
|
||||
|
||||
/// Decrypt an `EncryptedAttachment`.
|
||||
///
|
||||
/// The encrypted attachment can be created manually, or from the
|
||||
/// `encrypt` method.
|
||||
///
|
||||
/// **Warning**: The encrypted attachment can be used only
|
||||
/// **once**! The encrypted data will still be present, but the
|
||||
/// media encryption info (which contain secrets) will be
|
||||
/// destroyed. It is still possible to get a JSON-encoded backup
|
||||
/// by calling `EncryptedAttachment.mediaEncryptionInfo`.
|
||||
pub fn decrypt(attachment: &mut EncryptedAttachment) -> Result<Vec<u8>, JsError> {
|
||||
let media_encryption_info = match attachment.media_encryption_info.take() {
|
||||
Some(media_encryption_info) => media_encryption_info,
|
||||
None => {
|
||||
return Err(JsError::new(
|
||||
"The media encryption info are absent from the given encrypted attachment",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let encrypted_data: &[u8] = attachment.encrypted_data.as_slice();
|
||||
|
||||
let mut cursor = Cursor::new(encrypted_data);
|
||||
let mut decryptor =
|
||||
matrix_sdk_crypto::AttachmentDecryptor::new(&mut cursor, media_encryption_info)?;
|
||||
|
||||
let mut decrypted_data = Vec::new();
|
||||
decryptor.read_to_end(&mut decrypted_data)?;
|
||||
|
||||
Ok(decrypted_data)
|
||||
}
|
||||
}
|
||||
|
||||
/// An encrypted attachment, usually created from `Attachment.encrypt`.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct EncryptedAttachment {
|
||||
media_encryption_info: Option<matrix_sdk_crypto::MediaEncryptionInfo>,
|
||||
encrypted_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl EncryptedAttachment {
|
||||
/// Create a new encrypted attachment manually.
|
||||
///
|
||||
/// It needs encrypted data, stored in an `Uint8Array`, and a
|
||||
/// [media encryption
|
||||
/// information](https://docs.rs/matrix-sdk-crypto/latest/matrix_sdk_crypto/struct.MediaEncryptionInfo.html),
|
||||
/// as a JSON-encoded string.
|
||||
///
|
||||
/// The media encryption information aren't stored as a string:
|
||||
/// they are parsed, validated and fully deserialized.
|
||||
///
|
||||
/// See [the specification to learn
|
||||
/// more](https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes).
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
encrypted_data: Vec<u8>,
|
||||
media_encryption_info: &str,
|
||||
) -> Result<EncryptedAttachment, JsError> {
|
||||
Ok(Self {
|
||||
encrypted_data,
|
||||
media_encryption_info: Some(serde_json::from_str(media_encryption_info)?),
|
||||
})
|
||||
}
|
||||
|
||||
/// The actual encrypted data.
|
||||
///
|
||||
/// **Warning**: It returns a **copy** of the entire encrypted
|
||||
/// data; be nice with your memory.
|
||||
#[wasm_bindgen(getter, js_name = "encryptedData")]
|
||||
pub fn encrypted_data(&self) -> Vec<u8> {
|
||||
self.encrypted_data.clone()
|
||||
}
|
||||
|
||||
/// Return the media encryption info as a JSON-encoded string. The
|
||||
/// structure is fully valid.
|
||||
///
|
||||
/// If the media encryption info have been consumed already, it
|
||||
/// will return `null`.
|
||||
#[wasm_bindgen(getter, js_name = "mediaEncryptionInfo")]
|
||||
pub fn media_encryption_info(&self) -> Option<String> {
|
||||
serde_json::to_string(self.media_encryption_info.as_ref()?).ok()
|
||||
}
|
||||
|
||||
/// Check whether the media encryption info has been consumed by
|
||||
/// `Attachment.decrypt` already.
|
||||
#[wasm_bindgen(getter, js_name = "hasMediaEncryptionInfoBeenConsumed")]
|
||||
pub fn has_media_encryption_info_been_consumed(&self) -> bool {
|
||||
self.media_encryption_info.is_none()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
//! Types for a `Device`.
|
||||
|
||||
use js_sys::{Array, Map, Promise};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
future::future_to_promise,
|
||||
identifiers::{self, DeviceId, UserId},
|
||||
impl_from_to_inner,
|
||||
js::try_array_to_vec,
|
||||
types, verification, vodozemac,
|
||||
};
|
||||
|
||||
/// A device represents a E2EE capable client of an user.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct Device {
|
||||
pub(crate) inner: matrix_sdk_crypto::Device,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::Device => Device);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Device {
|
||||
/// Request an interactive verification with this device.
|
||||
#[wasm_bindgen(js_name = "requestVerification")]
|
||||
pub fn request_verification(&self, methods: Option<Array>) -> Result<Promise, JsError> {
|
||||
let methods =
|
||||
methods.map(try_array_to_vec::<verification::VerificationMethod, _>).transpose()?;
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
let tuple = Array::new();
|
||||
let (verification_request, outgoing_verification_request) = match methods {
|
||||
Some(methods) => me.request_verification_with_methods(methods).await,
|
||||
None => me.request_verification().await,
|
||||
};
|
||||
|
||||
tuple.set(0, verification::VerificationRequest::from(verification_request).into());
|
||||
tuple.set(
|
||||
1,
|
||||
verification::OutgoingVerificationRequest::from(outgoing_verification_request)
|
||||
.try_into()?,
|
||||
);
|
||||
|
||||
Ok(tuple)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Is this device considered to be verified.
|
||||
///
|
||||
/// This method returns true if either the `is_locally_trusted`
|
||||
/// method returns `true` or if the `is_cross_signing_trusted`
|
||||
/// method returns `true`.
|
||||
#[wasm_bindgen(js_name = "isVerified")]
|
||||
pub fn is_verified(&self) -> bool {
|
||||
self.inner.is_verified()
|
||||
}
|
||||
|
||||
/// Is this device considered to be verified using cross signing.
|
||||
#[wasm_bindgen(js_name = "isCrossSigningTrusted")]
|
||||
pub fn is_cross_signing_trusted(&self) -> bool {
|
||||
self.inner.is_cross_signing_trusted()
|
||||
}
|
||||
|
||||
/// Set the local trust state of the device to the given state.
|
||||
///
|
||||
/// This won’t affect any cross signing trust state, this only
|
||||
/// sets a flag marking to have the given trust state.
|
||||
///
|
||||
/// `trust_state` represents the new trust state that should be
|
||||
/// set for the device.
|
||||
#[wasm_bindgen(js_name = "setLocalTrust")]
|
||||
pub fn set_local_trust(&self, local_state: LocalTrust) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
me.set_local_trust(local_state.into()).await?;
|
||||
|
||||
Ok(JsValue::NULL)
|
||||
})
|
||||
}
|
||||
|
||||
/// The user ID of the device owner.
|
||||
#[wasm_bindgen(getter, js_name = "userId")]
|
||||
pub fn user_id(&self) -> UserId {
|
||||
self.inner.user_id().to_owned().into()
|
||||
}
|
||||
|
||||
/// The unique ID of the device.
|
||||
#[wasm_bindgen(getter, js_name = "deviceId")]
|
||||
pub fn device_id(&self) -> DeviceId {
|
||||
self.inner.device_id().to_owned().into()
|
||||
}
|
||||
|
||||
/// Get the human readable name of the device.
|
||||
#[wasm_bindgen(getter, js_name = "displayName")]
|
||||
pub fn display_name(&self) -> Option<String> {
|
||||
self.inner.display_name().map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
/// Get the key of the given key algorithm belonging to this device.
|
||||
#[wasm_bindgen(js_name = "getKey")]
|
||||
pub fn get_key(
|
||||
&self,
|
||||
algorithm: identifiers::DeviceKeyAlgorithmName,
|
||||
) -> Result<Option<vodozemac::DeviceKey>, JsError> {
|
||||
Ok(self.inner.get_key(algorithm.try_into()?).cloned().map(Into::into))
|
||||
}
|
||||
|
||||
/// Get the Curve25519 key of the given device.
|
||||
#[wasm_bindgen(getter, js_name = "curve25519Key")]
|
||||
pub fn curve25519_key(&self) -> Option<vodozemac::Curve25519PublicKey> {
|
||||
self.inner.curve25519_key().map(Into::into)
|
||||
}
|
||||
|
||||
/// Get the Ed25519 key of the given device.
|
||||
#[wasm_bindgen(getter, js_name = "ed25519Key")]
|
||||
pub fn ed25519_key(&self) -> Option<vodozemac::Ed25519PublicKey> {
|
||||
self.inner.ed25519_key().map(Into::into)
|
||||
}
|
||||
|
||||
/// Get a map containing all the device keys.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn keys(&self) -> Map {
|
||||
let map = Map::new();
|
||||
|
||||
for (device_key_id, device_key) in self.inner.keys() {
|
||||
map.set(
|
||||
&identifiers::DeviceKeyId::from(device_key_id.clone()).into(),
|
||||
&vodozemac::DeviceKey::from(device_key.clone()).into(),
|
||||
);
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/// Get a map containing all the device signatures.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn signatures(&self) -> types::Signatures {
|
||||
self.inner.signatures().clone().into()
|
||||
}
|
||||
|
||||
/// Get the trust state of the device.
|
||||
#[wasm_bindgen(getter, js_name = "localTrustState")]
|
||||
pub fn local_trust_state(&self) -> LocalTrust {
|
||||
self.inner.local_trust_state().into()
|
||||
}
|
||||
|
||||
/// Is the device locally marked as trusted?
|
||||
#[wasm_bindgen(js_name = "isLocallyTrusted")]
|
||||
pub fn is_locally_trusted(&self) -> bool {
|
||||
self.inner.is_locally_trusted()
|
||||
}
|
||||
|
||||
/// Is the device locally marked as blacklisted?
|
||||
///
|
||||
/// Blacklisted devices won’t receive any group sessions.
|
||||
#[wasm_bindgen(js_name = "isBlacklisted")]
|
||||
pub fn is_blacklisted(&self) -> bool {
|
||||
self.inner.is_blacklisted()
|
||||
}
|
||||
|
||||
/// Is the device deleted?
|
||||
#[wasm_bindgen(js_name = "isDeleted")]
|
||||
pub fn is_deleted(&self) -> bool {
|
||||
self.inner.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
/// The local trust state of a device.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum LocalTrust {
|
||||
/// The device has been verified and is trusted.
|
||||
Verified,
|
||||
|
||||
/// The device been blacklisted from communicating.
|
||||
BlackListed,
|
||||
|
||||
/// The trust state of the device is being ignored.
|
||||
Ignored,
|
||||
|
||||
/// The trust state is unset.
|
||||
Unset,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::LocalTrust> for LocalTrust {
|
||||
fn from(value: matrix_sdk_crypto::LocalTrust) -> Self {
|
||||
use matrix_sdk_crypto::LocalTrust::*;
|
||||
|
||||
match value {
|
||||
Verified => Self::Verified,
|
||||
BlackListed => Self::BlackListed,
|
||||
Ignored => Self::Ignored,
|
||||
Unset => Self::Unset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalTrust> for matrix_sdk_crypto::LocalTrust {
|
||||
fn from(value: LocalTrust) -> Self {
|
||||
use LocalTrust::*;
|
||||
|
||||
match value {
|
||||
Verified => Self::Verified,
|
||||
BlackListed => Self::BlackListed,
|
||||
Ignored => Self::Ignored,
|
||||
Unset => Self::Unset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A read only view over all devices belonging to a user.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct UserDevices {
|
||||
pub(crate) inner: matrix_sdk_crypto::UserDevices,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::UserDevices => UserDevices);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl UserDevices {
|
||||
/// Get the specific device with the given device ID.
|
||||
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
|
||||
self.inner.get(&device_id.inner).map(Into::into)
|
||||
}
|
||||
|
||||
/// Returns true if there is at least one devices of this user
|
||||
/// that is considered to be verified, false otherwise.
|
||||
///
|
||||
/// This won't consider your own device as verified, as your own
|
||||
/// device is always implicitly verified.
|
||||
#[wasm_bindgen(js_name = "isAnyVerified")]
|
||||
pub fn is_any_verified(&self) -> bool {
|
||||
self.inner.is_any_verified()
|
||||
}
|
||||
|
||||
/// Array over all the device IDs of the user devices.
|
||||
pub fn keys(&self) -> Array {
|
||||
self.inner.keys().map(ToOwned::to_owned).map(DeviceId::from).map(JsValue::from).collect()
|
||||
}
|
||||
|
||||
/// Iterator over all the devices of the user devices.
|
||||
pub fn devices(&self) -> Array {
|
||||
self.inner.devices().map(Device::from).map(JsValue::from).collect()
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,11 @@ pub struct EncryptionSettings {
|
||||
/// created.
|
||||
#[wasm_bindgen(js_name = "historyVisibility")]
|
||||
pub history_visibility: events::HistoryVisibility,
|
||||
|
||||
/// Should untrusted devices receive the room key, or should they be
|
||||
/// excluded from the conversation.
|
||||
#[wasm_bindgen(js_name = "onlyAllowTrustedDevices")]
|
||||
pub only_allow_trusted_devices: bool,
|
||||
}
|
||||
|
||||
impl Default for EncryptionSettings {
|
||||
@@ -40,6 +45,7 @@ impl Default for EncryptionSettings {
|
||||
rotation_period: default.rotation_period.as_micros().try_into().unwrap(),
|
||||
rotation_period_messages: default.rotation_period_msgs,
|
||||
history_visibility: default.history_visibility.into(),
|
||||
only_allow_trusted_devices: default.only_allow_trusted_devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,11 +61,14 @@ impl EncryptionSettings {
|
||||
|
||||
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
|
||||
fn from(value: &EncryptionSettings) -> Self {
|
||||
let algorithm = value.algorithm.clone().into();
|
||||
|
||||
Self {
|
||||
algorithm: value.algorithm.clone().into(),
|
||||
algorithm,
|
||||
rotation_period: Duration::from_micros(value.rotation_period),
|
||||
rotation_period_msgs: value.rotation_period_messages,
|
||||
history_visibility: value.history_visibility.clone().into(),
|
||||
only_allow_trusted_devices: value.only_allow_trusted_devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +85,7 @@ pub enum EncryptionAlgorithm {
|
||||
MegolmV1AesSha2,
|
||||
}
|
||||
|
||||
impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
|
||||
impl From<EncryptionAlgorithm> for matrix_sdk_crypto::types::EventEncryptionAlgorithm {
|
||||
fn from(value: EncryptionAlgorithm) -> Self {
|
||||
use EncryptionAlgorithm::*;
|
||||
|
||||
@@ -87,9 +96,9 @@ impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ruma::EventEncryptionAlgorithm> for EncryptionAlgorithm {
|
||||
fn from(value: ruma::EventEncryptionAlgorithm) -> Self {
|
||||
use ruma::EventEncryptionAlgorithm::*;
|
||||
impl From<matrix_sdk_crypto::types::EventEncryptionAlgorithm> for EncryptionAlgorithm {
|
||||
fn from(value: matrix_sdk_crypto::types::EventEncryptionAlgorithm) -> Self {
|
||||
use matrix_sdk_crypto::types::EventEncryptionAlgorithm::*;
|
||||
|
||||
match value {
|
||||
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::impl_from_to_inner;
|
||||
|
||||
/// A Matrix [user ID].
|
||||
///
|
||||
/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers
|
||||
@@ -12,11 +14,7 @@ pub struct UserId {
|
||||
pub(crate) inner: ruma::OwnedUserId,
|
||||
}
|
||||
|
||||
impl From<ruma::OwnedUserId> for UserId {
|
||||
fn from(inner: ruma::OwnedUserId) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
impl_from_to_inner!(ruma::OwnedUserId => UserId);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl UserId {
|
||||
@@ -66,11 +64,7 @@ pub struct DeviceId {
|
||||
pub(crate) inner: ruma::OwnedDeviceId,
|
||||
}
|
||||
|
||||
impl From<ruma::OwnedDeviceId> for DeviceId {
|
||||
fn from(inner: ruma::OwnedDeviceId) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
impl_from_to_inner!(ruma::OwnedDeviceId => DeviceId);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceId {
|
||||
@@ -88,6 +82,122 @@ impl DeviceId {
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix device key ID.
|
||||
///
|
||||
/// A key algorithm and a device ID, combined with a ‘:’.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeviceKeyId {
|
||||
pub(crate) inner: ruma::OwnedDeviceKeyId,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(ruma::OwnedDeviceKeyId => DeviceKeyId);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceKeyId {
|
||||
/// Parse/validate and create a new `DeviceKeyId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: String) -> Result<DeviceKeyId, JsError> {
|
||||
Ok(Self::from(ruma::DeviceKeyId::parse(id.as_str())?))
|
||||
}
|
||||
|
||||
/// Returns key algorithm of the device key ID.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn algorithm(&self) -> DeviceKeyAlgorithm {
|
||||
self.inner.algorithm().into()
|
||||
}
|
||||
|
||||
/// Returns device ID of the device key ID.
|
||||
#[wasm_bindgen(getter, js_name = "deviceId")]
|
||||
pub fn device_id(&self) -> DeviceId {
|
||||
self.inner.device_id().to_owned().into()
|
||||
}
|
||||
|
||||
/// Return the device key ID as a string.
|
||||
#[wasm_bindgen(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// The basic key algorithms in the specification.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceKeyAlgorithm {
|
||||
pub(crate) inner: ruma::DeviceKeyAlgorithm,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(ruma::DeviceKeyAlgorithm => DeviceKeyAlgorithm);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceKeyAlgorithm {
|
||||
/// Read the device key algorithm's name. If the name is
|
||||
/// `Unknown`, one may be interested by the `to_string` method to
|
||||
/// read the original name.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn name(&self) -> DeviceKeyAlgorithmName {
|
||||
self.inner.clone().into()
|
||||
}
|
||||
|
||||
/// Return the device key algorithm as a string.
|
||||
#[wasm_bindgen(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// The basic key algorithm names in the specification.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum DeviceKeyAlgorithmName {
|
||||
/// The Ed25519 signature algorithm.
|
||||
Ed25519,
|
||||
|
||||
/// The Curve25519 ECDH algorithm.
|
||||
Curve25519,
|
||||
|
||||
/// The Curve25519 ECDH algorithm, but the key also contains
|
||||
/// signatures.
|
||||
SignedCurve25519,
|
||||
|
||||
/// An unknown device key algorithm.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl TryFrom<DeviceKeyAlgorithmName> for ruma::DeviceKeyAlgorithm {
|
||||
type Error = JsError;
|
||||
|
||||
fn try_from(value: DeviceKeyAlgorithmName) -> Result<Self, Self::Error> {
|
||||
use DeviceKeyAlgorithmName::*;
|
||||
|
||||
Ok(match value {
|
||||
Ed25519 => Self::Ed25519,
|
||||
Curve25519 => Self::Curve25519,
|
||||
SignedCurve25519 => Self::SignedCurve25519,
|
||||
Unknown => {
|
||||
return Err(JsError::new(
|
||||
"The `DeviceKeyAlgorithmName.Unknown` variant cannot be converted",
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ruma::DeviceKeyAlgorithm> for DeviceKeyAlgorithmName {
|
||||
fn from(value: ruma::DeviceKeyAlgorithm) -> Self {
|
||||
use ruma::DeviceKeyAlgorithm::*;
|
||||
|
||||
match value {
|
||||
Ed25519 => Self::Ed25519,
|
||||
Curve25519 => Self::Curve25519,
|
||||
SignedCurve25519 => Self::SignedCurve25519,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix [room ID].
|
||||
///
|
||||
/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
|
||||
@@ -97,11 +207,7 @@ pub struct RoomId {
|
||||
pub(crate) inner: ruma::OwnedRoomId,
|
||||
}
|
||||
|
||||
impl From<ruma::OwnedRoomId> for RoomId {
|
||||
fn from(inner: ruma::OwnedRoomId) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
impl_from_to_inner!(ruma::OwnedRoomId => RoomId);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoomId {
|
||||
@@ -173,3 +279,43 @@ impl ServerName {
|
||||
self.inner.is_ip_literal()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix [event ID].
|
||||
///
|
||||
/// An `EventId` is generated randomly or converted from a string
|
||||
/// slice, and can be converted back into a string as needed.
|
||||
///
|
||||
/// [event ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct EventId {
|
||||
pub(crate) inner: ruma::OwnedEventId,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl EventId {
|
||||
/// Parse/validate and create a new `EventId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> Result<EventId, JsError> {
|
||||
Ok(Self { inner: <&ruma::EventId>::try_from(id)?.to_owned() })
|
||||
}
|
||||
|
||||
/// Returns the event's localpart.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn localpart(&self) -> String {
|
||||
self.inner.localpart().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the server name of the event ID.
|
||||
#[wasm_bindgen(getter, js_name = "serverName")]
|
||||
pub fn server_name(&self) -> Option<ServerName> {
|
||||
Some(ServerName { inner: self.inner.server_name()?.to_owned() })
|
||||
}
|
||||
|
||||
/// Return the event ID as a string.
|
||||
#[wasm_bindgen(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
//! User identities.
|
||||
|
||||
use js_sys::{Array, Promise};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
future::future_to_promise, identifiers, impl_from_to_inner, js::try_array_to_vec, requests,
|
||||
verification,
|
||||
};
|
||||
|
||||
pub(crate) struct UserIdentities {
|
||||
inner: matrix_sdk_crypto::UserIdentities,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::UserIdentities => UserIdentities);
|
||||
|
||||
impl From<UserIdentities> for JsValue {
|
||||
fn from(user_identities: UserIdentities) -> Self {
|
||||
use matrix_sdk_crypto::UserIdentities::*;
|
||||
|
||||
match user_identities.inner {
|
||||
Own(own) => JsValue::from(OwnUserIdentity::from(own)),
|
||||
Other(other) => JsValue::from(UserIdentity::from(other)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct representing a cross signing identity of a user.
|
||||
///
|
||||
/// This is the user identity of a user that is our own.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct OwnUserIdentity {
|
||||
inner: matrix_sdk_crypto::OwnUserIdentity,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::OwnUserIdentity => OwnUserIdentity);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl OwnUserIdentity {
|
||||
/// Mark our user identity as verified.
|
||||
///
|
||||
/// This will mark the identity locally as verified and sign it with our own
|
||||
/// device.
|
||||
///
|
||||
/// Returns a signature upload request that needs to be sent out.
|
||||
pub fn verify(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(requests::SignatureUploadRequest::try_from(&me.verify().await?)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a verification request to our other devices.
|
||||
#[wasm_bindgen(js_name = "requestVerification")]
|
||||
pub fn request_verification(&self, methods: Option<Array>) -> Result<Promise, JsError> {
|
||||
let methods =
|
||||
methods.map(try_array_to_vec::<verification::VerificationMethod, _>).transpose()?;
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
let tuple = Array::new();
|
||||
let (verification_request, outgoing_verification_request) = match methods {
|
||||
Some(methods) => me.request_verification_with_methods(methods).await?,
|
||||
None => me.request_verification().await?,
|
||||
};
|
||||
|
||||
tuple.set(0, verification::VerificationRequest::from(verification_request).into());
|
||||
tuple.set(
|
||||
1,
|
||||
verification::OutgoingVerificationRequest::from(outgoing_verification_request)
|
||||
.try_into()?,
|
||||
);
|
||||
|
||||
Ok(tuple)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Does our user identity trust our own device, i.e. have we signed our own
|
||||
/// device keys with our self-signing key?
|
||||
#[wasm_bindgen(js_name = "trustsOurOwnDevice")]
|
||||
pub fn trusts_our_own_device(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move { Ok(me.trusts_our_own_device().await?) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct representing a cross signing identity of a user.
|
||||
///
|
||||
/// This is the user identity of a user that isn't our own. Other users will
|
||||
/// only contain a master key and a self signing key, meaning that only device
|
||||
/// signatures can be checked with this identity.
|
||||
///
|
||||
/// This struct wraps a read-only version of the struct and allows verifications
|
||||
/// to be requested to verify our own device with the user identity.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct UserIdentity {
|
||||
inner: matrix_sdk_crypto::UserIdentity,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::UserIdentity => UserIdentity);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl UserIdentity {
|
||||
/// Is this user identity verified?
|
||||
#[wasm_bindgen(js_name = "isVerified")]
|
||||
pub fn is_verified(&self) -> bool {
|
||||
self.inner.is_verified()
|
||||
}
|
||||
|
||||
/// Manually verify this user.
|
||||
///
|
||||
/// This method will attempt to sign the user identity using our private
|
||||
/// cross signing key.
|
||||
///
|
||||
/// This method fails if we don't have the private part of our user-signing
|
||||
/// key.
|
||||
///
|
||||
/// Returns a request that needs to be sent out for the user to be marked as
|
||||
/// verified.
|
||||
pub fn verify(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(requests::SignatureUploadRequest::try_from(&me.verify().await?)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a `VerificationRequest` object after the verification
|
||||
/// request content has been sent out. }
|
||||
#[wasm_bindgen(js_name = "requestVerification")]
|
||||
pub fn request_verification(
|
||||
&self,
|
||||
room_id: &identifiers::RoomId,
|
||||
request_event_id: &identifiers::EventId,
|
||||
methods: Option<Array>,
|
||||
) -> Result<Promise, JsError> {
|
||||
let me = self.inner.clone();
|
||||
let room_id = room_id.inner.clone();
|
||||
let request_event_id = request_event_id.inner.clone();
|
||||
let methods =
|
||||
methods.map(try_array_to_vec::<verification::VerificationMethod, _>).transpose()?;
|
||||
|
||||
Ok(future_to_promise::<_, verification::VerificationRequest>(async move {
|
||||
Ok(me
|
||||
.request_verification(room_id.as_ref(), request_event_id.as_ref(), methods)
|
||||
.await
|
||||
.into())
|
||||
}))
|
||||
}
|
||||
|
||||
/// Send a verification request to the given user.
|
||||
///
|
||||
/// The returned content needs to be sent out into a DM room with the given
|
||||
/// user.
|
||||
///
|
||||
/// After the content has been sent out a VerificationRequest can be started
|
||||
/// with the `request_verification` method.
|
||||
#[wasm_bindgen(js_name = "verificationRequestContent")]
|
||||
pub fn verification_request_content(&self, methods: Option<Array>) -> Result<Promise, JsError> {
|
||||
let me = self.inner.clone();
|
||||
let methods =
|
||||
methods.map(try_array_to_vec::<verification::VerificationMethod, _>).transpose()?;
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(&me.verification_request_content(methods).await)?)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use js_sys::{Array, Object, Reflect};
|
||||
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
|
||||
|
||||
/// A really hacky and dirty code to downcast a `JsValue` to `T:
|
||||
/// RefFromWasmAbi`, inspired by
|
||||
/// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288.
|
||||
///
|
||||
/// The returned value is likely to be a `wasm_bindgen::__ref::Ref<T>`.
|
||||
pub(crate) fn downcast<T>(value: &JsValue, classname: &str) -> Result<T::Anchor, JsError>
|
||||
where
|
||||
T: RefFromWasmAbi<Abi = u32>,
|
||||
{
|
||||
let constructor_name = Object::get_prototype_of(value).constructor().name();
|
||||
|
||||
if constructor_name == classname {
|
||||
let pointer = Reflect::get(value, &JsValue::from_str("ptr"))
|
||||
.map_err(|_| JsError::new("Failed to read the `JsValue` pointer"))?;
|
||||
let pointer = pointer
|
||||
.as_f64()
|
||||
.ok_or_else(|| JsError::new("Failed to read the `JsValue` pointer as a `f64`"))?
|
||||
as u32;
|
||||
|
||||
Ok(unsafe { T::ref_from_abi(pointer) })
|
||||
} else {
|
||||
Err(JsError::new(&format!(
|
||||
"Expect an `{classname}` instance, received `{constructor_name}` instead",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform a value `JS` from JavaScript to a Rust wrapper, to a
|
||||
/// Rust wrapped type.
|
||||
pub(crate) fn try_array_to_vec<JS, Rust>(
|
||||
array: Array,
|
||||
) -> Result<Vec<Rust>, <JS as TryFrom<JsValue>>::Error>
|
||||
where
|
||||
JS: TryFrom<JsValue> + Into<Rust>,
|
||||
{
|
||||
array.iter().map(|item| JS::try_from(item).map(Into::into)).collect()
|
||||
}
|
||||
@@ -17,42 +17,34 @@
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
#![allow(clippy::drop_non_drop)] // triggered by wasm_bindgen code
|
||||
|
||||
pub mod attachment;
|
||||
pub mod device;
|
||||
pub mod encryption;
|
||||
pub mod events;
|
||||
mod future;
|
||||
pub mod identifiers;
|
||||
pub mod identities;
|
||||
mod js;
|
||||
pub mod machine;
|
||||
mod macros;
|
||||
pub mod olm;
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
pub mod store;
|
||||
pub mod sync_events;
|
||||
mod tracing;
|
||||
pub mod types;
|
||||
pub mod verification;
|
||||
pub mod vodozemac;
|
||||
|
||||
use js_sys::{Object, Reflect};
|
||||
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// A really hacky and dirty code to downcast a `JsValue` to `T:
|
||||
/// RefFromWasmAbi`, inspired by
|
||||
/// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288.
|
||||
/// Run some stuff when the Wasm module is instantiated.
|
||||
///
|
||||
/// The returned value is likely to be a `wasm_bindgen::__ref::Ref<T>`.
|
||||
fn downcast<T>(value: &JsValue, classname: &str) -> Result<T::Anchor, JsError>
|
||||
where
|
||||
T: RefFromWasmAbi<Abi = u32>,
|
||||
{
|
||||
let constructor_name = Object::get_prototype_of(value).constructor().name();
|
||||
|
||||
if constructor_name == classname {
|
||||
let pointer = Reflect::get(value, &JsValue::from_str("ptr"))
|
||||
.map_err(|_| JsError::new("Failed to read the `JsValue` pointer"))?;
|
||||
let pointer = pointer
|
||||
.as_f64()
|
||||
.ok_or_else(|| JsError::new("Failed to read the `JsValue` pointer as a `f64`"))?
|
||||
as u32;
|
||||
|
||||
Ok(unsafe { T::ref_from_abi(pointer) })
|
||||
} else {
|
||||
Err(JsError::new(&format!(
|
||||
"Expect an `{}` instance, received `{}` instead",
|
||||
classname, constructor_name,
|
||||
)))
|
||||
}
|
||||
/// Right now, it does the following:
|
||||
///
|
||||
/// * Redirect Rust panics to JavaScript console.
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn start() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use js_sys::{Array, Map, Promise, Set};
|
||||
use ruma::{
|
||||
events::room::encrypted::OriginalSyncRoomEncryptedEvent, DeviceKeyAlgorithm,
|
||||
OwnedTransactionId, UInt,
|
||||
};
|
||||
use serde_json::Value as JsonValue;
|
||||
use js_sys::{Array, Function, Map, Promise, Set};
|
||||
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
downcast, encryption,
|
||||
device, encryption,
|
||||
future::future_to_promise,
|
||||
identifiers, requests,
|
||||
identifiers, identities,
|
||||
js::downcast,
|
||||
olm, requests,
|
||||
requests::OutgoingRequest,
|
||||
responses::{self, response_from_string},
|
||||
sync_events,
|
||||
store, sync_events, types, verification, vodozemac,
|
||||
};
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol
|
||||
@@ -37,16 +36,74 @@ impl OlmMachine {
|
||||
/// `user_id` represents the unique ID of the user that owns this
|
||||
/// machine. `device_id` represents the unique ID of the device
|
||||
/// that owns this machine.
|
||||
///
|
||||
/// `store_name` and `store_passphrase` are both optional, but
|
||||
/// must be both set to have an effect. If they are both set, the
|
||||
/// state of the machine will persist in a database named
|
||||
/// `store_name` where its content is encrypted by the passphrase
|
||||
/// given by `store_passphrase`. If they are not both set, the
|
||||
/// created machine will keep the encryption keys only in memory,
|
||||
/// and once the object is dropped, the keys will be lost.
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(user_id: &identifiers::UserId, device_id: &identifiers::DeviceId) -> Promise {
|
||||
pub fn new(
|
||||
user_id: &identifiers::UserId,
|
||||
device_id: &identifiers::DeviceId,
|
||||
store_name: Option<String>,
|
||||
store_passphrase: Option<String>,
|
||||
) -> Promise {
|
||||
let user_id = user_id.inner.clone();
|
||||
let device_id = device_id.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
let store = match (store_name, store_passphrase) {
|
||||
// We need this `#[cfg]` because `IndexeddbCryptoStore`
|
||||
// implements `CryptoStore` only on `target_arch =
|
||||
// "wasm32"`. Without that, we could have a compilation
|
||||
// error when checking the entire workspace. In
|
||||
// practise, it doesn't impact this crate because it's
|
||||
// always compiled for `wasm32`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
(Some(store_name), Some(mut store_passphrase)) => {
|
||||
use std::sync::Arc;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let store = Some(
|
||||
matrix_sdk_indexeddb::IndexeddbCryptoStore::open_with_passphrase(
|
||||
&store_name,
|
||||
&store_passphrase,
|
||||
)
|
||||
.await
|
||||
.map(Arc::new)?,
|
||||
);
|
||||
|
||||
store_passphrase.zeroize();
|
||||
|
||||
store
|
||||
}
|
||||
|
||||
(Some(_), None) => return Err(anyhow::Error::msg("The `store_name` has been set, and so, it expects a `store_passphrase`, which is not set; please provide one")),
|
||||
|
||||
(None, Some(_)) => return Err(anyhow::Error::msg("The `store_passphrase` has been set, but it has an effect only if `store_name` is set, which is not; please provide one")),
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Ok(OlmMachine {
|
||||
inner: matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
|
||||
.await,
|
||||
inner: match store {
|
||||
Some(store) => {
|
||||
matrix_sdk_crypto::OlmMachine::with_store(
|
||||
user_id.as_ref(),
|
||||
device_id.as_ref(),
|
||||
store,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
None => {
|
||||
matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
|
||||
.await
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -65,7 +122,7 @@ impl OlmMachine {
|
||||
|
||||
/// Get the public parts of our Olm identity keys.
|
||||
#[wasm_bindgen(getter, js_name = "identityKeys")]
|
||||
pub fn identity_keys(&self) -> IdentityKeys {
|
||||
pub fn identity_keys(&self) -> vodozemac::IdentityKeys {
|
||||
self.inner.identity_keys().into()
|
||||
}
|
||||
|
||||
@@ -274,7 +331,7 @@ impl OlmMachine {
|
||||
event: &str,
|
||||
room_id: &identifiers::RoomId,
|
||||
) -> Result<Promise, JsError> {
|
||||
let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?;
|
||||
let event: Raw<_> = serde_json::from_str(event)?;
|
||||
let room_id = room_id.inner.clone();
|
||||
let me = self.inner.clone();
|
||||
|
||||
@@ -285,6 +342,98 @@ impl OlmMachine {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the status of the private cross signing keys.
|
||||
///
|
||||
/// This can be used to check which private cross signing keys we
|
||||
/// have stored locally.
|
||||
#[wasm_bindgen(js_name = "crossSigningStatus")]
|
||||
pub fn cross_signing_status(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise::<_, olm::CrossSigningStatus>(async move {
|
||||
Ok(me.cross_signing_status().await.into())
|
||||
})
|
||||
}
|
||||
|
||||
/// Export all the private cross signing keys we have.
|
||||
///
|
||||
/// The export will contain the seed for the ed25519 keys as a
|
||||
/// unpadded base64 encoded string.
|
||||
///
|
||||
/// This method returns None if we don’t have any private cross
|
||||
/// signing keys.
|
||||
#[wasm_bindgen(js_name = "exportCrossSigningKeys")]
|
||||
pub fn export_cross_signing_keys(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(me.export_cross_signing_keys().await.map(store::CrossSigningKeyExport::from))
|
||||
})
|
||||
}
|
||||
|
||||
/// Import our private cross signing keys.
|
||||
///
|
||||
/// The export needs to contain the seed for the ed25519 keys as
|
||||
/// an unpadded base64 encoded string.
|
||||
#[wasm_bindgen(js_name = "importCrossSigningKeys")]
|
||||
pub fn import_cross_signing_keys(&self, export: store::CrossSigningKeyExport) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
let export = export.inner;
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(me.import_cross_signing_keys(export).await.map(olm::CrossSigningStatus::from)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new cross signing identity and get the upload request
|
||||
/// to push the new public keys to the server.
|
||||
///
|
||||
/// Warning: This will delete any existing cross signing keys that
|
||||
/// might exist on the server and thus will reset the trust
|
||||
/// between all the devices.
|
||||
///
|
||||
/// Uploading these keys will require user interactive auth.
|
||||
#[wasm_bindgen(js_name = "bootstrapCrossSigning")]
|
||||
pub fn bootstrap_cross_signing(&self, reset: bool) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
let (upload_signing_keys_request, upload_signatures_request) =
|
||||
me.bootstrap_cross_signing(reset).await?;
|
||||
|
||||
let tuple = Array::new();
|
||||
tuple.set(
|
||||
0,
|
||||
requests::SigningKeysUploadRequest::try_from(&upload_signing_keys_request)?.into(),
|
||||
);
|
||||
tuple.set(
|
||||
1,
|
||||
requests::SignatureUploadRequest::try_from(&upload_signatures_request)?.into(),
|
||||
);
|
||||
|
||||
Ok(tuple)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the cross signing user identity of a user.
|
||||
#[wasm_bindgen(js_name = "getIdentity")]
|
||||
pub fn get_identity(&self, user_id: &identifiers::UserId) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
let user_id = user_id.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(me.get_identity(user_id.as_ref(), None).await?.map(identities::UserIdentities::from))
|
||||
})
|
||||
}
|
||||
|
||||
/// Sign the given message using our device key and if available
|
||||
/// cross-signing master key.
|
||||
pub fn sign(&self, message: String) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise::<_, types::Signatures>(async move { Ok(me.sign(&message).await.into()) })
|
||||
}
|
||||
|
||||
/// Invalidate the currently active outbound group session for the
|
||||
/// given room.
|
||||
///
|
||||
@@ -377,70 +526,206 @@ impl OlmMachine {
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// An Ed25519 public key, used to verify digital signatures.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ed25519PublicKey {
|
||||
inner: vodozemac::Ed25519PublicKey,
|
||||
}
|
||||
/// Get a map holding all the devices of a user.
|
||||
///
|
||||
/// `user_id` represents the unique ID of the user that the
|
||||
/// devices belong to.
|
||||
#[wasm_bindgen(js_name = "getUserDevices")]
|
||||
pub fn get_user_devices(&self, user_id: &identifiers::UserId) -> Promise {
|
||||
let user_id = user_id.inner.clone();
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Ed25519PublicKey {
|
||||
/// The number of bytes an Ed25519 public key has.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn length(&self) -> usize {
|
||||
vodozemac::Ed25519PublicKey::LENGTH
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise::<_, device::UserDevices>(async move {
|
||||
Ok(me.get_user_devices(&user_id, None).await.map(Into::into)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Serialize an Ed25519 public key to an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
/// Get a specific device of a user if one is found and the crypto store
|
||||
/// didn't throw an error.
|
||||
///
|
||||
/// `user_id` represents the unique ID of the user that the
|
||||
/// identity belongs to. `device_id` represents the unique ID of
|
||||
/// the device.
|
||||
#[wasm_bindgen(js_name = "getDevice")]
|
||||
pub fn get_device(
|
||||
&self,
|
||||
user_id: &identifiers::UserId,
|
||||
device_id: &identifiers::DeviceId,
|
||||
) -> Promise {
|
||||
let user_id = user_id.inner.clone();
|
||||
let device_id = device_id.inner.clone();
|
||||
|
||||
/// A Curve25519 public key.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Curve25519PublicKey {
|
||||
inner: vodozemac::Curve25519PublicKey,
|
||||
}
|
||||
let me = self.inner.clone();
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Curve25519PublicKey {
|
||||
/// The number of bytes a Curve25519 public key has.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn length(&self) -> usize {
|
||||
vodozemac::Curve25519PublicKey::LENGTH
|
||||
future_to_promise::<_, Option<device::Device>>(async move {
|
||||
Ok(me.get_device(&user_id, &device_id, None).await?.map(Into::into))
|
||||
})
|
||||
}
|
||||
|
||||
/// Serialize an Curve25519 public key to an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct holding the two public identity keys of an account.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct IdentityKeys {
|
||||
/// The Ed25519 public key, used for signing.
|
||||
pub ed25519: Ed25519PublicKey,
|
||||
|
||||
/// The Curve25519 public key, used for establish shared secrets.
|
||||
pub curve25519: Curve25519PublicKey,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::olm::IdentityKeys> for IdentityKeys {
|
||||
fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self {
|
||||
Self {
|
||||
ed25519: Ed25519PublicKey { inner: value.ed25519 },
|
||||
curve25519: Curve25519PublicKey { inner: value.curve25519 },
|
||||
}
|
||||
/// Get a verification object for the given user ID with the given
|
||||
/// flow ID (a to-device request ID if the verification has been
|
||||
/// requested by a to-device request, or a room event ID if the
|
||||
/// verification has been requested by a room event).
|
||||
///
|
||||
/// It returns a “`Verification` object”, which is either a `Sas`
|
||||
/// or `Qr` object.
|
||||
#[wasm_bindgen(js_name = "getVerification")]
|
||||
pub fn get_verification(
|
||||
&self,
|
||||
user_id: &identifiers::UserId,
|
||||
flow_id: &str,
|
||||
) -> Result<JsValue, JsError> {
|
||||
self.inner
|
||||
.get_verification(&user_id.inner, flow_id)
|
||||
.map(verification::Verification)
|
||||
.map(JsValue::try_from)
|
||||
.transpose()
|
||||
.map(JsValue::from)
|
||||
}
|
||||
|
||||
/// Get a verification request object with the given flow ID.
|
||||
#[wasm_bindgen(js_name = "getVerificationRequest")]
|
||||
pub fn get_verification_request(
|
||||
&self,
|
||||
user_id: &identifiers::UserId,
|
||||
flow_id: &str,
|
||||
) -> Option<verification::VerificationRequest> {
|
||||
self.inner.get_verification_request(&user_id.inner, flow_id).map(Into::into)
|
||||
}
|
||||
|
||||
/// Get all the verification requests of a given user.
|
||||
#[wasm_bindgen(js_name = "getVerificationRequests")]
|
||||
pub fn get_verification_requests(&self, user_id: &identifiers::UserId) -> Array {
|
||||
self.inner
|
||||
.get_verification_requests(&user_id.inner)
|
||||
.into_iter()
|
||||
.map(verification::VerificationRequest::from)
|
||||
.map(JsValue::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Receive an unencrypted verification event.
|
||||
///
|
||||
/// This method can be used to pass verification events that are
|
||||
/// happening in unencrypted rooms to the `OlmMachine`.
|
||||
///
|
||||
/// Note: This does not need to be called for encrypted events
|
||||
/// since those will get passed to the `OlmMachine` during
|
||||
/// decryption.
|
||||
#[wasm_bindgen(js_name = "receiveUnencryptedVerificationEvent")]
|
||||
pub fn receive_unencrypted_verification_event(&self, event: &str) -> Result<Promise, JsError> {
|
||||
let event: ruma::events::AnyMessageLikeEvent = serde_json::from_str(event)?;
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(me
|
||||
.receive_unencrypted_verification_event(&event)
|
||||
.await
|
||||
.map(|_| JsValue::UNDEFINED)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Export the keys that match the given predicate.
|
||||
///
|
||||
/// `predicate` is a closure that will be called for every known
|
||||
/// `InboundGroupSession`, which represents a room key. If the closure
|
||||
/// returns `true`, the `InboundGroupSession` will be included in the
|
||||
/// export, otherwise it won't.
|
||||
#[wasm_bindgen(js_name = "exportRoomKeys")]
|
||||
pub fn export_room_keys(&self, predicate: Function) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.export_room_keys(|session| {
|
||||
let session = session.clone();
|
||||
|
||||
predicate
|
||||
.call1(&JsValue::NULL, &olm::InboundGroupSession::from(session).into())
|
||||
.expect("Predicate function passed to `export_room_keys` failed")
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.await?,
|
||||
)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Import the given room keys into our store.
|
||||
///
|
||||
/// `exported_keys` is a list of previously exported keys that should be
|
||||
/// imported into our store. If we already have a better version of a key,
|
||||
/// the key will _not_ be imported.
|
||||
///
|
||||
/// `progress_listener` is a closure that takes 2 arguments: `progress` and
|
||||
/// `total`, and returns nothing.
|
||||
#[wasm_bindgen(js_name = "importRoomKeys")]
|
||||
pub fn import_room_keys(
|
||||
&self,
|
||||
exported_room_keys: &str,
|
||||
progress_listener: Function,
|
||||
) -> Result<Promise, JsError> {
|
||||
let me = self.inner.clone();
|
||||
let exported_room_keys: Vec<matrix_sdk_crypto::olm::ExportedRoomKey> =
|
||||
serde_json::from_str(exported_room_keys)?;
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
let matrix_sdk_crypto::RoomKeyImportResult { imported_count, total_count, keys } = me
|
||||
.import_room_keys(exported_room_keys, false, |progress, total| {
|
||||
let progress: u64 = progress.try_into().unwrap();
|
||||
let total: u64 = total.try_into().unwrap();
|
||||
|
||||
progress_listener
|
||||
.call2(&JsValue::NULL, &JsValue::from(progress), &JsValue::from(total))
|
||||
.expect("Progress listener passed to `import_room_keys` failed");
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(serde_json::to_string(&json!({
|
||||
"imported_count": imported_count,
|
||||
"total_count": total_count,
|
||||
"keys": keys,
|
||||
}))?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Encrypt the list of exported room keys using the given passphrase.
|
||||
///
|
||||
/// `exported_room_keys` is a list of sessions that should be encrypted
|
||||
/// (it's generally returned by `export_room_keys`). `passphrase` is the
|
||||
/// passphrase that will be used to encrypt the exported room keys. And
|
||||
/// `rounds` is the number of rounds that should be used for the key
|
||||
/// derivation when the passphrase gets turned into an AES key. More rounds
|
||||
/// are increasingly computationnally intensive and as such help against
|
||||
/// brute-force attacks. Should be at least `10_000`, while values in the
|
||||
/// `100_000` ranges should be preferred.
|
||||
#[wasm_bindgen(js_name = "encryptExportedRoomKeys")]
|
||||
pub fn encrypt_exported_room_keys(
|
||||
exported_room_keys: &str,
|
||||
passphrase: &str,
|
||||
rounds: u32,
|
||||
) -> Result<String, JsError> {
|
||||
let exported_room_keys: Vec<matrix_sdk_crypto::olm::ExportedRoomKey> =
|
||||
serde_json::from_str(exported_room_keys)?;
|
||||
|
||||
Ok(matrix_sdk_crypto::encrypt_room_key_export(&exported_room_keys, passphrase, rounds)?)
|
||||
}
|
||||
|
||||
/// Try to decrypt a reader into a list of exported room keys.
|
||||
///
|
||||
/// `encrypted_exported_room_keys` is the result from
|
||||
/// `encrypt_exported_room_keys`. `passphrase` is the passphrase that was
|
||||
/// used when calling `encrypt_exported_room_keys`.
|
||||
#[wasm_bindgen(js_name = "decryptExportedRoomKeys")]
|
||||
pub fn decrypt_exported_room_keys(
|
||||
encrypted_exported_room_keys: &str,
|
||||
passphrase: &str,
|
||||
) -> Result<String, JsError> {
|
||||
Ok(serde_json::to_string(&matrix_sdk_crypto::decrypt_room_key_export(
|
||||
encrypted_exported_room_keys.as_bytes(),
|
||||
passphrase,
|
||||
)?)?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/// We have the following pattern quite often in our code:
|
||||
///
|
||||
/// ```rust
|
||||
/// struct Foo {
|
||||
/// inner: Bar,
|
||||
/// }
|
||||
///
|
||||
/// impl From<Bar> for Foo {
|
||||
/// fn from(inner: Bar) -> Self {
|
||||
/// Self { inner }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Because I feel lazy, let's do a macro to write this:
|
||||
///
|
||||
/// ```rust
|
||||
/// struct Foo {
|
||||
/// inner: Bar,
|
||||
/// }
|
||||
///
|
||||
/// impl_from_to_inner!(Bar => Foo);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! impl_from_to_inner {
|
||||
($from:ty => $to:ty) => {
|
||||
impl From<$from> for $to {
|
||||
fn from(inner: $from) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//! Olm types.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{identifiers, impl_from_to_inner};
|
||||
|
||||
/// Struct representing the state of our private cross signing keys,
|
||||
/// it shows which private cross signing keys we have locally stored.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct CrossSigningStatus {
|
||||
inner: matrix_sdk_crypto::olm::CrossSigningStatus,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::olm::CrossSigningStatus => CrossSigningStatus);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl CrossSigningStatus {
|
||||
/// Do we have the master key?
|
||||
#[wasm_bindgen(getter, js_name = "hasMaster")]
|
||||
pub fn has_master(&self) -> bool {
|
||||
self.inner.has_master
|
||||
}
|
||||
|
||||
/// Do we have the self signing key? This one is necessary to sign
|
||||
/// our own devices.
|
||||
#[wasm_bindgen(getter, js_name = "hasSelfSigning")]
|
||||
pub fn has_self_signing(&self) -> bool {
|
||||
self.inner.has_self_signing
|
||||
}
|
||||
|
||||
/// Do we have the user signing key? This one is necessary to sign
|
||||
/// other users.
|
||||
#[wasm_bindgen(getter, js_name = "hasUserSigning")]
|
||||
pub fn has_user_signing(&self) -> bool {
|
||||
self.inner.has_user_signing
|
||||
}
|
||||
}
|
||||
|
||||
/// Inbound group session.
|
||||
///
|
||||
/// Inbound group sessions are used to exchange room messages between a group of
|
||||
/// participants. Inbound group sessions are used to decrypt the room messages.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct InboundGroupSession {
|
||||
inner: matrix_sdk_crypto::olm::InboundGroupSession,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::olm::InboundGroupSession => InboundGroupSession);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl InboundGroupSession {
|
||||
/// The room where this session is used in.
|
||||
#[wasm_bindgen(getter, js_name = "roomId")]
|
||||
pub fn room_id(&self) -> identifiers::RoomId {
|
||||
self.inner.room_id().to_owned().into()
|
||||
}
|
||||
|
||||
/// Returns the unique identifier for this session.
|
||||
#[wasm_bindgen(getter, js_name = "sessionId")]
|
||||
pub fn session_id(&self) -> String {
|
||||
self.inner.session_id().to_owned()
|
||||
}
|
||||
|
||||
/// Has the session been imported from a file or server-side backup? As
|
||||
/// opposed to being directly received as an `m.room_key` event.
|
||||
#[wasm_bindgen(js_name = "hasBeenImported")]
|
||||
pub fn has_been_imported(&self) -> bool {
|
||||
self.inner.has_been_imported()
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,23 @@
|
||||
use js_sys::JsString;
|
||||
use matrix_sdk_crypto::{
|
||||
requests::{
|
||||
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
|
||||
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
|
||||
KeysBackupRequest as OriginalKeysBackupRequest,
|
||||
KeysQueryRequest as OriginalKeysQueryRequest,
|
||||
RoomMessageRequest as OriginalRoomMessageRequest,
|
||||
ToDeviceRequest as OriginalToDeviceRequest,
|
||||
UploadSigningKeysRequest as OriginalUploadSigningKeysRequest,
|
||||
},
|
||||
OutgoingRequests,
|
||||
};
|
||||
use ruma::api::client::keys::{
|
||||
claim_keys::v3::Request as RumaKeysClaimRequest,
|
||||
upload_keys::v3::Request as RumaKeysUploadRequest,
|
||||
upload_signatures::v3::Request as RumaSignatureUploadRequest,
|
||||
claim_keys::v3::Request as OriginalKeysClaimRequest,
|
||||
upload_keys::v3::Request as OriginalKeysUploadRequest,
|
||||
upload_signatures::v3::Request as OriginalSignatureUploadRequest,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/** Outgoing Requests * */
|
||||
|
||||
/// Data for a request to the `/keys/upload` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
@@ -26,7 +31,7 @@ use wasm_bindgen::prelude::*;
|
||||
pub struct KeysUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -42,7 +47,7 @@ impl KeysUploadRequest {
|
||||
/// Create a new `KeysUploadRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysUploadRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -63,7 +68,7 @@ impl KeysUploadRequest {
|
||||
pub struct KeysQueryRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -79,7 +84,7 @@ impl KeysQueryRequest {
|
||||
/// Create a new `KeysQueryRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysQueryRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -101,7 +106,7 @@ impl KeysQueryRequest {
|
||||
pub struct KeysClaimRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -117,7 +122,7 @@ impl KeysClaimRequest {
|
||||
/// Create a new `KeysClaimRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysClaimRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -138,7 +143,7 @@ impl KeysClaimRequest {
|
||||
pub struct ToDeviceRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -154,7 +159,7 @@ impl ToDeviceRequest {
|
||||
/// Create a new `ToDeviceRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> ToDeviceRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -175,7 +180,7 @@ impl ToDeviceRequest {
|
||||
pub struct SignatureUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -191,7 +196,7 @@ impl SignatureUploadRequest {
|
||||
/// Create a new `SignatureUploadRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> SignatureUploadRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -210,7 +215,7 @@ impl SignatureUploadRequest {
|
||||
pub struct RoomMessageRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -226,7 +231,7 @@ impl RoomMessageRequest {
|
||||
/// Create a new `RoomMessageRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> RoomMessageRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -245,7 +250,7 @@ impl RoomMessageRequest {
|
||||
pub struct KeysBackupRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -256,12 +261,33 @@ pub struct KeysBackupRequest {
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
/** Other Requests * */
|
||||
|
||||
/// Request that will publish a cross signing identity.
|
||||
///
|
||||
/// This uploads the public cross signing key triplet.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct SigningKeysUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"master_key": …, "self_signing_key": …, "user_signing_key": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeysBackupRequest {
|
||||
/// Create a new `KeysBackupRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysBackupRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -272,35 +298,56 @@ impl KeysBackupRequest {
|
||||
}
|
||||
|
||||
macro_rules! request {
|
||||
($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
|
||||
impl TryFrom<(String, &$ruma_request)> for $request {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(
|
||||
(request_id, request): (String, &$ruma_request),
|
||||
) -> Result<Self, Self::Error> {
|
||||
($destination_request:ident from $source_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
|
||||
impl $destination_request {
|
||||
pub(crate) fn to_json(request: &$source_request) -> Result<String, serde_json::Error> {
|
||||
let mut map = serde_json::Map::new();
|
||||
$(
|
||||
map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap());
|
||||
)+
|
||||
let value = serde_json::Value::Object(map);
|
||||
let object = serde_json::Value::Object(map);
|
||||
|
||||
Ok($request {
|
||||
id: request_id.into(),
|
||||
body: serde_json::to_string(&value)?.into(),
|
||||
serde_json::to_string(&object)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&$source_request> for $destination_request {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(request: &$source_request) -> Result<Self, Self::Error> {
|
||||
Ok($destination_request {
|
||||
id: None,
|
||||
body: Self::to_json(request)?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(String, &$source_request)> for $destination_request {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(
|
||||
(request_id, request): (String, &$source_request),
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok($destination_request {
|
||||
id: Some(request_id.into()),
|
||||
body: Self::to_json(request)?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
|
||||
request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token);
|
||||
request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys);
|
||||
request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages);
|
||||
request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys);
|
||||
request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content);
|
||||
request!(KeysBackupRequest from RumaKeysBackupRequest maps fields rooms);
|
||||
// Outgoing Requests
|
||||
request!(KeysUploadRequest from OriginalKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
|
||||
request!(KeysQueryRequest from OriginalKeysQueryRequest maps fields timeout, device_keys, token);
|
||||
request!(KeysClaimRequest from OriginalKeysClaimRequest maps fields timeout, one_time_keys);
|
||||
request!(ToDeviceRequest from OriginalToDeviceRequest maps fields event_type, txn_id, messages);
|
||||
request!(SignatureUploadRequest from OriginalSignatureUploadRequest maps fields signed_keys);
|
||||
request!(RoomMessageRequest from OriginalRoomMessageRequest maps fields room_id, txn_id, content);
|
||||
request!(KeysBackupRequest from OriginalKeysBackupRequest maps fields rooms);
|
||||
|
||||
// Other Requests
|
||||
request!(SigningKeysUploadRequest from OriginalUploadSigningKeysRequest maps fields master_key, self_signing_key, user_signing_key);
|
||||
|
||||
// JavaScript has no complex enums like Rust. To return structs of
|
||||
// different types, we have no choice that hiding everything behind a
|
||||
|
||||
@@ -182,12 +182,8 @@ impl DecryptedRoomEvent {
|
||||
/// Chain of Curve25519 keys through which this session was
|
||||
/// forwarded, via `m.forwarded_room_key` events.
|
||||
#[wasm_bindgen(getter, js_name = "forwardingCurve25519KeyChain")]
|
||||
pub fn forwarding_curve25519_key_chain(&self) -> Option<Array> {
|
||||
Some(match &self.encryption_info.as_ref()?.algorithm_info {
|
||||
AlgorithmInfo::MegolmV1AesSha2 { forwarding_curve25519_key_chain, .. } => {
|
||||
forwarding_curve25519_key_chain.iter().map(JsValue::from).collect()
|
||||
}
|
||||
})
|
||||
pub fn forwarding_curve25519_key_chain(&self) -> Array {
|
||||
Array::new()
|
||||
}
|
||||
|
||||
/// The verification state of the device that sent us the event,
|
||||
@@ -200,8 +196,8 @@ impl DecryptedRoomEvent {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_common::deserialized_responses::RoomEvent> for DecryptedRoomEvent {
|
||||
fn from(value: matrix_sdk_common::deserialized_responses::RoomEvent) -> Self {
|
||||
impl From<matrix_sdk_common::deserialized_responses::TimelineEvent> for DecryptedRoomEvent {
|
||||
fn from(value: matrix_sdk_common::deserialized_responses::TimelineEvent) -> Self {
|
||||
Self {
|
||||
event: value.event.json().get().to_owned().into(),
|
||||
encryption_info: value.encryption_info,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//! Store types.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::impl_from_to_inner;
|
||||
|
||||
/// A struct containing private cross signing keys that can be backed
|
||||
/// up or uploaded to the secret store.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct CrossSigningKeyExport {
|
||||
pub(crate) inner: matrix_sdk_crypto::store::CrossSigningKeyExport,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::store::CrossSigningKeyExport => CrossSigningKeyExport);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl CrossSigningKeyExport {
|
||||
/// The seed of the master key encoded as unpadded base64.
|
||||
#[wasm_bindgen(getter, js_name = "masterKey")]
|
||||
pub fn master_key(&self) -> Option<String> {
|
||||
self.inner.master_key.clone()
|
||||
}
|
||||
|
||||
/// The seed of the self signing key encoded as unpadded base64.
|
||||
#[wasm_bindgen(getter, js_name = "self_signing_key")]
|
||||
pub fn self_signing_key(&self) -> Option<String> {
|
||||
self.inner.self_signing_key.clone()
|
||||
}
|
||||
|
||||
/// The seed of the user signing key encoded as unpadded base64.
|
||||
#[wasm_bindgen(getter, js_name = "userSigningKey")]
|
||||
pub fn user_signing_key(&self) -> Option<String> {
|
||||
self.inner.user_signing_key.clone()
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{downcast, identifiers};
|
||||
use crate::{identifiers, js::downcast};
|
||||
|
||||
/// Information on E2E device updates.
|
||||
#[wasm_bindgen]
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Logger level.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LoggerLevel {
|
||||
/// `TRACE` level.
|
||||
///
|
||||
/// Designate very low priority, often extremely verbose,
|
||||
/// information.
|
||||
Trace,
|
||||
|
||||
/// `DEBUG` level.
|
||||
///
|
||||
/// Designate lower priority information.
|
||||
Debug,
|
||||
|
||||
/// `INFO` level.
|
||||
///
|
||||
/// Designate useful information.
|
||||
Info,
|
||||
|
||||
/// `WARN` level.
|
||||
///
|
||||
/// Designate hazardous situations.
|
||||
Warn,
|
||||
|
||||
/// `ERROR` level.
|
||||
///
|
||||
/// Designate very serious errors.
|
||||
Error,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
mod inner {
|
||||
use std::{
|
||||
fmt,
|
||||
fmt::Write as _,
|
||||
sync::{Arc, Once},
|
||||
};
|
||||
|
||||
use tracing::{
|
||||
field::{Field, Visit},
|
||||
metadata::LevelFilter,
|
||||
Event, Level, Metadata, Subscriber,
|
||||
};
|
||||
use tracing_subscriber::{
|
||||
layer::{Context, Layer as TracingLayer},
|
||||
prelude::*,
|
||||
reload, Registry,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
type TracingInner = Arc<reload::Handle<Layer, Registry>>;
|
||||
|
||||
/// Type to install and to manipulate the tracing layer.
|
||||
#[wasm_bindgen]
|
||||
pub struct Tracing {
|
||||
handle: TracingInner,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Tracing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Tracing").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Tracing {
|
||||
/// Check whether the `tracing` feature has been enabled.
|
||||
#[wasm_bindgen(js_name = "isAvailable")]
|
||||
pub fn is_available() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Install the tracing layer.
|
||||
///
|
||||
/// `Tracing` is a singleton. Once it is installed,
|
||||
/// consecutive calls to the constructor will construct a new
|
||||
/// `Tracing` object but with the exact same inner
|
||||
/// state. Calling the constructor with a new `min_level` will
|
||||
/// just update the `min_level` parameter; in that regard, it
|
||||
/// is similar to calling the `min_level` method on an
|
||||
/// existing `Tracing` object.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(min_level: LoggerLevel) -> Result<Tracing, JsError> {
|
||||
static mut INSTALL: Option<TracingInner> = None;
|
||||
static INSTALLED: Once = Once::new();
|
||||
|
||||
INSTALLED.call_once(|| {
|
||||
let (filter, reload_handle) = reload::Layer::new(Layer::new(min_level.clone()));
|
||||
|
||||
tracing_subscriber::registry().with(filter).init();
|
||||
|
||||
unsafe { INSTALL = Some(Arc::new(reload_handle)) };
|
||||
});
|
||||
|
||||
let tracing = Tracing {
|
||||
handle: unsafe { INSTALL.as_ref() }
|
||||
.cloned()
|
||||
.expect("`Tracing` has not been installed correctly"),
|
||||
};
|
||||
|
||||
// If it's not the first call to `install`, the
|
||||
// `min_level` can be different. Let's update it.
|
||||
tracing.min_level(min_level);
|
||||
|
||||
Ok(tracing)
|
||||
}
|
||||
|
||||
/// Re-define the minimum logger level.
|
||||
#[wasm_bindgen(setter, js_name = "minLevel")]
|
||||
pub fn min_level(&self, min_level: LoggerLevel) {
|
||||
let _ = self.handle.modify(|layer| layer.min_level = min_level.into());
|
||||
}
|
||||
|
||||
/// Turn the logger on, i.e. it emits logs again if it was turned
|
||||
/// off.
|
||||
#[wasm_bindgen(js_name = "turnOn")]
|
||||
pub fn turn_on(&self) {
|
||||
let _ = self.handle.modify(|layer| layer.turn_on());
|
||||
}
|
||||
|
||||
/// Turn the logger off, i.e. it no long emits logs.
|
||||
#[wasm_bindgen(js_name = "turnOff")]
|
||||
pub fn turn_off(&self) {
|
||||
let _ = self.handle.modify(|layer| layer.turn_off());
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoggerLevel> for Level {
|
||||
fn from(value: LoggerLevel) -> Self {
|
||||
use LoggerLevel::*;
|
||||
|
||||
match value {
|
||||
Trace => Self::TRACE,
|
||||
Debug => Self::DEBUG,
|
||||
Info => Self::INFO,
|
||||
Warn => Self::WARN,
|
||||
Error => Self::ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "trace")]
|
||||
fn log_trace(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "debug")]
|
||||
fn log_debug(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "info")]
|
||||
fn log_info(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "warn")]
|
||||
fn log_warn(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "error")]
|
||||
fn log_error(message: String);
|
||||
}
|
||||
|
||||
struct Layer {
|
||||
min_level: Level,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
fn new<L>(min_level: L) -> Self
|
||||
where
|
||||
L: Into<Level>,
|
||||
{
|
||||
Self { min_level: min_level.into(), enabled: true }
|
||||
}
|
||||
|
||||
fn turn_on(&mut self) {
|
||||
self.enabled = true;
|
||||
}
|
||||
|
||||
fn turn_off(&mut self) {
|
||||
self.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> TracingLayer<S> for Layer
|
||||
where
|
||||
S: Subscriber,
|
||||
{
|
||||
fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
|
||||
self.enabled && metadata.level() <= &self.min_level
|
||||
}
|
||||
|
||||
fn max_level_hint(&self) -> Option<LevelFilter> {
|
||||
if !self.enabled {
|
||||
Some(LevelFilter::OFF)
|
||||
} else {
|
||||
Some(LevelFilter::from_level(self.min_level))
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(&self, event: &Event<'_>, _: Context<'_, S>) {
|
||||
let mut recorder = StringVisitor::new();
|
||||
event.record(&mut recorder);
|
||||
let metadata = event.metadata();
|
||||
let level = metadata.level();
|
||||
|
||||
let origin = metadata
|
||||
.file()
|
||||
.and_then(|file| metadata.line().map(|ln| format!("{file}:{ln}")))
|
||||
.unwrap_or_default();
|
||||
|
||||
let message = format!("{level} {origin}{recorder}");
|
||||
|
||||
match *level {
|
||||
Level::TRACE => log_trace(message),
|
||||
Level::DEBUG => log_debug(message),
|
||||
Level::INFO => log_info(message),
|
||||
Level::WARN => log_warn(message),
|
||||
Level::ERROR => log_error(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StringVisitor {
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl StringVisitor {
|
||||
fn new() -> Self {
|
||||
Self { string: String::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for StringVisitor {
|
||||
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
|
||||
match field.name() {
|
||||
"message" => {
|
||||
if !self.string.is_empty() {
|
||||
self.string.push('\n');
|
||||
}
|
||||
|
||||
let _ = write!(self.string, "{value:?}");
|
||||
}
|
||||
|
||||
field_name => {
|
||||
let _ = write!(self.string, "\n{field_name} = {value:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for StringVisitor {
|
||||
fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if !self.string.is_empty() {
|
||||
write!(&mut f, " {}", self.string)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
mod inner {
|
||||
use super::*;
|
||||
|
||||
/// Type to install and to manipulate the tracing layer.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct Tracing;
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Tracing {
|
||||
/// Check whether the `tracing` feature has been enabled.
|
||||
#[wasm_bindgen(js_name = "isAvailable")]
|
||||
pub fn is_available() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// The `tracing` feature is not enabled, so this constructor
|
||||
/// will raise an error.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(_min_level: LoggerLevel) -> Result<Tracing, JsError> {
|
||||
Err(JsError::new("The `tracing` feature is disabled. Check `Tracing.isAvailable` before constructing `Tracing`"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use inner::*;
|
||||
@@ -0,0 +1,156 @@
|
||||
//! Extra types, like `Signatures`.
|
||||
|
||||
use js_sys::Map;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
identifiers::{DeviceKeyId, UserId},
|
||||
impl_from_to_inner,
|
||||
vodozemac::Ed25519Signature,
|
||||
};
|
||||
|
||||
/// A collection of `Signature`.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Signatures {
|
||||
inner: matrix_sdk_crypto::types::Signatures,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::types::Signatures => Signatures);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Signatures {
|
||||
/// Creates a new, empty, signatures collection.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
matrix_sdk_crypto::types::Signatures::new().into()
|
||||
}
|
||||
|
||||
/// Add the given signature from the given signer and the given key ID to
|
||||
/// the collection.
|
||||
#[wasm_bindgen(js_name = "addSignature")]
|
||||
pub fn add_signature(
|
||||
&mut self,
|
||||
signer: &UserId,
|
||||
key_id: &DeviceKeyId,
|
||||
signature: &Ed25519Signature,
|
||||
) -> Option<MaybeSignature> {
|
||||
self.inner
|
||||
.add_signature(signer.inner.clone(), key_id.inner.clone(), signature.inner)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
/// Try to find an Ed25519 signature from the given signer with
|
||||
/// the given key ID.
|
||||
#[wasm_bindgen(js_name = "getSignature")]
|
||||
pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option<Ed25519Signature> {
|
||||
self.inner.get_signature(signer.inner.as_ref(), key_id.inner.as_ref()).map(Into::into)
|
||||
}
|
||||
|
||||
/// Get the map of signatures that belong to the given user.
|
||||
pub fn get(&self, signer: &UserId) -> Option<Map> {
|
||||
let map = Map::new();
|
||||
|
||||
for (device_key_id, maybe_signature) in
|
||||
self.inner.get(signer.inner.as_ref()).map(|map| {
|
||||
map.iter().map(|(device_key_id, maybe_signature)| {
|
||||
(
|
||||
device_key_id.as_str().to_owned(),
|
||||
MaybeSignature::from(maybe_signature.clone()),
|
||||
)
|
||||
})
|
||||
})?
|
||||
{
|
||||
map.set(&device_key_id.into(), &maybe_signature.into());
|
||||
}
|
||||
|
||||
Some(map)
|
||||
}
|
||||
|
||||
/// Remove all the signatures we currently hold.
|
||||
pub fn clear(&mut self) {
|
||||
self.inner.clear();
|
||||
}
|
||||
|
||||
/// Do we hold any signatures or is our collection completely
|
||||
/// empty.
|
||||
#[wasm_bindgen(js_name = "isEmpty")]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
/// How many signatures do we currently hold.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn count(&self) -> usize {
|
||||
self.inner.signature_count()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a potentially decoded signature (but not a validated
|
||||
/// one).
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct Signature {
|
||||
inner: matrix_sdk_crypto::types::Signature,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::types::Signature => Signature);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Signature {
|
||||
/// Get the Ed25519 signature, if this is one.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn ed25519(&self) -> Option<Ed25519Signature> {
|
||||
self.inner.ed25519().map(Into::into)
|
||||
}
|
||||
|
||||
/// Convert the signature to a base64 encoded string.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
type MaybeSignatureInner =
|
||||
Result<matrix_sdk_crypto::types::Signature, matrix_sdk_crypto::types::InvalidSignature>;
|
||||
|
||||
/// Represents a signature that is either valid _or_ that could not be
|
||||
/// decoded.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct MaybeSignature {
|
||||
inner: MaybeSignatureInner,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(MaybeSignatureInner => MaybeSignature);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl MaybeSignature {
|
||||
/// Check whether the signature has been successfully decoded.
|
||||
#[wasm_bindgen(js_name = "isValid")]
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.inner.is_ok()
|
||||
}
|
||||
|
||||
/// Check whether the signature could not be successfully decoded.
|
||||
#[wasm_bindgen(js_name = "isInvalid")]
|
||||
pub fn is_invalid(&self) -> bool {
|
||||
self.inner.is_err()
|
||||
}
|
||||
|
||||
/// The signature, if successfully decoded.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn signature(&self) -> Option<Signature> {
|
||||
self.inner.as_ref().cloned().map(Into::into).ok()
|
||||
}
|
||||
|
||||
/// The base64 encoded string that is claimed to contain a
|
||||
/// signature but could not be decoded, if any.
|
||||
#[wasm_bindgen(getter, js_name = "invalidSignatureSource")]
|
||||
pub fn invalid_signature_source(&self) -> Option<String> {
|
||||
match &self.inner {
|
||||
Ok(_) => None,
|
||||
Err(signature) => Some(signature.source.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,193 @@
|
||||
//! Vodozemac types.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::impl_from_to_inner;
|
||||
|
||||
/// An Ed25519 public key, used to verify digital signatures.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ed25519PublicKey {
|
||||
inner: vodozemac::Ed25519PublicKey,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Ed25519PublicKey {
|
||||
/// The number of bytes an Ed25519 public key has.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn length(&self) -> usize {
|
||||
vodozemac::Ed25519PublicKey::LENGTH
|
||||
}
|
||||
|
||||
/// Serialize an Ed25519 public key to an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
impl_from_to_inner!(vodozemac::Ed25519PublicKey => Ed25519PublicKey);
|
||||
|
||||
/// An Ed25519 digital signature, can be used to verify the
|
||||
/// authenticity of a message.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct Ed25519Signature {
|
||||
pub(crate) inner: vodozemac::Ed25519Signature,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(vodozemac::Ed25519Signature => Ed25519Signature);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Ed25519Signature {
|
||||
/// Try to create an Ed25519 signature from an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(signature: String) -> Result<Ed25519Signature, JsError> {
|
||||
Ok(Self { inner: vodozemac::Ed25519Signature::from_base64(signature.as_str())? })
|
||||
}
|
||||
|
||||
/// Serialize a Ed25519 signature to an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Curve25519 public key.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Curve25519PublicKey {
|
||||
inner: vodozemac::Curve25519PublicKey,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Curve25519PublicKey {
|
||||
/// The number of bytes a Curve25519 public key has.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn length(&self) -> usize {
|
||||
vodozemac::Curve25519PublicKey::LENGTH
|
||||
}
|
||||
|
||||
/// Serialize an Curve25519 public key to an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
impl_from_to_inner!(vodozemac::Curve25519PublicKey => Curve25519PublicKey);
|
||||
|
||||
/// Struct holding the two public identity keys of an account.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct IdentityKeys {
|
||||
/// The Ed25519 public key, used for signing.
|
||||
pub ed25519: Ed25519PublicKey,
|
||||
|
||||
/// The Curve25519 public key, used for establish shared secrets.
|
||||
pub curve25519: Curve25519PublicKey,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::olm::IdentityKeys> for IdentityKeys {
|
||||
fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self {
|
||||
Self {
|
||||
ed25519: Ed25519PublicKey { inner: value.ed25519 },
|
||||
curve25519: Curve25519PublicKey { inner: value.curve25519 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum over the different key types a device can have.
|
||||
///
|
||||
/// Currently devices have a curve25519 and ed25519 keypair. The keys
|
||||
/// transport format is a base64 encoded string, any unknown key type
|
||||
/// will be left as such a string.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceKey {
|
||||
inner: matrix_sdk_crypto::types::DeviceKey,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::types::DeviceKey => DeviceKey);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceKey {
|
||||
/// Get the name of the device key.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn name(&self) -> DeviceKeyName {
|
||||
(&self.inner).into()
|
||||
}
|
||||
|
||||
/// Get the value associated to the `Curve25519` device key name.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn curve25519(&self) -> Option<Curve25519PublicKey> {
|
||||
use matrix_sdk_crypto::types::DeviceKey::*;
|
||||
|
||||
match &self.inner {
|
||||
Curve25519(key) => Some((*key).into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value associated to the `Ed25519` device key name.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn ed25519(&self) -> Option<Ed25519PublicKey> {
|
||||
use matrix_sdk_crypto::types::DeviceKey::*;
|
||||
|
||||
match &self.inner {
|
||||
Ed25519(key) => Some((*key).into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value associated to the `Unknown` device key name.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn unknown(&self) -> Option<String> {
|
||||
use matrix_sdk_crypto::types::DeviceKey::*;
|
||||
|
||||
match &self.inner {
|
||||
Unknown(key) => Some(key.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the `DeviceKey` into a base64 encoded string.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&matrix_sdk_crypto::types::DeviceKey> for DeviceKeyName {
|
||||
fn from(device_key: &matrix_sdk_crypto::types::DeviceKey) -> Self {
|
||||
use matrix_sdk_crypto::types::DeviceKey::*;
|
||||
|
||||
match device_key {
|
||||
Curve25519(_) => Self::Curve25519,
|
||||
Ed25519(_) => Self::Ed25519,
|
||||
Unknown(_) => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum over the different key types a device can have.
|
||||
///
|
||||
/// Currently devices have a curve25519 and ed25519 keypair. The keys
|
||||
/// transport format is a base64 encoded string, any unknown key type
|
||||
/// will be left as such a string.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum DeviceKeyName {
|
||||
/// The curve25519 device key.
|
||||
Curve25519,
|
||||
|
||||
/// The ed25519 device key.
|
||||
Ed25519,
|
||||
|
||||
/// An unknown device key.
|
||||
Unknown,
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
const { Attachment, EncryptedAttachment } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe(Attachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const textEncoder = new TextEncoder();
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
let encryptedAttachment;
|
||||
|
||||
test('can encrypt data', () => {
|
||||
encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData));
|
||||
|
||||
const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo);
|
||||
|
||||
expect(mediaEncryptionInfo).toMatchObject({
|
||||
v: 'v2',
|
||||
key: {
|
||||
kty: expect.any(String),
|
||||
key_ops: expect.arrayContaining(['encrypt', 'decrypt']),
|
||||
alg: expect.any(String),
|
||||
k: expect.any(String),
|
||||
ext: expect.any(Boolean),
|
||||
},
|
||||
iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
|
||||
hashes: {
|
||||
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/)
|
||||
}
|
||||
});
|
||||
|
||||
const encryptedData = encryptedAttachment.encryptedData;
|
||||
expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can decrypt data', () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
|
||||
const decryptedAttachment = Attachment.decrypt(encryptedAttachment);
|
||||
|
||||
expect(textDecoder.decode(decryptedAttachment)).toStrictEqual(originalData);
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('can only decrypt once', () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
|
||||
expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow()
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptedAttachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
test('can be created manually', () => {
|
||||
const encryptedAttachment = new EncryptedAttachment(
|
||||
new Uint8Array([24, 150, 67, 37, 144]),
|
||||
JSON.stringify({
|
||||
v: 'v2',
|
||||
key: {
|
||||
kty: 'oct',
|
||||
key_ops: [ 'encrypt', 'decrypt' ],
|
||||
alg: 'A256CTR',
|
||||
k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM',
|
||||
ext: true
|
||||
},
|
||||
iv: 'xk2AcWkomiYAAAAAAAAAAA',
|
||||
hashes: {
|
||||
sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok'
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
expect(textDecoder.decode(Attachment.decrypt(encryptedAttachment))).toStrictEqual(originalData);
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,900 @@
|
||||
const {
|
||||
OlmMachine,
|
||||
UserId,
|
||||
DeviceId,
|
||||
DeviceKeyId,
|
||||
RoomId,
|
||||
Device,
|
||||
LocalTrust,
|
||||
UserDevices,
|
||||
DeviceKey,
|
||||
DeviceKeyName,
|
||||
DeviceKeyAlgorithmName,
|
||||
Ed25519PublicKey,
|
||||
Curve25519PublicKey,
|
||||
Signatures,
|
||||
VerificationMethod,
|
||||
VerificationRequest,
|
||||
ToDeviceRequest,
|
||||
DeviceLists,
|
||||
KeysUploadRequest,
|
||||
RequestType,
|
||||
KeysQueryRequest,
|
||||
Sas,
|
||||
Emoji,
|
||||
SigningKeysUploadRequest,
|
||||
SignatureUploadRequest,
|
||||
Qr,
|
||||
QrCode,
|
||||
QrCodeScan,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { zip, addMachineToMachine } = require('./helper');
|
||||
|
||||
describe('LocalTrust', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(LocalTrust.Verified).toStrictEqual(0);
|
||||
expect(LocalTrust.BlackListed).toStrictEqual(1);
|
||||
expect(LocalTrust.Ignored).toStrictEqual(2);
|
||||
expect(LocalTrust.Unset).toStrictEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeviceKeyName', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(DeviceKeyName.Curve25519).toStrictEqual(0);
|
||||
expect(DeviceKeyName.Ed25519).toStrictEqual(1);
|
||||
expect(DeviceKeyName.Unknown).toStrictEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('foobar');
|
||||
const room = new RoomId('!baz:matrix.org');
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return new OlmMachine(new_user || user, new_device || device);
|
||||
}
|
||||
|
||||
test('can read user devices', async () => {
|
||||
const m = await machine();
|
||||
const userDevices = await m.getUserDevices(user);
|
||||
|
||||
expect(userDevices).toBeInstanceOf(UserDevices);
|
||||
expect(userDevices.get(device)).toBeInstanceOf(Device);
|
||||
expect(userDevices.isAnyVerified()).toStrictEqual(false);
|
||||
expect(userDevices.keys().map(device_id => device_id.toString())).toStrictEqual([device.toString()]);
|
||||
expect(userDevices.devices().map(device => device.deviceId.toString())).toStrictEqual([device.toString()]);
|
||||
});
|
||||
|
||||
test('can read a user device', async () => {
|
||||
const m = await machine();
|
||||
const dev = await m.getDevice(user, device);
|
||||
|
||||
expect(dev).toBeInstanceOf(Device);
|
||||
expect(dev.isVerified()).toStrictEqual(false);
|
||||
expect(dev.isCrossSigningTrusted()).toStrictEqual(false);
|
||||
|
||||
expect(dev.localTrustState).toStrictEqual(LocalTrust.Unset);
|
||||
expect(dev.isLocallyTrusted()).toStrictEqual(false);
|
||||
expect(await dev.setLocalTrust(LocalTrust.Verified)).toBeNull();
|
||||
expect(dev.localTrustState).toStrictEqual(LocalTrust.Verified);
|
||||
expect(dev.isLocallyTrusted()).toStrictEqual(true);
|
||||
|
||||
expect(dev.userId.toString()).toStrictEqual(user.toString());
|
||||
expect(dev.deviceId.toString()).toStrictEqual(device.toString());
|
||||
expect(dev.deviceName).toBeUndefined();
|
||||
|
||||
const deviceKey = dev.getKey(DeviceKeyAlgorithmName.Ed25519);
|
||||
|
||||
expect(deviceKey).toBeInstanceOf(DeviceKey);
|
||||
expect(deviceKey.name).toStrictEqual(DeviceKeyName.Ed25519);
|
||||
expect(deviceKey.curve25519).toBeUndefined();
|
||||
expect(deviceKey.ed25519).toBeInstanceOf(Ed25519PublicKey);
|
||||
expect(deviceKey.unknown).toBeUndefined();
|
||||
expect(deviceKey.toBase64()).toMatch(/^[A-Za-z0-9\+/]+$/);
|
||||
|
||||
expect(dev.curve25519Key).toBeInstanceOf(Curve25519PublicKey);
|
||||
expect(dev.ed25519Key).toBeInstanceOf(Ed25519PublicKey);
|
||||
|
||||
for (const [deviceKeyId, deviceKey] of dev.keys) {
|
||||
expect(deviceKeyId).toBeInstanceOf(DeviceKeyId);
|
||||
expect(deviceKey).toBeInstanceOf(DeviceKey);
|
||||
}
|
||||
|
||||
expect(dev.signatures).toBeInstanceOf(Signatures);
|
||||
expect(dev.isBlacklisted()).toStrictEqual(false);
|
||||
expect(dev.isDeleted()).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Key Verification', () => {
|
||||
const userId1 = new UserId('@alice:example.org');
|
||||
const deviceId1 = new DeviceId('alice_device');
|
||||
|
||||
const userId2 = new UserId('@bob:example.org');
|
||||
const deviceId2 = new DeviceId('bob_device');
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return new OlmMachine(new_user || userId1, new_device || deviceId1);
|
||||
}
|
||||
|
||||
describe('SAS', () => {
|
||||
// First Olm machine.
|
||||
let m1;
|
||||
|
||||
// Second Olm machine.
|
||||
let m2;
|
||||
|
||||
beforeAll(async () => {
|
||||
m1 = await machine(userId1, deviceId1);
|
||||
m2 = await machine(userId2, deviceId2);
|
||||
});
|
||||
|
||||
// Verification request for `m1`.
|
||||
let verificationRequest1;
|
||||
|
||||
// The flow ID.
|
||||
let flowId;
|
||||
|
||||
test('can request verification (`m.key.verification.request`)', async () => {
|
||||
// Make `m1` and `m2` be aware of each other.
|
||||
{
|
||||
await addMachineToMachine(m2, m1);
|
||||
await addMachineToMachine(m1, m2);
|
||||
}
|
||||
|
||||
// Pick the device we want to start the verification with.
|
||||
const device2 = await m1.getDevice(userId2, deviceId2);
|
||||
|
||||
expect(device2).toBeInstanceOf(Device);
|
||||
|
||||
let outgoingVerificationRequest;
|
||||
// Request a verification from `m1` to `device2`.
|
||||
[verificationRequest1, outgoingVerificationRequest] = await device2.requestVerification();
|
||||
|
||||
expect(verificationRequest1).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
expect(verificationRequest1.ownUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(verificationRequest1.otherUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(verificationRequest1.otherDeviceId).toBeUndefined();
|
||||
expect(verificationRequest1.roomId).toBeUndefined();
|
||||
expect(verificationRequest1.cancelInfo).toBeUndefined();
|
||||
expect(verificationRequest1.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest1.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest1.weStarted()).toStrictEqual(true);
|
||||
expect(verificationRequest1.isDone()).toStrictEqual(false);
|
||||
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}]
|
||||
};
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
flowId = verificationRequest1.flowId;
|
||||
});
|
||||
|
||||
// Verification request for `m2`.
|
||||
let verificationRequest2;
|
||||
|
||||
test('can fetch received request verification', async () => {
|
||||
// Oh, a new verification request.
|
||||
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
|
||||
|
||||
expect(verificationRequest2).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
expect(verificationRequest2.ownUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(verificationRequest2.otherUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(verificationRequest2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(verificationRequest2.roomId).toBeUndefined();
|
||||
expect(verificationRequest2.cancelInfo).toBeUndefined();
|
||||
expect(verificationRequest2.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest2.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest2.flowId).toStrictEqual(flowId);
|
||||
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest2.weStarted()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isDone()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isCancelled()).toStrictEqual(false);
|
||||
|
||||
const verificationRequests = m2.getVerificationRequests(userId1);
|
||||
expect(verificationRequests).toHaveLength(1);
|
||||
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
|
||||
});
|
||||
|
||||
test('can accept a verification request (`m.key.verification.ready`)', async () => {
|
||||
// Accept the verification request.
|
||||
let outgoingVerificationRequest = verificationRequest2.accept();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
// The request verification is ready.
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send the verification ready to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('verification requests are synchronized and automatically updated', () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(true);
|
||||
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
});
|
||||
|
||||
// SAS verification for the second machine.
|
||||
let sas2;
|
||||
|
||||
test('can start a SAS verification (`m.key.verification.start`)', async () => {
|
||||
// Let's start a SAS verification, from `m2` for example.
|
||||
[sas2, outgoingVerificationRequest] = await verificationRequest2.startSas();
|
||||
expect(sas2).toBeInstanceOf(Sas);
|
||||
|
||||
expect(sas2.userId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(sas2.deviceId.toString()).toStrictEqual(deviceId2.toString());
|
||||
expect(sas2.otherUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(sas2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(sas2.flowId).toStrictEqual(flowId);
|
||||
expect(sas2.roomId).toBeUndefined();
|
||||
expect(sas2.supportsEmoji()).toStrictEqual(false);
|
||||
expect(sas2.startedFromRequest()).toStrictEqual(true);
|
||||
expect(sas2.isSelfVerification()).toStrictEqual(false);
|
||||
expect(sas2.haveWeConfirmed()).toStrictEqual(false);
|
||||
expect(sas2.hasBeenAccepted()).toStrictEqual(false);
|
||||
expect(sas2.cancelInfo()).toBeUndefined();
|
||||
expect(sas2.weStarted()).toStrictEqual(false);
|
||||
expect(sas2.timedOut()).toStrictEqual(false);
|
||||
expect(sas2.canBePresented()).toStrictEqual(false);
|
||||
expect(sas2.isDone()).toStrictEqual(false);
|
||||
expect(sas2.isCancelled()).toStrictEqual(false);
|
||||
expect(sas2.emoji()).toBeUndefined();
|
||||
expect(sas2.emojiIndex()).toBeUndefined();
|
||||
expect(sas2.decimals()).toBeUndefined();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send the SAS start to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
// SAS verification for the second machine.
|
||||
let sas1;
|
||||
|
||||
test('can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)', async () => {
|
||||
// Let's fetch the ongoing SAS verification.
|
||||
sas1 = await m1.getVerification(userId2, flowId);
|
||||
|
||||
expect(sas1).toBeInstanceOf(Sas);
|
||||
|
||||
expect(sas1.userId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(sas1.deviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(sas1.otherUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(sas1.otherDeviceId.toString()).toStrictEqual(deviceId2.toString());
|
||||
expect(sas1.flowId).toStrictEqual(flowId);
|
||||
expect(sas1.roomId).toBeUndefined();
|
||||
expect(sas1.startedFromRequest()).toStrictEqual(true);
|
||||
expect(sas1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(sas1.haveWeConfirmed()).toStrictEqual(false);
|
||||
expect(sas1.hasBeenAccepted()).toStrictEqual(false);
|
||||
expect(sas1.cancelInfo()).toBeUndefined();
|
||||
expect(sas1.weStarted()).toStrictEqual(true);
|
||||
expect(sas1.timedOut()).toStrictEqual(false);
|
||||
expect(sas1.canBePresented()).toStrictEqual(false);
|
||||
expect(sas1.isDone()).toStrictEqual(false);
|
||||
expect(sas1.isCancelled()).toStrictEqual(false);
|
||||
expect(sas1.emoji()).toBeUndefined();
|
||||
expect(sas1.emojiIndex()).toBeUndefined();
|
||||
expect(sas1.decimals()).toBeUndefined();
|
||||
|
||||
// Let's accept thet SAS start request.
|
||||
let outgoingVerificationRequest = sas1.accept();
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.accept');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send the SAS accept to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('emojis are supported by both sides', () => {
|
||||
expect(sas1.supportsEmoji()).toStrictEqual(true);
|
||||
expect(sas2.supportsEmoji()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('one side sends verification key (`m.key.verification.key`)', async () => {
|
||||
// Let's send the verification keys from `m2` to `m1`.
|
||||
const outgoingRequests = await m2.outgoingRequests();
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
const toDeviceRequestId = toDeviceRequest.id;
|
||||
const toDeviceRequestType = toDeviceRequest.type;
|
||||
|
||||
toDeviceRequest = JSON.parse(toDeviceRequest.body);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: toDeviceRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS key to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m2.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
|
||||
});
|
||||
|
||||
test('other side sends back verification key (`m.key.verification.key`)', async () => {
|
||||
// Let's send the verification keys from `m1` to `m2`.
|
||||
const outgoingRequests = await m1.outgoingRequests();
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
const toDeviceRequestId = toDeviceRequest.id;
|
||||
const toDeviceRequestType = toDeviceRequest.type;
|
||||
|
||||
toDeviceRequest = JSON.parse(toDeviceRequest.body);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS key to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
|
||||
});
|
||||
|
||||
test('emojis match from both sides', () => {
|
||||
const emojis1 = sas1.emoji();
|
||||
const emojiIndexes1 = sas1.emojiIndex();
|
||||
const emojis2 = sas2.emoji();
|
||||
const emojiIndexes2 = sas2.emojiIndex();
|
||||
|
||||
expect(emojis1).toHaveLength(7);
|
||||
expect(emojiIndexes1).toHaveLength(emojis1.length);
|
||||
expect(emojis2).toHaveLength(emojis1.length);
|
||||
expect(emojiIndexes2).toHaveLength(emojis1.length);
|
||||
|
||||
const isEmoji = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/;
|
||||
|
||||
for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(emojis1, emojiIndexes1, emojis2, emojiIndexes2)) {
|
||||
expect(emoji1).toBeInstanceOf(Emoji);
|
||||
expect(emoji1.symbol).toMatch(isEmoji);
|
||||
expect(emoji1.description).toBeTruthy();
|
||||
|
||||
expect(emojiIndex1).toBeGreaterThanOrEqual(0);
|
||||
expect(emojiIndex1).toBeLessThanOrEqual(63);
|
||||
|
||||
expect(emoji2).toBeInstanceOf(Emoji);
|
||||
expect(emoji2.symbol).toStrictEqual(emoji1.symbol);
|
||||
expect(emoji2.description).toStrictEqual(emoji1.description);
|
||||
|
||||
expect(emojiIndex2).toStrictEqual(emojiIndex1);
|
||||
}
|
||||
});
|
||||
|
||||
test('decimals match from both sides', () => {
|
||||
const decimals1 = sas1.decimals();
|
||||
const decimals2 = sas2.decimals();
|
||||
|
||||
expect(decimals1).toHaveLength(3);
|
||||
expect(decimals2).toHaveLength(decimals1.length);
|
||||
|
||||
const isDecimal = /^[0-9]{4}$/;
|
||||
|
||||
for (const [decimal1, decimal2] of zip(decimals1, decimals2)) {
|
||||
expect(decimal1.toString()).toMatch(isDecimal);
|
||||
|
||||
expect(decimal2).toStrictEqual(decimal1);
|
||||
}
|
||||
});
|
||||
|
||||
test('can confirm keys match (`m.key.verification.mac`)', async () => {
|
||||
// `m1` confirms.
|
||||
const [outgoingVerificationRequests, signatureUploadRequest] = await sas1.confirm();
|
||||
|
||||
expect(signatureUploadRequest).toBeUndefined();
|
||||
expect(outgoingVerificationRequests).toHaveLength(1);
|
||||
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[0];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS confirmation to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm back keys match (`m.key.verification.done`)', async () => {
|
||||
// `m2` confirms.
|
||||
const [outgoingVerificationRequests, signatureUploadRequest] = await sas2.confirm();
|
||||
|
||||
expect(signatureUploadRequest).toBeUndefined();
|
||||
expect(outgoingVerificationRequests).toHaveLength(2);
|
||||
|
||||
// `.mac`
|
||||
{
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[0];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS confirmation to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
}
|
||||
|
||||
// `.done`
|
||||
{
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[1];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS done to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
}
|
||||
});
|
||||
|
||||
test('can send final done (`m.key.verification.done`)', async () => {
|
||||
const outgoingRequests = await m1.outgoingRequests();
|
||||
expect(outgoingRequests).toHaveLength(4);
|
||||
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
const toDeviceRequestId = toDeviceRequest.id;
|
||||
const toDeviceRequestType = toDeviceRequest.type;
|
||||
|
||||
toDeviceRequest = JSON.parse(toDeviceRequest.body);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS key to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
|
||||
});
|
||||
|
||||
test('can see if verification is done', () => {
|
||||
expect(verificationRequest1.isDone()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isDone()).toStrictEqual(true);
|
||||
|
||||
expect(sas1.isDone()).toStrictEqual(true);
|
||||
expect(sas2.isDone()).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('QR Code', () => {
|
||||
if (undefined === Qr) {
|
||||
// qrcode supports is not enabled
|
||||
console.info('qrcode support is disabled, skip the associated test suite');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// First Olm machine.
|
||||
let m1;
|
||||
|
||||
// Second Olm machine.
|
||||
let m2;
|
||||
|
||||
beforeAll(async () => {
|
||||
m1 = await machine(userId1, deviceId1);
|
||||
m2 = await machine(userId2, deviceId2);
|
||||
});
|
||||
|
||||
// Verification request for `m1`.
|
||||
let verificationRequest1;
|
||||
|
||||
// The flow ID.
|
||||
let flowId;
|
||||
|
||||
test('can request verification (`m.key.verification.request`)', async () => {
|
||||
// Make `m1` and `m2` be aware of each other.
|
||||
{
|
||||
await addMachineToMachine(m2, m1);
|
||||
await addMachineToMachine(m1, m2);
|
||||
}
|
||||
|
||||
// Pick the device we want to start the verification with.
|
||||
const device2 = await m1.getDevice(userId2, deviceId2);
|
||||
|
||||
expect(device2).toBeInstanceOf(Device);
|
||||
|
||||
let outgoingVerificationRequest;
|
||||
// Request a verification from `m1` to `device2`.
|
||||
[verificationRequest1, outgoingVerificationRequest] = await device2.requestVerification([
|
||||
VerificationMethod.QrCodeScanV1, // by default
|
||||
VerificationMethod.QrCodeShowV1, // the one we add
|
||||
]);
|
||||
|
||||
expect(verificationRequest1).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
expect(verificationRequest1.ownUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(verificationRequest1.otherUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(verificationRequest1.otherDeviceId).toBeUndefined();
|
||||
expect(verificationRequest1.roomId).toBeUndefined();
|
||||
expect(verificationRequest1.cancelInfo).toBeUndefined();
|
||||
expect(verificationRequest1.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest1.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest1.weStarted()).toStrictEqual(true);
|
||||
expect(verificationRequest1.isDone()).toStrictEqual(false);
|
||||
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}]
|
||||
};
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
flowId = verificationRequest1.flowId;
|
||||
});
|
||||
|
||||
// Verification request for `m2`.
|
||||
let verificationRequest2;
|
||||
|
||||
test('can fetch received request verification', async () => {
|
||||
// Oh, a new verification request.
|
||||
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
|
||||
|
||||
expect(verificationRequest2).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
expect(verificationRequest2.ownUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(verificationRequest2.otherUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(verificationRequest2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(verificationRequest2.roomId).toBeUndefined();
|
||||
expect(verificationRequest2.cancelInfo).toBeUndefined();
|
||||
expect(verificationRequest2.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest2.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest2.flowId).toStrictEqual(flowId);
|
||||
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest2.weStarted()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isDone()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isCancelled()).toStrictEqual(false);
|
||||
|
||||
const verificationRequests = m2.getVerificationRequests(userId1);
|
||||
expect(verificationRequests).toHaveLength(1);
|
||||
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
|
||||
});
|
||||
|
||||
test('can accept a verification request with methods (`m.key.verification.ready`)', async () => {
|
||||
// Accept the verification request.
|
||||
let outgoingVerificationRequest = verificationRequest2.acceptWithMethods([
|
||||
VerificationMethod.QrCodeScanV1, // by default
|
||||
VerificationMethod.QrCodeShowV1, // the one we add
|
||||
]);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
// The request verification is ready.
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send the verification ready to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('verification requests are synchronized and automatically updated', () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(true);
|
||||
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
});
|
||||
|
||||
// QR verification for the second machine.
|
||||
let qr2;
|
||||
|
||||
test('can generate a QR code', async () => {
|
||||
qr2 = await verificationRequest2.generateQrCode();
|
||||
|
||||
expect(qr2).toBeInstanceOf(Qr);
|
||||
|
||||
expect(qr2.hasBeenScanned()).toStrictEqual(false);
|
||||
expect(qr2.hasBeenConfirmed()).toStrictEqual(false);
|
||||
expect(qr2.userId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(qr2.otherUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(qr2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(qr2.weStarted()).toStrictEqual(false);
|
||||
expect(qr2.cancelInfo()).toBeUndefined();
|
||||
expect(qr2.isDone()).toStrictEqual(false);
|
||||
expect(qr2.isCancelled()).toStrictEqual(false);
|
||||
expect(qr2.isSelfVerification()).toStrictEqual(false);
|
||||
expect(qr2.reciprocated()).toStrictEqual(false);
|
||||
expect(qr2.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(qr2.roomId).toBeUndefined();
|
||||
});
|
||||
|
||||
let qrCodeBytes;
|
||||
|
||||
test('can read QR code\'s bytes', async () => {
|
||||
const qrCodeHeader = 'MATRIX';
|
||||
const qrCodeVersion = '\x02';
|
||||
|
||||
qrCodeBytes = qr2.toBytes();
|
||||
|
||||
expect(qrCodeBytes).toHaveLength(122);
|
||||
expect(qrCodeBytes.slice(0, 7)).toStrictEqual([...qrCodeHeader, ...qrCodeVersion].map(char => char.charCodeAt(0)));
|
||||
});
|
||||
|
||||
test('can render QR code', async () => {
|
||||
const qrCode = qr2.toQrCode();
|
||||
|
||||
expect(qrCode).toBeInstanceOf(QrCode);
|
||||
|
||||
// Want to get `canvasBuffer` to render the QR code? Install `npm install canvas` and uncomment the following blocks.
|
||||
|
||||
//let canvasBuffer;
|
||||
|
||||
{
|
||||
const buffer = qrCode.renderIntoBuffer();
|
||||
|
||||
expect(buffer).toBeInstanceOf(Uint8ClampedArray);
|
||||
// 45px ⨉ 45px
|
||||
expect(buffer).toHaveLength(45 * 45);
|
||||
// 0 for a white pixel, 1 for a black pixel.
|
||||
expect(buffer.every(p => p == 0 || p == 1)).toStrictEqual(true);
|
||||
|
||||
/*
|
||||
const { Canvas } = require('canvas');
|
||||
const canvas = new Canvas(55, 55);
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
context.fillStyle = 'white';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// New image data, filled with black, transparent pixels.
|
||||
const imageData = context.createImageData(45, 45);
|
||||
const data = imageData.data;
|
||||
|
||||
const [r, g, b, a] = [0, 1, 2, 3];
|
||||
|
||||
for (
|
||||
let dataNth = 0,
|
||||
bufferNth = 0;
|
||||
dataNth < data.length && bufferNth < buffer.length;
|
||||
dataNth += 4,
|
||||
bufferNth += 1
|
||||
) {
|
||||
data[dataNth + a] = 255;
|
||||
|
||||
// White pixel
|
||||
if (buffer[bufferNth] == 0) {
|
||||
data[dataNth + r] = 255;
|
||||
data[dataNth + g] = 255;
|
||||
data[dataNth + b] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
context.putImageData(imageData, 5, 5);
|
||||
canvasBuffer = canvas.toBuffer('image/png');
|
||||
*/
|
||||
}
|
||||
|
||||
// Want to see the QR code? Uncomment the following block.
|
||||
/*
|
||||
{
|
||||
const fs = require('fs/promises');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const tempDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--'));
|
||||
const qrCodeFile = path.join(tempDirectory, 'qrcode.png');
|
||||
|
||||
console.log(`View the QR code at \`${qrCodeFile}\`.`);
|
||||
|
||||
expect(await fs.writeFile(qrCodeFile, canvasBuffer)).toBeUndefined();
|
||||
}
|
||||
*/
|
||||
});
|
||||
|
||||
let qr1;
|
||||
|
||||
test('can scan a QR code from bytes', async () => {
|
||||
const scan = QrCodeScan.fromBytes(qrCodeBytes);
|
||||
|
||||
expect(scan).toBeInstanceOf(QrCodeScan);
|
||||
|
||||
qr1 = await verificationRequest1.scanQrCode(scan);
|
||||
|
||||
expect(qr1).toBeInstanceOf(Qr);
|
||||
|
||||
expect(qr1.hasBeenScanned()).toStrictEqual(false);
|
||||
expect(qr1.hasBeenConfirmed()).toStrictEqual(false);
|
||||
expect(qr1.userId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(qr1.otherUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(qr1.otherDeviceId.toString()).toStrictEqual(deviceId2.toString());
|
||||
expect(qr1.weStarted()).toStrictEqual(true);
|
||||
expect(qr1.cancelInfo()).toBeUndefined();
|
||||
expect(qr1.isDone()).toStrictEqual(false);
|
||||
expect(qr1.isCancelled()).toStrictEqual(false);
|
||||
expect(qr1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(qr1.reciprocated()).toStrictEqual(true);
|
||||
expect(qr1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(qr1.roomId).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can start a QR verification/reciprocate (`m.key.verification.start`)', async () => {
|
||||
let outgoingVerificationRequest = qr1.reciprocate();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}]
|
||||
};
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm QR code has been scanned', () => {
|
||||
expect(qr2.hasBeenScanned()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('can confirm scanning (`m.key.verification.done`)', async () => {
|
||||
let outgoingVerificationRequest = qr2.confirmScanning();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}]
|
||||
};
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm QR code has been confirmed', () => {
|
||||
expect(qr2.hasBeenConfirmed()).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationMethod', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(VerificationMethod.SasV1).toStrictEqual(0);
|
||||
expect(VerificationMethod.QrCodeScanV1).toStrictEqual(1);
|
||||
expect(VerificationMethod.QrCodeShowV1).toStrictEqual(2);
|
||||
expect(VerificationMethod.ReciprocateV1).toStrictEqual(3);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto');
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe('EncryptionAlgorithm', () => {
|
||||
test('has the correct variant values', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto');
|
||||
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe('HistoryVisibility', () => {
|
||||
test('has the correct variant values', () => {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
function* zip(...arrays) {
|
||||
const len = Math.min(...arrays.map((array) => array.length));
|
||||
|
||||
for (let nth = 0; nth < len; ++nth) {
|
||||
yield [...arrays.map((array) => array.at(nth))]
|
||||
}
|
||||
}
|
||||
|
||||
// Add a machine to another machine, i.e. be sure a machine knows
|
||||
// another exists.
|
||||
async function addMachineToMachine(machineToAdd, machine) {
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
|
||||
expect(receiveSyncChanges).toEqual({});
|
||||
|
||||
const outgoingRequests = await machineToAdd.outgoingRequests();
|
||||
|
||||
expect(outgoingRequests).toHaveLength(2);
|
||||
|
||||
let keysUploadRequest;
|
||||
// Read the `KeysUploadRequest`.
|
||||
{
|
||||
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
|
||||
expect(outgoingRequests[0].id).toBeDefined();
|
||||
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
|
||||
|
||||
const body = JSON.parse(outgoingRequests[0].body);
|
||||
expect(body.device_keys).toBeDefined();
|
||||
expect(body.one_time_keys).toBeDefined();
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
});
|
||||
const marked = await machineToAdd.markRequestAsSent(outgoingRequests[0].id, outgoingRequests[0].type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
|
||||
keysUploadRequest = body;
|
||||
}
|
||||
|
||||
{
|
||||
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
|
||||
|
||||
let [signingKeysUploadRequest, _] = await machineToAdd.bootstrapCrossSigning(true);
|
||||
signingKeysUploadRequest = JSON.parse(signingKeysUploadRequest.body);
|
||||
|
||||
// Let's forge a `KeysQuery`'s response.
|
||||
let keyQueryResponse = {
|
||||
device_keys: {},
|
||||
master_keys: {},
|
||||
self_signing_keys: {},
|
||||
user_signing_keys: {},
|
||||
};
|
||||
const userId = machineToAdd.userId.toString();
|
||||
const deviceId = machineToAdd.deviceId.toString();
|
||||
keyQueryResponse.device_keys[userId] = {};
|
||||
keyQueryResponse.device_keys[userId][deviceId] = keysUploadRequest.device_keys;
|
||||
keyQueryResponse.master_keys[userId] = signingKeysUploadRequest.master_key;
|
||||
keyQueryResponse.self_signing_keys[userId] = signingKeysUploadRequest.self_signing_key;
|
||||
keyQueryResponse.user_signing_keys[userId] = signingKeysUploadRequest.user_signing_key;
|
||||
|
||||
const marked = await machine.markRequestAsSent(outgoingRequests[1].id, outgoingRequests[1].type, JSON.stringify(keyQueryResponse));
|
||||
expect(marked).toStrictEqual(true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
zip,
|
||||
addMachineToMachine,
|
||||
};
|
||||
@@ -1,4 +1,13 @@
|
||||
const { UserId, DeviceId, RoomId, ServerName } = require('../pkg/matrix_sdk_crypto');
|
||||
const {
|
||||
DeviceId,
|
||||
DeviceKeyAlgorithm,
|
||||
DeviceKeyAlgorithmName,
|
||||
DeviceKeyId,
|
||||
EventId,
|
||||
RoomId,
|
||||
ServerName,
|
||||
UserId,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe(UserId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
@@ -32,6 +41,52 @@ describe(DeviceId.name, () => {
|
||||
})
|
||||
});
|
||||
|
||||
describe(DeviceKeyId.name, () => {
|
||||
for (const deviceKey of [
|
||||
{ name: 'ed25519',
|
||||
id: 'ed25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Ed25519,
|
||||
algorithm: 'ed25519',
|
||||
deviceId: 'foobar' },
|
||||
|
||||
{ name: 'curve25519',
|
||||
id: 'curve25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Curve25519,
|
||||
algorithm: 'curve25519',
|
||||
deviceId: 'foobar' },
|
||||
|
||||
{ name: 'signed curve25519',
|
||||
id: 'signed_curve25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
|
||||
algorithm: 'signed_curve25519',
|
||||
deviceId: 'foobar' },
|
||||
|
||||
{ name: 'unknown',
|
||||
id: 'hello:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Unknown,
|
||||
algorithm: 'hello',
|
||||
deviceId: 'foobar' },
|
||||
]) {
|
||||
test(`${deviceKey.name} algorithm`, () => {
|
||||
const dk = new DeviceKeyId(deviceKey.id);
|
||||
|
||||
expect(dk.algorithm.name).toStrictEqual(deviceKey.algorithmName);
|
||||
expect(dk.algorithm.toString()).toStrictEqual(deviceKey.algorithm);
|
||||
expect(dk.deviceId.toString()).toStrictEqual(deviceKey.deviceId);
|
||||
expect(dk.toString()).toStrictEqual(deviceKey.id);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('DeviceKeyAlgorithmName', () => {
|
||||
test('has the correct variants', () => {
|
||||
expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0);
|
||||
expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1);
|
||||
expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2);
|
||||
expect(DeviceKeyAlgorithmName.Unknown).toStrictEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe(RoomId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new RoomId('!foo') }).toThrow();
|
||||
@@ -70,3 +125,57 @@ describe(ServerName.name, () => {
|
||||
expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(EventId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new EventId('%foo') }).toThrow();
|
||||
});
|
||||
|
||||
describe('Versions 1 & 2', () => {
|
||||
const room = new EventId('$h29iv0s8:foo.org');
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('h29iv0s8');
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
expect(room.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('$h29iv0s8:foo.org');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Version 3', () => {
|
||||
const room = new EventId('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
expect(room.serverName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Version 4', () => {
|
||||
const room = new EventId('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
expect(room.serverName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,10 +1,91 @@
|
||||
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState } = require('../pkg/matrix_sdk_crypto');
|
||||
const {
|
||||
CrossSigningStatus,
|
||||
DecryptedRoomEvent,
|
||||
DeviceId,
|
||||
DeviceKeyId,
|
||||
DeviceLists,
|
||||
EncryptionSettings,
|
||||
InboundGroupSession,
|
||||
KeysClaimRequest,
|
||||
KeysQueryRequest,
|
||||
KeysUploadRequest,
|
||||
MaybeSignature,
|
||||
OlmMachine,
|
||||
OwnUserIdentity,
|
||||
RequestType,
|
||||
RoomId,
|
||||
SignatureUploadRequest,
|
||||
ToDeviceRequest,
|
||||
UserId,
|
||||
VerificationRequest,
|
||||
VerificationState,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { addMachineToMachine } = require('./helper');
|
||||
require('fake-indexeddb/auto');
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
test('can be instantiated with the async initializer', async () => {
|
||||
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
|
||||
test('can be instantiated with a store', async () => {
|
||||
// No databases.
|
||||
expect(await indexedDB.databases()).toHaveLength(0);
|
||||
|
||||
let store_name = 'hello';
|
||||
let store_passphrase = 'world';
|
||||
|
||||
// Creating a new Olm machine.
|
||||
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine);
|
||||
|
||||
// Oh, there is 2 databases now, prefixed by `store_name`.
|
||||
let databases = await indexedDB.databases();
|
||||
|
||||
expect(databases).toHaveLength(2);
|
||||
expect(databases).toStrictEqual([
|
||||
{ name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 },
|
||||
{ name: `${store_name}::matrix-sdk-crypto`, version: 1 },
|
||||
]);
|
||||
|
||||
// Creating a new Olm machine, with the stored state.
|
||||
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine);
|
||||
|
||||
// Same number of databases.
|
||||
expect(await indexedDB.databases()).toHaveLength(2);
|
||||
});
|
||||
|
||||
describe('cannot be instantiated with a store', () => {
|
||||
test('store name is missing', async () => {
|
||||
let store_name = null;
|
||||
let store_passphrase = 'world';
|
||||
|
||||
let err = null;
|
||||
|
||||
try {
|
||||
await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase);
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
|
||||
expect(err).toBeDefined();
|
||||
});
|
||||
|
||||
test('store passphrase is missing', async () => {
|
||||
let store_name = 'hello';
|
||||
let store_passphrase = null;
|
||||
|
||||
let err = null;
|
||||
|
||||
try {
|
||||
await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase);
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
|
||||
expect(err).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('foobar');
|
||||
const room = new RoomId('!baz:matrix.org');
|
||||
@@ -299,7 +380,8 @@ describe(OlmMachine.name, () => {
|
||||
room,
|
||||
'm.room.message',
|
||||
JSON.stringify({
|
||||
"hello": "world"
|
||||
"msgtype": "m.text",
|
||||
"body": "Hello, World!"
|
||||
}),
|
||||
));
|
||||
|
||||
@@ -328,7 +410,8 @@ describe(OlmMachine.name, () => {
|
||||
expect(decrypted).toBeInstanceOf(DecryptedRoomEvent);
|
||||
|
||||
const event = JSON.parse(decrypted.event);
|
||||
expect(event.content.hello).toStrictEqual("world");
|
||||
expect(event.content.msgtype).toStrictEqual("m.text");
|
||||
expect(event.content.body).toStrictEqual("Hello, World!");
|
||||
|
||||
expect(decrypted.sender.toString()).toStrictEqual(user.toString());
|
||||
expect(decrypted.senderDevice.toString()).toStrictEqual(device.toString());
|
||||
@@ -338,4 +421,149 @@ describe(OlmMachine.name, () => {
|
||||
expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted);
|
||||
});
|
||||
});
|
||||
|
||||
test('can read cross-signing status', async () => {
|
||||
const m = await machine();
|
||||
const crossSigningStatus = await m.crossSigningStatus();
|
||||
|
||||
expect(crossSigningStatus).toBeInstanceOf(CrossSigningStatus);
|
||||
expect(crossSigningStatus.hasMaster).toStrictEqual(false);
|
||||
expect(crossSigningStatus.hasSelfSigning).toStrictEqual(false);
|
||||
expect(crossSigningStatus.hasUserSigning).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can sign a message', async () => {
|
||||
const m = await machine();
|
||||
const signatures = await m.sign('foo');
|
||||
|
||||
expect(signatures.isEmpty()).toStrictEqual(false);
|
||||
expect(signatures.count).toStrictEqual(1);
|
||||
|
||||
let base64;
|
||||
|
||||
// `get`
|
||||
{
|
||||
const signature = signatures.get(user);
|
||||
|
||||
expect(signature.has('ed25519:foobar')).toStrictEqual(true);
|
||||
|
||||
const s = signature.get('ed25519:foobar');
|
||||
|
||||
expect(s).toBeInstanceOf(MaybeSignature);
|
||||
|
||||
expect(s.isValid()).toStrictEqual(true);
|
||||
expect(s.isInvalid()).toStrictEqual(false);
|
||||
expect(s.invalidSignatureSource).toBeUndefined();
|
||||
|
||||
base64 = s.signature.toBase64();
|
||||
|
||||
expect(base64).toMatch(/^[A-Za-z0-9\+/]+$/);
|
||||
expect(s.signature.ed25519.toBase64()).toStrictEqual(base64);
|
||||
}
|
||||
|
||||
// `getSignature`
|
||||
{
|
||||
const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar'));
|
||||
expect(signature.toBase64()).toStrictEqual(base64);
|
||||
}
|
||||
|
||||
// Unknown signatures.
|
||||
{
|
||||
expect(signatures.get(new UserId('@hello:example.org'))).toBeUndefined();
|
||||
expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test('can get a user identities', async () => {
|
||||
const m = await machine();
|
||||
let _ = m.bootstrapCrossSigning(true);
|
||||
|
||||
const identity = await m.getIdentity(user);
|
||||
|
||||
expect(identity).toBeInstanceOf(OwnUserIdentity);
|
||||
|
||||
const signatureUploadRequest = await identity.verify();
|
||||
expect(signatureUploadRequest).toBeInstanceOf(SignatureUploadRequest);
|
||||
|
||||
const [verificationRequest, outgoingVerificationRequest] = await identity.requestVerification();
|
||||
expect(verificationRequest).toBeInstanceOf(VerificationRequest);
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
const isTrusted = await identity.trustsOurOwnDevice();
|
||||
|
||||
expect(isTrusted).toStrictEqual(false);
|
||||
});
|
||||
|
||||
describe('can export/import room keys', () => {
|
||||
let m;
|
||||
let exportedRoomKeys;
|
||||
|
||||
test('can export room keys', async () => {
|
||||
m = await machine();
|
||||
await m.shareRoomKey(room, [new UserId('@bob:example.org')], new EncryptionSettings());
|
||||
|
||||
exportedRoomKeys = await m.exportRoomKeys(session => {
|
||||
expect(session).toBeInstanceOf(InboundGroupSession);
|
||||
expect(session.roomId.toString()).toStrictEqual(room.toString());
|
||||
expect(session.sessionId).toBeDefined();
|
||||
expect(session.hasBeenImported()).toStrictEqual(false);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const roomKeys = JSON.parse(exportedRoomKeys);
|
||||
expect(roomKeys).toHaveLength(1);
|
||||
expect(roomKeys[0]).toMatchObject({
|
||||
algorithm: expect.any(String),
|
||||
room_id: room.toString(),
|
||||
sender_key: expect.any(String),
|
||||
session_id: expect.any(String),
|
||||
session_key: expect.any(String),
|
||||
sender_claimed_keys: {
|
||||
ed25519: expect.any(String),
|
||||
},
|
||||
forwarding_curve25519_key_chain: [],
|
||||
});
|
||||
});
|
||||
|
||||
let encryptedExportedRoomKeys;
|
||||
let encryptionPassphrase = 'Hello, Matrix!';
|
||||
|
||||
test('can encrypt the exported room keys', () => {
|
||||
encryptedExportedRoomKeys = OlmMachine.encryptExportedRoomKeys(
|
||||
exportedRoomKeys,
|
||||
encryptionPassphrase,
|
||||
100_000,
|
||||
);
|
||||
|
||||
expect(encryptedExportedRoomKeys).toMatch(/^-----BEGIN MEGOLM SESSION DATA-----/);
|
||||
});
|
||||
|
||||
test('can decrypt the exported room keys', () => {
|
||||
const decryptedExportedRoomKeys = OlmMachine.decryptExportedRoomKeys(
|
||||
encryptedExportedRoomKeys,
|
||||
encryptionPassphrase,
|
||||
);
|
||||
|
||||
expect(decryptedExportedRoomKeys).toStrictEqual(exportedRoomKeys);
|
||||
});
|
||||
|
||||
test('can import room keys', async () => {
|
||||
const progressListener = (progress, total) => {
|
||||
expect(progress).toBeLessThan(total);
|
||||
|
||||
// Since it's called only once, let's be crazy.
|
||||
expect(progress).toStrictEqual(0n);
|
||||
expect(total).toStrictEqual(1n);
|
||||
};
|
||||
|
||||
const result = JSON.parse(await m.importRoomKeys(exportedRoomKeys, progressListener));
|
||||
|
||||
expect(result).toMatchObject({
|
||||
imported_count: expect.any(Number),
|
||||
total_count: expect.any(Number),
|
||||
keys: expect.any(Object),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto');
|
||||
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe('RequestType', () => {
|
||||
test('has the correct variant values', () => {
|
||||
@@ -12,7 +12,7 @@ describe('RequestType', () => {
|
||||
});
|
||||
});
|
||||
|
||||
for (const [request, request_type] of [
|
||||
for (const [request, requestType] of [
|
||||
[KeysUploadRequest, RequestType.KeysUpload],
|
||||
[KeysQueryRequest, RequestType.KeysQuery],
|
||||
[KeysClaimRequest, RequestType.KeysClaim],
|
||||
@@ -28,7 +28,7 @@ for (const [request, request_type] of [
|
||||
expect(r).toBeInstanceOf(request);
|
||||
expect(r.id).toStrictEqual('foo');
|
||||
expect(r.body).toStrictEqual('{"bar": "baz"}');
|
||||
expect(r.type).toStrictEqual(request_type);
|
||||
expect(r.type).toStrictEqual(requestType);
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto');
|
||||
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe(DeviceLists.name, () => {
|
||||
test('can be empty', () => {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe('LoggerLevel', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(LoggerLevel.Trace).toStrictEqual(0);
|
||||
expect(LoggerLevel.Debug).toStrictEqual(1);
|
||||
expect(LoggerLevel.Info).toStrictEqual(2);
|
||||
expect(LoggerLevel.Warn).toStrictEqual(3);
|
||||
expect(LoggerLevel.Error).toStrictEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe(Tracing.name, () => {
|
||||
if (Tracing.isAvailable()) {
|
||||
let tracing = new Tracing(LoggerLevel.Debug);
|
||||
|
||||
test('can installed several times', () => {
|
||||
new Tracing(LoggerLevel.Debug);
|
||||
new Tracing(LoggerLevel.Warn);
|
||||
new Tracing(LoggerLevel.Debug);
|
||||
});
|
||||
|
||||
const originalConsoleDebug = console.debug;
|
||||
|
||||
for (const [testName, testPreState, testPostState, expectedGotcha] of [
|
||||
[
|
||||
'can log something',
|
||||
() => {},
|
||||
() => {},
|
||||
true,
|
||||
],
|
||||
[
|
||||
'can change the logger level',
|
||||
() => { tracing.minLevel = LoggerLevel.Warn },
|
||||
() => { tracing.minLevel = LoggerLevel.Debug },
|
||||
false,
|
||||
],
|
||||
[
|
||||
'can be turned off',
|
||||
() => { tracing.turnOff() },
|
||||
() => {},
|
||||
false,
|
||||
],
|
||||
[
|
||||
'can be turned on',
|
||||
() => { tracing.turnOn() },
|
||||
() => {},
|
||||
true,
|
||||
],
|
||||
|
||||
// This one *must* be the last. We are turning tracing off
|
||||
// again for the other tests.
|
||||
[
|
||||
'can be turned off',
|
||||
() => { tracing.turnOff() },
|
||||
() => {},
|
||||
false,
|
||||
],
|
||||
]) {
|
||||
test(testName, async () => {
|
||||
testPreState();
|
||||
|
||||
let gotcha = false;
|
||||
|
||||
console.debug = (msg) => {
|
||||
gotcha = true;
|
||||
expect(msg).not.toHaveLength(0);
|
||||
};
|
||||
|
||||
// Do something that emits a `DEBUG` log.
|
||||
await new OlmMachine(new UserId('@alice:example.org'), new DeviceId('foo'));
|
||||
|
||||
console.debug = originalConsoleDebug;
|
||||
testPostState();
|
||||
|
||||
expect(gotcha).toStrictEqual(expectedGotcha);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
test('cannot be constructed', () => {
|
||||
expect(() => { new Tracing(LoggerLevel.Error) }).toThrow();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
"strict": true
|
||||
},
|
||||
"typedocOptions": {
|
||||
"entryPoints": ["pkg/matrix_sdk_crypto.d.ts"],
|
||||
"entryPoints": ["pkg/matrix_sdk_crypto_js.d.ts"],
|
||||
"out": "docs",
|
||||
"readme": "README.md",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Matrix-Rust-SDK Node.js Bindings
|
||||
|
||||
## 0.1.0-beta.0 - 2022-07-21
|
||||
## 0.1.0-beta.1 - 2022-07-14
|
||||
|
||||
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
|
||||
|
||||
## 0.1.0-beta.0 - 2022-07-12
|
||||
|
||||
Welcome to the first release of `matrix-sdk-crypto-nodejs`. This is a
|
||||
Node.js binding for the Rust `matrix-sdk-crypto` library. This is a
|
||||
|
||||
@@ -9,10 +9,9 @@ name = "matrix-sdk-crypto-nodejs"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lib]
|
||||
@@ -21,21 +20,23 @@ crate-type = ["cdylib"]
|
||||
[features]
|
||||
default = []
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
docsrs = []
|
||||
tracing = ["tracing-subscriber"]
|
||||
tracing = ["dep:tracing-subscriber"]
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
|
||||
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
|
||||
matrix-sdk-sled = { version = "0.1.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" }
|
||||
napi = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t", default-features = false, features = ["napi6", "tokio_rt"] }
|
||||
napi-derive = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t" }
|
||||
matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] }
|
||||
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
|
||||
matrix-sdk-sled = { version = "0.2.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
|
||||
ruma = { version = "0.7.0", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
napi = { version = "2.9.1", default-features = false, features = ["napi6", "tokio_rt"] }
|
||||
napi-derive = "2.9.1"
|
||||
serde_json = "1.0.79"
|
||||
http = "0.2.6"
|
||||
zeroize = "1.3.0"
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["tracing-log", "time", "smallvec", "fmt", "env-filter"], optional = true }
|
||||
|
||||
[dependencies.vodozemac]
|
||||
version = "0.3.0"
|
||||
features = ["js"]
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.0.0"
|
||||
|
||||
@@ -13,7 +13,7 @@ Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
|
||||
|
||||
Just add the latest release to your `package.json`:
|
||||
```sh
|
||||
$ npm install --save matrix-sdk-crypto
|
||||
$ npm install --save @matrix-org/matrix-sdk-crypto-nodejs
|
||||
```
|
||||
|
||||
When installing, NPM will download the corresponding prebuilt Rust library for your current host system. The following are supported:
|
||||
@@ -186,7 +186,11 @@ to learn more about the `RUST_LOG`/`MATRIX_LOG` environment variable.
|
||||
|
||||
## Documentation
|
||||
|
||||
To generate the documentation, please run the following command:
|
||||
[The documentation can be found
|
||||
online](https://matrix-org.github.io/matrix-rust-sdk/bindings/matrix-sdk-crypto-nodejs/).
|
||||
|
||||
To generate the documentation locally, please run the following
|
||||
command:
|
||||
|
||||
```sh
|
||||
$ npm run doc
|
||||
|
||||
@@ -40,9 +40,11 @@ commit_preprocessors = [
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^refactor", group = "Refactoring"},
|
||||
{ message = "^ci", group = "Continuous Integration"},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
|
||||
@@ -3,7 +3,7 @@ const { version } = require("./package.json");
|
||||
const { platform, arch } = process
|
||||
|
||||
const DOWNLOADS_BASE_URL = "https://github.com/matrix-org/matrix-rust-sdk/releases/download";
|
||||
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-${version}`;
|
||||
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-v${version}`;
|
||||
|
||||
const byteHelper = function (value) {
|
||||
if (value === 0) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@matrix-org/matrix-sdk-crypto-nodejs",
|
||||
"version": "0.1.0-beta.0",
|
||||
"version": "0.1.0-beta.1",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
@@ -15,7 +15,8 @@
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.9.0",
|
||||
"jest": "^28.1.0",
|
||||
"typedoc": "^0.22.17"
|
||||
"typedoc": "^0.22.17",
|
||||
"yargs-parser": "~21.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct Attachment;
|
||||
impl Attachment {
|
||||
/// Encrypt the content of the `Uint8Array`.
|
||||
///
|
||||
/// It produces an `EncryptedAttachment`, we can be used to
|
||||
/// It produces an `EncryptedAttachment`, which can be used to
|
||||
/// retrieve the media encryption information, or the encrypted
|
||||
/// data.
|
||||
#[napi]
|
||||
@@ -55,7 +55,7 @@ impl Attachment {
|
||||
None => {
|
||||
return Err(napi::Error::from_reason(
|
||||
"The media encryption info are absent from the given encrypted attachment"
|
||||
.to_string(),
|
||||
.to_owned(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ pub enum EncryptionAlgorithm {
|
||||
MegolmV1AesSha2,
|
||||
}
|
||||
|
||||
impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
|
||||
impl From<EncryptionAlgorithm> for matrix_sdk_crypto::types::EventEncryptionAlgorithm {
|
||||
fn from(value: EncryptionAlgorithm) -> Self {
|
||||
use EncryptionAlgorithm::*;
|
||||
|
||||
@@ -27,9 +27,9 @@ impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ruma::EventEncryptionAlgorithm> for EncryptionAlgorithm {
|
||||
fn from(value: ruma::EventEncryptionAlgorithm) -> Self {
|
||||
use ruma::EventEncryptionAlgorithm::*;
|
||||
impl From<matrix_sdk_crypto::types::EventEncryptionAlgorithm> for EncryptionAlgorithm {
|
||||
fn from(value: matrix_sdk_crypto::types::EventEncryptionAlgorithm) -> Self {
|
||||
use matrix_sdk_crypto::types::EventEncryptionAlgorithm::*;
|
||||
|
||||
match value {
|
||||
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
@@ -58,6 +58,10 @@ pub struct EncryptionSettings {
|
||||
/// The history visibility of the room when the session was
|
||||
/// created.
|
||||
pub history_visibility: events::HistoryVisibility,
|
||||
|
||||
/// Should untrusted devices receive the room key, or should they be
|
||||
/// excluded from the conversation.
|
||||
pub only_allow_trusted_devices: bool,
|
||||
}
|
||||
|
||||
impl Default for EncryptionSettings {
|
||||
@@ -77,6 +81,7 @@ impl Default for EncryptionSettings {
|
||||
n.into()
|
||||
},
|
||||
history_visibility: default.history_visibility.into(),
|
||||
only_allow_trusted_devices: default.only_allow_trusted_devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +102,7 @@ impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
|
||||
rotation_period: Duration::from_micros(value.rotation_period.get_u64().1),
|
||||
rotation_period_msgs: value.rotation_period_messages.get_u64().1,
|
||||
history_visibility: value.history_visibility.into(),
|
||||
only_allow_trusted_devices: value.only_allow_trusted_devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,8 @@ use std::{
|
||||
|
||||
use napi::bindgen_prelude::Either7;
|
||||
use napi_derive::*;
|
||||
use ruma::{
|
||||
events::room::encrypted::OriginalSyncRoomEncryptedEvent, DeviceKeyAlgorithm,
|
||||
OwnedTransactionId, UInt,
|
||||
};
|
||||
use serde_json::Value as JsonValue;
|
||||
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
|
||||
use serde_json::{value::RawValue, Value as JsonValue};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::{
|
||||
@@ -68,7 +65,7 @@ impl OlmMachine {
|
||||
|
||||
let store = store_path
|
||||
.map(|store_path| {
|
||||
matrix_sdk_sled::CryptoStore::open_with_passphrase(
|
||||
matrix_sdk_sled::SledCryptoStore::open_with_passphrase(
|
||||
store_path,
|
||||
store_passphrase.as_deref(),
|
||||
)
|
||||
@@ -103,8 +100,7 @@ impl OlmMachine {
|
||||
/// constructor because building an `OlmMachine` is
|
||||
/// asynchronous. Please use the `finalize` method.
|
||||
#[napi(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> napi::Result<()> {
|
||||
pub fn new() -> napi::Result<Self> {
|
||||
Err(napi::Error::from_reason(
|
||||
"To build an `OldMachine`, please use the `initialize` method",
|
||||
))
|
||||
@@ -138,8 +134,7 @@ impl OlmMachine {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `to_device_events`, thhe to-device events of the current sync
|
||||
/// response.
|
||||
/// * `to_device_events`, the to-device events of the current sync response.
|
||||
/// * `changed_devices`, the list of devices that changed in this sync
|
||||
/// response.
|
||||
/// * `one_time_keys_count`, the current one-time keys counts that the sync
|
||||
@@ -389,8 +384,7 @@ impl OlmMachine {
|
||||
event: String,
|
||||
room_id: &identifiers::RoomId,
|
||||
) -> napi::Result<responses::DecryptedRoomEvent> {
|
||||
let event: OriginalSyncRoomEncryptedEvent =
|
||||
serde_json::from_str(event.as_str()).map_err(into_err)?;
|
||||
let event = Raw::from_json(RawValue::from_string(event).map_err(into_err)?);
|
||||
let room_id = room_id.inner.clone();
|
||||
|
||||
let room_event = self.inner.decrypt_room_event(&event, &room_id).await.map_err(into_err)?;
|
||||
|
||||
@@ -178,12 +178,8 @@ impl DecryptedRoomEvent {
|
||||
/// Chain of Curve25519 keys through which this session was
|
||||
/// forwarded, via `m.forwarded_room_key` events.
|
||||
#[napi(getter)]
|
||||
pub fn forwarding_curve25519_key_chain(&self) -> Option<Vec<String>> {
|
||||
Some(match &self.encryption_info.as_ref()?.algorithm_info {
|
||||
AlgorithmInfo::MegolmV1AesSha2 { forwarding_curve25519_key_chain, .. } => {
|
||||
forwarding_curve25519_key_chain.clone()
|
||||
}
|
||||
})
|
||||
pub fn forwarding_curve25519_key_chain(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// The verification state of the device that sent us the event,
|
||||
@@ -196,8 +192,8 @@ impl DecryptedRoomEvent {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_common::deserialized_responses::RoomEvent> for DecryptedRoomEvent {
|
||||
fn from(value: matrix_sdk_common::deserialized_responses::RoomEvent) -> Self {
|
||||
impl From<matrix_sdk_common::deserialized_responses::TimelineEvent> for DecryptedRoomEvent {
|
||||
fn from(value: matrix_sdk_common::deserialized_responses::TimelineEvent) -> Self {
|
||||
Self { event: value.event.json().get().to_owned(), encryption_info: value.encryption_info }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "matrix-sdk-ffi"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
keywords = ["matrix", "chat", "messaging", "ffi"]
|
||||
@@ -12,18 +12,19 @@ repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] }
|
||||
uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06", features = ["builtin-bindgen"] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.51"
|
||||
extension-trait = "1.0.1"
|
||||
futures-core = "0.3.17"
|
||||
futures-signals = { version = "0.3.28" }
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
matrix-sdk = { path = "../../crates/matrix-sdk", features = ["experimental-timeline", "markdown"] }
|
||||
# FIXME: we currently can't feature flag anything in the api.udl, therefore we must enforce sliding-sync being exposed here..
|
||||
# see https://github.com/matrix-org/matrix-rust-sdk/issues/1014
|
||||
matrix-sdk = { path = "../../crates/matrix-sdk", features = ["anyhow", "experimental-timeline", "markdown", "sliding-sync", "socks"], version = "0.6.0" }
|
||||
once_cell = "1.10.0"
|
||||
parking_lot = "0.12.0"
|
||||
sanitize-filename-reader-friendly = "2.2.1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1" }
|
||||
@@ -31,6 +32,7 @@ thiserror = "1.0.30"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
tokio-stream = "0.1.8"
|
||||
tracing = "0.1.32"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
# keep in sync with uniffi dependency in matrix-sdk-crypto-ffi, and uniffi_bindgen in ffi CI job
|
||||
uniffi = "0.18.0"
|
||||
uniffi_macros = "0.18.0"
|
||||
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06" }
|
||||
uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06" }
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
namespace sdk {
|
||||
MediaSource media_source_from_url(string url);
|
||||
MessageEventContent message_event_content_from_markdown(string md);
|
||||
string gen_transaction_id();
|
||||
namespace matrix_sdk_ffi {};
|
||||
|
||||
|
||||
/// Cancels on drop
|
||||
interface StoppableSpawn {
|
||||
boolean is_cancelled();
|
||||
void cancel();
|
||||
};
|
||||
|
||||
[Error]
|
||||
@@ -11,6 +14,178 @@ interface ClientError {
|
||||
|
||||
callback interface ClientDelegate {
|
||||
void did_receive_sync_update();
|
||||
void did_receive_auth_error(boolean is_soft_logout);
|
||||
void did_update_restore_token();
|
||||
};
|
||||
|
||||
dictionary RequiredState {
|
||||
string key;
|
||||
string value;
|
||||
};
|
||||
|
||||
dictionary RoomSubscription {
|
||||
sequence<RequiredState>? required_state;
|
||||
u32? timeline_limit;
|
||||
};
|
||||
|
||||
dictionary UpdateSummary {
|
||||
sequence<string> views;
|
||||
sequence<string> rooms;
|
||||
};
|
||||
|
||||
|
||||
callback interface SlidingSyncObserver {
|
||||
void did_receive_sync_update(UpdateSummary summary);
|
||||
};
|
||||
|
||||
enum SlidingSyncState {
|
||||
/// Hasn't started yet
|
||||
"Cold",
|
||||
/// We are quickly preloading a preview of the most important rooms
|
||||
"Preload",
|
||||
/// We are trying to load all remaining rooms, might be in batches
|
||||
"CatchingUp",
|
||||
/// We are all caught up and now only sync the live responses.
|
||||
"Live",
|
||||
};
|
||||
|
||||
enum SlidingSyncMode {
|
||||
/// Sync up the entire room list first
|
||||
"FullSync",
|
||||
/// Only ever sync the currently selected window
|
||||
"Selective",
|
||||
};
|
||||
|
||||
callback interface SlidingSyncViewStateObserver {
|
||||
void did_receive_update(SlidingSyncState new_state);
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface RoomListEntry {
|
||||
Empty();
|
||||
Invalidated(string room_id);
|
||||
Filled(string room_id);
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface SlidingSyncViewRoomsListDiff {
|
||||
Replace(sequence<RoomListEntry> values);
|
||||
InsertAt(
|
||||
u32 index,
|
||||
RoomListEntry value
|
||||
);
|
||||
UpdateAt(
|
||||
u32 index,
|
||||
RoomListEntry value
|
||||
);
|
||||
RemoveAt(u32 index);
|
||||
Move(
|
||||
u32 old_index,
|
||||
u32 new_index
|
||||
);
|
||||
Push(RoomListEntry value);
|
||||
// The following are supported by the generic VecDiff-type but
|
||||
// in sliding sync effectively do not happen and thus aren't exposed
|
||||
// to not pollute the API: Pop(); Clear();
|
||||
};
|
||||
|
||||
callback interface SlidingSyncViewRoomListObserver {
|
||||
void did_receive_update(SlidingSyncViewRoomsListDiff diff);
|
||||
};
|
||||
|
||||
callback interface SlidingSyncViewRoomsCountObserver {
|
||||
void did_receive_update(u32 count);
|
||||
};
|
||||
|
||||
callback interface SlidingSyncViewRoomItemsObserver {
|
||||
void did_receive_update();
|
||||
};
|
||||
|
||||
interface SlidingSyncViewBuilder {
|
||||
constructor();
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder timeline_limit(u32 limit);
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder sync_mode(SlidingSyncMode mode);
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder batch_size(u32 size);
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder name(string name);
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder sort(sequence<string> sort);
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder add_range(u32 from, u32 to);
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder reset_ranges();
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder required_state(sequence<RequiredState> required_state);
|
||||
|
||||
[Throws=ClientError, Self=ByArc]
|
||||
SlidingSyncView build();
|
||||
};
|
||||
|
||||
interface SlidingSyncView {
|
||||
StoppableSpawn observe_room_list(SlidingSyncViewRoomListObserver observer);
|
||||
StoppableSpawn observe_rooms_count(SlidingSyncViewRoomsCountObserver observer);
|
||||
StoppableSpawn observe_state(SlidingSyncViewStateObserver observer);
|
||||
StoppableSpawn observe_room_items(SlidingSyncViewRoomItemsObserver observer);
|
||||
|
||||
u32? current_room_count();
|
||||
sequence<RoomListEntry> current_rooms_list();
|
||||
|
||||
void add_range(u32 from, u32 to);
|
||||
void reset_ranges();
|
||||
void set_range(u32 from, u32 to);
|
||||
};
|
||||
|
||||
interface UnreadNotificationsCount {
|
||||
boolean has_notifications();
|
||||
|
||||
u32 highlight_count();
|
||||
u32 notification_count();
|
||||
};
|
||||
|
||||
interface SlidingSyncRoom {
|
||||
string? name();
|
||||
string room_id();
|
||||
|
||||
Room? full_room();
|
||||
|
||||
boolean? is_dm();
|
||||
boolean? is_initial();
|
||||
|
||||
boolean has_unread_notifications();
|
||||
UnreadNotificationsCount unread_notifications();
|
||||
|
||||
// aliveness
|
||||
boolean is_loading_more();
|
||||
|
||||
AnyMessage? latest_room_message();
|
||||
};
|
||||
|
||||
interface SlidingSync {
|
||||
void set_observer(SlidingSyncObserver? observer);
|
||||
|
||||
StoppableSpawn sync();
|
||||
|
||||
[Throws=ClientError]
|
||||
void subscribe(string room_id, RoomSubscription? settings);
|
||||
[Throws=ClientError]
|
||||
void unsubscribe(string room_id);
|
||||
|
||||
SlidingSyncView? get_view(string name);
|
||||
[Throws=ClientError]
|
||||
SlidingSyncRoom? get_room(string room_id);
|
||||
[Throws=ClientError]
|
||||
sequence<SlidingSyncRoom?> get_rooms(sequence<string> room_ids);
|
||||
};
|
||||
|
||||
interface ClientBuilder {
|
||||
@@ -29,28 +204,37 @@ interface ClientBuilder {
|
||||
Client build();
|
||||
};
|
||||
|
||||
interface SlidingSyncBuilder {
|
||||
[Throws=ClientError, Self=ByArc]
|
||||
SlidingSyncBuilder homeserver(string url);
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncBuilder add_fullsync_view();
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncBuilder no_views();
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncBuilder add_view(SlidingSyncView view);
|
||||
|
||||
[Throws=ClientError, Self=ByArc]
|
||||
SlidingSync build();
|
||||
};
|
||||
|
||||
interface Client {
|
||||
void set_delegate(ClientDelegate? delegate);
|
||||
|
||||
[Throws=ClientError]
|
||||
void login(string username, string password);
|
||||
void login(string username, string password, string? initial_device_name, string? device_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void restore_login(string restore_token);
|
||||
|
||||
string homeserver();
|
||||
|
||||
void start_sync();
|
||||
void start_sync(u16? timeline_limit);
|
||||
|
||||
[Throws=ClientError]
|
||||
string restore_token();
|
||||
|
||||
boolean is_guest();
|
||||
|
||||
boolean has_first_synced();
|
||||
|
||||
boolean is_syncing();
|
||||
|
||||
[Throws=ClientError]
|
||||
string user_id();
|
||||
|
||||
@@ -63,19 +247,40 @@ interface Client {
|
||||
[Throws=ClientError]
|
||||
string device_id();
|
||||
|
||||
sequence<Room> rooms();
|
||||
[Throws=ClientError]
|
||||
string? account_data(string event_type);
|
||||
|
||||
[Throws=ClientError]
|
||||
void set_account_data(string event_type, string content);
|
||||
|
||||
[Throws=ClientError]
|
||||
sequence<u8> get_media_content(MediaSource source);
|
||||
|
||||
[Throws=ClientError]
|
||||
sequence<u8> get_media_thumbnail(MediaSource source, u64 width, u64 height);
|
||||
|
||||
[Throws=ClientError]
|
||||
SessionVerificationController get_session_verification_controller();
|
||||
|
||||
[Throws=ClientError]
|
||||
SlidingSync full_sliding_sync();
|
||||
|
||||
SlidingSyncBuilder sliding_sync();
|
||||
|
||||
[Throws=ClientError]
|
||||
void logout();
|
||||
};
|
||||
|
||||
callback interface RoomDelegate {
|
||||
void did_receive_message(AnyMessage message);
|
||||
};
|
||||
|
||||
enum Membership {
|
||||
"Invited",
|
||||
"Joined",
|
||||
"Left",
|
||||
};
|
||||
|
||||
interface Room {
|
||||
void set_delegate(RoomDelegate? delegate);
|
||||
|
||||
@@ -84,6 +289,8 @@ interface Room {
|
||||
string? topic();
|
||||
string? avatar_url();
|
||||
|
||||
Membership membership();
|
||||
|
||||
boolean is_direct();
|
||||
boolean is_public();
|
||||
boolean is_space();
|
||||
@@ -103,14 +310,20 @@ interface Room {
|
||||
void stop_live_event_listener();
|
||||
|
||||
[Throws=ClientError]
|
||||
void send(MessageEventContent msg, string? txn_id);
|
||||
void send(RoomMessageEventContent msg, string? txn_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void send_reply(string msg, string in_reply_to_event_id, string? txn_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void redact(string event_id, string? reason, string? txn_id);
|
||||
};
|
||||
|
||||
interface BackwardsStream {
|
||||
sequence<AnyMessage> paginate_backwards(u64 count);
|
||||
};
|
||||
|
||||
interface MessageEventContent {};
|
||||
interface RoomMessageEventContent {};
|
||||
|
||||
interface AnyMessage {
|
||||
TextMessage? text_message();
|
||||
@@ -156,27 +369,30 @@ interface MediaSource {
|
||||
|
||||
[Error]
|
||||
enum AuthenticationError {
|
||||
"ClientMissing",
|
||||
"Generic",
|
||||
"ClientMissing",
|
||||
"SessionMissing",
|
||||
"Generic",
|
||||
};
|
||||
|
||||
interface HomeserverLoginDetails {
|
||||
string url();
|
||||
string? authentication_issuer();
|
||||
boolean supports_password_login();
|
||||
};
|
||||
|
||||
interface AuthenticationService {
|
||||
constructor(string base_path);
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
string homeserver();
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
string? authentication_issuer();
|
||||
|
||||
HomeserverLoginDetails? homeserver_details();
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
boolean supports_password_login();
|
||||
|
||||
void configure_homeserver(string server_name);
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
void use_server(string server_name);
|
||||
|
||||
Client login(string username, string password, string? initial_device_name, string? device_id);
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
Client login(string username, string password);
|
||||
Client restore_with_access_token(string token, string device_id);
|
||||
};
|
||||
|
||||
interface SessionVerificationEmoji {
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use futures_util::future::join3;
|
||||
use matrix_sdk::{
|
||||
ruma::{OwnedDeviceId, UserId},
|
||||
Session,
|
||||
};
|
||||
|
||||
use super::{client::Client, client_builder::ClientBuilder};
|
||||
use super::{client::Client, client_builder::ClientBuilder, RUNTIME};
|
||||
|
||||
pub struct AuthenticationService {
|
||||
base_path: String,
|
||||
client: RwLock<Option<Arc<Client>>>,
|
||||
homeserver_details: RwLock<Option<Arc<HomeserverLoginDetails>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AuthenticationError {
|
||||
#[error("A successful call to use_server must be made first.")]
|
||||
ClientMissing,
|
||||
#[error("Login was successful but is missing a valid Session to configure the file store.")]
|
||||
SessionMissing,
|
||||
#[error("An error occurred: {message}")]
|
||||
Generic { message: String },
|
||||
}
|
||||
@@ -23,52 +30,65 @@ impl From<anyhow::Error> for AuthenticationError {
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthenticationService {
|
||||
/// Creates a new service to authenticate a user with.
|
||||
pub fn new(base_path: String) -> Self {
|
||||
AuthenticationService { base_path, client: RwLock::new(None) }
|
||||
}
|
||||
pub struct HomeserverLoginDetails {
|
||||
url: String,
|
||||
authentication_issuer: Option<String>,
|
||||
supports_password_login: bool,
|
||||
}
|
||||
|
||||
/// The currently configured homeserver.
|
||||
pub fn homeserver(&self) -> Result<String, AuthenticationError> {
|
||||
self.client
|
||||
.read()
|
||||
.as_ref()
|
||||
.ok_or(AuthenticationError::ClientMissing)
|
||||
.map(|client| client.homeserver())
|
||||
impl HomeserverLoginDetails {
|
||||
/// The URL of the currently configured homeserver.
|
||||
pub fn url(&self) -> String {
|
||||
self.url.clone()
|
||||
}
|
||||
|
||||
/// The OIDC Provider that is trusted by the homeserver. `None` when
|
||||
/// not configured.
|
||||
pub fn authentication_issuer(&self) -> Result<Option<String>, AuthenticationError> {
|
||||
self.client
|
||||
.read()
|
||||
.as_ref()
|
||||
.ok_or(AuthenticationError::ClientMissing)
|
||||
.map(|client| client.authentication_issuer())
|
||||
pub fn authentication_issuer(&self) -> Option<String> {
|
||||
self.authentication_issuer.clone()
|
||||
}
|
||||
|
||||
/// Whether the current homeserver supports the password login flow.
|
||||
pub fn supports_password_login(&self) -> Result<bool, AuthenticationError> {
|
||||
self.client
|
||||
.read()
|
||||
.as_ref()
|
||||
.ok_or(AuthenticationError::ClientMissing)
|
||||
.and_then(|client| client.supports_password_login().map_err(AuthenticationError::from))
|
||||
pub fn supports_password_login(&self) -> bool {
|
||||
self.supports_password_login
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthenticationService {
|
||||
/// Creates a new service to authenticate a user with.
|
||||
pub fn new(base_path: String) -> Self {
|
||||
AuthenticationService {
|
||||
base_path,
|
||||
client: RwLock::new(None),
|
||||
homeserver_details: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the server to authenticate with the specified homeserver.
|
||||
pub fn use_server(&self, server_name: String) -> Result<(), AuthenticationError> {
|
||||
// Construct a username as the builder currently requires one.
|
||||
let username = format!("@auth:{}", server_name);
|
||||
let client = Arc::new(ClientBuilder::new())
|
||||
.base_path(self.base_path.clone())
|
||||
.username(username)
|
||||
.build()
|
||||
.map_err(AuthenticationError::from)?;
|
||||
pub fn homeserver_details(&self) -> Option<Arc<HomeserverLoginDetails>> {
|
||||
self.homeserver_details.read().unwrap().clone()
|
||||
}
|
||||
|
||||
*self.client.write() = Some(client);
|
||||
Ok(())
|
||||
/// Updates the service to authenticate with the homeserver for the
|
||||
/// specified address.
|
||||
pub fn configure_homeserver(&self, server_name: String) -> Result<(), AuthenticationError> {
|
||||
let mut builder = Arc::new(ClientBuilder::new()).base_path(self.base_path.clone());
|
||||
|
||||
if server_name.starts_with("http://") || server_name.starts_with("https://") {
|
||||
builder = builder.homeserver_url(server_name)
|
||||
} else {
|
||||
builder = builder.server_name(server_name);
|
||||
}
|
||||
|
||||
let client = builder.build().map_err(AuthenticationError::from)?;
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
let details = Arc::new(self.details_from_client(&client).await?);
|
||||
|
||||
*self.client.write().unwrap() = Some(client);
|
||||
*self.homeserver_details.write().unwrap() = Some(details);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Performs a password login using the current homeserver.
|
||||
@@ -76,25 +96,105 @@ impl AuthenticationService {
|
||||
&self,
|
||||
username: String,
|
||||
password: String,
|
||||
initial_device_name: Option<String>,
|
||||
device_id: Option<String>,
|
||||
) -> Result<Arc<Client>, AuthenticationError> {
|
||||
match self.client.read().as_ref() {
|
||||
Some(client) => {
|
||||
let homeserver_url = client.homeserver();
|
||||
let client = match &*self.client.read().unwrap() {
|
||||
Some(client) => client.clone(),
|
||||
None => return Err(AuthenticationError::ClientMissing),
|
||||
};
|
||||
|
||||
// Create a new client to setup the store path for the username
|
||||
let client = Arc::new(ClientBuilder::new())
|
||||
.base_path(self.base_path.clone())
|
||||
.homeserver_url(homeserver_url)
|
||||
.username(username.clone())
|
||||
.build()
|
||||
.map_err(AuthenticationError::from)?;
|
||||
// Login and ask the server for the full user ID as this could be different from
|
||||
// the username that was entered.
|
||||
client
|
||||
.login(username, password, initial_device_name, device_id)
|
||||
.map_err(AuthenticationError::from)?;
|
||||
let whoami = client.whoami()?;
|
||||
|
||||
client
|
||||
.login(username, password)
|
||||
.map(|_| client.clone())
|
||||
.map_err(AuthenticationError::from)
|
||||
}
|
||||
None => Err(AuthenticationError::ClientMissing),
|
||||
}
|
||||
// Create a new client to setup the store path now the user ID is known.
|
||||
let homeserver_url = client.homeserver();
|
||||
let session = client.session().ok_or(AuthenticationError::SessionMissing)?;
|
||||
let client = Arc::new(ClientBuilder::new())
|
||||
.base_path(self.base_path.clone())
|
||||
.homeserver_url(homeserver_url)
|
||||
.username(whoami.user_id.to_string())
|
||||
.build()
|
||||
.map_err(AuthenticationError::from)?;
|
||||
|
||||
// Restore the client using the session from the login request.
|
||||
client.restore_session(session).map_err(AuthenticationError::from)?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// Restore an existing session on the current homeserver using an access
|
||||
/// token issued by an authentication server.
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `token` - The access token issued by the authentication server.
|
||||
///
|
||||
/// * `device_id` - The device ID that the access token was scoped for.
|
||||
pub fn restore_with_access_token(
|
||||
&self,
|
||||
token: String,
|
||||
device_id: String,
|
||||
) -> Result<Arc<Client>, AuthenticationError> {
|
||||
let client = match &*self.client.read().unwrap() {
|
||||
Some(client) => client.clone(),
|
||||
None => return Err(AuthenticationError::ClientMissing),
|
||||
};
|
||||
|
||||
// Restore the client and ask the server for the full user ID as this
|
||||
// could be different from the username that was entered.
|
||||
let discovery_user_id = UserId::parse("@unknown:unknown")
|
||||
.map_err(|e| AuthenticationError::Generic { message: e.to_string() })?;
|
||||
let device_id: OwnedDeviceId = device_id.as_str().into();
|
||||
|
||||
let discovery_session = Session {
|
||||
access_token: token.clone(),
|
||||
refresh_token: None,
|
||||
user_id: discovery_user_id,
|
||||
device_id: device_id.clone(),
|
||||
};
|
||||
|
||||
client.restore_session(discovery_session).map_err(AuthenticationError::from)?;
|
||||
let whoami = client.whoami()?;
|
||||
|
||||
// Create the actual client with a store path from the user ID.
|
||||
let homeserver_url = client.homeserver();
|
||||
let session = Session {
|
||||
access_token: token,
|
||||
refresh_token: None,
|
||||
user_id: whoami.user_id.clone(),
|
||||
device_id,
|
||||
};
|
||||
let client = Arc::new(ClientBuilder::new())
|
||||
.base_path(self.base_path.clone())
|
||||
.homeserver_url(homeserver_url)
|
||||
.username(whoami.user_id.to_string())
|
||||
.build()
|
||||
.map_err(AuthenticationError::from)?;
|
||||
|
||||
// Restore the client using the session.
|
||||
client.restore_session(session).map_err(AuthenticationError::from)?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// Get the homeserver login details from a client.
|
||||
async fn details_from_client(
|
||||
&self,
|
||||
client: &Arc<Client>,
|
||||
) -> Result<HomeserverLoginDetails, AuthenticationError> {
|
||||
let login_details = join3(
|
||||
client.async_homeserver(),
|
||||
client.authentication_issuer(),
|
||||
client.supports_password_login(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let url = login_details.0;
|
||||
let authentication_issuer = login_details.1;
|
||||
let supports_password_login = login_details.2.map_err(AuthenticationError::from)?;
|
||||
|
||||
Ok(HomeserverLoginDetails { url, authentication_issuer, supports_password_login })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use core::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures_core::Stream;
|
||||
use matrix_sdk::{deserialized_responses::SyncRoomEvent, locks::Mutex, Result};
|
||||
use matrix_sdk::{deserialized_responses::SyncTimelineEvent, locks::Mutex, Result};
|
||||
use tokio_stream::StreamExt;
|
||||
use tracing::error;
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::{
|
||||
RUNTIME,
|
||||
};
|
||||
|
||||
type MsgStream = Pin<Box<dyn Stream<Item = Result<SyncRoomEvent>> + Send>>;
|
||||
type MsgStream = Pin<Box<dyn Stream<Item = Result<SyncTimelineEvent>> + Send>>;
|
||||
|
||||
pub struct BackwardsStream {
|
||||
stream: Arc<Mutex<MsgStream>>,
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use matrix_sdk::{
|
||||
config::SyncSettings,
|
||||
media::{MediaFormat, MediaRequest},
|
||||
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
|
||||
ruma::{
|
||||
api::client::{
|
||||
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
|
||||
session::get_login_types,
|
||||
sync::sync_events::v3::Filter,
|
||||
api::{
|
||||
client::{
|
||||
account::whoami,
|
||||
error::ErrorKind,
|
||||
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
|
||||
media::get_content_thumbnail::v3::Method,
|
||||
session::get_login_types,
|
||||
sync::sync_events::v3::Filter,
|
||||
},
|
||||
error::{FromHttpResponseError, ServerError},
|
||||
},
|
||||
events::room::MediaSource,
|
||||
TransactionId,
|
||||
serde::Raw,
|
||||
TransactionId, UInt,
|
||||
},
|
||||
Client as MatrixClient, LoopCtrl,
|
||||
Client as MatrixClient, Error, HttpError, LoopCtrl, RumaApiError, Session,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::{
|
||||
room::Room, session_verification::SessionVerificationController, ClientState, RestoreToken,
|
||||
@@ -30,11 +37,13 @@ impl std::ops::Deref for Client {
|
||||
|
||||
pub trait ClientDelegate: Sync + Send {
|
||||
fn did_receive_sync_update(&self);
|
||||
fn did_receive_auth_error(&self, is_soft_logout: bool);
|
||||
fn did_update_restore_token(&self);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Client {
|
||||
client: MatrixClient,
|
||||
pub(crate) client: MatrixClient,
|
||||
state: Arc<RwLock<ClientState>>,
|
||||
delegate: Arc<RwLock<Option<Box<dyn ClientDelegate>>>>,
|
||||
session_verification_controller:
|
||||
@@ -51,17 +60,40 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn login(&self, username: String, password: String) -> anyhow::Result<()> {
|
||||
/// Login using a username and password.
|
||||
pub fn login(
|
||||
&self,
|
||||
username: String,
|
||||
password: String,
|
||||
initial_device_name: Option<String>,
|
||||
device_id: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
self.client.login_username(&username, &password).send().await?;
|
||||
let mut builder = self.client.login_username(&username, &password);
|
||||
if let Some(initial_device_name) = initial_device_name.as_ref() {
|
||||
builder = builder.initial_device_display_name(initial_device_name);
|
||||
}
|
||||
if let Some(device_id) = device_id.as_ref() {
|
||||
builder = builder.device_id(device_id);
|
||||
}
|
||||
builder.send().await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Restores the client from a `RestoreToken`.
|
||||
pub fn restore_login(&self, restore_token: String) -> anyhow::Result<()> {
|
||||
let RestoreToken { session, homeurl: _, is_guest: _ } =
|
||||
let RestoreToken { session, homeurl: _, is_guest: _, is_soft_logout } =
|
||||
serde_json::from_str(&restore_token)?;
|
||||
|
||||
// update soft logout state
|
||||
self.state.write().unwrap().is_soft_logout = is_soft_logout;
|
||||
|
||||
self.restore_session(session)
|
||||
}
|
||||
|
||||
/// Restores the client from a `Session`.
|
||||
pub fn restore_session(&self, session: Session) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
self.client.restore_login(session).await?;
|
||||
Ok(())
|
||||
@@ -69,115 +101,108 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn set_delegate(&self, delegate: Option<Box<dyn ClientDelegate>>) {
|
||||
*self.delegate.write() = delegate;
|
||||
*self.delegate.write().unwrap() = delegate;
|
||||
}
|
||||
|
||||
/// The homeserver this client is configured to use.
|
||||
pub fn homeserver(&self) -> String {
|
||||
RUNTIME.block_on(async move { self.client.homeserver().await.to_string() })
|
||||
pub async fn async_homeserver(&self) -> String {
|
||||
self.client.homeserver().await.to_string()
|
||||
}
|
||||
|
||||
/// The OIDC Provider that is trusted by the homeserver. `None` when
|
||||
/// not configured.
|
||||
pub fn authentication_issuer(&self) -> Option<String> {
|
||||
RUNTIME.block_on(async move {
|
||||
self.client.authentication_issuer().await.map(|server| server.to_string())
|
||||
})
|
||||
pub async fn authentication_issuer(&self) -> Option<String> {
|
||||
self.client.authentication_issuer().await.map(|server| server.to_string())
|
||||
}
|
||||
|
||||
/// Whether or not the client's homeserver supports the password login flow.
|
||||
pub fn supports_password_login(&self) -> anyhow::Result<bool> {
|
||||
RUNTIME.block_on(async move {
|
||||
let login_types = self.client.get_login_types().await?;
|
||||
let supports_password = login_types.flows.iter().any(|login_type| {
|
||||
matches!(login_type, get_login_types::v3::LoginType::Password(_))
|
||||
});
|
||||
Ok(supports_password)
|
||||
})
|
||||
pub async fn supports_password_login(&self) -> anyhow::Result<bool> {
|
||||
let login_types = self.client.get_login_types().await?;
|
||||
let supports_password = login_types
|
||||
.flows
|
||||
.iter()
|
||||
.any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Password(_)));
|
||||
Ok(supports_password)
|
||||
}
|
||||
|
||||
pub fn start_sync(&self) {
|
||||
/// Gets information about the owner of a given access token.
|
||||
pub fn whoami(&self) -> anyhow::Result<whoami::v3::Response> {
|
||||
RUNTIME
|
||||
.block_on(async move { self.client.whoami().await.map_err(|e| anyhow!(e.to_string())) })
|
||||
}
|
||||
|
||||
pub fn start_sync(&self, timeline_limit: Option<u16>) {
|
||||
let client = self.client.clone();
|
||||
let state = self.state.clone();
|
||||
let delegate = self.delegate.clone();
|
||||
let session_verification_controller = self.session_verification_controller.clone();
|
||||
let local_self = self.clone();
|
||||
RUNTIME.spawn(async move {
|
||||
let mut filter = FilterDefinition::default();
|
||||
let mut room_filter = RoomFilter::default();
|
||||
let mut event_filter = RoomEventFilter::default();
|
||||
let mut timeline_filter = RoomEventFilter::default();
|
||||
|
||||
event_filter.lazy_load_options =
|
||||
LazyLoadOptions::Enabled { include_redundant_members: false };
|
||||
room_filter.state = event_filter;
|
||||
filter.room = room_filter;
|
||||
|
||||
timeline_filter.limit = timeline_limit.map(|limit| limit.into());
|
||||
filter.room.timeline = timeline_filter;
|
||||
|
||||
let filter_id = client.get_or_upload_filter("sync", filter).await.unwrap();
|
||||
|
||||
let sync_settings = SyncSettings::new().filter(Filter::FilterId(&filter_id));
|
||||
|
||||
client
|
||||
.sync_with_callback(sync_settings, |sync_response| async {
|
||||
if !state.read().has_first_synced {
|
||||
state.write().has_first_synced = true
|
||||
}
|
||||
.sync_with_result_callback(sync_settings, |result| async {
|
||||
Ok(if let Ok(sync_response) = result {
|
||||
if !state.read().unwrap().has_first_synced {
|
||||
state.write().unwrap().has_first_synced = true;
|
||||
}
|
||||
|
||||
if state.read().should_stop_syncing {
|
||||
state.write().is_syncing = false;
|
||||
return LoopCtrl::Break;
|
||||
} else if !state.read().is_syncing {
|
||||
state.write().is_syncing = true;
|
||||
}
|
||||
if state.read().unwrap().should_stop_syncing {
|
||||
state.write().unwrap().is_syncing = false;
|
||||
return Ok(LoopCtrl::Break);
|
||||
} else if !state.read().unwrap().is_syncing {
|
||||
state.write().unwrap().is_syncing = true;
|
||||
}
|
||||
|
||||
if let Some(delegate) = &*delegate.read() {
|
||||
delegate.did_receive_sync_update()
|
||||
}
|
||||
if let Some(delegate) = &*delegate.read().unwrap() {
|
||||
delegate.did_receive_sync_update()
|
||||
}
|
||||
|
||||
if let Some(session_verification_controller) =
|
||||
&*session_verification_controller.read().await
|
||||
{
|
||||
session_verification_controller
|
||||
.process_to_device_messages(sync_response.to_device)
|
||||
.await;
|
||||
}
|
||||
if let Some(session_verification_controller) =
|
||||
&*session_verification_controller.read().await
|
||||
{
|
||||
session_verification_controller
|
||||
.process_to_device_messages(sync_response.to_device)
|
||||
.await;
|
||||
}
|
||||
|
||||
LoopCtrl::Continue
|
||||
LoopCtrl::Continue
|
||||
} else {
|
||||
local_self.process_sync_error(result.err().unwrap())
|
||||
})
|
||||
})
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
/// Indication whether we've received a first sync response since
|
||||
/// establishing the client (in memory)
|
||||
pub fn has_first_synced(&self) -> bool {
|
||||
self.state.read().has_first_synced
|
||||
}
|
||||
|
||||
/// Indication whether we are currently syncing
|
||||
pub fn is_syncing(&self) -> bool {
|
||||
self.state.read().has_first_synced
|
||||
}
|
||||
|
||||
/// Is this a guest account?
|
||||
pub fn is_guest(&self) -> bool {
|
||||
self.state.read().is_guest
|
||||
}
|
||||
|
||||
pub fn restore_token(&self) -> anyhow::Result<String> {
|
||||
RUNTIME.block_on(async move {
|
||||
let session = self.client.session().expect("Missing session").clone();
|
||||
let session = self.client.session().expect("Missing session");
|
||||
let homeurl = self.client.homeserver().await.into();
|
||||
Ok(serde_json::to_string(&RestoreToken {
|
||||
session,
|
||||
homeurl,
|
||||
is_guest: self.state.read().is_guest,
|
||||
is_guest: self.state.read().unwrap().is_guest,
|
||||
is_soft_logout: self.state.read().unwrap().is_soft_logout,
|
||||
})?)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn rooms(&self) -> Vec<Arc<Room>> {
|
||||
self.client.rooms().into_iter().map(|room| Arc::new(Room::new(room))).collect()
|
||||
}
|
||||
|
||||
pub fn user_id(&self) -> anyhow::Result<String> {
|
||||
let user_id = self.client.user_id().expect("No User ID found");
|
||||
Ok(user_id.to_string())
|
||||
@@ -204,12 +229,61 @@ impl Client {
|
||||
Ok(device_id.to_string())
|
||||
}
|
||||
|
||||
/// Get the content of the event of the given type out of the account data
|
||||
/// store.
|
||||
///
|
||||
/// It will be returned as a JSON string.
|
||||
pub fn account_data(&self, event_type: String) -> anyhow::Result<Option<String>> {
|
||||
RUNTIME.block_on(async move {
|
||||
let event = self.client.account().account_data_raw(event_type.into()).await?;
|
||||
Ok(event.map(|e| e.json().get().to_owned()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the given account data content for the given event type.
|
||||
///
|
||||
/// It should be supplied as a JSON string.
|
||||
pub fn set_account_data(&self, event_type: String, content: String) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
let raw_content = Raw::from_json_string(content)?;
|
||||
self.client.account().set_account_data_raw(event_type.into(), raw_content).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_media_content(&self, media_source: Arc<MediaSource>) -> anyhow::Result<Vec<u8>> {
|
||||
let l = self.client.clone();
|
||||
let source = (*media_source).clone();
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
Ok(l.get_media_content(&MediaRequest { source, format: MediaFormat::File }, true)
|
||||
Ok(l.media()
|
||||
.get_media_content(&MediaRequest { source, format: MediaFormat::File }, true)
|
||||
.await?)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_media_thumbnail(
|
||||
&self,
|
||||
media_source: Arc<MediaSource>,
|
||||
width: u64,
|
||||
height: u64,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
let l = self.client.clone();
|
||||
let source = (*media_source).clone();
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
Ok(l.media()
|
||||
.get_media_content(
|
||||
&MediaRequest {
|
||||
source,
|
||||
format: MediaFormat::Thumbnail(MediaThumbnailSize {
|
||||
method: Method::Scale,
|
||||
width: UInt::new(width).unwrap(),
|
||||
height: UInt::new(height).unwrap(),
|
||||
}),
|
||||
},
|
||||
true,
|
||||
)
|
||||
.await?)
|
||||
})
|
||||
}
|
||||
@@ -240,8 +314,71 @@ impl Client {
|
||||
Ok(Arc::new(session_verification_controller))
|
||||
})
|
||||
}
|
||||
|
||||
/// Log out the current user
|
||||
pub fn logout(&self) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
match self.client.logout().await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(error) => Err(anyhow!(error.to_string())),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Process a sync error and return loop control accordingly
|
||||
fn process_sync_error(&self, sync_error: Error) -> LoopCtrl {
|
||||
let mut control = LoopCtrl::Continue;
|
||||
if let Error::Http(HttpError::Api(FromHttpResponseError::Server(ServerError::Known(
|
||||
RumaApiError::ClientApi(error),
|
||||
)))) = sync_error
|
||||
{
|
||||
if let ErrorKind::UnknownToken { soft_logout } = error.kind {
|
||||
self.state.write().unwrap().is_soft_logout = soft_logout;
|
||||
if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
delegate.did_update_restore_token();
|
||||
delegate.did_receive_auth_error(soft_logout);
|
||||
}
|
||||
control = LoopCtrl::Break
|
||||
}
|
||||
}
|
||||
control
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_transaction_id() -> String {
|
||||
#[uniffi::export]
|
||||
impl Client {
|
||||
/// The homeserver this client is configured to use.
|
||||
pub fn homeserver(&self) -> String {
|
||||
RUNTIME.block_on(async move { self.async_homeserver().await })
|
||||
}
|
||||
|
||||
/// Indication whether we've received a first sync response since
|
||||
/// establishing the client (in memory)
|
||||
pub fn has_first_synced(&self) -> bool {
|
||||
self.state.read().unwrap().has_first_synced
|
||||
}
|
||||
|
||||
/// Indication whether we are currently syncing
|
||||
pub fn is_syncing(&self) -> bool {
|
||||
self.state.read().unwrap().has_first_synced
|
||||
}
|
||||
|
||||
/// Is this a guest account?
|
||||
pub fn is_guest(&self) -> bool {
|
||||
self.state.read().unwrap().is_guest
|
||||
}
|
||||
|
||||
/// Flag indicating whether the session is in soft logout mode
|
||||
pub fn is_soft_logout(&self) -> bool {
|
||||
self.state.read().unwrap().is_soft_logout
|
||||
}
|
||||
|
||||
pub fn rooms(&self) -> Vec<Arc<Room>> {
|
||||
self.client.rooms().into_iter().map(|room| Arc::new(Room::new(room))).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
fn gen_transaction_id() -> String {
|
||||
TransactionId::new().to_string()
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
use std::{fs, path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::anyhow;
|
||||
use matrix_sdk::{
|
||||
ruma::UserId, store::make_store_config, Client as MatrixClient,
|
||||
ClientBuilder as MatrixClientBuilder,
|
||||
ruma::{ServerName, UserId},
|
||||
store::make_store_config,
|
||||
Client as MatrixClient, ClientBuilder as MatrixClientBuilder,
|
||||
};
|
||||
use sanitize_filename_reader_friendly::sanitize;
|
||||
|
||||
use super::{client::Client, ClientState, RUNTIME};
|
||||
use crate::helpers::unwrap_or_clone_arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientBuilder {
|
||||
base_path: Option<String>,
|
||||
username: Option<String>,
|
||||
server_name: Option<String>,
|
||||
homeserver_url: Option<String>,
|
||||
inner: MatrixClientBuilder,
|
||||
}
|
||||
@@ -22,6 +25,7 @@ impl ClientBuilder {
|
||||
Self {
|
||||
base_path: None,
|
||||
username: None,
|
||||
server_name: None,
|
||||
homeserver_url: None,
|
||||
inner: MatrixClient::builder().user_agent("rust-sdk-ios"),
|
||||
}
|
||||
@@ -39,6 +43,12 @@ impl ClientBuilder {
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn server_name(self: Arc<Self>, server_name: String) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.server_name = Some(server_name);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn homeserver_url(self: Arc<Self>, url: String) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.homeserver_url = Some(url);
|
||||
@@ -47,25 +57,30 @@ impl ClientBuilder {
|
||||
|
||||
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<Client>> {
|
||||
let builder = unwrap_or_clone_arc(self);
|
||||
let mut inner_builder = builder.inner;
|
||||
|
||||
let base_path = builder.base_path.context("Base path was not set")?;
|
||||
let username = builder
|
||||
.username
|
||||
.context("Username to determine homeserver and home path was not set")?;
|
||||
if let (Some(base_path), Some(username)) = (builder.base_path, &builder.username) {
|
||||
// Determine store path
|
||||
let data_path = PathBuf::from(base_path).join(sanitize(username));
|
||||
fs::create_dir_all(&data_path)?;
|
||||
let store_config = make_store_config(&data_path, None)?;
|
||||
|
||||
// Determine store path
|
||||
let data_path = PathBuf::from(base_path).join(sanitize(&username));
|
||||
fs::create_dir_all(&data_path)?;
|
||||
let store_config = make_store_config(&data_path, None)?;
|
||||
inner_builder = inner_builder.store_config(store_config);
|
||||
}
|
||||
|
||||
let mut inner_builder = builder.inner.store_config(store_config);
|
||||
|
||||
// Determine server either from explicitly set homeserver or from userId
|
||||
// Determine server either from URL, server name or user ID.
|
||||
if let Some(homeserver_url) = builder.homeserver_url {
|
||||
inner_builder = inner_builder.homeserver_url(homeserver_url);
|
||||
} else {
|
||||
} else if let Some(server_name) = builder.server_name {
|
||||
let server_name = ServerName::parse(server_name)?;
|
||||
inner_builder = inner_builder.server_name(&server_name);
|
||||
} else if let Some(username) = builder.username {
|
||||
let user = UserId::parse(username)?;
|
||||
inner_builder = inner_builder.server_name(user.server_name());
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"Failed to build: One of homeserver_url, server_name or username must be called."
|
||||
));
|
||||
}
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -81,7 +96,3 @@ impl Default for ClientBuilder {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_or_clone_arc<T: Clone>(arc: Arc<T>) -> T {
|
||||
Arc::try_unwrap(arc).unwrap_or_else(|x| (*x).clone())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) fn unwrap_or_clone_arc<T: Clone>(arc: Arc<T>) -> T {
|
||||
Arc::try_unwrap(arc).unwrap_or_else(|x| (*x).clone())
|
||||
}
|
||||
@@ -6,17 +6,22 @@ pub mod authentication_service;
|
||||
pub mod backward_stream;
|
||||
pub mod client;
|
||||
pub mod client_builder;
|
||||
mod helpers;
|
||||
pub mod messages;
|
||||
pub mod room;
|
||||
pub mod session_verification;
|
||||
pub mod sliding_sync;
|
||||
mod uniffi_api;
|
||||
|
||||
use std::io;
|
||||
|
||||
use client::Client;
|
||||
use client_builder::ClientBuilder;
|
||||
use matrix_sdk::Session;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::runtime::Runtime;
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
pub use uniffi_api::*;
|
||||
|
||||
pub static RUNTIME: Lazy<Runtime> =
|
||||
@@ -26,7 +31,7 @@ pub use matrix_sdk::ruma::{api::client::account::register, UserId};
|
||||
|
||||
pub use self::{
|
||||
authentication_service::*, backward_stream::*, client::*, messages::*, room::*,
|
||||
session_verification::*,
|
||||
session_verification::*, sliding_sync::*,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -35,6 +40,7 @@ pub struct ClientState {
|
||||
has_first_synced: bool,
|
||||
is_syncing: bool,
|
||||
should_stop_syncing: bool,
|
||||
is_soft_logout: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -42,6 +48,8 @@ struct RestoreToken {
|
||||
is_guest: bool,
|
||||
homeurl: String,
|
||||
session: Session,
|
||||
#[serde(default)]
|
||||
is_soft_logout: bool,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
@@ -55,3 +63,15 @@ impl From<anyhow::Error> for ClientError {
|
||||
ClientError::Generic { msg: e.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
fn setup_tracing(configuration: String) {
|
||||
tracing_subscriber::registry()
|
||||
.with(EnvFilter::new(configuration))
|
||||
.with(fmt::layer().with_ansi(false).with_writer(io::stderr))
|
||||
.init();
|
||||
}
|
||||
|
||||
mod uniffi_types {
|
||||
pub use matrix_sdk::ruma::events::room::{message::RoomMessageEventContent, MediaSource};
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use extension_trait::extension_trait;
|
||||
pub use matrix_sdk::ruma::events::room::{
|
||||
message::RoomMessageEventContent as MessageEventContent, MediaSource,
|
||||
};
|
||||
pub use matrix_sdk::ruma::events::room::{message::RoomMessageEventContent, MediaSource};
|
||||
use matrix_sdk::{
|
||||
deserialized_responses::SyncRoomEvent,
|
||||
deserialized_responses::SyncTimelineEvent,
|
||||
ruma::events::{
|
||||
room::{
|
||||
message::{ImageMessageEventContent, MessageFormat, MessageType},
|
||||
ImageInfo,
|
||||
},
|
||||
AnySyncMessageLikeEvent, AnySyncRoomEvent, SyncMessageLikeEvent,
|
||||
AnySyncMessageLikeEvent, AnySyncTimelineEvent, SyncMessageLikeEvent,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -144,9 +142,9 @@ impl AnyMessage {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_event_to_message(sync_event: SyncRoomEvent) -> Option<Arc<AnyMessage>> {
|
||||
pub fn sync_event_to_message(sync_event: SyncTimelineEvent) -> Option<Arc<AnyMessage>> {
|
||||
match sync_event.event.deserialize() {
|
||||
Ok(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
|
||||
Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
|
||||
SyncMessageLikeEvent::Original(m),
|
||||
))) => {
|
||||
let base_message = Arc::new(BaseMessage {
|
||||
@@ -235,12 +233,14 @@ pub fn sync_event_to_message(sync_event: SyncRoomEvent) -> Option<Arc<AnyMessage
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn media_source_from_url(url: String) -> Arc<MediaSource> {
|
||||
Arc::new(MediaSource::Plain(url.into()))
|
||||
}
|
||||
|
||||
pub fn message_event_content_from_markdown(md: String) -> Arc<MessageEventContent> {
|
||||
Arc::new(MessageEventContent::text_markdown(md))
|
||||
#[uniffi::export]
|
||||
pub fn message_event_content_from_markdown(md: String) -> Arc<RoomMessageEventContent> {
|
||||
Arc::new(RoomMessageEventContent::text_markdown(md))
|
||||
}
|
||||
|
||||
#[extension_trait]
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use matrix_sdk::{
|
||||
room::Room as MatrixRoom,
|
||||
ruma::{events::room::message::RoomMessageEventContent, UserId},
|
||||
ruma::{
|
||||
events::room::message::{RoomMessageEvent, RoomMessageEventContent},
|
||||
EventId, UserId,
|
||||
},
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::{
|
||||
backward_stream::BackwardsStream,
|
||||
@@ -18,6 +23,12 @@ pub trait RoomDelegate: Sync + Send {
|
||||
fn did_receive_message(&self, messages: Arc<AnyMessage>);
|
||||
}
|
||||
|
||||
pub enum Membership {
|
||||
Invited,
|
||||
Joined,
|
||||
Left,
|
||||
}
|
||||
|
||||
pub struct Room {
|
||||
room: MatrixRoom,
|
||||
delegate: Arc<RwLock<Option<Box<dyn RoomDelegate>>>>,
|
||||
@@ -34,7 +45,7 @@ impl Room {
|
||||
}
|
||||
|
||||
pub fn set_delegate(&self, delegate: Option<Box<dyn RoomDelegate>>) {
|
||||
*self.delegate.write() = delegate;
|
||||
*self.delegate.write().unwrap() = delegate;
|
||||
}
|
||||
|
||||
pub fn id(&self) -> String {
|
||||
@@ -80,6 +91,14 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn membership(&self) -> Membership {
|
||||
match &self.room {
|
||||
MatrixRoom::Invited(_) => Membership::Invited,
|
||||
MatrixRoom::Joined(_) => Membership::Joined,
|
||||
MatrixRoom::Left(_) => Membership::Left,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_direct(&self) -> bool {
|
||||
self.room.is_direct()
|
||||
}
|
||||
@@ -101,11 +120,11 @@ impl Room {
|
||||
}
|
||||
|
||||
pub fn start_live_event_listener(&self) -> Option<Arc<BackwardsStream>> {
|
||||
if *self.is_listening_to_live_events.read() {
|
||||
if *self.is_listening_to_live_events.read().unwrap() {
|
||||
return None;
|
||||
}
|
||||
|
||||
*self.is_listening_to_live_events.write() = true;
|
||||
*self.is_listening_to_live_events.write().unwrap() = true;
|
||||
|
||||
let room = self.room.clone();
|
||||
let delegate = self.delegate.clone();
|
||||
@@ -119,11 +138,11 @@ impl Room {
|
||||
pin_mut!(forward_stream);
|
||||
|
||||
while let Some(sync_event) = forward_stream.next().await {
|
||||
if !(*is_listening_to_live_events.read()) {
|
||||
if !(*is_listening_to_live_events.read().unwrap()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(delegate) = &*delegate.read() {
|
||||
if let Some(delegate) = &*delegate.read().unwrap() {
|
||||
if let Some(message) = sync_event_to_message(sync_event) {
|
||||
delegate.did_receive_message(message)
|
||||
}
|
||||
@@ -134,7 +153,7 @@ impl Room {
|
||||
}
|
||||
|
||||
pub fn stop_live_event_listener(&self) {
|
||||
*self.is_listening_to_live_events.write() = false;
|
||||
*self.is_listening_to_live_events.write().unwrap() = false;
|
||||
}
|
||||
|
||||
pub fn send(&self, msg: Arc<RoomMessageEventContent>, txn_id: Option<String>) -> Result<()> {
|
||||
@@ -144,10 +163,71 @@ impl Room {
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
room.send((*msg).to_owned(), txn_id.as_deref().map(Into::into)).await
|
||||
})?;
|
||||
room.send((*msg).to_owned(), txn_id.as_deref().map(Into::into)).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn send_reply(
|
||||
&self,
|
||||
msg: String,
|
||||
in_reply_to_event_id: String,
|
||||
txn_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
let room = match &self.room {
|
||||
MatrixRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't send to a room that isn't in joined state"),
|
||||
};
|
||||
|
||||
let event_id: &EventId =
|
||||
in_reply_to_event_id.as_str().try_into().context("Failed to create EventId.")?;
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
let timeline_event = room.event(event_id).await.context("Couldn't find event.")?;
|
||||
|
||||
let event_content = timeline_event
|
||||
.event
|
||||
.deserialize_as::<RoomMessageEvent>()
|
||||
.context("Couldn't deserialise event")?;
|
||||
|
||||
let original_message =
|
||||
event_content.as_original().context("Couldn't retrieve original message.")?;
|
||||
|
||||
let reply_content =
|
||||
RoomMessageEventContent::text_markdown(msg).make_reply_to(original_message);
|
||||
|
||||
room.send(reply_content, txn_id.as_deref().map(Into::into)).await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Redacts an event from the room.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event_id` - The ID of the event to redact
|
||||
///
|
||||
/// * `reason` - The reason for the event being redacted (optional).
|
||||
///
|
||||
/// * `txn_id` - A unique ID that can be attached to this event as
|
||||
/// its transaction ID (optional). If not given one is created.
|
||||
pub fn redact(
|
||||
&self,
|
||||
event_id: String,
|
||||
reason: Option<String>,
|
||||
txn_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
let room = match &self.room {
|
||||
MatrixRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't redact in a room that isn't in joined state"),
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
let event_id = EventId::parse(event_id)?;
|
||||
room.redact(&event_id, reason.as_deref(), txn_id.map(Into::into)).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use matrix_sdk::{
|
||||
encryption::{
|
||||
@@ -10,7 +10,6 @@ use matrix_sdk::{
|
||||
events::{key::verification::VerificationMethod, AnyToDeviceEvent},
|
||||
},
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::RUNTIME;
|
||||
|
||||
@@ -55,11 +54,11 @@ impl SessionVerificationController {
|
||||
}
|
||||
|
||||
pub fn set_delegate(&self, delegate: Option<Box<dyn SessionVerificationControllerDelegate>>) {
|
||||
*self.delegate.write() = delegate;
|
||||
*self.delegate.write().unwrap() = delegate;
|
||||
}
|
||||
|
||||
pub fn is_verified(&self) -> bool {
|
||||
self.user_identity.verified()
|
||||
self.user_identity.is_verified()
|
||||
}
|
||||
|
||||
pub fn request_verification(&self) -> anyhow::Result<()> {
|
||||
@@ -67,7 +66,7 @@ impl SessionVerificationController {
|
||||
let methods = vec![VerificationMethod::SasV1];
|
||||
let verification_request =
|
||||
self.user_identity.request_verification_with_methods(methods).await?;
|
||||
*self.verification_request.write() = Some(verification_request);
|
||||
*self.verification_request.write().unwrap() = Some(verification_request);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
@@ -75,7 +74,7 @@ impl SessionVerificationController {
|
||||
|
||||
pub fn approve_verification(&self) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
let sas_verification = self.sas_verification.read().clone();
|
||||
let sas_verification = self.sas_verification.read().unwrap().clone();
|
||||
if let Some(sas_verification) = sas_verification {
|
||||
sas_verification.confirm().await?;
|
||||
}
|
||||
@@ -86,7 +85,7 @@ impl SessionVerificationController {
|
||||
|
||||
pub fn decline_verification(&self) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
let sas_verification = self.sas_verification.read().clone();
|
||||
let sas_verification = self.sas_verification.read().unwrap().clone();
|
||||
if let Some(sas_verification) = sas_verification {
|
||||
sas_verification.mismatch().await?;
|
||||
}
|
||||
@@ -97,7 +96,7 @@ impl SessionVerificationController {
|
||||
|
||||
pub fn cancel_verification(&self) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
let verification_request = self.verification_request.read().clone();
|
||||
let verification_request = self.verification_request.read().unwrap().clone();
|
||||
if let Some(verification) = verification_request {
|
||||
verification.cancel().await?;
|
||||
}
|
||||
@@ -122,7 +121,7 @@ impl SessionVerificationController {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(delegate) = &*self.delegate.read() {
|
||||
if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
delegate.did_cancel()
|
||||
}
|
||||
}
|
||||
@@ -131,9 +130,9 @@ impl SessionVerificationController {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(sas_verification) = &*sas_verification.read() {
|
||||
if let Some(sas_verification) = &*sas_verification.read().unwrap() {
|
||||
if let Some(emojis) = sas_verification.emoji() {
|
||||
if let Some(delegate) = &*self.delegate.read() {
|
||||
if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
let emojis = emojis
|
||||
.iter()
|
||||
.map(|e| {
|
||||
@@ -146,10 +145,10 @@ impl SessionVerificationController {
|
||||
|
||||
delegate.did_receive_verification_data(emojis);
|
||||
}
|
||||
} else if let Some(delegate) = &*self.delegate.read() {
|
||||
} else if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
delegate.did_fail()
|
||||
}
|
||||
} else if let Some(delegate) = &*self.delegate.read() {
|
||||
} else if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
delegate.did_fail()
|
||||
}
|
||||
}
|
||||
@@ -158,7 +157,7 @@ impl SessionVerificationController {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(delegate) = &*self.delegate.read() {
|
||||
if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
delegate.did_finish()
|
||||
}
|
||||
}
|
||||
@@ -168,7 +167,7 @@ impl SessionVerificationController {
|
||||
}
|
||||
|
||||
fn is_transaction_id_valid(&self, transaction_id: String) -> bool {
|
||||
if let Some(verification) = &*self.verification_request.read() {
|
||||
if let Some(verification) = &*self.verification_request.read().unwrap() {
|
||||
return verification.flow_id() == transaction_id;
|
||||
}
|
||||
|
||||
@@ -176,14 +175,14 @@ impl SessionVerificationController {
|
||||
}
|
||||
|
||||
async fn start_sas_verification(&self) {
|
||||
let verification_request = self.verification_request.read().clone();
|
||||
let verification_request = self.verification_request.read().unwrap().clone();
|
||||
if let Some(verification) = verification_request {
|
||||
match verification.start_sas().await {
|
||||
Ok(verification) => {
|
||||
*self.sas_verification.write() = verification;
|
||||
*self.sas_verification.write().unwrap() = verification;
|
||||
}
|
||||
Err(_) => {
|
||||
if let Some(delegate) = &*self.delegate.read() {
|
||||
if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
delegate.did_fail()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,582 @@
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use futures_signals::{
|
||||
signal::SignalExt,
|
||||
signal_vec::{SignalVecExt, VecDiff},
|
||||
};
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use matrix_sdk::ruma::{
|
||||
api::client::sync::sync_events::{
|
||||
v4::RoomSubscription as RumaRoomSubscription,
|
||||
UnreadNotificationsCount as RumaUnreadNotificationsCount,
|
||||
},
|
||||
assign, IdParseError, OwnedRoomId,
|
||||
};
|
||||
pub use matrix_sdk::{
|
||||
Client as MatrixClient, RoomListEntry as MatrixRoomEntry,
|
||||
SlidingSyncBuilder as MatrixSlidingSyncBuilder, SlidingSyncMode, SlidingSyncState,
|
||||
};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::{Client, RUNTIME};
|
||||
use crate::helpers::unwrap_or_clone_arc;
|
||||
|
||||
pub struct StoppableSpawn {
|
||||
handle: Arc<RwLock<Option<JoinHandle<()>>>>,
|
||||
}
|
||||
|
||||
impl StoppableSpawn {
|
||||
fn with_handle(handle: JoinHandle<()>) -> StoppableSpawn {
|
||||
StoppableSpawn { handle: Arc::new(RwLock::new(Some(handle))) }
|
||||
}
|
||||
|
||||
fn with_handle_ref(handle: Arc<RwLock<Option<JoinHandle<()>>>>) -> StoppableSpawn {
|
||||
StoppableSpawn { handle }
|
||||
}
|
||||
|
||||
pub fn cancel(&self) {
|
||||
if let Some(handle) = self.handle.write().unwrap().take() {
|
||||
handle.abort();
|
||||
}
|
||||
}
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.handle.read().unwrap().is_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UnreadNotificationsCount {
|
||||
highlight_count: u32,
|
||||
notification_count: u32,
|
||||
}
|
||||
|
||||
impl UnreadNotificationsCount {
|
||||
pub fn highlight_count(&self) -> u32 {
|
||||
self.highlight_count
|
||||
}
|
||||
pub fn notification_count(&self) -> u32 {
|
||||
self.notification_count
|
||||
}
|
||||
pub fn has_notifications(&self) -> bool {
|
||||
self.notification_count != 0 || self.highlight_count != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
|
||||
fn from(inner: RumaUnreadNotificationsCount) -> Self {
|
||||
UnreadNotificationsCount {
|
||||
highlight_count: inner
|
||||
.highlight_count
|
||||
.and_then(|x| x.try_into().ok())
|
||||
.unwrap_or_default(),
|
||||
notification_count: inner
|
||||
.notification_count
|
||||
.and_then(|x| x.try_into().ok())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SlidingSyncRoom {
|
||||
inner: matrix_sdk::SlidingSyncRoom,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl SlidingSyncRoom {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
self.inner.name().map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub fn room_id(&self) -> String {
|
||||
self.inner.room_id().to_string()
|
||||
}
|
||||
|
||||
pub fn is_dm(&self) -> Option<bool> {
|
||||
self.inner.is_dm
|
||||
}
|
||||
|
||||
pub fn is_initial(&self) -> Option<bool> {
|
||||
self.inner.initial
|
||||
}
|
||||
pub fn is_loading_more(&self) -> bool {
|
||||
self.inner.is_loading_more()
|
||||
}
|
||||
|
||||
pub fn has_unread_notifications(&self) -> bool {
|
||||
!self.inner.unread_notifications.is_empty()
|
||||
}
|
||||
|
||||
pub fn unread_notifications(&self) -> Arc<UnreadNotificationsCount> {
|
||||
Arc::new(self.inner.unread_notifications.clone().into())
|
||||
}
|
||||
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
pub fn latest_room_message(&self) -> Option<Arc<crate::messages::AnyMessage>> {
|
||||
let messages = self.inner.timeline();
|
||||
// room is having the latest events at the end,
|
||||
let lock = messages.lock_ref();
|
||||
for m in lock.iter().rev() {
|
||||
if let Some(e) = crate::messages::sync_event_to_message(m.clone().into()) {
|
||||
return Some(e);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn full_room(&self) -> Option<Arc<super::Room>> {
|
||||
self.client.get_room(self.inner.room_id()).map(|room| Arc::new(super::Room::new(room)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UpdateSummary {
|
||||
/// The views (according to their name), which have seen an update
|
||||
pub views: Vec<String>,
|
||||
pub rooms: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct RequiredState {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
pub struct RoomSubscription {
|
||||
pub required_state: Option<Vec<RequiredState>>,
|
||||
pub timeline_limit: Option<u32>,
|
||||
}
|
||||
|
||||
impl TryInto<RumaRoomSubscription> for RoomSubscription {
|
||||
type Error = anyhow::Error;
|
||||
fn try_into(self) -> anyhow::Result<RumaRoomSubscription> {
|
||||
Ok(assign!(RumaRoomSubscription::default(), {
|
||||
required_state: self.required_state.map(|r|
|
||||
r.into_iter().map(|s| (s.key.into(), s.value)).collect()
|
||||
).unwrap_or_default(),
|
||||
timeline_limit: self.timeline_limit.map(|u| u.into())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<matrix_sdk::UpdateSummary> for UpdateSummary {
|
||||
fn from(other: matrix_sdk::UpdateSummary) -> UpdateSummary {
|
||||
UpdateSummary {
|
||||
views: other.views,
|
||||
rooms: other.rooms.into_iter().map(|r| r.as_str().to_owned()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SlidingSyncViewRoomsListDiff {
|
||||
Replace { values: Vec<RoomListEntry> },
|
||||
InsertAt { index: u32, value: RoomListEntry },
|
||||
UpdateAt { index: u32, value: RoomListEntry },
|
||||
RemoveAt { index: u32 },
|
||||
Move { old_index: u32, new_index: u32 },
|
||||
Push { value: RoomListEntry },
|
||||
}
|
||||
|
||||
impl From<VecDiff<MatrixRoomEntry>> for SlidingSyncViewRoomsListDiff {
|
||||
fn from(other: VecDiff<MatrixRoomEntry>) -> Self {
|
||||
match other {
|
||||
VecDiff::Replace { values } => SlidingSyncViewRoomsListDiff::Replace {
|
||||
values: values.into_iter().map(|e| (&e).into()).collect(),
|
||||
},
|
||||
VecDiff::InsertAt { index, value } => SlidingSyncViewRoomsListDiff::InsertAt {
|
||||
index: index as u32,
|
||||
value: (&value).into(),
|
||||
},
|
||||
VecDiff::UpdateAt { index, value } => SlidingSyncViewRoomsListDiff::UpdateAt {
|
||||
index: index as u32,
|
||||
value: (&value).into(),
|
||||
},
|
||||
VecDiff::RemoveAt { index } => {
|
||||
SlidingSyncViewRoomsListDiff::RemoveAt { index: index as u32 }
|
||||
}
|
||||
VecDiff::Move { old_index, new_index } => SlidingSyncViewRoomsListDiff::Move {
|
||||
old_index: old_index as u32,
|
||||
new_index: new_index as u32,
|
||||
},
|
||||
VecDiff::Push { value } => {
|
||||
SlidingSyncViewRoomsListDiff::Push { value: (&value).into() }
|
||||
}
|
||||
_ => unimplemented!("Clear and Pop aren't provided within sliding sync"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RoomListEntry {
|
||||
Empty,
|
||||
Invalidated { room_id: String },
|
||||
Filled { room_id: String },
|
||||
}
|
||||
|
||||
impl From<&MatrixRoomEntry> for RoomListEntry {
|
||||
fn from(other: &MatrixRoomEntry) -> Self {
|
||||
match other {
|
||||
MatrixRoomEntry::Empty => RoomListEntry::Empty,
|
||||
MatrixRoomEntry::Filled(b) => RoomListEntry::Filled { room_id: b.to_string() },
|
||||
MatrixRoomEntry::Invalidated(b) => {
|
||||
RoomListEntry::Invalidated { room_id: b.to_string() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub trait SlidingSyncViewRoomItemsObserver: Sync + Send {
|
||||
fn did_receive_update(&self);
|
||||
}
|
||||
|
||||
pub trait SlidingSyncViewRoomListObserver: Sync + Send {
|
||||
fn did_receive_update(&self, diff: SlidingSyncViewRoomsListDiff);
|
||||
}
|
||||
|
||||
pub trait SlidingSyncViewRoomsCountObserver: Sync + Send {
|
||||
fn did_receive_update(&self, new_count: u32);
|
||||
}
|
||||
|
||||
pub trait SlidingSyncViewStateObserver: Sync + Send {
|
||||
fn did_receive_update(&self, new_state: SlidingSyncState);
|
||||
}
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SlidingSyncViewBuilder {
|
||||
inner: matrix_sdk::SlidingSyncViewBuilder,
|
||||
}
|
||||
|
||||
impl SlidingSyncViewBuilder {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn sync_mode(self: Arc<Self>, mode: SlidingSyncMode) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.sync_mode(mode);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn sort(self: Arc<Self>, sort: Vec<String>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.sort(sort);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn required_state(self: Arc<Self>, required_state: Vec<RequiredState>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder
|
||||
.inner
|
||||
.required_state(required_state.into_iter().map(|s| (s.key.into(), s.value)).collect());
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn batch_size(self: Arc<Self>, batch_size: u32) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.batch_size(batch_size);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn timeline_limit(self: Arc<Self>, limit: u32) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.timeline_limit(limit);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn no_timeline_limit(self: Arc<Self>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.no_timeline_limit();
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn name(self: Arc<Self>, name: String) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.name(name);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn ranges(self: Arc<Self>, ranges: Vec<(u32, u32)>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.ranges(ranges);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn add_range(self: Arc<Self>, from: u32, to: u32) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.add_range(from, to);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn reset_ranges(self: Arc<Self>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.reset_ranges();
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSyncView>> {
|
||||
let builder = unwrap_or_clone_arc(self);
|
||||
Ok(Arc::new(builder.inner.build()?.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SlidingSyncView {
|
||||
inner: matrix_sdk::SlidingSyncView,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk::SlidingSyncView> for SlidingSyncView {
|
||||
fn from(inner: matrix_sdk::SlidingSyncView) -> Self {
|
||||
SlidingSyncView { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl SlidingSyncView {
|
||||
pub fn observe_state(
|
||||
&self,
|
||||
observer: Box<dyn SlidingSyncViewStateObserver>,
|
||||
) -> Arc<StoppableSpawn> {
|
||||
let mut signal = self.inner.state.signal_cloned().to_stream();
|
||||
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
|
||||
loop {
|
||||
if let Some(new_state) = signal.next().await {
|
||||
observer.did_receive_update(new_state);
|
||||
}
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn observe_room_list(
|
||||
&self,
|
||||
observer: Box<dyn SlidingSyncViewRoomListObserver>,
|
||||
) -> Arc<StoppableSpawn> {
|
||||
let mut room_list = self.inner.rooms_list.signal_vec_cloned().to_stream();
|
||||
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
|
||||
loop {
|
||||
if let Some(diff) = room_list.next().await {
|
||||
observer.did_receive_update(diff.into());
|
||||
}
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn observe_room_items(
|
||||
&self,
|
||||
observer: Box<dyn SlidingSyncViewRoomItemsObserver>,
|
||||
) -> Arc<StoppableSpawn> {
|
||||
let mut rooms_updated = self.inner.rooms_updated_broadcaster.signal_cloned().to_stream();
|
||||
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
|
||||
loop {
|
||||
if rooms_updated.next().await.is_some() {
|
||||
observer.did_receive_update();
|
||||
}
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn observe_rooms_count(
|
||||
&self,
|
||||
observer: Box<dyn SlidingSyncViewRoomsCountObserver>,
|
||||
) -> Arc<StoppableSpawn> {
|
||||
let mut rooms_count = self.inner.rooms_count.signal_cloned().to_stream();
|
||||
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
|
||||
loop {
|
||||
if let Some(Some(new)) = rooms_count.next().await {
|
||||
observer.did_receive_update(new);
|
||||
}
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
/// Reset the ranges to a particular set
|
||||
///
|
||||
/// Remember to cancel the existing stream and fetch a new one as this will
|
||||
/// only be applied on the next request.
|
||||
pub fn set_range(&self, start: u32, end: u32) {
|
||||
self.inner.set_range(start, end);
|
||||
}
|
||||
|
||||
/// Set the ranges to fetch
|
||||
///
|
||||
/// Remember to cancel the existing stream and fetch a new one as this will
|
||||
/// only be applied on the next request.
|
||||
pub fn add_range(&self, start: u32, end: u32) {
|
||||
self.inner.add_range(start, end);
|
||||
}
|
||||
|
||||
pub fn reset_ranges(&self) {
|
||||
self.inner.reset_ranges();
|
||||
}
|
||||
|
||||
pub fn current_room_count(&self) -> Option<u32> {
|
||||
self.inner.rooms_count.get_cloned()
|
||||
}
|
||||
|
||||
pub fn current_rooms_list(&self) -> Vec<RoomListEntry> {
|
||||
self.inner.rooms_list.lock_ref().as_slice().iter().map(|e| e.into()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SlidingSyncObserver: Sync + Send {
|
||||
fn did_receive_sync_update(&self, summary: UpdateSummary);
|
||||
}
|
||||
|
||||
pub struct SlidingSync {
|
||||
inner: matrix_sdk::SlidingSync,
|
||||
client: Client,
|
||||
observer: Arc<RwLock<Option<Box<dyn SlidingSyncObserver>>>>,
|
||||
sync_handle: Arc<RwLock<Option<JoinHandle<()>>>>,
|
||||
}
|
||||
|
||||
impl SlidingSync {
|
||||
fn new(inner: matrix_sdk::SlidingSync, client: Client) -> Self {
|
||||
SlidingSync { inner, client, observer: Default::default(), sync_handle: Default::default() }
|
||||
}
|
||||
|
||||
pub fn set_observer(&self, observer: Option<Box<dyn SlidingSyncObserver>>) {
|
||||
*self.observer.write().unwrap() = observer;
|
||||
}
|
||||
|
||||
pub fn subscribe(
|
||||
&self,
|
||||
room_id: String,
|
||||
settings: Option<RoomSubscription>,
|
||||
) -> anyhow::Result<()> {
|
||||
let settings =
|
||||
if let Some(settings) = settings { Some(settings.try_into()?) } else { None };
|
||||
self.inner.subscribe(room_id.try_into()?, settings);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&self, room_id: String) -> anyhow::Result<()> {
|
||||
self.inner.unsubscribe(room_id.try_into()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
pub fn get_view(&self, name: String) -> Option<Arc<SlidingSyncView>> {
|
||||
let views = self.inner.views.lock_ref();
|
||||
for s in views.iter() {
|
||||
if s.name == name {
|
||||
return Some(Arc::new(SlidingSyncView { inner: s.clone() }));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_room(&self, room_id: String) -> anyhow::Result<Option<Arc<SlidingSyncRoom>>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.get_room(OwnedRoomId::try_from(room_id)?)
|
||||
.map(|inner| Arc::new(SlidingSyncRoom { inner, client: self.client.clone() })))
|
||||
}
|
||||
|
||||
pub fn get_rooms(
|
||||
&self,
|
||||
room_ids: Vec<String>,
|
||||
) -> anyhow::Result<Vec<Option<Arc<SlidingSyncRoom>>>> {
|
||||
let actual_ids = room_ids
|
||||
.into_iter()
|
||||
.map(OwnedRoomId::try_from)
|
||||
.collect::<Result<Vec<OwnedRoomId>, IdParseError>>()?;
|
||||
Ok(self
|
||||
.inner
|
||||
.get_rooms(actual_ids.into_iter())
|
||||
.into_iter()
|
||||
.map(|o| {
|
||||
o.map(|inner| Arc::new(SlidingSyncRoom { inner, client: self.client.clone() }))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn sync(&self) -> Arc<StoppableSpawn> {
|
||||
let inner = self.inner.clone();
|
||||
let observer = self.observer.clone();
|
||||
let spawn = Arc::new(StoppableSpawn::with_handle_ref(self.sync_handle.clone()));
|
||||
let inner_spawn = spawn.clone();
|
||||
{
|
||||
let mut sync_handle = self.sync_handle.write().unwrap();
|
||||
|
||||
if let Some(handle) = sync_handle.take() {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
*sync_handle = Some(RUNTIME.spawn(async move {
|
||||
let stream = inner.stream().await.unwrap();
|
||||
pin_mut!(stream);
|
||||
loop {
|
||||
let update = match stream.next().await {
|
||||
Some(Ok(u)) => u,
|
||||
Some(Err(e)) => {
|
||||
// FIXME: send this over the FFI
|
||||
tracing::warn!("Sliding Sync failure: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
tracing::debug!("No update from loop, cancelled");
|
||||
break;
|
||||
}
|
||||
};
|
||||
if let Some(ref observer) = *observer.read().unwrap() {
|
||||
observer.did_receive_sync_update(update.into());
|
||||
} else {
|
||||
// when the observer has been removed
|
||||
// we cancel the loop
|
||||
inner_spawn.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
spawn
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SlidingSyncBuilder {
|
||||
inner: MatrixSlidingSyncBuilder,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl SlidingSyncBuilder {
|
||||
pub fn homeserver(self: Arc<Self>, url: String) -> anyhow::Result<Arc<Self>> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.homeserver(url.parse()?);
|
||||
Ok(Arc::new(builder))
|
||||
}
|
||||
|
||||
pub fn add_fullsync_view(self: Arc<Self>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.add_fullsync_view();
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn no_views(self: Arc<Self>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.no_views();
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn add_view(self: Arc<Self>, v: Arc<SlidingSyncView>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
let view = unwrap_or_clone_arc(v);
|
||||
builder.inner = builder.inner.add_view(view.inner);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSync>> {
|
||||
let builder = unwrap_or_clone_arc(self);
|
||||
Ok(Arc::new(SlidingSync::new(builder.inner.build()?, builder.client)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn full_sliding_sync(&self) -> anyhow::Result<Arc<SlidingSync>> {
|
||||
RUNTIME.block_on(async move {
|
||||
let builder = self.client.sliding_sync().await;
|
||||
let inner = builder.add_fullsync_view().build()?;
|
||||
Ok(Arc::new(SlidingSync::new(inner, self.clone())))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sliding_sync(&self) -> Arc<SlidingSyncBuilder> {
|
||||
RUNTIME.block_on(async move {
|
||||
let inner = self.client.sliding_sync().await;
|
||||
Arc::new(SlidingSyncBuilder { inner, client: self.clone() })
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -31,7 +31,6 @@ ignore:
|
||||
- "bindings/matrix-sdk-crypto-nodejs"
|
||||
- "bindings/matrix-sdk-ffi"
|
||||
- "crates/matrix-sdk-indexeddb"
|
||||
- "crates/matrix-sdk-test"
|
||||
- "crates/matrix-sdk-test-macros"
|
||||
- "testing"
|
||||
- "labs"
|
||||
- "xtask"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user