nicolium: add migrations for keys used for data storage
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -12,7 +12,7 @@ const FRONTEND_CONFIG_REQUEST_FAIL = 'FRONTEND_CONFIG_REQUEST_FAIL' as const;
|
||||
const FRONTEND_CONFIG_REMEMBER_SUCCESS = 'FRONTEND_CONFIG_REMEMBER_SUCCESS' as const;
|
||||
|
||||
const rememberFrontendConfig = (host: string | null) => (dispatch: AppDispatch) =>
|
||||
KVStore.getItemOrError(`plfe_config:${host}`)
|
||||
KVStore.getItemOrError(`frontendConfig:${host}`)
|
||||
.then((frontendConfig) => {
|
||||
dispatch<FrontendConfigAction>({
|
||||
type: FRONTEND_CONFIG_REMEMBER_SUCCESS,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createPushSubscription } from '@/actions/push-subscriptions';
|
||||
import { pushNotificationsSetting } from '@/settings';
|
||||
import { pushNotificationsSettings } from '@/settings';
|
||||
import { getVapidKey } from '@/utils/auth';
|
||||
import { decode as decodeBase64 } from '@/utils/base64';
|
||||
|
||||
@ -55,7 +55,7 @@ const sendSubscriptionToBackend =
|
||||
const params = { subscription, data: { alerts } };
|
||||
|
||||
if (me) {
|
||||
const data = pushNotificationsSetting.get(me);
|
||||
const data = pushNotificationsSettings.get(me);
|
||||
if (data) {
|
||||
params.data = data;
|
||||
}
|
||||
@ -124,7 +124,7 @@ const register = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!(subscription instanceof PushSubscription)) {
|
||||
dispatch(setSubscription(subscription));
|
||||
if (me) {
|
||||
pushNotificationsSetting.set(me, { alerts: subscription.alerts });
|
||||
pushNotificationsSettings.set(me, { alerts: subscription.alerts });
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -141,7 +141,7 @@ const register = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(clearSubscription());
|
||||
|
||||
if (me) {
|
||||
pushNotificationsSetting.remove(me);
|
||||
pushNotificationsSettings.remove(me);
|
||||
}
|
||||
|
||||
return getRegistration().then(getPushSubscription).then(unsubscribe);
|
||||
|
||||
@ -11,6 +11,7 @@ declare global {
|
||||
}
|
||||
|
||||
import './polyfills';
|
||||
import '@/storage/migrate-legacy-data';
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
|
||||
@ -97,8 +97,8 @@ const buildKey = (parts: string[]) => parts.join(':');
|
||||
|
||||
// For subdirectory support
|
||||
const NAMESPACE = trim(BuildConfig.FE_SUBDIRECTORY, '/')
|
||||
? `pl-fe@${BuildConfig.FE_SUBDIRECTORY}`
|
||||
: 'pl-fe';
|
||||
? `nicolium@${BuildConfig.FE_SUBDIRECTORY}`
|
||||
: 'nicolium';
|
||||
|
||||
const STORAGE_KEY = buildKey([NAMESPACE, 'auth']);
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ const preloadImport = (state: Record<string, any>, action: Record<string, any>)
|
||||
|
||||
const persistFrontendConfig = (frontendConfig: PartialFrontendConfig, host: string) => {
|
||||
if (host) {
|
||||
KVStore.setItem(`plfe_config:${host}`, frontendConfig).catch(console.error);
|
||||
KVStore.setItem(`frontendConfig:${host}`, frontendConfig).catch(console.error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -45,6 +45,6 @@ class Settings {
|
||||
}
|
||||
|
||||
/** Remember push notification settings. */
|
||||
const pushNotificationsSetting = new Settings('plfe_push_notification_data');
|
||||
const pushNotificationsSettings = new Settings('nicolium:pushNotificationSettings');
|
||||
|
||||
export { pushNotificationsSetting };
|
||||
export { pushNotificationsSettings };
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import localforage from 'localforage';
|
||||
|
||||
import { migrationComplete } from './migrate-legacy-data';
|
||||
|
||||
interface IKVStore extends LocalForage {
|
||||
getItemOrError: (key: string) => Promise<any>;
|
||||
}
|
||||
@ -7,12 +9,18 @@ interface IKVStore extends LocalForage {
|
||||
// localForage
|
||||
// https://localforage.github.io/localForage/#settings-api-config
|
||||
const KVStore = localforage.createInstance({
|
||||
name: 'pl-fe',
|
||||
name: 'nicolium',
|
||||
description: 'Nicolium offline data store',
|
||||
driver: localforage.INDEXEDDB,
|
||||
storeName: 'keyvaluepairs',
|
||||
}) as IKVStore;
|
||||
|
||||
const originalGetItem = KVStore.getItem.bind(KVStore);
|
||||
KVStore.getItem = async (...args) => {
|
||||
await migrationComplete;
|
||||
return originalGetItem(...args);
|
||||
};
|
||||
|
||||
// localForage returns 'null' when a key isn't found.
|
||||
// In the Redux action flow, we want it to fail harder.
|
||||
KVStore.getItemOrError = (key: string) =>
|
||||
|
||||
95
packages/nicolium/src/storage/migrate-legacy-data.ts
Normal file
95
packages/nicolium/src/storage/migrate-legacy-data.ts
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Migrate data from the legacy `pl-fe` namespaces to the new ones using `nicolium`.
|
||||
* Includes synchronous migrations for localStorage (ran on import) and async for IndexedDB.
|
||||
* Made a hack in @/storage/kv-store to delay getItem calls until the async migration is complete to avoid race conditions.
|
||||
* Will remove this migration handling in like a month or so.
|
||||
*/
|
||||
|
||||
import localforage from 'localforage';
|
||||
import trim from 'lodash/trim';
|
||||
|
||||
import { FE_SUBDIRECTORY } from '@/build-config';
|
||||
|
||||
// synchronous localStorage migrations
|
||||
|
||||
const migrateLocalStorageKey = (oldKey: string, newKey: string) => {
|
||||
const value = localStorage.getItem(oldKey);
|
||||
if (value !== null && localStorage.getItem(newKey) === null) {
|
||||
localStorage.setItem(newKey, value);
|
||||
localStorage.removeItem(oldKey);
|
||||
}
|
||||
};
|
||||
|
||||
const migrateAuthStorage = () => {
|
||||
// subdirectory support, see @/reducers/auth
|
||||
const subdir = trim(FE_SUBDIRECTORY, '/');
|
||||
const oldNamespace = subdir ? `pl-fe@${FE_SUBDIRECTORY}` : 'pl-fe';
|
||||
const newNamespace = subdir ? `nicolium@${FE_SUBDIRECTORY}` : 'nicolium';
|
||||
|
||||
migrateLocalStorageKey(`${oldNamespace}:auth`, `${newNamespace}:auth`);
|
||||
};
|
||||
|
||||
const migratePushNotificationSettings = () => {
|
||||
const keysToMigrate: string[] = [];
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key?.startsWith('plfe_push_notification_data')) {
|
||||
keysToMigrate.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const oldKey of keysToMigrate) {
|
||||
const newKey = oldKey.replace(
|
||||
'plfe_push_notification_data',
|
||||
'nicolium:pushNotificationSettings',
|
||||
);
|
||||
migrateLocalStorageKey(oldKey, newKey);
|
||||
}
|
||||
};
|
||||
|
||||
migrateAuthStorage();
|
||||
migratePushNotificationSettings();
|
||||
|
||||
// async migrations
|
||||
|
||||
const migrateIndexedDB = async () => {
|
||||
const oldStore = localforage.createInstance({
|
||||
name: 'pl-fe',
|
||||
driver: localforage.INDEXEDDB,
|
||||
storeName: 'keyvaluepairs',
|
||||
});
|
||||
|
||||
const newStore = localforage.createInstance({
|
||||
name: 'nicolium',
|
||||
driver: localforage.INDEXEDDB,
|
||||
storeName: 'keyvaluepairs',
|
||||
});
|
||||
|
||||
try {
|
||||
const oldKeys = await oldStore.keys();
|
||||
const newKeys = await newStore.keys();
|
||||
|
||||
if (oldKeys.length === 0 || newKeys.length > 0) {
|
||||
await oldStore.dropInstance({ name: 'pl-fe' });
|
||||
return;
|
||||
}
|
||||
|
||||
for (const oldKey of oldKeys) {
|
||||
const value = await oldStore.getItem(oldKey);
|
||||
|
||||
const newKey = oldKey.startsWith('plfe_config:')
|
||||
? oldKey.replace('plfe_config:', 'frontendConfig:')
|
||||
: oldKey;
|
||||
await newStore.setItem(newKey, value);
|
||||
}
|
||||
|
||||
await oldStore.dropInstance({ name: 'pl-fe' });
|
||||
} catch (e) {
|
||||
console.error('Failed to migrate IndexedDB data', e);
|
||||
}
|
||||
};
|
||||
|
||||
const migrationComplete = migrateIndexedDB();
|
||||
|
||||
export { migrationComplete };
|
||||
Reference in New Issue
Block a user