mirror of
https://github.com/immich-app/immich.git
synced 2025-05-01 20:42:12 +02:00
refactor: event manager (#17862)
* refactor: event manager * refactor: event manager
This commit is contained in:
parent
e822e3eca9
commit
d0014bdf94
12 changed files with 127 additions and 47 deletions
e2e/src/web/specs
web/src
lib
components/shared-components/navigation-bar
stores
auth-manager.svelte.tsevent-manager.svelte.tsfolders.svelte.tsmemory.store.svelte.tssearch.svelte.tsuser.store.tsuser.svelte.tswebsocket.ts
utils
routes/auth/change-password
|
@ -25,7 +25,7 @@ test.describe('Registration', () => {
|
|||
|
||||
// login
|
||||
await expect(page).toHaveTitle(/Login/);
|
||||
await page.goto('/auth/login');
|
||||
await page.goto('/auth/login?autoLaunch=0');
|
||||
await page.getByLabel('Email').fill('admin@immich.app');
|
||||
await page.getByLabel('Password').fill('password');
|
||||
await page.getByRole('button', { name: 'Login' }).click();
|
||||
|
@ -59,7 +59,7 @@ test.describe('Registration', () => {
|
|||
await context.clearCookies();
|
||||
|
||||
// login
|
||||
await page.goto('/auth/login');
|
||||
await page.goto('/auth/login?autoLaunch=0');
|
||||
await page.getByLabel('Email').fill('user@immich.cloud');
|
||||
await page.getByLabel('Password').fill('password');
|
||||
await page.getByRole('button', { name: 'Login' }).click();
|
||||
|
@ -72,7 +72,7 @@ test.describe('Registration', () => {
|
|||
await page.getByRole('button', { name: 'Change password' }).click();
|
||||
|
||||
// login with new password
|
||||
await expect(page).toHaveURL('/auth/login');
|
||||
await expect(page).toHaveURL('/auth/login?autoLaunch=0');
|
||||
await page.getByLabel('Email').fill('user@immich.cloud');
|
||||
await page.getByLabel('Password').fill('new-password');
|
||||
await page.getByRole('button', { name: 'Login' }).click();
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
|
||||
import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { authManager } from '$lib/stores/auth-manager.svelte';
|
||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||
import { featureFlags } from '$lib/stores/server-config.store';
|
||||
import { sidebarStore } from '$lib/stores/sidebar.svelte';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
import { userInteraction } from '$lib/stores/user.svelte';
|
||||
import { handleLogout } from '$lib/utils/auth';
|
||||
import { getAboutInfo, logout, type ServerAboutResponseDto } from '@immich/sdk';
|
||||
import { getAboutInfo, type ServerAboutResponseDto } from '@immich/sdk';
|
||||
import { Button, IconButton } from '@immich/ui';
|
||||
import { mdiHelpCircleOutline, mdiMagnify, mdiMenu, mdiTrayArrowUp } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
|
@ -23,8 +25,6 @@
|
|||
import ThemeButton from '../theme-button.svelte';
|
||||
import UserAvatar from '../user-avatar.svelte';
|
||||
import AccountInfoPanel from './account-info-panel.svelte';
|
||||
import { sidebarStore } from '$lib/stores/sidebar.svelte';
|
||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||
|
||||
interface Props {
|
||||
showUploadButton?: boolean;
|
||||
|
@ -38,11 +38,6 @@
|
|||
let shouldShowHelpPanel = $state(false);
|
||||
let innerWidth: number = $state(0);
|
||||
|
||||
const onLogout = async () => {
|
||||
const { redirectUri } = await logout();
|
||||
await handleLogout(redirectUri);
|
||||
};
|
||||
|
||||
let info: ServerAboutResponseDto | undefined = $state();
|
||||
|
||||
onMount(async () => {
|
||||
|
@ -183,7 +178,7 @@
|
|||
{/if}
|
||||
|
||||
{#if shouldShowAccountInfoPanel}
|
||||
<AccountInfoPanel {onLogout} />
|
||||
<AccountInfoPanel onLogout={() => authManager.logout()} />
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
|
33
web/src/lib/stores/auth-manager.svelte.ts
Normal file
33
web/src/lib/stores/auth-manager.svelte.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { eventManager } from '$lib/stores/event-manager.svelte';
|
||||
import { logout } from '@immich/sdk';
|
||||
|
||||
class AuthManager {
|
||||
async logout() {
|
||||
let redirectUri;
|
||||
|
||||
try {
|
||||
const response = await logout();
|
||||
if (response.redirectUri) {
|
||||
redirectUri = response.redirectUri;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error logging out:', error);
|
||||
}
|
||||
|
||||
redirectUri = redirectUri ?? AppRoute.AUTH_LOGIN;
|
||||
|
||||
try {
|
||||
if (redirectUri.startsWith('/')) {
|
||||
await goto(redirectUri);
|
||||
} else {
|
||||
globalThis.location.href = redirectUri;
|
||||
}
|
||||
} finally {
|
||||
eventManager.emit('auth.logout');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const authManager = new AuthManager();
|
54
web/src/lib/stores/event-manager.svelte.ts
Normal file
54
web/src/lib/stores/event-manager.svelte.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
type Listener<EventMap extends Record<string, unknown[]>, K extends keyof EventMap> = (...params: EventMap[K]) => void;
|
||||
|
||||
class EventManager<EventMap extends Record<string, unknown[]>> {
|
||||
private listeners: {
|
||||
[K in keyof EventMap]?: {
|
||||
listener: Listener<EventMap, K>;
|
||||
once?: boolean;
|
||||
}[];
|
||||
} = {};
|
||||
|
||||
on<T extends keyof EventMap>(key: T, listener: (...params: EventMap[T]) => void) {
|
||||
return this.addListener(key, listener, false);
|
||||
}
|
||||
|
||||
once<T extends keyof EventMap>(key: T, listener: (...params: EventMap[T]) => void) {
|
||||
return this.addListener(key, listener, true);
|
||||
}
|
||||
|
||||
off<K extends keyof EventMap>(key: K, listener: Listener<EventMap, K>) {
|
||||
if (this.listeners[key]) {
|
||||
this.listeners[key] = this.listeners[key].filter((item) => item.listener !== listener);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
emit<T extends keyof EventMap>(key: T, ...params: EventMap[T]) {
|
||||
if (!this.listeners[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const { listener } of this.listeners[key]) {
|
||||
listener(...params);
|
||||
}
|
||||
|
||||
// remove one time listeners
|
||||
this.listeners[key] = this.listeners[key].filter((item) => !item.once);
|
||||
}
|
||||
|
||||
private addListener<T extends keyof EventMap>(key: T, listener: (...params: EventMap[T]) => void, once: boolean) {
|
||||
if (!this.listeners[key]) {
|
||||
this.listeners[key] = [];
|
||||
}
|
||||
|
||||
this.listeners[key].push({ listener, once });
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export const eventManager = new EventManager<{
|
||||
'user.login': [];
|
||||
'auth.logout': [];
|
||||
}>();
|
|
@ -1,3 +1,4 @@
|
|||
import { eventManager } from '$lib/stores/event-manager.svelte';
|
||||
import {
|
||||
getAssetsByOriginalPath,
|
||||
getUniqueOriginalPaths,
|
||||
|
@ -16,6 +17,10 @@ class FoldersStore {
|
|||
uniquePaths = $state<string[]>([]);
|
||||
assets = $state<AssetCache>({});
|
||||
|
||||
constructor() {
|
||||
eventManager.on('auth.logout', () => this.clearCache());
|
||||
}
|
||||
|
||||
async fetchUniquePaths() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { eventManager } from '$lib/stores/event-manager.svelte';
|
||||
import { asLocalTimeISO } from '$lib/utils/date-time';
|
||||
import {
|
||||
type AssetResponseDto,
|
||||
|
@ -24,6 +25,10 @@ export type MemoryAsset = MemoryIndex & {
|
|||
};
|
||||
|
||||
class MemoryStoreSvelte {
|
||||
constructor() {
|
||||
eventManager.on('auth.logout', () => this.clearCache());
|
||||
}
|
||||
|
||||
memories = $state<MemoryResponseDto[]>([]);
|
||||
private initialized = false;
|
||||
private memoryAssets = $derived.by(() => {
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import { eventManager } from '$lib/stores/event-manager.svelte';
|
||||
|
||||
class SearchStore {
|
||||
savedSearchTerms = $state<string[]>([]);
|
||||
isSearchEnabled = $state(false);
|
||||
|
||||
constructor() {
|
||||
eventManager.on('auth.logout', () => this.clearCache());
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.savedSearchTerms = [];
|
||||
this.isSearchEnabled = false;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { eventManager } from '$lib/stores/event-manager.svelte';
|
||||
import { purchaseStore } from '$lib/stores/purchase.store';
|
||||
import { type UserAdminResponseDto, type UserPreferencesResponseDto } from '@immich/sdk';
|
||||
import { writable } from 'svelte/store';
|
||||
|
@ -14,3 +15,5 @@ export const resetSavedUser = () => {
|
|||
preferences.set(undefined as unknown as UserPreferencesResponseDto);
|
||||
purchaseStore.setPurchaseStatus(false);
|
||||
};
|
||||
|
||||
eventManager.on('auth.logout', () => resetSavedUser());
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { eventManager } from '$lib/stores/event-manager.svelte';
|
||||
import type {
|
||||
AlbumResponseDto,
|
||||
ServerAboutResponseDto,
|
||||
|
@ -19,8 +20,10 @@ const defaultUserInteraction: UserInteractions = {
|
|||
serverInfo: undefined,
|
||||
};
|
||||
|
||||
export const resetUserInteraction = () => {
|
||||
export const userInteraction = $state<UserInteractions>(defaultUserInteraction);
|
||||
|
||||
const reset = () => {
|
||||
Object.assign(userInteraction, defaultUserInteraction);
|
||||
};
|
||||
|
||||
export const userInteraction = $state<UserInteractions>(defaultUserInteraction);
|
||||
eventManager.on('auth.logout', () => reset());
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { AppRoute } from '$lib/constants';
|
||||
import { handleLogout } from '$lib/utils/auth';
|
||||
import { authManager } from '$lib/stores/auth-manager.svelte';
|
||||
import { createEventEmitter } from '$lib/utils/eventemitter';
|
||||
import type { AssetResponseDto, ServerVersionResponseDto } from '@immich/sdk';
|
||||
import { io, type Socket } from 'socket.io-client';
|
||||
|
@ -50,7 +49,7 @@ websocket
|
|||
.on('disconnect', () => websocketStore.connected.set(false))
|
||||
.on('on_server_version', (serverVersion) => websocketStore.serverVersion.set(serverVersion))
|
||||
.on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion))
|
||||
.on('on_session_delete', () => handleLogout(AppRoute.AUTH_LOGIN))
|
||||
.on('on_session_delete', () => authManager.logout())
|
||||
.on('connect_error', (e) => console.log('Websocket Connect Error', e));
|
||||
|
||||
export const openWebsocketConnection = () => {
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { foldersStore } from '$lib/stores/folders.svelte';
|
||||
import { memoryStore } from '$lib/stores/memory.store.svelte';
|
||||
import { purchaseStore } from '$lib/stores/purchase.store';
|
||||
import { searchStore } from '$lib/stores/search.svelte';
|
||||
import { preferences as preferences$, resetSavedUser, user as user$ } from '$lib/stores/user.store';
|
||||
import { resetUserInteraction, userInteraction } from '$lib/stores/user.svelte';
|
||||
import { preferences as preferences$, user as user$ } from '$lib/stores/user.store';
|
||||
import { userInteraction } from '$lib/stores/user.svelte';
|
||||
import { getAboutInfo, getMyPreferences, getMyUser, getStorage } from '@immich/sdk';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { DateTime } from 'luxon';
|
||||
|
@ -91,19 +87,3 @@ export const getAccountAge = (): number => {
|
|||
|
||||
return Number(accountAge);
|
||||
};
|
||||
|
||||
export const handleLogout = async (redirectUri: string) => {
|
||||
try {
|
||||
if (redirectUri.startsWith('/')) {
|
||||
await goto(redirectUri);
|
||||
} else {
|
||||
globalThis.location.href = redirectUri;
|
||||
}
|
||||
} finally {
|
||||
resetSavedUser();
|
||||
resetUserInteraction();
|
||||
foldersStore.clearCache();
|
||||
memoryStore.clearCache();
|
||||
searchStore.clearCache();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { resetSavedUser, user } from '$lib/stores/user.store';
|
||||
import { logout, updateMyUser } from '@immich/sdk';
|
||||
import { authManager } from '$lib/stores/auth-manager.svelte';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
import { updateMyUser } from '@immich/sdk';
|
||||
import { Alert, Button, Field, HelperText, PasswordInput, Stack, Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
@ -25,9 +24,7 @@
|
|||
}
|
||||
|
||||
await updateMyUser({ userUpdateMeDto: { password } });
|
||||
await goto(AppRoute.AUTH_LOGIN);
|
||||
resetSavedUser();
|
||||
await logout();
|
||||
await authManager.logout();
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue