Compare commits
1063 Commits
| 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 | |||
| 402d061d42 | |||
| 2a1bc372fc | |||
| bcab2a6d8c | |||
| 703b3a3561 | |||
| 36f62ce67f | |||
| 9bc605a76d | |||
| 13a6825af7 | |||
| c5796991e8 | |||
| 9a45325683 | |||
| 0bde5ccf38 | |||
| 94b635c074 | |||
| f69123b0d8 | |||
| 15be2dc45e | |||
| 420ca26bf5 | |||
| c1f0c73728 | |||
| 54ed1af223 | |||
| 6a57461f74 | |||
| 2d0653894c | |||
| 47cfac7f4c | |||
| 9539cbcfb9 | |||
| 9778518347 | |||
| dc2276cd8a | |||
| f1c880ff5f | |||
| 93e5728d65 | |||
| a7af96d081 | |||
| d7b974ac04 | |||
| aae9b5c6d8 | |||
| 0dee880cd0 | |||
| f0190b4601 | |||
| 6d83f01e73 | |||
| 2eb5fc77f5 | |||
| 5ab8bd0885 | |||
| ee69863912 | |||
| e87d599f84 | |||
| 0b011d9097 | |||
| 0f5851cc01 | |||
| d6a2f15c68 | |||
| 36a47c28ed | |||
| 900016b249 | |||
| de60a24602 | |||
| 29c10b8424 | |||
| 4b856ce9d6 | |||
| c043daede0 | |||
| 9f6988f766 | |||
| ed0709373d | |||
| acf9b15571 | |||
| ba39185679 | |||
| dc40309cbe | |||
| da277c4978 | |||
| 9925d73e7b | |||
| d465b70bea | |||
| fec879f0f3 | |||
| 91427b82a5 | |||
| d7739369ae | |||
| 4fd24eebea | |||
| 73daec3757 | |||
| d9f3b257b4 | |||
| 771c33d710 | |||
| 56adf6a89b | |||
| e5a7a975a3 | |||
| f3e69a2352 | |||
| 607d7ebc22 | |||
| 0178b71437 | |||
| dd6a902240 | |||
| 4eb1337dc8 | |||
| 81f02f0d0b | |||
| 76fe6d54ac | |||
| eb358889e9 | |||
| c82631c414 | |||
| fb4a940a26 | |||
| 05561a8777 | |||
| 909ada43d7 | |||
| 6176b3b658 | |||
| 566227576e | |||
| d6c0ef1497 | |||
| f72a14890d | |||
| 62378b4abc | |||
| f96069f591 | |||
| 59615d4ae3 | |||
| fd38c757e4 | |||
| 861d899541 | |||
| fd08c9e7da | |||
| cffb565a5f | |||
| f20d1c3d76 | |||
| e4f6c0cc58 | |||
| d3ae99eb22 | |||
| bc47caa356 | |||
| afa96f1bf4 | |||
| c99f42347c | |||
| 51cb35502d | |||
| b59077e83d | |||
| 3f197734d9 | |||
| 1961403512 | |||
| f1ebbfd245 | |||
| 0458ed9be1 | |||
| 12c7b76fea | |||
| a8601e186a | |||
| 8a2d13feea | |||
| e2ca56114e | |||
| 3c6d159a04 | |||
| 464bc43290 | |||
| 297861e186 | |||
| 8313029e33 | |||
| 913bdd683e | |||
| 041b9bc405 | |||
| 1526f76686 | |||
| f0e0194ff2 | |||
| ebc7177438 | |||
| 091fab8a2a | |||
| 818d715395 | |||
| 5a0089da52 | |||
| 68b6c19dd4 | |||
| c29e2b9563 | |||
| ecc28efd53 | |||
| f3a61020e7 | |||
| a423e92246 | |||
| b5d7f10c6b | |||
| 931eabf55c | |||
| 8cd7fa9fb0 | |||
| 1604f24136 | |||
| 3da737b9e2 | |||
| 2ffcc1a415 | |||
| 829dab42c5 | |||
| 54acd314cc | |||
| 74953031ee | |||
| 0436eb9349 | |||
| a23bb8f5a0 | |||
| 8db58986fb | |||
| 6ad323bc4e | |||
| b0d51fdfa5 | |||
| 3bfc68d476 | |||
| eb33333925 | |||
| d9475c131a | |||
| 399862d955 | |||
| 2c1f5fed8d | |||
| 8b2237fa7a | |||
| e5ea2a770b | |||
| 5f31e9d131 | |||
| 6cb9c11b88 | |||
| a00c130fc3 | |||
| 4971802e75 | |||
| 8690addfd5 | |||
| 00a20f325b | |||
| a4e4bfe833 | |||
| 02aa537f2a | |||
| 5316d2e6e7 | |||
| 7adef1d24e | |||
| c755e19fb3 | |||
| 8250c24525 | |||
| fe29fa57eb | |||
| c564b1a5e1 | |||
| 9d691e238a | |||
| 520e2f30f7 | |||
| c56ab5928c | |||
| f8cd2310be | |||
| 073fb45580 | |||
| 3833d35348 | |||
| 83b730d1c8 | |||
| 6477cc5072 | |||
| 2117b36a75 | |||
| 5e8ed3bcbf | |||
| 87639a4c4c | |||
| de04aba5b3 | |||
| 5243091bec | |||
| 1d746f1ef1 | |||
| 56d74e25b8 | |||
| a758d98f84 | |||
| 5adae6fd41 | |||
| 38d771cca6 | |||
| dd4c329f57 | |||
| e3edf0139a | |||
| d07001a581 | |||
| 62b8169ac2 | |||
| d35063412f | |||
| 1a162e5dd5 | |||
| b8069af8ba | |||
| fa3e192c37 | |||
| efc53569ed | |||
| 5c12132569 | |||
| 251a38285c | |||
| b500fa1daa | |||
| 4430dae421 | |||
| eead09984c | |||
| dcfcac0bd3 | |||
| dc32fc282d | |||
| b6eed09564 | |||
| 29ba171953 | |||
| 9e9152745c | |||
| 12d1607cdc | |||
| 6c8b520f14 | |||
| fedcdb1e63 | |||
| 1cfd69a880 | |||
| dad035d170 | |||
| 3c14acf163 | |||
| 253affeb0c | |||
| fe4ddfde89 | |||
| 8a332ca9e1 | |||
| 506f57a22c | |||
| 8b05f9276f | |||
| e8b2655d52 | |||
| 537ef1409b | |||
| e2bf3d0d18 | |||
| d188ef3386 | |||
| ba7ccb40cc | |||
| 1b2c644277 | |||
| 6a853d0173 | |||
| 7487b24fe3 | |||
| 5c4f2b3430 | |||
| 901b670a22 | |||
| 28d6e2821b | |||
| 7f0b74a39d | |||
| 7b6869310f | |||
| 445b2e627d | |||
| 0e206506d9 | |||
| 0fb1d72100 | |||
| 6d8c54deb5 | |||
| 07620452df | |||
| 099db20555 | |||
| 75571c2c30 | |||
| 8c95b1c4cf | |||
| 530c268e61 | |||
| 99dcf84340 | |||
| 2a76d17bd9 | |||
| 15af364c97 | |||
| ec7724f393 | |||
| 1e7d509920 | |||
| 520758bf1e | |||
| 7f49618d35 | |||
| a308771a7a | |||
| 2c4379909c | |||
| d4f49ca334 | |||
| c97bb83af9 | |||
| 12c53bb2bc | |||
| 7fd973c563 | |||
| f8dae723c3 | |||
| 8ccf78c025 | |||
| 47bb73cf89 | |||
| f3d952839e | |||
| 85ea9279dc | |||
| be159356cd | |||
| 571b5e61cf | |||
| 77af11bcbc | |||
| 335251695a | |||
| bf17012d6b | |||
| 328ebdba9c | |||
| c359b011fa | |||
| 07fed7f4df | |||
| 0ed74d8c31 | |||
| 35d7cab330 | |||
| 407e27d176 | |||
| 0f758a643c | |||
| fd2ae1ed8f | |||
| 2b13c0832f | |||
| b3fbd15270 | |||
| 4e3e393596 | |||
| 7f81e7c61b | |||
| a75ae16b79 | |||
| 9ebc61ad0f | |||
| 0d66480cd6 | |||
| 02802e9088 | |||
| 1a6adc6d41 | |||
| 6d10d6150c | |||
| fb1a2f6d94 | |||
| bdee4e0547 | |||
| 42d5eec8b7 | |||
| e50c1465ea | |||
| 5827cc9a39 | |||
| c95fd93666 | |||
| d96fcf7199 | |||
| e2348471db | |||
| 8e120eb648 | |||
| c573985c64 | |||
| 6a996c7657 | |||
| 7bb96e0ece | |||
| dc7f0389b1 | |||
| 5d4b3a0457 | |||
| 888ffb0a56 | |||
| 475fffb64d | |||
| a8b44f26ea | |||
| a7697fbd32 | |||
| 02e63ab9c1 | |||
| 8d909ccabe | |||
| 2e22f6b6c0 | |||
| 7c85fdaf28 | |||
| 1ecf90bb42 | |||
| e2ecba7d45 | |||
| 8a0e0a7a4d | |||
| c562d91533 | |||
| 3cf4150df2 | |||
| b3d52ca68c | |||
| e673954b98 | |||
| 6d7573dced | |||
| 2d14452398 | |||
| 7931c4a589 | |||
| 4db1ad350b | |||
| 0debbf24d7 | |||
| bb8217d10f | |||
| 6ff5c8e918 | |||
| 7fa89f76aa | |||
| 0335fdd07f | |||
| 3f8e3b61ff | |||
| efe5ea6a9c | |||
| 6afeeea56c | |||
| bef1dfbf79 | |||
| 51488b40d0 | |||
| b6a637893e | |||
| 2a4dce30b2 | |||
| 3194ad1f9a | |||
| ee648144a2 | |||
| afef9103a3 | |||
| 4df5ee6087 | |||
| 1a731ec385 | |||
| 84fe78bab3 | |||
| bce11b209e | |||
| 6c472f873f | |||
| 250b85dc79 | |||
| f3ab1ae276 | |||
| 6b2df7afdd | |||
| cdb252be5e | |||
| db30ef6ee4 | |||
| da8699fe40 | |||
| 16b5eebe23 | |||
| 8161360852 | |||
| f01cfe42b4 | |||
| a024c9b268 | |||
| c20349f46f | |||
| e9d37d7c4e | |||
| 8b94aed27f | |||
| dc5b15e799 | |||
| 30e9189f7b | |||
| b6a5d4962b | |||
| ee713f928f | |||
| 444ff9936b | |||
| 2451815226 | |||
| 86c6e601bd | |||
| 9f159ff5a4 | |||
| 056f34883f | |||
| 2d15f758da | |||
| acd5de3cf3 | |||
| f0bb35a96c | |||
| 1d38e54739 | |||
| 08665dcd1c | |||
| 569adb7ceb | |||
| fd4dff79d4 | |||
| fdb9fe21c6 | |||
| 7e5eec82c5 | |||
| 901715bcf3 | |||
| 3aa46fa746 | |||
| 3318789a8b | |||
| 0fe0910fea | |||
| a11633381b | |||
| 2ff6497604 | |||
| 7f87abf408 | |||
| dd49a8bd43 | |||
| f8c3a1d03b | |||
| 808c4c6f3c | |||
| a14549d5cc | |||
| 9b5b4ab2b2 | |||
| 5756057719 | |||
| c5ef3e7c94 | |||
| fa7c1b60f6 | |||
| 39b1fff218 | |||
| c6df6b421c | |||
| f9b4fd2d12 | |||
| e5390e18de | |||
| 28cd0c19e9 | |||
| 15ebc95cd0 | |||
| 39e12a86ca | |||
| ec9ec2567f | |||
| fa9c91fb0e | |||
| 24b71bf869 | |||
| dd83cdd74b | |||
| 453cae641b | |||
| f838b5fae8 | |||
| 1f722f224f | |||
| a992a4e831 | |||
| 017359b081 | |||
| 08718acd57 | |||
| dd1f817701 | |||
| 81db9ef4ca | |||
| e927834108 | |||
| f3045dbf99 | |||
| d6c6211c00 | |||
| 85949081ce | |||
| cbcc5feef2 | |||
| 1b569a8fd4 | |||
| 813f388812 | |||
| 552de33dbc | |||
| c3f2003eb7 | |||
| 3b70d7f9ba | |||
| c00c9d7b81 | |||
| 126e8f1bd9 | |||
| 9e6e76e5ed | |||
| d1410fcded | |||
| b955e7aad9 | |||
| a364018c0e | |||
| 0f910b6229 | |||
| 736f0e7687 | |||
| 6a1b85c560 | |||
| e3503fe102 | |||
| 4522b4e8f5 | |||
| 886809b579 | |||
| 97b91a47f1 | |||
| 4b8b9db075 | |||
| bc207f1e5c | |||
| d863b4eb75 | |||
| 45cb162e5a | |||
| ba2cef62b0 | |||
| 2d7fb1b61c | |||
| d6f9e5f6b6 | |||
| 9c07ff1166 | |||
| 5bd7d17234 | |||
| 7d439b1697 | |||
| 39f1b9d464 | |||
| 8a86369e68 | |||
| a19999b240 | |||
| a72a05edc3 | |||
| d0cc3d9c2b | |||
| 2862934b87 | |||
| 3f3f9d653b | |||
| 83fc91f87e | |||
| 1e77c39498 | |||
| 1de27bcc0c | |||
| 248fff370a | |||
| 26dd2d0e62 | |||
| 4bbfa4345f | |||
| 5f92113627 | |||
| 426a93f07c | |||
| 0949979a1b | |||
| 76144de881 | |||
| bd4763235a | |||
| 911b5415b9 | |||
| c3fc6ff58f | |||
| 4c84f252d2 | |||
| 70c0626882 | |||
| 5570181bf8 | |||
| 7088ff89b5 | |||
| 3f36528a98 | |||
| 1fdd1ab23a | |||
| 40dcc988d6 | |||
| 9909cb597a | |||
| 56cfe8f231 | |||
| ab9d8eb52b | |||
| c8233b6c85 | |||
| b72bdcd7d4 | |||
| 5c6a6464c4 | |||
| d3c20b2a13 | |||
| 7817af9aa6 | |||
| 7569c08ada |
@@ -1,3 +1,13 @@
|
||||
# Pass the rustflags specified to host dependencies (build scripts, proc-macros)
|
||||
# when a `--target` is passed to Cargo. Historically this was not the case, and
|
||||
# because of that, cross-compilation would not set the rustflags configured
|
||||
# below in `target.'cfg(all())'` for them, resulting in cache invalidation.
|
||||
#
|
||||
# Since this is an unstable feature (enabled at the bottom of the file), this
|
||||
# setting is unfortunately ignored on stable toolchains, but it's still better
|
||||
# to have it apply on nightly than using the old behavior for all toolchains.
|
||||
target-applies-to-host = false
|
||||
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
|
||||
@@ -23,3 +33,9 @@ rustflags = [
|
||||
"-Wclippy::str_to_string",
|
||||
"-Wclippy::todo",
|
||||
]
|
||||
|
||||
# activate the target-applies-to-host feature.
|
||||
# Required for `target-applies-to-host` at the top to take effect.
|
||||
[unstable]
|
||||
rustdoc-map = true
|
||||
target-applies-to-host = true
|
||||
|
||||
@@ -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,46 +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 }} / appservice / stable
|
||||
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu, macOS]
|
||||
|
||||
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: Run checks
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-appservice
|
||||
@@ -0,0 +1,13 @@
|
||||
name: Security audit
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
name: Bindings tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
MATRIX_SDK_CRYPTO_NODEJS_PATH: bindings/matrix-sdk-crypto-nodejs
|
||||
MATRIX_SDK_CRYPTO_JS_PATH: bindings/matrix-sdk-crypto-js
|
||||
|
||||
jobs:
|
||||
test-matrix-sdk-crypto-nodejs:
|
||||
name: ${{ matrix.os-name }} [m]-crypto-nodejs, v${{ matrix.node-version }}
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node-version: [14.0, 16.0, 18.0]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
|
||||
- os: macos-latest
|
||||
os-name: 🍏
|
||||
node-version: 18.0
|
||||
|
||||
- node-version: 18.0
|
||||
build-doc: true
|
||||
|
||||
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 Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install NPM dependencies
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: npm install
|
||||
|
||||
- name: Build the Node.js binding
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: npm run release-build
|
||||
|
||||
- name: Test the Node.js binding
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: npm run test
|
||||
|
||||
# Building in dev-mode and copy lib in failure case
|
||||
- name: Build the Node.js binding in non-release
|
||||
if: failure()
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: |
|
||||
cp *.node release-mode-lib.node
|
||||
npm run build
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: Failure Files
|
||||
path: |
|
||||
bindings/matrix-sdk-crypto-nodejs/*.node
|
||||
/var/crash/*.crash
|
||||
|
||||
- if: ${{ matrix.build-doc }}
|
||||
name: Build the documentation
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: npm run doc
|
||||
|
||||
test-matrix-sdk-crypto-js:
|
||||
name: 🕸 [m]-crypto-js
|
||||
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
|
||||
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.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
run: npm install
|
||||
|
||||
- name: Build the WebAssembly + JavaScript binding
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
run: npm run build
|
||||
|
||||
- name: Test the JavaScript binding
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
run: npm run test
|
||||
|
||||
- name: Build the documentation
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
run: npm run doc
|
||||
|
||||
test-apple:
|
||||
name: matrix-rust-components-swift
|
||||
runs-on: macos-12
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install targets
|
||||
run: |
|
||||
rustup target add aarch64-apple-ios-sim --toolchain nightly
|
||||
rustup target add x86_64-apple-ios --toolchain nightly
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install Uniffi
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
# keep in sync with uniffi dependency in Cargo.toml's
|
||||
args: uniffi_bindgen --git https://github.com/mozilla/uniffi-rs --rev 091c3561656e72e1a4160412c83b36d98e556d06
|
||||
|
||||
- name: Generate .xcframework
|
||||
working-directory: bindings/apple
|
||||
run: sh ./debug_build_xcframework.sh x86_64 ci
|
||||
|
||||
- name: Run XCTests
|
||||
working-directory: bindings/apple
|
||||
run: |
|
||||
xcodebuild test \
|
||||
-scheme MatrixRustSDK \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 13'
|
||||
+365
-32
@@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: Rust tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -16,8 +16,46 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test-features:
|
||||
name: linux / features-${{ matrix.name }}
|
||||
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
|
||||
@@ -48,58 +86,111 @@ jobs:
|
||||
- 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-features ${{ matrix.name }}
|
||||
|
||||
test-crypto-features:
|
||||
name: linux / crypto-crate features
|
||||
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: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-crypto
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
test:
|
||||
- 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 }}
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
name:
|
||||
- linux / stable
|
||||
- linux / beta
|
||||
- macOS / stable
|
||||
|
||||
include:
|
||||
- name: linux / stable
|
||||
- name: 🐧 all crates, 🦀 stable
|
||||
rust: stable
|
||||
os: ubuntu-latest
|
||||
|
||||
- name: linux / beta
|
||||
- name: 🐧 all crates, 🦀 beta
|
||||
rust: beta
|
||||
os: ubuntu-latest
|
||||
|
||||
- name: macOS / stable
|
||||
os: macOS-latest
|
||||
- name: 🍏 all crates, 🦀 stable
|
||||
rust: stable
|
||||
os: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -108,14 +199,256 @@ jobs:
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust || 'stable' }}
|
||||
toolchain: ${{ matrix.rust }}
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: nextest
|
||||
args: run --workspace --exclude matrix-sdk-integration-testing
|
||||
|
||||
- name: Test documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --doc
|
||||
|
||||
test-wasm:
|
||||
name: 🕸️ ${{ matrix.name }}
|
||||
needs: xtask
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- name: '[m]-qrcode'
|
||||
cmd: matrix-sdk-qrcode
|
||||
|
||||
- name: '[m]-base'
|
||||
cmd: matrix-sdk-base
|
||||
|
||||
- name: '[m]-common'
|
||||
cmd: matrix-sdk-common
|
||||
|
||||
- name: '[m]-indexeddb, no crypto'
|
||||
cmd: indexeddb-no-crypto
|
||||
|
||||
- name: '[m]-indexeddb, with crypto'
|
||||
cmd: indexeddb-with-crypto
|
||||
|
||||
- name: '[m], no-default, wasm-flags'
|
||||
cmd: matrix-sdk-no-default
|
||||
|
||||
- name: '[m], indexeddb stores'
|
||||
cmd: matrix-sdk-indexeddb-stores
|
||||
|
||||
- name: '[m], indexeddb stores, no crypto'
|
||||
cmd: matrix-sdk-indexeddb-stores-no-crypto
|
||||
|
||||
- name: '[m], wasm-example'
|
||||
cmd: matrix-sdk-command-bot
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- 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: Rust Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci wasm ${{ matrix.cmd }}
|
||||
|
||||
- name: Wasm-Pack test
|
||||
uses: actions-rs/cargo@v1
|
||||
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
|
||||
|
||||
@@ -13,10 +13,13 @@ jobs:
|
||||
code_coverage:
|
||||
name: Code Coverage
|
||||
runs-on: "ubuntu-latest"
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
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
|
||||
@@ -34,11 +37,23 @@ 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:
|
||||
command: tarpaulin
|
||||
args: --ignore-config --exclude-files "crates/matrix-sdk/examples/*,crates/matrix-sdk-common,crates/matrix-sdk-test" --out Xml
|
||||
args: --out Xml
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Docs
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
# Keep in sync with xtask docs
|
||||
- name: Build docs
|
||||
uses: actions-rs/cargo@v1
|
||||
env:
|
||||
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings"
|
||||
with:
|
||||
command: doc
|
||||
args: --no-deps --workspace --features docsrs -Zrustdoc-map
|
||||
|
||||
- name: Deploy docs
|
||||
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/
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: All crates
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
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 rust documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
env:
|
||||
# Work around https://github.com/rust-lang/cargo/issues/10744
|
||||
CARGO_TARGET_APPLIES_TO_HOST: "true"
|
||||
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings"
|
||||
with:
|
||||
command: doc
|
||||
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: ./doc/
|
||||
force_orphan: true
|
||||
@@ -0,0 +1,117 @@
|
||||
name: Prepare Crypto-Node.js Release
|
||||
#
|
||||
# This is a helper workflow to craft a new Node.js release, trigger this via
|
||||
# the Github Workflow UI by dispatching it manually. Provide the version, the
|
||||
# matrix-sdk-crypto-nodejs npm package should be set to, and a optionally the
|
||||
# old version (as used in the git tag) this release should be compared to.
|
||||
#
|
||||
# This will then:
|
||||
# 1. bump the npm version to the one you specified
|
||||
# 2. commit that change together with the changelog (if it changed, see below)
|
||||
# 3. create the appropriate tag on that commit
|
||||
# 4. create the Github draft release, including the changes (if given, see below)
|
||||
# 5. push these to a new branch, including tag, triggering the `release-crypto-nodejs` workflow
|
||||
# 6. create a PR to merge these back into `main`
|
||||
#
|
||||
# Additionally, if you provide a tag to comapare this tag to, this will:
|
||||
# 1. create a changelog between the two releases, used for the github release
|
||||
# 2. update the Changelog.md and include it in the commit
|
||||
#
|
||||
# The remaining tasks are done by the release-crypto-nodejs workflow.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'New Node.js SemVer version to create'
|
||||
required: true
|
||||
type: string
|
||||
previous_version:
|
||||
description: 'Create the changelog by comparing to this old SemVer Version (as used in the tag) '
|
||||
type: string
|
||||
|
||||
env:
|
||||
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
|
||||
TAG_PREFIX: "matrix-sdk-crypto-nodejs-v"
|
||||
|
||||
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
|
||||
|
||||
# Generate changelog since last tag, if given
|
||||
- name: Generate a changelog for upload
|
||||
if: inputs.previous_version
|
||||
uses: orhun/git-cliff-action@v1
|
||||
with:
|
||||
config: "${{ env.PKG_PATH }}/cliff.toml"
|
||||
args: --strip header "${{env.TAG_PREFIX}}${{ inputs.previous_version }}..HEAD"
|
||||
env:
|
||||
GIT_CLIFF_TAG: "Changes ${{ inputs.previous_version }} -> ${{ inputs.version }}"
|
||||
GIT_CLIFF_OUTPUT: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
|
||||
|
||||
# Update changelog since last tag, if given
|
||||
- name: Update existing Changelog
|
||||
if: inputs.previous_version
|
||||
uses: orhun/git-cliff-action@v1
|
||||
with:
|
||||
config: "${{ env.PKG_PATH }}/cliff.toml"
|
||||
args: "${{ inputs.previous_version }}..HEAD"
|
||||
env:
|
||||
GIT_CLIFF_TAG: "${{ inputs.version }}"
|
||||
GIT_CLIFF_PREPEND: "${{ env.PKG_PATH }}/CHANGELOG.md"
|
||||
|
||||
- name: Set version
|
||||
id: package_version
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
run: npm version ${{ inputs.version }}
|
||||
|
||||
- uses: EndBug/add-and-commit@v9
|
||||
with:
|
||||
default_author: github_actions
|
||||
message: "Tagging Crypto-Node.js for release"
|
||||
tag: "${{env.TAG_PREFIX}}${{ inputs.version }}"
|
||||
new_branch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
push: true
|
||||
add: |
|
||||
${{ env.PKG_PATH }}/package.json
|
||||
${{ env.PKG_PATH }}/CHANGELOG.md
|
||||
|
||||
# if we have generated changes
|
||||
- name: Update Github Release notes
|
||||
if: inputs.previous_version
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
body_path: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
|
||||
|
||||
# no changes, use the default changelog for the body
|
||||
- name: Update Github Release notes
|
||||
if: ${{!inputs.previous_version}}
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
body_path: "${{ env.PKG_PATH }}/CHANGELOG.md"
|
||||
|
||||
# let's create a PR for all this, too
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
title: "Preparing Release ${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
body: |
|
||||
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}}
|
||||
@@ -0,0 +1,139 @@
|
||||
name: Release Crypto-Node.js
|
||||
#
|
||||
# This workflow releases the crypto-bindings for nodejs
|
||||
#
|
||||
# It is triggered when seeing a tag prefixed matching `matrix-sdk-crypto-nodejs-v[0-9]+.*`,
|
||||
# which then build the native bindings for linux, mac and windows via the CI and uploads
|
||||
# them to the corresponding Github Release tag. Once they are finished, this workflow will
|
||||
# package the npm tar.gz and uploads that to the Github Release tag as well, before publishing
|
||||
# it to npmjs.com automatically.
|
||||
#
|
||||
# The usual way to trigger this is by manually triggering the `prep-crypto-nodejs-release`
|
||||
# workflow. See its documentation for instructions how to use it.
|
||||
|
||||
env:
|
||||
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: 'aarch64-linux-gnu-gcc'
|
||||
CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER: 'i686-linux-gnu-gcc'
|
||||
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: 'arm-linux-gnueabihf-gcc'
|
||||
|
||||
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"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# ----------------------------------- Linux
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: i686-unknown-linux-gnu
|
||||
apt_install: gcc-i686-linux-gnu g++-i686-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
apt_install: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
os: ubuntu-latest
|
||||
apt_install: gcc-arm-linux-gnueabihf
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
# ----------------------------------- macOS
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
# ----------------------------------- Windows
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: i686-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: aarch64-pc-windows-msvc
|
||||
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:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- if: ${{ matrix.apt_install }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ${{ matrix.apt_install }}
|
||||
- name: Build lib
|
||||
working-directory: ${{env.PKG_PATH}}
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
npx napi build --platform --release --strip --target ${{ matrix.target }}
|
||||
- name: Upload artifacts to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
files: ${{env.PKG_PATH}}/*.node
|
||||
|
||||
publish-nodejs-package:
|
||||
name: "Package nodejs package"
|
||||
runs-on: ubuntu-latest
|
||||
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:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- name: Build lib
|
||||
working-directory: ${{env.PKG_PATH}}
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
npm run build
|
||||
npm pack
|
||||
- name: Upload npm package to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
files: ${{env.PKG_PATH}}/*tgz
|
||||
- name: Publish to npmjs.com
|
||||
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,75 +0,0 @@
|
||||
name: WASM
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
check-wasm:
|
||||
name: Build test / ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
name:
|
||||
- matrix-sdk-qrcode
|
||||
- matrix-sdk-base
|
||||
- matrix-sdk-common
|
||||
- matrix-sdk-crypto
|
||||
- indexeddb-no-crypto
|
||||
- indexeddb-with-crypto
|
||||
|
||||
include:
|
||||
- name: matrix-sdk (no-default, wasm-flags)
|
||||
cmd: matrix-sdk-no-default
|
||||
- name: matrix-sdk / indexeddb_stores
|
||||
cmd: matrix-sdk-indexeddb-stores
|
||||
- name: matrix-sdk / indexeddb_stores / no crypto
|
||||
cmd: matrix-sdk-indexeddb-stores-no-crypto
|
||||
- name: matrix-sdk / wasm-example
|
||||
cmd: matrix-sdk-command-bot
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install WasmPack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: 'latest'
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Rust Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci wasm ${{ matrix.cmd || matrix.name }}
|
||||
|
||||
- name: Wasm-Pack test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci wasm-pack ${{ matrix.cmd || matrix.name }}
|
||||
+8
-1
@@ -1,6 +1,13 @@
|
||||
Cargo.lock
|
||||
target
|
||||
generated
|
||||
master.zip
|
||||
emsdk-*
|
||||
.idea/
|
||||
.env
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
.vscode/
|
||||
|
||||
## OS garbage
|
||||
.DS_Store
|
||||
|
||||
@@ -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
+29
-2
@@ -1,4 +1,31 @@
|
||||
[workspace]
|
||||
members = ["benchmarks", "crates/*", "labs/*", "xtask"]
|
||||
# xtask and labs should only be compiled when invoked explicitly
|
||||
members = [
|
||||
"benchmarks",
|
||||
"bindings/matrix-sdk-crypto-ffi",
|
||||
"bindings/matrix-sdk-crypto-js",
|
||||
"bindings/matrix-sdk-crypto-nodejs",
|
||||
"bindings/matrix-sdk-ffi",
|
||||
"crates/*",
|
||||
"testing/*",
|
||||
"examples/*",
|
||||
"labs/*",
|
||||
"xtask",
|
||||
]
|
||||
# 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.
|
||||
quote = { opt-level = 2 }
|
||||
sha2 = { opt-level = 2 }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
[](https://codecov.io/gh/matrix-org/matrix-rust-sdk)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://matrix.to/#/#matrix-rust-sdk:matrix.org)
|
||||
[](https://matrix-org.github.io/matrix-rust-sdk/)
|
||||
[](https://matrix-org.github.io/matrix-rust-sdk/matrix_sdk/)
|
||||
[](https://docs.rs/matrix-sdk)
|
||||
|
||||
# matrix-rust-sdk
|
||||
@@ -36,6 +36,12 @@ the API will change in breaking ways.
|
||||
If you are interested in using the matrix-sdk now is the time to try it out and
|
||||
provide feedback.
|
||||
|
||||
## Bindings
|
||||
|
||||
Some crates of the **matrix-rust-sdk** can be embedded inside other
|
||||
environments, like Swift, Kotlin, JavaScript, Node.js etc. Please,
|
||||
explore the [`bindings/`](./bindings/) directory to learn more.
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
@@ -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 = "0.6.1"
|
||||
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"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::ops::Deref;
|
||||
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)
|
||||
@@ -71,7 +71,7 @@ pub fn keys_query(c: &mut Criterion) {
|
||||
});
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Box::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
let machine =
|
||||
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
|
||||
|
||||
@@ -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(
|
||||
@@ -119,7 +119,7 @@ pub fn keys_claiming(c: &mut Criterion) {
|
||||
|| {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store =
|
||||
Box::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
|
||||
let machine = runtime
|
||||
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store))
|
||||
@@ -158,12 +158,12 @@ 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 {
|
||||
let requests = machine
|
||||
.share_group_session(
|
||||
.share_room_key(
|
||||
room_id,
|
||||
users.iter().map(Deref::deref),
|
||||
EncryptionSettings::default(),
|
||||
@@ -181,7 +181,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
})
|
||||
});
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Box::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
|
||||
let machine =
|
||||
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
|
||||
@@ -191,7 +191,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
group.bench_function(BenchmarkId::new("sled store", &name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
let requests = machine
|
||||
.share_group_session(
|
||||
.share_room_key(
|
||||
room_id,
|
||||
users.iter().map(Deref::deref),
|
||||
EncryptionSettings::default(),
|
||||
@@ -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();
|
||||
|
||||
@@ -236,7 +236,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
|
||||
});
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Box::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
|
||||
let machine =
|
||||
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Matrix Rust SDK bindings
|
||||
|
||||
In this directory, one can find bindings to the Rust SDK that are
|
||||
maintained by the owners of the Matrix Rust SDK project.
|
||||
|
||||
* [`apple`] or `matrix-rust-components-swift`, Swift bindings of the
|
||||
[`matrix-sdk`] crate via [`matrix-sdk-ffi`],
|
||||
* [`matrix-sdk-crypto-ffi`], bindings of the [`matrix-sdk-crypto`]
|
||||
crate,
|
||||
* [`matrix-sdk-crypto-js`], JavaScript bindings of the
|
||||
[`matrix-sdk-crypto`] crate,
|
||||
* [`matrix-sdk-crypto-nodejs`], Node.js bindings of the
|
||||
[`matrix-sdk-crypto`] crate,
|
||||
* [`matrix-sdk-ffi`], bindings of the [`matrix-sdk`] crate,
|
||||
|
||||
[`apple`]: ./apple
|
||||
[`matrix-sdk-crypto-ffi`]: ./matrix-sdk-crypto-ffi
|
||||
[`matrix-sdk-crypto-js`]: ../crates/matrix-sdk-crypto
|
||||
[`matrix-sdk-crypto-nodejs`]: ../crates/matrix-sdk-crypto
|
||||
[`matrix-sdk-crypto`]: ../crates/matrix-sdk-crypto
|
||||
[`matrix-sdk-ffi`]: ./matrix-sdk-ffi
|
||||
[`matrix-sdk`]: ../crates/matrix-sdk
|
||||
@@ -0,0 +1,17 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "MatrixSDKCrypto"
|
||||
s.version = "0.1.0"
|
||||
s.summary = "Uniffi based bindings for the Rust SDK crypto crate."
|
||||
s.homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" }
|
||||
s.author = { "matrix.org" => "support@matrix.org" }
|
||||
|
||||
s.ios.deployment_target = "11.0"
|
||||
s.swift_versions = ['5.0']
|
||||
|
||||
s.source = { :http => "https://github.com/matrix-org/matrix-rust-sdk/releases/download/matrix-sdk-crypto-ffi-#{s.version}/MatrixSDKCryptoFFI.zip" }
|
||||
s.vendored_frameworks = "MatrixSDKCryptoFFI.xcframework"
|
||||
s.source_files = "Sources/**/*.{swift}"
|
||||
|
||||
end
|
||||
@@ -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,53 @@
|
||||
# Apple platforms support
|
||||
|
||||
This project and build script demonstrate how to create an XCFramework that can be imported into an Xcode project and run on Apple platforms. It can compile and bundle an [entire SDK](#Building-the-SDK), or only a smaller [Crypto module](#Building-only-the-Crypto-SDK) that provides end-to-end encryption for clients that already depend on an SDK (e.g. [Matrix iOS SDK](https://github.com/matrix-org/matrix-ios-sdk))
|
||||
|
||||
## Prerequisites for building universal frameworks
|
||||
|
||||
* the Rust toolchain
|
||||
* UniFFI - `cargo install uniffi_bindgen`
|
||||
* Apple targets (e.g. `rustup target add aarch64-apple-ios`)
|
||||
* `xcodebuild` command line tool from [Apple](https://developer.apple.com/library/archive/technotes/tn2339/_index.html)
|
||||
* `lipo` for creating the fat static libs
|
||||
|
||||
## Building the SDK
|
||||
|
||||
```
|
||||
sh build_xcframework.sh
|
||||
```
|
||||
|
||||
The `build_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
|
||||
|
||||
1. compile `matrix-sdk-ffi` libraries for iOS, the iOS simulator, MacOS, and Mac Catalyst under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain.
|
||||
2. `lipo` together the libraries for the same platform under `/generated`
|
||||
3. run `uniffi` and generate the C header, module map and swift files
|
||||
4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKFFI.xcframework`
|
||||
5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
|
||||
|
||||
## Building only the Crypto SDK
|
||||
|
||||
```
|
||||
sh build_crypto_xcframework.sh
|
||||
```
|
||||
|
||||
The `build_crypto_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
|
||||
|
||||
1. compile `matrix-sdk-crypto-ffi` libraries for iOS and the iOS simulator under `/target`
|
||||
2. `lipo` together the libraries for the same platform under `/generated`
|
||||
3. run `uniffi` and generate the C header, module map and swift files
|
||||
4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKCryptoFFI.xcframework`
|
||||
5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
|
||||
|
||||
## Running the Xcode project
|
||||
|
||||
The Xcode project is meant to provide a simple example on how to integrate everything together but also a place to run unit and integration tests from.
|
||||
|
||||
It's pre-configured to link to the generated .xcframework and .swift files so successfully running the script first is necessary for it to compile.
|
||||
|
||||
It makes the compiled code available to swift by importing the C header through its bridging header.
|
||||
|
||||
Once all the generated components are available running it should be as easy as choosing a platform and clicking run.
|
||||
|
||||
## Distribution
|
||||
|
||||
The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a Swift package available [TBD](here) in the case of SDK, and as CocoaPods podspec in the case of Crypto SDK.
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Path to the repo root
|
||||
SRC_ROOT=../..
|
||||
|
||||
TARGET_DIR="${SRC_ROOT}/target"
|
||||
|
||||
GENERATED_DIR="${SRC_ROOT}/generated"
|
||||
if [ -d "${GENERATED_DIR}" ]; then rm -rf "${GENERATED_DIR}"; fi
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
REL_FLAG="--release"
|
||||
REL_TYPE_DIR="release"
|
||||
|
||||
TARGET_CRATE=matrix-sdk-crypto-ffi
|
||||
|
||||
# Build static libs for all the different architectures
|
||||
|
||||
# iOS
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios"
|
||||
|
||||
# iOS Simulator
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "x86_64-apple-ios"
|
||||
|
||||
# Lipo together the libraries for the same platform
|
||||
|
||||
# iOS Simulator
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_crypto_ffi.a"
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl" --language swift --config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" --out-dir ${GENERATED_DIR}
|
||||
|
||||
# Move headers to the right place
|
||||
HEADERS_DIR=${GENERATED_DIR}/headers
|
||||
mkdir -p ${HEADERS_DIR}
|
||||
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
|
||||
|
||||
# Rename and move modulemap to the right place
|
||||
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
|
||||
|
||||
# Move source files to the right place
|
||||
SWIFT_DIR="${GENERATED_DIR}/Sources"
|
||||
mkdir -p ${SWIFT_DIR}
|
||||
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
|
||||
|
||||
# Build the xcframework
|
||||
|
||||
if [ -d "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework"; fi
|
||||
|
||||
xcodebuild -create-xcframework \
|
||||
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/libmatrix_crypto_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-output "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework"
|
||||
|
||||
# Cleanup
|
||||
|
||||
if [ -f "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a"; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_crypto_ffi.a"; fi
|
||||
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
|
||||
# Zip up framework, sources and LICENSE, ready to be uploaded to GitHub Releases and used by MatrixSDKCrypto.podspec
|
||||
cp ${SRC_ROOT}/LICENSE $GENERATED_DIR
|
||||
cd $GENERATED_DIR
|
||||
zip -r MatrixSDKCryptoFFI.zip MatrixSDKCryptoFFI.xcframework Sources LICENSE
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Path to the repo root
|
||||
SRC_ROOT=../..
|
||||
|
||||
TARGET_DIR="${SRC_ROOT}/target"
|
||||
|
||||
GENERATED_DIR="${SRC_ROOT}/generated"
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
REL_FLAG="--release"
|
||||
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"
|
||||
|
||||
echo -e "\nCreating XCFramework"
|
||||
# Lipo together the libraries for the same platform
|
||||
|
||||
# MacOS
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"
|
||||
|
||||
# iOS Simulator
|
||||
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" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
|
||||
|
||||
# Generate uniffi files
|
||||
# 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 ${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}
|
||||
|
||||
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
|
||||
|
||||
# Build the xcframework
|
||||
|
||||
if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi
|
||||
|
||||
xcodebuild -create-xcframework \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.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"
|
||||
|
||||
# Cleanup
|
||||
|
||||
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 [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
IS_CI=false
|
||||
ACTIVE_ARCH="arm64"
|
||||
|
||||
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}/bindings/apple/generated"
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
REL_FLAG=""
|
||||
REL_TYPE_DIR="debug"
|
||||
|
||||
# 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}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
|
||||
# Generate uniffi files
|
||||
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 ${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}
|
||||
|
||||
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
|
||||
|
||||
# Build the xcframework
|
||||
|
||||
if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi
|
||||
|
||||
xcodebuild -create-xcframework \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-output "${GENERATED_DIR}/MatrixSDKFFI.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 [ "$IS_CI" = false ] ; then
|
||||
echo "Preparing matrix-rust-components-swift"
|
||||
|
||||
# Debug -> Copy generated files over to ../../../matrix-rust-components-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
|
||||
+14
-13
@@ -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"
|
||||
@@ -10,8 +10,8 @@ license = "Apache-2.0"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "matrix_crypto"
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
name = "matrix_crypto_ffi"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
@@ -20,14 +20,15 @@ hmac = "0.12.1"
|
||||
http = "0.2.6"
|
||||
pbkdf2 = "0.11.0"
|
||||
rand = "0.8.5"
|
||||
ruma = { version = "0.6.1", 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"
|
||||
thiserror = "1.0.30"
|
||||
tracing = "0.1.34"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
|
||||
uniffi = "0.17.0"
|
||||
# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job
|
||||
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06" }
|
||||
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
|
||||
|
||||
[dependencies.js_int]
|
||||
@@ -35,17 +36,17 @@ version = "0.2.2"
|
||||
features = ["lax_deserialize"]
|
||||
|
||||
[dependencies.matrix-sdk-common]
|
||||
path = "../matrix-sdk-common"
|
||||
version = "0.5.0"
|
||||
path = "../../crates/matrix-sdk-common"
|
||||
version = "0.6.0"
|
||||
|
||||
[dependencies.matrix-sdk-crypto]
|
||||
path = "../matrix-sdk-crypto"
|
||||
version = "0.5.0"
|
||||
path = "../../crates/matrix-sdk-crypto"
|
||||
version = "0.6.0"
|
||||
features = ["qrcode", "backups_v1"]
|
||||
|
||||
[dependencies.matrix-sdk-sled]
|
||||
path = "../matrix-sdk-sled"
|
||||
version = "0.1.0"
|
||||
path = "../../crates/matrix-sdk-sled"
|
||||
version = "0.2.0"
|
||||
default_features = false
|
||||
features = ["crypto-store"]
|
||||
|
||||
@@ -55,10 +56,10 @@ default_features = false
|
||||
features = ["rt-multi-thread"]
|
||||
|
||||
[dependencies.vodozemac]
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
|
||||
[build-dependencies]
|
||||
uniffi_build = { version = "0.17.0", features = ["builtin-bindgen"] }
|
||||
uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06", features = ["builtin-bindgen"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
use std::{collections::HashMap, iter};
|
||||
use std::{collections::HashMap, iter, ops::DerefMut};
|
||||
|
||||
use hmac::Hmac;
|
||||
use matrix_sdk_crypto::{
|
||||
@@ -13,8 +13,8 @@ use zeroize::Zeroize;
|
||||
|
||||
/// The private part of the backup key, the one used for recovery.
|
||||
pub struct BackupRecoveryKey {
|
||||
inner: RecoveryKey,
|
||||
passphrase_info: Option<PassphraseInfo>,
|
||||
pub(crate) inner: RecoveryKey,
|
||||
pub(crate) passphrase_info: Option<PassphraseInfo>,
|
||||
}
|
||||
|
||||
/// Error type for the decryption of backed up room keys.
|
||||
@@ -101,7 +101,7 @@ impl BackupRecoveryKey {
|
||||
let mut key = Box::new([0u8; Self::KEY_SIZE]);
|
||||
let rounds = rounds as u32;
|
||||
|
||||
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt.as_bytes(), rounds, &mut *key);
|
||||
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt.as_bytes(), rounds, key.deref_mut());
|
||||
|
||||
let recovery_key = RecoveryKey::from_bytes(&key);
|
||||
|
||||
+45
-14
@@ -14,7 +14,7 @@ mod responses;
|
||||
mod users;
|
||||
mod verification;
|
||||
|
||||
use std::{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,
|
||||
@@ -48,7 +49,7 @@ pub struct MigrationData {
|
||||
/// The list of Megolm inbound group sessions.
|
||||
inbound_group_sessions: Vec<PickledInboundGroupSession>,
|
||||
/// The Olm pickle key that was used to pickle all the Olm objects.
|
||||
pickle_key: String,
|
||||
pickle_key: Vec<u8>,
|
||||
/// The backup version that is currently active.
|
||||
backup_version: Option<String>,
|
||||
// The backup recovery key, as a base58 encoded string.
|
||||
@@ -67,7 +68,7 @@ pub struct MigrationData {
|
||||
pub struct PickledAccount {
|
||||
/// The user id of the account owner.
|
||||
pub user_id: String,
|
||||
/// The device id of the account owner.
|
||||
/// The device ID of the account owner.
|
||||
pub device_id: String,
|
||||
/// The pickled version of the Olm account.
|
||||
pub pickle: String,
|
||||
@@ -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,13 +185,18 @@ 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;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let user_id: Arc<UserId> = (&*parse_user_id(&data.account.user_id)?).into();
|
||||
let user_id: Arc<UserId> = {
|
||||
let user_id: OwnedUserId = parse_user_id(&data.account.user_id)?;
|
||||
let user_id: &UserId = user_id.borrow();
|
||||
|
||||
user_id.into()
|
||||
};
|
||||
let device_id: Box<DeviceId> = data.account.device_id.into();
|
||||
let device_id: Arc<DeviceId> = device_id.into();
|
||||
|
||||
@@ -244,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)?;
|
||||
@@ -382,9 +395,21 @@ pub struct RoomKeyCounts {
|
||||
/// Backup keys and information we load from the store.
|
||||
pub struct BackupKeys {
|
||||
/// The recovery key as a base64 encoded string.
|
||||
pub recovery_key: String,
|
||||
recovery_key: Arc<BackupRecoveryKey>,
|
||||
/// The version that is used with the recovery key.
|
||||
pub backup_version: String,
|
||||
backup_version: String,
|
||||
}
|
||||
|
||||
impl BackupKeys {
|
||||
/// Get the recovery key that we're holding on to.
|
||||
pub fn recovery_key(&self) -> Arc<BackupRecoveryKey> {
|
||||
self.recovery_key.clone()
|
||||
}
|
||||
|
||||
/// Get the backups version that we're holding on to.
|
||||
pub fn backup_version(&self) -> String {
|
||||
self.backup_version.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<matrix_sdk_crypto::store::BackupKeys> for BackupKeys {
|
||||
@@ -392,7 +417,11 @@ impl TryFrom<matrix_sdk_crypto::store::BackupKeys> for BackupKeys {
|
||||
|
||||
fn try_from(keys: matrix_sdk_crypto::store::BackupKeys) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
recovery_key: keys.recovery_key.ok_or(())?.to_base64(),
|
||||
recovery_key: BackupRecoveryKey {
|
||||
inner: keys.recovery_key.ok_or(())?,
|
||||
passphrase_info: None,
|
||||
}
|
||||
.into(),
|
||||
backup_version: keys.backup_version.ok_or(())?,
|
||||
})
|
||||
}
|
||||
@@ -521,7 +550,9 @@ mod test {
|
||||
"backed_up":true
|
||||
}
|
||||
],
|
||||
"pickle_key":"\u{0011}$xJ_N8$>{\u{0005}iJoF03eBVt\u{000e}rUU\\,GYc7J",
|
||||
"pickle_key": [17, 36, 120, 74, 95, 78, 56, 36, 62, 123, 5, 105, 74,
|
||||
111, 70, 48, 51, 101, 66, 86, 116, 14, 114, 85, 85,
|
||||
92, 44, 71, 89, 99, 55, 74],
|
||||
"backup_version":"3",
|
||||
"backup_recovery_key":"EsTHScmRV5oT1WBhe2mj2Gn3odeYantZ4NEk7L51p6L8hrmB",
|
||||
"cross_signing":{
|
||||
@@ -552,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();
|
||||
+177
-82
@@ -1,15 +1,16 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
convert::TryInto,
|
||||
io::Cursor,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
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,
|
||||
@@ -30,10 +31,8 @@ use ruma::{
|
||||
},
|
||||
IncomingResponse,
|
||||
},
|
||||
events::{
|
||||
key::verification::VerificationMethod, room::encrypted::OriginalSyncRoomEncryptedEvent,
|
||||
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, EventContent,
|
||||
},
|
||||
events::{key::verification::VerificationMethod, AnySyncMessageLikeEvent},
|
||||
serde::Raw,
|
||||
DeviceKeyAlgorithm, EventId, OwnedTransactionId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -45,11 +44,11 @@ use crate::{
|
||||
error::{CryptoStoreError, DecryptionError, SecretImportError, SignatureError},
|
||||
parse_user_id,
|
||||
responses::{response_from_string, OutgoingVerificationRequest, OwnedResponse},
|
||||
BackupKeys, BootstrapCrossSigningResult, ConfirmVerificationResult, CrossSigningKeyExport,
|
||||
CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, KeyImportError,
|
||||
KeysImportResult, MegolmV1BackupKey, ProgressListener, QrCode, Request, RequestType,
|
||||
RequestVerificationResult, RoomKeyCounts, ScanResult, SignatureUploadRequest, StartSasResult,
|
||||
UserIdentity, Verification, VerificationRequest,
|
||||
BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, ConfirmVerificationResult,
|
||||
CrossSigningKeyExport, CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists,
|
||||
KeyImportError, KeysImportResult, MegolmV1BackupKey, ProgressListener, QrCode, Request,
|
||||
RequestType, RequestVerificationResult, RoomKeyCounts, ScanResult, SignatureUploadRequest,
|
||||
StartSasResult, UserIdentity, Verification, VerificationRequest,
|
||||
};
|
||||
|
||||
/// A high level state machine that handles E2EE for Matrix.
|
||||
@@ -92,8 +91,8 @@ impl OlmMachine {
|
||||
let device_id = device_id.into();
|
||||
let runtime = Runtime::new().expect("Couldn't create a tokio runtime");
|
||||
|
||||
let store = Box::new(
|
||||
matrix_sdk_sled::CryptoStore::open_with_passphrase(path, passphrase.as_deref())
|
||||
let store = Arc::new(
|
||||
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
|
||||
@@ -133,28 +132,53 @@ impl OlmMachine {
|
||||
}
|
||||
|
||||
/// Get a cross signing user identity for the given user ID.
|
||||
pub fn get_identity(&self, user_id: &str) -> Result<Option<UserIdentity>, CryptoStoreError> {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The unique id of the user that the identity belongs to
|
||||
///
|
||||
/// * `timeout` - The time in seconds we should wait before returning if
|
||||
/// the user's device list has been marked as stale. Passing a 0 as the
|
||||
/// timeout means that we won't wait at all. **Note**, this assumes that
|
||||
/// the requests from [`OlmMachine::outgoing_requests`] are being processed
|
||||
/// and sent out. Namely, this waits for a `/keys/query` response to be
|
||||
/// received.
|
||||
pub fn get_identity(
|
||||
&self,
|
||||
user_id: &str,
|
||||
timeout: u32,
|
||||
) -> Result<Option<UserIdentity>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
|
||||
Ok(if let Some(identity) = self.runtime.block_on(self.inner.get_identity(&user_id))? {
|
||||
Some(self.runtime.block_on(UserIdentity::from_rust(identity))?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
|
||||
|
||||
Ok(
|
||||
if let Some(identity) =
|
||||
self.runtime.block_on(self.inner.get_identity(&user_id, timeout))?
|
||||
{
|
||||
Some(self.runtime.block_on(UserIdentity::from_rust(identity))?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if a user identity is considered to be verified by us.
|
||||
pub fn is_identity_verified(&self, user_id: &str) -> Result<bool, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
|
||||
Ok(if let Some(identity) = self.runtime.block_on(self.inner.get_identity(&user_id))? {
|
||||
match identity {
|
||||
UserIdentities::Own(i) => i.is_verified(),
|
||||
UserIdentities::Other(i) => i.verified(),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
})
|
||||
Ok(
|
||||
if let Some(identity) =
|
||||
self.runtime.block_on(self.inner.get_identity(&user_id, None))?
|
||||
{
|
||||
match identity {
|
||||
UserIdentities::Own(i) => i.is_verified(),
|
||||
UserIdentities::Other(i) => i.is_verified(),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Manually the user with the given user ID.
|
||||
@@ -171,7 +195,7 @@ impl OlmMachine {
|
||||
pub fn verify_identity(&self, user_id: &str) -> Result<SignatureUploadRequest, SignatureError> {
|
||||
let user_id = UserId::parse(user_id)?;
|
||||
|
||||
let user_identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
|
||||
let user_identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
|
||||
|
||||
if let Some(user_identity) = user_identity {
|
||||
Ok(match user_identity {
|
||||
@@ -191,16 +215,26 @@ impl OlmMachine {
|
||||
/// * `user_id` - The id of the device owner.
|
||||
///
|
||||
/// * `device_id` - The id of the device itself.
|
||||
///
|
||||
/// * `timeout` - The time in seconds we should wait before returning if
|
||||
/// the user's device list has been marked as stale. Passing a 0 as the
|
||||
/// timeout means that we won't wait at all. **Note**, this assumes that
|
||||
/// the requests from [`OlmMachine::outgoing_requests`] are being processed
|
||||
/// and sent out. Namely, this waits for a `/keys/query` response to be
|
||||
/// received.
|
||||
pub fn get_device(
|
||||
&self,
|
||||
user_id: &str,
|
||||
device_id: &str,
|
||||
timeout: u32,
|
||||
) -> Result<Option<Device>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
|
||||
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
|
||||
|
||||
Ok(self
|
||||
.runtime
|
||||
.block_on(self.inner.get_device(&user_id, device_id.into()))?
|
||||
.block_on(self.inner.get_device(&user_id, device_id.into(), timeout))?
|
||||
.map(|d| d.into()))
|
||||
}
|
||||
|
||||
@@ -223,7 +257,8 @@ impl OlmMachine {
|
||||
device_id: &str,
|
||||
) -> Result<SignatureUploadRequest, SignatureError> {
|
||||
let user_id = UserId::parse(user_id)?;
|
||||
let device = self.runtime.block_on(self.inner.get_device(&user_id, device_id.into()))?;
|
||||
let device =
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?;
|
||||
|
||||
if let Some(device) = device {
|
||||
Ok(self.runtime.block_on(device.verify())?.into())
|
||||
@@ -232,7 +267,7 @@ impl OlmMachine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark the device of the given user with the given device id as trusted.
|
||||
/// Mark the device of the given user with the given device ID as trusted.
|
||||
pub fn mark_device_as_trusted(
|
||||
&self,
|
||||
user_id: &str,
|
||||
@@ -240,7 +275,8 @@ impl OlmMachine {
|
||||
) -> Result<(), CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
|
||||
let device = self.runtime.block_on(self.inner.get_device(&user_id, device_id.into()))?;
|
||||
let device =
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?;
|
||||
|
||||
if let Some(device) = device {
|
||||
self.runtime.block_on(device.set_local_trust(LocalTrust::Verified))?;
|
||||
@@ -254,12 +290,24 @@ impl OlmMachine {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The id of the device owner.
|
||||
pub fn get_user_devices(&self, user_id: &str) -> Result<Vec<Device>, CryptoStoreError> {
|
||||
///
|
||||
/// * `timeout` - The time in seconds we should wait before returning if
|
||||
/// the user's device list has been marked as stale. Passing a 0 as the
|
||||
/// timeout means that we won't wait at all. **Note**, this assumes that
|
||||
/// the requests from [`OlmMachine::outgoing_requests`] are being processed
|
||||
/// and sent out. Namely, this waits for a `/keys/query` response to be
|
||||
/// received.
|
||||
pub fn get_user_devices(
|
||||
&self,
|
||||
user_id: &str,
|
||||
timeout: u32,
|
||||
) -> Result<Vec<Device>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
|
||||
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
|
||||
Ok(self
|
||||
.runtime
|
||||
.block_on(self.inner.get_user_devices(&user_id))?
|
||||
.block_on(self.inner.get_user_devices(&user_id, timeout))?
|
||||
.devices()
|
||||
.map(|d| d.into())
|
||||
.collect())
|
||||
@@ -424,7 +472,7 @@ impl OlmMachine {
|
||||
/// [mark_request_as_sent()](#method.mark_request_as_sent) method.
|
||||
///
|
||||
/// This method should be called every time before a call to
|
||||
/// [`share_group_session()`](#method.share_group_session) is made.
|
||||
/// [`share_room_key()`](#method.share_room_key) is made.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -469,13 +517,13 @@ impl OlmMachine {
|
||||
users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect();
|
||||
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
let requests = self.runtime.block_on(self.inner.share_group_session(
|
||||
let requests = self.runtime.block_on(self.inner.share_room_key(
|
||||
&room_id,
|
||||
users.iter().map(Deref::deref),
|
||||
EncryptionSettings::default(),
|
||||
))?;
|
||||
|
||||
Ok(requests.into_iter().map(|r| (&*r).into()).collect())
|
||||
Ok(requests.into_iter().map(|r| r.as_ref().into()).collect())
|
||||
}
|
||||
|
||||
/// Encrypt the given event with the given type and content for the given
|
||||
@@ -493,7 +541,7 @@ impl OlmMachine {
|
||||
/// method. This method call should be locked per call.
|
||||
///
|
||||
/// 2. Share a room key with all the room members using the
|
||||
/// [`share_group_session()`](#method.share_group_session). This method
|
||||
/// [`share_room_key()`](#method.share_room_key). This method
|
||||
/// call should be locked per room.
|
||||
///
|
||||
/// 3. Encrypt the event using this method.
|
||||
@@ -518,12 +566,11 @@ impl OlmMachine {
|
||||
content: &str,
|
||||
) -> Result<String, CryptoStoreError> {
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
let content: Box<RawValue> = serde_json::from_str(content)?;
|
||||
let content: Value = serde_json::from_str(content)?;
|
||||
|
||||
let content = AnyMessageLikeEventContent::from_parts(event_type, &content)?;
|
||||
let encrypted_content = self
|
||||
.runtime
|
||||
.block_on(self.inner.encrypt(&room_id, content))
|
||||
.block_on(self.inner.encrypt_room_event_raw(&room_id, content, event_type))
|
||||
.expect("Encrypting an event produced an error");
|
||||
|
||||
Ok(serde_json::to_string(&encrypted_content)?)
|
||||
@@ -552,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))?;
|
||||
@@ -563,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![],
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -590,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) =
|
||||
@@ -611,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,
|
||||
@@ -630,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,
|
||||
@@ -658,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.
|
||||
@@ -682,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>,
|
||||
@@ -691,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
|
||||
@@ -807,7 +859,7 @@ impl OlmMachine {
|
||||
) -> Result<Option<String>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
|
||||
let identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
|
||||
let identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
|
||||
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
|
||||
@@ -851,7 +903,7 @@ impl OlmMachine {
|
||||
let event_id = EventId::parse(event_id)?;
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
|
||||
let identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
|
||||
let identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
|
||||
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
|
||||
@@ -891,7 +943,7 @@ impl OlmMachine {
|
||||
|
||||
Ok(
|
||||
if let Some(device) =
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into()))?
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?
|
||||
{
|
||||
let (verification, request) =
|
||||
self.runtime.block_on(device.request_verification_with_methods(methods));
|
||||
@@ -916,7 +968,8 @@ impl OlmMachine {
|
||||
&self,
|
||||
methods: Vec<String>,
|
||||
) -> Result<Option<RequestVerificationResult>, CryptoStoreError> {
|
||||
let identity = self.runtime.block_on(self.inner.get_identity(self.inner.user_id()))?;
|
||||
let identity =
|
||||
self.runtime.block_on(self.inner.get_identity(self.inner.user_id(), None))?;
|
||||
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
|
||||
@@ -1166,7 +1219,7 @@ impl OlmMachine {
|
||||
|
||||
Ok(
|
||||
if let Some(device) =
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into()))?
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?
|
||||
{
|
||||
let (sas, request) = self.runtime.block_on(device.start_verification())?;
|
||||
|
||||
@@ -1192,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
|
||||
@@ -1330,22 +1380,35 @@ impl OlmMachine {
|
||||
Ok(self.runtime.block_on(self.inner.backup_machine().room_key_counts())?.into())
|
||||
}
|
||||
|
||||
/// Store the recovery key in the cryptostore.
|
||||
/// Store the recovery key in the crypto store.
|
||||
///
|
||||
/// This is useful if the client wants to support gossiping of the backup
|
||||
/// key.
|
||||
pub fn save_recovery_key(
|
||||
&self,
|
||||
key: Option<String>,
|
||||
key: Option<Arc<BackupRecoveryKey>>,
|
||||
version: Option<String>,
|
||||
) -> Result<(), CryptoStoreError> {
|
||||
let key = key.map(|k| RecoveryKey::from_base64(&k)).transpose().ok().flatten();
|
||||
let key = key.map(|k| {
|
||||
// We need to clone here due to FFI limitations but RecoveryKey does
|
||||
// not want to expose clone since it's private key material.
|
||||
let mut encoded = k.to_base64();
|
||||
let key = RecoveryKey::from_base64(&encoded)
|
||||
.expect("Encoding and decoding from base64 should always work");
|
||||
encoded.zeroize();
|
||||
key
|
||||
});
|
||||
Ok(self.runtime.block_on(self.inner.backup_machine().save_recovery_key(key, version))?)
|
||||
}
|
||||
|
||||
/// Get the backup keys we have saved in our crypto store.
|
||||
pub fn get_backup_keys(&self) -> Result<Option<BackupKeys>, CryptoStoreError> {
|
||||
Ok(self.runtime.block_on(self.inner.backup_machine().get_backup_keys())?.try_into().ok())
|
||||
pub fn get_backup_keys(&self) -> Result<Option<Arc<BackupKeys>>, CryptoStoreError> {
|
||||
Ok(self
|
||||
.runtime
|
||||
.block_on(self.inner.backup_machine().get_backup_keys())?
|
||||
.try_into()
|
||||
.ok()
|
||||
.map(Arc::new))
|
||||
}
|
||||
|
||||
/// Sign the given message using our device key and if available cross
|
||||
@@ -1354,14 +1417,46 @@ impl OlmMachine {
|
||||
self.runtime
|
||||
.block_on(self.inner.sign(message))
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.into_iter().map(|(k, v)| (k.to_string(), v)).collect()))
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.to_string(),
|
||||
v.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.to_string(),
|
||||
match v {
|
||||
Ok(s) => s.to_base64(),
|
||||
Err(i) => i.source,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Check if the given backup has been verified by us or by another of our
|
||||
/// devices that we trust.
|
||||
pub fn verify_backup(&self, auth_data: &str) -> Result<bool, CryptoStoreError> {
|
||||
let auth_data = serde_json::from_str(auth_data)?;
|
||||
Ok(self.runtime.block_on(self.inner.backup_machine().verify_backup(auth_data))?)
|
||||
///
|
||||
/// The `backup_info` should be a JSON encoded object with the following
|
||||
/// format:
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
|
||||
/// "auth_data": {
|
||||
/// "public_key":"XjhWTCjW7l59pbfx9tlCBQolfnIQWARoKOzjTOPSlWM",
|
||||
/// "signatures": {}
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn verify_backup(&self, backup_info: &str) -> Result<bool, CryptoStoreError> {
|
||||
let backup_info = serde_json::from_str(backup_info)?;
|
||||
|
||||
Ok(self
|
||||
.runtime
|
||||
.block_on(self.inner.backup_machine().verify_backup(backup_info, false))?
|
||||
.trusted())
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -15,7 +15,7 @@ interface MigrationError {
|
||||
};
|
||||
|
||||
callback interface Logger {
|
||||
void log(string log_line);
|
||||
void log(string logLine);
|
||||
};
|
||||
|
||||
callback interface ProgressListener {
|
||||
@@ -280,17 +280,17 @@ interface OlmMachine {
|
||||
string encrypt([ByRef] string room_id, [ByRef] string event_type, [ByRef] string content);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
UserIdentity? get_identity([ByRef] string user_id);
|
||||
UserIdentity? get_identity([ByRef] string user_id, u32 timeout);
|
||||
[Throws=SignatureError]
|
||||
SignatureUploadRequest verify_identity([ByRef] string user_id);
|
||||
[Throws=CryptoStoreError]
|
||||
Device? get_device([ByRef] string user_id, [ByRef] string device_id);
|
||||
Device? get_device([ByRef] string user_id, [ByRef] string device_id, u32 timeout);
|
||||
[Throws=CryptoStoreError]
|
||||
void mark_device_as_trusted([ByRef] string user_id, [ByRef] string device_id);
|
||||
[Throws=SignatureError]
|
||||
SignatureUploadRequest verify_device([ByRef] string user_id, [ByRef] string device_id);
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Device> get_user_devices([ByRef] string user_id);
|
||||
sequence<Device> get_user_devices([ByRef] string user_id, u32 timeout);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
boolean is_user_tracked([ByRef] string user_id);
|
||||
@@ -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
|
||||
);
|
||||
@@ -390,7 +390,7 @@ interface OlmMachine {
|
||||
[Throws=CryptoStoreError]
|
||||
Request? backup_room_keys();
|
||||
[Throws=CryptoStoreError]
|
||||
void save_recovery_key(string? key, string? version);
|
||||
void save_recovery_key(BackupRecoveryKey? key, string? version);
|
||||
[Throws=CryptoStoreError]
|
||||
RoomKeyCounts room_key_counts();
|
||||
[Throws=CryptoStoreError]
|
||||
@@ -412,9 +412,9 @@ dictionary MegolmV1BackupKey {
|
||||
string backup_algorithm;
|
||||
};
|
||||
|
||||
dictionary BackupKeys {
|
||||
string recovery_key;
|
||||
string backup_version;
|
||||
interface BackupKeys {
|
||||
BackupRecoveryKey recovery_key();
|
||||
string backup_version();
|
||||
};
|
||||
|
||||
dictionary RoomKeyCounts {
|
||||
@@ -451,7 +451,7 @@ dictionary MigrationData {
|
||||
sequence<PickledInboundGroupSession> inbound_group_sessions;
|
||||
string? backup_version;
|
||||
string? backup_recovery_key;
|
||||
string pickle_key;
|
||||
sequence<u8> pickle_key;
|
||||
CrossSigningKeyExport cross_signing;
|
||||
sequence<string> tracked_users;
|
||||
};
|
||||
+1
@@ -132,6 +132,7 @@ impl From<OutgoingRequest> for Request {
|
||||
let body = json!({
|
||||
"device_keys": u.device_keys,
|
||||
"one_time_keys": u.one_time_keys,
|
||||
"fallback_keys": u.fallback_keys,
|
||||
});
|
||||
|
||||
Request::KeysUpload {
|
||||
@@ -0,0 +1,2 @@
|
||||
[bindings.swift]
|
||||
module_name = "MatrixSDKCrypto"
|
||||
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
@@ -0,0 +1,3 @@
|
||||
/docs
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
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"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
version = "0.1.0-alpha.0"
|
||||
publish = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ['-Oz']
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["tracing", "qrcode"]
|
||||
qrcode = ["matrix-sdk-crypto/qrcode", "dep:matrix-sdk-qrcode"]
|
||||
tracing = []
|
||||
|
||||
[dependencies]
|
||||
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.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"
|
||||
@@ -0,0 +1,59 @@
|
||||
# `matrix-sdk-crypto-js`
|
||||
|
||||
Welcome to the [WebAssembly] + JavaScript binding for the Rust
|
||||
[`matrix-sdk-crypto`] library! WebAssembly can run anywhere, but these
|
||||
bindings are designed to run on a JavaScript host. These bindings are
|
||||
part of the [`matrix-rust-sdk`] project, which is a library
|
||||
implementation of a [Matrix] client-server.
|
||||
|
||||
`matrix-sdk-crypto` is a no-network-IO implementation of a state
|
||||
machine, named `OlmMachine`, that handles E2EE ([End-to-End
|
||||
Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
|
||||
[Matrix] clients.
|
||||
|
||||
## Usage
|
||||
|
||||
These WebAssembly bindings are written in [Rust]. To build them, you
|
||||
need to install the Rust compiler, see [the Install Rust
|
||||
Page](https://www.rust-lang.org/tools/install). Then, the workflow is
|
||||
pretty classical by using [npm], see [the Downloading and installing
|
||||
Node.js and npm
|
||||
Page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
|
||||
|
||||
Once the Rust compiler, Node.js and npm are installed, you can run the
|
||||
following commands:
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
$ npm run build
|
||||
$ npm run test
|
||||
```
|
||||
|
||||
A `matrix_sdk_crypto.js`, `matrix_sdk_crypto.d.ts` and a
|
||||
`matrix_sdk_crypto_bg.wasm` files should be generated in the `pkg/`
|
||||
directory.
|
||||
|
||||
TBD
|
||||
|
||||
## Documentation
|
||||
|
||||
[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
|
||||
```
|
||||
|
||||
The documentation is generated in the `./docs` directory.
|
||||
|
||||
|
||||
|
||||
[WebAssembly]: https://webassembly.org/
|
||||
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
|
||||
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
|
||||
[Matrix]: https://matrix.org/
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[npm]: https://www.npmjs.com/
|
||||
@@ -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"
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@matrix-org/matrix-sdk-crypto-js",
|
||||
"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",
|
||||
"collaborators": [
|
||||
"Ivan Enderlin <ivane@element.io>"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
},
|
||||
"keywords": [
|
||||
"matrix",
|
||||
"chat",
|
||||
"messaging",
|
||||
"ruma",
|
||||
"nio"
|
||||
],
|
||||
"main": "matrix_sdk_crypto.js",
|
||||
"types": "pkg/matrix_sdk_crypto.d.ts",
|
||||
"files": [
|
||||
"pkg/matrix_sdk_crypto_bg.wasm",
|
||||
"pkg/matrix_sdk_crypto.js",
|
||||
"pkg/matrix_sdk_crypto.d.ts"
|
||||
],
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"fake-indexeddb": "^4.0",
|
||||
"jest": "^28.1.0",
|
||||
"typedoc": "^0.22.17",
|
||||
"wasm-pack": "^0.10.2",
|
||||
"yargs-parser": "~21.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"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 .",
|
||||
"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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//! Encryption types & siblings.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::events;
|
||||
|
||||
/// Settings for an encrypted room.
|
||||
///
|
||||
/// This determines the algorithm and rotation periods of a group
|
||||
/// session.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EncryptionSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EncryptionAlgorithm,
|
||||
|
||||
/// How long the session should be used before changing it,
|
||||
/// expressed in microseconds.
|
||||
#[wasm_bindgen(js_name = "rotationPeriod")]
|
||||
pub rotation_period: u64,
|
||||
|
||||
/// How many messages should be sent before changing the session.
|
||||
#[wasm_bindgen(js_name = "rotationPeriodMessages")]
|
||||
pub rotation_period_messages: u64,
|
||||
|
||||
/// The history visibility of the room when the session was
|
||||
/// 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 {
|
||||
fn default() -> Self {
|
||||
let default = matrix_sdk_crypto::olm::EncryptionSettings::default();
|
||||
|
||||
Self {
|
||||
algorithm: default.algorithm.into(),
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl EncryptionSettings {
|
||||
/// Create a new `EncryptionSettings` with default values.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> EncryptionSettings {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
|
||||
fn from(value: &EncryptionSettings) -> Self {
|
||||
let algorithm = value.algorithm.clone().into();
|
||||
|
||||
Self {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An encryption algorithm to be used to encrypt messages sent to a
|
||||
/// room.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EncryptionAlgorithm {
|
||||
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
|
||||
OlmV1Curve25519AesSha2,
|
||||
|
||||
/// Megolm version 1 using AES-256 and SHA-256.
|
||||
MegolmV1AesSha2,
|
||||
}
|
||||
|
||||
impl From<EncryptionAlgorithm> for matrix_sdk_crypto::types::EventEncryptionAlgorithm {
|
||||
fn from(value: EncryptionAlgorithm) -> Self {
|
||||
use EncryptionAlgorithm::*;
|
||||
|
||||
match value {
|
||||
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
_ => unreachable!("Unknown variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The verification state of the device that sent an event to us.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum VerificationState {
|
||||
/// The device is trusted.
|
||||
Trusted,
|
||||
|
||||
/// The device is not trusted.
|
||||
Untrusted,
|
||||
|
||||
/// The device is not known to us.
|
||||
UnknownDevice,
|
||||
}
|
||||
|
||||
impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState {
|
||||
fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self {
|
||||
use matrix_sdk_common::deserialized_responses::VerificationState::*;
|
||||
|
||||
match value {
|
||||
Trusted => Self::Trusted,
|
||||
Untrusted => Self::Untrusted,
|
||||
UnknownDevice => Self::UnknownDevice,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//! Types related to events.
|
||||
|
||||
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Who can see a room's history.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HistoryVisibility {
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they were invited onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *invite* or *join*.
|
||||
Invited,
|
||||
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they joined the room onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *join*.
|
||||
Joined,
|
||||
|
||||
/// Previous events are always accessible to newly joined members.
|
||||
///
|
||||
/// All events in the room are accessible, even those sent when
|
||||
/// the member was not a part of the room.
|
||||
Shared,
|
||||
|
||||
/// All events while this is the `HistoryVisibility` value may be
|
||||
/// shared by any participating homeserver with anyone, regardless
|
||||
/// of whether they have ever joined the room.
|
||||
WorldReadable,
|
||||
}
|
||||
|
||||
impl From<HistoryVisibility> for RumaHistoryVisibility {
|
||||
fn from(value: HistoryVisibility) -> Self {
|
||||
use HistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RumaHistoryVisibility> for HistoryVisibility {
|
||||
fn from(value: RumaHistoryVisibility) -> Self {
|
||||
use RumaHistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
_ => unreachable!("Unknown variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::future::Future;
|
||||
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::{JsValue, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
pub(crate) fn future_to_promise<F, T>(future: F) -> Promise
|
||||
where
|
||||
F: Future<Output = Result<T, anyhow::Error>> + 'static,
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
let mut future = Some(future);
|
||||
|
||||
Promise::new(&mut |resolve, reject| {
|
||||
let future = future.take().unwrap_throw();
|
||||
|
||||
spawn_local(async move {
|
||||
match future.await {
|
||||
Ok(value) => resolve.call1(&JsValue::UNDEFINED, &value.into()).unwrap_throw(),
|
||||
Err(value) => {
|
||||
reject.call1(&JsValue::UNDEFINED, &value.to_string().into()).unwrap_throw()
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
|
||||
//! events, keys, rooms, servers, users and URIs.
|
||||
|
||||
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
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserId {
|
||||
pub(crate) inner: ruma::OwnedUserId,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(ruma::OwnedUserId => UserId);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl UserId {
|
||||
/// Parse/validate and create a new `UserId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> Result<UserId, JsError> {
|
||||
Ok(Self::from(ruma::UserId::parse(id)?))
|
||||
}
|
||||
|
||||
/// Returns the user's localpart.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn localpart(&self) -> String {
|
||||
self.inner.localpart().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the server name of the user ID.
|
||||
#[wasm_bindgen(getter, js_name = "serverName")]
|
||||
pub fn server_name(&self) -> ServerName {
|
||||
ServerName { inner: self.inner.server_name().to_owned() }
|
||||
}
|
||||
|
||||
/// Whether this user ID is a historical one.
|
||||
///
|
||||
/// A historical user ID is one that doesn't conform to the latest
|
||||
/// specification of the user ID grammar but is still accepted
|
||||
/// because it was previously allowed.
|
||||
#[wasm_bindgen(js_name = "isHistorical")]
|
||||
pub fn is_historical(&self) -> bool {
|
||||
self.inner.is_historical()
|
||||
}
|
||||
|
||||
/// Return the user 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()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix key ID.
|
||||
///
|
||||
/// Device identifiers in Matrix are completely opaque character
|
||||
/// sequences. This type is provided simply for its semantic value.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeviceId {
|
||||
pub(crate) inner: ruma::OwnedDeviceId,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(ruma::OwnedDeviceId => DeviceId);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceId {
|
||||
/// Create a new `DeviceId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> DeviceId {
|
||||
Self::from(ruma::OwnedDeviceId::from(id))
|
||||
}
|
||||
|
||||
/// Return the device 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()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoomId {
|
||||
pub(crate) inner: ruma::OwnedRoomId,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(ruma::OwnedRoomId => RoomId);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoomId {
|
||||
/// Parse/validate and create a new `RoomId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> Result<RoomId, JsError> {
|
||||
Ok(Self::from(ruma::RoomId::parse(id)?))
|
||||
}
|
||||
|
||||
/// Returns the user's localpart.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn localpart(&self) -> String {
|
||||
self.inner.localpart().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the server name of the room ID.
|
||||
#[wasm_bindgen(getter, js_name = "serverName")]
|
||||
pub fn server_name(&self) -> ServerName {
|
||||
ServerName { inner: self.inner.server_name().to_owned() }
|
||||
}
|
||||
|
||||
/// Return the room 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()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix-spec compliant [server name].
|
||||
///
|
||||
/// It consists of a host and an optional port (separated by a colon if
|
||||
/// present).
|
||||
///
|
||||
/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct ServerName {
|
||||
inner: ruma::OwnedServerName,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ServerName {
|
||||
/// Parse/validate and create a new `ServerName`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(name: &str) -> Result<ServerName, JsError> {
|
||||
Ok(Self { inner: ruma::ServerName::parse(name)? })
|
||||
}
|
||||
|
||||
/// Returns the host of the server name.
|
||||
///
|
||||
/// That is: Return the part of the server before `:<port>` or the
|
||||
/// full server name if there is no port.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn host(&self) -> String {
|
||||
self.inner.host().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the port of the server name if any.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn port(&self) -> Option<u16> {
|
||||
self.inner.port()
|
||||
}
|
||||
|
||||
/// Returns true if and only if the server name is an IPv4 or IPv6
|
||||
/// address.
|
||||
#[wasm_bindgen(js_name = "isIpLiteral")]
|
||||
pub fn is_ip_literal(&self) -> bool {
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![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 wasm_bindgen::prelude::*;
|
||||
|
||||
/// Run some stuff when the Wasm module is instantiated.
|
||||
///
|
||||
/// Right now, it does the following:
|
||||
///
|
||||
/// * Redirect Rust panics to JavaScript console.
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn start() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
@@ -0,0 +1,731 @@
|
||||
//! The crypto specific Olm objects.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
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::{
|
||||
device, encryption,
|
||||
future::future_to_promise,
|
||||
identifiers, identities,
|
||||
js::downcast,
|
||||
olm, requests,
|
||||
requests::OutgoingRequest,
|
||||
responses::{self, response_from_string},
|
||||
store, sync_events, types, verification, vodozemac,
|
||||
};
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol
|
||||
/// used for Matrix end to end encryption.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OlmMachine {
|
||||
inner: matrix_sdk_crypto::OlmMachine,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl OlmMachine {
|
||||
/// Create a new memory based `OlmMachine`.
|
||||
///
|
||||
/// The created machine will keep the encryption keys only in
|
||||
/// memory and once the objects is dropped, the keys will be lost.
|
||||
///
|
||||
/// `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,
|
||||
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: 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
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// The unique user ID that owns this `OlmMachine` instance.
|
||||
#[wasm_bindgen(getter, js_name = "userId")]
|
||||
pub fn user_id(&self) -> identifiers::UserId {
|
||||
identifiers::UserId::from(self.inner.user_id().to_owned())
|
||||
}
|
||||
|
||||
/// The unique device ID that identifies this `OlmMachine`.
|
||||
#[wasm_bindgen(getter, js_name = "deviceId")]
|
||||
pub fn device_id(&self) -> identifiers::DeviceId {
|
||||
identifiers::DeviceId::from(self.inner.device_id().to_owned())
|
||||
}
|
||||
|
||||
/// Get the public parts of our Olm identity keys.
|
||||
#[wasm_bindgen(getter, js_name = "identityKeys")]
|
||||
pub fn identity_keys(&self) -> vodozemac::IdentityKeys {
|
||||
self.inner.identity_keys().into()
|
||||
}
|
||||
|
||||
/// Get the display name of our own device.
|
||||
#[wasm_bindgen(getter, js_name = "displayName")]
|
||||
pub fn display_name(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move { Ok(me.display_name().await?) })
|
||||
}
|
||||
|
||||
/// Get all the tracked users of our own device.
|
||||
///
|
||||
/// Returns a `Set<UserId>`.
|
||||
#[wasm_bindgen(js_name = "trackedUsers")]
|
||||
pub fn tracked_users(&self) -> Set {
|
||||
let set = Set::new(&JsValue::UNDEFINED);
|
||||
|
||||
for user in self.inner.tracked_users() {
|
||||
set.add(&identifiers::UserId::from(user).into());
|
||||
}
|
||||
|
||||
set
|
||||
}
|
||||
|
||||
/// Update the tracked users.
|
||||
///
|
||||
/// `users` is an iterator over user IDs that should be marked for
|
||||
/// tracking.
|
||||
///
|
||||
/// This will mark users that weren't seen before for a key query
|
||||
/// and tracking.
|
||||
///
|
||||
/// If the user is already known to the Olm machine, it will not
|
||||
/// be considered for a key query.
|
||||
#[wasm_bindgen(js_name = "updateTrackedUsers")]
|
||||
pub fn update_tracked_users(&self, users: &Array) -> Result<Promise, JsError> {
|
||||
let users = users
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
me.update_tracked_users(users.iter().map(AsRef::as_ref)).await;
|
||||
Ok(JsValue::UNDEFINED)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Handle to-device events and one-time key counts from a sync
|
||||
/// response.
|
||||
///
|
||||
/// This will decrypt and handle to-device events returning the
|
||||
/// decrypted versions of them.
|
||||
///
|
||||
/// To decrypt an event from the room timeline call
|
||||
/// `decrypt_room_event`.
|
||||
#[wasm_bindgen(js_name = "receiveSyncChanges")]
|
||||
pub fn receive_sync_changes(
|
||||
&self,
|
||||
to_device_events: &str,
|
||||
changed_devices: &sync_events::DeviceLists,
|
||||
one_time_key_counts: &Map,
|
||||
unused_fallback_keys: &Set,
|
||||
) -> Result<Promise, JsError> {
|
||||
let to_device_events = serde_json::from_str(to_device_events)?;
|
||||
let changed_devices = changed_devices.inner.clone();
|
||||
let one_time_key_counts: BTreeMap<DeviceKeyAlgorithm, UInt> = one_time_key_counts
|
||||
.entries()
|
||||
.into_iter()
|
||||
.filter_map(|js_value| {
|
||||
let pair = Array::from(&js_value.ok()?);
|
||||
let (key, value) = (
|
||||
DeviceKeyAlgorithm::from(pair.at(0).as_string()?),
|
||||
UInt::new(pair.at(1).as_f64()? as u64)?,
|
||||
);
|
||||
|
||||
Some((key, value))
|
||||
})
|
||||
.collect();
|
||||
let unused_fallback_keys: Option<Vec<DeviceKeyAlgorithm>> = Some(
|
||||
unused_fallback_keys
|
||||
.values()
|
||||
.into_iter()
|
||||
.filter_map(|js_value| Some(DeviceKeyAlgorithm::from(js_value.ok()?.as_string()?)))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.receive_sync_changes(
|
||||
to_device_events,
|
||||
&changed_devices,
|
||||
&one_time_key_counts,
|
||||
unused_fallback_keys.as_deref(),
|
||||
)
|
||||
.await?,
|
||||
)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the outgoing requests that need to be sent out.
|
||||
///
|
||||
/// This returns a list of `JsValue` to represent either:
|
||||
/// * `KeysUploadRequest`,
|
||||
/// * `KeysQueryRequest`,
|
||||
/// * `KeysClaimRequest`,
|
||||
/// * `ToDeviceRequest`,
|
||||
/// * `SignatureUploadRequest`,
|
||||
/// * `RoomMessageRequest` or
|
||||
/// * `KeysBackupRequest`.
|
||||
///
|
||||
/// Those requests need to be sent out to the server and the
|
||||
/// responses need to be passed back to the state machine using
|
||||
/// `mark_request_as_sent`.
|
||||
#[wasm_bindgen(js_name = "outgoingRequests")]
|
||||
pub fn outgoing_requests(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(me
|
||||
.outgoing_requests()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(OutgoingRequest)
|
||||
.map(TryFrom::try_from)
|
||||
.collect::<Result<Vec<JsValue>, _>>()?
|
||||
.into_iter()
|
||||
.collect::<Array>())
|
||||
})
|
||||
}
|
||||
|
||||
/// Mark the request with the given request ID as sent (see
|
||||
/// `outgoing_requests`).
|
||||
///
|
||||
/// Arguments are:
|
||||
///
|
||||
/// * `request_id` represents the unique ID of the request that was sent
|
||||
/// out. This is needed to couple the response with the now sent out
|
||||
/// request.
|
||||
/// * `response_type` represents the type of the request that was sent out.
|
||||
/// * `response` represents the response that was received from the server
|
||||
/// after the outgoing request was sent out.
|
||||
#[wasm_bindgen(js_name = "markRequestAsSent")]
|
||||
pub fn mark_request_as_sent(
|
||||
&self,
|
||||
request_id: &str,
|
||||
request_type: requests::RequestType,
|
||||
response: &str,
|
||||
) -> Result<Promise, JsError> {
|
||||
let transaction_id = OwnedTransactionId::from(request_id);
|
||||
let response = response_from_string(response)?;
|
||||
let incoming_response = responses::OwnedResponse::try_from((request_type, response))?;
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(me.mark_request_as_sent(&transaction_id, &incoming_response).await.map(|_| true)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Encrypt a room message for the given room.
|
||||
///
|
||||
/// Beware that a room key needs to be shared before this
|
||||
/// method can be called using the `share_room_key` method.
|
||||
///
|
||||
/// `room_id` is the ID of the room for which the message should
|
||||
/// be encrypted. `event_type` is the type of the event. `content`
|
||||
/// is the plaintext content of the message that should be
|
||||
/// encrypted.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if a group session for the given room wasn't shared
|
||||
/// beforehand.
|
||||
#[wasm_bindgen(js_name = "encryptRoomEvent")]
|
||||
pub fn encrypt_room_event(
|
||||
&self,
|
||||
room_id: &identifiers::RoomId,
|
||||
event_type: String,
|
||||
content: &str,
|
||||
) -> Result<Promise, JsError> {
|
||||
let room_id = room_id.inner.clone();
|
||||
let content: JsonValue = serde_json::from_str(content)?;
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.encrypt_room_event_raw(&room_id, content, event_type.as_ref()).await?,
|
||||
)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Decrypt an event from a room timeline.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event`, the event that should be decrypted.
|
||||
/// * `room_id`, the ID of the room where the event was sent to.
|
||||
#[wasm_bindgen(js_name = "decryptRoomEvent")]
|
||||
pub fn decrypt_room_event(
|
||||
&self,
|
||||
event: &str,
|
||||
room_id: &identifiers::RoomId,
|
||||
) -> Result<Promise, JsError> {
|
||||
let event: Raw<_> = serde_json::from_str(event)?;
|
||||
let room_id = room_id.inner.clone();
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
let room_event = me.decrypt_room_event(&event, room_id.as_ref()).await?;
|
||||
|
||||
Ok(responses::DecryptedRoomEvent::from(room_event))
|
||||
}))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Returns true if a session was invalidated, false if there was
|
||||
/// no session to invalidate.
|
||||
#[wasm_bindgen(js_name = "invalidateGroupSession")]
|
||||
pub fn invalidate_group_session(&self, room_id: &identifiers::RoomId) -> Promise {
|
||||
let room_id = room_id.inner.clone();
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move { Ok(me.invalidate_group_session(&room_id).await?) })
|
||||
}
|
||||
|
||||
/// Get to-device requests to share a room key with users in a room.
|
||||
///
|
||||
/// `room_id` is the room ID. `users` is an array of `UserId`
|
||||
/// objects. `encryption_settings` are an `EncryptionSettings`
|
||||
/// object.
|
||||
#[wasm_bindgen(js_name = "shareRoomKey")]
|
||||
pub fn share_room_key(
|
||||
&self,
|
||||
room_id: &identifiers::RoomId,
|
||||
users: &Array,
|
||||
encryption_settings: &encryption::EncryptionSettings,
|
||||
) -> Result<Promise, JsError> {
|
||||
let room_id = room_id.inner.clone();
|
||||
let users = users
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
let encryption_settings =
|
||||
matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings);
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
|
||||
.await?,
|
||||
)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the a key claiming request for the user/device pairs that
|
||||
/// we are missing Olm sessions for.
|
||||
///
|
||||
/// Returns `NULL` if no key claiming request needs to be sent
|
||||
/// out, otherwise it returns an `Array` where the first key is
|
||||
/// the transaction ID as a string, and the second key is the keys
|
||||
/// claim request serialized to JSON.
|
||||
///
|
||||
/// Sessions need to be established between devices so group
|
||||
/// sessions for a room can be shared with them.
|
||||
///
|
||||
/// This should be called every time a group session needs to be
|
||||
/// shared as well as between sync calls. After a sync some
|
||||
/// devices may request room keys without us having a valid Olm
|
||||
/// session with them, making it impossible to server the room key
|
||||
/// request, thus it’s necessary to check for missing sessions
|
||||
/// between sync as well.
|
||||
///
|
||||
/// Note: Care should be taken that only one such request at a
|
||||
/// time is in flight, e.g. using a lock.
|
||||
///
|
||||
/// The response of a successful key claiming requests needs to be
|
||||
/// passed to the `OlmMachine` with the `mark_request_as_sent`.
|
||||
///
|
||||
/// `users` represents the list of users that we should check if
|
||||
/// we lack a session with one of their devices. This can be an
|
||||
/// empty iterator when calling this method between sync requests.
|
||||
#[wasm_bindgen(js_name = "getMissingSessions")]
|
||||
pub fn get_missing_sessions(&self, users: &Array) -> Result<Promise, JsError> {
|
||||
let users = users
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
match me.get_missing_sessions(users.iter().map(AsRef::as_ref)).await? {
|
||||
Some((transaction_id, keys_claim_request)) => {
|
||||
Ok(JsValue::from(requests::KeysClaimRequest::try_from((
|
||||
transaction_id.to_string(),
|
||||
&keys_claim_request,
|
||||
))?))
|
||||
}
|
||||
|
||||
None => Ok(JsValue::NULL),
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise::<_, device::UserDevices>(async move {
|
||||
Ok(me.get_user_devices(&user_id, None).await.map(Into::into)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise::<_, Option<device::Device>>(async move {
|
||||
Ok(me.get_device(&user_id, &device_id, None).await?.map(Into::into))
|
||||
})
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
//! Types to handle requests.
|
||||
|
||||
use js_sys::JsString;
|
||||
use matrix_sdk_crypto::{
|
||||
requests::{
|
||||
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 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]).
|
||||
///
|
||||
/// Publishes end-to-end encryption keys for the device.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysupload
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"device_keys": …, "one_time_keys": …, "fallback_keys": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeysUploadRequest {
|
||||
/// Create a new `KeysUploadRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysUploadRequest {
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::KeysUpload
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for a request to the `/keys/query` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Returns the current devices and identity keys for the given users.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysquery
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysQueryRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"timeout": …, "device_keys": …, "token": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeysQueryRequest {
|
||||
/// Create a new `KeysQueryRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysQueryRequest {
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::KeysQuery
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for a request to the `/keys/claim` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Claims one-time keys that can be used to establish 1-to-1 E2EE
|
||||
/// sessions.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysclaim
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysClaimRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"timeout": …, "one_time_keys": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeysClaimRequest {
|
||||
/// Create a new `KeysClaimRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysClaimRequest {
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::KeysClaim
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for a request to the `/sendToDevice` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Send an event to a single device or to a group of devices.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct ToDeviceRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"event_type": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ToDeviceRequest {
|
||||
/// Create a new `ToDeviceRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> ToDeviceRequest {
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::ToDevice
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for a request to the `/keys/signatures/upload` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Publishes cross-signing signatures for the user.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keyssignaturesupload
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct SignatureUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"signed_keys": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SignatureUploadRequest {
|
||||
/// Create a new `SignatureUploadRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> SignatureUploadRequest {
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::SignatureUpload
|
||||
}
|
||||
}
|
||||
|
||||
/// A customized owned request type for sending out room messages
|
||||
/// ([specification]).
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct RoomMessageRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"room_id": …, "txn_id": …, "content": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoomMessageRequest {
|
||||
/// Create a new `RoomMessageRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> RoomMessageRequest {
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::RoomMessage
|
||||
}
|
||||
}
|
||||
|
||||
/// A request that will back up a batch of room keys to the server
|
||||
/// ([specification]).
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3room_keyskeys
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysBackupRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"rooms": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
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: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::KeysBackup
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! request {
|
||||
($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 object = serde_json::Value::Object(map);
|
||||
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 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
|
||||
// `JsValue`.
|
||||
pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest);
|
||||
|
||||
impl TryFrom<OutgoingRequest> for JsValue {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(outgoing_request: OutgoingRequest) -> Result<Self, Self::Error> {
|
||||
let request_id = outgoing_request.0.request_id().to_string();
|
||||
|
||||
Ok(match outgoing_request.0.request() {
|
||||
OutgoingRequests::KeysUpload(request) => {
|
||||
JsValue::from(KeysUploadRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::KeysQuery(request) => {
|
||||
JsValue::from(KeysQueryRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::KeysClaim(request) => {
|
||||
JsValue::from(KeysClaimRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::ToDeviceRequest(request) => {
|
||||
JsValue::from(ToDeviceRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::SignatureUpload(request) => {
|
||||
JsValue::from(SignatureUploadRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::RoomMessage(request) => {
|
||||
JsValue::from(RoomMessageRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::KeysBackup(request) => {
|
||||
JsValue::from(KeysBackupRequest::try_from((request_id, request))?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent the type of a request.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum RequestType {
|
||||
/// Represents a `KeysUploadRequest`.
|
||||
KeysUpload,
|
||||
|
||||
/// Represents a `KeysQueryRequest`.
|
||||
KeysQuery,
|
||||
|
||||
/// Represents a `KeysClaimRequest`.
|
||||
KeysClaim,
|
||||
|
||||
/// Represents a `ToDeviceRequest`.
|
||||
ToDevice,
|
||||
|
||||
/// Represents a `SignatureUploadRequest`.
|
||||
SignatureUpload,
|
||||
|
||||
/// Represents a `RoomMessageRequest`.
|
||||
RoomMessage,
|
||||
|
||||
/// Represents a `KeysBackupRequest`.
|
||||
KeysBackup,
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
//! Types related to responses.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use js_sys::{Array, JsString};
|
||||
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
|
||||
use matrix_sdk_crypto::IncomingResponse;
|
||||
pub(crate) use ruma::api::client::{
|
||||
backup::add_backup_keys::v3::Response as KeysBackupResponse,
|
||||
keys::{
|
||||
claim_keys::v3::Response as KeysClaimResponse, get_keys::v3::Response as KeysQueryResponse,
|
||||
upload_keys::v3::Response as KeysUploadResponse,
|
||||
upload_signatures::v3::Response as SignatureUploadResponse,
|
||||
},
|
||||
message::send_message_event::v3::Response as RoomMessageResponse,
|
||||
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
|
||||
};
|
||||
use ruma::api::IncomingResponse as RumaIncomingResponse;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{encryption, identifiers, requests::RequestType};
|
||||
|
||||
pub(crate) fn response_from_string(body: &str) -> http::Result<http::Response<Vec<u8>>> {
|
||||
http::Response::builder().status(200).body(body.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
/// Intermediate private type to store an incoming owned response,
|
||||
/// without the need to manage lifetime.
|
||||
pub(crate) enum OwnedResponse {
|
||||
KeysUpload(KeysUploadResponse),
|
||||
KeysQuery(KeysQueryResponse),
|
||||
KeysClaim(KeysClaimResponse),
|
||||
ToDevice(ToDeviceResponse),
|
||||
SignatureUpload(SignatureUploadResponse),
|
||||
RoomMessage(RoomMessageResponse),
|
||||
KeysBackup(KeysBackupResponse),
|
||||
}
|
||||
|
||||
impl From<KeysUploadResponse> for OwnedResponse {
|
||||
fn from(response: KeysUploadResponse) -> Self {
|
||||
OwnedResponse::KeysUpload(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysQueryResponse> for OwnedResponse {
|
||||
fn from(response: KeysQueryResponse) -> Self {
|
||||
OwnedResponse::KeysQuery(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysClaimResponse> for OwnedResponse {
|
||||
fn from(response: KeysClaimResponse) -> Self {
|
||||
OwnedResponse::KeysClaim(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ToDeviceResponse> for OwnedResponse {
|
||||
fn from(response: ToDeviceResponse) -> Self {
|
||||
OwnedResponse::ToDevice(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignatureUploadResponse> for OwnedResponse {
|
||||
fn from(response: SignatureUploadResponse) -> Self {
|
||||
Self::SignatureUpload(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoomMessageResponse> for OwnedResponse {
|
||||
fn from(response: RoomMessageResponse) -> Self {
|
||||
OwnedResponse::RoomMessage(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysBackupResponse> for OwnedResponse {
|
||||
fn from(r: KeysBackupResponse) -> Self {
|
||||
Self::KeysBackup(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(RequestType, http::Response<Vec<u8>>)> for OwnedResponse {
|
||||
type Error = JsError;
|
||||
|
||||
fn try_from(
|
||||
(request_type, response): (RequestType, http::Response<Vec<u8>>),
|
||||
) -> Result<Self, Self::Error> {
|
||||
match request_type {
|
||||
RequestType::KeysUpload => {
|
||||
KeysUploadResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::KeysQuery => {
|
||||
KeysQueryResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::KeysClaim => {
|
||||
KeysClaimResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::ToDevice => {
|
||||
ToDeviceResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::SignatureUpload => {
|
||||
SignatureUploadResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::RoomMessage => {
|
||||
RoomMessageResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::KeysBackup => {
|
||||
KeysBackupResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
}
|
||||
.map_err(JsError::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
|
||||
fn from(response: &'a OwnedResponse) -> Self {
|
||||
match response {
|
||||
OwnedResponse::KeysUpload(response) => IncomingResponse::KeysUpload(response),
|
||||
OwnedResponse::KeysQuery(response) => IncomingResponse::KeysQuery(response),
|
||||
OwnedResponse::KeysClaim(response) => IncomingResponse::KeysClaim(response),
|
||||
OwnedResponse::ToDevice(response) => IncomingResponse::ToDevice(response),
|
||||
OwnedResponse::SignatureUpload(response) => IncomingResponse::SignatureUpload(response),
|
||||
OwnedResponse::RoomMessage(response) => IncomingResponse::RoomMessage(response),
|
||||
OwnedResponse::KeysBackup(response) => IncomingResponse::KeysBackup(response),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A decrypted room event.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct DecryptedRoomEvent {
|
||||
/// The JSON-encoded decrypted event.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub event: JsString,
|
||||
|
||||
encryption_info: Option<EncryptionInfo>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DecryptedRoomEvent {
|
||||
/// The user ID of the event sender, note this is untrusted data
|
||||
/// unless the `verification_state` is as well trusted.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn sender(&self) -> Option<identifiers::UserId> {
|
||||
Some(identifiers::UserId::from(self.encryption_info.as_ref()?.sender.clone()))
|
||||
}
|
||||
|
||||
/// The device ID of the device that sent us the event, note this
|
||||
/// is untrusted data unless `verification_state` is as well
|
||||
/// trusted.
|
||||
#[wasm_bindgen(getter, js_name = "senderDevice")]
|
||||
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
|
||||
Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone()))
|
||||
}
|
||||
|
||||
/// The Curve25519 key of the device that created the megolm
|
||||
/// decryption key originally.
|
||||
#[wasm_bindgen(getter, js_name = "senderCurve25519Key")]
|
||||
pub fn sender_curve25519_key(&self) -> Option<JsString> {
|
||||
Some(match &self.encryption_info.as_ref()?.algorithm_info {
|
||||
AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => curve25519_key.clone().into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// The signing Ed25519 key that have created the megolm key that
|
||||
/// was used to decrypt this session.
|
||||
#[wasm_bindgen(getter, js_name = "senderClaimedEd25519Key")]
|
||||
pub fn sender_claimed_ed25519_key(&self) -> Option<JsString> {
|
||||
match &self.encryption_info.as_ref()?.algorithm_info {
|
||||
AlgorithmInfo::MegolmV1AesSha2 { sender_claimed_keys, .. } => {
|
||||
sender_claimed_keys.get(&ruma::DeviceKeyAlgorithm::Ed25519).cloned().map(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) -> Array {
|
||||
Array::new()
|
||||
}
|
||||
|
||||
/// The verification state of the device that sent us the event,
|
||||
/// note this is the state of the device at the time of
|
||||
/// decryption. It may change in the future if a device gets
|
||||
/// verified or deleted.
|
||||
#[wasm_bindgen(getter, js_name = "verificationState")]
|
||||
pub fn verification_state(&self) -> Option<encryption::VerificationState> {
|
||||
Some((self.encryption_info.as_ref()?.verification_state.borrow()).into())
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//! `GET /_matrix/client/*/sync`
|
||||
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{identifiers, js::downcast};
|
||||
|
||||
/// Information on E2E device updates.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceLists {
|
||||
pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceLists {
|
||||
/// Create an empty `DeviceLists`.
|
||||
///
|
||||
/// `changed` and `left` must be an array of `UserId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(changed: Option<Array>, left: Option<Array>) -> Result<DeviceLists, JsError> {
|
||||
let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default();
|
||||
|
||||
inner.changed = changed
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
inner.left = left
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
/// Returns true if there are no device list updates.
|
||||
#[wasm_bindgen(js_name = "isEmpty")]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
/// List of users who have updated their device identity keys or
|
||||
/// who now share an encrypted room with the client since the
|
||||
/// previous sync
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn changed(&self) -> Array {
|
||||
self.inner
|
||||
.changed
|
||||
.iter()
|
||||
.map(|user| identifiers::UserId::from(user.clone()))
|
||||
.map(JsValue::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// List of users who no longer share encrypted rooms since the
|
||||
/// previous sync response.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn left(&self) -> Array {
|
||||
self.inner
|
||||
.left
|
||||
.iter()
|
||||
.map(|user| identifiers::UserId::from(user.clone()))
|
||||
.map(JsValue::from)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe('EncryptionAlgorithm', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
|
||||
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptionSettings.name, () => {
|
||||
test('can be instantiated with default values', () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
|
||||
expect(es.rotationPeriod).toStrictEqual(604800000000n);
|
||||
expect(es.rotationPeriodMessages).toStrictEqual(100n);
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
|
||||
});
|
||||
|
||||
test('checks the history visibility values', () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
es.historyVisibility = HistoryVisibility.Invited;
|
||||
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
|
||||
expect(() => { es.historyVisibility = 42 }).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationState', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(VerificationState.Trusted).toStrictEqual(0);
|
||||
expect(VerificationState.Untrusted).toStrictEqual(1);
|
||||
expect(VerificationState.UnknownDevice).toStrictEqual(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe('HistoryVisibility', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(HistoryVisibility.Invited).toStrictEqual(0);
|
||||
expect(HistoryVisibility.Joined).toStrictEqual(1);
|
||||
expect(HistoryVisibility.Shared).toStrictEqual(2);
|
||||
expect(HistoryVisibility.WorldReadable).toStrictEqual(3);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
const {
|
||||
DeviceId,
|
||||
DeviceKeyAlgorithm,
|
||||
DeviceKeyAlgorithmName,
|
||||
DeviceKeyId,
|
||||
EventId,
|
||||
RoomId,
|
||||
ServerName,
|
||||
UserId,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe(UserId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new UserId('@foobar') }).toThrow();
|
||||
});
|
||||
|
||||
const user = new UserId('@foo:bar.org');
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(user.localpart).toStrictEqual('foo');
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
expect(user.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('user ID is not historical', () => {
|
||||
expect(user.isHistorical()).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can read the user ID as a string', () => {
|
||||
expect(user.toString()).toStrictEqual('@foo:bar.org');
|
||||
})
|
||||
});
|
||||
|
||||
describe(DeviceId.name, () => {
|
||||
const device = new DeviceId('foo');
|
||||
|
||||
test('can read the device ID as a string', () => {
|
||||
expect(device.toString()).toStrictEqual('foo');
|
||||
})
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
const room = new RoomId('!foo:bar.org');
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('foo');
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
expect(room.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('!foo:bar.org');
|
||||
});
|
||||
});
|
||||
|
||||
describe(ServerName.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new ServerName('@foobar') }).toThrow()
|
||||
});
|
||||
|
||||
test('host is present', () => {
|
||||
expect(new ServerName('foo.org').host).toStrictEqual('foo.org');
|
||||
});
|
||||
|
||||
test('port can be optional', () => {
|
||||
expect(new ServerName('foo.org').port).toStrictEqual(undefined);
|
||||
expect(new ServerName('foo.org:1234').port).toStrictEqual(1234);
|
||||
});
|
||||
|
||||
test('server is not an IP literal', () => {
|
||||
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');
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -0,0 +1,569 @@
|
||||
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');
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return new OlmMachine(new_user || user, new_device || device);
|
||||
}
|
||||
|
||||
test('can read user ID', async () => {
|
||||
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
|
||||
});
|
||||
|
||||
test('can read device ID', async () => {
|
||||
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
|
||||
});
|
||||
|
||||
test('can read identity keys', async () => {
|
||||
const identityKeys = (await machine()).identityKeys;
|
||||
|
||||
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
});
|
||||
|
||||
test('can read display name', async () => {
|
||||
expect(await machine().displayName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read tracked users', async () => {
|
||||
const trackedUsers = (await machine()).trackedUsers();
|
||||
|
||||
expect(trackedUsers).toBeInstanceOf(Set);
|
||||
expect(trackedUsers.size).toStrictEqual(0);
|
||||
});
|
||||
|
||||
test('can update tracked users', async () => {
|
||||
const m = await machine();
|
||||
|
||||
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
test('can receive sync changes', async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
|
||||
expect(receiveSyncChanges).toEqual({});
|
||||
});
|
||||
|
||||
test('can get the outgoing requests that need to be send out', async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
|
||||
expect(receiveSyncChanges).toEqual({});
|
||||
|
||||
const outgoingRequests = await m.outgoingRequests();
|
||||
|
||||
expect(outgoingRequests).toHaveLength(2);
|
||||
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
{
|
||||
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
|
||||
expect(outgoingRequests[1].id).toBeDefined();
|
||||
expect(outgoingRequests[1].type).toStrictEqual(RequestType.KeysQuery);
|
||||
|
||||
const body = JSON.parse(outgoingRequests[1].body);
|
||||
expect(body.timeout).toBeDefined();
|
||||
expect(body.device_keys).toBeDefined();
|
||||
expect(body.token).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
describe('setup workflow to mark requests as sent', () => {
|
||||
let m;
|
||||
let ougoingRequests;
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID'));
|
||||
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys);
|
||||
outgoingRequests = await m.outgoingRequests();
|
||||
|
||||
expect(outgoingRequests).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('can mark requests as sent', async () => {
|
||||
{
|
||||
const request = outgoingRequests[0];
|
||||
expect(request).toBeInstanceOf(KeysUploadRequest);
|
||||
|
||||
// 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 m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
}
|
||||
|
||||
{
|
||||
const request = outgoingRequests[1];
|
||||
expect(request).toBeInstanceOf(KeysQueryRequest);
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
"@alice:example.org": {
|
||||
"JLAFKJWSCS": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"keys": {
|
||||
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
|
||||
},
|
||||
"signatures": {
|
||||
"@alice:example.org": {
|
||||
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
|
||||
}
|
||||
},
|
||||
"unsigned": {
|
||||
"device_display_name": "Alice's mobile phone"
|
||||
},
|
||||
"user_id": "@alice:example.org"
|
||||
}
|
||||
}
|
||||
},
|
||||
"failures": {}
|
||||
});
|
||||
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup workflow to encrypt/decrypt events', () => {
|
||||
let m;
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('JLAFKJWSCS');
|
||||
const room = new RoomId('!test:localhost');
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(user, device);
|
||||
});
|
||||
|
||||
test('can pass keysquery and keysclaim requests directly', async () => {
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "AFGUOBTZWM",
|
||||
"keys": {
|
||||
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
|
||||
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA"
|
||||
}
|
||||
},
|
||||
"user_id": "@example:localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "rust-sdk"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"failures": {},
|
||||
"master_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"master"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"self_signing_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user_signing_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
|
||||
}
|
||||
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_keys": {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
"signed_curve25519:AAAABQ": {
|
||||
"key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"failures": {}
|
||||
});
|
||||
const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response);
|
||||
}
|
||||
});
|
||||
|
||||
test('can share a room key', async () => {
|
||||
const other_users = [new UserId('@example:localhost')];
|
||||
|
||||
const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings()));
|
||||
|
||||
expect(requests).toHaveLength(1);
|
||||
expect(requests[0].event_type).toBeDefined();
|
||||
expect(requests[0].txn_id).toBeDefined();
|
||||
expect(requests[0].messages).toBeDefined();
|
||||
expect(requests[0].messages['@example:localhost']).toBeDefined();
|
||||
});
|
||||
|
||||
let encrypted;
|
||||
|
||||
test('can encrypt an event', async () => {
|
||||
encrypted = JSON.parse(await m.encryptRoomEvent(
|
||||
room,
|
||||
'm.room.message',
|
||||
JSON.stringify({
|
||||
"msgtype": "m.text",
|
||||
"body": "Hello, World!"
|
||||
}),
|
||||
));
|
||||
|
||||
expect(encrypted.algorithm).toBeDefined();
|
||||
expect(encrypted.ciphertext).toBeDefined();
|
||||
expect(encrypted.sender_key).toBeDefined();
|
||||
expect(encrypted.device_id).toStrictEqual(device.toString());
|
||||
expect(encrypted.session_id).toBeDefined();
|
||||
});
|
||||
|
||||
test('can decrypt an event', async () => {
|
||||
const decrypted = await m.decryptRoomEvent(
|
||||
JSON.stringify({
|
||||
"type": "m.room.encrypted",
|
||||
"event_id": "$xxxxx:example.org",
|
||||
"origin_server_ts": Date.now(),
|
||||
"sender": user.toString(),
|
||||
content: encrypted,
|
||||
unsigned: {
|
||||
"age": 1234
|
||||
}
|
||||
}),
|
||||
room,
|
||||
);
|
||||
|
||||
expect(decrypted).toBeInstanceOf(DecryptedRoomEvent);
|
||||
|
||||
const event = JSON.parse(decrypted.event);
|
||||
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());
|
||||
expect(decrypted.senderCurve25519Key).toBeDefined();
|
||||
expect(decrypted.senderClaimedEd25519Key).toBeDefined();
|
||||
expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0);
|
||||
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),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe('RequestType', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(RequestType.KeysUpload).toStrictEqual(0);
|
||||
expect(RequestType.KeysQuery).toStrictEqual(1);
|
||||
expect(RequestType.KeysClaim).toStrictEqual(2);
|
||||
expect(RequestType.ToDevice).toStrictEqual(3);
|
||||
expect(RequestType.SignatureUpload).toStrictEqual(4);
|
||||
expect(RequestType.RoomMessage).toStrictEqual(5);
|
||||
expect(RequestType.KeysBackup).toStrictEqual(6);
|
||||
});
|
||||
});
|
||||
|
||||
for (const [request, requestType] of [
|
||||
[KeysUploadRequest, RequestType.KeysUpload],
|
||||
[KeysQueryRequest, RequestType.KeysQuery],
|
||||
[KeysClaimRequest, RequestType.KeysClaim],
|
||||
[ToDeviceRequest, RequestType.ToDevice],
|
||||
[SignatureUploadRequest, RequestType.SignatureUpload],
|
||||
[RoomMessageRequest, RequestType.RoomMessage],
|
||||
[KeysBackupRequest, RequestType.KeysBackup],
|
||||
]) {
|
||||
describe(request.name, () => {
|
||||
test('can be instantiated', () => {
|
||||
const r = new (request)('foo', '{"bar": "baz"}');
|
||||
|
||||
expect(r).toBeInstanceOf(request);
|
||||
expect(r.id).toStrictEqual('foo');
|
||||
expect(r.body).toStrictEqual('{"bar": "baz"}');
|
||||
expect(r.type).toStrictEqual(requestType);
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
describe(DeviceLists.name, () => {
|
||||
test('can be empty', () => {
|
||||
const empty = new DeviceLists();
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
expect(empty.changed).toHaveLength(0);
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('can be coerced empty', () => {
|
||||
const empty = new DeviceLists([], []);
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
expect(empty.changed).toHaveLength(0);
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('returns the correct `changed` and `left`', () => {
|
||||
const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]);
|
||||
|
||||
expect(list.isEmpty()).toStrictEqual(false);
|
||||
|
||||
expect(list.changed).toHaveLength(1);
|
||||
expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org');
|
||||
|
||||
expect(list.left).toHaveLength(1);
|
||||
expect(list.left[0].toString()).toStrictEqual('@baz:qux.org');
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
},
|
||||
"typedocOptions": {
|
||||
"entryPoints": ["pkg/matrix_sdk_crypto_js.d.ts"],
|
||||
"out": "docs",
|
||||
"readme": "README.md",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
/index.js
|
||||
/index.d.ts
|
||||
/matrix-sdk-crypto.*.node
|
||||
/docs/*
|
||||
*.tgz
|
||||
@@ -0,0 +1,8 @@
|
||||
src/
|
||||
tests/
|
||||
Cargo.toml
|
||||
build.rs
|
||||
*.node
|
||||
*.tgz
|
||||
tsconfig.json
|
||||
cliff.toml
|
||||
@@ -0,0 +1,32 @@
|
||||
# Matrix-Rust-SDK Node.js Bindings
|
||||
|
||||
## 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
|
||||
no-network-IO implementation of a state machine, named `OlmMachine`,
|
||||
that handles E2EE (End-to-End Encryption) for Matrix clients.
|
||||
|
||||
The goal of this binding is _not_ to cover the entirety of the
|
||||
`matrix-sdk-crypto` API, but only what's required to build Matrix bots
|
||||
or Matrix bridges (i.e. to connect different networks together via the
|
||||
Matrix protocol).
|
||||
|
||||
This project replaces and deprecates a previous project, with the same
|
||||
name and same goals, inside [the `matrix-rust-sdk-bindings`
|
||||
repository](https://github.com/matrix-org/matrix-rust-sdk-bindings),
|
||||
with the NPM package name `@turt2live/matrix-sdk-crypto-nodejs`. The
|
||||
The new official package name is
|
||||
`@matrix-org/matrix-sdk-crypto-nodejs`.
|
||||
|
||||
Note: All bindings are now part of [the `matrix-rust-sdk`
|
||||
repository](https://github.com/matrix-org/matrix-rust-sdk) (see the
|
||||
`bindings/` root directory).
|
||||
|
||||
[A documentation is available inside the new
|
||||
`matrix-sdk-crypto-nodejs`
|
||||
project](https://github.com/matrix-org/matrix-rust-sdk/tree/0bde5ccf38f8cda3865297a2d12ddcdaf4b80ca7/bindings/matrix-sdk-crypto-nodejs).
|
||||
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
authors = ["Ivan Enderlin <ivane@element.io>"]
|
||||
description = "Matrix encryption library, for NodeJS"
|
||||
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-nodejs"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
version = "0.6.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
tracing = ["dep:tracing-subscriber"]
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
@@ -0,0 +1,208 @@
|
||||
# `matrix-sdk-crypto-nodejs`
|
||||
|
||||
Welcome to the [Node.js] binding for the Rust [`matrix-sdk-crypto`]
|
||||
library! This binding is part of the [`matrix-rust-sdk`] project,
|
||||
which is a library implementation of a [Matrix] client-server.
|
||||
|
||||
`matrix-sdk-crypto-nodejs` is a no-network-IO implementation of a
|
||||
state machine, named `OlmMachine`, that handles E2EE ([End-to-End
|
||||
Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
|
||||
[Matrix] clients.
|
||||
|
||||
## Usage
|
||||
|
||||
Just add the latest release to your `package.json`:
|
||||
```sh
|
||||
$ 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:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>Architecture</th>
|
||||
<th>Triple</th>
|
||||
<th>Prebuilt available</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="5">Linux</td>
|
||||
<td rowspan="2"><code>aarch</code></td>
|
||||
<td><code>aarch64-unknown-linux-gnu</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>arm-unknown-linux-gnueabihf</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3"><code>amd</code></td>
|
||||
<td><code>x86_64-unknown-linux-gnu</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>x86_64-unknown-linux-musl</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>i686-unknown-linux-gnu</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">macOS</td>
|
||||
<td><code>aarch</code></td>
|
||||
<td><code>arch64-apple-darwin</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>amd</code></td>
|
||||
<td><code>x86_64-apple-darwin</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">Windows</td>
|
||||
<td><code>aarch</code></td>
|
||||
<td><code>aarch64-pc-windows-msvc</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><code>amd</code></td>
|
||||
<td><code>x86_64-pc-windows-msvc</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>i686-pc-windows-msvc</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Development
|
||||
|
||||
This Node.js binding is written in [Rust]. To build this binding, you
|
||||
need to install the Rust compiler, see [the Install Rust
|
||||
Page](https://www.rust-lang.org/tools/install). Then, the workflow is
|
||||
pretty classical by using [npm], see [the Downloading and installing
|
||||
Node.js and npm
|
||||
Page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
|
||||
|
||||
The binding is compatible with, and tested against, the Node.js
|
||||
versions that are in “current”, “active” or “maintenance” states,
|
||||
according to [the Node.js Releases
|
||||
Page](https://nodejs.org/en/about/releases/), _and_ which are
|
||||
compatible with [NAPI v6 (Node.js
|
||||
API)](https://nodejs.org/api/n-api.html#node-api-version-matrix). It
|
||||
means that this binding will work with the following versions: 14.0.0,
|
||||
16.0.0 and 18.0.0.
|
||||
|
||||
Once the Rust compiler, Node.js and npm are installed, you can run the
|
||||
following commands:
|
||||
|
||||
```sh
|
||||
$ npm install --ignore-scripts
|
||||
$ npm run build
|
||||
$ npm run test
|
||||
```
|
||||
|
||||
An `index.js`, `index.d.ts` and a `*.node` files should be
|
||||
generated. At the same level of those files, you can edit a file and
|
||||
try this:
|
||||
|
||||
```javascript
|
||||
const { OlmMachine } = require('./index.js');
|
||||
|
||||
// Let's see what we can do.
|
||||
```
|
||||
|
||||
The `OlmMachine` state machine works in a push/pull manner:
|
||||
|
||||
* You push state changes and events retrieved from a Matrix homeserver
|
||||
`/sync` response, into the state machine,
|
||||
|
||||
* You pull requests that you will need to send back to the homeserver
|
||||
out of the state machine.
|
||||
|
||||
```javascript
|
||||
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require('./index.js');
|
||||
|
||||
async function main() {
|
||||
// Define a user ID.
|
||||
const alice = new UserId('@alice:example.org');
|
||||
|
||||
// Define a device ID.
|
||||
const device = new DeviceId('DEVICEID');
|
||||
|
||||
// Let's create the `OlmMachine` state machine.
|
||||
const machine = await OlmMachine.initialize(alice, device);
|
||||
|
||||
// Let's pretend we have received changes and events from a
|
||||
// `/sync` endpoint of a Matrix homeserver, …
|
||||
const toDeviceEvents = "{}"; // JSON-encoded
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = {};
|
||||
const unusedFallbackKeys = [];
|
||||
|
||||
// … and push them into the state machine.
|
||||
const decryptedToDevice = await machine.receiveSyncChanges(
|
||||
toDeviceEvents,
|
||||
changedDevices,
|
||||
oneTimeKeyCounts,
|
||||
unusedFallbackKeys,
|
||||
);
|
||||
|
||||
// Now, let's pull requests that we need to send out to the Matrix
|
||||
// homeserver.
|
||||
const outgoingRequests = await machine.outgoingRequests();
|
||||
|
||||
// To complete the workflow, send the requests here out and call
|
||||
// `machine.markRequestAsSent`.
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
### With tracing (experimental)
|
||||
|
||||
If you want to enable [tracing](https://tracing.rs), i.e. to get the
|
||||
logs, you should re-compile the extension with the `tracing` feature
|
||||
turned on:
|
||||
|
||||
```sh
|
||||
$ npm run build -- --features tracing
|
||||
```
|
||||
|
||||
Now, you can use the `MATRIX_LOG` environment variable to tweak the log filtering, such as:
|
||||
|
||||
```sh
|
||||
$ MATRIX_LOG=debug npm run test
|
||||
```
|
||||
|
||||
See
|
||||
[`tracing-subscriber`](https://tracing.rs/tracing_subscriber/index.html)
|
||||
to learn more about the `RUST_LOG`/`MATRIX_LOG` environment variable.
|
||||
|
||||
## Documentation
|
||||
|
||||
[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
|
||||
```
|
||||
|
||||
The documentation is generated in the `./docs` directory.
|
||||
|
||||
|
||||
|
||||
[Node.js]: https://nodejs.org/
|
||||
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
|
||||
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
|
||||
[Matrix]: https://matrix.org/
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[npm]: https://www.npmjs.com/
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
# configuration file for git-cliff (0.1.0)
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Matrix SDK Crypto Node.js 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-nodejs") | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {{ commit.id | truncate(length=7, end="") }}{% 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 = true
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = "v0.1.0-beta.1"
|
||||
# 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"
|
||||
@@ -0,0 +1,113 @@
|
||||
const { DownloaderHelper } = require('node-downloader-helper');
|
||||
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-v${version}`;
|
||||
|
||||
const byteHelper = function (value) {
|
||||
if (value === 0) {
|
||||
return '0 b';
|
||||
}
|
||||
const units = ['b', 'kB', 'MB', 'GB', 'TB'];
|
||||
const number = Math.floor(Math.log(value) / Math.log(1024));
|
||||
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + ' ' +
|
||||
units[number];
|
||||
};
|
||||
|
||||
function download_lib(libname) {
|
||||
let startTime = new Date();
|
||||
|
||||
const url = `${DOWNLOADS_BASE_URL}/${CURRENT_VERSION}/${libname}`;
|
||||
console.info(`Downloading lib ${libname} from ${url}`);
|
||||
const dl = new DownloaderHelper(url, __dirname, {
|
||||
override: true,
|
||||
});
|
||||
|
||||
dl.on('end', () => console.info('Download Completed'));
|
||||
dl.on('error', (err) => console.info('Download Failed', err));
|
||||
dl.on('progress', stats => {
|
||||
const progress = stats.progress.toFixed(1);
|
||||
const speed = byteHelper(stats.speed);
|
||||
const downloaded = byteHelper(stats.downloaded);
|
||||
const total = byteHelper(stats.total);
|
||||
|
||||
// print every one second (`progress.throttled` can be used instead)
|
||||
const currentTime = new Date();
|
||||
const elaspsedTime = currentTime - startTime;
|
||||
if (elaspsedTime > 1000) {
|
||||
startTime = currentTime;
|
||||
console.info(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
|
||||
}
|
||||
});
|
||||
dl.start().catch(err => console.error(err));
|
||||
}
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.win32-x64-msvc.node')
|
||||
break
|
||||
case 'ia32':
|
||||
download_lib('matrix-sdk-crypto.win32-ia32-msvc.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.win32-arm64-msvc.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.darwin-x64.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.darwin-arm64.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-musl.node')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-gnu.node')
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
throw new Error('Linux for arm64 musl isn\'t support at the moment')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-arm64-gnu.node')
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
download_lib('matrix-sdk-crypto.linux-arm-gnueabihf.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@matrix-org/matrix-sdk-crypto-nodejs",
|
||||
"version": "0.1.0-beta.1",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"name": "matrix-sdk-crypto",
|
||||
"triples": {
|
||||
"additional": [
|
||||
"aarch64-apple-darwin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.9.0",
|
||||
"jest": "^28.1.0",
|
||||
"typedoc": "^0.22.17",
|
||||
"yargs-parser": "~21.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"scripts": {
|
||||
"release-build": "napi build --platform --release --strip",
|
||||
"build": "napi build --platform",
|
||||
"postinstall": "node download-lib.js",
|
||||
"test": "jest --verbose --testTimeout 10000",
|
||||
"doc": "typedoc --tsconfig ."
|
||||
},
|
||||
"dependencies": {
|
||||
"node-downloader-helper": "^2.1.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
use std::{
|
||||
io::{Cursor, Read},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use napi::bindgen_prelude::Uint8Array;
|
||||
use napi_derive::*;
|
||||
|
||||
use crate::into_err;
|
||||
|
||||
/// A type to encrypt and to decrypt anything that can fit in an
|
||||
/// `Uint8Array`, usually big buffer.
|
||||
#[napi]
|
||||
pub struct Attachment;
|
||||
|
||||
#[napi]
|
||||
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.
|
||||
#[napi]
|
||||
pub fn encrypt(array: Uint8Array) -> napi::Result<EncryptedAttachment> {
|
||||
let buffer: &[u8] = array.deref();
|
||||
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
|
||||
|
||||
let mut encrypted_data = Vec::new();
|
||||
encryptor.read_to_end(&mut encrypted_data).map_err(into_err)?;
|
||||
|
||||
let media_encryption_info = Some(encryptor.finish());
|
||||
|
||||
Ok(EncryptedAttachment {
|
||||
encrypted_data: Uint8Array::new(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`.
|
||||
#[napi]
|
||||
pub fn decrypt(attachment: &mut EncryptedAttachment) -> napi::Result<Uint8Array> {
|
||||
let media_encryption_info = match attachment.media_encryption_info.take() {
|
||||
Some(media_encryption_info) => media_encryption_info,
|
||||
None => {
|
||||
return Err(napi::Error::from_reason(
|
||||
"The media encryption info are absent from the given encrypted attachment"
|
||||
.to_owned(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let encrypted_data: &[u8] = attachment.encrypted_data.deref();
|
||||
|
||||
let mut cursor = Cursor::new(encrypted_data);
|
||||
let mut decryptor =
|
||||
matrix_sdk_crypto::AttachmentDecryptor::new(&mut cursor, media_encryption_info)
|
||||
.map_err(into_err)?;
|
||||
|
||||
let mut decrypted_data = Vec::new();
|
||||
decryptor.read_to_end(&mut decrypted_data).map_err(into_err)?;
|
||||
|
||||
Ok(Uint8Array::new(decrypted_data))
|
||||
}
|
||||
}
|
||||
|
||||
/// An encrypted attachment, usually created from `Attachment.encrypt`.
|
||||
#[napi]
|
||||
pub struct EncryptedAttachment {
|
||||
media_encryption_info: Option<matrix_sdk_crypto::MediaEncryptionInfo>,
|
||||
|
||||
/// The actual encrypted data.
|
||||
pub encrypted_data: Uint8Array,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
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).
|
||||
#[napi(constructor)]
|
||||
pub fn new(encrypted_data: Uint8Array, media_encryption_info: String) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
encrypted_data,
|
||||
media_encryption_info: Some(
|
||||
serde_json::from_str(media_encryption_info.as_str()).map_err(into_err)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
#[napi(getter)]
|
||||
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.
|
||||
#[napi(getter)]
|
||||
pub fn has_media_encryption_info_been_consumed(&self) -> bool {
|
||||
self.media_encryption_info.is_none()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use napi::bindgen_prelude::{BigInt, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
|
||||
use crate::events;
|
||||
|
||||
/// An encryption algorithm to be used to encrypt messages sent to a
|
||||
/// room.
|
||||
#[napi]
|
||||
pub enum EncryptionAlgorithm {
|
||||
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
|
||||
OlmV1Curve25519AesSha2,
|
||||
|
||||
/// Megolm version 1 using AES-256 and SHA-256.
|
||||
MegolmV1AesSha2,
|
||||
}
|
||||
|
||||
impl From<EncryptionAlgorithm> for matrix_sdk_crypto::types::EventEncryptionAlgorithm {
|
||||
fn from(value: EncryptionAlgorithm) -> Self {
|
||||
use EncryptionAlgorithm::*;
|
||||
|
||||
match value {
|
||||
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
_ => unreachable!("Unknown variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings for an encrypted room.
|
||||
///
|
||||
/// This determines the algorithm and rotation periods of a group
|
||||
/// session.
|
||||
#[napi]
|
||||
pub struct EncryptionSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EncryptionAlgorithm,
|
||||
|
||||
/// How long the session should be used before changing it,
|
||||
/// expressed in microseconds.
|
||||
pub rotation_period: BigInt,
|
||||
|
||||
/// How many messages should be sent before changing the session.
|
||||
pub rotation_period_messages: BigInt,
|
||||
|
||||
/// 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 {
|
||||
fn default() -> Self {
|
||||
let default = matrix_sdk_crypto::olm::EncryptionSettings::default();
|
||||
|
||||
Self {
|
||||
algorithm: default.algorithm.into(),
|
||||
rotation_period: {
|
||||
let n: u64 = default.rotation_period.as_micros().try_into().unwrap();
|
||||
|
||||
n.into()
|
||||
},
|
||||
rotation_period_messages: {
|
||||
let n = default.rotation_period_msgs;
|
||||
|
||||
n.into()
|
||||
},
|
||||
history_visibility: default.history_visibility.into(),
|
||||
only_allow_trusted_devices: default.only_allow_trusted_devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl EncryptionSettings {
|
||||
/// Create a new `EncryptionSettings` with default values.
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> EncryptionSettings {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
|
||||
fn from(value: &EncryptionSettings) -> Self {
|
||||
Self {
|
||||
algorithm: value.algorithm.into(),
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The verification state of the device that sent an event to us.
|
||||
#[napi]
|
||||
pub enum VerificationState {
|
||||
/// The device is trusted.
|
||||
Trusted,
|
||||
|
||||
/// The device is not trusted.
|
||||
Untrusted,
|
||||
|
||||
/// The device is not known to us.
|
||||
UnknownDevice,
|
||||
}
|
||||
|
||||
impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState {
|
||||
fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self {
|
||||
use matrix_sdk_common::deserialized_responses::VerificationState::*;
|
||||
|
||||
match value {
|
||||
Trusted => Self::Trusted,
|
||||
Untrusted => Self::Untrusted,
|
||||
UnknownDevice => Self::UnknownDevice,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/// Generic error wrapping `napi::Error`.
|
||||
#[derive(Debug)]
|
||||
pub struct Error(napi::Error);
|
||||
|
||||
impl<E> From<E> for Error
|
||||
where
|
||||
E: std::error::Error,
|
||||
{
|
||||
fn from(error: E) -> Self {
|
||||
Self(napi::Error::from_reason(error.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for napi::Error {
|
||||
#[inline]
|
||||
fn from(value: Error) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to replace the `E` to `Error` to `napi::Error` conversion.
|
||||
#[inline]
|
||||
pub fn into_err<E>(error: E) -> napi::Error
|
||||
where
|
||||
E: std::error::Error,
|
||||
{
|
||||
Error::from(error).into()
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//! Types related to events.
|
||||
|
||||
use napi::bindgen_prelude::ToNapiValue;
|
||||
use napi_derive::*;
|
||||
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
|
||||
|
||||
/// Who can see a room's history.
|
||||
#[napi]
|
||||
#[derive(Debug)]
|
||||
pub enum HistoryVisibility {
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they were invited onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *invite* or *join*.
|
||||
Invited,
|
||||
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they joined the room onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *join*.
|
||||
Joined,
|
||||
|
||||
/// Previous events are always accessible to newly joined members.
|
||||
///
|
||||
/// All events in the room are accessible, even those sent when
|
||||
/// the member was not a part of the room.
|
||||
Shared,
|
||||
|
||||
/// All events while this is the `HistoryVisibility` value may be
|
||||
/// shared by any participating homeserver with anyone, regardless
|
||||
/// of whether they have ever joined the room.
|
||||
WorldReadable,
|
||||
}
|
||||
|
||||
impl From<HistoryVisibility> for RumaHistoryVisibility {
|
||||
fn from(value: HistoryVisibility) -> Self {
|
||||
use HistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RumaHistoryVisibility> for HistoryVisibility {
|
||||
fn from(value: RumaHistoryVisibility) -> Self {
|
||||
use RumaHistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
_ => unreachable!("Unknown variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user