From e1797411c6e946c1076b73093cfbb6a9d964688c Mon Sep 17 00:00:00 2001 From: N-Pex Date: Mon, 13 Dec 2021 22:21:59 +0100 Subject: [PATCH] Update Olm --- .npmrc | 1 + package-lock.json | 138 +-- package.json | 6 +- public/js/olm.wasm | Bin 153551 -> 0 bytes src/services/matrix.service.js | 1798 +++++++++++++++++--------------- vue.config.js | 38 +- 6 files changed, 1036 insertions(+), 945 deletions(-) create mode 100644 .npmrc delete mode 100644 public/js/olm.wasm diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..d768668 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@matrix-org:registry=https://gitlab.matrix.org/api/v4/projects/27/packages/npm/ diff --git a/package-lock.json b/package-lock.json index c2fe675..a235c57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "keanuapp-weblite", - "version": "0.1.16", + "version": "0.1.19", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1248,6 +1248,11 @@ } } }, + "@matrix-org/olm": { + "version": "3.2.8", + "resolved": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz", + "integrity": "sha1-jVNjbQReF3biouxmE+VzMN2c6FY=" + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -1836,6 +1841,17 @@ "color-convert": "^2.0.1" } }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -1868,6 +1884,13 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1903,6 +1926,28 @@ "ansi-regex": "^5.0.0" } }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.8.3", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz", + "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + } + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -2731,9 +2776,9 @@ } }, "base-x": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", - "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", "requires": { "safe-buffer": "^5.0.1" } @@ -7971,9 +8016,9 @@ "integrity": "sha512-wRJtOo1v1ch+gN8PRsj0IGJznk+kQ8mz13ds/nuhLI+Qyf/931ZlRpd92oq0IRPpZIb+bhX8pRjzIVdcPDKmiQ==" }, "matrix-js-sdk": { - "version": "12.4.1", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-12.4.1.tgz", - "integrity": "sha512-C9aSGX9e4GoCm0Rli+iGBXmcnRxnwETw7MvgNcSBfPaLHOMZi/wz4YOV7HEZK8R+OXuDrDYyglncWSJkkoDpAQ==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-15.2.0.tgz", + "integrity": "sha512-jZOM8Fn86oNvU3zVQcc+JTKKrtYq4ADN6rPZs4Mwxj/X/GDP+2YIP5176GtviF0GM6VO1dcnPZY73ykl8DayjA==", "requires": { "@babel/runtime": "^7.12.5", "another-json": "^0.2.0", @@ -8706,10 +8751,6 @@ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, - "olm": { - "version": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz", - "integrity": "sha512-B87bTpGIGieuV2FNauChjjQtVltwTGagQFoHm+3Dcse4amKAAGJB/I54dnP/JtbHZ+RYVoApM2OQ46Z4VH6eNg==" - }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -10760,9 +10801,9 @@ } }, "qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.2.tgz", + "integrity": "sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw==", "requires": { "side-channel": "^1.0.4" } @@ -13230,75 +13271,6 @@ } } }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.5.0", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.5.0.tgz", - "integrity": "sha512-WXh+7AgFxGTgb5QAkQtFeUcHNIEq3PGVQ8WskY5ZiFbWBkOwcCPRs4w/2tVyTbh2q6TVRlO3xfvIukUtjsu62A==", - "dev": true, - "optional": true, - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "vue-property-decorator": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz", diff --git a/package.json b/package.json index b9eda89..4d742e7 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "create-sticker-config": "node ./create_sticker_config.js $1" }, "dependencies": { + "@matrix-org/olm": "^3.2.8", "aes-js": "^3.1.2", "axios": "^0.21.0", "clean-insights-sdk": "^2.4", @@ -23,9 +24,8 @@ "json-web-key": "^0.4.0", "linkifyjs": "3.0.0-beta.3", "material-design-icons-iconfont": "^6.1", - "matrix-js-sdk": "^12.4", + "matrix-js-sdk": "^15.2.0", "md-gum-polyfill": "^1.0.0", - "olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz", "pretty-bytes": "^5.6.0", "qrcode": "^1.4.4", "raw-loader": "^4.0.2", @@ -86,4 +86,4 @@ "last 2 versions", "not dead" ] -} \ No newline at end of file +} diff --git a/public/js/olm.wasm b/public/js/olm.wasm deleted file mode 100644 index 97cce63b97b23a042422ed18ca7cdcad096cb11d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153551 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 o$b9$jK&MM_3#>ErE+G!}ji|ua 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 M*WF}*eJlR|1NS;EBLDyZ diff --git a/src/services/matrix.service.js b/src/services/matrix.service.js index be52c1c..c1db9c1 100644 --- a/src/services/matrix.service.js +++ b/src/services/matrix.service.js @@ -1,850 +1,966 @@ -global.Olm = require("olm"); +import olm from "@matrix-org/olm/olm"; +global.Olm = olm; import sdk from "matrix-js-sdk"; import { TimelineWindow, EventTimeline } from "matrix-js-sdk"; import util from "../plugins/utils"; import User from "../models/user"; const LocalStorageCryptoStore = require("matrix-js-sdk/lib/crypto/store/localStorage-crypto-store") - .LocalStorageCryptoStore; + .LocalStorageCryptoStore; export default { - install(Vue, options) { - if (!options || !options.store) { - throw new Error('Please initialise plugin with a Vuex store.') - } - - // Set User-Agent headers. - // Update: browser do not allow this, "Refused to set unsafe header "User-Agent"" - // Keep this code around however, since it's an example of how to add headers to a request... - // sdk.wrapRequest((orig, opts, callback) => { - // opts.headers = opts.headers || {} - // opts.headers['User-Agent'] = "Keanu"; - // var ret = orig(opts, callback); - // return ret; - // }); - const store = options.store; - const i18n = options.i18n; - - const matrixService = new Vue({ - store, - i18n, - data() { - return { - matrixClient: null, - matrixClientReady: false, - rooms: [], - userDisplayName: null, - userAvatar: null, - currentRoom: null, - notificationCount: 0 - } - }, - mounted() { - console.log("Matrix service mounted"); - }, - - computed: { - ready() { - return this.matrixClient != null && this.matrixClientReady; - }, - - currentUser() { - return this.$store.state.auth.user; - }, - - currentUserId() { - const user = this.currentUser || {} - return user.user_id; - }, - - currentUserDisplayName() { - if (this.ready) { - const user = this.matrixClient.getUser(this.currentUserId) || {} - return this.userDisplayName || user.displayName; - } - return null; - }, - - currentUserHomeServer() { - return User.serverName(this.currentUserId); - }, - - currentRoomId() { - return this.$store.state.currentRoomId; - }, - - joinedRooms() { - return this.rooms.filter(room => { - return room.selfMembership === 'join' - }); - }, - - invites() { - return this.rooms.filter(room => { - return room.selfMembership === 'invite' - }); - } - }, - - watch: { - currentRoomId: { - immediate: true, - handler(roomId) { - this.currentRoom = this.getRoom(roomId); - } - }, - }, - - methods: { - createCryptoStore() { - console.log("create crypto store"); - return new LocalStorageCryptoStore(this.$store.getters.storage); - }, - login(user) { - const tempMatrixClient = sdk.createClient(User.homeServerUrl(user.home_server)); - var promiseLogin; - - const self = this; - if (user.access_token) { - // Logged in on "real" account - promiseLogin = Promise.resolve(user); - } else if (user.is_guest && !user.user_id) { - // Generate random username and password. We don't user REAL matrix - // guest accounts because 1. They are not allowed to post media, 2. They - // can not use avatars and 3. They can not seamlessly be upgraded to real accounts. - // - // Instead, we use an ILAG approach, Improved Landing as Guest. - const user = util.randomUser(); - const pass = util.randomPass(); - promiseLogin = tempMatrixClient - .register(user, pass, null, { - type: "m.login.dummy", - initial_device_display_name: this.$config.appName - }) - .then((response) => { - console.log("Response", response); - var u = Object.assign({}, response); - u.home_server = tempMatrixClient.baseUrl; // Don't use deprecated field from response. - u.password = pass; - u.is_guest = true; - this.$store.commit("setUser", u); - return u; - }) - } else { - var data = { user: User.localPart(user.user_id), password: user.password, type: "m.login.password", initial_device_display_name: this.$config.appName }; - if (user.device_id) { - data.device_id = user.device_id; - } - promiseLogin = tempMatrixClient - .login("m.login.password", data) - .then((response) => { - var u = Object.assign({}, response); - if (user.is_guest) { - // Copy over needed properties - u = Object.assign(user, response); - } - u.home_server = tempMatrixClient.baseUrl; // Don't use deprecated field from response. - this.$store.commit("setUser", u); - return u; - }) - } - - return promiseLogin - .then((user) => { - return self.getMatrixClient(user); - }); - }, - - clearCryptoStore() { - // Clear crypto related data - // TODO - for some reason "clearStores" called in "logout" only clears the "account" crypto - // data item, not all sessions etc. Why? We need to do that manually here! - const toRemove = []; - const storage = this.$store.getters.storage; - for (let i = 0; i < storage.length; ++i) { - const key = storage.key(i); - if (key.startsWith("crypto.")) toRemove.push(key); - } - for (const key of toRemove) { - storage.removeItem(key); - } - }, - - logout() { - if (this.matrixClient) { - this.removeMatrixClientListeners(this.matrixClient); - this.matrixClient.stopClient(); - this.matrixClient.clearStores().then(() => { - this.clearCryptoStore() - }) - this.matrixClient = null; - this.matrixClientReady = false; - } else { - this.clearCryptoStore(); - } - - - this.$store.commit("setUser", null); - this.$store.commit("setCurrentRoomId", null); - this.rooms = []; - this.userDisplayName = null; - this.userAvatar = null; - this.currentRoom = null; - this.notificationCount = 0; - }, - - initClient() { - this.reloadRooms(); - this.matrixClientReady = true; - this.matrixClient.emit('Matrix.initialized', this.matrixClient); - this.matrixClient.getProfileInfo(this.currentUserId) - .then(info => { - console.log("Got user profile: " + JSON.stringify(info)); - this.userDisplayName = info.displayname; - this.userAvatar = info.avatar_url; - }) - .catch(err => { - console.log("Failed to get user profile: ", err); - }) - }, - - getMatrixClient(user) { - if (user === undefined) { - user = this.$store.state.auth.user; - } - if (this.matrixClientReady) { - return new Promise((resolve, ignoredreject) => { - resolve(user); - }) - } else if (this.matrixClient) { - return new Promise((resolve, ignoredreject) => { - this.matrixClient.once('Matrix.initialized', (ignoredclient) => { - resolve(user); - }); - }) - } - - const matrixStore = new sdk.MemoryStore(this.$store.getters.storage); - const webStorageSessionStore = new sdk.WebStorageSessionStore( - this.$store.getters.storage - ); - - var homeServer = user.home_server; - if (!homeServer.startsWith("https://")) { - homeServer = "https://" + homeServer; - } - - var opts = { - baseUrl: homeServer, - userId: user.user_id, - store: matrixStore, - sessionStore: webStorageSessionStore, - deviceId: user.device_id, - accessToken: user.access_token, - timelineSupport: true, - unstableClientRelationAggregation: true, - //useAuthorizationHeader: true - } - this.matrixClient = sdk.createClient(opts); - // if (user.is_guest) { - // this.matrixClient.setGuest(true); - // } - return this.matrixClient - .initCrypto() - .then(() => { - console.log("Crypto initialized"); - - this.addMatrixClientListeners(this.matrixClient); - - this.matrixClient.startClient(); - return this.matrixClient; - }) - .then(matrixClient => { - if (matrixClient.isInitialSyncComplete()) { - console.log("Initial sync done already!"); - return matrixClient; - } else { - return new Promise((resolve, reject) => { - matrixClient.once( - "sync", - function (state, ignoredprevState, ignoredres) { - console.log(state); // state will be 'PREPARED' when the client is ready to use - if (state == "PREPARED") { - resolve(matrixClient); - } else if (state == "ERROR") { - reject("Error syncing"); - } - } - ) - }); - } - }) - .then(() => { - // Ready to use! Start by loading rooms. - this.initClient(); - return user; - }) - }, - - /** - * Returns a promise that will log us into the Matrix. - * - * Will use a real account, if we have one, otherwise will create - * a random account. - */ - getLoginPromise() { - if (this.ready) { - return Promise.resolve(this.currentUser); - } - return this.$store.dispatch("login", this.currentUser || new User(this.$config.defaultServer, "", "", true)); - }, - - addMatrixClientListeners(client) { - if (client) { - client.on("event", this.onEvent); - client.on("Room", this.onRoom); - client.on("Session.logged_out", this.onSessionLoggedOut); - client.on("Room.myMembership", this.onRoomMyMembership); - } - }, - - removeMatrixClientListeners(client) { - if (client) { - client.off("event", this.onEvent); - client.off("Room", this.onRoom); - client.off("Session.logged_out", this.onSessionLoggedOut); - client.off("Room.myMembership", this.onRoomMyMembership); - } - }, - - onEvent(event) { - switch (event.getType()) { - case "m.room.topic": { - const room = this.matrixClient.getRoom(event.getRoomId()); - if (room) { - Vue.set(room, "topic", event.getContent().topic); - } - } - break; - - case "m.room.avatar": { - const room = this.matrixClient.getRoom(event.getRoomId()); - if (room) { - Vue.set(room, "avatar", room.getAvatarUrl(this.matrixClient.getHomeserverUrl(), 80, 80, "scale", true)); - } - } - break; - - case "m.room.member": { - if (this.currentRoom && event.getRoomId() == this.currentRoom.roomId) { // Don't use this.currentRoomId, may be an alias. We need the real id! - if (event.getContent().membership == "leave" && (event.getPrevContent() || {}).membership == "join" && event.getStateKey() == this.currentUserId && event.getSender() != this.currentUserId) { - // We were kicked - const wasPurged = (event.getContent().reason == "Room Deleted"); - this.$navigation.push({ name: "Goodbye", params: { roomWasPurged: wasPurged } }, -1); - } - } - } - break; - } - this.updateNotificationCount(); - }, - - onRoom(ignoredroom) { - console.log("Got room", ignoredroom); - this.reloadRooms(); - this.updateNotificationCount(); - }, - - onRoomMyMembership(room) { - if (room.selfMembership === "invite") { - // Invitation. Need to call "recalculate" to pick - // up room name, not sure why exactly. - room.recalculate(); - this.reloadRooms(); - } - }, - - onSessionLoggedOut() { - console.log("Logged out!"); - if (this.matrixClient) { - this.removeMatrixClientListeners(this.matrixClient); - this.matrixClient.stopClient(); - this.matrixClient = null; - this.matrixClientReady = false; - } - - // For "real" accounts we totally wipe the user object, but for "guest" - // accounts (i.e. created from random data and with password never changed) - // we need to hang on to the generated password and use that to login to a new - // session, so only wipe the token in s that case. - // Clear the access token - var user = JSON.parse(this.$store.state.auth.user); - if (user.is_guest) { - delete user.access_token; - this.$store.commit("setUser", user); - // Login again - this.login(user); - } else { - this.$store.commit("setUser", null); - this.$store.commit("setCurrentRoomId", null); - this.$navigation.push({ path: "/login" }, -1); - } - }, - - reloadRooms() { - // TODO - do incremental update instead of replacing the whole array - // each time! - var updatedRooms = this.matrixClient.getVisibleRooms(); - updatedRooms = updatedRooms.filter(room => { - return room.selfMembership && (room.selfMembership == "invite" || room.selfMembership == "join"); - }); - updatedRooms.forEach(room => { - if (!room.avatar) { - Vue.set(room, "avatar", room.getAvatarUrl(this.matrixClient.getHomeserverUrl(), 80, 80, "scale", true)); - } - }); - console.log("Reload rooms", updatedRooms); - Vue.set(this, "rooms", updatedRooms); - const currentRoom = this.getRoom(this.$store.state.currentRoomId); - if (this.currentRoom != currentRoom) { - this.currentRoom = currentRoom; - } - }, - - setCurrentRoomId(roomId) { - this.$store.commit("setCurrentRoomId", roomId); - this.currentRoom = this.getRoom(roomId); - }, - - getRoom(roomId) { - if (!roomId) { - return null; - } - var room = null; - if (this.matrixClient) { - const visibleRooms = this.matrixClient.getRooms(); - room = visibleRooms.find(room => { - if (roomId.startsWith("#")) { - return room.getCanonicalAlias() == roomId; - } - return room.roomId == roomId; - }); - } - return room || null; - }, - - /** - * Return all users we are in a "invite" only room with! - */ - getAllFriends() { - var ids = {}; - const ret = []; - for (const room of this.rooms) { - if (room.selfMembership == 'join' && this.getRoomJoinRule(room) == 'invite') { - for (const member of room.getJoinedMembers()) { - if (member.userId != this.currentUserId && !ids[member.userId]) { - ids[member.userId] = member; - ret.push(member); - } - } - } - } - ret.sort((a, b) => { - const aName = a.user ? a.user.displayName : a.name; - const bName = b.user ? b.user.displayName : b.name; - return aName.localeCompare(bName); - }); - return ret; - }, - - getRoomJoinRule(room) { - if (room) { - const joinRules = room.currentState.getStateEvents( - "m.room.join_rules", - "" - ); - return joinRules && joinRules.getContent().join_rule; - } - return null; - }, - - getRoomHistoryVisibility(room) { - if (room) { - const historyVisibility = room.currentState.getStateEvents( - "m.room.history_visibility", - "" - ); - return historyVisibility && historyVisibility.getContent().history_visibility; - } - return null; - }, - - leaveRoom(roomId) { - return this.matrixClient.leave(roomId, undefined) - .then(() => { - this.rooms = this.rooms.filter(room => { - room.roomId != roomId; - }); - this.matrixClient.store.removeRoom(roomId); - //this.matrixClient.forget(roomId, true, undefined); - }) - }, - - /** - * Purge the room with the given id! This means: - * - Make room invite only - * - Disallow guest access - * - Set history visibility to 'joined' - * - Redact all events - * - Kick all members - * @param roomId - */ - purgeRoom(roomId, statusCallback) { - const oldGlobalErrorSetting = this.matrixClient.getGlobalErrorOnUnknownDevices(); - return new Promise((resolve, reject) => { - const room = this.getRoom(roomId); - if (!room) { - reject("Room not found!"); - return; - } - - const timelineWindow = new TimelineWindow( - this.matrixClient, - room.getUnfilteredTimelineSet(), - {} - ); - const self = this; - - //console.log("Purge: set invite only"); - statusCallback(this.$t('room.purge_set_room_state')); - this.matrixClient.sendStateEvent( - roomId, - "m.room.join_rules", - { join_rule: "invite" }, - "" - ) - .then(() => { - //console.log("Purge: forbid guest access"); - return this.matrixClient.sendStateEvent( - roomId, - "m.room.guest_access", - { guest_access: "forbidden" }, - "" - ); - }) - .then(() => { - //console.log("Purge: set history visibility to 'joined'"); - return this.matrixClient.sendStateEvent(roomId, "m.room.history_visibility", { - history_visibility: "joined", - }); - }) - .then(() => { - //console.log("Purge: create timeline"); - return timelineWindow.load(null, 100) - }) - .then(() => { - const getMoreIfAvailable = function _getMoreIfAvailable() { - if (timelineWindow.canPaginate(EventTimeline.BACKWARDS) - ) { - //console.log("Purge: page back"); - return timelineWindow - .paginate(EventTimeline.BACKWARDS, 100, true, 5) - .then((ignoredsuccess) => { - return _getMoreIfAvailable.call(self); - }); - } else { - return Promise.resolve("Done"); - } - }.bind(self); - return getMoreIfAvailable(); - }) - .then(() => { - //console.log("Purge: redact events"); - statusCallback(this.$t('room.purge_redacting_events')); - // First ignore unknown device errors - this.matrixClient.setGlobalErrorOnUnknownDevices(false); - var redactionPromises = []; - timelineWindow.getEvents().forEach(event => { - if (!event.isRedacted() && !event.isRedaction() && !event.isState()) { - // Redact! - redactionPromises.push(this.matrixClient.redactEvent(event.getRoomId(), event.getId())); - } - }); - return Promise.all(redactionPromises); - }) - .then(() => { - //console.log("Purge: kick members"); - statusCallback(this.$t('room.purge_removing_members')); - var joined = room.getMembersWithMembership("join"); - var invited = room.getMembersWithMembership("invite"); - var members = joined.concat(invited); - - var kickPromises = []; - members.forEach(member => { - if (member.userId != self.currentUserId) { - kickPromises.push(this.matrixClient.kick(roomId, member.userId, "Room Deleted")); - } - }); - return Promise.all(kickPromises); - }) - .then(() => { - statusCallback(null); - this.matrixClient.setGlobalErrorOnUnknownDevices(oldGlobalErrorSetting); - return this.leaveRoom(roomId); - }) - .then(() => { - resolve(true); // Done! - }) - .catch((err) => { - console.error("Error purging room", err); - this.matrixClient.setGlobalErrorOnUnknownDevices(oldGlobalErrorSetting); - reject(err); - }); - }) - }, - - /** - * Get a private chat room with the given user. Searches through our rooms to see - * if a suitable room already exists. If not, one is created. - * @param {*} userId The user to chat with. - */ - getOrCreatePrivateChat(userId) { - return new Promise((resolve, reject) => { - for (const room of this.rooms) { - // Is the other member the one we are looking for? - if (this.isDirectRoomWith(room, userId)) { - var member = room.getMember(userId); - if (member && member.membership != "join") { - // Resend invite - this.matrixClient.invite(room.roomId, userId); - } - resolve(room); - return; - } - } - - // No room found, create one - // - const createRoomOptions = { - visibility: "private", // Not listed! - preset: "private_chat", - initial_state: [ - { - type: "m.room.encryption", - state_key: "", - content: { - algorithm: "m.megolm.v1.aes-sha2", - }, - }, - { - type: "m.room.guest_access", - state_key: "", - content: { - guest_access: "forbidden", - }, - }, - { - type: "m.room.history_visibility", - state_key: "", - content: { - history_visibility: "joined", - }, - }, - ], - invite: [userId], - }; - return this.matrixClient - .createRoom(createRoomOptions) - .then(({ room_id, room_alias }) => { - resolve(this.getRoom(room_alias || room_id)); - }) - .catch((error) => { - reject(error); - }); - }) - }, - - /** - * Return true if this room is a direct room with the given user. - * @param { } room - * @param {*} userId - */ - isDirectRoomWith(room, userId) { - if (room.selfMembership == "join" && room.getInvitedAndJoinedMemberCount() == 2) { - // Is the other member the one we are looking for? - if (room.getMembersWithMembership("join").some(item => item.userId == userId)) { - return true; - } else if (room.getMembersWithMembership("invite").some(item => item.userId == userId)) { - return true; - } - } - return false; - }, - - on(event, handler) { - if (this.matrixClient) { - this.matrixClient.on(event, handler); - } - }, - - off(event, handler) { - if (this.matrixClient) { - this.matrixClient.off(event, handler); - } - }, - - setPassword(oldPassword, newPassword) { - if (this.matrixClient && this.currentUser) { - const authDict = { - type: 'm.login.password', - identifier: { - type: 'm.id.user', - user: this.currentUser.user_id, - }, - // TODO: Remove `user` once servers support proper UIA - // See https://github.com/matrix-org/synapse/issues/5665 - user: this.currentUser.user_id, - password: oldPassword, - }; - const self = this; - return this.matrixClient.setPassword(authDict, newPassword) - .then(() => { - // Forget password and remove the 'is_guest' flag, we are now a "real" user! - self.currentUser.password = undefined; - self.currentUser.is_guest = false; - self.$store.commit("setUser", self.currentUser); - }) - .then(() => { - return true; - }) - } - return Promise.resolve(false); - }, - - uploadFile(file, opts) { - return this.matrixClient.uploadContent(file, opts); - }, - - getPublicRoomInfo(roomId) { - if (!roomId) { - return Promise.reject("Invalid parameters"); - } - - const parts = roomId.split(':'); - if (parts.length != 2) { - return Promise.reject("Unknown room server"); - } - const server = parts[1]; - - var clientPromise; - if (this.matrixClient) { - clientPromise = this.getMatrixClient().then(() => { - return this.matrixClient; - }) - } else { - const tempMatrixClient = sdk.createClient(this.$config.defaultServer); - var tempUserString = this.$store.state.tempuser; - var tempUser = null; - if (tempUserString) { - tempUser = JSON.parse(tempUserString); - } - - // Need to create an account? - // - if (tempUser) { - clientPromise = Promise.resolve(tempUser); - } else { - const user = util.randomUser(); - const pass = util.randomPass(); - clientPromise = tempMatrixClient - .register(user, pass, null, { - type: "m.login.dummy", - initial_device_display_name: this.$config.appName - }) - .then((response) => { - console.log("Response", response); - response.password = pass; - response.is_guest = true; - this.$store.commit("setTempUser", response); - return response; - }); - } - - // Get an access token - clientPromise = clientPromise.then(user => { - var data = { user: User.localPart(user.user_id), password: user.password, type: "m.login.password", initial_device_display_name: this.$config.appName }; - if (user.device_id) { - data.device_id = user.device_id; - } - return tempMatrixClient.login("m.login.password", data) - }) - - // Then login - // - // Create a slimmed down client, without crypto. This one is - // Only used to get public room info from. - clientPromise = clientPromise.then(user => { - var opts = { - baseUrl: this.$config.defaultServer, - userId: user.user_id, - accessToken: user.access_token, - timelineSupport: false, - } - var matrixClient = sdk.createClient(opts); - matrixClient.startClient(); - return matrixClient; - }); - } - - const findOrGetMore = function _findOrGetMore(client, response) { - for (var room of response.chunk) { - if ((roomId.startsWith("#") && room.canonical_alias == roomId) || (roomId.startsWith("!") && room.room_id == roomId)) { - if (room.avatar_url) { - room.avatar = client.mxcUrlToHttp(room.avatar_url, 80, 80, 'scale', true); - } - return Promise.resolve(room); - } - } - if (response.next_batch) { - return client.publicRooms({ server: server, limit: 1000, since: response.next_batch }) - .then(response => { - return _findOrGetMore(client, response); - }) - .catch(err => { - return Promise.reject("Failed to find room: " + err); - }); - } else { - return Promise.reject("No more data"); - } - }; - - var matrixClient; - return clientPromise - .then(client => { - matrixClient = client; - return matrixClient.publicRooms({ server: server, limit: 1000 }) - }) - .then(response => { - return findOrGetMore(matrixClient, response); - }) - .catch(err => { - return Promise.reject("Failed to find room: " + err); - }); - }, - - updateNotificationCount() { - var count = 0; - this.rooms.forEach(room => { - count += room.getUnreadNotificationCount('total') || 0; - }); - this.notificationCount = count; - } - - } - }) - - sdk.setCryptoStoreFactory(matrixService.createCryptoStore.bind(matrixService)); - - Vue.prototype.$matrix = matrixService; + install(Vue, options) { + if (!options || !options.store) { + throw new Error("Please initialise plugin with a Vuex store."); } -} + + // Set User-Agent headers. + // Update: browser do not allow this, "Refused to set unsafe header "User-Agent"" + // Keep this code around however, since it's an example of how to add headers to a request... + // sdk.wrapRequest((orig, opts, callback) => { + // opts.headers = opts.headers || {} + // opts.headers['User-Agent'] = "Keanu"; + // var ret = orig(opts, callback); + // return ret; + // }); + const store = options.store; + const i18n = options.i18n; + + const matrixService = new Vue({ + store, + i18n, + data() { + return { + matrixClient: null, + matrixClientReady: false, + rooms: [], + userDisplayName: null, + userAvatar: null, + currentRoom: null, + notificationCount: 0, + }; + }, + mounted() { + console.log("Matrix service mounted"); + }, + + computed: { + ready() { + return this.matrixClient != null && this.matrixClientReady; + }, + + currentUser() { + return this.$store.state.auth.user; + }, + + currentUserId() { + const user = this.currentUser || {}; + return user.user_id; + }, + + currentUserDisplayName() { + if (this.ready) { + const user = this.matrixClient.getUser(this.currentUserId) || {}; + return this.userDisplayName || user.displayName; + } + return null; + }, + + currentUserHomeServer() { + return User.serverName(this.currentUserId); + }, + + currentRoomId() { + return this.$store.state.currentRoomId; + }, + + joinedRooms() { + return this.rooms.filter((room) => { + return room.selfMembership === "join"; + }); + }, + + invites() { + return this.rooms.filter((room) => { + return room.selfMembership === "invite"; + }); + }, + }, + + watch: { + currentRoomId: { + immediate: true, + handler(roomId) { + this.currentRoom = this.getRoom(roomId); + }, + }, + }, + + methods: { + createCryptoStore() { + console.log("create crypto store"); + return new LocalStorageCryptoStore(this.$store.getters.storage); + }, + login(user) { + const tempMatrixClient = sdk.createClient( + User.homeServerUrl(user.home_server) + ); + var promiseLogin; + + const self = this; + if (user.access_token) { + // Logged in on "real" account + promiseLogin = Promise.resolve(user); + } else if (user.is_guest && !user.user_id) { + // Generate random username and password. We don't user REAL matrix + // guest accounts because 1. They are not allowed to post media, 2. They + // can not use avatars and 3. They can not seamlessly be upgraded to real accounts. + // + // Instead, we use an ILAG approach, Improved Landing as Guest. + const user = util.randomUser(); + const pass = util.randomPass(); + promiseLogin = tempMatrixClient + .register(user, pass, null, { + type: "m.login.dummy", + initial_device_display_name: this.$config.appName, + }) + .then((response) => { + console.log("Response", response); + var u = Object.assign({}, response); + u.home_server = tempMatrixClient.baseUrl; // Don't use deprecated field from response. + u.password = pass; + u.is_guest = true; + this.$store.commit("setUser", u); + return u; + }); + } else { + var data = { + user: User.localPart(user.user_id), + password: user.password, + type: "m.login.password", + initial_device_display_name: this.$config.appName, + }; + if (user.device_id) { + data.device_id = user.device_id; + } + promiseLogin = tempMatrixClient + .login("m.login.password", data) + .then((response) => { + var u = Object.assign({}, response); + if (user.is_guest) { + // Copy over needed properties + u = Object.assign(user, response); + } + u.home_server = tempMatrixClient.baseUrl; // Don't use deprecated field from response. + this.$store.commit("setUser", u); + return u; + }); + } + + return promiseLogin.then((user) => { + return self.getMatrixClient(user); + }); + }, + + clearCryptoStore() { + // Clear crypto related data + // TODO - for some reason "clearStores" called in "logout" only clears the "account" crypto + // data item, not all sessions etc. Why? We need to do that manually here! + const toRemove = []; + const storage = this.$store.getters.storage; + for (let i = 0; i < storage.length; ++i) { + const key = storage.key(i); + if (key.startsWith("crypto.")) toRemove.push(key); + } + for (const key of toRemove) { + storage.removeItem(key); + } + }, + + logout() { + if (this.matrixClient) { + this.removeMatrixClientListeners(this.matrixClient); + this.matrixClient.stopClient(); + this.matrixClient.clearStores().then(() => { + this.clearCryptoStore(); + }); + this.matrixClient = null; + this.matrixClientReady = false; + } else { + this.clearCryptoStore(); + } + + this.$store.commit("setUser", null); + this.$store.commit("setCurrentRoomId", null); + this.rooms = []; + this.userDisplayName = null; + this.userAvatar = null; + this.currentRoom = null; + this.notificationCount = 0; + }, + + initClient() { + this.reloadRooms(); + this.matrixClientReady = true; + this.matrixClient.emit("Matrix.initialized", this.matrixClient); + this.matrixClient + .getProfileInfo(this.currentUserId) + .then((info) => { + console.log("Got user profile: " + JSON.stringify(info)); + this.userDisplayName = info.displayname; + this.userAvatar = info.avatar_url; + }) + .catch((err) => { + console.log("Failed to get user profile: ", err); + }); + }, + + getMatrixClient(user) { + if (user === undefined) { + user = this.$store.state.auth.user; + } + if (this.matrixClientReady) { + return new Promise((resolve, ignoredreject) => { + resolve(user); + }); + } else if (this.matrixClient) { + return new Promise((resolve, ignoredreject) => { + this.matrixClient.once("Matrix.initialized", (ignoredclient) => { + resolve(user); + }); + }); + } + + const matrixStore = new sdk.MemoryStore(this.$store.getters.storage); + const webStorageSessionStore = new sdk.WebStorageSessionStore( + this.$store.getters.storage + ); + + var homeServer = user.home_server; + if (!homeServer.startsWith("https://")) { + homeServer = "https://" + homeServer; + } + + var opts = { + baseUrl: homeServer, + userId: user.user_id, + store: matrixStore, + sessionStore: webStorageSessionStore, + deviceId: user.device_id, + accessToken: user.access_token, + timelineSupport: true, + unstableClientRelationAggregation: true, + //useAuthorizationHeader: true + }; + this.matrixClient = sdk.createClient(opts); + // if (user.is_guest) { + // this.matrixClient.setGuest(true); + // } + return this.matrixClient + .initCrypto() + .then(() => { + console.log("Crypto initialized"); + + this.addMatrixClientListeners(this.matrixClient); + + this.matrixClient.startClient(); + return this.matrixClient; + }) + .then((matrixClient) => { + if (matrixClient.isInitialSyncComplete()) { + console.log("Initial sync done already!"); + return matrixClient; + } else { + return new Promise((resolve, reject) => { + matrixClient.once("sync", function( + state, + ignoredprevState, + ignoredres + ) { + console.log(state); // state will be 'PREPARED' when the client is ready to use + if (state == "PREPARED") { + resolve(matrixClient); + } else if (state == "ERROR") { + reject("Error syncing"); + } + }); + }); + } + }) + .then(() => { + // Ready to use! Start by loading rooms. + this.initClient(); + return user; + }); + }, + + /** + * Returns a promise that will log us into the Matrix. + * + * Will use a real account, if we have one, otherwise will create + * a random account. + */ + getLoginPromise() { + if (this.ready) { + return Promise.resolve(this.currentUser); + } + return this.$store.dispatch( + "login", + this.currentUser || + new User(this.$config.defaultServer, "", "", true) + ); + }, + + addMatrixClientListeners(client) { + if (client) { + client.on("event", this.onEvent); + client.on("Room", this.onRoom); + client.on("Session.logged_out", this.onSessionLoggedOut); + client.on("Room.myMembership", this.onRoomMyMembership); + } + }, + + removeMatrixClientListeners(client) { + if (client) { + client.off("event", this.onEvent); + client.off("Room", this.onRoom); + client.off("Session.logged_out", this.onSessionLoggedOut); + client.off("Room.myMembership", this.onRoomMyMembership); + } + }, + + onEvent(event) { + switch (event.getType()) { + case "m.room.topic": + { + const room = this.matrixClient.getRoom(event.getRoomId()); + if (room) { + Vue.set(room, "topic", event.getContent().topic); + } + } + break; + + case "m.room.avatar": + { + const room = this.matrixClient.getRoom(event.getRoomId()); + if (room) { + Vue.set( + room, + "avatar", + room.getAvatarUrl( + this.matrixClient.getHomeserverUrl(), + 80, + 80, + "scale", + true + ) + ); + } + } + break; + + case "m.room.member": + { + if ( + this.currentRoom && + event.getRoomId() == this.currentRoom.roomId + ) { + // Don't use this.currentRoomId, may be an alias. We need the real id! + if ( + event.getContent().membership == "leave" && + (event.getPrevContent() || {}).membership == "join" && + event.getStateKey() == this.currentUserId && + event.getSender() != this.currentUserId + ) { + // We were kicked + const wasPurged = + event.getContent().reason == "Room Deleted"; + this.$navigation.push( + { name: "Goodbye", params: { roomWasPurged: wasPurged } }, + -1 + ); + } + } + } + break; + } + this.updateNotificationCount(); + }, + + onRoom(ignoredroom) { + console.log("Got room", ignoredroom); + this.reloadRooms(); + this.updateNotificationCount(); + }, + + onRoomMyMembership(room) { + if (room.selfMembership === "invite") { + // Invitation. Need to call "recalculate" to pick + // up room name, not sure why exactly. + room.recalculate(); + this.reloadRooms(); + } + }, + + onSessionLoggedOut() { + console.log("Logged out!"); + if (this.matrixClient) { + this.removeMatrixClientListeners(this.matrixClient); + this.matrixClient.stopClient(); + this.matrixClient = null; + this.matrixClientReady = false; + } + + // For "real" accounts we totally wipe the user object, but for "guest" + // accounts (i.e. created from random data and with password never changed) + // we need to hang on to the generated password and use that to login to a new + // session, so only wipe the token in s that case. + // Clear the access token + var user = JSON.parse(this.$store.state.auth.user); + if (user.is_guest) { + delete user.access_token; + this.$store.commit("setUser", user); + // Login again + this.login(user); + } else { + this.$store.commit("setUser", null); + this.$store.commit("setCurrentRoomId", null); + this.$navigation.push({ path: "/login" }, -1); + } + }, + + reloadRooms() { + // TODO - do incremental update instead of replacing the whole array + // each time! + var updatedRooms = this.matrixClient.getVisibleRooms(); + updatedRooms = updatedRooms.filter((room) => { + return ( + room.selfMembership && + (room.selfMembership == "invite" || room.selfMembership == "join") + ); + }); + updatedRooms.forEach((room) => { + if (!room.avatar) { + Vue.set( + room, + "avatar", + room.getAvatarUrl( + this.matrixClient.getHomeserverUrl(), + 80, + 80, + "scale", + true + ) + ); + } + }); + console.log("Reload rooms", updatedRooms); + Vue.set(this, "rooms", updatedRooms); + const currentRoom = this.getRoom(this.$store.state.currentRoomId); + if (this.currentRoom != currentRoom) { + this.currentRoom = currentRoom; + } + }, + + setCurrentRoomId(roomId) { + this.$store.commit("setCurrentRoomId", roomId); + this.currentRoom = this.getRoom(roomId); + }, + + getRoom(roomId) { + if (!roomId) { + return null; + } + var room = null; + if (this.matrixClient) { + const visibleRooms = this.matrixClient.getRooms(); + room = visibleRooms.find((room) => { + if (roomId.startsWith("#")) { + return room.getCanonicalAlias() == roomId; + } + return room.roomId == roomId; + }); + } + return room || null; + }, + + /** + * Return all users we are in a "invite" only room with! + */ + getAllFriends() { + var ids = {}; + const ret = []; + for (const room of this.rooms) { + if ( + room.selfMembership == "join" && + this.getRoomJoinRule(room) == "invite" + ) { + for (const member of room.getJoinedMembers()) { + if ( + member.userId != this.currentUserId && + !ids[member.userId] + ) { + ids[member.userId] = member; + ret.push(member); + } + } + } + } + ret.sort((a, b) => { + const aName = a.user ? a.user.displayName : a.name; + const bName = b.user ? b.user.displayName : b.name; + return aName.localeCompare(bName); + }); + return ret; + }, + + getRoomJoinRule(room) { + if (room) { + const joinRules = room.currentState.getStateEvents( + "m.room.join_rules", + "" + ); + return joinRules && joinRules.getContent().join_rule; + } + return null; + }, + + getRoomHistoryVisibility(room) { + if (room) { + const historyVisibility = room.currentState.getStateEvents( + "m.room.history_visibility", + "" + ); + return ( + historyVisibility && + historyVisibility.getContent().history_visibility + ); + } + return null; + }, + + leaveRoom(roomId) { + return this.matrixClient.leave(roomId, undefined).then(() => { + this.rooms = this.rooms.filter((room) => { + room.roomId != roomId; + }); + this.matrixClient.store.removeRoom(roomId); + //this.matrixClient.forget(roomId, true, undefined); + }); + }, + + /** + * Purge the room with the given id! This means: + * - Make room invite only + * - Disallow guest access + * - Set history visibility to 'joined' + * - Redact all events + * - Kick all members + * @param roomId + */ + purgeRoom(roomId, statusCallback) { + const oldGlobalErrorSetting = this.matrixClient.getGlobalErrorOnUnknownDevices(); + return new Promise((resolve, reject) => { + const room = this.getRoom(roomId); + if (!room) { + reject("Room not found!"); + return; + } + + const timelineWindow = new TimelineWindow( + this.matrixClient, + room.getUnfilteredTimelineSet(), + {} + ); + const self = this; + + //console.log("Purge: set invite only"); + statusCallback(this.$t("room.purge_set_room_state")); + this.matrixClient + .sendStateEvent( + roomId, + "m.room.join_rules", + { join_rule: "invite" }, + "" + ) + .then(() => { + //console.log("Purge: forbid guest access"); + return this.matrixClient.sendStateEvent( + roomId, + "m.room.guest_access", + { guest_access: "forbidden" }, + "" + ); + }) + .then(() => { + //console.log("Purge: set history visibility to 'joined'"); + return this.matrixClient.sendStateEvent( + roomId, + "m.room.history_visibility", + { + history_visibility: "joined", + } + ); + }) + .then(() => { + //console.log("Purge: create timeline"); + return timelineWindow.load(null, 100); + }) + .then(() => { + const getMoreIfAvailable = function _getMoreIfAvailable() { + if (timelineWindow.canPaginate(EventTimeline.BACKWARDS)) { + //console.log("Purge: page back"); + return timelineWindow + .paginate(EventTimeline.BACKWARDS, 100, true, 5) + .then((ignoredsuccess) => { + return _getMoreIfAvailable.call(self); + }); + } else { + return Promise.resolve("Done"); + } + }.bind(self); + return getMoreIfAvailable(); + }) + .then(() => { + //console.log("Purge: redact events"); + statusCallback(this.$t("room.purge_redacting_events")); + // First ignore unknown device errors + this.matrixClient.setGlobalErrorOnUnknownDevices(false); + var redactionPromises = []; + timelineWindow.getEvents().forEach((event) => { + if ( + !event.isRedacted() && + !event.isRedaction() && + !event.isState() + ) { + // Redact! + redactionPromises.push( + this.matrixClient.redactEvent( + event.getRoomId(), + event.getId() + ) + ); + } + }); + return Promise.all(redactionPromises); + }) + .then(() => { + //console.log("Purge: kick members"); + statusCallback(this.$t("room.purge_removing_members")); + var joined = room.getMembersWithMembership("join"); + var invited = room.getMembersWithMembership("invite"); + var members = joined.concat(invited); + + var kickPromises = []; + members.forEach((member) => { + if (member.userId != self.currentUserId) { + kickPromises.push( + this.matrixClient.kick( + roomId, + member.userId, + "Room Deleted" + ) + ); + } + }); + return Promise.all(kickPromises); + }) + .then(() => { + statusCallback(null); + this.matrixClient.setGlobalErrorOnUnknownDevices( + oldGlobalErrorSetting + ); + return this.leaveRoom(roomId); + }) + .then(() => { + resolve(true); // Done! + }) + .catch((err) => { + console.error("Error purging room", err); + this.matrixClient.setGlobalErrorOnUnknownDevices( + oldGlobalErrorSetting + ); + reject(err); + }); + }); + }, + + /** + * Get a private chat room with the given user. Searches through our rooms to see + * if a suitable room already exists. If not, one is created. + * @param {*} userId The user to chat with. + */ + getOrCreatePrivateChat(userId) { + return new Promise((resolve, reject) => { + for (const room of this.rooms) { + // Is the other member the one we are looking for? + if (this.isDirectRoomWith(room, userId)) { + var member = room.getMember(userId); + if (member && member.membership != "join") { + // Resend invite + this.matrixClient.invite(room.roomId, userId); + } + resolve(room); + return; + } + } + + // No room found, create one + // + const createRoomOptions = { + visibility: "private", // Not listed! + preset: "private_chat", + initial_state: [ + { + type: "m.room.encryption", + state_key: "", + content: { + algorithm: "m.megolm.v1.aes-sha2", + }, + }, + { + type: "m.room.guest_access", + state_key: "", + content: { + guest_access: "forbidden", + }, + }, + { + type: "m.room.history_visibility", + state_key: "", + content: { + history_visibility: "joined", + }, + }, + ], + invite: [userId], + }; + return this.matrixClient + .createRoom(createRoomOptions) + .then(({ room_id, room_alias }) => { + resolve(this.getRoom(room_alias || room_id)); + }) + .catch((error) => { + reject(error); + }); + }); + }, + + /** + * Return true if this room is a direct room with the given user. + * @param { } room + * @param {*} userId + */ + isDirectRoomWith(room, userId) { + if ( + room.selfMembership == "join" && + room.getInvitedAndJoinedMemberCount() == 2 + ) { + // Is the other member the one we are looking for? + if ( + room + .getMembersWithMembership("join") + .some((item) => item.userId == userId) + ) { + return true; + } else if ( + room + .getMembersWithMembership("invite") + .some((item) => item.userId == userId) + ) { + return true; + } + } + return false; + }, + + on(event, handler) { + if (this.matrixClient) { + this.matrixClient.on(event, handler); + } + }, + + off(event, handler) { + if (this.matrixClient) { + this.matrixClient.off(event, handler); + } + }, + + setPassword(oldPassword, newPassword) { + if (this.matrixClient && this.currentUser) { + const authDict = { + type: "m.login.password", + identifier: { + type: "m.id.user", + user: this.currentUser.user_id, + }, + // TODO: Remove `user` once servers support proper UIA + // See https://github.com/matrix-org/synapse/issues/5665 + user: this.currentUser.user_id, + password: oldPassword, + }; + const self = this; + return this.matrixClient + .setPassword(authDict, newPassword) + .then(() => { + // Forget password and remove the 'is_guest' flag, we are now a "real" user! + self.currentUser.password = undefined; + self.currentUser.is_guest = false; + self.$store.commit("setUser", self.currentUser); + }) + .then(() => { + return true; + }); + } + return Promise.resolve(false); + }, + + uploadFile(file, opts) { + return this.matrixClient.uploadContent(file, opts); + }, + + getPublicRoomInfo(roomId) { + if (!roomId) { + return Promise.reject("Invalid parameters"); + } + + const parts = roomId.split(":"); + if (parts.length != 2) { + return Promise.reject("Unknown room server"); + } + const server = parts[1]; + + var clientPromise; + if (this.matrixClient) { + clientPromise = this.getMatrixClient().then(() => { + return this.matrixClient; + }); + } else { + const tempMatrixClient = sdk.createClient( + this.$config.defaultServer + ); + var tempUserString = this.$store.state.tempuser; + var tempUser = null; + if (tempUserString) { + tempUser = JSON.parse(tempUserString); + } + + // Need to create an account? + // + if (tempUser) { + clientPromise = Promise.resolve(tempUser); + } else { + const user = util.randomUser(); + const pass = util.randomPass(); + clientPromise = tempMatrixClient + .register(user, pass, null, { + type: "m.login.dummy", + initial_device_display_name: this.$config.appName, + }) + .then((response) => { + console.log("Response", response); + response.password = pass; + response.is_guest = true; + this.$store.commit("setTempUser", response); + return response; + }); + } + + // Get an access token + clientPromise = clientPromise.then((user) => { + var data = { + user: User.localPart(user.user_id), + password: user.password, + type: "m.login.password", + initial_device_display_name: this.$config.appName, + }; + if (user.device_id) { + data.device_id = user.device_id; + } + return tempMatrixClient.login("m.login.password", data); + }); + + // Then login + // + // Create a slimmed down client, without crypto. This one is + // Only used to get public room info from. + clientPromise = clientPromise.then((user) => { + var opts = { + baseUrl: this.$config.defaultServer, + userId: user.user_id, + accessToken: user.access_token, + timelineSupport: false, + }; + var matrixClient = sdk.createClient(opts); + matrixClient.startClient(); + return matrixClient; + }); + } + + const findOrGetMore = function _findOrGetMore(client, response) { + for (var room of response.chunk) { + if ( + (roomId.startsWith("#") && room.canonical_alias == roomId) || + (roomId.startsWith("!") && room.room_id == roomId) + ) { + if (room.avatar_url) { + room.avatar = client.mxcUrlToHttp( + room.avatar_url, + 80, + 80, + "scale", + true + ); + } + return Promise.resolve(room); + } + } + if (response.next_batch) { + return client + .publicRooms({ + server: server, + limit: 1000, + since: response.next_batch, + }) + .then((response) => { + return _findOrGetMore(client, response); + }) + .catch((err) => { + return Promise.reject("Failed to find room: " + err); + }); + } else { + return Promise.reject("No more data"); + } + }; + + var matrixClient; + return clientPromise + .then((client) => { + matrixClient = client; + return matrixClient.publicRooms({ server: server, limit: 1000 }); + }) + .then((response) => { + return findOrGetMore(matrixClient, response); + }) + .catch((err) => { + return Promise.reject("Failed to find room: " + err); + }); + }, + + updateNotificationCount() { + var count = 0; + this.rooms.forEach((room) => { + count += room.getUnreadNotificationCount("total") || 0; + }); + this.notificationCount = count; + }, + }, + }); + + sdk.setCryptoStoreFactory( + matrixService.createCryptoStore.bind(matrixService) + ); + + Vue.prototype.$matrix = matrixService; + }, +}; diff --git a/vue.config.js b/vue.config.js index 043039e..bab83bd 100644 --- a/vue.config.js +++ b/vue.config.js @@ -1,33 +1,35 @@ -const CopyWebpackPlugin = require('copy-webpack-plugin') +const CopyWebpackPlugin = require("copy-webpack-plugin"); module.exports = { - "transpileDependencies": [ - "vuetify" - ], + transpileDependencies: ["vuetify"], - publicPath: process.env.NODE_ENV === 'production' - ? './' - : './', + publicPath: process.env.NODE_ENV === "production" ? "./" : "./", - chainWebpack: config => { - config.plugin('html').tap(args => { + chainWebpack: (config) => { + config.plugin("html").tap((args) => { var c = require("./src/assets/config.json"); args[0].title = c.appName; return args; - }) + }); }, configureWebpack: { - devtool: 'source-map', + devtool: "source-map", plugins: [ - new CopyWebpackPlugin([{ - from: "./src/assets/config.json", - to: "./", - }]) - ] + new CopyWebpackPlugin([ + { + from: "./src/assets/config.json", + to: "./", + }, + { + from: "./node_modules/@matrix-org/olm/olm.wasm", + to: "./js/olm.wasm", + }, + ]), + ], }, devServer: { - //https: true + //https: true, }, -} \ No newline at end of file +};