Merge pull request #5349 from urbit/hm/local-storage-versioning

userspace: updated local storage versioning schema
This commit is contained in:
Hunter Miller 2021-10-25 10:47:38 -05:00 committed by GitHub
commit 72a9d90ec1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 104 additions and 30 deletions

2
pkg/grid/.env Normal file
View File

@ -0,0 +1,2 @@
# Change manually to clear local storage once
VITE_LAST_WIPE=2021-10-20

1
pkg/grid/.gitignore vendored
View File

@ -6,4 +6,3 @@ dist-ssr
stats.html
.eslintcache
.vercel
.env

View File

@ -18,6 +18,7 @@
</head>
<body class="text-sm leading-6 font-sans text-gray-900 bg-white antialiased">
<div id="app"></div>
<script type="module" src="/src/storage-wipe.ts"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

8
pkg/grid/src/env.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
interface ImportMetaEnv extends Readonly<Record<string, string>> {
readonly VITE_LAST_WIPE: string;
readonly VITE_STORAGE_VERSION: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@ -4,7 +4,7 @@ import { useProtocolHandling, setLocalState } from '../../state/local';
export function InterfacePrefs() {
const protocolHandling = useProtocolHandling();
const secure = window.location.protocol === 'https:';
const secure = window.location.protocol === 'https:' || window.location.hostname === 'localhost';
const linkHandlingAllowed = secure && 'registerProtocolHandler' in window.navigator;
const toggleProtoHandling = async () => {
if (!protocolHandling && window?.navigator?.registerProtocolHandler) {

View File

@ -24,7 +24,7 @@ async function toggleMentions() {
export const NotificationPrefs = () => {
const doNotDisturb = useSettingsState(selDnd);
const mentions = useHarkStore(selMentions);
const secure = window.location.protocol === 'https:';
const secure = window.location.protocol === 'https:' || window.location.hostname === 'localhost';
return (
<>

View File

@ -11,7 +11,12 @@ import { AppLink } from '../../components/AppLink';
import { ShipName } from '../../components/ShipName';
import { ProviderLink } from '../../components/ProviderLink';
import useDocketState, { ChargesWithDesks, useCharges } from '../../state/docket';
import { getAppHref } from '../../state/util';
import {
clearStorageMigration,
createStorageKey,
getAppHref,
storageVersion
} from '../../state/util';
import useContactState from '../../state/contact';
export interface RecentsStore {
@ -61,8 +66,9 @@ export const useRecentsStore = create<RecentsStore>(
}),
{
whitelist: ['recentApps', 'recentDevs'],
name: `${window.ship}-recents-store`,
version: import.meta.env.VITE_SHORTHASH as any
name: createStorageKey('recents-store'),
version: storageVersion,
migrate: clearStorageMigration
}
)
);

View File

@ -7,7 +7,7 @@ import { persist } from 'zustand/middleware';
import Urbit, { SubscriptionRequestInterface } from '@urbit/http-api';
import { Poke } from '@urbit/api';
import api from './api';
import { useMockData } from './util';
import { clearStorageMigration, createStorageKey, storageVersion, useMockData } from './util';
setAutoFreeze(false);
enablePatches();
@ -73,10 +73,10 @@ export const optReduceState = <S extends Record<string, unknown>, U>(
/* eslint-disable-next-line import/no-mutable-exports */
export let stateStorageKeys: string[] = [];
export const stateStorageKey = (stateName: string) => {
stateName = `${window.ship}-Grid${stateName}State-${import.meta.env.VITE_SHORTHASH as any}`;
stateStorageKeys = [...new Set([...stateStorageKeys, stateName])];
return stateName;
export const stateStorageKey = (stateName: string): string => {
const key = createStorageKey(`${stateName}State`);
stateStorageKeys = [...new Set([...stateStorageKeys, key])];
return key;
};
(window as any).clearStates = () => {
@ -149,7 +149,9 @@ export const createState = <T extends Record<string, unknown>>(
}),
{
blacklist,
name: stateStorageKey(name)
name: stateStorageKey(name),
version: storageVersion,
migrate: clearStorageMigration
}
)
);

View File

@ -1,6 +1,7 @@
import create from 'zustand';
import { persist } from 'zustand/middleware';
import produce from 'immer';
import { clearStorageMigration, createStorageKey, storageVersion } from './util';
interface LocalState {
protocolHandling: boolean;
@ -16,7 +17,9 @@ export const useLocalState = create<LocalState>(
protocolHandling: false
}),
{
name: 'grid-local'
name: createStorageKey('local'),
version: storageVersion,
migrate: clearStorageMigration
}
)
);

View File

@ -50,3 +50,14 @@ export function getDarkColor(color: string): string {
const hslaColor = parseToHsla(color);
return hsla(hslaColor[0], hslaColor[1], 1 - hslaColor[2], 1);
}
export function createStorageKey(name: string): string {
return `~${window.ship}/${window.desk}/${name}`;
}
// for purging storage with version updates
export function clearStorageMigration<T>() {
return {} as T;
}
export const storageVersion = parseInt(import.meta.env.VITE_STORAGE_VERSION, 10);

View File

@ -0,0 +1,12 @@
import { createStorageKey } from './state/util';
const key = createStorageKey(`storage-wipe-${import.meta.env.VITE_LAST_WIPE}`);
const wiped = localStorage.getItem(key);
// Loaded before everything, this clears local storage just once.
// Change VITE_LAST_WIPE in .env to date of wipe
if (!wiped) {
localStorage.clear();
localStorage.setItem(key, 'true');
}

View File

@ -7,13 +7,7 @@ import { execSync } from 'child_process';
// https://vitejs.dev/config/
export default ({ mode }) => {
if (mode !== 'mock') {
// using current commit until release
const GIT_DESC = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim();
process.env.VITE_SHORTHASH = GIT_DESC;
} else {
process.env.VITE_SHORTHASH = '1';
}
process.env.VITE_STORAGE_VERSION = Date.now().toString();
Object.assign(process.env, loadEnv(mode, process.cwd()));
const SHIP_URL = process.env.SHIP_URL || process.env.VITE_SHIP_URL || 'http://localhost:8080';

View File

@ -109,6 +109,8 @@ module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.LANDSCAPE_SHORTHASH': JSON.stringify(GIT_DESC),
'process.env.LANDSCAPE_STORAGE_VERSION': JSON.stringify(Date.now()),
'process.env.LANDSCAPE_LAST_WIPE': JSON.stringify('2021-10-20'),
'process.env.TUTORIAL_HOST': JSON.stringify('~difmex-passed'),
'process.env.TUTORIAL_GROUP': JSON.stringify('beginner-island'),
'process.env.TUTORIAL_CHAT': JSON.stringify('introduce-yourself-7010'),

View File

@ -73,6 +73,8 @@ module.exports = {
new webpack.DefinePlugin({
'process.env.LANDSCAPE_STREAM': JSON.stringify(process.env.LANDSCAPE_STREAM),
'process.env.LANDSCAPE_SHORTHASH': JSON.stringify(GIT_DESC),
'process.env.LANDSCAPE_STORAGE_VERSION': Date.now().toString(),
'process.env.LANDSCAPE_LAST_WIPE': '2021-10-20',
'process.env.TUTORIAL_HOST': JSON.stringify('~difmex-passed'),
'process.env.TUTORIAL_GROUP': JSON.stringify('beginner-island'),
'process.env.TUTORIAL_CHAT': JSON.stringify('introduce-yourself-7010'),

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import './register-sw';
import './storage-wipe';
import App from './views/App';
import './wdyr';

View File

@ -568,3 +568,14 @@ export function toHarkPlace(graph: string, index = '') {
path: toHarkPath(graph, index)
};
}
export function createStorageKey(name: string): string {
return `~${window.ship}/${window.desk}/${name}`;
}
// for purging storage with version updates
export function clearStorageMigration<T>() {
return {} as T;
}
export const storageVersion = parseInt(process.env.LANDSCAPE_STORAGE_VERSION, 10);

View File

@ -6,6 +6,7 @@ import { persist } from 'zustand/middleware';
import Urbit, { SubscriptionRequestInterface, FatalError } from '@urbit/http-api';
import { Poke } from '@urbit/api';
import airlock from '~/logic/api';
import { clearStorageMigration, createStorageKey, storageVersion } from '../lib/util';
setAutoFreeze(false);
enablePatches();
@ -73,9 +74,9 @@ export const optReduceState = <S, U>(
export let stateStorageKeys: string[] = [];
export const stateStorageKey = (stateName: string) => {
stateName = `Landscape${stateName}State-${process.env.LANDSCAPE_SHORTHASH}`;
stateStorageKeys = [...new Set([...stateStorageKeys, stateName])];
return stateName;
const key = createStorageKey(`${stateName}State`);
stateStorageKeys = [...new Set([...stateStorageKeys, key])];
return key;
};
(window as any).clearStates = () => {
@ -147,7 +148,9 @@ export const createState = <T extends {}>(
...(typeof properties === 'function' ? (properties as any)(set, get) : properties)
}), {
blacklist,
name: stateStorageKey(name)
name: stateStorageKey(name),
version: storageVersion,
migrate: clearStorageMigration
}));
export async function doOptimistically<A, S extends {}>(state: UseStore<S & BaseState<S>>, action: A, call: (a: A) => Promise<any>, reduce: ((a: A, fn: S & BaseState<S>) => S & BaseState<S>)[]) {

View File

@ -6,7 +6,7 @@ import { persist } from 'zustand/middleware';
import { BackgroundConfig, LeapCategories, RemoteContentPolicy, TutorialProgress, tutorialProgress } from '~/types/local-update';
import airlock from '~/logic/api';
import { bootstrapApi } from '../api/bootstrap';
import { wait } from '~/logic/lib/util';
import { clearStorageMigration, createStorageKey, storageVersion, wait } from '~/logic/lib/util';
export type SubscriptionStatus = 'connected' | 'disconnected' | 'reconnecting';
@ -136,7 +136,9 @@ const useLocalState = create<LocalStateZus>(persist((set, get) => ({
'prevTutStep', 'nextTutStep', 'tutorialRef', 'setTutorialRef', 'subscription',
'errorCount', 'breaks'
],
name: 'localReducer'
name: createStorageKey('local'),
version: storageVersion,
migrate: clearStorageMigration
}));
function withLocalState<P, S extends keyof LocalState, C extends React.ComponentType<P>>(Component: C, stateMemberKeys?: S[]) {

View File

@ -0,0 +1,12 @@
import { createStorageKey } from './logic/lib/util';
const key = createStorageKey(`storage-wipe-${process.env.LANDSCAPE_LAST_WIPE}`);
const wiped = localStorage.getItem(key);
// Loaded before everything, this clears local storage just once.
// Change VITE_LAST_WIPE in .env to date of wipe
if (!wiped) {
localStorage.clear();
localStorage.setItem(key, 'true');
}

View File

@ -3,5 +3,6 @@ import { PatpNoSig } from '@urbit/api';
declare global {
interface Window {
ship: PatpNoSig;
desk: string;
}
}

View File

@ -5,6 +5,7 @@ import React, { ReactElement, useCallback, useEffect, useState } from 'react';
import create from 'zustand';
import { persist } from 'zustand/middleware';
import { useFileUpload } from '~/logic/lib/useFileUpload';
import { createStorageKey, storageVersion, clearStorageMigration } from '~/logic/lib/util';
import { useOurContact } from '~/logic/state/contact';
import { useGraphTimesent } from '~/logic/state/graph';
import ShareProfile from '~/views/apps/chat/components/ShareProfile';
@ -21,7 +22,6 @@ interface useChatStoreType {
setMessage: (message: string) => void;
}
const unsentKey = 'chat-unsent';
export const useChatStore = create<useChatStoreType>(persist((set, get) => ({
id: '',
message: '',
@ -41,8 +41,10 @@ export const useChatStore = create<useChatStoreType>(persist((set, get) => ({
set({ message, messageStore: store });
}
}), {
name: unsentKey,
whitelist: ['messageStore']
whitelist: ['messageStore'],
name: createStorageKey('chat-unsent'),
version: storageVersion,
migrate: clearStorageMigration
}));
interface ChatPaneProps {