very hacky account switcher poc

This commit is contained in:
ash lea 2022-08-22 16:45:18 -04:00
parent fd79ea4b9b
commit c3be616cc7
8 changed files with 154 additions and 17 deletions

View file

@ -5,12 +5,14 @@ import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import settings from '../../../client/state/settings'; import settings from '../../../client/state/settings';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { openReusableDialog } from '../../../client/action/navigation'
import { import {
toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents, toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents,
toggleNotifications, toggleNotificationSounds, toggleNotifications, toggleNotificationSounds,
} from '../../../client/action/settings'; } from '../../../client/action/settings';
import logout from '../../../client/action/logout'; import logout from '../../../client/action/logout';
import { usePermission } from '../../hooks/usePermission'; import { usePermission } from '../../hooks/usePermission';
import colorMXID from '../../../util/colorMXID';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import IconButton from '../../atoms/button/IconButton'; 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 ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys';
import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys'; import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys';
import { ImagePackUser, ImagePackGlobal } from '../../molecules/image-pack/ImagePack'; import { ImagePackUser, ImagePackGlobal } from '../../molecules/image-pack/ImagePack';
import PeopleSelector from '../../molecules/people-selector/PeopleSelector';
import ProfileEditor from '../profile-editor/ProfileEditor'; import ProfileEditor from '../profile-editor/ProfileEditor';
import CrossSigning from './CrossSigning'; 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 InfoIC from '../../../../public/res/ic/outlined/info.svg';
import PowerIC from '../../../../public/res/ic/outlined/power.svg'; import PowerIC from '../../../../public/res/ic/outlined/power.svg';
import CrossIC from '../../../../public/res/ic/outlined/cross.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 CinnySVG from '../../../../public/res/svg/cinny.svg';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
@ -322,6 +326,50 @@ function Settings() {
logout(); logout();
} }
}; };
const handleSwitchUsers = async () => {
const loggedInUsers = JSON.parse(window.localStorage.getItem("loggedInUsers"));
const renderUserSwitcher = () => (
<div>
{
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 (
<PeopleSelector
key={userId}
onClick={() => {
if (userId != window.localStorage.getItem("currentUser")) {
window.localStorage.setItem("currentUser", userId);
window.location.reload();
}
}}
name={user.displayName}
avatarSrc={avatarUrl}
color={colorMXID(userId)}
/>
);
})
}
<Button
variant="caution"
iconSrc={AddUserIC}
onClick={() => {
window.localStorage.removeItem("currentUser");
window.location.reload();
}}
>
Add user
</Button>
</div>
);
await openReusableDialog(
<Text variant="s1" weight="medium">Switch users</Text>,
renderUserSwitcher,
)
}
return ( return (
<PopupWindow <PopupWindow
@ -330,6 +378,9 @@ function Settings() {
title={<Text variant="s1" weight="medium" primary>Settings</Text>} title={<Text variant="s1" weight="medium" primary>Settings</Text>}
contentOptions={( contentOptions={(
<> <>
<Button variant="caution" onClick={handleSwitchUsers}>
Switch users
</Button>
<Button variant="danger" iconSrc={PowerIC} onClick={handleLogout}> <Button variant="danger" iconSrc={PowerIC} onClick={handleLogout}>
Logout Logout
</Button> </Button>

View file

@ -9,6 +9,7 @@ import * as auth from '../../../client/action/auth';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import { Debounce, getUrlPrams } from '../../../util/common'; import { Debounce, getUrlPrams } from '../../../util/common';
import { getBaseUrl } from '../../../util/matrixUtil'; import { getBaseUrl } from '../../../util/matrixUtil';
import colorMXID from '../../../util/colorMXID';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button'; 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 EyeBlindIC from '../../../../public/res/ic/outlined/eye-blind.svg';
import CinnySvg from '../../../../public/res/svg/cinny.svg'; import CinnySvg from '../../../../public/res/svg/cinny.svg';
import SSOButtons from '../../molecules/sso-buttons/SSOButtons'; import SSOButtons from '../../molecules/sso-buttons/SSOButtons';
import PeopleSelector from '../../molecules/people-selector/PeopleSelector';
const LOCALPART_SIGNUP_REGEX = /^[a-z0-9_\-.=/]+$/; const LOCALPART_SIGNUP_REGEX = /^[a-z0-9_\-.=/]+$/;
const BAD_LOCALPART_ERROR = 'Username can only contain characters a-z, 0-9, or \'=_-./\''; const BAD_LOCALPART_ERROR = 'Username can only contain characters a-z, 0-9, or \'=_-./\'';
@ -539,11 +541,11 @@ function Auth() {
useEffect(async () => { useEffect(async () => {
if (!loginToken) return; if (!loginToken) return;
if (localStorage.getItem(cons.secretKey.BASE_URL) === undefined) { if (window.userLocalStorage.getItem(cons.secretKey.BASE_URL) === undefined) {
setLoginToken(null); setLoginToken(null);
return; return;
} }
const baseUrl = localStorage.getItem(cons.secretKey.BASE_URL); const baseUrl = window.userLocalStorage.getItem(cons.secretKey.BASE_URL);
try { try {
await auth.loginWithToken(baseUrl, loginToken); await auth.loginWithToken(baseUrl, loginToken);
@ -567,6 +569,24 @@ function Auth() {
<Text variant="h2" weight="medium">Cinny</Text> <Text variant="h2" weight="medium">Cinny</Text>
</TitleWrapper> </TitleWrapper>
</Header> </Header>
{(() => {
const loggedInUsers = JSON.parse(window.localStorage.getItem("loggedInUsers"));
return (
loggedInUsers.map((userId) => {
return (
<PeopleSelector
key={userId}
onClick={() => {
window.localStorage.setItem("currentUser", userId);
window.location.reload();
}}
name={userId}
color={colorMXID(userId)}
/>
);
})
);
})()}
<div className="auth-card__content"> <div className="auth-card__content">
<AuthCard /> <AuthCard />
</div> </div>

View file

@ -1,11 +1,19 @@
import * as sdk from 'matrix-js-sdk'; import * as sdk from 'matrix-js-sdk';
import cons from '../state/cons'; import cons from '../state/cons';
import NamespacedStorage from '../state/NamespacedStorage';
function updateLocalStore(accessToken, deviceId, userId, baseUrl) { function updateLocalStore(accessToken, deviceId, userId, baseUrl) {
localStorage.setItem(cons.secretKey.ACCESS_TOKEN, accessToken); window.localStorage.setItem("currentUser", userId);
localStorage.setItem(cons.secretKey.DEVICE_ID, deviceId); let loggedInUsers = new Set(JSON.parse(window.localStorage.getItem("loggedInUsers")));
localStorage.setItem(cons.secretKey.USER_ID, userId); loggedInUsers.add(userId);
localStorage.setItem(cons.secretKey.BASE_URL, baseUrl); 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) { function createTemporaryClient(baseUrl) {
@ -14,7 +22,7 @@ function createTemporaryClient(baseUrl) {
async function startSsoLogin(baseUrl, type, idpId) { async function startSsoLogin(baseUrl, type, idpId) {
const client = createTemporaryClient(baseUrl); 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); window.location.href = client.getSsoLoginUrl(window.location.href, type, idpId);
} }

View file

@ -9,7 +9,14 @@ async function logout() {
// ignore if failed to logout // ignore if failed to logout
} }
mx.clearStores(); 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(); window.location.reload();
} }

View file

@ -9,6 +9,7 @@ import RoomsInput from './state/RoomsInput';
import Notifications from './state/Notifications'; import Notifications from './state/Notifications';
import { cryptoCallbacks } from './state/secretStorageKeys'; import { cryptoCallbacks } from './state/secretStorageKeys';
import navigation from './state/navigation'; import navigation from './state/navigation';
import NamespacedStorage from './state/NamespacedStorage';
global.Olm = require('@matrix-org/olm'); global.Olm = require('@matrix-org/olm');
@ -22,16 +23,19 @@ class InitMatrix extends EventEmitter {
} }
async init() { 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.setupSync();
this.listenEvents(); this.listenEvents();
} }
async startClient() { async startClient(user, storage) {
const indexedDBStore = new sdk.IndexedDBStore({ const indexedDBStore = new sdk.IndexedDBStore({
indexedDB: global.indexedDB, indexedDB: global.indexedDB,
localStorage: global.localStorage, localStorage: storage,
dbName: 'web-sync-store', dbName: `${user}.web-sync-store`,
}); });
await indexedDBStore.startup(); await indexedDBStore.startup();
@ -40,7 +44,7 @@ class InitMatrix extends EventEmitter {
accessToken: secret.accessToken, accessToken: secret.accessToken,
userId: secret.userId, userId: secret.userId,
store: indexedDBStore, store: indexedDBStore,
cryptoStore: new sdk.IndexedDBCryptoStore(global.indexedDB, 'crypto-store'), cryptoStore: new sdk.IndexedDBCryptoStore(global.indexedDB, `${user}.crypto-store`),
deviceId: secret.deviceId, deviceId: secret.deviceId,
timelineSupport: true, timelineSupport: true,
cryptoCallbacks, cryptoCallbacks,
@ -98,7 +102,7 @@ class InitMatrix extends EventEmitter {
this.matrixClient.on('Session.logged_out', () => { this.matrixClient.on('Session.logged_out', () => {
this.matrixClient.stopClient(); this.matrixClient.stopClient();
this.matrixClient.clearStores(); this.matrixClient.clearStores();
window.localStorage.clear(); window.userLocalStorage.clear();
window.location.reload(); window.location.reload();
}); });
} }

View file

@ -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;

View file

@ -1,7 +1,10 @@
import cons from './cons'; import cons from './cons';
import NamespacedStorage from './NamespacedStorage';
function getSecret(key) { 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; const isAuthenticated = () => getSecret(cons.secretKey.ACCESS_TOKEN) !== null;

View file

@ -4,7 +4,7 @@ import appDispatcher from '../dispatcher';
import cons from './cons'; import cons from './cons';
function getSettings() { function getSettings() {
const settings = localStorage.getItem('settings'); const settings = window.localStorage.getItem('settings');
if (settings === null) return null; if (settings === null) return null;
return JSON.parse(settings); return JSON.parse(settings);
} }
@ -13,7 +13,7 @@ function setSettings(key, value) {
let settings = getSettings(); let settings = getSettings();
if (settings === null) settings = {}; if (settings === null) settings = {};
settings[key] = value; settings[key] = value;
localStorage.setItem('settings', JSON.stringify(settings)); window.localStorage.setItem('settings', JSON.stringify(settings));
} }
class Settings extends EventEmitter { class Settings extends EventEmitter {