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 | ||||
| - 依存関係の更新 | ||||
| - localStorageのaccountsはindexedDBで保持するように | ||||
| 
 | ||||
| ### Bugfixes | ||||
| - チャンネルを作成しているとアカウントを削除できないのを修正 | ||||
|  |  | |||
|  | @ -1,7 +1,8 @@ | |||
| import { get, set } from '@client/scripts/idb-proxy'; | ||||
| import { reactive } from 'vue'; | ||||
| import { apiUrl } from '@client/config'; | ||||
| import { waiting } from '@client/os'; | ||||
| import { unisonReload } from '@client/scripts/unison-reload'; | ||||
| import { unisonReload, reloadChannel } from '@client/scripts/unison-reload'; | ||||
| 
 | ||||
| // TODO: 他のタブと永続化されたstateを同期
 | ||||
| 
 | ||||
|  | @ -17,22 +18,43 @@ const data = localStorage.getItem('account'); | |||
| // TODO: 外部からはreadonlyに
 | ||||
| export const $i = data ? reactive(JSON.parse(data) as Account) : null; | ||||
| 
 | ||||
| export function signout() { | ||||
| export async function signout() { | ||||
| 	waiting(); | ||||
| 	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=/`; | ||||
| 	location.href = '/'; | ||||
| 
 | ||||
| 	if (accounts.length > 0) login(accounts[0].token); | ||||
| 	else unisonReload(); | ||||
| } | ||||
| 
 | ||||
| export function getAccounts() { | ||||
| 	const accountsData = localStorage.getItem('accounts'); | ||||
| 	const accounts: { id: Account['id'], token: Account['token'] }[] = accountsData ? JSON.parse(accountsData) : []; | ||||
| 	return accounts; | ||||
| export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> { | ||||
| 	return (await get('accounts')) || []; | ||||
| } | ||||
| 
 | ||||
| export function addAccount(id: Account['id'], token: Account['token']) { | ||||
| 	const accounts = getAccounts(); | ||||
| export async function addAccount(id: Account['id'], token: Account['token']) { | ||||
| 	const accounts = await getAccounts(); | ||||
| 	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 => { | ||||
| 			// When failed to authenticate user
 | ||||
| 			if (res.status >= 400 && res.status < 500) { | ||||
| 			if (res.status !== 200 && res.status < 500) { | ||||
| 				return signout(); | ||||
| 			} | ||||
| 
 | ||||
|  | @ -69,15 +91,22 @@ export function updateAccount(data) { | |||
| } | ||||
| 
 | ||||
| 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(); | ||||
| 	if (_DEV_) console.log('logging as token ', token); | ||||
| 	const me = await fetchAccount(token); | ||||
| 	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(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,6 +4,15 @@ | |||
| 
 | ||||
| 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 { Integrations } from '@sentry/tracing'; | ||||
| import { computed, createApp, watch, markRaw } from 'vue'; | ||||
|  |  | |||
|  | @ -48,10 +48,10 @@ export default defineComponent({ | |||
| 				title: this.$ts.accounts, | ||||
| 				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, | ||||
| 			init: () => os.api('users/show', { | ||||
| 				userIds: this.storedAccounts.map(x => x.id) | ||||
| 			init: async () => os.api('users/show', { | ||||
| 				userIds: (await this.storedAccounts).map(x => x.id) | ||||
| 			}).then(accounts => { | ||||
| 				this.accounts = accounts; | ||||
| 			}), | ||||
|  | @ -104,8 +104,8 @@ export default defineComponent({ | |||
| 			}, 'closed'); | ||||
| 		}, | ||||
| 
 | ||||
| 		switchAccount(account: any) { | ||||
| 			const storedAccounts = getAccounts(); | ||||
| 		async switchAccount(account: any) { | ||||
| 			const storedAccounts = await getAccounts(); | ||||
| 			const token = storedAccounts.find(x => x.id === account.id).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) { | ||||
| 			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 accountItemPromises = storedAccounts.map(a => new Promise(res => { | ||||
|  | @ -195,8 +195,8 @@ export default defineComponent({ | |||
| 			}, 'closed'); | ||||
| 		}, | ||||
| 
 | ||||
| 		switchAccount(account: any) { | ||||
| 			const storedAccounts = getAccounts(); | ||||
| 		async switchAccount(account: any) { | ||||
| 			const storedAccounts = await getAccounts(); | ||||
| 			const token = storedAccounts.find(x => x.id === account.id).token; | ||||
| 			this.switchAccountWithToken(token); | ||||
| 		}, | ||||
|  |  | |||
|  | @ -101,7 +101,7 @@ export default defineComponent({ | |||
| 		}, | ||||
| 
 | ||||
| 		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 accountItemPromises = storedAccounts.map(a => new Promise(res => { | ||||
|  | @ -161,8 +161,8 @@ export default defineComponent({ | |||
| 			}, 'closed'); | ||||
| 		}, | ||||
| 
 | ||||
| 		switchAccount(account: any) { | ||||
| 			const storedAccounts = getAccounts(); | ||||
| 		async switchAccount(account: any) { | ||||
| 			const storedAccounts = await getAccounts(); | ||||
| 			const token = storedAccounts.find(x => x.id === account.id).token; | ||||
| 			this.switchAccountWithToken(token); | ||||
| 		}, | ||||
|  |  | |||
|  | @ -121,7 +121,7 @@ export default defineComponent({ | |||
| 		}, | ||||
| 
 | ||||
| 		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 accountItemPromises = storedAccounts.map(a => new Promise(res => { | ||||
|  | @ -181,8 +181,8 @@ export default defineComponent({ | |||
| 			}, 'closed'); | ||||
| 		}, | ||||
| 
 | ||||
| 		switchAccount(account: any) { | ||||
| 			const storedAccounts = getAccounts(); | ||||
| 		async switchAccount(account: any) { | ||||
| 			const storedAccounts = await getAccounts(); | ||||
| 			const token = storedAccounts.find(x => x.id === account.id).token; | ||||
| 			this.switchAccountWithToken(token); | ||||
| 		}, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue