diff options
| author | tamaina <tamaina@hotmail.co.jp> | 2022-11-24 18:27:05 +0900 |
|---|---|---|
| committer | tamaina <tamaina@hotmail.co.jp> | 2022-11-24 18:27:05 +0900 |
| commit | 8b9dc962ae7b04354c65f6e80ad0e9a6bafd57ea (patch) | |
| tree | d491ef0e9aa9e2145e9a47ac20b45bba525694b2 /packages/client/src/pizzax.ts | |
| parent | update lockfile (diff) | |
| download | misskey-8b9dc962ae7b04354c65f6e80ad0e9a6bafd57ea.tar.gz misskey-8b9dc962ae7b04354c65f6e80ad0e9a6bafd57ea.tar.bz2 misskey-8b9dc962ae7b04354c65f6e80ad0e9a6bafd57ea.zip | |
Revert #8098
Diffstat (limited to 'packages/client/src/pizzax.ts')
| -rw-r--r-- | packages/client/src/pizzax.ts | 263 |
1 files changed, 80 insertions, 183 deletions
diff --git a/packages/client/src/pizzax.ts b/packages/client/src/pizzax.ts index c98de098af..642e1f4f7f 100644 --- a/packages/client/src/pizzax.ts +++ b/packages/client/src/pizzax.ts @@ -1,12 +1,8 @@ // PIZZAX --- A lightweight store import { onUnmounted, Ref, ref, watch } from 'vue'; -import { BroadcastChannel } from 'broadcast-channel'; -import deepcopy from 'deepcopy'; import { $i } from './account'; import { api } from './os'; -import { get, set } from './scripts/idb-proxy'; -import { defaultStore } from './store'; import { stream } from './stream'; type StateDef = Record<string, { @@ -14,196 +10,119 @@ type StateDef = Record<string, { default: any; }>; -type State<T extends StateDef> = { [K in keyof T]: T[K]['default']; }; -type ReactiveState<T extends StateDef> = { [K in keyof T]: Ref<T[K]['default']>; }; - type ArrayElement<A> = A extends readonly (infer T)[] ? T : never; -type PizzaxChannelMessage<T extends StateDef> = { - where: 'device' | 'deviceAccount'; - key: keyof T; - value: T[keyof T]['default']; - userId?: string; -}; - const connection = $i && stream.useChannel('main'); export class Storage<T extends StateDef> { - public readonly ready: Promise<void>; - public readonly loaded: Promise<void>; - public readonly key: string; - public readonly deviceStateKeyName: `pizzax::${this['key']}`; - public readonly deviceAccountStateKeyName: `pizzax::${this['key']}::${string}` | ''; - public readonly registryCacheKeyName: `pizzax::${this['key']}::cache::${string}` | ''; + public readonly keyForLocalStorage: string; public readonly def: T; // TODO: これが実装されたらreadonlyにしたい: https://github.com/microsoft/TypeScript/issues/37487 - public readonly state: State<T>; - public readonly reactiveState: ReactiveState<T>; - - private pizzaxChannel: BroadcastChannel<PizzaxChannelMessage<T>>; - - // 簡易的にキューイングして占有ロックとする - private currentIdbJob: Promise<any> = Promise.resolve(); - private addIdbSetJob<T>(job: () => Promise<T>) { - const promise = this.currentIdbJob.then(job, e => { - console.error('Pizzax failed to save data to idb!', e); - return job(); - }); - this.currentIdbJob = promise; - return promise; - } + public readonly state: { [K in keyof T]: T[K]['default'] }; + public readonly reactiveState: { [K in keyof T]: Ref<T[K]['default']> }; constructor(key: string, def: T) { this.key = key; - this.deviceStateKeyName = `pizzax::${key}`; - this.deviceAccountStateKeyName = $i ? `pizzax::${key}::${$i.id}` : ''; - this.registryCacheKeyName = $i ? `pizzax::${key}::cache::${$i.id}` : ''; + this.keyForLocalStorage = 'pizzax::' + key; this.def = def; - this.pizzaxChannel = new BroadcastChannel(`pizzax::${key}`); + // TODO: indexedDBにする + const deviceState = JSON.parse(localStorage.getItem(this.keyForLocalStorage) || '{}'); + const deviceAccountState = $i ? JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::' + $i.id) || '{}') : {}; + const registryCache = $i ? JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::cache::' + $i.id) || '{}') : {}; - this.state = {} as State<T>; - this.reactiveState = {} as ReactiveState<T>; - - for (const [k, v] of Object.entries(def) as [keyof T, T[keyof T]['default']][]) { - this.state[k] = v.default; - this.reactiveState[k] = ref(v.default); - } - - this.ready = this.init(); - this.loaded = this.ready.then(() => this.load()); - } - - private async init(): Promise<void> { - await this.migrate(); - - const deviceState: State<T> = await get(this.deviceStateKeyName) || {}; - const deviceAccountState = $i ? await get(this.deviceAccountStateKeyName) || {} : {}; - const registryCache = $i ? await get(this.registryCacheKeyName) || {} : {}; - - for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) { + const state = {}; + const reactiveState = {}; + for (const [k, v] of Object.entries(def)) { if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) { - this.reactiveState[k].value = this.state[k] = deviceState[k]; + state[k] = deviceState[k]; } else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) { - this.reactiveState[k].value = this.state[k] = registryCache[k]; + state[k] = registryCache[k]; } else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) { - this.reactiveState[k].value = this.state[k] = deviceAccountState[k]; + state[k] = deviceAccountState[k]; } else { - this.reactiveState[k].value = this.state[k] = v.default; + state[k] = v.default; if (_DEV_) console.log('Use default value', k, v.default); } } - - this.pizzaxChannel.addEventListener('message', ({ where, key, value, userId }) => { - // アカウント変更すればunisonReloadが効くため、このreturnが発火することは - // まずないと思うけど一応弾いておく - if (where === 'deviceAccount' && !($i && userId !== $i.id)) return; - this.reactiveState[key].value = this.state[key] = value; - }); - - if ($i) { - // streamingのuser storage updateイベントを監視して更新 - connection?.on('registryUpdated', ({ scope, key, value }: { scope?: string[], key: keyof T, value: T[typeof key]['default'] }) => { - if (!scope || scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return; - - this.reactiveState[key].value = this.state[key] = value; + for (const [k, v] of Object.entries(state)) { + reactiveState[k] = ref(v); + } + this.state = state as any; + this.reactiveState = reactiveState as any; - this.addIdbSetJob(async () => { - const cache = await get(this.registryCacheKeyName); - if (cache[key] !== value) { - cache[key] = value; - await set(this.registryCacheKeyName, cache); + if ($i) { + // なぜかsetTimeoutしないとapi関数内でエラーになる(おそらく循環参照してることに原因がありそう) + window.setTimeout(() => { + api('i/registry/get-all', { scope: ['client', this.key] }).then(kvs => { + const cache = {}; + for (const [k, v] of Object.entries(def)) { + if (v.where === 'account') { + if (Object.prototype.hasOwnProperty.call(kvs, k)) { + state[k] = kvs[k]; + reactiveState[k].value = kvs[k]; + cache[k] = kvs[k]; + } else { + state[k] = v.default; + reactiveState[k].value = v.default; + } + } } + localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache)); }); - }); - } - } + }, 1); + // streamingのuser storage updateイベントを監視して更新 + connection?.on('registryUpdated', ({ scope, key, value }: { scope: string[], key: keyof T, value: T[typeof key]['default'] }) => { + if (scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return; - private load(): Promise<void> { - return new Promise((resolve, reject) => { - if ($i) { - // api関数と循環参照なので一応setTimeoutしておく - window.setTimeout(async () => { - await defaultStore.ready; + this.state[key] = value; + this.reactiveState[key].value = value; - api('i/registry/get-all', { scope: ['client', this.key] }) - .then(kvs => { - const cache: Partial<T> = {}; - for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) { - if (v.where === 'account') { - if (Object.prototype.hasOwnProperty.call(kvs, k)) { - this.reactiveState[k].value = this.state[k] = (kvs as Partial<T>)[k]; - cache[k] = (kvs as Partial<T>)[k]; - } else { - this.reactiveState[k].value = this.state[k] = v.default; - } - } - } - - return set(this.registryCacheKeyName, cache); - }) - .then(() => resolve()); - }, 1); - } - - resolve(); - }); + const cache = JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::cache::' + $i.id) || '{}'); + if (cache[key] !== value) { + cache[key] = value; + localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache)); + } + }); + } } - public set<K extends keyof T>(key: K, value: T[K]['default']): Promise<void> { - // IndexedDBやBroadcastChannelで扱うために単純なオブジェクトにする - // (JSON.parse(JSON.stringify(value))の代わり) - const rawValue = deepcopy(value); + public set<K extends keyof T>(key: K, value: T[K]['default']): void { + if (_DEV_) console.log('set', key, value); - if (_DEV_) console.log('set', key, rawValue, value); + this.state[key] = value; + this.reactiveState[key].value = value; - this.reactiveState[key].value = this.state[key] = rawValue; - - return this.addIdbSetJob(async () => { - if (_DEV_) console.log(`set ${key} start`); - switch (this.def[key].where) { - case 'device': { - this.pizzaxChannel.postMessage({ - where: 'device', - key, - value: rawValue, - }); - const deviceState = await get(this.deviceStateKeyName) || {}; - deviceState[key] = rawValue; - await set(this.deviceStateKeyName, deviceState); - break; - } - case 'deviceAccount': { - if ($i == null) break; - this.pizzaxChannel.postMessage({ - where: 'deviceAccount', - key, - value: rawValue, - userId: $i.id, - }); - const deviceAccountState = await get(this.deviceAccountStateKeyName) || {}; - deviceAccountState[key] = rawValue; - await set(this.deviceAccountStateKeyName, deviceAccountState); - break; - } - case 'account': { - if ($i == null) break; - const cache = await get(this.registryCacheKeyName) || {}; - cache[key] = rawValue; - await set(this.registryCacheKeyName, cache); - await api('i/registry/set', { - scope: ['client', this.key], - key: key.toString(), - value: rawValue, - }); - break; - } + switch (this.def[key].where) { + case 'device': { + const deviceState = JSON.parse(localStorage.getItem(this.keyForLocalStorage) || '{}'); + deviceState[key] = value; + localStorage.setItem(this.keyForLocalStorage, JSON.stringify(deviceState)); + break; } - if (_DEV_) console.log(`set ${key} complete`); - }); + case 'deviceAccount': { + if ($i == null) break; + const deviceAccountState = JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::' + $i.id) || '{}'); + deviceAccountState[key] = value; + localStorage.setItem(this.keyForLocalStorage + '::' + $i.id, JSON.stringify(deviceAccountState)); + break; + } + case 'account': { + if ($i == null) break; + const cache = JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::cache::' + $i.id) || '{}'); + cache[key] = value; + localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache)); + api('i/registry/set', { + scope: ['client', this.key], + key: key, + value: value, + }); + break; + } + } } public push<K extends keyof T>(key: K, value: ArrayElement<T[K]['default']>): void { @@ -213,7 +132,6 @@ export class Storage<T extends StateDef> { public reset(key: keyof T) { this.set(key, this.def[key].default); - return this.def[key].default; } /** @@ -248,25 +166,4 @@ export class Storage<T extends StateDef> { }, }; } - - // localStorage => indexedDBのマイグレーション - private async migrate() { - const deviceState = localStorage.getItem(this.deviceStateKeyName); - if (deviceState) { - await set(this.deviceStateKeyName, JSON.parse(deviceState)); - localStorage.removeItem(this.deviceStateKeyName); - } - - const deviceAccountState = $i && localStorage.getItem(this.deviceAccountStateKeyName); - if ($i && deviceAccountState) { - await set(this.deviceAccountStateKeyName, JSON.parse(deviceAccountState)); - localStorage.removeItem(this.deviceAccountStateKeyName); - } - - const registryCache = $i && localStorage.getItem(this.registryCacheKeyName); - if ($i && registryCache) { - await set(this.registryCacheKeyName, JSON.parse(registryCache)); - localStorage.removeItem(this.registryCacheKeyName); - } - } } |