Revert #8098
This commit is contained in:
		
							parent
							
								
									11d2654ffc
								
							
						
					
					
						commit
						8b9dc962ae
					
				
					 7 changed files with 83 additions and 212 deletions
				
			
		|  | @ -27,7 +27,6 @@ | |||
| 		"compare-versions": "5.0.1", | ||||
| 		"cropperjs": "2.0.0-beta", | ||||
| 		"date-fns": "2.29.3", | ||||
| 		"deepcopy": "2.1.0", | ||||
| 		"escape-regexp": "0.0.1", | ||||
| 		"eventemitter3": "4.0.7", | ||||
| 		"idb-keyval": "6.2.0", | ||||
|  |  | |||
|  | @ -38,7 +38,6 @@ import { reloadChannel } from '@/scripts/unison-reload'; | |||
| import { reactionPicker } from '@/scripts/reaction-picker'; | ||||
| import { getUrlWithoutLoginId } from '@/scripts/login-id'; | ||||
| import { getAccountFromId } from '@/scripts/get-account-from-id'; | ||||
| import { deckStore } from './ui/deck/deck-store'; | ||||
| 
 | ||||
| (async () => { | ||||
| 	console.info(`Misskey v${version}`); | ||||
|  | @ -74,8 +73,6 @@ import { deckStore } from './ui/deck/deck-store'; | |||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	await defaultStore.ready; | ||||
| 
 | ||||
| 	// タッチデバイスでCSSの:hoverを機能させる
 | ||||
| 	document.addEventListener('touchend', () => {}, { passive: true }); | ||||
| 
 | ||||
|  | @ -191,8 +188,6 @@ import { deckStore } from './ui/deck/deck-store'; | |||
| 		splash.remove(); | ||||
| 	}); | ||||
| 
 | ||||
| 	await deckStore.ready; | ||||
| 
 | ||||
| 	// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
 | ||||
| 	// なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する
 | ||||
| 	const rootEl = (() => { | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
| 				this.state[key] = value; | ||||
| 				this.reactiveState[key].value = value; | ||||
| 
 | ||||
| 				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)); | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private load(): Promise<void> { | ||||
| 		return new Promise((resolve, reject) => { | ||||
| 			if ($i) { | ||||
| 				// api関数と循環参照なので一応setTimeoutしておく
 | ||||
| 				window.setTimeout(async () => { | ||||
| 					await defaultStore.ready; | ||||
| 	public set<K extends keyof T>(key: K, value: T[K]['default']): void { | ||||
| 		if (_DEV_) console.log('set', key, 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); | ||||
| 		this.state[key] = value; | ||||
| 		this.reactiveState[key].value = value; | ||||
| 
 | ||||
| 		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; | ||||
| 			} | ||||
| 
 | ||||
| 			resolve(); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 
 | ||||
| 		if (_DEV_) console.log('set', key, rawValue, 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; | ||||
| 				} | ||||
| 			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; | ||||
| 			} | ||||
| 			if (_DEV_) console.log(`set ${key} complete`); | ||||
| 		}); | ||||
| 			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); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| // SafariがBroadcastChannel未実装なのでライブラリを使う
 | ||||
| import { BroadcastChannel } from 'broadcast-channel'; | ||||
| 
 | ||||
| export const reloadChannel = new BroadcastChannel<string | null>('reload'); | ||||
|  |  | |||
|  | @ -303,16 +303,6 @@ export class ColdDeviceStorage { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public static getAll(): Partial<typeof this.default> { | ||||
| 		return (Object.keys(this.default) as (keyof typeof this.default)[]).reduce((acc, key) => { | ||||
| 			const value = localStorage.getItem(PREFIX + key); | ||||
| 			if (value != null) { | ||||
| 				acc[key] = JSON.parse(value); | ||||
| 			} | ||||
| 			return acc; | ||||
| 		}, {} as any); | ||||
| 	} | ||||
| 
 | ||||
| 	public static set<T extends keyof typeof ColdDeviceStorage.default>(key: T, value: typeof ColdDeviceStorage.default[T]): void { | ||||
| 		// 呼び出し側のバグ等で undefined が来ることがある
 | ||||
| 		// undefined を文字列として localStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視
 | ||||
|  |  | |||
|  | @ -11,8 +11,7 @@ | |||
| 		"sourceMap": false, | ||||
| 		"target": "es2017", | ||||
| 		"module": "esnext", | ||||
| 		"moduleResolution": "node", | ||||
| 		"allowSyntheticDefaultImports": true, | ||||
| 		"moduleResolution": "node" | ||||
| 		"removeComments": false, | ||||
| 		"noLib": false, | ||||
| 		"strict": true, | ||||
|  |  | |||
							
								
								
									
										12
									
								
								yarn.lock
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								yarn.lock
									
										
									
									
									
								
							|  | @ -5067,7 +5067,6 @@ __metadata: | |||
|     cross-env: 7.0.3 | ||||
|     cypress: 11.1.0 | ||||
|     date-fns: 2.29.3 | ||||
|     deepcopy: 2.1.0 | ||||
|     escape-regexp: 0.0.1 | ||||
|     eslint: 8.28.0 | ||||
|     eslint-plugin-import: 2.26.0 | ||||
|  | @ -6067,15 +6066,6 @@ __metadata: | |||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "deepcopy@npm:2.1.0": | ||||
|   version: 2.1.0 | ||||
|   resolution: "deepcopy@npm:2.1.0" | ||||
|   dependencies: | ||||
|     type-detect: ^4.0.8 | ||||
|   checksum: 7890ccaa8ad672bdc33d02626d13666bfbb33a68f7c6e8921a348b962b77edc4daf7f1301a789ec74a97e7f0acd611099c5cdd1e8b32ce93287819722f57b92e | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "deepmerge@npm:^4.2.2": | ||||
|   version: 4.2.2 | ||||
|   resolution: "deepmerge@npm:4.2.2" | ||||
|  | @ -16564,7 +16554,7 @@ __metadata: | |||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "type-detect@npm:4.0.8, type-detect@npm:^4.0.8": | ||||
| "type-detect@npm:4.0.8": | ||||
|   version: 4.0.8 | ||||
|   resolution: "type-detect@npm:4.0.8" | ||||
|   checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue