diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx
index b50c9926..c9b720d1 100644
--- a/src/app/organisms/settings/Settings.jsx
+++ b/src/app/organisms/settings/Settings.jsx
@@ -5,12 +5,14 @@ import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import settings from '../../../client/state/settings';
import navigation from '../../../client/state/navigation';
+import { openReusableDialog } from '../../../client/action/navigation'
import {
toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents,
toggleNotifications, toggleNotificationSounds,
} from '../../../client/action/settings';
import logout from '../../../client/action/logout';
import { usePermission } from '../../hooks/usePermission';
+import colorMXID from '../../../util/colorMXID';
import Text from '../../atoms/text/Text';
import IconButton from '../../atoms/button/IconButton';
@@ -25,6 +27,7 @@ import SettingTile from '../../molecules/setting-tile/SettingTile';
import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys';
import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys';
import { ImagePackUser, ImagePackGlobal } from '../../molecules/image-pack/ImagePack';
+import PeopleSelector from '../../molecules/people-selector/PeopleSelector';
import ProfileEditor from '../profile-editor/ProfileEditor';
import CrossSigning from './CrossSigning';
@@ -38,6 +41,7 @@ import BellIC from '../../../../public/res/ic/outlined/bell.svg';
import InfoIC from '../../../../public/res/ic/outlined/info.svg';
import PowerIC from '../../../../public/res/ic/outlined/power.svg';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
+import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg';
import CinnySVG from '../../../../public/res/svg/cinny.svg';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
@@ -322,6 +326,50 @@ function Settings() {
logout();
}
};
+ const handleSwitchUsers = async () => {
+ const loggedInUsers = JSON.parse(window.localStorage.getItem("loggedInUsers"));
+
+ const renderUserSwitcher = () => (
+
+ {
+ loggedInUsers.map((userId) => {
+ let mx = initMatrix.matrixClient;
+ let user = mx.getUser(userId);
+ let avatarUrl = user.avatarUrl ? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop') : null;
+ return (
+
{
+ if (userId != window.localStorage.getItem("currentUser")) {
+ window.localStorage.setItem("currentUser", userId);
+ window.location.reload();
+ }
+ }}
+ name={user.displayName}
+ avatarSrc={avatarUrl}
+ color={colorMXID(userId)}
+ />
+ );
+ })
+ }
+
+
+ );
+
+ await openReusableDialog(
+ Switch users,
+ renderUserSwitcher,
+ )
+ }
return (
Settings}
contentOptions={(
<>
+
diff --git a/src/app/templates/auth/Auth.jsx b/src/app/templates/auth/Auth.jsx
index 7c211736..d8553133 100644
--- a/src/app/templates/auth/Auth.jsx
+++ b/src/app/templates/auth/Auth.jsx
@@ -9,6 +9,7 @@ import * as auth from '../../../client/action/auth';
import cons from '../../../client/state/cons';
import { Debounce, getUrlPrams } from '../../../util/common';
import { getBaseUrl } from '../../../util/matrixUtil';
+import colorMXID from '../../../util/colorMXID';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
@@ -25,6 +26,7 @@ import EyeIC from '../../../../public/res/ic/outlined/eye.svg';
import EyeBlindIC from '../../../../public/res/ic/outlined/eye-blind.svg';
import CinnySvg from '../../../../public/res/svg/cinny.svg';
import SSOButtons from '../../molecules/sso-buttons/SSOButtons';
+import PeopleSelector from '../../molecules/people-selector/PeopleSelector';
const LOCALPART_SIGNUP_REGEX = /^[a-z0-9_\-.=/]+$/;
const BAD_LOCALPART_ERROR = 'Username can only contain characters a-z, 0-9, or \'=_-./\'';
@@ -539,11 +541,11 @@ function Auth() {
useEffect(async () => {
if (!loginToken) return;
- if (localStorage.getItem(cons.secretKey.BASE_URL) === undefined) {
+ if (window.userLocalStorage.getItem(cons.secretKey.BASE_URL) === undefined) {
setLoginToken(null);
return;
}
- const baseUrl = localStorage.getItem(cons.secretKey.BASE_URL);
+ const baseUrl = window.userLocalStorage.getItem(cons.secretKey.BASE_URL);
try {
await auth.loginWithToken(baseUrl, loginToken);
@@ -567,6 +569,24 @@ function Auth() {
Cinny
+ {(() => {
+ const loggedInUsers = JSON.parse(window.localStorage.getItem("loggedInUsers"));
+ return (
+ loggedInUsers.map((userId) => {
+ return (
+ {
+ window.localStorage.setItem("currentUser", userId);
+ window.location.reload();
+ }}
+ name={userId}
+ color={colorMXID(userId)}
+ />
+ );
+ })
+ );
+ })()}
diff --git a/src/client/action/auth.js b/src/client/action/auth.js
index f9be13bc..19caa0ae 100644
--- a/src/client/action/auth.js
+++ b/src/client/action/auth.js
@@ -1,11 +1,19 @@
import * as sdk from 'matrix-js-sdk';
import cons from '../state/cons';
+import NamespacedStorage from '../state/NamespacedStorage';
function updateLocalStore(accessToken, deviceId, userId, baseUrl) {
- localStorage.setItem(cons.secretKey.ACCESS_TOKEN, accessToken);
- localStorage.setItem(cons.secretKey.DEVICE_ID, deviceId);
- localStorage.setItem(cons.secretKey.USER_ID, userId);
- localStorage.setItem(cons.secretKey.BASE_URL, baseUrl);
+ window.localStorage.setItem("currentUser", userId);
+ let loggedInUsers = new Set(JSON.parse(window.localStorage.getItem("loggedInUsers")));
+ loggedInUsers.add(userId);
+ window.localStorage.setItem("loggedInUsers", JSON.stringify(Array.from(loggedInUsers)));
+
+ window.userLocalStorage = new NamespacedStorage(userId, window.localStorage);
+
+ window.userLocalStorage.setItem(cons.secretKey.ACCESS_TOKEN, accessToken);
+ window.userLocalStorage.setItem(cons.secretKey.DEVICE_ID, deviceId);
+ window.userLocalStorage.setItem(cons.secretKey.USER_ID, userId);
+ window.userLocalStorage.setItem(cons.secretKey.BASE_URL, baseUrl);
}
function createTemporaryClient(baseUrl) {
@@ -14,7 +22,7 @@ function createTemporaryClient(baseUrl) {
async function startSsoLogin(baseUrl, type, idpId) {
const client = createTemporaryClient(baseUrl);
- localStorage.setItem(cons.secretKey.BASE_URL, client.baseUrl);
+ window.userLocalStorage.setItem(cons.secretKey.BASE_URL, client.baseUrl);
window.location.href = client.getSsoLoginUrl(window.location.href, type, idpId);
}
diff --git a/src/client/action/logout.js b/src/client/action/logout.js
index c4047bb7..caf9554f 100644
--- a/src/client/action/logout.js
+++ b/src/client/action/logout.js
@@ -9,7 +9,14 @@ async function logout() {
// ignore if failed to logout
}
mx.clearStores();
- window.localStorage.clear();
+
+ window.userLocalStorage.clear();
+ let currentUser = window.localStorage.getItem("currentUser");
+ let loggedInUsers = new Set(JSON.parse(window.localStorage.getItem("loggedInUsers")));
+ loggedInUsers.delete(currentUser);
+ window.localStorage.setItem("loggedInUsers", JSON.stringify(loggedInUsers));
+ window.localStorage.removeItem("currentUser");
+
window.location.reload();
}
diff --git a/src/client/initMatrix.js b/src/client/initMatrix.js
index e219a777..2cfbbcfd 100644
--- a/src/client/initMatrix.js
+++ b/src/client/initMatrix.js
@@ -9,6 +9,7 @@ import RoomsInput from './state/RoomsInput';
import Notifications from './state/Notifications';
import { cryptoCallbacks } from './state/secretStorageKeys';
import navigation from './state/navigation';
+import NamespacedStorage from './state/NamespacedStorage';
global.Olm = require('@matrix-org/olm');
@@ -22,16 +23,19 @@ class InitMatrix extends EventEmitter {
}
async init() {
- await this.startClient();
+ const user = window.localStorage.getItem("currentUser");
+ window.userLocalStorage = new NamespacedStorage(user, window.localStorage);
+
+ await this.startClient(user, window.userLocalStorage);
this.setupSync();
this.listenEvents();
}
- async startClient() {
+ async startClient(user, storage) {
const indexedDBStore = new sdk.IndexedDBStore({
indexedDB: global.indexedDB,
- localStorage: global.localStorage,
- dbName: 'web-sync-store',
+ localStorage: storage,
+ dbName: `${user}.web-sync-store`,
});
await indexedDBStore.startup();
@@ -40,7 +44,7 @@ class InitMatrix extends EventEmitter {
accessToken: secret.accessToken,
userId: secret.userId,
store: indexedDBStore,
- cryptoStore: new sdk.IndexedDBCryptoStore(global.indexedDB, 'crypto-store'),
+ cryptoStore: new sdk.IndexedDBCryptoStore(global.indexedDB, `${user}.crypto-store`),
deviceId: secret.deviceId,
timelineSupport: true,
cryptoCallbacks,
@@ -98,7 +102,7 @@ class InitMatrix extends EventEmitter {
this.matrixClient.on('Session.logged_out', () => {
this.matrixClient.stopClient();
this.matrixClient.clearStores();
- window.localStorage.clear();
+ window.userLocalStorage.clear();
window.location.reload();
});
}
diff --git a/src/client/state/NamespacedStorage.js b/src/client/state/NamespacedStorage.js
new file mode 100644
index 00000000..1d2c220b
--- /dev/null
+++ b/src/client/state/NamespacedStorage.js
@@ -0,0 +1,44 @@
+class NamespacedStorage {
+ #namespace;
+ #storage;
+ #cache;
+
+ constructor(namespace, storage) {
+ this.#namespace = namespace;
+ this.#storage = storage;
+
+ this.#cache = new Set();
+ for (let i = 0; i < this.#storage.length; i++) {
+ let key = this.#storage.key(i);
+ if (key.startsWith(`${this.#namespace}.`)) {
+ this.#cache.add(key.replace(`${this.#namespace}.`, ""));
+ }
+ }
+ }
+
+ key(n) {
+ return this.#cache[n];
+ }
+
+ getItem(key) {
+ return this.#storage.getItem(`${this.#namespace}.${key}`);
+ }
+
+ setItem(key, value) {
+ this.#storage.setItem(`${this.#namespace}.${key}`, value);
+ this.#cache.add(key);
+ }
+
+ removeItem(key) {
+ this.#storage.removeItem(`${this.#namespace}.${key}`);
+ this.#cache.delete(key);
+ }
+
+ clear() {
+ for (let key of this.#cache) {
+ this.removeItem(key);
+ }
+ }
+}
+
+export default NamespacedStorage;
diff --git a/src/client/state/auth.js b/src/client/state/auth.js
index fbc23f6f..9fc4142a 100644
--- a/src/client/state/auth.js
+++ b/src/client/state/auth.js
@@ -1,7 +1,10 @@
import cons from './cons';
+import NamespacedStorage from './NamespacedStorage';
function getSecret(key) {
- return localStorage.getItem(key);
+ const user = global.localStorage.getItem("currentUser");
+ const storage = new NamespacedStorage(user, global.localStorage);
+ return storage.getItem(key);
}
const isAuthenticated = () => getSecret(cons.secretKey.ACCESS_TOKEN) !== null;
diff --git a/src/client/state/settings.js b/src/client/state/settings.js
index 32f55fcc..0c442d26 100644
--- a/src/client/state/settings.js
+++ b/src/client/state/settings.js
@@ -4,7 +4,7 @@ import appDispatcher from '../dispatcher';
import cons from './cons';
function getSettings() {
- const settings = localStorage.getItem('settings');
+ const settings = window.localStorage.getItem('settings');
if (settings === null) return null;
return JSON.parse(settings);
}
@@ -13,7 +13,7 @@ function setSettings(key, value) {
let settings = getSettings();
if (settings === null) settings = {};
settings[key] = value;
- localStorage.setItem('settings', JSON.stringify(settings));
+ window.localStorage.setItem('settings', JSON.stringify(settings));
}
class Settings extends EventEmitter {