very hacky account switcher poc
This commit is contained in:
parent
fd79ea4b9b
commit
c3be616cc7
8 changed files with 154 additions and 17 deletions
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
44
src/client/state/NamespacedStorage.js
Normal file
44
src/client/state/NamespacedStorage.js
Normal 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;
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue