mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-09-13 14:22:25 +03:00
Merge ffa6a9c6aa
into 399b1a373e
This commit is contained in:
commit
63a5e6e983
7 changed files with 194 additions and 87 deletions
|
@ -1,4 +1,3 @@
|
|||
import { atom } from 'jotai';
|
||||
import {
|
||||
atomWithLocalStorage,
|
||||
getLocalStorageItem,
|
||||
|
@ -71,22 +70,19 @@ export const getSessionStoreName = (session: Session): SessionStoreName => {
|
|||
};
|
||||
|
||||
export const MATRIX_SESSIONS_KEY = 'matrixSessions';
|
||||
const baseSessionsAtom = atomWithLocalStorage<Sessions>(
|
||||
export const sessionsAtom = atomWithLocalStorage<Sessions>(
|
||||
MATRIX_SESSIONS_KEY,
|
||||
(key) => {
|
||||
const defaultSessions: Sessions = [];
|
||||
const sessions = getLocalStorageItem(key, defaultSessions);
|
||||
|
||||
// Before multi account support session was stored
|
||||
// as multiple item in local storage.
|
||||
// So we need these migration code.
|
||||
const fallbackSession = getFallbackSession();
|
||||
if (fallbackSession) {
|
||||
console.warn('Migrating from a fallback session...');
|
||||
const newSessions: Sessions = [fallbackSession];
|
||||
setLocalStorageItem(key, newSessions);
|
||||
removeFallbackSession();
|
||||
sessions.push(fallbackSession);
|
||||
setLocalStorageItem(key, sessions);
|
||||
return newSessions;
|
||||
}
|
||||
return sessions;
|
||||
|
||||
return getLocalStorageItem(key, []);
|
||||
},
|
||||
(key, value) => {
|
||||
setLocalStorageItem(key, value);
|
||||
|
@ -102,28 +98,3 @@ export type SessionsAction =
|
|||
type: 'DELETE';
|
||||
session: Session;
|
||||
};
|
||||
|
||||
export const sessionsAtom = atom<Sessions, [SessionsAction], undefined>(
|
||||
(get) => get(baseSessionsAtom),
|
||||
(get, set, action) => {
|
||||
if (action.type === 'PUT') {
|
||||
const sessions = [...get(baseSessionsAtom)];
|
||||
const sessionIndex = sessions.findIndex(
|
||||
(session) => session.userId === action.session.userId
|
||||
);
|
||||
if (sessionIndex === -1) {
|
||||
sessions.push(action.session);
|
||||
} else {
|
||||
sessions.splice(sessionIndex, 1, action.session);
|
||||
}
|
||||
set(baseSessionsAtom, sessions);
|
||||
return;
|
||||
}
|
||||
if (action.type === 'DELETE') {
|
||||
const sessions = get(baseSessionsAtom).filter(
|
||||
(session) => session.userId !== action.session.userId
|
||||
);
|
||||
set(baseSessionsAtom, sessions);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
48
src/app/state/utils/atomWithIndexedDB.ts
Normal file
48
src/app/state/utils/atomWithIndexedDB.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { atom, PrimitiveAtom } from 'jotai';
|
||||
import type { SetStateAction } from 'jotai';
|
||||
import { get as getFromDB, set as setInDB } from 'idb-keyval';
|
||||
|
||||
export const setIndexedDBItem = async <T>(key: string, value: T) => {
|
||||
await setInDB(key, value);
|
||||
};
|
||||
|
||||
export const atomWithIndexedDB = <T>(key: string, initialValue: T): PrimitiveAtom<T> => {
|
||||
const channel = new BroadcastChannel(key);
|
||||
|
||||
const baseAtom = atom(initialValue);
|
||||
let isInitialized = false;
|
||||
|
||||
baseAtom.onMount = (setAtom) => {
|
||||
(async () => {
|
||||
const storedValue = await getFromDB<T>(key);
|
||||
if (storedValue !== undefined && !isInitialized) {
|
||||
setAtom(storedValue);
|
||||
}
|
||||
isInitialized = true;
|
||||
})();
|
||||
|
||||
const handleChange = (event: MessageEvent) => {
|
||||
setAtom(event.data);
|
||||
};
|
||||
channel.addEventListener('message', handleChange);
|
||||
return () => {
|
||||
channel.removeEventListener('message', handleChange);
|
||||
};
|
||||
};
|
||||
|
||||
const derivedAtom = atom<T, [SetStateAction<T>], void>(
|
||||
(get) => get(baseAtom),
|
||||
(get, set, update: SetStateAction<T>) => {
|
||||
const currentValue = get(baseAtom);
|
||||
const newValue =
|
||||
typeof update === 'function' ? (update as (prev: T) => T)(currentValue) : update;
|
||||
|
||||
isInitialized = true;
|
||||
set(baseAtom, newValue);
|
||||
setIndexedDBItem(key, newValue);
|
||||
channel.postMessage(newValue);
|
||||
}
|
||||
);
|
||||
|
||||
return derivedAtom;
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
import cons from '../state/cons';
|
||||
import { setLocalStorageItem } from '../../app/state/utils/atomWithLocalStorage';
|
||||
import { Session } from '../../app/state/sessions';
|
||||
|
||||
export function updateLocalStore(
|
||||
accessToken: string,
|
||||
|
@ -6,8 +7,13 @@ export function updateLocalStore(
|
|||
userId: string,
|
||||
baseUrl: string
|
||||
) {
|
||||
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);
|
||||
const newSession: Session = {
|
||||
accessToken,
|
||||
deviceId,
|
||||
userId,
|
||||
baseUrl,
|
||||
fallbackSdkStores: false,
|
||||
};
|
||||
|
||||
setLocalStorageItem('matrixSessions', [newSession]);
|
||||
}
|
||||
|
|
|
@ -1,30 +1,25 @@
|
|||
import { createClient, MatrixClient, IndexedDBStore, IndexedDBCryptoStore } from 'matrix-js-sdk';
|
||||
|
||||
import { cryptoCallbacks } from './state/secretStorageKeys';
|
||||
import { clearNavToActivePathStore } from '../app/state/navToActivePath';
|
||||
|
||||
type Session = {
|
||||
baseUrl: string;
|
||||
accessToken: string;
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
};
|
||||
import { Session, getSessionStoreName } from '../app/state/sessions';
|
||||
|
||||
export const initClient = async (session: Session): Promise<MatrixClient> => {
|
||||
const storeName = getSessionStoreName(session);
|
||||
|
||||
const indexedDBStore = new IndexedDBStore({
|
||||
indexedDB: global.indexedDB,
|
||||
localStorage: global.localStorage,
|
||||
dbName: 'web-sync-store',
|
||||
dbName: storeName.sync,
|
||||
});
|
||||
|
||||
const legacyCryptoStore = new IndexedDBCryptoStore(global.indexedDB, 'crypto-store');
|
||||
const cryptoStore = new IndexedDBCryptoStore(global.indexedDB, storeName.crypto); // 4. USE THE DYNAMIC NAME
|
||||
|
||||
const mx = createClient({
|
||||
baseUrl: session.baseUrl,
|
||||
accessToken: session.accessToken,
|
||||
userId: session.userId,
|
||||
store: indexedDBStore,
|
||||
cryptoStore: legacyCryptoStore,
|
||||
cryptoStore,
|
||||
deviceId: session.deviceId,
|
||||
timelineSupport: true,
|
||||
cryptoCallbacks: cryptoCallbacks as any,
|
||||
|
|
|
@ -1,12 +1,36 @@
|
|||
import cons from './cons';
|
||||
import { Session, Sessions } from '../../app/state/sessions';
|
||||
|
||||
const isAuthenticated = () => localStorage.getItem(cons.secretKey.ACCESS_TOKEN) !== null;
|
||||
/*
|
||||
* Transition code for moving to the multi-account session storage solution
|
||||
*/
|
||||
|
||||
const getSecret = () => ({
|
||||
accessToken: localStorage.getItem(cons.secretKey.ACCESS_TOKEN),
|
||||
deviceId: localStorage.getItem(cons.secretKey.DEVICE_ID),
|
||||
userId: localStorage.getItem(cons.secretKey.USER_ID),
|
||||
baseUrl: localStorage.getItem(cons.secretKey.BASE_URL),
|
||||
});
|
||||
const getActiveSession = (): Session | null => {
|
||||
const sessionsJSON = localStorage.getItem('matrixSessions');
|
||||
if (!sessionsJSON) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const sessions = JSON.parse(sessionsJSON) as Sessions;
|
||||
return sessions[0] || null;
|
||||
} catch (e) {
|
||||
console.error('Failed to parse matrixSessions from localStorage', e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const isAuthenticated = (): boolean => {
|
||||
const session = getActiveSession();
|
||||
return !!session?.accessToken;
|
||||
};
|
||||
|
||||
const getSecret = () => {
|
||||
const session = getActiveSession();
|
||||
return {
|
||||
accessToken: session?.accessToken,
|
||||
deviceId: session?.deviceId,
|
||||
userId: session?.userId,
|
||||
baseUrl: session?.baseUrl,
|
||||
};
|
||||
};
|
||||
|
||||
export { isAuthenticated, getSecret };
|
||||
|
|
|
@ -5,38 +5,16 @@ import { enableMapSet } from 'immer';
|
|||
import '@fontsource/inter/variable.css';
|
||||
import 'folds/dist/style.css';
|
||||
import { configClass, varsClass } from 'folds';
|
||||
import './index.scss';
|
||||
import App from './app/pages/App';
|
||||
import './app/i18n';
|
||||
import { readyServiceWorker } from './serviceWorkerBridge';
|
||||
|
||||
enableMapSet();
|
||||
|
||||
import './index.scss';
|
||||
|
||||
import { trimTrailingSlash } from './app/utils/common';
|
||||
import App from './app/pages/App';
|
||||
|
||||
// import i18n (needs to be bundled ;))
|
||||
import './app/i18n';
|
||||
|
||||
document.body.classList.add(configClass, varsClass);
|
||||
|
||||
// Register Service Worker
|
||||
if ('serviceWorker' in navigator) {
|
||||
const swUrl =
|
||||
import.meta.env.MODE === 'production'
|
||||
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
|
||||
: `/dev-sw.js?dev-sw`;
|
||||
|
||||
navigator.serviceWorker.register(swUrl);
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
if (event.data?.type === 'token' && event.data?.responseKey) {
|
||||
// Get the token for SW.
|
||||
const token = localStorage.getItem('cinny_access_token') ?? undefined;
|
||||
event.source!.postMessage({
|
||||
responseKey: event.data.responseKey,
|
||||
token,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
readyServiceWorker();
|
||||
|
||||
const mountApp = () => {
|
||||
const rootContainer = document.getElementById('root');
|
||||
|
|
85
src/serviceWorkerBridge.ts
Normal file
85
src/serviceWorkerBridge.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { trimTrailingSlash } from './app/utils/common';
|
||||
|
||||
const SESSIONS_KEY = 'matrixSessions';
|
||||
|
||||
function getActiveSessionFromStorage() {
|
||||
try {
|
||||
const sessionsJSON = localStorage.getItem(SESSIONS_KEY);
|
||||
if (!sessionsJSON) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sessions = JSON.parse(sessionsJSON);
|
||||
return sessions[0] || null;
|
||||
} catch (e) {
|
||||
console.error('SW: Error reading or parsing sessions from localStorage', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const readyServiceWorker = () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const isProduction = import.meta.env.MODE === 'production';
|
||||
const swUrl = isProduction
|
||||
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
|
||||
: `/dev-sw.js?dev-sw`;
|
||||
|
||||
const swRegisterOptions: RegistrationOptions = {};
|
||||
if (!isProduction) {
|
||||
swRegisterOptions.type = 'module';
|
||||
}
|
||||
|
||||
const showUpdateAvailablePrompt = (registration: ServiceWorkerRegistration) => {
|
||||
const DONT_SHOW_PROMPT_KEY = 'cinny_dont_show_sw_update_prompt';
|
||||
const userPreference = localStorage.getItem(DONT_SHOW_PROMPT_KEY);
|
||||
|
||||
if (userPreference === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.confirm('A new version of the app is available. Refresh to update?')) {
|
||||
if (registration.waiting) {
|
||||
registration.waiting.postMessage({ type: 'SKIP_WAITING_AND_CLAIM' });
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
navigator.serviceWorker.register(swUrl, swRegisterOptions).then((registration) => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker) {
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
showUpdateAvailablePrompt(registration);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
if (!event.data || !event.source) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.type === 'token' && event.data.id) {
|
||||
const token = getActiveSessionFromStorage().accessToken ?? undefined;
|
||||
event.source.postMessage({
|
||||
replyTo: event.data.id,
|
||||
payload: token,
|
||||
});
|
||||
} else if (event.data.type === 'openRoom' && event.data.id) {
|
||||
/* Example:
|
||||
event.source.postMessage({
|
||||
replyTo: event.data.id,
|
||||
payload: success?,
|
||||
});
|
||||
*/
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue