Skip to content
Snippets Groups Projects
Commit 7b7368b8 authored by tamaina's avatar tamaina
Browse files

wip

parent 32eb7503
No related branches found
No related tags found
No related merge requests found
import { onUnmounted, Ref, ref, watch } from 'vue';
import { $i } from './account';
import { api } from './os';
import { get, set } from './scripts/idb-proxy';
import { stream } from './stream';
type StateDef = Record<string, {
......@@ -8,119 +9,144 @@ 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;
const connection = $i ? stream.useChannel('main') : null;
export class Storage<T extends StateDef> {
public readonly ready: PromiseLike<void>;
public readonly key: string;
public readonly keyForLocalStorage: string;
public readonly deviceStateKeyName: string;
public readonly deviceAccountStateKeyName: string;
public readonly registryCacheKeyName: string;
public readonly def: T;
// TODO: これが実装されたらreadonlyにしたい: https://github.com/microsoft/TypeScript/issues/37487
public readonly state: { [K in keyof T]: T[K]['default'] };
public readonly reactiveState: { [K in keyof T]: Ref<T[K]['default']> };
private connection = stream.useChannel('main');
public readonly state = {} as State<T>;
public readonly reactiveState = {} as ReactiveState<T>;
private nextIdbJob: Promise<any> = Promise.resolve();
private addIdbSetJob<T>(job: () => Promise<T>) {
const promise = this.nextIdbJob.then(job, e => {
console.error('Pizzax failed to save data to idb!', e);
return job();
});
this.nextIdbJob = promise;
return promise;
}
constructor(key: string, def: T) {
this.key = key;
this.keyForLocalStorage = 'pizzax::' + key;
this.deviceStateKeyName = `pizzax::${key}`;
this.deviceAccountStateKeyName = $i ? `pizzax::${key}::${$i.id}` : '';
this.registryCacheKeyName = $i ? `pizzax::${key}::cache::${$i.id}` : '';
this.def = def;
// 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) || '{}') : {};
const state = {};
const reactiveState = {};
for (const [k, v] of Object.entries(def)) {
if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) {
state[k] = deviceState[k];
} else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) {
state[k] = registryCache[k];
} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) {
state[k] = deviceAccountState[k];
} else {
state[k] = v.default;
if (_DEV_) console.log('Use default value', k, v.default);
this.ready = this.init();
}
private init(): Promise<void> {
return new Promise(async (resolve, reject) => {
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]][]) {
if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) {
this.state[k] = deviceState[k];
} else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) {
this.state[k] = registryCache[k];
} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) {
this.state[k] = deviceAccountState[k];
} else {
this.state[k] = v.default;
if (_DEV_) console.log('Use default value', k, v.default);
}
}
}
for (const [k, v] of Object.entries(state)) {
reactiveState[k] = ref(v);
}
this.state = state as any;
this.reactiveState = reactiveState as any;
if ($i) {
// なぜかsetTimeoutしないとapi関数内でエラーになる(おそらく循環参照してることに原因がありそう)
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;
for (const [k, v] of Object.entries(this.state) as [keyof T, T[keyof T]][]) {
this.reactiveState[k] = ref(v);
}
if ($i) {
// なぜかsetTimeoutしないとapi関数内でエラーになる(おそらく循環参照してることに原因がありそう)
setTimeout(() => {
api('i/registry/get-all', { scope: ['client', this.key] })
.then(kvs => {
const cache = {};
for (const [k, v] of Object.entries(this.def)) {
if (v.where === 'account') {
if (Object.prototype.hasOwnProperty.call(kvs, k)) {
this.state[k as keyof T] = kvs[k];
this.reactiveState[k as keyof T].value = kvs[k as string];
cache[k] = kvs[k];
} else {
this.state[k as keyof T] = v.default;
this.reactiveState[k].value = v.default;
}
}
}
return set(this.registryCacheKeyName, cache);
})
.then(() => resolve());
}, 1);
// streamingのuser storage updateイベントを監視して更新
connection?.on('registryUpdated', async ({ scope, key, value }: { scope: string[], key: keyof T, value: T[typeof key]['default'] }) => {
if (scope[1] !== this.key || this.state[key] === value) return;
this.state[key] = value;
this.reactiveState[key].value = value;
const cache = await get(this.registryCacheKeyName);
if (cache[key] !== value) {
cache[key] = value;
await set(this.registryCacheKeyName, cache);
}
localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache));
});
}, 1);
// streamingのuser storage updateイベントを監視して更新
this.connection.on('registryUpdated', ({ scope, key, value }: { scope: string[], key: keyof T, value: T[typeof key]['default'] }) => {
if (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));
}
});
}
}
});
}
public set<K extends keyof T>(key: K, value: T[K]['default']): void {
public set<K extends keyof T>(key: K, value: T[K]['default']): Promise<void> {
if (_DEV_) console.log('set', key, value);
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;
}
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;
return this.addIdbSetJob(async () => {
switch (this.def[key].where) {
case 'device': {
const deviceState = await get(this.deviceStateKeyName) || {};
deviceState[key] = value;
await set(this.deviceStateKeyName, deviceState);
break;
}
case 'deviceAccount': {
if ($i == null) break;
const deviceAccountState = await get(this.deviceAccountStateKeyName) || {};
deviceAccountState[key] = value;
await set(this.deviceAccountStateKeyName, deviceAccountState);
break;
}
case 'account': {
if ($i == null) break;
const cache = await get(this.registryCacheKeyName) || {};
cache[key] = value;
await set(this.registryCacheKeyName, cache);
await 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 {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment