mirror of
https://github.com/immich-app/immich.git
synced 2025-01-01 08:31:59 +00:00
feat(web): logout of all tabs (#12407)
This commit is contained in:
parent
0dabb890cf
commit
2554cc96b0
9 changed files with 73 additions and 25 deletions
|
@ -21,6 +21,9 @@ type EmitEventMap = {
|
||||||
'asset.tag': [{ assetId: string }];
|
'asset.tag': [{ assetId: string }];
|
||||||
'asset.untag': [{ assetId: string }];
|
'asset.untag': [{ assetId: string }];
|
||||||
|
|
||||||
|
// session events
|
||||||
|
'session.delete': [{ sessionId: string }];
|
||||||
|
|
||||||
// user events
|
// user events
|
||||||
'user.signup': [{ notify: boolean; id: string; tempPassword?: string }];
|
'user.signup': [{ notify: boolean; id: string; tempPassword?: string }];
|
||||||
};
|
};
|
||||||
|
@ -43,6 +46,7 @@ export enum ClientEvent {
|
||||||
SERVER_VERSION = 'on_server_version',
|
SERVER_VERSION = 'on_server_version',
|
||||||
CONFIG_UPDATE = 'on_config_update',
|
CONFIG_UPDATE = 'on_config_update',
|
||||||
NEW_RELEASE = 'on_new_release',
|
NEW_RELEASE = 'on_new_release',
|
||||||
|
SESSION_DELETE = 'on_session_delete',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientEventMap {
|
export interface ClientEventMap {
|
||||||
|
@ -58,6 +62,7 @@ export interface ClientEventMap {
|
||||||
[ClientEvent.SERVER_VERSION]: ServerVersionResponseDto;
|
[ClientEvent.SERVER_VERSION]: ServerVersionResponseDto;
|
||||||
[ClientEvent.CONFIG_UPDATE]: Record<string, never>;
|
[ClientEvent.CONFIG_UPDATE]: Record<string, never>;
|
||||||
[ClientEvent.NEW_RELEASE]: ReleaseNotification;
|
[ClientEvent.NEW_RELEASE]: ReleaseNotification;
|
||||||
|
[ClientEvent.SESSION_DELETE]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ServerEvent {
|
export enum ServerEvent {
|
||||||
|
@ -77,7 +82,7 @@ export interface IEventRepository {
|
||||||
/**
|
/**
|
||||||
* Send to connected clients for a specific user
|
* Send to connected clients for a specific user
|
||||||
*/
|
*/
|
||||||
clientSend<E extends keyof ClientEventMap>(event: E, userId: string, data: ClientEventMap[E]): void;
|
clientSend<E extends keyof ClientEventMap>(event: E, room: string, data: ClientEventMap[E]): void;
|
||||||
/**
|
/**
|
||||||
* Send to all connected clients
|
* Send to all connected clients
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { ModuleRef } from '@nestjs/core';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import {
|
import {
|
||||||
OnGatewayConnection,
|
OnGatewayConnection,
|
||||||
|
@ -37,7 +38,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
|
||||||
private server?: Server;
|
private server?: Server;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private moduleRef: ModuleRef,
|
||||||
private eventEmitter: EventEmitter2,
|
private eventEmitter: EventEmitter2,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
@ -62,12 +63,15 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
|
||||||
async handleConnection(client: Socket) {
|
async handleConnection(client: Socket) {
|
||||||
try {
|
try {
|
||||||
this.logger.log(`Websocket Connect: ${client.id}`);
|
this.logger.log(`Websocket Connect: ${client.id}`);
|
||||||
const auth = await this.authService.authenticate({
|
const auth = await this.moduleRef.get(AuthService).authenticate({
|
||||||
headers: client.request.headers,
|
headers: client.request.headers,
|
||||||
queryParams: {},
|
queryParams: {},
|
||||||
metadata: { adminRoute: false, sharedLinkRoute: false, uri: '/api/socket.io' },
|
metadata: { adminRoute: false, sharedLinkRoute: false, uri: '/api/socket.io' },
|
||||||
});
|
});
|
||||||
await client.join(auth.user.id);
|
await client.join(auth.user.id);
|
||||||
|
if (auth.session) {
|
||||||
|
await client.join(auth.session.id);
|
||||||
|
}
|
||||||
this.serverSend(ServerEvent.WEBSOCKET_CONNECT, { userId: auth.user.id });
|
this.serverSend(ServerEvent.WEBSOCKET_CONNECT, { userId: auth.user.id });
|
||||||
} catch (error: Error | any) {
|
} catch (error: Error | any) {
|
||||||
this.logger.error(`Websocket connection error: ${error}`, error?.stack);
|
this.logger.error(`Websocket connection error: ${error}`, error?.stack);
|
||||||
|
@ -96,8 +100,8 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientSend<E extends keyof ClientEventMap>(event: E, userId: string, data: ClientEventMap[E]) {
|
clientSend<E extends keyof ClientEventMap>(event: E, room: string, data: ClientEventMap[E]) {
|
||||||
this.server?.to(userId).emit(event, data);
|
this.server?.to(room).emit(event, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientBroadcast<E extends keyof ClientEventMap>(event: E, data: ClientEventMap[E]) {
|
clientBroadcast<E extends keyof ClientEventMap>(event: E, data: ClientEventMap[E]) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||||
|
@ -20,6 +21,7 @@ import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
import { userStub } from 'test/fixtures/user.stub';
|
||||||
import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock';
|
import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock';
|
||||||
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
|
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
|
||||||
|
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
|
||||||
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
|
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
|
||||||
import { newSessionRepositoryMock } from 'test/repositories/session.repository.mock';
|
import { newSessionRepositoryMock } from 'test/repositories/session.repository.mock';
|
||||||
import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock';
|
import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock';
|
||||||
|
@ -56,6 +58,7 @@ const oauthUserWithDefaultQuota = {
|
||||||
describe('AuthService', () => {
|
describe('AuthService', () => {
|
||||||
let sut: AuthService;
|
let sut: AuthService;
|
||||||
let cryptoMock: Mocked<ICryptoRepository>;
|
let cryptoMock: Mocked<ICryptoRepository>;
|
||||||
|
let eventMock: Mocked<IEventRepository>;
|
||||||
let userMock: Mocked<IUserRepository>;
|
let userMock: Mocked<IUserRepository>;
|
||||||
let loggerMock: Mocked<ILoggerRepository>;
|
let loggerMock: Mocked<ILoggerRepository>;
|
||||||
let systemMock: Mocked<ISystemMetadataRepository>;
|
let systemMock: Mocked<ISystemMetadataRepository>;
|
||||||
|
@ -87,6 +90,7 @@ describe('AuthService', () => {
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
cryptoMock = newCryptoRepositoryMock();
|
cryptoMock = newCryptoRepositoryMock();
|
||||||
|
eventMock = newEventRepositoryMock();
|
||||||
userMock = newUserRepositoryMock();
|
userMock = newUserRepositoryMock();
|
||||||
loggerMock = newLoggerRepositoryMock();
|
loggerMock = newLoggerRepositoryMock();
|
||||||
systemMock = newSystemMetadataRepositoryMock();
|
systemMock = newSystemMetadataRepositoryMock();
|
||||||
|
@ -94,7 +98,7 @@ describe('AuthService', () => {
|
||||||
shareMock = newSharedLinkRepositoryMock();
|
shareMock = newSharedLinkRepositoryMock();
|
||||||
keyMock = newKeyRepositoryMock();
|
keyMock = newKeyRepositoryMock();
|
||||||
|
|
||||||
sut = new AuthService(cryptoMock, systemMock, loggerMock, userMock, sessionMock, shareMock, keyMock);
|
sut = new AuthService(cryptoMock, eventMock, systemMock, loggerMock, userMock, sessionMock, shareMock, keyMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
|
@ -208,6 +212,7 @@ describe('AuthService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sessionMock.delete).toHaveBeenCalledWith('token123');
|
expect(sessionMock.delete).toHaveBeenCalledWith('token123');
|
||||||
|
expect(eventMock.emit).toHaveBeenCalledWith('session.delete', { sessionId: 'token123' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the default redirect if auth type is OAUTH but oauth is not enabled', async () => {
|
it('should return the default redirect if auth type is OAUTH but oauth is not enabled', async () => {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { Permission } from 'src/enum';
|
import { Permission } from 'src/enum';
|
||||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||||
|
@ -75,6 +76,7 @@ export class AuthService {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
|
@ -114,6 +116,7 @@ export class AuthService {
|
||||||
async logout(auth: AuthDto, authType: AuthType): Promise<LogoutResponseDto> {
|
async logout(auth: AuthDto, authType: AuthType): Promise<LogoutResponseDto> {
|
||||||
if (auth.session) {
|
if (auth.session) {
|
||||||
await this.sessionRepository.delete(auth.session.id);
|
await this.sessionRepository.delete(auth.session.id);
|
||||||
|
await this.eventRepository.emit('session.delete', { sessionId: auth.session.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||||
import { AssetFileType, UserMetadataKey } from 'src/enum';
|
import { AssetFileType, UserMetadataKey } from 'src/enum';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface';
|
import { EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface';
|
||||||
|
@ -17,6 +18,7 @@ import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
import { userStub } from 'test/fixtures/user.stub';
|
||||||
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
|
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
|
||||||
|
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
|
||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
|
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
|
||||||
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
|
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
|
||||||
import { newNotificationRepositoryMock } from 'test/repositories/notification.repository.mock';
|
import { newNotificationRepositoryMock } from 'test/repositories/notification.repository.mock';
|
||||||
|
@ -64,6 +66,7 @@ const configs = {
|
||||||
describe(NotificationService.name, () => {
|
describe(NotificationService.name, () => {
|
||||||
let albumMock: Mocked<IAlbumRepository>;
|
let albumMock: Mocked<IAlbumRepository>;
|
||||||
let assetMock: Mocked<IAssetRepository>;
|
let assetMock: Mocked<IAssetRepository>;
|
||||||
|
let eventMock: Mocked<IEventRepository>;
|
||||||
let jobMock: Mocked<IJobRepository>;
|
let jobMock: Mocked<IJobRepository>;
|
||||||
let loggerMock: Mocked<ILoggerRepository>;
|
let loggerMock: Mocked<ILoggerRepository>;
|
||||||
let notificationMock: Mocked<INotificationRepository>;
|
let notificationMock: Mocked<INotificationRepository>;
|
||||||
|
@ -74,13 +77,23 @@ describe(NotificationService.name, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
albumMock = newAlbumRepositoryMock();
|
albumMock = newAlbumRepositoryMock();
|
||||||
assetMock = newAssetRepositoryMock();
|
assetMock = newAssetRepositoryMock();
|
||||||
|
eventMock = newEventRepositoryMock();
|
||||||
jobMock = newJobRepositoryMock();
|
jobMock = newJobRepositoryMock();
|
||||||
loggerMock = newLoggerRepositoryMock();
|
loggerMock = newLoggerRepositoryMock();
|
||||||
notificationMock = newNotificationRepositoryMock();
|
notificationMock = newNotificationRepositoryMock();
|
||||||
systemMock = newSystemMetadataRepositoryMock();
|
systemMock = newSystemMetadataRepositoryMock();
|
||||||
userMock = newUserRepositoryMock();
|
userMock = newUserRepositoryMock();
|
||||||
|
|
||||||
sut = new NotificationService(systemMock, notificationMock, userMock, jobMock, loggerMock, assetMock, albumMock);
|
sut = new NotificationService(
|
||||||
|
eventMock,
|
||||||
|
systemMock,
|
||||||
|
notificationMock,
|
||||||
|
userMock,
|
||||||
|
jobMock,
|
||||||
|
loggerMock,
|
||||||
|
assetMock,
|
||||||
|
albumMock,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto';
|
||||||
import { AlbumEntity } from 'src/entities/album.entity';
|
import { AlbumEntity } from 'src/entities/album.entity';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ArgOf } from 'src/interfaces/event.interface';
|
import { ArgOf, ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import {
|
import {
|
||||||
IEmailJob,
|
IEmailJob,
|
||||||
IJobRepository,
|
IJobRepository,
|
||||||
|
@ -30,6 +30,7 @@ export class NotificationService {
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(INotificationRepository) private notificationRepository: INotificationRepository,
|
@Inject(INotificationRepository) private notificationRepository: INotificationRepository,
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
|
@ -74,6 +75,12 @@ export class NotificationService {
|
||||||
await this.jobRepository.queue({ name: JobName.NOTIFY_ALBUM_INVITE, data: { id, recipientId: userId } });
|
await this.jobRepository.queue({ name: JobName.NOTIFY_ALBUM_INVITE, data: { id, recipientId: userId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnEmit({ event: 'session.delete' })
|
||||||
|
onSessionDelete({ sessionId }: ArgOf<'session.delete'>) {
|
||||||
|
// after the response is sent
|
||||||
|
setTimeout(() => this.eventRepository.clientSend(ClientEvent.SESSION_DELETE, sessionId, sessionId), 500);
|
||||||
|
}
|
||||||
|
|
||||||
async sendTestEmail(id: string, dto: SystemConfigSmtpDto) {
|
async sendTestEmail(id: string, dto: SystemConfigSmtpDto) {
|
||||||
const user = await this.userRepository.get(id, { withDeleted: false });
|
const user = await this.userRepository.get(id, { withDeleted: false });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
import { clickOutside } from '$lib/actions/click-outside';
|
||||||
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
|
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
|
||||||
import SkipLink from '$lib/components/elements/buttons/skip-link.svelte';
|
import SkipLink from '$lib/components/elements/buttons/skip-link.svelte';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { resetSavedUser, user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { clickOutside } from '$lib/actions/click-outside';
|
import { handleLogout } from '$lib/utils/auth';
|
||||||
import { logout } from '@immich/sdk';
|
import { logout } from '@immich/sdk';
|
||||||
import { mdiCog, mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
|
import { mdiCog, mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
import { fade, fly } from 'svelte/transition';
|
import { fade, fly } from 'svelte/transition';
|
||||||
import { AppRoute } from '../../../constants';
|
import { AppRoute } from '../../../constants';
|
||||||
import ImmichLogo from '../immich-logo.svelte';
|
import ImmichLogo from '../immich-logo.svelte';
|
||||||
|
@ -17,9 +19,6 @@
|
||||||
import ThemeButton from '../theme-button.svelte';
|
import ThemeButton from '../theme-button.svelte';
|
||||||
import UserAvatar from '../user-avatar.svelte';
|
import UserAvatar from '../user-avatar.svelte';
|
||||||
import AccountInfoPanel from './account-info-panel.svelte';
|
import AccountInfoPanel from './account-info-panel.svelte';
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
import { foldersStore } from '$lib/stores/folders.store';
|
|
||||||
|
|
||||||
export let showUploadButton = true;
|
export let showUploadButton = true;
|
||||||
|
|
||||||
|
@ -30,16 +29,9 @@
|
||||||
uploadClicked: void;
|
uploadClicked: void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const logOut = async () => {
|
const onLogout = async () => {
|
||||||
const { redirectUri } = await logout();
|
const { redirectUri } = await logout();
|
||||||
|
await handleLogout(redirectUri);
|
||||||
if (redirectUri.startsWith('/')) {
|
|
||||||
await goto(redirectUri);
|
|
||||||
} else {
|
|
||||||
window.location.href = redirectUri;
|
|
||||||
}
|
|
||||||
resetSavedUser();
|
|
||||||
foldersStore.clearCache();
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -153,7 +145,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if shouldShowAccountInfoPanel}
|
{#if shouldShowAccountInfoPanel}
|
||||||
<AccountInfoPanel on:logout={logOut} />
|
<AccountInfoPanel on:logout={onLogout} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { AppRoute } from '$lib/constants';
|
||||||
|
import { handleLogout } from '$lib/utils/auth';
|
||||||
import { createEventEmitter } from '$lib/utils/eventemitter';
|
import { createEventEmitter } from '$lib/utils/eventemitter';
|
||||||
import type { AssetResponseDto, ServerVersionResponseDto } from '@immich/sdk';
|
import type { AssetResponseDto, ServerVersionResponseDto } from '@immich/sdk';
|
||||||
import { io, type Socket } from 'socket.io-client';
|
import { io, type Socket } from 'socket.io-client';
|
||||||
|
@ -24,6 +26,7 @@ export interface Events {
|
||||||
on_server_version: (serverVersion: ServerVersionResponseDto) => void;
|
on_server_version: (serverVersion: ServerVersionResponseDto) => void;
|
||||||
on_config_update: () => void;
|
on_config_update: () => void;
|
||||||
on_new_release: (newRelase: ReleaseEvent) => void;
|
on_new_release: (newRelase: ReleaseEvent) => void;
|
||||||
|
on_session_delete: (sessionId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const websocket: Socket<Events> = io({
|
const websocket: Socket<Events> = io({
|
||||||
|
@ -47,6 +50,7 @@ websocket
|
||||||
.on('disconnect', () => websocketStore.connected.set(false))
|
.on('disconnect', () => websocketStore.connected.set(false))
|
||||||
.on('on_server_version', (serverVersion) => websocketStore.serverVersion.set(serverVersion))
|
.on('on_server_version', (serverVersion) => websocketStore.serverVersion.set(serverVersion))
|
||||||
.on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion))
|
.on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion))
|
||||||
|
.on('on_session_delete', () => handleLogout(AppRoute.AUTH_LOGIN))
|
||||||
.on('connect_error', (e) => console.log('Websocket Connect Error', e));
|
.on('connect_error', (e) => console.log('Websocket Connect Error', e));
|
||||||
|
|
||||||
export const openWebsocketConnection = () => {
|
export const openWebsocketConnection = () => {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { foldersStore } from '$lib/stores/folders.store';
|
||||||
import { purchaseStore } from '$lib/stores/purchase.store';
|
import { purchaseStore } from '$lib/stores/purchase.store';
|
||||||
import { serverInfo } from '$lib/stores/server-info.store';
|
import { serverInfo } from '$lib/stores/server-info.store';
|
||||||
import { preferences as preferences$, user as user$ } from '$lib/stores/user.store';
|
import { preferences as preferences$, resetSavedUser, user as user$ } from '$lib/stores/user.store';
|
||||||
import { getAboutInfo, getMyPreferences, getMyUser, getStorage } from '@immich/sdk';
|
import { getAboutInfo, getMyPreferences, getMyUser, getStorage } from '@immich/sdk';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
@ -87,3 +89,16 @@ export const getAccountAge = (): number => {
|
||||||
|
|
||||||
return Number(accountAge);
|
return Number(accountAge);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const handleLogout = async (redirectUri: string) => {
|
||||||
|
try {
|
||||||
|
if (redirectUri.startsWith('/')) {
|
||||||
|
await goto(redirectUri);
|
||||||
|
} else {
|
||||||
|
window.location.href = redirectUri;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
resetSavedUser();
|
||||||
|
foldersStore.clearCache();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue