2016-08-02 13:04:20 +00:00
/ *
Copyright 2015 , 2016 OpenMarket Ltd
2017-02-15 18:44:15 +00:00
Copyright 2017 Vector Creations Ltd
2016-08-02 13:04:20 +00:00
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 .
* /
2016-08-10 10:33:58 +00:00
import q from 'q' ;
2016-08-11 10:00:15 +00:00
import Matrix from 'matrix-js-sdk' ;
2016-08-10 10:33:58 +00:00
2016-08-02 13:04:20 +00:00
import MatrixClientPeg from './MatrixClientPeg' ;
2017-05-27 19:47:09 +00:00
import Analytics from './Analytics' ;
2017-01-20 14:22:27 +00:00
import Notifier from './Notifier' ;
2016-08-02 13:04:20 +00:00
import UserActivity from './UserActivity' ;
import Presence from './Presence' ;
import dis from './dispatcher' ;
2016-09-26 17:02:14 +00:00
import DMRoomMap from './utils/DMRoomMap' ;
2017-02-07 15:23:23 +00:00
import RtsClient from './RtsClient' ;
2017-02-15 18:44:15 +00:00
import Modal from './Modal' ;
import sdk from './index' ;
2017-05-25 10:39:08 +00:00
import { _t } from './languageHandler' ;
2016-08-02 13:04:20 +00:00
2016-08-10 10:33:58 +00:00
/ * *
* 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 .
*
2016-08-11 10:00:15 +00:00
* 1. if we have a loginToken in the ( real ) query params , it uses that to log
* in .
2016-08-10 10:33:58 +00:00
*
2016-08-11 10:00:15 +00:00
* 2. if we have a guest access token in the fragment query params , it uses
* that .
*
* 3. if an access token is stored in local storage ( from a previous session ) ,
2016-08-10 10:33:58 +00:00
* it uses that .
*
2016-08-11 10:00:15 +00:00
* 4. it attempts to auto - register as a guest user .
2016-08-10 10:33:58 +00:00
*
2016-08-11 10:00:15 +00:00
* If any of steps 1 - 4 are successful , it will call { setLoggedIn } , which in
2016-08-10 10:33:58 +00:00
* turn will raise on _logged _in and will _start _client events .
*
2017-05-04 17:03:35 +00:00
* @ param { object } opts
2016-08-10 10:33:58 +00:00
*
2016-08-11 10:00:15 +00:00
* @ param { object } opts . realQueryParams : string - > string map of the
* query - parameters extracted from the real query - string of the starting
* URI .
*
* @ param { object } opts . fragmentQueryParams : string - > string map of the
* query - parameters extracted from the # - fragment of the starting URI .
2016-08-10 10:33:58 +00:00
*
* @ param { boolean } opts . enableGuest : set to true to enable guest access tokens
* and auto - guest registrations .
*
2016-08-11 00:30:43 +00:00
* @ params { string } opts . guestHsUrl : homeserver URL . Only used if enableGuest is
2016-08-10 10:33:58 +00:00
* true ; defines the HS to register against .
*
2016-08-11 00:30:43 +00:00
* @ params { string } opts . guestIsUrl : homeserver URL . Only used if enableGuest is
2016-08-10 10:33:58 +00:00
* true ; defines the IS to use .
*
2017-05-04 17:03:35 +00:00
* @ returns { Promise } a promise which resolves when the above process completes .
2016-08-10 10:33:58 +00:00
* /
export function loadSession ( opts ) {
2016-08-11 10:00:15 +00:00
const realQueryParams = opts . realQueryParams || { } ;
const fragmentQueryParams = opts . fragmentQueryParams || { } ;
2016-08-10 10:33:58 +00:00
let enableGuest = opts . enableGuest || false ;
2016-08-11 00:30:43 +00:00
const guestHsUrl = opts . guestHsUrl ;
const guestIsUrl = opts . guestIsUrl ;
2016-08-11 15:15:42 +00:00
const defaultDeviceDisplayName = opts . defaultDeviceDisplayName ;
2016-08-10 10:33:58 +00:00
2016-08-11 10:00:15 +00:00
if ( fragmentQueryParams . client _secret && fragmentQueryParams . sid ) {
2016-08-10 10:33:58 +00:00
// 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 ( ) ;
}
2016-08-11 00:30:43 +00:00
if ( ! guestHsUrl ) {
2016-08-10 10:33:58 +00:00
console . warn ( "Cannot enable guest access: can't determine HS URL to use" ) ;
enableGuest = false ;
}
2016-08-11 10:00:15 +00:00
if ( realQueryParams . loginToken ) {
if ( ! realQueryParams . homeserver ) {
console . warn ( "Cannot log in with token: can't determine HS URL to use" ) ;
} else {
2016-08-11 15:15:42 +00:00
return _loginWithToken ( realQueryParams , defaultDeviceDisplayName ) ;
2016-08-11 10:00:15 +00:00
}
}
2016-08-10 10:33:58 +00:00
if ( enableGuest &&
2016-08-11 10:00:15 +00:00
fragmentQueryParams . guest _user _id &&
fragmentQueryParams . guest _access _token
2016-08-10 10:33:58 +00:00
) {
console . log ( "Using guest access credentials" ) ;
setLoggedIn ( {
2016-08-11 10:00:15 +00:00
userId : fragmentQueryParams . guest _user _id ,
accessToken : fragmentQueryParams . guest _access _token ,
2016-08-11 00:30:43 +00:00
homeserverUrl : guestHsUrl ,
identityServerUrl : guestIsUrl ,
2016-08-10 10:33:58 +00:00
guest : true ,
} ) ;
return q ( ) ;
}
2017-02-15 18:44:15 +00:00
return _restoreFromLocalStorage ( ) . then ( ( success ) => {
if ( success ) {
return ;
}
2016-08-10 10:33:58 +00:00
2017-02-15 18:44:15 +00:00
if ( enableGuest ) {
return _registerAsGuest ( guestHsUrl , guestIsUrl , defaultDeviceDisplayName ) ;
}
2016-08-10 10:33:58 +00:00
2017-02-15 18:44:15 +00:00
// fall back to login screen
} ) ;
2016-08-10 10:33:58 +00:00
}
2016-08-11 15:15:42 +00:00
function _loginWithToken ( queryParams , defaultDeviceDisplayName ) {
2016-08-11 10:00:15 +00:00
// create a temporary MatrixClient to do the login
2017-05-04 17:03:35 +00:00
const client = Matrix . createClient ( {
2016-08-11 10:00:15 +00:00
baseUrl : queryParams . homeserver ,
} ) ;
2016-08-11 15:15:42 +00:00
return client . login (
"m.login.token" , {
token : queryParams . loginToken ,
initial _device _display _name : defaultDeviceDisplayName ,
} ,
) . then ( function ( data ) {
2016-08-11 10:00:15 +00:00
console . log ( "Logged in with token" ) ;
setLoggedIn ( {
userId : data . user _id ,
2016-08-11 13:21:52 +00:00
deviceId : data . device _id ,
2016-08-11 10:00:15 +00:00
accessToken : data . access _token ,
homeserverUrl : queryParams . homeserver ,
identityServerUrl : queryParams . identityServer ,
guest : false ,
2017-01-20 14:22:27 +00:00
} ) ;
2016-08-11 10:00:15 +00:00
} , ( err ) => {
console . error ( "Failed to log in with login token: " + err + " " +
err . data ) ;
} ) ;
}
2016-08-11 15:15:42 +00:00
function _registerAsGuest ( hsUrl , isUrl , defaultDeviceDisplayName ) {
2016-08-10 10:33:58 +00:00
console . log ( "Doing guest login on %s" , hsUrl ) ;
2017-02-24 11:41:23 +00:00
// TODO: we should probably de-duplicate this and Login.loginAsGuest.
2016-08-11 15:15:42 +00:00
// Not really sure where the right home for it is.
2016-08-11 12:50:38 +00:00
// create a temporary MatrixClient to do the login
2017-05-04 17:03:35 +00:00
const client = Matrix . createClient ( {
2016-08-11 12:50:38 +00:00
baseUrl : hsUrl ,
} ) ;
2016-08-11 15:15:42 +00:00
return client . registerGuest ( {
body : {
initial _device _display _name : defaultDeviceDisplayName ,
} ,
} ) . then ( ( creds ) => {
2016-08-10 10:33:58 +00:00
console . log ( "Registered as guest: %s" , creds . user _id ) ;
setLoggedIn ( {
userId : creds . user _id ,
2016-08-11 13:21:52 +00:00
deviceId : creds . device _id ,
2016-08-10 10:33:58 +00:00
accessToken : creds . access _token ,
homeserverUrl : hsUrl ,
identityServerUrl : isUrl ,
guest : true ,
} ) ;
} , ( err ) => {
console . error ( "Failed to register as guest: " + err + " " + err . data ) ;
} ) ;
}
2017-02-15 18:44:15 +00:00
// returns a promise which resolves to true if a session is found in
// localstorage
2016-08-10 09:33:27 +00:00
function _restoreFromLocalStorage ( ) {
if ( ! localStorage ) {
2017-02-15 18:44:15 +00:00
return q ( false ) ;
2016-08-10 09:33:27 +00:00
}
2017-05-04 17:03:35 +00:00
const hsUrl = localStorage . getItem ( "mx_hs_url" ) ;
const isUrl = localStorage . getItem ( "mx_is_url" ) || 'https://matrix.org' ;
const accessToken = localStorage . getItem ( "mx_access_token" ) ;
const userId = localStorage . getItem ( "mx_user_id" ) ;
const deviceId = localStorage . getItem ( "mx_device_id" ) ;
2016-08-10 09:33:27 +00:00
2017-05-04 17:03:35 +00:00
let isGuest ;
2016-08-10 09:33:27 +00:00
if ( localStorage . getItem ( "mx_is_guest" ) !== null ) {
2017-05-04 17:03:35 +00:00
isGuest = localStorage . getItem ( "mx_is_guest" ) === "true" ;
2016-08-10 09:33:27 +00:00
} else {
// legacy key name
2017-05-04 17:03:35 +00:00
isGuest = localStorage . getItem ( "matrix-is-guest" ) === "true" ;
2016-08-10 09:33:27 +00:00
}
2017-05-04 17:03:35 +00:00
if ( accessToken && userId && hsUrl ) {
console . log ( "Restoring session for %s" , userId ) ;
2016-09-02 10:25:10 +00:00
try {
setLoggedIn ( {
2017-05-04 17:03:35 +00:00
userId : userId ,
deviceId : deviceId ,
accessToken : accessToken ,
homeserverUrl : hsUrl ,
identityServerUrl : isUrl ,
guest : isGuest ,
2016-09-02 10:25:10 +00:00
} ) ;
2017-02-15 18:44:15 +00:00
return q ( true ) ;
2016-09-02 10:25:10 +00:00
} catch ( e ) {
2017-02-15 18:44:15 +00:00
return _handleRestoreFailure ( e ) ;
2016-09-02 10:25:10 +00:00
}
2016-08-10 09:33:27 +00:00
} else {
console . log ( "No previous session found." ) ;
2017-02-15 18:44:15 +00:00
return q ( false ) ;
2016-08-10 09:33:27 +00:00
}
}
2016-08-10 10:33:58 +00:00
2017-02-15 18:44:15 +00:00
function _handleRestoreFailure ( e ) {
console . log ( "Unable to restore session" , e ) ;
let msg = e . message ;
if ( msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE" ) {
2017-05-23 14:16:31 +00:00
msg = _t (
2017-05-27 16:30:32 +00:00
'You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.'
) ;
2017-02-15 18:44:15 +00:00
_clearLocalStorage ( ) ;
return q . reject ( new Error (
2017-05-23 14:16:31 +00:00
_t ( 'Unable to restore previous session' ) + ': ' + msg ,
2017-02-15 18:44:15 +00:00
) ) ;
}
const def = q . defer ( ) ;
const SessionRestoreErrorDialog =
sdk . getComponent ( 'views.dialogs.SessionRestoreErrorDialog' ) ;
Modal . createDialog ( SessionRestoreErrorDialog , {
error : msg ,
onFinished : ( success ) => {
def . resolve ( success ) ;
} ,
} ) ;
return def . promise . then ( ( success ) => {
if ( success ) {
// user clicked continue.
_clearLocalStorage ( ) ;
return false ;
}
// try, try again
return _restoreFromLocalStorage ( ) ;
} ) ;
}
2017-02-03 14:34:24 +00:00
let rtsClient = null ;
export function initRtsClient ( url ) {
rtsClient = new RtsClient ( url ) ;
}
2016-08-10 10:33:58 +00:00
2016-08-02 17:52:56 +00:00
/ * *
* Transitions to a logged - in state using the given credentials
2016-08-03 15:31:42 +00:00
* @ param { MatrixClientCreds } credentials The credentials to use
2016-08-02 17:52:56 +00:00
* /
2016-08-10 10:33:58 +00:00
export function setLoggedIn ( credentials ) {
2016-08-02 13:04:20 +00:00
credentials . guest = Boolean ( credentials . guest ) ;
2017-05-04 17:04:47 +00:00
2017-05-29 18:04:37 +00:00
Analytics . setGuest ( credentials . guest ) ;
2017-05-04 17:04:47 +00:00
console . log (
"setLoggedIn: mxid:" , credentials . userId ,
"deviceId:" , credentials . deviceId ,
"guest:" , credentials . guest ,
"hs:" , credentials . homeserverUrl ,
) ;
2017-03-27 15:39:04 +00:00
// 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' } ) ;
2016-08-10 17:04:22 +00:00
2017-02-28 15:05:49 +00:00
// Resolves by default
2017-02-28 15:40:49 +00:00
let teamPromise = Promise . resolve ( null ) ;
2017-02-28 15:05:49 +00:00
2016-08-10 17:04:22 +00:00
// persist the session
if ( localStorage ) {
try {
2016-08-10 23:58:48 +00:00
localStorage . setItem ( "mx_hs_url" , credentials . homeserverUrl ) ;
2016-08-10 22:52:09 +00:00
localStorage . setItem ( "mx_is_url" , credentials . identityServerUrl ) ;
localStorage . setItem ( "mx_user_id" , credentials . userId ) ;
localStorage . setItem ( "mx_access_token" , credentials . accessToken ) ;
localStorage . setItem ( "mx_is_guest" , JSON . stringify ( credentials . guest ) ) ;
2016-08-12 10:20:09 +00:00
// if we didn't get a deviceId from the login, leave mx_device_id unset,
// rather than setting it to "undefined".
//
// (in this case MatrixClient doesn't bother with the crypto stuff
// - that's fine for us).
if ( credentials . deviceId ) {
localStorage . setItem ( "mx_device_id" , credentials . deviceId ) ;
}
2016-08-10 22:52:09 +00:00
console . log ( "Session persisted for %s" , credentials . userId ) ;
2016-08-10 17:04:22 +00:00
} catch ( e ) {
console . warn ( "Error using local storage: can't persist session!" , e ) ;
}
2017-02-03 14:34:24 +00:00
2017-02-28 15:05:49 +00:00
if ( rtsClient && ! credentials . guest ) {
teamPromise = rtsClient . login ( credentials . userId ) . then ( ( body ) => {
2017-02-08 16:49:33 +00:00
if ( body . team _token ) {
localStorage . setItem ( "mx_team_token" , body . team _token ) ;
}
2017-02-28 15:05:49 +00:00
return body . team _token ;
2017-02-03 14:34:24 +00:00
} ) ;
}
2016-08-10 17:04:22 +00:00
} else {
console . warn ( "No local storage available: can't persist session!" ) ;
}
2017-02-24 11:41:23 +00:00
// stop any running clients before we create a new one with these new credentials
stopMatrixClient ( ) ;
2016-08-03 09:46:42 +00:00
MatrixClientPeg . replaceUsingCreds ( credentials ) ;
2016-08-02 13:04:20 +00:00
2017-02-28 15:05:49 +00:00
teamPromise . then ( ( teamToken ) => {
dis . dispatch ( { action : 'on_logged_in' , teamToken : teamToken } ) ;
} , ( err ) => {
console . warn ( "Failed to get team token on login" , err ) ;
dis . dispatch ( { action : 'on_logged_in' , teamToken : null } ) ;
} ) ;
2016-08-02 13:04:20 +00:00
2016-08-02 17:52:56 +00:00
startMatrixClient ( ) ;
2016-08-02 13:04:20 +00:00
}
2016-08-02 17:55:13 +00:00
/ * *
* Logs the current session out and transitions to the logged - out state
* /
2016-08-10 10:33:58 +00:00
export function logout ( ) {
2016-08-02 13:04:20 +00:00
if ( MatrixClientPeg . get ( ) . isGuest ( ) ) {
// logout doesn't work for guest sessions
// Also we sometimes want to re-log in a guest session
// if we abort the login
2016-09-17 01:19:27 +00:00
// use settimeout to avoid racing with react unmounting components
// which need a valid matrixclientpeg
setTimeout ( ( ) => {
onLoggedOut ( ) ;
} , 0 ) ;
2016-08-02 13:04:20 +00:00
return ;
}
2017-05-04 17:03:35 +00:00
MatrixClientPeg . get ( ) . logout ( ) . then ( onLoggedOut ,
2016-08-03 09:01:23 +00:00
( err ) => {
// Just throwing an error here is going to be very unhelpful
// if you're trying to log out because your server's down and
// you want to log into a different server, so just forget the
// access token. It's annoying that this will leave the access
// token still valid, but we should fix this by having access
// tokens expire (and if you really think you've been compromised,
// change your password).
console . log ( "Failed to call logout API: token will not be invalidated" ) ;
2016-08-03 10:47:29 +00:00
onLoggedOut ( ) ;
2017-05-04 17:03:35 +00:00
} ,
) . done ( ) ;
2016-08-02 13:04:20 +00:00
}
2016-08-02 17:56:12 +00:00
/ * *
* Starts the matrix client and all other react - sdk services that
* listen for events while a session is logged in .
* /
2016-08-10 10:33:58 +00:00
export function startMatrixClient ( ) {
2016-08-02 13:04:20 +00:00
// 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
// to work).
dis . dispatch ( { action : 'will_start_client' } , true ) ;
Notifier . start ( ) ;
UserActivity . start ( ) ;
Presence . start ( ) ;
2016-09-27 08:56:31 +00:00
DMRoomMap . makeShared ( ) . start ( ) ;
2016-08-02 17:58:18 +00:00
2016-08-03 15:39:47 +00:00
MatrixClientPeg . start ( ) ;
2016-08-02 13:04:20 +00:00
}
2016-08-04 09:49:34 +00:00
/ *
* Stops a running client and all related services , used after
* a session has been logged out / ended .
* /
2016-08-10 10:33:58 +00:00
export function onLoggedOut ( ) {
2016-09-02 10:25:10 +00:00
_clearLocalStorage ( ) ;
2016-08-11 12:50:38 +00:00
stopMatrixClient ( ) ;
2016-08-02 13:04:20 +00:00
dis . dispatch ( { action : 'on_logged_out' } ) ;
}
2016-09-02 10:25:10 +00:00
function _clearLocalStorage ( ) {
2017-05-27 19:47:09 +00:00
Analytics . logout ( ) ;
2016-09-02 10:25:10 +00:00
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 ) ;
}
2016-08-04 09:49:34 +00:00
/ * *
* Stop all the background processes related to the current client
* /
2016-08-11 12:50:38 +00:00
export function stopMatrixClient ( ) {
2016-08-02 13:04:20 +00:00
Notifier . stop ( ) ;
UserActivity . stop ( ) ;
Presence . stop ( ) ;
2016-12-09 10:32:56 +00:00
if ( DMRoomMap . shared ( ) ) DMRoomMap . shared ( ) . stop ( ) ;
2017-05-04 17:03:35 +00:00
const cli = MatrixClientPeg . get ( ) ;
2016-08-11 12:50:38 +00:00
if ( cli ) {
cli . stopClient ( ) ;
cli . removeAllListeners ( ) ;
2017-02-17 10:43:55 +00:00
cli . store . deleteAllData ( ) ;
2016-08-11 12:50:38 +00:00
MatrixClientPeg . unset ( ) ;
}
2016-08-02 13:04:20 +00:00
}