diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 4584d7ed65..ffd492d491 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -146,7 +146,6 @@ src/Roles.js src/RoomListSorter.js src/RoomNotifs.js src/Rooms.js -src/RtsClient.js src/ScalarAuthClient.js src/ScalarMessaging.js src/SdkConfig.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 16eeca8fa7..ed6fb3ba36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +Changes in [0.9.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.4) (2017-06-14) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.3...v0.9.4) + + * Ask for email address after setting password for the first time + [\#1090](https://github.com/matrix-org/matrix-react-sdk/pull/1090) + * DM guessing: prefer oldest joined member + [\#1087](https://github.com/matrix-org/matrix-react-sdk/pull/1087) + * More translations + Changes in [0.9.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.3) (2017-06-12) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.3-rc.2...v0.9.3) diff --git a/package.json b/package.json index 12a17900be..151b6d6170 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.9.3", + "version": "0.9.4", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 54014a0166..39a159869c 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -19,6 +19,7 @@ import q from 'q'; import Matrix from 'matrix-js-sdk'; import MatrixClientPeg from './MatrixClientPeg'; +import createMatrixClient from './utils/createMatrixClient'; import Analytics from './Analytics'; import Notifier from './Notifier'; import UserActivity from './UserActivity'; @@ -34,9 +35,6 @@ import { _t } from './languageHandler'; * Called at startup, to attempt to build a logged-in Matrix session. It tries * a number of things: * - * 0. if it looks like we are in the middle of a registration process, it does - * nothing. - * * 1. if we have a loginToken in the (real) query params, it uses that to log * in. * @@ -48,7 +46,7 @@ import { _t } from './languageHandler'; * * 4. it attempts to auto-register as a guest user. * - * If any of steps 1-4 are successful, it will call {setLoggedIn}, which in + * If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in * turn will raise on_logged_in and will_start_client events. * * @param {object} opts @@ -79,14 +77,6 @@ export function loadSession(opts) { const guestIsUrl = opts.guestIsUrl; const defaultDeviceDisplayName = opts.defaultDeviceDisplayName; - if (fragmentQueryParams.client_secret && fragmentQueryParams.sid) { - // this happens during email validation: the email contains a link to the - // IS, which in turn redirects back to vector. We let MatrixChat create a - // Registration component which completes the next stage of registration. - console.log("Not registering as guest: registration already in progress."); - return q(); - } - if (!guestHsUrl) { console.warn("Cannot enable guest access: can't determine HS URL to use"); enableGuest = false; @@ -105,14 +95,13 @@ export function loadSession(opts) { fragmentQueryParams.guest_access_token ) { console.log("Using guest access credentials"); - setLoggedIn({ + return _doSetLoggedIn({ userId: fragmentQueryParams.guest_user_id, accessToken: fragmentQueryParams.guest_access_token, homeserverUrl: guestHsUrl, identityServerUrl: guestIsUrl, guest: true, - }); - return q(); + }, true); } return _restoreFromLocalStorage().then((success) => { @@ -141,14 +130,14 @@ function _loginWithToken(queryParams, defaultDeviceDisplayName) { }, ).then(function(data) { console.log("Logged in with token"); - setLoggedIn({ + return _doSetLoggedIn({ userId: data.user_id, deviceId: data.device_id, accessToken: data.access_token, homeserverUrl: queryParams.homeserver, identityServerUrl: queryParams.identityServer, guest: false, - }); + }, true); }, (err) => { console.error("Failed to log in with login token: " + err + " " + err.data); @@ -172,14 +161,14 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) { }, }).then((creds) => { console.log("Registered as guest: %s", creds.user_id); - setLoggedIn({ + return _doSetLoggedIn({ userId: creds.user_id, deviceId: creds.device_id, accessToken: creds.access_token, homeserverUrl: hsUrl, identityServerUrl: isUrl, guest: true, - }); + }, true); }, (err) => { console.error("Failed to register as guest: " + err + " " + err.data); }); @@ -216,15 +205,14 @@ function _restoreFromLocalStorage() { if (accessToken && userId && hsUrl) { console.log("Restoring session for %s", userId); try { - setLoggedIn({ + return _doSetLoggedIn({ userId: userId, deviceId: deviceId, accessToken: accessToken, homeserverUrl: hsUrl, identityServerUrl: isUrl, guest: isGuest, - }); - return q(true); + }, false).then(() => true); } catch (e) { return _handleRestoreFailure(e); } @@ -245,7 +233,7 @@ function _handleRestoreFailure(e) { + ' This is a once off; sorry for the inconvenience.', ); - _clearLocalStorage(); + _clearStorage(); return q.reject(new Error( _t('Unable to restore previous session') + ': ' + msg, @@ -266,7 +254,7 @@ function _handleRestoreFailure(e) { return def.promise.then((success) => { if (success) { // user clicked continue. - _clearLocalStorage(); + _clearStorage(); return false; } @@ -277,17 +265,40 @@ function _handleRestoreFailure(e) { let rtsClient = null; export function initRtsClient(url) { - rtsClient = new RtsClient(url); + if (url) { + rtsClient = new RtsClient(url); + } else { + rtsClient = null; + } } /** - * Transitions to a logged-in state using the given credentials + * Transitions to a logged-in state using the given credentials. + * + * Starts the matrix client and all other react-sdk services that + * listen for events while a session is logged in. + * + * Also stops the old MatrixClient and clears old credentials/etc out of + * storage before starting the new client. + * * @param {MatrixClientCreds} credentials The credentials to use */ export function setLoggedIn(credentials) { - credentials.guest = Boolean(credentials.guest); + stopMatrixClient(); + _doSetLoggedIn(credentials, true); +} - Analytics.setGuest(credentials.guest); +/** + * fires on_logging_in, optionally clears localstorage, persists new credentials + * to localstorage, starts the new client. + * + * @param {MatrixClientCreds} credentials + * @param {Boolean} clearStorage + * + * returns a Promise which resolves once the client has been started + */ +async function _doSetLoggedIn(credentials, clearStorage) { + credentials.guest = Boolean(credentials.guest); console.log( "setLoggedIn: mxid:", credentials.userId, @@ -295,12 +306,19 @@ export function setLoggedIn(credentials) { "guest:", credentials.guest, "hs:", credentials.homeserverUrl, ); + // This is dispatched to indicate that the user is still in the process of logging in // because `teamPromise` may take some time to resolve, breaking the assumption that // `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms // later than MatrixChat might assume. dis.dispatch({action: 'on_logging_in'}); + if (clearStorage) { + await _clearStorage(); + } + + Analytics.setGuest(credentials.guest); + // Resolves by default let teamPromise = Promise.resolve(null); @@ -349,9 +367,6 @@ export function setLoggedIn(credentials) { console.warn("No local storage available: can't persist session!"); } - // stop any running clients before we create a new one with these new credentials - stopMatrixClient(); - MatrixClientPeg.replaceUsingCreds(credentials); teamPromise.then((teamToken) => { @@ -400,7 +415,7 @@ export function logout() { * Starts the matrix client and all other react-sdk services that * listen for events while a session is logged in. */ -export function startMatrixClient() { +function startMatrixClient() { // dispatch this before starting the matrix client: it's used // to add listeners for the 'sync' event so otherwise we'd have // a race condition (and we need to dispatch synchronously for this @@ -416,34 +431,44 @@ export function startMatrixClient() { } /* - * Stops a running client and all related services, used after - * a session has been logged out / ended. + * Stops a running client and all related services, and clears persistent + * storage. Used after a session has been logged out. */ export function onLoggedOut() { - _clearLocalStorage(); stopMatrixClient(); + _clearStorage().done(); dis.dispatch({action: 'on_logged_out'}); } -function _clearLocalStorage() { +/** + * @returns {Promise} promise which resolves once the stores have been cleared + */ +function _clearStorage() { Analytics.logout(); - if (!window.localStorage) { - return; - } - const hsUrl = window.localStorage.getItem("mx_hs_url"); - const isUrl = window.localStorage.getItem("mx_is_url"); - window.localStorage.clear(); - // preserve our HS & IS URLs for convenience - // N.B. we cache them in hsUrl/isUrl and can't really inline them - // as getCurrentHsUrl() may call through to localStorage. - // NB. We do clear the device ID (as well as all the settings) - if (hsUrl) window.localStorage.setItem("mx_hs_url", hsUrl); - if (isUrl) window.localStorage.setItem("mx_is_url", isUrl); + if (window.localStorage) { + const hsUrl = window.localStorage.getItem("mx_hs_url"); + const isUrl = window.localStorage.getItem("mx_is_url"); + window.localStorage.clear(); + + // preserve our HS & IS URLs for convenience + // N.B. we cache them in hsUrl/isUrl and can't really inline them + // as getCurrentHsUrl() may call through to localStorage. + // NB. We do clear the device ID (as well as all the settings) + if (hsUrl) window.localStorage.setItem("mx_hs_url", hsUrl); + if (isUrl) window.localStorage.setItem("mx_is_url", isUrl); + } + + // create a temporary client to clear out the persistent stores. + const cli = createMatrixClient({ + // we'll never make any requests, so can pass a bogus HS URL + baseUrl: "", + }); + return cli.clearStores(); } /** - * Stop all the background processes related to the current client + * Stop all the background processes related to the current client. */ export function stopMatrixClient() { Notifier.stop(); @@ -454,7 +479,6 @@ export function stopMatrixClient() { if (cli) { cli.stopClient(); cli.removeAllListeners(); - cli.store.deleteAllData(); MatrixClientPeg.unset(); } } diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 94e55a8d8a..47370e2142 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -16,12 +16,10 @@ limitations under the License. 'use strict'; -import Matrix from 'matrix-js-sdk'; import utils from 'matrix-js-sdk/lib/utils'; import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline'; import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set'; - -const localStorage = window.localStorage; +import createMatrixClient from './utils/createMatrixClient'; interface MatrixClientCreds { homeserverUrl: string, @@ -129,22 +127,7 @@ class MatrixClientPeg { timelineSupport: true, }; - if (localStorage) { - opts.sessionStore = new Matrix.WebStorageSessionStore(localStorage); - } - if (window.indexedDB && localStorage) { - // FIXME: bodge to remove old database. Remove this after a few weeks. - window.indexedDB.deleteDatabase("matrix-js-sdk:default"); - - opts.store = new Matrix.IndexedDBStore({ - indexedDB: window.indexedDB, - dbName: "riot-web-sync", - localStorage: localStorage, - workerScript: this.indexedDbWorkerScript, - }); - } - - this.matrixClient = Matrix.createClient(opts); + this.matrixClient = createMatrixClient(opts); // we're going to add eventlisteners for each matrix event tile, so the // potential number of event listeners is quite high. diff --git a/src/Rooms.js b/src/Rooms.js index 16b5ab9ee2..3ac7c68533 100644 --- a/src/Rooms.js +++ b/src/Rooms.js @@ -144,7 +144,18 @@ export function guessDMRoomTarget(room, me) { let oldestTs; let oldestUser; - // Pick the user who's been here longest (and isn't us) + // Pick the joined user who's been here longest (and isn't us), + for (const user of room.getJoinedMembers()) { + if (user.userId == me.userId) continue; + + if (oldestTs === undefined || user.events.member.getTs() < oldestTs) { + oldestUser = user; + oldestTs = user.events.member.getTs(); + } + } + if (oldestUser) return oldestUser; + + // if there are no joined members other than us, use the oldest member for (const user of room.currentState.getMembers()) { if (user.userId == me.userId) continue; diff --git a/src/RtsClient.js b/src/RtsClient.js index 8c3ce54b37..493b19599c 100644 --- a/src/RtsClient.js +++ b/src/RtsClient.js @@ -1,5 +1,7 @@ import 'whatwg-fetch'; +let fetchFunction = fetch; + function checkStatus(response) { if (!response.ok) { return response.text().then((text) => { @@ -31,7 +33,7 @@ const request = (url, opts) => { opts.body = JSON.stringify(opts.body); opts.headers['Content-Type'] = 'application/json'; } - return fetch(url, opts) + return fetchFunction(url, opts) .then(checkStatus) .then(parseJson); }; @@ -64,7 +66,7 @@ export default class RtsClient { client_secret: clientSecret, }, method: 'POST', - } + }, ); } @@ -74,7 +76,7 @@ export default class RtsClient { qs: { team_token: teamToken, }, - } + }, ); } @@ -91,7 +93,12 @@ export default class RtsClient { qs: { user_id: userId, }, - } + }, ); } + + // allow fetch to be replaced, for testing. + static setFetch(fn) { + fetchFunction = fn; + } } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index a2a2c30889..efca22cc85 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -263,10 +263,22 @@ module.exports = React.createClass({ window.addEventListener('resize', this.handleResize); this.handleResize(); - if (this.props.config.teamServerConfig && - this.props.config.teamServerConfig.teamServerURL - ) { - Lifecycle.initRtsClient(this.props.config.teamServerConfig.teamServerURL); + const teamServerConfig = this.props.config.teamServerConfig || {}; + Lifecycle.initRtsClient(teamServerConfig.teamServerURL); + + // if the user has followed a login or register link, don't reanimate + // the old creds, but rather go straight to the relevant page + + const firstScreen = this.state.screenAfterLogin ? + this.state.screenAfterLogin.screen : null; + + if (firstScreen === 'login' || + firstScreen === 'register' || + firstScreen === 'forgot_password') { + this.props.onLoadCompleted(); + this.setState({loading: false}); + this._showScreenAfterLogin(); + return; } // the extra q() ensures that synchronous exceptions hit the same codepath as @@ -840,14 +852,6 @@ module.exports = React.createClass({ _onLoadCompleted: function() { this.props.onLoadCompleted(); this.setState({loading: false}); - - // Show screens (like 'register') that need to be shown without _onLoggedIn - // being called. 'register' needs to be routed here when the email confirmation - // link is clicked on. - if (this.state.screenAfterLogin && - ['register'].indexOf(this.state.screenAfterLogin.screen) !== -1) { - this._showScreenAfterLogin(); - } }, /** @@ -946,6 +950,7 @@ module.exports = React.createClass({ this.state.screenAfterLogin.screen, this.state.screenAfterLogin.params, ); + // XXX: is this necessary? `showScreen` should do it for us. this.notifyNewScreen(this.state.screenAfterLogin.screen); this.setState({screenAfterLogin: null}); } else if (localStorage && localStorage.getItem('mx_last_room_id')) { @@ -1229,6 +1234,8 @@ module.exports = React.createClass({ onReturnToGuestClick: function() { // reanimate our guest login if (this.state.guestCreds) { + // TODO: this is probably a bit broken - we don't want to be + // clearing storage when we reanimate the guest creds. Lifecycle.setLoggedIn(this.state.guestCreds); this.setState({guestCreds: null}); } diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 79420e776a..c1f59c8e28 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -170,6 +170,25 @@ module.exports = React.createClass({ isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(), }; + // Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307 + console.log( + 'RVS update:', + newState.roomId, + newState.roomAlias, + 'loading?', newState.roomLoading, + 'joining?', newState.joining, + ); + + // finished joining, start waiting for a room and show a spinner. See onRoom. + newState.waitingForRoom = this.state.joining && !newState.joining && + !RoomViewStore.getJoinError(); + + // NB: This does assume that the roomID will not change for the lifetime of + // the RoomView instance + if (initial) { + newState.room = MatrixClientPeg.get().getRoom(newState.roomId); + } + // Clear the search results when clicking a search result (which changes the // currently scrolled to event, this.state.initialEventId). if (this.state.initialEventId !== newState.initialEventId) { @@ -185,8 +204,9 @@ module.exports = React.createClass({ this.setState(newState, () => { // At this point, this.state.roomId could be null (e.g. the alias might not // have been resolved yet) so anything called here must handle this case. - this._onHaveRoom(); - this.onRoom(MatrixClientPeg.get().getRoom(this.state.roomId)); + if (initial) { + this._onHaveRoom(); + } }); }, @@ -202,25 +222,20 @@ module.exports = React.createClass({ // which must be by alias or invite wherever possible (peeking currently does // not work over federation). - // NB. We peek if we are not in the room, although if we try to peek into - // a room in which we have a member event (ie. we've left) synapse will just - // send us the same data as we get in the sync (ie. the last events we saw). - const room = MatrixClientPeg.get().getRoom(this.state.roomId); - let isUserJoined = null; + // NB. We peek if we have never seen the room before (i.e. js-sdk does not know + // about it). We don't peek in the historical case where we were joined but are + // now not joined because the js-sdk peeking API will clobber our historical room, + // making it impossible to indicate a newly joined room. + const room = this.state.room; if (room) { - isUserJoined = room.hasMembershipState( - MatrixClientPeg.get().credentials.userId, 'join', - ); - this._updateAutoComplete(room); this.tabComplete.loadEntries(room); } - if (!isUserJoined && !this.state.joining && this.state.roomId) { + if (!this.state.joining && this.state.roomId) { if (this.props.autoJoin) { this.onJoinButtonClicked(); - } else if (this.state.roomId) { + } else if (!room) { console.log("Attempting to peek into room %s", this.state.roomId); - this.setState({ peekLoading: true, }); @@ -244,7 +259,8 @@ module.exports = React.createClass({ } }).done(); } - } else if (isUserJoined) { + } else if (room) { + // Stop peeking because we have joined this room previously MatrixClientPeg.get().stopPeeking(); this.setState({ unsentMessageError: this._getUnsentMessageError(room), @@ -607,6 +623,7 @@ module.exports = React.createClass({ } this.setState({ room: room, + waitingForRoom: false, }, () => { this._onRoomLoaded(room); }); @@ -662,7 +679,14 @@ module.exports = React.createClass({ onRoomMemberMembership: function(ev, member, oldMembership) { if (member.userId == MatrixClientPeg.get().credentials.userId) { - this.forceUpdate(); + + if (member.membership === 'join') { + this.setState({ + waitingForRoom: false, + }); + } else { + this.forceUpdate(); + } } }, @@ -1416,6 +1440,10 @@ module.exports = React.createClass({ const Loader = sdk.getComponent("elements.Spinner"); const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); + // Whether the preview bar spinner should be shown. We do this when joining or + // when waiting for a room to be returned by js-sdk when joining + const previewBarSpinner = this.state.joining || this.state.waitingForRoom; + if (!this.state.room) { if (this.state.roomLoading || this.state.peekLoading) { return ( @@ -1449,7 +1477,7 @@ module.exports = React.createClass({ onRejectClick={ this.onRejectThreepidInviteButtonClicked } canPreview={ false } error={ this.state.roomLoadError } roomAlias={room_alias} - spinner={this.state.joining} + spinner={previewBarSpinner} inviterName={inviterName} invitedEmail={invitedEmail} room={this.state.room} @@ -1492,7 +1520,7 @@ module.exports = React.createClass({ onRejectClick={ this.onRejectButtonClicked } inviterName={ inviterName } canPreview={ false } - spinner={this.state.joining} + spinner={previewBarSpinner} room={this.state.room} /> @@ -1568,7 +1596,7 @@ module.exports = React.createClass({ excessHeight) { + break; + } // The tile may not have a scroll token, so guard it if (tile.dataset.scrollTokens) { markerScrollToken = tile.dataset.scrollTokens.split(',')[0]; } - if (tile.clientHeight > excessHeight) { - break; - } } if (markerScrollToken) { diff --git a/src/components/views/dialogs/SetEmailDialog.js b/src/components/views/dialogs/SetEmailDialog.js index 6d0e8468ca..82256970de 100644 --- a/src/components/views/dialogs/SetEmailDialog.js +++ b/src/components/views/dialogs/SetEmailDialog.js @@ -14,15 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import q from 'q'; import React from 'react'; import sdk from '../../../index'; -import MatrixClientPeg from '../../../MatrixClientPeg'; -import classnames from 'classnames'; -import KeyCode from '../../../KeyCode'; import Email from '../../../email'; import AddThreepid from '../../../AddThreepid'; -import { _t, _tJsx } from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; @@ -89,6 +85,10 @@ export default React.createClass({ this.setState({emailBusy: true}); }, + onCancelled: function() { + this.props.onFinished(false); + }, + onEmailDialogFinished: function(ok) { if (ok) { this.verifyEmailAddress(); @@ -137,7 +137,7 @@ export default React.createClass({ return (
@@ -155,7 +155,7 @@ export default React.createClass({
diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index ebd703a0bc..099ae2deae 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -223,7 +223,7 @@ module.exports = React.createClass({ const passwordLabel = this.state.cachedPassword ? _t('Password') : _t('New Password'); return ( -
+
{ currentPassword }
@@ -246,7 +246,7 @@ module.exports = React.createClass({ element="button"> { _t('Change Password') } -
+ ); case this.Phases.Uploading: var Loader = sdk.getComponent("elements.Spinner"); diff --git a/src/dispatcher.js b/src/dispatcher.js index f3ebed8357..be74dc856e 100644 --- a/src/dispatcher.js +++ b/src/dispatcher.js @@ -41,9 +41,6 @@ class MatrixDispatcher extends flux.Dispatcher { } } -// XXX this is a big anti-pattern, and makes testing hard. Because dispatches -// happen asynchronously, it is possible for actions dispatched in one thread -// to arrive in another, with *hilarious* consequences. if (global.mxDispatcher === undefined) { global.mxDispatcher = new MatrixDispatcher(); } diff --git a/src/index.js b/src/index.js index 7d0547d9c9..8c290fcb64 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,7 @@ limitations under the License. */ import Skinner from './Skinner'; +import RtsClient from './RtsClient'; module.exports.loadSkin = function(skinObject) { Skinner.load(skinObject); @@ -27,3 +28,7 @@ module.exports.resetSkin = function() { module.exports.getComponent = function(componentName) { return Skinner.getComponent(componentName); }; + +module.exports.setFetch = function(fetchFunction) { + RtsClient.setFetch(fetchFunction); +}; diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 929696896a..ac06d41e81 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -123,6 +123,7 @@ class RoomViewStore extends Store { if (payload.room_id) { const newState = { roomId: payload.room_id, + roomAlias: payload.room_alias, initialEventId: payload.event_id, initialEventPixelOffset: undefined, isInitialEventHighlighted: payload.highlighted, diff --git a/src/utils/createMatrixClient.js b/src/utils/createMatrixClient.js new file mode 100644 index 0000000000..5effd63f2a --- /dev/null +++ b/src/utils/createMatrixClient.js @@ -0,0 +1,55 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import Matrix from 'matrix-js-sdk'; + +const localStorage = window.localStorage; + +/** + * Create a new matrix client, with the persistent stores set up appropriately + * (using localstorage/indexeddb, etc) + * + * @param {Object} opts options to pass to Matrix.createClient. This will be + * extended with `sessionStore` and `store` members. + * + * @param {string} indexedDbWorkerScript Optional URL for a web worker script + * for IndexedDB store operations. If not given, indexeddb ops are done on + * the main thread. + * + * @returns {MatrixClient} the newly-created MatrixClient + */ +export default function createMatrixClient(opts, indexedDbWorkerScript) { + const storeOpts = {}; + + if (localStorage) { + storeOpts.sessionStore = new Matrix.WebStorageSessionStore(localStorage); + } + if (window.indexedDB && localStorage) { + // FIXME: bodge to remove old database. Remove this after a few weeks. + window.indexedDB.deleteDatabase("matrix-js-sdk:default"); + + storeOpts.store = new Matrix.IndexedDBStore({ + indexedDB: window.indexedDB, + dbName: "riot-web-sync", + localStorage: localStorage, + workerScript: indexedDbWorkerScript, + }); + } + + opts = Object.assign(storeOpts, opts); + + return Matrix.createClient(opts); +}