matrix-react-sdk
Version:
SDK for matrix.org using React
1,018 lines (969 loc) • 140 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.attemptDelegatedAuthLogin = attemptDelegatedAuthLogin;
exports.attemptTokenLogin = attemptTokenLogin;
exports.getStoredSessionOwner = getStoredSessionOwner;
exports.getStoredSessionVars = getStoredSessionVars;
exports.isLoggingOut = isLoggingOut;
exports.isSoftLogout = isSoftLogout;
exports.loadSession = loadSession;
exports.logout = logout;
exports.onLoggedOut = onLoggedOut;
exports.onSessionLockStolen = onSessionLockStolen;
exports.restoreSessionFromStorage = restoreSessionFromStorage;
exports.setLoggedIn = setLoggedIn;
exports.setSessionLockNotStolen = setSessionLockNotStolen;
exports.softLogout = softLogout;
exports.stopMatrixClient = stopMatrixClient;
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _matrix = require("matrix-js-sdk/src/matrix");
var _logger = require("matrix-js-sdk/src/logger");
var _MatrixClientPeg = require("./MatrixClientPeg");
var _ModuleRunner = require("./modules/ModuleRunner");
var _EventIndexPeg = _interopRequireDefault(require("./indexing/EventIndexPeg"));
var _createMatrixClient = _interopRequireDefault(require("./utils/createMatrixClient"));
var _Notifier = _interopRequireDefault(require("./Notifier"));
var _UserActivity = _interopRequireDefault(require("./UserActivity"));
var _Presence = _interopRequireDefault(require("./Presence"));
var _dispatcher = _interopRequireDefault(require("./dispatcher/dispatcher"));
var _DMRoomMap = _interopRequireDefault(require("./utils/DMRoomMap"));
var _Modal = _interopRequireDefault(require("./Modal"));
var _ActiveWidgetStore = _interopRequireDefault(require("./stores/ActiveWidgetStore"));
var _PlatformPeg = _interopRequireDefault(require("./PlatformPeg"));
var _Login = require("./Login");
var StorageManager = _interopRequireWildcard(require("./utils/StorageManager"));
var StorageAccess = _interopRequireWildcard(require("./utils/StorageAccess"));
var _SettingsStore = _interopRequireDefault(require("./settings/SettingsStore"));
var _SettingLevel = require("./settings/SettingLevel");
var _ToastStore = _interopRequireDefault(require("./stores/ToastStore"));
var _IntegrationManagers = require("./integrations/IntegrationManagers");
var _Mjolnir = require("./mjolnir/Mjolnir");
var _DeviceListener = _interopRequireDefault(require("./DeviceListener"));
var _Jitsi = require("./widgets/Jitsi");
var _BasePlatform = require("./BasePlatform");
var _ThreepidInviteStore = _interopRequireDefault(require("./stores/ThreepidInviteStore"));
var _PosthogAnalytics = require("./PosthogAnalytics");
var _LegacyCallHandler = _interopRequireDefault(require("./LegacyCallHandler"));
var _Lifecycle = _interopRequireDefault(require("./customisations/Lifecycle"));
var _ErrorDialog = _interopRequireDefault(require("./components/views/dialogs/ErrorDialog"));
var _languageHandler = require("./languageHandler");
var _SessionRestoreErrorDialog = _interopRequireDefault(require("./components/views/dialogs/SessionRestoreErrorDialog"));
var _StorageEvictedDialog = _interopRequireDefault(require("./components/views/dialogs/StorageEvictedDialog"));
var _sentry = require("./sentry");
var _SdkConfig = _interopRequireDefault(require("./SdkConfig"));
var _DialogOpener = require("./utils/DialogOpener");
var _actions = require("./dispatcher/actions");
var _SDKContext = require("./contexts/SDKContext");
var _ErrorUtils = require("./utils/ErrorUtils");
var _authorize = require("./utils/oidc/authorize");
var _error = require("./utils/oidc/error");
var _persistOidcSettings = require("./utils/oidc/persistOidcSettings");
var _tokens = require("./utils/tokens/tokens");
var _TokenRefresher = require("./utils/oidc/TokenRefresher");
var _SupportedBrowser = require("./SupportedBrowser");
const _excluded = ["roomId"];
/*
Copyright 2024 New Vector Ltd.
Copyright 2019, 2020 , 2023 The Matrix.org Foundation C.I.C.
Copyright 2018 New Vector Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2015, 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";
_dispatcher.default.register(payload => {
if (payload.action === _actions.Action.TriggerLogout) {
// noinspection JSIgnoredPromiseFromCall - we don't care if it fails
onLoggedOut();
} else if (payload.action === _actions.Action.OverwriteLogin) {
const typed = payload;
// Stop the current client before overwriting the login.
// If not done it might be impossible to clear the storage, as the
// rust crypto backend might be holding an open connection to the indexeddb store.
// We also use the `unsetClient` flag to false, because at this point we are
// already in the logged in flows of the `MatrixChat` component, and it will
// always expect to have a client (calls to `MatrixClientPeg.safeGet()`).
// If we unset the client and the component is updated, the render will fail and unmount everything.
// (The module dialog closes and fires a `aria_unhide_main_app` that will trigger a re-render)
stopMatrixClient(false);
doSetLoggedIn(typed.credentials, true, true).catch(e => {
// XXX we might want to fire a new event here to let the app know that the login failed ?
// The module api could use it to display a message to the user.
_logger.logger.warn("Failed to overwrite login", e);
});
}
});
/**
* This is set to true by {@link #onSessionLockStolen}.
*
* It is used in various of the async functions to prevent races where we initialise a client after the lock is stolen.
*/
let sessionLockStolen = false;
// this is exposed solely for unit tests.
// ts-prune-ignore-next
function setSessionLockNotStolen() {
sessionLockStolen = false;
}
/**
* Handle the session lock being stolen. Stops any active Matrix Client, and aborts any ongoing client initialisation.
*/
async function onSessionLockStolen() {
sessionLockStolen = true;
stopMatrixClient();
}
/**
* Check if we still hold the session lock.
*
* If not, raises a {@link SessionLockStolenError}.
*/
function checkSessionLock() {
if (sessionLockStolen) {
throw new SessionLockStolenError("session lock has been released");
}
}
/** Error type raised by various functions in the Lifecycle workflow if session lock is stolen during execution */
class SessionLockStolenError extends Error {}
/**
* Called at startup, to attempt to build a logged-in Matrix session. It tries
* a number of things:
*
* 1. if we have a guest access token in the fragment query params, it uses
* that.
* 2. if an access token is stored in local storage (from a previous session),
* it uses that.
* 3. it attempts to auto-register as a guest user.
*
* 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]
* @param {object} [opts.fragmentQueryParams]: string->string map of the
* query-parameters extracted from the #-fragment of the starting URI.
* @param {boolean} [opts.enableGuest]: set to true to enable guest access
* tokens and auto-guest registrations.
* @param {string} [opts.guestHsUrl]: homeserver URL. Only used if enableGuest
* is true; defines the HS to register against.
* @param {string} [opts.guestIsUrl]: homeserver URL. Only used if enableGuest
* is true; defines the IS to use.
* @param {bool} [opts.ignoreGuest]: If the stored session is a guest account,
* ignore it and don't load it.
* @param {string} [opts.defaultDeviceDisplayName]: Default display name to use
* when registering as a guest.
* @returns {Promise} a promise which resolves when the above process completes.
* Resolves to `true` if we ended up starting a session, or `false` if we
* failed.
*/
async function loadSession(opts = {}) {
try {
let enableGuest = opts.enableGuest || false;
const guestHsUrl = opts.guestHsUrl;
const guestIsUrl = opts.guestIsUrl;
const fragmentQueryParams = opts.fragmentQueryParams || {};
const defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
if (enableGuest && !guestHsUrl) {
_logger.logger.warn("Cannot enable guest access: can't determine HS URL to use");
enableGuest = false;
}
if (enableGuest && guestHsUrl && fragmentQueryParams.guest_user_id && fragmentQueryParams.guest_access_token) {
_logger.logger.log("Using guest access credentials");
return doSetLoggedIn({
userId: fragmentQueryParams.guest_user_id,
accessToken: fragmentQueryParams.guest_access_token,
homeserverUrl: guestHsUrl,
identityServerUrl: guestIsUrl,
guest: true
}, true, false).then(() => true);
}
const success = await restoreSessionFromStorage({
ignoreGuest: Boolean(opts.ignoreGuest)
});
if (success) {
return true;
}
if (sessionLockStolen) {
return false;
}
if (enableGuest && guestHsUrl) {
return registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
}
// fall back to welcome screen
return false;
} catch (e) {
if (e instanceof AbortLoginAndRebuildStorage) {
// If we're aborting login because of a storage inconsistency, we don't
// need to show the general failure dialog. Instead, just go back to welcome.
return false;
}
// likewise, if the session lock has been stolen while we've been trying to start
if (sessionLockStolen) {
return false;
}
return handleLoadSessionFailure(e);
}
}
/**
* Gets the user ID of the persisted session, if one exists. This does not validate
* that the user's credentials still work, just that they exist and that a user ID
* is associated with them. The session is not loaded.
* @returns {[string, boolean]} The persisted session's owner and whether the stored
* session is for a guest user, if an owner exists. If there is no stored session,
* return [null, null].
*/
async function getStoredSessionOwner() {
const {
hsUrl,
userId,
hasAccessToken,
isGuest
} = await getStoredSessionVars();
return hsUrl && userId && hasAccessToken ? [userId, !!isGuest] : [null, null];
}
/**
* If query string includes OIDC authorization code flow parameters attempt to login using oidc flow
* Else, we may be returning from SSO - attempt token login
*
* @param {Object} queryParams string->string map of the
* query-parameters extracted from the real query-string of the starting
* URI.
*
* @param {string} defaultDeviceDisplayName
* @param {string} fragmentAfterLogin path to go to after a successful login, only used for "Try again"
*
* @returns {Promise} promise which resolves to true if we completed the delegated auth login
* else false
*/
async function attemptDelegatedAuthLogin(queryParams, defaultDeviceDisplayName, fragmentAfterLogin) {
if (queryParams.code && queryParams.state) {
console.log("We have OIDC params - attempting OIDC login");
return attemptOidcNativeLogin(queryParams);
}
return attemptTokenLogin(queryParams, defaultDeviceDisplayName, fragmentAfterLogin);
}
/**
* Attempt to login by completing OIDC authorization code flow
* @param queryParams string->string map of the query-parameters extracted from the real query-string of the starting URI.
* @returns Promise that resolves to true when login succceeded, else false
*/
async function attemptOidcNativeLogin(queryParams) {
try {
const {
accessToken,
refreshToken,
homeserverUrl,
identityServerUrl,
idToken,
clientId,
issuer
} = await (0, _authorize.completeOidcLogin)(queryParams);
const {
user_id: userId,
device_id: deviceId,
is_guest: isGuest
} = await getUserIdFromAccessToken(accessToken, homeserverUrl, identityServerUrl);
const credentials = {
accessToken,
refreshToken,
homeserverUrl,
identityServerUrl,
deviceId,
userId,
isGuest
};
_logger.logger.debug("Logged in via OIDC native flow");
await onSuccessfulDelegatedAuthLogin(credentials);
// this needs to happen after success handler which clears storages
(0, _persistOidcSettings.persistOidcAuthenticatedSettings)(clientId, issuer, idToken);
return true;
} catch (error) {
_logger.logger.error("Failed to login via OIDC", error);
await onFailedDelegatedAuthLogin((0, _error.getOidcErrorMessage)(error));
return false;
}
}
/**
* Gets information about the owner of a given access token.
* @param accessToken
* @param homeserverUrl
* @param identityServerUrl
* @returns Promise that resolves with whoami response
* @throws when whoami request fails
*/
async function getUserIdFromAccessToken(accessToken, homeserverUrl, identityServerUrl) {
try {
const client = (0, _matrix.createClient)({
baseUrl: homeserverUrl,
accessToken: accessToken,
idBaseUrl: identityServerUrl
});
return await client.whoami();
} catch (error) {
_logger.logger.error("Failed to retrieve userId using accessToken", error);
throw new Error("Failed to retrieve userId using accessToken");
}
}
/**
* @param {QueryDict} queryParams string->string map of the
* query-parameters extracted from the real query-string of the starting
* URI.
*
* @param {string} defaultDeviceDisplayName
* @param {string} fragmentAfterLogin path to go to after a successful login, only used for "Try again"
*
* @returns {Promise} promise which resolves to true if we completed the token
* login, else false
*/
function attemptTokenLogin(queryParams, defaultDeviceDisplayName, fragmentAfterLogin) {
if (!queryParams.loginToken) {
return Promise.resolve(false);
}
console.log("We have token login params - attempting token login");
const homeserver = localStorage.getItem(_BasePlatform.SSO_HOMESERVER_URL_KEY);
const identityServer = localStorage.getItem(_BasePlatform.SSO_ID_SERVER_URL_KEY) ?? undefined;
if (!homeserver) {
_logger.logger.warn("Cannot log in with token: can't determine HS URL to use");
onFailedDelegatedAuthLogin((0, _languageHandler._t)("auth|sso_failed_missing_storage"));
return Promise.resolve(false);
}
return (0, _Login.sendLoginRequest)(homeserver, identityServer, "m.login.token", {
token: queryParams.loginToken,
initial_device_display_name: defaultDeviceDisplayName
}).then(async function (creds) {
_logger.logger.log("Logged in with token");
await onSuccessfulDelegatedAuthLogin(creds);
return true;
}).catch(error => {
const tryAgainCallback = () => {
const cli = (0, _matrix.createClient)({
baseUrl: homeserver,
idBaseUrl: identityServer
});
const idpId = localStorage.getItem(_BasePlatform.SSO_IDP_ID_KEY) || undefined;
_PlatformPeg.default.get()?.startSingleSignOn(cli, "sso", fragmentAfterLogin, idpId, _matrix.SSOAction.LOGIN);
};
onFailedDelegatedAuthLogin((0, _ErrorUtils.messageForLoginError)(error, {
hsUrl: homeserver,
hsName: homeserver
}), tryAgainCallback);
_logger.logger.error("Failed to log in with login token:", error);
return false;
});
}
/**
* Called after a successful token login or OIDC authorization.
* Clear storage then save new credentials in storage
* @param credentials as returned from login
*/
async function onSuccessfulDelegatedAuthLogin(credentials) {
await clearStorage();
await persistCredentials(credentials);
// remember that we just logged in
sessionStorage.setItem("mx_fresh_login", String(true));
}
/**
* Display a friendly error to the user when token login or OIDC authorization fails
* @param description error description
* @param tryAgain OPTIONAL function to call on try again button from error dialog
*/
async function onFailedDelegatedAuthLogin(description, tryAgain) {
_Modal.default.createDialog(_ErrorDialog.default, {
title: (0, _languageHandler._t)("auth|oidc|error_title"),
description,
button: (0, _languageHandler._t)("action|try_again"),
// if we have a tryAgain callback, call it the primary 'try again' button was clicked in the dialog
onFinished: tryAgain ? shouldTryAgain => shouldTryAgain && tryAgain() : undefined
});
}
function registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
_logger.logger.log(`Doing guest login on ${hsUrl}`);
// create a temporary MatrixClient to do the login
const client = (0, _matrix.createClient)({
baseUrl: hsUrl
});
return client.registerGuest({
body: {
initial_device_display_name: defaultDeviceDisplayName
}
}).then(creds => {
_logger.logger.log(`Registered as guest: ${creds.user_id}`);
return doSetLoggedIn({
userId: creds.user_id,
deviceId: creds.device_id,
accessToken: creds.access_token,
homeserverUrl: hsUrl,
identityServerUrl: isUrl,
guest: true
}, true, true).then(() => true);
}, err => {
_logger.logger.error("Failed to register as guest", err);
return false;
});
}
/**
* Retrieve a token, as stored by `persistCredentials`
* Attempts to migrate token from localStorage to idb
* @param storageKey key used to store the token, eg ACCESS_TOKEN_STORAGE_KEY
* @returns Promise that resolves to token or undefined
*/
async function getStoredToken(storageKey) {
let token;
try {
token = await StorageAccess.idbLoad("account", storageKey);
} catch (e) {
_logger.logger.error(`StorageManager.idbLoad failed for account:${storageKey}`, e);
}
if (!token) {
token = localStorage.getItem(storageKey) ?? undefined;
if (token) {
try {
// try to migrate access token to IndexedDB if we can
await StorageAccess.idbSave("account", storageKey, token);
localStorage.removeItem(storageKey);
} catch (e) {
_logger.logger.error(`migration of token ${storageKey} to IndexedDB failed`, e);
}
}
}
return token;
}
/**
* Retrieves information about the stored session from the browser's storage. The session
* may not be valid, as it is not tested for consistency here.
* @returns {Object} Information about the session - see implementation for variables.
*/
async function getStoredSessionVars() {
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY) ?? undefined;
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY) ?? undefined;
const accessToken = await getStoredToken(_tokens.ACCESS_TOKEN_STORAGE_KEY);
const refreshToken = await getStoredToken(_tokens.REFRESH_TOKEN_STORAGE_KEY);
// if we pre-date storing "mx_has_access_token", but we retrieved an access
// token, then we should say we have an access token
const hasAccessToken = localStorage.getItem(_tokens.HAS_ACCESS_TOKEN_STORAGE_KEY) === "true" || !!accessToken;
const hasRefreshToken = localStorage.getItem(_tokens.HAS_REFRESH_TOKEN_STORAGE_KEY) === "true" || !!refreshToken;
const userId = localStorage.getItem("mx_user_id") ?? undefined;
const deviceId = localStorage.getItem("mx_device_id") ?? undefined;
let isGuest;
if (localStorage.getItem("mx_is_guest") !== null) {
isGuest = localStorage.getItem("mx_is_guest") === "true";
} else {
// legacy key name
isGuest = localStorage.getItem("matrix-is-guest") === "true";
}
return {
hsUrl,
isUrl,
hasAccessToken,
accessToken,
refreshToken,
hasRefreshToken,
userId,
deviceId,
isGuest
};
}
async function abortLogin() {
const signOut = await showStorageEvictedDialog();
if (signOut) {
await clearStorage();
// This error feels a bit clunky, but we want to make sure we don't go any
// further and instead head back to sign in.
throw new AbortLoginAndRebuildStorage("Aborting login in progress because of storage inconsistency");
}
}
/** Attempt to restore the session from localStorage or indexeddb.
*
* @returns true if a session was found; false if no existing session was found.
*
* N.B. Lifecycle.js should not maintain any further localStorage state, we
* are moving towards using SessionStore to keep track of state related
* to the current session (which is typically backed by localStorage).
*
* The plan is to gradually move the localStorage access done here into
* SessionStore to avoid bugs where the view becomes out-of-sync with
* localStorage (e.g. isGuest etc.)
*/
async function restoreSessionFromStorage(opts) {
const ignoreGuest = opts?.ignoreGuest;
if (!localStorage) {
return false;
}
const {
hsUrl,
isUrl,
hasAccessToken,
accessToken,
refreshToken,
userId,
deviceId,
isGuest
} = await getStoredSessionVars();
if (hasAccessToken && !accessToken) {
await abortLogin();
}
if (accessToken && userId && hsUrl) {
if (ignoreGuest && isGuest) {
_logger.logger.log("Ignoring stored guest account: " + userId);
return false;
}
const pickleKey = (await _PlatformPeg.default.get()?.getPickleKey(userId, deviceId ?? "")) ?? undefined;
if (pickleKey) {
_logger.logger.log(`Got pickle key for ${userId}|${deviceId}`);
} else {
_logger.logger.log(`No pickle key available for ${userId}|${deviceId}`);
}
const decryptedAccessToken = await (0, _tokens.tryDecryptToken)(pickleKey, accessToken, _tokens.ACCESS_TOKEN_IV);
const decryptedRefreshToken = refreshToken && (await (0, _tokens.tryDecryptToken)(pickleKey, refreshToken, _tokens.REFRESH_TOKEN_IV));
const freshLogin = sessionStorage.getItem("mx_fresh_login") === "true";
sessionStorage.removeItem("mx_fresh_login");
_logger.logger.log(`Restoring session for ${userId}`);
await doSetLoggedIn({
userId: userId,
deviceId: deviceId,
accessToken: decryptedAccessToken,
refreshToken: decryptedRefreshToken,
homeserverUrl: hsUrl,
identityServerUrl: isUrl,
guest: isGuest,
pickleKey: pickleKey ?? undefined,
freshLogin: freshLogin
}, false, false);
return true;
} else {
_logger.logger.log("No previous session found.");
return false;
}
}
async function handleLoadSessionFailure(e) {
_logger.logger.error("Unable to load session", e);
const modal = _Modal.default.createDialog(_SessionRestoreErrorDialog.default, {
error: e
});
const [success] = await modal.finished;
if (success) {
// user clicked continue.
await clearStorage();
return false;
}
// try, try again
return loadSession();
}
/**
* 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 {IMatrixClientCreds} credentials The credentials to use
*
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/
async function setLoggedIn(credentials) {
credentials.freshLogin = true;
stopMatrixClient();
const pickleKey = credentials.userId && credentials.deviceId ? await _PlatformPeg.default.get()?.createPickleKey(credentials.userId, credentials.deviceId) : null;
if (pickleKey) {
_logger.logger.log(`Created pickle key for ${credentials.userId}|${credentials.deviceId}`);
} else {
_logger.logger.log("Pickle key not created");
}
return doSetLoggedIn(Object.assign({}, credentials, {
pickleKey
}), true, true);
}
/**
* When we have a authenticated via OIDC-native flow and have a refresh token
* try to create a token refresher.
* @param credentials from current session
* @returns Promise that resolves to a TokenRefresher, or undefined
*/
async function createOidcTokenRefresher(credentials) {
if (!credentials.refreshToken) {
return;
}
// stored token issuer indicates we authenticated via OIDC-native flow
const tokenIssuer = (0, _persistOidcSettings.getStoredOidcTokenIssuer)();
if (!tokenIssuer) {
return;
}
try {
const clientId = (0, _persistOidcSettings.getStoredOidcClientId)();
const idTokenClaims = (0, _persistOidcSettings.getStoredOidcIdTokenClaims)();
const redirectUri = _PlatformPeg.default.get().getOidcCallbackUrl().href;
const deviceId = credentials.deviceId;
if (!deviceId) {
throw new Error("Expected deviceId in user credentials.");
}
const tokenRefresher = new _TokenRefresher.TokenRefresher(tokenIssuer, clientId, redirectUri, deviceId, idTokenClaims, credentials.userId);
// wait for the OIDC client to initialise
await tokenRefresher.oidcClientReady;
return tokenRefresher;
} catch (error) {
_logger.logger.error("Failed to initialise OIDC token refresher", error);
}
}
/**
* optionally clears localstorage, persists new credentials
* to localstorage, starts the new client.
*
* @param {IMatrixClientCreds} credentials The credentials to use
* @param {Boolean} clearStorageEnabled True to clear storage before starting the new client
* @param {Boolean} isFreshLogin True if this is a fresh login, false if it is previous session being restored
*
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/
async function doSetLoggedIn(credentials, clearStorageEnabled, isFreshLogin) {
checkSessionLock();
credentials.guest = Boolean(credentials.guest);
const softLogout = isSoftLogout();
_logger.logger.log("setLoggedIn: mxid: " + credentials.userId + " deviceId: " + credentials.deviceId + " guest: " + credentials.guest + " hs: " + credentials.homeserverUrl + " softLogout: " + softLogout, " freshLogin: " + credentials.freshLogin);
if (clearStorageEnabled) {
await clearStorage();
}
const results = await StorageManager.checkConsistency();
// If there's an inconsistency between account data in local storage and the
// crypto store, we'll be generally confused when handling encrypted data.
// Show a modal recommending a full reset of storage.
if (results.dataInLocalStorage && results.cryptoInited && !results.dataInCryptoStore) {
await abortLogin();
}
const tokenRefresher = await createOidcTokenRefresher(credentials);
// check the session lock just before creating the new client
checkSessionLock();
_MatrixClientPeg.MatrixClientPeg.replaceUsingCreds(credentials, tokenRefresher?.doRefreshAccessToken.bind(tokenRefresher));
const client = _MatrixClientPeg.MatrixClientPeg.safeGet();
(0, _sentry.setSentryUser)(credentials.userId);
if (_PosthogAnalytics.PosthogAnalytics.instance.isEnabled()) {
_PosthogAnalytics.PosthogAnalytics.instance.startListeningToSettingsChanges(client);
}
if (localStorage) {
try {
await persistCredentials(credentials);
// make sure we don't think that it's a fresh login any more
sessionStorage.removeItem("mx_fresh_login");
} catch (e) {
_logger.logger.warn("Error using local storage: can't persist session!", e);
}
} else {
_logger.logger.warn("No local storage available: can't persist session!");
}
checkSessionLock();
// We are now logged in, so fire this. We have yet to start the client but the
// client_started dispatch is for that.
_dispatcher.default.fire(_actions.Action.OnLoggedIn);
const clientPegOpts = {};
if (credentials.pickleKey) {
// The pickleKey, if provided, is probably a base64-encoded 256-bit key, so can be used for the crypto store.
if (credentials.pickleKey.length === 43) {
clientPegOpts.rustCryptoStoreKey = (0, _matrix.decodeBase64)(credentials.pickleKey);
} else {
// We have some legacy pickle key. Continue using it as a password.
clientPegOpts.rustCryptoStorePassword = credentials.pickleKey;
}
}
try {
await startMatrixClient(client, /*startSyncing=*/!softLogout, clientPegOpts);
} finally {
clientPegOpts.rustCryptoStoreKey?.fill(0);
}
// Run the migrations after the MatrixClientPeg has been assigned
_SettingsStore.default.runMigrations(isFreshLogin);
if (isFreshLogin && !credentials.guest) {
// For newly registered users, set a flag so that we force them to verify,
// (we don't want to force users with existing sessions to verify though)
localStorage.setItem("must_verify_device", "true");
}
return client;
}
async function showStorageEvictedDialog() {
const {
finished
} = _Modal.default.createDialog(_StorageEvictedDialog.default);
const [ok] = await finished;
return !!ok;
}
// Note: Babel 6 requires the `transform-builtin-extend` plugin for this to satisfy
// `instanceof`. Babel 7 supports this natively in their class handling.
class AbortLoginAndRebuildStorage extends Error {}
async function persistCredentials(credentials) {
localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl);
if (credentials.identityServerUrl) {
localStorage.setItem(ID_SERVER_URL_KEY, credentials.identityServerUrl);
}
localStorage.setItem("mx_user_id", credentials.userId);
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
await (0, _tokens.persistAccessTokenInStorage)(credentials.accessToken, credentials.pickleKey);
await (0, _tokens.persistRefreshTokenInStorage)(credentials.refreshToken, credentials.pickleKey);
if (credentials.pickleKey) {
localStorage.setItem("mx_has_pickle_key", String(true));
} else {
if (localStorage.getItem("mx_has_pickle_key") === "true") {
_logger.logger.error("Expected a pickle key, but none provided. Encryption may not work.");
}
}
// 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);
}
_ModuleRunner.ModuleRunner.instance.extensions.cryptoSetup?.persistCredentials(credentials);
_logger.logger.log(`Session persisted for ${credentials.userId}`);
}
let _isLoggingOut = false;
/**
* Logs out the current session.
* When user has authenticated using OIDC native flow revoke tokens with OIDC provider.
* Otherwise, call /logout on the homeserver.
* @param client
* @param oidcClientStore
*/
async function doLogout(client, oidcClientStore) {
if (oidcClientStore?.isUserAuthenticatedWithOidc) {
const accessToken = client.getAccessToken() ?? undefined;
const refreshToken = client.getRefreshToken() ?? undefined;
await oidcClientStore.revokeTokens(accessToken, refreshToken);
} else {
await client.logout(true);
}
}
/**
* Logs the current session out and transitions to the logged-out state
* @param oidcClientStore store instance from SDKContext
*/
function logout(oidcClientStore) {
const client = _MatrixClientPeg.MatrixClientPeg.get();
if (!client) return;
_PosthogAnalytics.PosthogAnalytics.instance.logout();
if (client.isGuest()) {
// logout doesn't work for guest sessions
// Also we sometimes want to re-log in a guest session if we abort the login.
// defer until next tick because it calls a synchronous dispatch, and we are likely here from a dispatch.
setTimeout(onLoggedOut, 0);
return;
}
_isLoggingOut = true;
_PlatformPeg.default.get()?.destroyPickleKey(client.getSafeUserId(), client.getDeviceId() ?? "");
doLogout(client, oidcClientStore).then(onLoggedOut, 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).
_logger.logger.warn("Failed to call logout API: token will not be invalidated", err);
onLoggedOut();
});
}
function softLogout() {
if (!_MatrixClientPeg.MatrixClientPeg.get()) return;
// Track that we've detected and trapped a soft logout. This helps prevent other
// parts of the app from starting if there's no point (ie: don't sync if we've
// been soft logged out, despite having credentials and data for a MatrixClient).
localStorage.setItem("mx_soft_logout", "true");
// Dev note: please keep this log line around. It can be useful for track down
// random clients stopping in the middle of the logs.
_logger.logger.log("Soft logout initiated");
_isLoggingOut = true; // to avoid repeated flags
// Ensure that we dispatch a view change **before** stopping the client so
// so that React components unmount first. This avoids React soft crashes
// that can occur when components try to use a null client.
_dispatcher.default.dispatch({
action: "on_client_not_viable"
}); // generic version of on_logged_out
stopMatrixClient( /*unsetClient=*/false);
// DO NOT CALL LOGOUT. A soft logout preserves data, logout does not.
}
function isSoftLogout() {
return localStorage.getItem("mx_soft_logout") === "true";
}
function isLoggingOut() {
return _isLoggingOut;
}
/**
* Starts the matrix client and all other react-sdk services that
* listen for events while a session is logged in.
*
* @param client the matrix client to start
* @param startSyncing - `true` to actually start syncing the client.
* @param clientPegOpts - Options to pass through to {@link MatrixClientPeg.start}.
*/
async function startMatrixClient(client, startSyncing, clientPegOpts) {
_logger.logger.log(`Lifecycle: Starting MatrixClient`);
// 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).
_dispatcher.default.dispatch({
action: "will_start_client"
}, true);
// reset things first just in case
_SDKContext.SdkContextClass.instance.typingStore.reset();
_ToastStore.default.sharedInstance().reset();
_DialogOpener.DialogOpener.instance.prepare(client);
_Notifier.default.start();
_UserActivity.default.sharedInstance().start();
_DMRoomMap.default.makeShared(client).start();
_IntegrationManagers.IntegrationManagers.sharedInstance().startWatching();
_ActiveWidgetStore.default.instance.start();
_LegacyCallHandler.default.instance.start();
(0, _SupportedBrowser.checkBrowserSupport)();
// Start Mjolnir even though we haven't checked the feature flag yet. Starting
// the thing just wastes CPU cycles, but should result in no actual functionality
// being exposed to the user.
_Mjolnir.Mjolnir.sharedInstance().start();
if (startSyncing) {
// The client might want to populate some views with events from the
// index (e.g. the FilePanel), therefore initialize the event index
// before the client.
await _EventIndexPeg.default.init();
await _MatrixClientPeg.MatrixClientPeg.start(clientPegOpts);
} else {
_logger.logger.warn("Caller requested only auxiliary services be started");
await _MatrixClientPeg.MatrixClientPeg.assign(clientPegOpts);
}
checkSessionLock();
// This needs to be started after crypto is set up
_DeviceListener.default.sharedInstance().start(client);
// Similarly, don't start sending presence updates until we've started
// the client
if (!_SettingsStore.default.getValue("lowBandwidth")) {
_Presence.default.start();
}
// Now that we have a MatrixClientPeg, update the Jitsi info
_Jitsi.Jitsi.getInstance().start();
// dispatch that we finished starting up to wire up any other bits
// of the matrix client that cannot be set prior to starting up.
_dispatcher.default.dispatch({
action: "client_started"
});
if (isSoftLogout()) {
softLogout();
}
}
/*
* Stops a running client and all related services, and clears persistent
* storage. Used after a session has been logged out.
*/
async function onLoggedOut() {
// Ensure that we dispatch a view change **before** stopping the client,
// that React components unmount first. This avoids React soft crashes
// that can occur when components try to use a null client.
_dispatcher.default.fire(_actions.Action.OnLoggedOut, true);
stopMatrixClient();
await clearStorage({
deleteEverything: true
});
_Lifecycle.default.onLoggedOutAndStorageCleared?.();
await _PlatformPeg.default.get()?.clearStorage();
_SettingsStore.default.reset();
// Do this last, so we can make sure all storage has been cleared and all
// customisations got the memo.
if (_SdkConfig.default.get().logout_redirect_url) {
_logger.logger.log("Redirecting to external provider to finish logout");
// XXX: Defer this so that it doesn't race with MatrixChat unmounting the world by going to /#/login
window.setTimeout(() => {
window.location.href = _SdkConfig.default.get().logout_redirect_url;
}, 100);
}
// Do this last to prevent racing `stopMatrixClient` and `on_logged_out` with MatrixChat handling Session.logged_out
_isLoggingOut = false;
}
/**
* @param {object} opts Options for how to clear storage.
* @returns {Promise} promise which resolves once the stores have been cleared
*/
async function clearStorage(opts) {
if (window.localStorage) {
// get the currently defined device language, if set, so we can restore it later
const language = _SettingsStore.default.getValueAt(_SettingLevel.SettingLevel.DEVICE, "language", null, true, true);
// try to save any 3pid invites from being obliterated and registration time
const pendingInvites = _ThreepidInviteStore.default.instance.getWireInvites();
const registrationTime = window.localStorage.getItem("mx_registration_time");
window.localStorage.clear();
try {
await StorageAccess.idbDelete("account", _tokens.ACCESS_TOKEN_STORAGE_KEY);
} catch (e) {
_logger.logger.error("idbDelete failed for account:mx_access_token", e);
}
// now restore those invites, registration time and previously set device language
if (!opts?.deleteEverything) {
if (language) {
await _SettingsStore.default.setValue("language", null, _SettingLevel.SettingLevel.DEVICE, language);
}
pendingInvites.forEach(_ref => {
let {
roomId
} = _ref,
invite = (0, _objectWithoutProperties2.default)(_ref, _excluded);
_ThreepidInviteStore.default.instance.storeInvite(roomId, invite);
});
if (registrationTime) {
window.localStorage.setItem("mx_registration_time", registrationTime);
}
}
}
window.sessionStorage?.clear();
// create a temporary client to clear out the persistent stores.
const cli = (0, _createMatrixClient.default)({
// we'll never make any requests, so can pass a bogus HS URL
baseUrl: ""
});
await _EventIndexPeg.default.deleteEventIndex();
await cli.clearStores();
}
/**
* Stop all the background processes related to the current client.
* @param {boolean} unsetClient True (default) to abandon the client
* on MatrixClientPeg after stopping.
*/
function stopMatrixClient(unsetClient = true) {
_Notifier.default.stop();
_LegacyCallHandler.default.instance.stop();
_UserActivity.default.sharedInstance().stop();
_SDKContext.SdkContextClass.instance.typingStore.reset();
_Presence.default.stop();
_ActiveWidgetStore.default.instance.stop();
_IntegrationManagers.IntegrationManagers.sharedInstance().stopWatching();
_Mjolnir.Mjolnir.sharedInstance().stop();
_DeviceListener.default.sharedInstance().stop();
_DMRoomMap.default.shared()?.stop();
_EventIndexPeg.default.stop();
const cli = _MatrixClientPeg.MatrixClientPeg.get();
if (cli) {
cli.stopClient();
cli.removeAllListeners();
if (unsetClient) {
_MatrixClientPeg.MatrixClientPeg.unset();
_EventIndexPeg.default.unset();
cli.store.destroy();
}
}
}
// Utility method to perform a login with an existing access_token
window.mxLoginWithAccessToken = async (hsUrl, accessToken) => {
const tempClient = (0, _matrix.createClient)({
baseUrl: hsUrl,
accessToken
});
const {
user_id: userId
} = await tempClient.whoami();
await doSetLoggedIn({
homeserverUrl: hsUrl,
accessToken,
userId
}, true, false);
};
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbWF0cml4IiwicmVxdWlyZSIsIl9sb2dnZXIiLCJfTWF0cml4Q2xpZW50UGVnIiwiX01vZHVsZVJ1bm5lciIsIl9FdmVudEluZGV4UGVnIiwiX2ludGVyb3BSZXF1aXJlRGVmYXVsdCIsIl9jcmVhdGVNYXRyaXhDbGllbnQiLCJfTm90aWZpZXIiLCJfVXNlckFjdGl2aXR5IiwiX1ByZXNlbmNlIiwiX2Rpc3BhdGNoZXIiLCJfRE1Sb29tTWFwIiwiX01vZGFsIiwiX0FjdGl2ZVdpZGdldFN0b3JlIiwiX1BsYXRmb3JtUGVnIiwiX0xvZ2luIiwiU3RvcmFnZU1hbmFnZXIiLCJfaW50ZXJvcFJlcXVpcmVXaWxkY2FyZCIsIlN0b3JhZ2VBY2Nlc3MiLCJfU2V0dGluZ3NTdG9yZSIsIl9TZXR0aW5nTGV2ZWwiLCJfVG9hc3RTdG9yZSIsIl9JbnRlZ3JhdGlvbk1hbmFnZXJzIiwiX01qb2xuaXIiLCJfRGV2aWNlTGlzdGVuZXIiLCJfSml0c2kiLCJfQmFzZVBsYXRmb3JtIiwiX1RocmVlcGlkSW52aXRlU3RvcmUiLCJfUG9zdGhvZ0FuYWx5dGljcyIsIl9MZWdhY3lDYWxsSGFuZGxlciIsIl9MaWZlY3ljbGUiLCJfRXJyb3JEaWFsb2ciLCJfbGFuZ3VhZ2VIYW5kbGVyIiwiX1Nlc3Npb25SZXN0b3JlRXJyb3JEaWFsb2ciLCJfU3RvcmFnZUV2aWN0ZWREaWFsb2ciLCJfc2VudHJ5IiwiX1Nka0NvbmZpZyIsIl9EaWFsb2dPcGVuZXIiLCJfYWN0aW9ucyIsIl9TREtDb250ZXh0IiwiX0Vycm9yVXRpbHMiLCJfYXV0aG9yaXplIiwiX2Vycm9yIiwiX3BlcnNpc3RPaWRjU2V0dGluZ3MiLCJfdG9rZW5zIiwiX1Rva2VuUmVmcmVzaGVyIiwiX1N1cHBvcnRlZEJyb3dzZXIiLCJfZXhjbHVkZWQiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJlIiwiV2Vha01hcCIsInIiLCJ0IiwiX19lc01vZHVsZSIsImRlZmF1bHQiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwidSIsImhhc093blByb3BlcnR5IiwiY2FsbCIsImkiLCJzZXQiLCJIT01FU0VSVkVSX1VSTF9LRVkiLCJJRF9TRVJWRVJfVVJMX0tFWSIsImRpcyIsInJlZ2lzdGVyIiwicGF5bG9hZCIsImFjdGlvbiIsIkFjdGlvbiIsIlRyaWdnZXJMb2dvdXQiLCJvbkxvZ2dlZE91dCIsIk92ZXJ3cml0ZUxvZ2luIiwidHlwZWQiLCJzdG9wTWF0cml4Q2xpZW50IiwiZG9TZXRMb2dnZWRJbiIsImNyZWRlbnRpYWxzIiwiY2F0Y2giLCJsb2dnZXIiLCJ3YXJuIiwic2Vzc2lvbkxvY2tTdG9sZW4iLCJzZXRTZXNzaW9uTG9ja05vdFN0b2xlbiIsIm9uU2Vzc2lvbkxvY2tTdG9sZW4iLCJjaGVja1Nlc3Npb25Mb2NrIiwiU2Vzc2lvbkxvY2tTdG9sZW5FcnJvciIsIkVycm9yIiwibG9hZFNlc3Npb24iLCJvcHRzIiwiZW5hYmxlR3Vlc3QiLCJndWVzdEhzVXJsIiwiZ3Vlc3RJc1VybCIsImZyYWdtZW50UXVlcnlQYXJhbXMiLCJkZWZhdWx0RGV2aWNlRGlzcGxheU5hbWUiLCJndWVzdF91c2VyX2lkIiwiZ3Vlc3RfYWNjZXNzX3Rva2VuIiwibG9nIiwidXNlcklkIiwiYWNjZXNzVG9rZW4iLCJob21lc2VydmVyVXJsIiwiaWRlbnRpdHlTZXJ2ZXJVcmwiLCJndWVzdCIsInRoZW4iLCJzdWNjZXNzIiwicmVzdG9yZVNlc3Npb25Gcm9tU3RvcmFnZSIsImlnbm9yZUd1ZXN0IiwiQm9vbGVhbiIsInJlZ2lzdGVyQXNHdWVzdCIsIkFib3J0TG9naW5BbmRSZWJ1aWxkU3RvcmFnZSIsImhhbmRsZUxvYWRTZXNzaW9uRmFpbHVyZSIsImdldFN0b3JlZFNlc3Npb25Pd25lciIsImhzVXJsIiwiaGFzQWNjZXNzVG9rZW4iLCJpc0d1ZXN0IiwiZ2V0U3RvcmVkU2Vzc2lvblZhcnMiLCJhdHRlbXB0RGVsZWdhdGVkQXV0aExvZ2luIiwicXVlcnlQYXJhbXMiLCJmcmFnbWVudEFmdGVyTG9naW4iLCJjb2RlIiwic3RhdGUiLCJjb25zb2xlIiwiYXR0ZW1wdE9pZGNOYXRpdmVMb2dpbiIsImF0dGVtcHRUb2tlbkxvZ2luIiwicmVmcmVzaFRva2VuIiwiaWRUb2tlbiIsImNsaWVudElkIiwiaXNzdWVyIiwiY29tcGxldGVPaWRjTG9naW4iLCJ1c2VyX2lkIiwiZGV2aWNlX2lkIiwiZGV2aWNlSWQiLCJpc19ndWVzdCIsImdldFVzZXJJZEZyb21BY2Nlc3NUb2tlbiIsImRlYnVnIiwib25TdWNjZXNzZnVsRGVsZWdhdGVkQXV0aExvZ2luIiwicGVyc2lzdE9pZGNBdXRoZW50aWNhdGVkU2V0dGluZ3MiLCJlcnJvciIsIm9uRmFpbGVkRGVsZWdhdGVkQXV0aExvZ2luIiwiZ2V0T2lkY0Vycm9yTWVzc2FnZSIsImNsaWVudCIsImNyZWF0ZUNsaWVudCIsImJhc2VVcmwiLCJpZEJhc2VVcmwiLCJ3aG9hbWkiLCJsb2dpblRva2VuIiwiUHJvbWlzZSIsInJlc29sdmUiLCJob21lc2VydmVyIiwibG9jYWxTdG9yYWdlIiwiZ2V0SXRlbSIsIlNTT19IT01FU0VSVkVSX1VSTF9LRVkiLCJpZGVudGl0eVNlcnZlciIsIlNTT19JRF9TRVJWRVJfVVJMX0tFWSIsInVuZGVmaW5lZCIsIl90Iiwic2VuZExvZ2luUmVxdWVzdCIsInRva2VuIiwiaW5pdGlhbF9kZXZpY2VfZGlzcGxheV9uYW1lIiwiY3JlZHMiLCJ0cnlBZ2FpbkNhbGxiYWNrIiwiY2xpIiwiaWRwSWQiLCJTU09fSURQX0lEX0tFWSIsIlBsYXRmb3JtUGVnIiwic3RhcnRTaW5nbGVTaWduT24iLCJTU09BY3Rpb24iLCJMT0dJTiIsIm1lc3NhZ2VGb3JMb2dpbkVycm9yIiwiaHNOYW1lIiwiY2xlYXJTdG9yYWdlIiwicGVyc2lzdENyZWRlbnRpYWxzIiwic2Vzc2lvblN0b3JhZ2UiLCJzZXRJdGVtIiwiU3RyaW5nIiwiZGVzY3JpcHRpb24iLCJ0cnlBZ2FpbiIsIk1vZGFsIiwiY3JlYXRlRGlhbG9nIiwiRXJyb3JEaWFsb2ciLCJ0aXRsZSIsImJ1dHRvbiIsIm9uRmluaXNoZWQiLCJzaG91bGRUcnlBZ2FpbiIsImlzVXJsIiwicmVnaXN0ZXJHdWVzdCIsImJvZHkiLCJhY2Nlc3NfdG9rZW4iLCJlcnIiLCJnZXRTdG9yZWRUb2tlbiIsInN0b3JhZ2VLZXkiLCJpZGJMb2FkIiwiaWRiU2F2ZSIsInJlbW92ZUl0ZW0iLCJBQ0NFU1NfVE9LRU5fU1RPUkFHRV9LRVkiLCJSRUZSRVNIX1RPS0VOX1NUT1JBR0VfS0VZIiwiSEFTX0FDQ0VTU19UT0tFTl9TVE9SQUdFX0tFWSIsImhhc1JlZnJlc2hUb2tlbiIsIkhBU19SRUZSRVNIX1RPS0VOX1NUT1JBR0VfS0VZIiwiYWJvcnRMb2dpbiIsInNpZ25PdXQiLCJzaG93U3RvcmFnZUV2aWN0ZWREaWFsb2ciLCJwaWNrbGVLZXkiLCJnZXRQaWNrbGVLZXkiLCJkZWNyeXB0ZWRBY2Nlc3NUb2tlbiIsInRyeURlY3J5cHRUb2tlbiIsIkFDQ0VTU19UT0tFTl9JViIsImRlY3J5cHRlZFJlZnJlc2hUb2tlbiIsIlJFRlJFU0hfVE9LRU5fSVYiLCJmcmVzaExvZ2luIiwibW9kYWwiLCJTZXNzaW9uUmVzdG9yZUVycm9yRGlhbG9nIiwiZmluaXNoZWQiLCJzZXRMb2dnZWRJbiIsImNyZWF0ZVBpY2tsZUtleSIsImFzc2lnbiIsImNyZWF0ZU9pZGNUb2tlblJlZnJlc2hlciIsInRva2VuSXNzdWVyIiwiZ2V0U3RvcmVkT2lkY1Rva2VuSXNzdWVyIiwiZ2V0U3RvcmVkT2lkY0NsaWVudElkIiwiaWRUb2tlbkNsYWltcyIsImdldFN0b3JlZE9pZGNJZFRva2VuQ2xhaW1zIiwicmVkaXJlY3RVcmkiLCJnZXRPaWRjQ2FsbGJhY2tVcmwiLCJocmVmIiwidG9rZW5SZWZyZXNoZXIiLCJUb2tlblJlZnJlc2hlciIsIm9pZGNDbGllbnRSZWFkeSIsImNsZWFyU3RvcmFnZUVuYWJsZWQiLCJpc0ZyZXNoTG9naW4iLCJzb2Z0TG9nb3V0IiwiaXNTb2Z0TG9nb3V0IiwicmVzdWx0cyIsImNoZWNrQ29uc2lzdGVuY3kiLCJkYXRhSW5Mb2NhbFN0b3JhZ2UiLCJjcnlwdG9Jbml0ZWQiLCJkYXRhSW5DcnlwdG9TdG9yZSIsIk1hdHJpeENsaWVudFBlZyIsInJlcGxhY2VVc2luZ0NyZWRzIiwiZG9SZWZyZXNoQWNjZXNzVG9rZW4iLCJiaW5kIiwic2FmZUdldCIsInNldFNlbnRyeVVzZXIiLCJQb3N0aG9nQW5hbHl0aWNzIiwiaW5zdGFuY2UiLCJpc0VuYWJsZWQiLCJzdGFydExpc3RlbmluZ1RvU2V0dGluZ3NDaGFuZ2VzIiwiZmlyZSIsIk9uTG9nZ2VkSW4iLCJjbGllbnRQZWdPcHRzIiwibGVuZ3RoIiwicnVzdENyeXB0b1N0b3JlS2V5IiwiZGVjb2RlQmFzZTY0IiwicnVzdENyeXB0b1N0b3JlUGFzc3dvcmQiLCJzdGFydE1hdHJpeENsaWVudCIsImZpbGwiLCJTZXR0aW5nc1N0b3JlIiwicnVuTWlncmF0aW9ucyIsIlN0b3JhZ2VFdmljdGVkRGlhbG9nIiwib2siLCJKU09OIiwic3RyaW5naWZ5IiwicGVyc2lzdEFjY2Vzc1Rva2VuSW5TdG9yYWdlIiwicGVyc2lzdFJlZnJlc2hUb2tlbkluU3RvcmFnZSIsIk1vZHVsZVJ1bm5lciIsImV4dGVuc2lvbnMiLCJjcnlwdG9TZXR1cCIsIl9pc0xvZ2dpbmdPdXQiLCJkb0xvZ291dCIsIm9pZGNDbGllbnRTdG9yZSIsImlzVXNlckF1dGhlbnRpY2F0ZWRXaXRoT2lkYyIsImdldEFjY2Vzc1Rva2VuIiwiZ2V0UmVmcmVzaFRva2VuIiwicmV2b2tlVG9rZW5zIiwibG9nb3V0Iiwic2V0VGltZW91dCIsImRlc3Ryb3lQaWNrbGVLZXkiLCJnZXRTYWZlVXNlcklkIiwiZ2V0RGV2aWNlSWQiLCJkaXNwYXRjaCIsImlzTG9nZ2luZ091dCIsInN0YXJ0U3luY2luZyIsIlNka0NvbnRleHRDbGFzcyIsInR5cGluZ1N0b3JlIiwicmVzZXQiLCJUb2FzdFN0b3JlIiwic2hhcmVkSW5zdGFuY2UiLCJEaWFsb2dPcGVuZXIiLCJwcmVwYXJlIiwiTm90aWZpZXIiLCJzdGFydCIsIlVzZXJBY3Rpdml0eSIsIkRNUm9vbU1hcCIsIm1ha2VTaGFyZWQiLCJJbnRlZ3JhdGlvbk1hbmFnZXJzIiwic3RhcnRXYXRjaGluZyIsIkFjdGl2ZVdpZGdldFN0b3JlIiwiTGVnYWN5Q2FsbEhhbmRsZXIiLCJjaGVja0Jyb3dzZXJTdXBwb3J0IiwiTWpvbG5pciIsIkV2ZW50SW5kZXhQZWciLCJpbml0IiwiRGV2aWNlTGlzdGVuZXIiLCJnZXRWYWx1ZSIsIlByZXNlbmNlIiwiSml0c2kiLCJnZXRJbnN0YW5jZSIsIk9uTG9nZ2VkT3V0IiwiZGVsZXRlRXZlcnl0aGluZyIsIkxpZmVjeWNsZUN1c3RvbWlzYXRpb25zIiwib25Mb2dnZWRPdXRBbmRTdG9yYWdlQ2xlYXJlZCIsIlNka0NvbmZpZyIsImxvZ291dF9yZWRpcmVjdF91cmwiLCJ3aW5kb3ciLCJsb2NhdGlvbiIsImxhbmd1YWdlIiwiZ2V0VmFsdWVBdCIsIlNldHRpbmdMZXZlbCIsIkRFVklDRSIsInBlbmRpbmdJbnZpdGVzIiwiVGhyZWVwaWRJbnZpdGVTdG9yZSIsImdldFdpcmVJbnZpdGVzIiwicmVnaXN0cmF0aW9uVGltZSIsImNsZWFyIiwiaWRiRGVsZXRlIiwic2V0VmFsdWUiLCJmb3JFYWNoIiwiX3JlZiIsInJvb21JZCIsImludml0ZSIsIl9vYmplY3RXaXRob3V0UHJvcGVydGllczIiLCJzdG9yZUludml0ZSIsImNyZWF0ZU1hdHJpeENsaWVudCIsImRlbGV0ZUV2ZW50SW5kZXgiLCJjbGVhclN0b3JlcyIsInVuc2V0Q2xpZW50Iiwic3RvcCIsInN0b3BXYXRjaGluZyIsInNoYXJlZCIsInN0b3BDbGllbnQiLCJyZW1vdmVBbGxMaXN0ZW5lcnMiLCJ1bnNldCIsInN0b3JlIiwiZGVzdHJveSIsIm14TG9naW5XaXRoQWNjZXNzVG9rZW4iLCJ0ZW1wQ2xpZW50Il0sInNvdXJjZXMiOlsiLi4vc3JjL0xpZmVjeWNsZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAxOSwgMjAyMCAsIDIwMjMgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cbkNvcHlyaWdodCAyMDE4IE5ldyBWZWN0b3IgTHRkXG5Db3B5cmlnaHQgMjAxNyBWZWN0b3IgQ3JlYXRpb25zIEx0ZFxuQ29weXJpZ2h0IDIwMTUsIDIwMTYgT3Blbk1hcmtldCBMdGRcblxuU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFHUEwtMy4wLW9ubHkgT1IgR1BMLTMuMC1vbmx5XG5QbGVhc2Ugc2VlIExJQ0VOU0UgZmlsZXMgaW4gdGhlIHJlcG9zaXRvcnkgcm9vdCBmb3IgZnVsbCBkZXRhaWxzLlxuKi9cblxuaW1wb3J0IHsgUmVhY3ROb2RlIH0gZnJvbSBcInJlYWN0XCI7XG5pbXBvcnQgeyBjcmVhdGVDbGllbnQsIE1hdHJpeENsaWVudCwgU1NPQWN0aW9uLCBPaWRjVG9rZW5SZWZyZXNoZXIsIGRlY29kZUJhc2U2NCB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9tYXRyaXhcIjtcbmltcG9ydCB7IEFFU0VuY3J5cHRlZFNlY3JldFN0b3JhZ2VQYXlsb2FkIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL3R5cGVzXCI7XG5pbXBvcnQgeyBRdWVyeURpY3QgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvdXRpbHNcIjtcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9sb2dnZXJcIjtcblxuaW1wb3J0IHsgSU1hdHJpeENsaWVudENyZWRzLCBNYXRyaXhDbGllbnRQZWcsIE1hdHJpeENsaWVudFBlZ0Fzc2lnbk9wdHMgfSBmcm9tIFwiLi9NYXRyaXhDbGllbnRQZWdcIjtcbmltcG9ydCB7IE1vZHVsZVJ1bm5lciB9IGZyb20gXCIuL21vZHVsZXMvTW9kdWxlUnVubmVyXCI7XG5pbXBvcnQgRXZlbnRJbmRleFBlZyBmcm9tIFwiLi9pbmRleGluZy9FdmVudEluZGV4UGVnXCI7XG5pbXBvcnQgY3JlYXRlTWF0cml4Q2xpZW50IGZyb20gXCIuL3V0aWxzL2NyZWF0ZU1hdHJpeENsaWVudFwiO1xuaW1wb3J0IE5vdGlmaWVyIGZyb20gXCIuL05vdGlmaWVyXCI7XG5pbXBvcnQgV