refactor: localStorageのaccountsはindexedDBで保持するように (#7609)
* accountsストアはindexedDBで保持するように * fix lint * fix indexeddb available detection * remove debugging code * fix lint * resolve https://github.com/misskey-dev/misskey/pull/7609/files/ba756204b77ce6e1189b8443e9641f2d02119621#diff-f565878e8202f0037b830c780b7c0932dc1bb5fd3d05ede14d72d10efbc3740c Firefoxでの動作を改善 * fix lint * fix lint * add changelog
This commit is contained in:
		
							parent
							
								
									60e768436e
								
							
						
					
					
						commit
						fc56b12690
					
				
					 9 changed files with 112 additions and 28 deletions
				
			
		|  | @ -11,6 +11,7 @@ | ||||||
| 
 | 
 | ||||||
| ### Improvements | ### Improvements | ||||||
| - 依存関係の更新 | - 依存関係の更新 | ||||||
|  | - localStorageのaccountsはindexedDBで保持するように | ||||||
| 
 | 
 | ||||||
| ### Bugfixes | ### Bugfixes | ||||||
| - チャンネルを作成しているとアカウントを削除できないのを修正 | - チャンネルを作成しているとアカウントを削除できないのを修正 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
|  | import { get, set } from '@client/scripts/idb-proxy'; | ||||||
| import { reactive } from 'vue'; | import { reactive } from 'vue'; | ||||||
| import { apiUrl } from '@client/config'; | import { apiUrl } from '@client/config'; | ||||||
| import { waiting } from '@client/os'; | import { waiting } from '@client/os'; | ||||||
| import { unisonReload } from '@client/scripts/unison-reload'; | import { unisonReload, reloadChannel } from '@client/scripts/unison-reload'; | ||||||
| 
 | 
 | ||||||
| // TODO: 他のタブと永続化されたstateを同期
 | // TODO: 他のタブと永続化されたstateを同期
 | ||||||
| 
 | 
 | ||||||
|  | @ -17,22 +18,43 @@ const data = localStorage.getItem('account'); | ||||||
| // TODO: 外部からはreadonlyに
 | // TODO: 外部からはreadonlyに
 | ||||||
| export const $i = data ? reactive(JSON.parse(data) as Account) : null; | export const $i = data ? reactive(JSON.parse(data) as Account) : null; | ||||||
| 
 | 
 | ||||||
| export function signout() { | export async function signout() { | ||||||
|  | 	waiting(); | ||||||
| 	localStorage.removeItem('account'); | 	localStorage.removeItem('account'); | ||||||
|  | 
 | ||||||
|  | 	//#region Remove account
 | ||||||
|  | 	const accounts = await getAccounts(); | ||||||
|  | 	accounts.splice(accounts.findIndex(x => x.id === $i.id), 1); | ||||||
|  | 	set('accounts', accounts); | ||||||
|  | 	//#endregion
 | ||||||
|  | 
 | ||||||
|  | 	//#region Remove push notification registration
 | ||||||
|  | 	const registration = await navigator.serviceWorker.ready; | ||||||
|  | 	const push = await registration.pushManager.getSubscription(); | ||||||
|  | 	if (!push) return; | ||||||
|  | 	await fetch(`${apiUrl}/sw/unregister`, { | ||||||
|  | 		method: 'POST', | ||||||
|  | 		body: JSON.stringify({ | ||||||
|  | 			i: $i.token, | ||||||
|  | 			endpoint: push.endpoint, | ||||||
|  | 		}), | ||||||
|  | 	}); | ||||||
|  | 	//#endregion
 | ||||||
|  | 
 | ||||||
| 	document.cookie = `igi=; path=/`; | 	document.cookie = `igi=; path=/`; | ||||||
| 	location.href = '/'; | 
 | ||||||
|  | 	if (accounts.length > 0) login(accounts[0].token); | ||||||
|  | 	else unisonReload(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getAccounts() { | export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> { | ||||||
| 	const accountsData = localStorage.getItem('accounts'); | 	return (await get('accounts')) || []; | ||||||
| 	const accounts: { id: Account['id'], token: Account['token'] }[] = accountsData ? JSON.parse(accountsData) : []; |  | ||||||
| 	return accounts; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function addAccount(id: Account['id'], token: Account['token']) { | export async function addAccount(id: Account['id'], token: Account['token']) { | ||||||
| 	const accounts = getAccounts(); | 	const accounts = await getAccounts(); | ||||||
| 	if (!accounts.some(x => x.id === id)) { | 	if (!accounts.some(x => x.id === id)) { | ||||||
| 		localStorage.setItem('accounts', JSON.stringify(accounts.concat([{ id, token }]))); | 		await set('accounts', accounts.concat([{ id, token }])); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -47,7 +69,7 @@ function fetchAccount(token): Promise<Account> { | ||||||
| 		}) | 		}) | ||||||
| 		.then(res => { | 		.then(res => { | ||||||
| 			// When failed to authenticate user
 | 			// When failed to authenticate user
 | ||||||
| 			if (res.status >= 400 && res.status < 500) { | 			if (res.status !== 200 && res.status < 500) { | ||||||
| 				return signout(); | 				return signout(); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -69,15 +91,22 @@ export function updateAccount(data) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function refreshAccount() { | export function refreshAccount() { | ||||||
| 	fetchAccount($i.token).then(updateAccount); | 	return fetchAccount($i.token).then(updateAccount); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function login(token: Account['token']) { | export async function login(token: Account['token'], redirect?: string) { | ||||||
| 	waiting(); | 	waiting(); | ||||||
| 	if (_DEV_) console.log('logging as token ', token); | 	if (_DEV_) console.log('logging as token ', token); | ||||||
| 	const me = await fetchAccount(token); | 	const me = await fetchAccount(token); | ||||||
| 	localStorage.setItem('account', JSON.stringify(me)); | 	localStorage.setItem('account', JSON.stringify(me)); | ||||||
| 	addAccount(me.id, token); | 	await addAccount(me.id, token); | ||||||
|  | 
 | ||||||
|  | 	if (redirect) { | ||||||
|  | 		reloadChannel.postMessage('reload'); | ||||||
|  | 		location.href = redirect; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	unisonReload(); | 	unisonReload(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,15 @@ | ||||||
| 
 | 
 | ||||||
| import '@client/style.scss'; | import '@client/style.scss'; | ||||||
| 
 | 
 | ||||||
|  | //#region account indexedDB migration
 | ||||||
|  | import { set } from '@client/scripts/idb-proxy'; | ||||||
|  | 
 | ||||||
|  | if (localStorage.getItem('accounts') != null) { | ||||||
|  | 	set('accounts', JSON.parse(localStorage.getItem('accounts'))); | ||||||
|  | 	localStorage.removeItem('accounts'); | ||||||
|  | } | ||||||
|  | //#endregion
 | ||||||
|  | 
 | ||||||
| import * as Sentry from '@sentry/browser'; | import * as Sentry from '@sentry/browser'; | ||||||
| import { Integrations } from '@sentry/tracing'; | import { Integrations } from '@sentry/tracing'; | ||||||
| import { computed, createApp, watch, markRaw } from 'vue'; | import { computed, createApp, watch, markRaw } from 'vue'; | ||||||
|  |  | ||||||
|  | @ -48,10 +48,10 @@ export default defineComponent({ | ||||||
| 				title: this.$ts.accounts, | 				title: this.$ts.accounts, | ||||||
| 				icon: 'fas fa-users', | 				icon: 'fas fa-users', | ||||||
| 			}, | 			}, | ||||||
| 			storedAccounts: getAccounts().filter(x => x.id !== this.$i.id), | 			storedAccounts: getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)), | ||||||
| 			accounts: null, | 			accounts: null, | ||||||
| 			init: () => os.api('users/show', { | 			init: async () => os.api('users/show', { | ||||||
| 				userIds: this.storedAccounts.map(x => x.id) | 				userIds: (await this.storedAccounts).map(x => x.id) | ||||||
| 			}).then(accounts => { | 			}).then(accounts => { | ||||||
| 				this.accounts = accounts; | 				this.accounts = accounts; | ||||||
| 			}), | 			}), | ||||||
|  | @ -104,8 +104,8 @@ export default defineComponent({ | ||||||
| 			}, 'closed'); | 			}, 'closed'); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		switchAccount(account: any) { | 		async switchAccount(account: any) { | ||||||
| 			const storedAccounts = getAccounts(); | 			const storedAccounts = await getAccounts(); | ||||||
| 			const token = storedAccounts.find(x => x.id === account.id).token; | 			const token = storedAccounts.find(x => x.id === account.id).token; | ||||||
| 			this.switchAccountWithToken(token); | 			this.switchAccountWithToken(token); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								src/client/scripts/get-account-from-id.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/client/scripts/get-account-from-id.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | import { get } from '@client/scripts/idb-proxy'; | ||||||
|  | 
 | ||||||
|  | export async function getAccountFromId(id: string) { | ||||||
|  | 	const accounts = await get('accounts') as { token: string; id: string; }[]; | ||||||
|  | 	if (!accounts) console.log('Accounts are not recorded'); | ||||||
|  | 	return accounts.find(e => e.id === id); | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/client/scripts/idb-proxy.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/client/scripts/idb-proxy.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | // FirefoxのプライベートモードなどではindexedDBが使用不可能なので、
 | ||||||
|  | // indexedDBが使えない環境ではlocalStorageを使う
 | ||||||
|  | import { | ||||||
|  | 	get as iget, | ||||||
|  | 	set as iset, | ||||||
|  | 	del as idel, | ||||||
|  | 	createStore, | ||||||
|  | } from 'idb-keyval'; | ||||||
|  | 
 | ||||||
|  | const fallbackName = (key: string) => `idbfallback::${key}`; | ||||||
|  | 
 | ||||||
|  | let idbAvailable = typeof window !== 'undefined' ? !!window.indexedDB : true; | ||||||
|  | 
 | ||||||
|  | if (idbAvailable) { | ||||||
|  | 	try { | ||||||
|  | 		await createStore('keyval-store', 'keyval'); | ||||||
|  | 	} catch (e) { | ||||||
|  | 		console.error('idb open error', e); | ||||||
|  | 		idbAvailable = false; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if (!idbAvailable) console.error('indexedDB is unavailable. It will use localStorage.'); | ||||||
|  | 
 | ||||||
|  | export async function get(key: string) { | ||||||
|  | 	if (idbAvailable) return iget(key); | ||||||
|  | 	return JSON.parse(localStorage.getItem(fallbackName(key))); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function set(key: string, val: any) { | ||||||
|  | 	if (idbAvailable) return iset(key, val); | ||||||
|  | 	return localStorage.setItem(fallbackName(key), JSON.stringify(val)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function del(key: string) { | ||||||
|  | 	if (idbAvailable) return idel(key); | ||||||
|  | 	return localStorage.removeItem(fallbackName(key)); | ||||||
|  | } | ||||||
|  | @ -135,7 +135,7 @@ export default defineComponent({ | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		async openAccountMenu(ev) { | 		async openAccountMenu(ev) { | ||||||
| 			const storedAccounts = getAccounts().filter(x => x.id !== this.$i.id); | 			const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)); | ||||||
| 			const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); | 			const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); | ||||||
| 
 | 
 | ||||||
| 			const accountItemPromises = storedAccounts.map(a => new Promise(res => { | 			const accountItemPromises = storedAccounts.map(a => new Promise(res => { | ||||||
|  | @ -195,8 +195,8 @@ export default defineComponent({ | ||||||
| 			}, 'closed'); | 			}, 'closed'); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		switchAccount(account: any) { | 		async switchAccount(account: any) { | ||||||
| 			const storedAccounts = getAccounts(); | 			const storedAccounts = await getAccounts(); | ||||||
| 			const token = storedAccounts.find(x => x.id === account.id).token; | 			const token = storedAccounts.find(x => x.id === account.id).token; | ||||||
| 			this.switchAccountWithToken(token); | 			this.switchAccountWithToken(token); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -101,7 +101,7 @@ export default defineComponent({ | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		async openAccountMenu(ev) { | 		async openAccountMenu(ev) { | ||||||
| 			const storedAccounts = getAccounts().filter(x => x.id !== this.$i.id); | 			const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)); | ||||||
| 			const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); | 			const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); | ||||||
| 
 | 
 | ||||||
| 			const accountItemPromises = storedAccounts.map(a => new Promise(res => { | 			const accountItemPromises = storedAccounts.map(a => new Promise(res => { | ||||||
|  | @ -161,8 +161,8 @@ export default defineComponent({ | ||||||
| 			}, 'closed'); | 			}, 'closed'); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		switchAccount(account: any) { | 		async switchAccount(account: any) { | ||||||
| 			const storedAccounts = getAccounts(); | 			const storedAccounts = await getAccounts(); | ||||||
| 			const token = storedAccounts.find(x => x.id === account.id).token; | 			const token = storedAccounts.find(x => x.id === account.id).token; | ||||||
| 			this.switchAccountWithToken(token); | 			this.switchAccountWithToken(token); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -121,7 +121,7 @@ export default defineComponent({ | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		async openAccountMenu(ev) { | 		async openAccountMenu(ev) { | ||||||
| 			const storedAccounts = getAccounts().filter(x => x.id !== this.$i.id); | 			const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)); | ||||||
| 			const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); | 			const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); | ||||||
| 
 | 
 | ||||||
| 			const accountItemPromises = storedAccounts.map(a => new Promise(res => { | 			const accountItemPromises = storedAccounts.map(a => new Promise(res => { | ||||||
|  | @ -181,8 +181,8 @@ export default defineComponent({ | ||||||
| 			}, 'closed'); | 			}, 'closed'); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		switchAccount(account: any) { | 		async switchAccount(account: any) { | ||||||
| 			const storedAccounts = getAccounts(); | 			const storedAccounts = await getAccounts(); | ||||||
| 			const token = storedAccounts.find(x => x.id === account.id).token; | 			const token = storedAccounts.find(x => x.id === account.id).token; | ||||||
| 			this.switchAccountWithToken(token); | 			this.switchAccountWithToken(token); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue