commit 3f144dbf7e4a4b2e0cc7c27e26d7545f1a9d43d2
parent 0243be4babbc31b7e4d3f3f50edf339bce6e085b
Author: MTRNord <mtrnord1@gmail.com>
Date: Fri, 28 Apr 2023 18:26:59 +0200
feat: Ditch js-sdk and hookup login and mainpage to redux using rtk-query
Diffstat:
15 files changed, 250 insertions(+), 369 deletions(-)
diff --git a/index.html b/index.html
@@ -9,9 +9,8 @@
/>
<title>Matrix</title>
<script>
- if (global === undefined) {
- var global = window;
- }
+ global = globalThis // fix for "global not defined" error
+ globalThis.OLM_OPTIONS = undefined; // Fix missing var for olm
</script>
</head>
<body>
diff --git a/package-lock.json b/package-lock.json
@@ -20,14 +20,12 @@
"dompurify": "^3.0.2",
"lexical": "^0.10.0",
"lucide-react": "^0.171.0",
- "matrix-js-sdk": "^24.1.0",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-intersection-observer": "^9.4.3",
"react-redux": "^8.0.5",
"react-router-dom": "^6.10.0",
- "redux-saga": "^1.2.3",
"typescript": "^5.0.2",
"web-vitals": "^2.1.4"
},
@@ -37,7 +35,7 @@
"@storybook/addon-essentials": "^7.0.7",
"@storybook/addon-interactions": "^7.0.7",
"@storybook/addon-links": "^7.0.7",
- "@storybook/addon-styling": "^1.0.2",
+ "@storybook/addon-styling": "^1.0.3",
"@storybook/blocks": "^7.0.7",
"@storybook/react": "^7.0.7",
"@storybook/react-vite": "^7.0.7",
@@ -3021,14 +3019,6 @@
"yjs": ">=13.5.22"
}
},
- "node_modules/@matrix-org/matrix-sdk-crypto-js": {
- "version": "0.1.0-alpha.7",
- "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.7.tgz",
- "integrity": "sha512-sQEG9cSfNji5NYBf5h7j5IxYVO0dwtAKoetaVyR+LhIXz/Su7zyEE3EwlAWAeJOFdAV/vZ5LTNyh39xADuNlTg==",
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/@mdx-js/react": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz",
@@ -3092,57 +3082,6 @@
"node": ">= 8"
}
},
- "node_modules/@redux-saga/core": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.2.3.tgz",
- "integrity": "sha512-U1JO6ncFBAklFTwoQ3mjAeQZ6QGutsJzwNBjgVLSWDpZTRhobUzuVDS1qH3SKGJD8fvqoaYOjp6XJ3gCmeZWgA==",
- "dependencies": {
- "@babel/runtime": "^7.6.3",
- "@redux-saga/deferred": "^1.2.1",
- "@redux-saga/delay-p": "^1.2.1",
- "@redux-saga/is": "^1.1.3",
- "@redux-saga/symbols": "^1.1.3",
- "@redux-saga/types": "^1.2.1",
- "redux": "^4.0.4",
- "typescript-tuple": "^2.2.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/redux-saga"
- }
- },
- "node_modules/@redux-saga/deferred": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.2.1.tgz",
- "integrity": "sha512-cmin3IuuzMdfQjA0lG4B+jX+9HdTgHZZ+6u3jRAOwGUxy77GSlTi4Qp2d6PM1PUoTmQUR5aijlA39scWWPF31g=="
- },
- "node_modules/@redux-saga/delay-p": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.2.1.tgz",
- "integrity": "sha512-MdiDxZdvb1m+Y0s4/hgdcAXntpUytr9g0hpcOO1XFVyyzkrDu3SKPgBFOtHn7lhu7n24ZKIAT1qtKyQjHqRd+w==",
- "dependencies": {
- "@redux-saga/symbols": "^1.1.3"
- }
- },
- "node_modules/@redux-saga/is": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.1.3.tgz",
- "integrity": "sha512-naXrkETG1jLRfVfhOx/ZdLj0EyAzHYbgJWkXbB3qFliPcHKiWbv/ULQryOAEKyjrhiclmr6AMdgsXFyx7/yE6Q==",
- "dependencies": {
- "@redux-saga/symbols": "^1.1.3",
- "@redux-saga/types": "^1.2.1"
- }
- },
- "node_modules/@redux-saga/symbols": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.1.3.tgz",
- "integrity": "sha512-hCx6ZvU4QAEUojETnX8EVg4ubNLBFl1Lps4j2tX7o45x/2qg37m3c6v+kSp8xjDJY+2tJw4QB3j8o8dsl1FDXg=="
- },
- "node_modules/@redux-saga/types": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz",
- "integrity": "sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA=="
- },
"node_modules/@reduxjs/toolkit": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
@@ -5515,11 +5454,6 @@
"integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
"dev": true
},
- "node_modules/@types/events": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
- "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g=="
- },
"node_modules/@types/express": {
"version": "4.17.17",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
@@ -5715,11 +5649,6 @@
"@types/react": "*"
}
},
- "node_modules/@types/retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
- "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
- },
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
@@ -6113,11 +6042,6 @@
"ajv": "^6.9.1"
}
},
- "node_modules/another-json": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz",
- "integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg=="
- },
"node_modules/ansi-align": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
@@ -6406,11 +6330,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "node_modules/base-x": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
- "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
- },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -6725,14 +6644,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/bs58": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
- "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
- "dependencies": {
- "base-x": "^4.0.0"
- }
- },
"node_modules/bser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@@ -7216,6 +7127,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -8103,6 +8015,8 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "dev": true,
+ "peer": true,
"engines": {
"node": ">=0.8.x"
}
@@ -10599,18 +10513,6 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true
},
- "node_modules/loglevel": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz",
- "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==",
- "engines": {
- "node": ">= 0.6.0"
- },
- "funding": {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/loglevel"
- }
- },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -10715,42 +10617,6 @@
"react": ">= 0.14.0"
}
},
- "node_modules/matrix-events-sdk": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz",
- "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="
- },
- "node_modules/matrix-js-sdk": {
- "version": "24.1.0",
- "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-24.1.0.tgz",
- "integrity": "sha512-xEx2ZoNsS56dwgqLJ3rIv2SUpFxdQLrLKmJCpMatMUKCAg+NGuZfpQ3QXblIbGaqFNQZCH7fC7S48AeTMZp1Jw==",
- "dependencies": {
- "@babel/runtime": "^7.12.5",
- "@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.5",
- "another-json": "^0.2.0",
- "bs58": "^5.0.0",
- "content-type": "^1.0.4",
- "loglevel": "^1.7.1",
- "matrix-events-sdk": "0.0.1",
- "matrix-widget-api": "^1.3.1",
- "p-retry": "4",
- "sdp-transform": "^2.14.1",
- "unhomoglyph": "^1.0.6",
- "uuid": "9"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/matrix-widget-api": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz",
- "integrity": "sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q==",
- "dependencies": {
- "@types/events": "^3.0.0",
- "events": "^3.2.0"
- }
- },
"node_modules/mdast-util-definitions": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz",
@@ -11407,18 +11273,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/p-retry": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
- "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
- "dependencies": {
- "@types/retry": "0.12.0",
- "retry": "^0.13.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -12691,14 +12545,6 @@
"@babel/runtime": "^7.9.2"
}
},
- "node_modules/redux-saga": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz",
- "integrity": "sha512-HDe0wTR5nhd8Xr5xjGzoyTbdAw6rjy1GDplFt3JKtKN8/MnkQSRqK/n6aQQhpw5NI4ekDVOaW+w4sdxPBaCoTQ==",
- "dependencies": {
- "@redux-saga/core": "^1.2.3"
- }
- },
"node_modules/redux-thunk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
@@ -12887,14 +12733,6 @@
"node": ">=12"
}
},
- "node_modules/retry": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
- "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
- "engines": {
- "node": ">= 4"
- }
- },
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -13117,14 +12955,6 @@
"url": "https://opencollective.com/webpack"
}
},
- "node_modules/sdp-transform": {
- "version": "2.14.1",
- "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz",
- "integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==",
- "bin": {
- "sdp-verify": "checker.js"
- }
- },
"node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -14395,27 +14225,6 @@
"node": ">=12.20"
}
},
- "node_modules/typescript-compare": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
- "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==",
- "dependencies": {
- "typescript-logic": "^0.0.0"
- }
- },
- "node_modules/typescript-logic": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz",
- "integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q=="
- },
- "node_modules/typescript-tuple": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz",
- "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==",
- "dependencies": {
- "typescript-compare": "^0.0.2"
- }
- },
"node_modules/uglify-js": {
"version": "3.17.4",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
@@ -14435,11 +14244,6 @@
"integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==",
"dev": true
},
- "node_modules/unhomoglyph": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz",
- "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg=="
- },
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@@ -14675,6 +14479,7 @@
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+ "dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
diff --git a/package.json b/package.json
@@ -16,14 +16,12 @@
"dompurify": "^3.0.2",
"lexical": "^0.10.0",
"lucide-react": "^0.171.0",
- "matrix-js-sdk": "^24.1.0",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-intersection-observer": "^9.4.3",
"react-redux": "^8.0.5",
"react-router-dom": "^6.10.0",
- "redux-saga": "^1.2.3",
"typescript": "^5.0.2",
"web-vitals": "^2.1.4"
},
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
@@ -0,0 +1,7 @@
+
+import "matrix-react-sdk/src/@types/global"; // load matrix-react-sdk's type extensions first
+declare global {
+ interface Window {
+ OLM_OPTIONS: any;
+ }
+}
+\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
@@ -3,10 +3,44 @@ import {
Route,
Routes,
} from "react-router-dom";
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import olmWasmPath from "olm/olm.wasm?url";
+import OlmLegacy from 'olm/olm_legacy.js?url';
+import Olm from "olm";
import LoginPage from './pages/LoginPage';
import MainPage from './pages/MainPage';
import { ProtectedRoute } from './app/protectedRoute';
+Olm
+ .init({
+ locateFile: () => olmWasmPath,
+ })
+ .then(() => {
+ console.log("Using WebAssembly Olm");
+ })
+ .catch((wasmLoadError: any) => {
+ console.log("Failed to load Olm: trying legacy version", wasmLoadError);
+ return new Promise((resolve, reject) => {
+ const s = document.createElement("script");
+ s.src = OlmLegacy;
+ s.onload = resolve;
+ s.onerror = reject;
+ document.body.appendChild(s);
+ })
+ .then(() => {
+ // Init window.Olm, ie. the one just loaded by the script tag,
+ // not 'Olm' which is still the failed wasm version.
+ return window.Olm.init();
+ })
+ .then(() => {
+ console.log("Using legacy Olm");
+ })
+ .catch((legacyLoadError) => {
+ console.log("Both WebAssembly and asm.js Olm failed!", legacyLoadError);
+ });
+ });
+
function App() {
return <BrowserRouter>
<Routes>
diff --git a/src/app/api/api.ts b/src/app/api/api.ts
@@ -1,91 +1,121 @@
-import { all, call, fork, put, takeEvery } from "redux-saga/effects";
-import { LOGIN_REQUEST_ACTION, LOGIN_SUCCESS_ACTION, LOGIN, LOGIN_ACTION, LOGIN_FAILURE_ACTION } from "./reducers";
-import { IndexedDBCryptoStore, IndexedDBStore, MatrixClient, MemoryStore, createClient, setCryptoStoreFactory } from "matrix-js-sdk";
-import { AutoDiscovery } from 'matrix-js-sdk/lib/autodiscovery';
+import { BaseQueryFn, FetchArgs, FetchBaseQueryError, createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
+import { RootState } from '../store';
+import { ILoginFlows, ILoginParams, ILoginResponse, IWellKnown } from './apiTypes';
+import { createAction, createReducer } from '@reduxjs/toolkit';
+// async function login(baseUrl: string, userId: string, password: string): Promise<MatrixClient> {
+// //const client = await initMatrixClient(baseUrl, userId, undefined, undefined, password);
-async function login(baseUrl: string, userId: string, password: string): Promise<MatrixClient> {
- const client = await initMatrixClient(baseUrl, userId, undefined, password);
+// window.localStorage.setItem("accessToken", client.getAccessToken()!);
+// window.localStorage.setItem("baseUrl", client.baseUrl);
+// window.localStorage.setItem("userId", client.getUserId()!);
+// window.localStorage.setItem("deviceId", client.getDeviceId()!);
- window.localStorage.setItem("accessToken", client.getAccessToken()!);
- window.localStorage.setItem("baseUrl", client.baseUrl);
- window.localStorage.setItem("userId", client.getUserId()!);
+// return client;
+// }
- return client;
+export const setHost = createAction<string, 'auth/host'>('auth/host');
+export const setAccessToken = createAction<string, 'auth/access_token'>('auth/access_token');
+export const setLoggedIn = createAction<boolean, 'auth/logged_in'>('auth/logged_in');
+interface IAuthState {
+ host?: string;
+ accessToken?: string;
+ logged_in: boolean;
}
-
-function* onLoginSaga(action: LOGIN): any {
- yield put(LOGIN_REQUEST_ACTION());
- if (!action.baseUrl.startsWith("https://")) {
- yield put(LOGIN_FAILURE_ACTION("Homeserver url must start with https://"));
- return;
- }
- if (!action.username) {
- yield put(LOGIN_FAILURE_ACTION("Username must be a non empty string"));
- return;
- }
- if (!action.password) {
- yield put(LOGIN_FAILURE_ACTION("Password must be a non empty string"));
- return;
- }
- try {
- const client: MatrixClient = yield call(login, action.baseUrl, action.username, action.password);
- yield put(LOGIN_SUCCESS_ACTION(client));
- } catch (e) {
- yield put(LOGIN_FAILURE_ACTION((e as any).toString()));
- return;
- }
-}
-
-function* watchLoginSaga() {
- yield takeEvery<LOGIN>(LOGIN_ACTION, onLoginSaga);
-}
-
-export function* apiSagas() {
- yield all([fork(watchLoginSaga)]);
+const initialAuthState: IAuthState = {
+ host: undefined,
+ accessToken: undefined,
+ logged_in: false,
}
+export const auth = createReducer(initialAuthState, (builder) => builder
+ .addCase(setHost, (state, action) => { state.host = action.payload })
+ .addCase(setAccessToken, (state, action) => { state.accessToken = action.payload })
+ .addCase(setLoggedIn, (state, action) => { state.logged_in = action.payload })
+);
-export async function initMatrixClient(baseUrl: string, userId: string, accessToken?: string, password?: string): Promise<MatrixClient> {
- // just *accessing* indexedDB throws an exception in firefox with indexeddb disabled.
- let indexedDB: IDBFactory | undefined;
- try {
- indexedDB = global.indexedDB;
- } catch (e) { }
-
- // if our browser (appears to) support indexeddb, use an indexeddb crypto store.
- let store = new MemoryStore({ localStorage: window.localStorage });
- if (indexedDB) {
- setCryptoStoreFactory(() => new IndexedDBCryptoStore(indexedDB!, "matrix-js-sdk:crypto"));
- store = new IndexedDBStore({ indexedDB: indexedDB, localStorage: window.localStorage });
- await store.startup();
- }
+const baseQuery: BaseQueryFn<string | FetchArgs,
+ unknown,
+ FetchBaseQueryError> = async (args, api, extraOptions) => {
+ const baseUrl = (api.getState() as RootState).auth.host;
+ // gracefully handle scenarios where data to generate the URL is missing
+ if (!baseUrl) {
+ return {
+ error: {
+ status: 400,
+ statusText: 'Bad Request',
+ data: 'Invalid Matrix Host',
+ },
+ };
+ }
- const clientConfig = await AutoDiscovery.findClientConfig(baseUrl.replace("https://", ''));
+ return rawBaseQuery(baseUrl)(args, api, extraOptions);
+ };
- if (clientConfig["m.homeserver"].state === AutoDiscovery.FAIL_PROMPT) {
- throw Error(clientConfig["m.homeserver"].error?.toString())
- }
- if (clientConfig["m.homeserver"].state !== AutoDiscovery.FAIL_ERROR) {
- if (clientConfig["m.homeserver"].base_url) {
- baseUrl = clientConfig["m.homeserver"].base_url;
- }
- }
+export const matrixApi = createApi({
+ reducerPath: 'matrixApi',
+ baseQuery: baseQuery,
+ tagTypes: ['Login', 'Flows', "WellKnown"],
+ endpoints: (builder) => ({
+ getWellKnown: builder.query<IWellKnown, undefined>({
+ query: () => `/.well-known/matrix/client`,
+ providesTags: ['WellKnown'],
+ async onQueryStarted(_arg, { dispatch, queryFulfilled }) {
+ try {
+ const { data, meta } = await queryFulfilled;
+ const response = (meta as { request: Request, response: Response }).response;
+ if (response.status === 200) {
+ if (data?.["m.homeserver"]?.base_url) {
+ dispatch(setHost(data["m.homeserver"].base_url))
+ }
+ }
+ } catch (err) {
+ // `onError` side-effect
+ }
+ },
+ }),
+ getLoginFlows: builder.query<ILoginFlows, undefined>({
+ query: () => `/_matrix/client/v3/login`,
+ providesTags: ['Flows'],
+ }),
+ doLogin: builder.mutation<ILoginResponse, ILoginParams>({
+ query: (data) => ({
+ url: `/_matrix/client/r0/login`,
+ method: 'POST',
+ body: data,
+ }),
+ async onQueryStarted(_arg, { dispatch, queryFulfilled }) {
+ try {
+ const { data, meta } = await queryFulfilled;
+ const response = (meta as { request: Request, response: Response }).response;
+ if (response.status === 200) {
+ dispatch(setLoggedIn(true))
+ dispatch(setAccessToken(data.access_token))
+ if (data.well_known?.["m.homeserver"]?.base_url) {
+ dispatch(setHost(data.well_known["m.homeserver"].base_url))
+ }
+ } else {
+ dispatch(setLoggedIn(false))
+ }
+ } catch (err) {
+ // `onError` side-effect
+ dispatch(setLoggedIn(false))
+ }
+ },
+ invalidatesTags: ['Login'],
+ }),
+ }),
+});
- const matrixClient = createClient({
- baseUrl: baseUrl,
- accessToken: accessToken,
- userId: accessToken ? userId : undefined,
- useAuthorizationHeader: true,
- store
- });
+const rawBaseQuery = (baseUrl: string) => fetchBaseQuery({
+ baseUrl,
+ prepareHeaders: (headers, { getState }) => {
+ const token = (getState() as RootState).auth.accessToken;
+ !!token && headers.set('Authorization', `Bearer ${token}`);
- if (!accessToken && !password) {
- throw Error("Missing password and accessToken. Unable to proceed")
- }
+ return headers;
+ },
+});
- if (!accessToken) {
- await matrixClient.loginWithPassword(userId, password!!);
- }
- await matrixClient.initCrypto();
- return matrixClient
-}
-\ No newline at end of file
+// Export hooks for usage in functional components, which are
+// auto-generated based on the defined endpoints
+export const { useGetLoginFlowsQuery, useDoLoginMutation, useLazyGetWellKnownQuery } = matrixApi
diff --git a/src/app/api/apiTypes.ts b/src/app/api/apiTypes.ts
@@ -0,0 +1,49 @@
+export interface ILoginFlow {
+ type: string;
+}
+export interface ILoginFlows {
+ flows: ILoginFlow[];
+}
+export interface IRateLimitError {
+ errcode: string;
+ error?: string;
+ retry_after_ms: number;
+}
+
+export interface ILoginResponse {
+ access_token: string;
+ device_id: string;
+ home_server?: string;
+ user_id: string;
+ expires_in_ms?: number;
+ refresh_token?: string;
+ well_known?: IWellKnown
+}
+
+export interface ILoginParams {
+ address?: string;
+ device_id?: string;
+ identifier?: {
+ type: "m.id.user" | "m.id.thirdparty" | "m.id.phone";
+ user?: string;
+ medium?: string;
+ address?: string;
+ country?: string;
+ phone?: string;
+ };
+ initial_device_display_name?: string;
+ password?: string;
+ user?: string;
+ refresh_token?: string;
+ token?: string;
+ type: "m.login.password" | "m.login.token";
+}
+
+export interface IWellKnown {
+ "m.homeserver": {
+ base_url: string;
+ }
+ "m.identity_server"?: {
+ base_url: string;
+ }
+}
+\ No newline at end of file
diff --git a/src/app/api/reducers.ts b/src/app/api/reducers.ts
@@ -1,41 +0,0 @@
-import { Action, createAction, createReducer } from "@reduxjs/toolkit";
-import { RootState } from "../store";
-import { MatrixClient } from "matrix-js-sdk";
-
-interface ApiLoginStatus {
- loginPending: boolean
- error?: string
- client?: MatrixClient
-}
-
-export const LOGIN_ACTION = "api/LOGIN";
-export const LOGIN_REQUEST_ACTION = createAction('api/LOGIN_REQUEST');
-export const LOGIN_SUCCESS_ACTION = createAction<MatrixClient>('api/LOGIN_SUCCESS');
-export const LOGIN_FAILURE_ACTION = createAction<string>('api/LOGIN_FAILURE');
-
-export interface LOGIN extends Action { type: typeof LOGIN_ACTION, baseUrl: string, username: string, password: string };
-
-
-const initialLoginState: ApiLoginStatus = { loginPending: false };
-
-
-export const apiLoginReducer = createReducer(initialLoginState, (builder) => {
- builder
- .addCase(LOGIN_REQUEST_ACTION, (state, _) => {
- state.loginPending = true;
- state.error = undefined;
- })
- // @ts-ignore: Somehow this state is causing issues here since its written but not read. TS6133
- .addCase(LOGIN_SUCCESS_ACTION, (state, action) => {
- state = initialLoginState;
- state.client = action.payload
- })
- .addCase(LOGIN_FAILURE_ACTION, (state, action) => {
- state.loginPending = false;
- state.error = action.payload
- })
-});
-
-export function getLoginError(state: RootState): string | undefined {
- return state.login.error
-}
-\ No newline at end of file
diff --git a/src/app/protectedRoute.tsx b/src/app/protectedRoute.tsx
@@ -3,7 +3,7 @@ import { Navigate } from "react-router-dom";
import { useAppSelector } from "./hooks";
export const ProtectedRoute = ({ children }: { children: ReactElement<any, any> }) => {
- const client = useAppSelector(state => state.login.client);
+ const client = useAppSelector(state => state.auth.host);
// TODO: relaunch a client if we can and skip login
if (!client) {
diff --git a/src/app/sagas/rootSaga.ts b/src/app/sagas/rootSaga.ts
@@ -1,6 +0,0 @@
-import { all } from "redux-saga/effects";
-import { apiSagas } from "../api/api";
-
-export default function* rootSaga() {
- yield all([apiSagas()]);
-}
diff --git a/src/app/store.ts b/src/app/store.ts
@@ -1,23 +1,20 @@
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
-import createSagaMiddleware from "redux-saga";
-import rootSaga from "./sagas/rootSaga";
-import { apiLoginReducer } from "./api/reducers";
-import { initMatrixClient } from "./api/api";
-
-const sagaMiddleware = createSagaMiddleware();
-const middlewares = [sagaMiddleware];
+import { matrixApi, auth } from "./api/api";
+import { setupListeners } from "@reduxjs/toolkit/dist/query/react";
export const store = configureStore({
reducer: {
- login: apiLoginReducer,
+ auth: auth,
+ [matrixApi.reducerPath]: matrixApi.reducer,
},
middleware: (getDefaultMiddleware) => [
...getDefaultMiddleware(),
- ...middlewares,
+ matrixApi.middleware,
],
- preloadedState: await getInitialState()
+ //preloadedState: await getInitialState()
});
-sagaMiddleware.run(rootSaga);
+
+setupListeners(store.dispatch)
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
@@ -33,11 +30,12 @@ async function getInitialState() {
const accessToken = localStorage.getItem("accessToken");
const baseUrl = localStorage.getItem("baseUrl");
const userId = localStorage.getItem("userId");
+ const deviceId = localStorage.getItem("deviceId");
if (accessToken && baseUrl && userId) {
- const client = await initMatrixClient(baseUrl, userId, accessToken);
+ //const client = await initMatrixClient(baseUrl, userId, deviceId!, accessToken, undefined, true);
return {
login: {
- client,
+ //client,
loginPending: false
}
}
diff --git a/src/components/login/login.tsx b/src/components/login/login.tsx
@@ -1,35 +1,45 @@
import { useDeferredValue, useState } from 'react';
-import { getLoginError } from '../../app/api/reducers';
-import { useAppDispatch, useAppSelector } from '../../app/hooks';
-import { RootState } from '../../app/store';
+import { useAppDispatch } from '../../app/hooks';
+import { setHost, useDoLoginMutation, useLazyGetWellKnownQuery } from '../../app/api/api';
import Button from '../button/button';
import Header from '../header/header';
import Input from '../input/basic/input';
-import { LOGIN_ACTION } from '../../app/api/reducers';
+import { Navigate } from 'react-router-dom';
export function Login() {
- const loginError = useAppSelector((state: RootState) => getLoginError(state));
+ const [login, { isLoading: loginPending, error: loginError }] = useDoLoginMutation();
+ const [triggerWellKnown, _result, _lastPromiseInfo] = useLazyGetWellKnownQuery();
const deferredLoginError = useDeferredValue(loginError);
- const loginPending = useAppSelector((state: RootState) => state.login.loginPending);
- const [homeserver, setHomeserver] = useState('');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
+ const [loginPendingLast, setLoginPendingLast] = useState(false);
const dispatch = useAppDispatch();
+ if (loginPendingLast !== loginPending) {
+ return <Navigate to="/" />;
+ }
+ const startLogin = async () => {
+ dispatch(setHost(`https://${username.split(':')[1]}`));
+ await triggerWellKnown(undefined);
+ login({
+ type: 'm.login.password',
+ identifier: {
+ type: 'm.id.user',
+ user: username,
+ },
+ user: username,
+ password: password
+ })
+ setLoginPendingLast(true);
+ }
+ // TODO: We need to make sure that we do well-known before login
return (
<form className="flex flex-col rounded-md shadow p-4 bg-white gap-2 min-w-[30rem]" onSubmit={(e) => {
e.preventDefault();
- dispatch({ type: LOGIN_ACTION, baseUrl: homeserver, username: username, password: password });
+ startLogin();
}}>
<Header>Login</Header>
- {deferredLoginError ? <h2 className='text-red-500 font-normal text-sm'>{deferredLoginError}</h2> : <div className='min-h-[1.25rem]'></div>}
- <Input
- readonly={loginPending}
- value={homeserver}
- autoFocus={true}
- placeholder="Homeserver"
- onChange={e => setHomeserver(e.target.value)}
- />
+ {deferredLoginError ? <h2 className='text-red-500 font-normal text-sm'>{'error' in deferredLoginError ? deferredLoginError.error : ('data' in deferredLoginError ? JSON.stringify(deferredLoginError.data) : deferredLoginError.message)}</h2> : <div className='min-h-[1.25rem]'></div>}
<Input
readonly={loginPending}
value={username}
@@ -47,7 +57,6 @@ export function Login() {
readonly={loginPending}
style="primary"
type="submit"
- onClick={() => dispatch({ type: LOGIN_ACTION, baseUrl: homeserver, username: username, password: password })}
>
Login
</Button>
diff --git a/src/index.scss b/src/index.scss
@@ -1,3 +1,4 @@
body, html {
margin: 0;
+ height: 100%;
}
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
@@ -1,3 +1,4 @@
+/// <reference types="./@types/global.d.ts" />
import React from 'react'
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
@@ -8,9 +9,6 @@ import './index.scss';
const container = document.getElementById('root')!;
const root = createRoot(container);
-// js-sdk helper
-window.Olm = await import("olm");
-
root.render(
<React.StrictMode>
<Provider store={store}>
diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx
@@ -64,8 +64,8 @@ export default function MainPage() {
online: false,
},
];
- return <div className='flex flex-row w-full gap-2 h-full'>
- <div className='flex flex-col h-full bg-gradient-to-br from-slate-100 via-gray-200 to-orange-200 border-r-[1px] border-slate-300'>
+ return <div className='flex flex-row w-full gap-2 min-h-screen'>
+ <div className='flex flex-col bg-gradient-to-br from-slate-100 via-gray-200 to-orange-200 border-r-[1px] border-slate-300'>
<div className='flex flex-row gap-2 m-2 p-1 items-center border-b-2'>
<Avatar displayname='Test' avatarUrl='https://randomuser.me/api/portraits/men/62.jpg' dm={false} online={false} />
<div className='flex flex-row justify-between items-center w-full'>