Add "ui login" support
This commit is contained in:
@@ -63,6 +63,7 @@
|
||||
"i18next": "25.3.6",
|
||||
"i18next-browser-languagedetector": "8.2.0",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsonrepair": "3.13.0",
|
||||
"material-ui-popup-state": "5.3.6",
|
||||
"mui-nested-menu": "4.0.1",
|
||||
@@ -88,6 +89,7 @@
|
||||
"@graphql-codegen/typescript-apollo-client-helpers": "3.0.1",
|
||||
"@graphql-codegen/typescript-operations": "4.6.1",
|
||||
"@types/apollo-upload-client": "18.0.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "24.3.0",
|
||||
"@types/react": "19.1.10",
|
||||
"@types/react-beautiful-dnd": "13.1.8",
|
||||
|
||||
@@ -116,9 +116,9 @@
|
||||
},
|
||||
"label": {
|
||||
"action": {
|
||||
"all": "Mark all as read",
|
||||
"current": "Mark as read",
|
||||
"previous": "Mark previous as read",
|
||||
"all": "Mark all as read"
|
||||
"previous": "Mark previous as read"
|
||||
},
|
||||
"confirmation_one": "You are about to mark one chapter as read",
|
||||
"confirmation_other": "You are about to mark {{count}} chapters as read",
|
||||
@@ -521,6 +521,11 @@
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"days": {
|
||||
"day_one": "Day",
|
||||
"day_other": "Days",
|
||||
"value": "{{count}} $t(global.time.days.day, lowercase)"
|
||||
},
|
||||
"hour_short": "h",
|
||||
"minutes": {
|
||||
"minute_one": "Minute",
|
||||
@@ -1297,6 +1302,11 @@
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"jwt": {
|
||||
"access_token_expiry": "JWT access token expiry",
|
||||
"audience": "JWT audience claim",
|
||||
"refresh_token_expiry": "JWT refresh token expiry"
|
||||
},
|
||||
"label": {
|
||||
"password": "$t(global.label.password)",
|
||||
"title": "Authentication Mode",
|
||||
@@ -1317,12 +1327,18 @@
|
||||
"title": "None"
|
||||
}
|
||||
},
|
||||
"simpleLogin": {
|
||||
"simple_login": {
|
||||
"label": {
|
||||
"description": "A simple login page will be presented to login.",
|
||||
"description": "The login will be handled by the server.",
|
||||
"info": "When you enable this, you may need to refresh this tab for the login page to appear.",
|
||||
"title": "Simple Login"
|
||||
}
|
||||
},
|
||||
"ui_login": {
|
||||
"label": {
|
||||
"description": "The login will be handled by the client.",
|
||||
"title": "UI Login"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
83
src/App.tsx
83
src/App.tsx
@@ -8,7 +8,7 @@
|
||||
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { Navigate, Outlet, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { loadErrorMessages, loadDevMessages } from '@apollo/client/dev';
|
||||
import { loadable } from 'react-lazily/loadable';
|
||||
import Box from '@mui/material/Box';
|
||||
@@ -27,6 +27,10 @@ import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
||||
import { useSessionContext } from '@/features/authentication/SessionContext.tsx';
|
||||
import { LoginPage } from '@/features/authentication/screens/LoginPage.tsx';
|
||||
import { AuthGuard } from '@/features/authentication/components/AuthGuard.tsx';
|
||||
import { SearchParam } from '@/base/Base.types.ts';
|
||||
|
||||
const { Browse } = loadable(() => import('@/features/browse/screens/Browse.tsx'), lazyLoadFallback);
|
||||
const { DownloadQueue } = loadable(() => import('@/features/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback);
|
||||
@@ -82,7 +86,7 @@ const { More } = loadable(() => import('@/features/settings/screens/More.tsx'),
|
||||
const { Reader } = loadable(() => import('@/features/reader/screens/Reader.tsx'), lazyLoadFallback);
|
||||
const { HistorySettings } = loadable(() => import('@/features/history/screens/HistorySettings.tsx'), lazyLoadFallback);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (import.meta.env.DEV) {
|
||||
// Adds messages only in a dev environment
|
||||
loadDevMessages();
|
||||
loadErrorMessages();
|
||||
@@ -105,15 +109,39 @@ const ScrollToTop = () => {
|
||||
* and thus, data of existing chapters/mangas in the cache get outdated
|
||||
*/
|
||||
const BackgroundSubscriptions = () => {
|
||||
// load the full download status once on startup to fill the cache
|
||||
// Listen to session changes
|
||||
const { isAuthRequired, accessToken } = useSessionContext();
|
||||
|
||||
const skipConnection = isAuthRequired == null || (!!isAuthRequired && !accessToken);
|
||||
|
||||
// Load the full download status once on startup to fill the cache
|
||||
requestManager.useGetDownloadStatus({ nextFetchPolicy: 'standby' });
|
||||
requestManager.useDownloadSubscription();
|
||||
requestManager.useUpdaterSubscription();
|
||||
requestManager.useWebUIUpdateSubscription();
|
||||
requestManager.useDownloadSubscription({ skip: skipConnection });
|
||||
requestManager.useUpdaterSubscription({ skip: skipConnection });
|
||||
requestManager.useWebUIUpdateSubscription({ skip: skipConnection });
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const PrivateRoutes = () => {
|
||||
const { isAuthRequired, accessToken, refreshToken } = useSessionContext();
|
||||
|
||||
const isAuthenticated = !isAuthRequired || (isAuthRequired && (accessToken || refreshToken));
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<Navigate
|
||||
to={{
|
||||
pathname: AppRoutes.authentication.childRoutes.login.path,
|
||||
search: `${SearchParam.REDIRECT}=${window.location.pathname}`,
|
||||
}}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
};
|
||||
|
||||
const MainApp = () => {
|
||||
const { navBarWidth, appBarHeight, bottomBarHeight } = useNavBarContext();
|
||||
const isMobileWidth = MediaQuery.useIsMobileWidth();
|
||||
@@ -139,15 +167,32 @@ const MainApp = () => {
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
<Route path={AppRoutes.authentication.match}>
|
||||
<Route path={AppRoutes.authentication.childRoutes.login.match} element={<LoginPage />} />
|
||||
</Route>
|
||||
|
||||
<Route element={<PrivateRoutes />}>
|
||||
{/* General Routes */}
|
||||
<Route path={AppRoutes.root.match} element={<Navigate to={AppRoutes.library.path()} replace />} />
|
||||
<Route path={AppRoutes.matchAll.match} element={<Navigate to={AppRoutes.root.path} replace />} />
|
||||
<Route
|
||||
path={AppRoutes.root.match}
|
||||
element={<Navigate to={AppRoutes.library.path()} replace />}
|
||||
/>
|
||||
<Route
|
||||
path={AppRoutes.matchAll.match}
|
||||
element={<Navigate to={AppRoutes.root.path} replace />}
|
||||
/>
|
||||
{isMobileWidth && <Route path={AppRoutes.more.match} element={<More />} />}
|
||||
<Route path={AppRoutes.about.match} element={<About />} />
|
||||
<Route path={AppRoutes.settings.match}>
|
||||
<Route index element={<Settings />} />
|
||||
<Route path={AppRoutes.settings.childRoutes.categories.match} element={<CategorySettings />} />
|
||||
<Route path={AppRoutes.settings.childRoutes.reader.match} element={<GlobalReaderSettings />} />
|
||||
<Route
|
||||
path={AppRoutes.settings.childRoutes.categories.match}
|
||||
element={<CategorySettings />}
|
||||
/>
|
||||
<Route
|
||||
path={AppRoutes.settings.childRoutes.reader.match}
|
||||
element={<GlobalReaderSettings />}
|
||||
/>
|
||||
<Route path={AppRoutes.settings.childRoutes.library.match}>
|
||||
<Route index element={<LibrarySettings />} />
|
||||
<Route
|
||||
@@ -155,14 +200,20 @@ const MainApp = () => {
|
||||
element={<LibraryDuplicates />}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={AppRoutes.settings.childRoutes.download.match} element={<DownloadSettings />} />
|
||||
<Route
|
||||
path={AppRoutes.settings.childRoutes.download.match}
|
||||
element={<DownloadSettings />}
|
||||
/>
|
||||
<Route path={AppRoutes.settings.childRoutes.backup.match} element={<Backup />} />
|
||||
<Route path={AppRoutes.settings.childRoutes.server.match} element={<ServerSettings />} />
|
||||
<Route path={AppRoutes.settings.childRoutes.webui.match} element={<WebUISettings />} />
|
||||
<Route path={AppRoutes.settings.childRoutes.browse.match} element={<BrowseSettings />} />
|
||||
<Route path={AppRoutes.settings.childRoutes.history.match} element={<HistorySettings />} />
|
||||
<Route path={AppRoutes.settings.childRoutes.device.match} element={<DeviceSetting />} />
|
||||
<Route path={AppRoutes.settings.childRoutes.tracking.match} element={<TrackingSettings />} />
|
||||
<Route
|
||||
path={AppRoutes.settings.childRoutes.tracking.match}
|
||||
element={<TrackingSettings />}
|
||||
/>
|
||||
<Route path={AppRoutes.settings.childRoutes.appearance.match} element={<Appearance />} />
|
||||
</Route>
|
||||
|
||||
@@ -176,7 +227,10 @@ const MainApp = () => {
|
||||
<Route path={AppRoutes.sources.childRoutes.searchAll.match} element={<SearchAll />} />
|
||||
</Route>
|
||||
<Route path={AppRoutes.extension.match}>
|
||||
<Route index element={<Navigate to={AppRoutes.browse.path(BrowseTab.EXTENSIONS)} replace />} />
|
||||
<Route
|
||||
index
|
||||
element={<Navigate to={AppRoutes.browse.path(BrowseTab.EXTENSIONS)} replace />}
|
||||
/>
|
||||
<Route path={AppRoutes.extension.childRoutes.info.match} element={<ExtensionInfo />} />
|
||||
</Route>
|
||||
<Route path={AppRoutes.downloads.match} element={<DownloadQueue />} />
|
||||
@@ -193,6 +247,7 @@ const MainApp = () => {
|
||||
<Route path={AppRoutes.migrate.childRoutes.search.match} element={<SearchAll />} />
|
||||
</Route>
|
||||
<Route path={AppRoutes.tracker.match} element={<TrackerOAuthLogin />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
</Box>
|
||||
@@ -214,6 +269,7 @@ export const App: React.FC = () => (
|
||||
<WebUIUpdateChecker />
|
||||
<BackgroundSubscriptions />
|
||||
<CssBaseline enableColorScheme />
|
||||
<AuthGuard>
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<Box sx={{ flexShrink: 0, position: 'relative', height: '100vh' }}>
|
||||
<DefaultNavBar />
|
||||
@@ -223,5 +279,6 @@ export const App: React.FC = () => (
|
||||
<Route path={AppRoutes.reader.match} element={<ReaderApp />} />
|
||||
</Routes>
|
||||
</Box>
|
||||
</AuthGuard>
|
||||
</AppContext>
|
||||
);
|
||||
|
||||
31
src/assets/SuwayomiLogo.tsx
Normal file
31
src/assets/SuwayomiLogo.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
|
||||
|
||||
export const SuwayomiLogo = ({
|
||||
circleRingColor = '#35d4d5',
|
||||
...props
|
||||
}: SvgIconProps & { circleRingColor?: string }) => (
|
||||
<SvgIcon {...props}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100" height="100">
|
||||
<svg version="1.1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="50" cy="50" r="50" fill={circleRingColor} />
|
||||
<circle cx="50" cy="50" r="39" fill="#1A1A1D" />
|
||||
<path
|
||||
d="m26.46 38.07 15.633-.27c3.348-.081 3.348-.081 4.482.783.783.972 1.161 1.701 1.404 2.916-.216 1.053-.567 1.593-1.242 2.403l-3.321 4.509c-.513.702-.513.702-.756 1.539.216.108.216.108.459.189 2.322 1.269 4.347 3.402 6.291 5.211l1.539 1.431c.459.621.594 1.053.621 1.809l-2.268 2.619c-.675.54-1.188.459-2.052.351-.702-.513-.702-.513-1.404-1.242l-3.861-3.996c-.81-.837-1.674-1.593-2.565-2.322-.972.459-1.62.999-2.376 1.728-7.29 6.723-7.29 6.723-10.206 6.615-1.161-.432-1.998-1.215-2.808-2.133-.27-.918-.27-.918-.27-1.62 1.08-.918 2.187-1.755 3.402-2.457 4.428-2.592 8.046-6.183 11.124-10.287 1.026-1.431 1.35-1.998 1.674-2.646l-12.15.054c-.54-.054-.54-.054-1.08-.594-.243-1.107-.27-2.16-.27-3.24l0-1.35z"
|
||||
fill="#fdfdfd"
|
||||
/>
|
||||
<path
|
||||
d="m78.6486 37.8378 1.5405 1.5135c-.5405 8.027-5.4324 16.1892-10.8649 21.8378-1.4054 1.4324-1.8649 1.8919-3.0811 2.1081-.9459-.1892-1.6757-.4324-2.5405-.8378l-1.3514-.6757c-.1081-1.2162-.1081-1.2162.3243-1.8378l3.2432-3.0811c3.8919-3.8919 6.1892-9.2703 7.5135-14.5405h-11.6216v7.8378c-.6216.2973-.973.2973-1.6486.2973-3.7297 0-3.7297 0-4.027-.2973v-13.2432l18.4865-.2973c1.6216.027 2.8378.0541 4.027 1.2162z"
|
||||
fill="#f9f9f9"
|
||||
/>
|
||||
</svg>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
@@ -38,13 +38,23 @@ export const AppRoutes = {
|
||||
matchAll: {
|
||||
match: '*',
|
||||
},
|
||||
authentication: {
|
||||
match: 'auth',
|
||||
path: '/auth',
|
||||
childRoutes: {
|
||||
login: {
|
||||
match: 'login',
|
||||
path: '/auth/login',
|
||||
},
|
||||
},
|
||||
},
|
||||
about: {
|
||||
match: 'about',
|
||||
path: '/about',
|
||||
},
|
||||
settings: {
|
||||
path: '/settings',
|
||||
match: 'settings',
|
||||
path: '/settings',
|
||||
childRoutes: {
|
||||
categories: {
|
||||
match: 'categories',
|
||||
|
||||
@@ -71,4 +71,5 @@ export enum ScrollDirection {
|
||||
export enum SearchParam {
|
||||
TAB = 'tab',
|
||||
QUERY = 'query',
|
||||
REDIRECT = 'redirect',
|
||||
}
|
||||
|
||||
30
src/base/components/AvatarSpinner.tsx
Normal file
30
src/base/components/AvatarSpinner.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import Avatar, { AvatarProps } from '@mui/material/Avatar';
|
||||
import { SpinnerImage, SpinnerImageProps } from '@/base/components/SpinnerImage.tsx';
|
||||
|
||||
export const AvatarSpinner = ({
|
||||
iconUrl,
|
||||
alt,
|
||||
slots,
|
||||
}: {
|
||||
iconUrl: string;
|
||||
alt: string;
|
||||
slots?: { avatarProps?: Partial<AvatarProps>; spinnerImageProps?: Partial<SpinnerImageProps> };
|
||||
}) => (
|
||||
<Avatar variant="rounded" alt={alt} {...slots?.avatarProps}>
|
||||
<SpinnerImage
|
||||
alt={alt}
|
||||
src={iconUrl}
|
||||
{...slots?.spinnerImageProps}
|
||||
spinnerStyle={{ small: true, ...slots?.spinnerImageProps?.spinnerStyle }}
|
||||
imgStyle={{ objectFit: 'cover', width: '100%', height: '100%', ...slots?.spinnerImageProps?.imgStyle }}
|
||||
/>
|
||||
</Avatar>
|
||||
);
|
||||
@@ -6,36 +6,28 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import Avatar, { AvatarProps } from '@mui/material/Avatar';
|
||||
import { SpinnerImage, SpinnerImageProps } from '@/base/components/SpinnerImage.tsx';
|
||||
import { ComponentProps } from 'react';
|
||||
import { AvatarSpinner } from '@/base/components/AvatarSpinner.tsx';
|
||||
|
||||
export const ListCardAvatar = ({
|
||||
iconUrl,
|
||||
alt,
|
||||
slots,
|
||||
}: {
|
||||
iconUrl: string;
|
||||
alt: string;
|
||||
slots?: { avatarProps?: Partial<AvatarProps>; spinnerImageProps?: Partial<SpinnerImageProps> };
|
||||
}) => (
|
||||
<Avatar
|
||||
variant="rounded"
|
||||
alt={alt}
|
||||
{...slots?.avatarProps}
|
||||
sx={{
|
||||
export const ListCardAvatar = (props: ComponentProps<typeof AvatarSpinner>) => {
|
||||
const { slots } = props;
|
||||
|
||||
return (
|
||||
<AvatarSpinner
|
||||
{...props}
|
||||
slots={{
|
||||
...slots,
|
||||
avatarProps: {
|
||||
...slots?.avatarProps,
|
||||
sx: {
|
||||
width: 56,
|
||||
height: 56,
|
||||
flex: '0 0 auto',
|
||||
background: 'transparent',
|
||||
...slots?.avatarProps?.sx,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SpinnerImage
|
||||
alt={alt}
|
||||
src={iconUrl}
|
||||
{...slots?.spinnerImageProps}
|
||||
spinnerStyle={{ small: true, ...slots?.spinnerImageProps?.spinnerStyle }}
|
||||
imgStyle={{ objectFit: 'cover', width: '100%', height: '100%', ...slots?.spinnerImageProps?.imgStyle }}
|
||||
/>
|
||||
</Avatar>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,12 +19,14 @@ import { SnackbarWithDescription } from '@/base/components/feedback/SnackbarWith
|
||||
import { AppPageHistoryContextProvider } from '@/base/contexts/AppPageHistoryContext.tsx';
|
||||
import { AppThemeContextProvider } from '@/features/theme/AppThemeContext.tsx';
|
||||
import { NavBarContextProvider } from '@/features/navigation-bar/NavbarContext.tsx';
|
||||
import { SessionContextProvider } from '@/features/authentication/SessionContext.tsx';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const AppContext: React.FC<Props> = ({ children }) => (
|
||||
<SessionContextProvider>
|
||||
<Router>
|
||||
<StyledEngineProvider injectFirst>
|
||||
<AppThemeContextProvider>
|
||||
@@ -52,4 +54,5 @@ export const AppContext: React.FC<Props> = ({ children }) => (
|
||||
</AppThemeContextProvider>
|
||||
</StyledEngineProvider>
|
||||
</Router>
|
||||
</SessionContextProvider>
|
||||
);
|
||||
|
||||
93
src/features/authentication/AuthManager.ts
Normal file
93
src/features/authentication/AuthManager.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||
import { useSessionStorage } from '@/base/hooks/useStorage.tsx';
|
||||
|
||||
export class AuthManager {
|
||||
static readonly AUTH_REQUIRED_KEY = 'auth-required';
|
||||
|
||||
static readonly REFRESH_TOKEN_KEY = 'auth-refresh-token';
|
||||
|
||||
static readonly REACT_SESSION_REFRESH_KEY = 'auth-react-session-refresh';
|
||||
|
||||
private static accessToken: string | null = null;
|
||||
|
||||
static isAuthRequired(): boolean | null {
|
||||
return AppStorage.session.getItemParsed(AuthManager.AUTH_REQUIRED_KEY, null);
|
||||
}
|
||||
|
||||
static setAuthRequired(value: boolean | null): void {
|
||||
AppStorage.session.setItem(AuthManager.AUTH_REQUIRED_KEY, value);
|
||||
}
|
||||
|
||||
static useIsAuthRequired(): boolean | null {
|
||||
const [value] = useSessionStorage(AuthManager.AUTH_REQUIRED_KEY, null);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static getAccessToken(): string | null {
|
||||
return AuthManager.accessToken;
|
||||
}
|
||||
|
||||
static getRefreshToken(): string | null {
|
||||
return AppStorage.session.getItemParsed(AuthManager.REFRESH_TOKEN_KEY, null);
|
||||
}
|
||||
|
||||
static getTokens(): { accessToken: string | null; refreshToken: string | null } {
|
||||
return {
|
||||
accessToken: AuthManager.getAccessToken(),
|
||||
refreshToken: AuthManager.getRefreshToken(),
|
||||
};
|
||||
}
|
||||
|
||||
static setAccessToken(token: string): void {
|
||||
AuthManager.accessToken = token;
|
||||
AuthManager.refreshReactSessionContext();
|
||||
}
|
||||
|
||||
static setRefreshToken(token: string): void {
|
||||
AppStorage.session.setItem(AuthManager.REFRESH_TOKEN_KEY, token);
|
||||
AuthManager.refreshReactSessionContext();
|
||||
}
|
||||
|
||||
static setTokens(accessToken: string, refreshToken: string): void {
|
||||
AuthManager.setAccessToken(accessToken);
|
||||
AuthManager.setRefreshToken(refreshToken);
|
||||
}
|
||||
|
||||
static removeAccessToken(): void {
|
||||
AuthManager.accessToken = null;
|
||||
AuthManager.refreshReactSessionContext();
|
||||
}
|
||||
|
||||
static removeRefreshToken(): void {
|
||||
AppStorage.session.setItem(AuthManager.REFRESH_TOKEN_KEY, undefined);
|
||||
AuthManager.refreshReactSessionContext();
|
||||
}
|
||||
|
||||
static removeTokens(): void {
|
||||
AuthManager.removeAccessToken();
|
||||
AuthManager.removeRefreshToken();
|
||||
}
|
||||
|
||||
private static getNextReactSessionContextId(): number {
|
||||
const id = AppStorage.session.getItemParsed(AuthManager.REACT_SESSION_REFRESH_KEY, 0);
|
||||
|
||||
return (id + 1) % Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
static refreshReactSessionContext(): void {
|
||||
AppStorage.session.setItem(AuthManager.REACT_SESSION_REFRESH_KEY, AuthManager.getNextReactSessionContextId());
|
||||
}
|
||||
|
||||
static useListenToReactSessionContextRefreshEvent(): void {
|
||||
useSessionStorage(AuthManager.REACT_SESSION_REFRESH_KEY, 0);
|
||||
}
|
||||
}
|
||||
42
src/features/authentication/SessionContext.tsx
Normal file
42
src/features/authentication/SessionContext.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { createContext, ReactNode, useContext, useMemo } from 'react';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
|
||||
interface TSessionContext {
|
||||
isAuthRequired: boolean | null;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
}
|
||||
|
||||
const SessionContext = createContext<TSessionContext>({
|
||||
isAuthRequired: null,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
});
|
||||
|
||||
export const SessionContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
AuthManager.useListenToReactSessionContextRefreshEvent();
|
||||
|
||||
const isAuthRequired = AuthManager.useIsAuthRequired();
|
||||
const { accessToken, refreshToken } = AuthManager.getTokens();
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
isAuthRequired,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
}),
|
||||
[isAuthRequired, accessToken, refreshToken],
|
||||
);
|
||||
|
||||
return <SessionContext.Provider value={value}>{children}</SessionContext.Provider>;
|
||||
};
|
||||
|
||||
export const useSessionContext = () => useContext(SessionContext);
|
||||
36
src/features/authentication/components/AuthGuard.tsx
Normal file
36
src/features/authentication/components/AuthGuard.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { useSessionContext } from '@/features/authentication/SessionContext.tsx';
|
||||
import { SplashScreen } from '@/features/authentication/components/SplashScreen.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
|
||||
export const AuthGuard = ({ children }: { children: ReactNode }) => {
|
||||
const { isAuthRequired } = useSessionContext();
|
||||
|
||||
requestManager.useGetAbout({
|
||||
skip: isAuthRequired !== null,
|
||||
onCompleted: () => AuthManager.setAuthRequired(false),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const onUnload = () => AuthManager.setAuthRequired(null);
|
||||
|
||||
window.addEventListener('beforeunload', onUnload);
|
||||
|
||||
return () => window.removeEventListener('beforeunload', onUnload);
|
||||
}, []);
|
||||
|
||||
if (isAuthRequired === null) {
|
||||
return <SplashScreen />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
46
src/features/authentication/components/SplashScreen.tsx
Normal file
46
src/features/authentication/components/SplashScreen.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { SuwayomiLogo } from '@/assets/SuwayomiLogo.tsx';
|
||||
import { ServerAddressSetting } from '@/features/settings/components/ServerAddressSetting.tsx';
|
||||
|
||||
export const SplashScreen = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
minWidth: '100vw',
|
||||
minHeight: '100vh',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'primary.light',
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: 'primary.dark',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<SuwayomiLogo
|
||||
sx={{
|
||||
fontSize: 250,
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
fontSize: 350,
|
||||
},
|
||||
}}
|
||||
circleRingColor={theme.palette.primary.light}
|
||||
/>
|
||||
{import.meta.env.DEV && (
|
||||
<Stack sx={{ height: 'auto', mt: 5 }}>
|
||||
<ServerAddressSetting />
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
143
src/features/authentication/screens/LoginPage.tsx
Normal file
143
src/features/authentication/screens/LoginPage.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Button from '@mui/material/Button';
|
||||
import { Navigate, useNavigate } from 'react-router-dom';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { StringParam, useQueryParam } from 'use-query-params';
|
||||
import { PasswordTextField } from '@/base/components/inputs/PasswordTextField.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
import { SuwayomiLogo } from '@/assets/SuwayomiLogo.tsx';
|
||||
import { useSessionContext } from '@/features/authentication/SessionContext.tsx';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
|
||||
import { SearchParam } from '@/base/Base.types.ts';
|
||||
import { ServerAddressSetting } from '@/features/settings/components/ServerAddressSetting.tsx';
|
||||
|
||||
export const LoginPage = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { setOverride } = useNavBarContext();
|
||||
const navigate = useNavigate();
|
||||
const { isAuthRequired, accessToken, refreshToken } = useSessionContext();
|
||||
|
||||
const [redirect] = useQueryParam(SearchParam.REDIRECT, StringParam);
|
||||
const [loginUser, { loading: isLoading }] = requestManager.useLoginUser();
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const isAuthenticated = !isAuthRequired || (!!isAuthRequired && !!accessToken && !!refreshToken);
|
||||
|
||||
const doLogin = async () => {
|
||||
try {
|
||||
const { data } = await loginUser({ variables: { username, password } });
|
||||
|
||||
if (data) {
|
||||
AuthManager.setTokens(data.login.accessToken, data.login.refreshToken);
|
||||
navigate(redirect ?? AppRoutes.root.path);
|
||||
}
|
||||
} catch (e) {
|
||||
makeToast(t('tracking.action.login.label.failure', { name: 'Suwayomi' }), 'error', getErrorMessage(e));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setOverride({ status: true, value: null });
|
||||
|
||||
return () => setOverride({ status: false, value: null });
|
||||
}, []);
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Navigate to={AppRoutes.root.path} replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
sx={{
|
||||
minHeight: '50vh',
|
||||
flexBasis: '60%',
|
||||
p: 4,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'primary.light',
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: 'primary.dark',
|
||||
}),
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
minHeight: '0vh',
|
||||
height: '100vh',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SuwayomiLogo
|
||||
sx={{
|
||||
fontSize: 250,
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
fontSize: 350,
|
||||
},
|
||||
}}
|
||||
circleRingColor={theme.palette.primary.light}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
sx={{
|
||||
minHeight: '50vh',
|
||||
flexBasis: '40%',
|
||||
p: 4,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
minHeight: '0vh',
|
||||
height: '100vh',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack sx={{ maxWidth: 300, gap: 2 }}>
|
||||
<Stack>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="username"
|
||||
name="username"
|
||||
label={t('global.label.username')}
|
||||
type="text"
|
||||
fullWidth
|
||||
variant="standard"
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<PasswordTextField
|
||||
margin="dense"
|
||||
fullWidth
|
||||
variant="standard"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
{import.meta.env.DEV && <ServerAddressSetting />}
|
||||
</Stack>
|
||||
<Button disabled={isLoading || (!username && !password)} variant="contained" onClick={doLogin}>
|
||||
{t('global.button.log_in')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { DEFAULT_DEVICE } from '@/features/device/services/Device.ts';
|
||||
import { DEFAULT_SORT_SETTINGS } from '@/features/migration/Migration.constants.ts';
|
||||
import { GlobalUpdateSkipEntriesSettings, MetadataServerSettings } from '@/features/settings/Settings.types.ts';
|
||||
@@ -90,9 +91,13 @@ const AUTH_MODES_TO_TRANSLATION_KEY: { [mode in AuthMode]: SelectSettingValueDis
|
||||
description: 'settings.server.auth.mode.option.basicAuth.label.description',
|
||||
},
|
||||
[AuthMode.SimpleLogin]: {
|
||||
text: 'settings.server.auth.mode.option.simpleLogin.label.title',
|
||||
description: 'settings.server.auth.mode.option.simpleLogin.label.description',
|
||||
disclaimer: 'settings.server.auth.mode.option.simpleLogin.label.info',
|
||||
text: 'settings.server.auth.mode.option.simple_login.label.title',
|
||||
description: 'settings.server.auth.mode.option.simple_login.label.description',
|
||||
disclaimer: 'settings.server.auth.mode.option.simple_login.label.info',
|
||||
},
|
||||
[AuthMode.UiLogin]: {
|
||||
text: 'settings.server.auth.mode.option.ui_login.label.title',
|
||||
description: 'settings.server.auth.mode.option.ui_login.label.description',
|
||||
},
|
||||
};
|
||||
export const AUTH_MODES_SELECT_VALUES: SelectSettingValue<AuthMode>[] = AUTH_MODES.map((mode) => [
|
||||
@@ -183,3 +188,15 @@ export const WEB_UI_UPDATE_INTERVAL = {
|
||||
min: 1,
|
||||
max: 23,
|
||||
};
|
||||
|
||||
export const JWT_ACCESS_TOKEN_EXPIRY = {
|
||||
default: dayjs.duration(5, 'minute').asMinutes(),
|
||||
min: dayjs.duration(1, 'minute').asMinutes(),
|
||||
max: dayjs.duration(4, 'hour').asMinutes(),
|
||||
};
|
||||
|
||||
export const JWT_REFRESH_TOKEN_EXPIRY = {
|
||||
default: dayjs.duration(60, 'day').asDays(),
|
||||
min: dayjs.duration(1, 'day').asDays(),
|
||||
max: dayjs.duration(1, 'year').asDays(),
|
||||
};
|
||||
|
||||
@@ -55,6 +55,9 @@ export type ServerSettingsType = Pick<
|
||||
| 'authMode'
|
||||
| 'authUsername'
|
||||
| 'authPassword'
|
||||
| 'jwtAudience'
|
||||
| 'jwtTokenExpiry'
|
||||
| 'jwtRefreshExpiry'
|
||||
| 'flareSolverrEnabled'
|
||||
| 'flareSolverrTimeout'
|
||||
| 'flareSolverrUrl'
|
||||
|
||||
33
src/features/settings/components/ServerAddressSetting.tsx
Normal file
33
src/features/settings/components/ServerAddressSetting.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TextSetting } from '@/base/components/settings/text/TextSetting.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { useLocalStorage } from '@/base/hooks/useStorage.tsx';
|
||||
|
||||
export const ServerAddressSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [serverAddress, setServerAddress] = useLocalStorage<string>('serverBaseURL', window.location.origin);
|
||||
|
||||
const handleServerAddressChange = (address: string) => {
|
||||
const serverBaseUrl = address.replaceAll(/(\/)+$/g, '');
|
||||
setServerAddress(serverBaseUrl);
|
||||
requestManager.reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<TextSetting
|
||||
settingName={t('settings.about.server.label.address')}
|
||||
handleChange={handleServerAddressChange}
|
||||
value={serverAddress}
|
||||
placeholder="http://localhost:4567"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -15,8 +15,8 @@ import ListItemText from '@mui/material/ListItemText';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import { t as translate } from 'i18next';
|
||||
import dayjs from 'dayjs';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { useLocalStorage } from '@/base/hooks/useStorage.tsx';
|
||||
import { TextSetting } from '@/base/components/settings/text/TextSetting.tsx';
|
||||
import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx';
|
||||
import { SelectSetting } from '@/base/components/settings/SelectSetting.tsx';
|
||||
@@ -33,7 +33,13 @@ import { ServerSettings as GqlServerSettings, ServerSettingsType } from '@/featu
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
import { AuthMode, SortOrder } from '@/lib/graphql/generated/graphql';
|
||||
import { AUTH_MODES_SELECT_VALUES } from '@/features/settings/Settings.constants.ts';
|
||||
import {
|
||||
AUTH_MODES_SELECT_VALUES,
|
||||
JWT_ACCESS_TOKEN_EXPIRY,
|
||||
JWT_REFRESH_TOKEN_EXPIRY,
|
||||
} from '@/features/settings/Settings.constants.ts';
|
||||
import { ServerAddressSetting } from '@/features/settings/components/ServerAddressSetting.tsx';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
|
||||
const extractServerSettings = (settings: GqlServerSettings): ServerSettingsType => ({
|
||||
ip: settings.ip,
|
||||
@@ -52,6 +58,9 @@ const extractServerSettings = (settings: GqlServerSettings): ServerSettingsType
|
||||
authMode: settings.authMode,
|
||||
authUsername: settings.authUsername,
|
||||
authPassword: settings.authPassword,
|
||||
jwtAudience: settings.jwtAudience,
|
||||
jwtTokenExpiry: settings.jwtTokenExpiry,
|
||||
jwtRefreshExpiry: settings.jwtRefreshExpiry,
|
||||
flareSolverrEnabled: settings.flareSolverrEnabled,
|
||||
flareSolverrTimeout: settings.flareSolverrTimeout,
|
||||
flareSolverrUrl: settings.flareSolverrUrl,
|
||||
@@ -99,24 +108,18 @@ export const ServerSettings = () => {
|
||||
});
|
||||
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
||||
|
||||
const [serverAddress, setServerAddress] = useLocalStorage<string>(
|
||||
'serverBaseURL',
|
||||
import.meta.env.VITE_SERVER_URL_DEFAULT,
|
||||
);
|
||||
|
||||
const handleServerAddressChange = (address: string) => {
|
||||
const serverBaseUrl = address.replaceAll(/(\/)+$/g, '');
|
||||
setServerAddress(serverBaseUrl);
|
||||
requestManager.reset();
|
||||
};
|
||||
|
||||
const updateSetting = <Setting extends keyof ServerSettingsType>(
|
||||
const updateSetting = async <Setting extends keyof ServerSettingsType>(
|
||||
setting: Setting,
|
||||
value: ServerSettingsType[Setting],
|
||||
onCompletion?: (success: boolean) => void,
|
||||
) => {
|
||||
mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
|
||||
);
|
||||
try {
|
||||
await mutateSettings({ variables: { input: { settings: { [setting]: value } } } });
|
||||
onCompletion?.(true);
|
||||
} catch (e) {
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e));
|
||||
onCompletion?.(false);
|
||||
}
|
||||
};
|
||||
|
||||
const localSettings = useMemo(
|
||||
@@ -128,12 +131,7 @@ export const ServerSettings = () => {
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<TextSetting
|
||||
settingName={t('settings.about.server.label.address')}
|
||||
handleChange={handleServerAddressChange}
|
||||
value={serverAddress}
|
||||
placeholder="http://localhost:4567"
|
||||
/>
|
||||
<ServerAddressSetting />
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('global.update.settings.inform.label.title')}
|
||||
@@ -147,7 +145,7 @@ export const ServerSettings = () => {
|
||||
</ListItem>
|
||||
</List>
|
||||
),
|
||||
[serverAddress, serverInformAvailableUpdate],
|
||||
[serverInformAvailableUpdate],
|
||||
);
|
||||
|
||||
const loading = areMetadataServerSettingsLoading || areServerSettingsLoading;
|
||||
@@ -187,8 +185,8 @@ export const ServerSettings = () => {
|
||||
}
|
||||
|
||||
const serverSettings = extractServerSettings(data!.settings);
|
||||
const authModeDisabled = !serverSettings.authUsername.trim() || !serverSettings.authPassword.trim();
|
||||
|
||||
const authModeDisabled = !serverSettings.authUsername?.trim() || !serverSettings.authPassword?.trim();
|
||||
console.log('asdf', serverSettings.jwtTokenExpiry, serverSettings.jwtRefreshExpiry);
|
||||
return (
|
||||
<List sx={{ pt: 0 }}>
|
||||
{localSettings}
|
||||
@@ -271,7 +269,19 @@ export const ServerSettings = () => {
|
||||
settingName={t('settings.server.auth.label.title')}
|
||||
value={serverSettings.authMode}
|
||||
values={AUTH_MODES_SELECT_VALUES}
|
||||
handleChange={(mode) => updateSetting('authMode', mode)}
|
||||
handleChange={(mode) => {
|
||||
updateSetting('authMode', mode, (success) => {
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode !== AuthMode.UiLogin) {
|
||||
AuthManager.removeTokens();
|
||||
}
|
||||
|
||||
AuthManager.setAuthRequired(mode === AuthMode.UiLogin);
|
||||
});
|
||||
}}
|
||||
disabled={authModeDisabled}
|
||||
/>
|
||||
<TextSetting
|
||||
@@ -287,6 +297,41 @@ export const ServerSettings = () => {
|
||||
validate={(value) => serverSettings.authMode === AuthMode.None || !!value.trim()}
|
||||
handleChange={(authPassword) => updateSetting('authPassword', authPassword)}
|
||||
/>
|
||||
{serverSettings.authMode === AuthMode.UiLogin && (
|
||||
<>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.auth.jwt.audience')}
|
||||
value={serverSettings.jwtAudience}
|
||||
handleChange={(audience) => updateSetting('jwtAudience', audience)}
|
||||
/>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.auth.jwt.access_token_expiry')}
|
||||
settingValue={dayjs.duration(serverSettings.jwtTokenExpiry).humanize()}
|
||||
value={dayjs.duration(serverSettings.jwtTokenExpiry).asMinutes()}
|
||||
valueUnit={t('global.time.minutes.minute_other')}
|
||||
defaultValue={JWT_ACCESS_TOKEN_EXPIRY.default}
|
||||
minValue={JWT_ACCESS_TOKEN_EXPIRY.min}
|
||||
maxValue={JWT_ACCESS_TOKEN_EXPIRY.max}
|
||||
handleUpdate={(expiry) =>
|
||||
updateSetting('jwtTokenExpiry', dayjs.duration(expiry, 'minute').toISOString())
|
||||
}
|
||||
showSlider
|
||||
/>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.auth.jwt.refresh_token_expiry')}
|
||||
settingValue={dayjs.duration(serverSettings.jwtRefreshExpiry).humanize()}
|
||||
value={dayjs.duration(serverSettings.jwtRefreshExpiry).asDays()}
|
||||
valueUnit={t('global.time.days.day_other')}
|
||||
defaultValue={JWT_REFRESH_TOKEN_EXPIRY.default}
|
||||
minValue={JWT_REFRESH_TOKEN_EXPIRY.min}
|
||||
maxValue={JWT_REFRESH_TOKEN_EXPIRY.max}
|
||||
handleUpdate={(expiry) =>
|
||||
updateSetting('jwtRefreshExpiry', dayjs.duration(expiry, 'day').toISOString())
|
||||
}
|
||||
showSlider
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</List>
|
||||
<List
|
||||
subheader={
|
||||
|
||||
@@ -13,7 +13,6 @@ import ListItemButton from '@mui/material/ListItemButton';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import ListItemAvatar from '@mui/material/ListItemAvatar';
|
||||
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
@@ -28,6 +27,7 @@ import { Trackers } from '@/features/tracker/services/Trackers.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { useLocalStorage } from '@/base/hooks/useStorage.tsx';
|
||||
import { TTrackerSearch } from '@/features/tracker/Tracker.types.ts';
|
||||
import { AvatarSpinner } from '@/base/components/AvatarSpinner.tsx';
|
||||
|
||||
export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -86,11 +86,15 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) =>
|
||||
<>
|
||||
<ListItemButton {...bindTrigger(popupState)} onClick={() => onClick(popupState.open)}>
|
||||
<ListItemAvatar sx={{ paddingRight: '20px' }}>
|
||||
<Avatar
|
||||
<AvatarSpinner
|
||||
alt={`${tracker.name}`}
|
||||
src={requestManager.getValidImgUrlFor(tracker.icon)}
|
||||
variant="rounded"
|
||||
sx={{ width: 64, height: 64 }}
|
||||
iconUrl={requestManager.getValidImgUrlFor(tracker.icon)}
|
||||
slots={{
|
||||
avatarProps: {
|
||||
variant: 'rounded',
|
||||
sx: { width: 64, height: 64 },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={tracker.name} />
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
@@ -44,6 +43,7 @@ import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx';
|
||||
import { TrackRecordType } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { TTrackerBind, TTrackRecordBind } from '@/features/tracker/Tracker.types.ts';
|
||||
import { AvatarSpinner } from '@/base/components/AvatarSpinner.tsx';
|
||||
|
||||
const TrackerActiveLink = ({ children, url }: { children: React.ReactNode; url: string }) => (
|
||||
<Link href={url} rel="noreferrer" target="_blank" underline="none" color="inherit">
|
||||
@@ -203,11 +203,15 @@ const TrackerActiveHeader = ({
|
||||
}
|
||||
>
|
||||
<TrackerActiveLink url={trackRecord.remoteUrl}>
|
||||
<Avatar
|
||||
<AvatarSpinner
|
||||
alt={`${tracker.name}`}
|
||||
src={requestManager.getValidImgUrlFor(tracker.icon)}
|
||||
variant="rounded"
|
||||
sx={{ width: 64, height: 64 }}
|
||||
iconUrl={requestManager.getValidImgUrlFor(tracker.icon)}
|
||||
slots={{
|
||||
avatarProps: {
|
||||
variant: 'rounded',
|
||||
sx: { width: 64, height: 64 },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</TrackerActiveLink>
|
||||
</Badge>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Button from '@mui/material/Button';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Stack from '@mui/material/Stack';
|
||||
@@ -16,6 +15,7 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { CARD_STYLING } from '@/features/tracker/Tracker.constants.ts';
|
||||
|
||||
import { TTrackerBase } from '@/features/tracker/Tracker.types.ts';
|
||||
import { AvatarSpinner } from '@/base/components/AvatarSpinner.tsx';
|
||||
|
||||
export const TrackerUntrackedCard = ({
|
||||
tracker,
|
||||
@@ -35,11 +35,15 @@ export const TrackerUntrackedCard = ({
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
<AvatarSpinner
|
||||
alt={`${tracker.name}`}
|
||||
src={requestManager.getValidImgUrlFor(tracker.icon)}
|
||||
variant="rounded"
|
||||
sx={{ width: 64, height: 64 }}
|
||||
iconUrl={requestManager.getValidImgUrlFor(tracker.icon)}
|
||||
slots={{
|
||||
avatarProps: {
|
||||
variant: 'rounded',
|
||||
sx: { width: 64, height: 64 },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button sx={{ flexGrow: '1' }} onClick={onClick}>
|
||||
{t('tracking.action.button.add_tracking')}
|
||||
|
||||
@@ -7,14 +7,13 @@
|
||||
*/
|
||||
|
||||
import '@/polyfill.manual';
|
||||
import '@fontsource/roboto';
|
||||
import '@/lib/dayjs/Setup.ts';
|
||||
import '@/index.css';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App } from '@/App';
|
||||
import '@/index.css';
|
||||
// roboto font
|
||||
import '@fontsource/roboto';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
import '@/lib/dayjs/Setup.ts';
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then((registration) => {
|
||||
|
||||
@@ -12,6 +12,7 @@ import calendar from 'dayjs/plugin/calendar';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import isToday from 'dayjs/plugin/isToday';
|
||||
import isYesterday from 'dayjs/plugin/isYesterday';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import { importDayJsLocale } from '@/lib/dayjs/LocaleImporter.ts';
|
||||
// import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
// import updateLocale from 'dayjs/plugin/updateLocale';
|
||||
@@ -22,6 +23,7 @@ dayjs.extend(calendar);
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(isToday);
|
||||
dayjs.extend(isYesterday);
|
||||
dayjs.extend(duration);
|
||||
// dayjs.extend(localizedFormat);
|
||||
// dayjs.extend(updateLocale);
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ export const SERVER_SETTINGS = gql`
|
||||
authMode
|
||||
authPassword
|
||||
authUsername
|
||||
jwtAudience
|
||||
jwtTokenExpiry
|
||||
jwtRefreshExpiry
|
||||
|
||||
# misc
|
||||
debugLogsEnabled
|
||||
|
||||
@@ -110,10 +110,11 @@ export type CheckBoxFilterFieldPolicy = {
|
||||
default?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
name?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type CheckBoxPreferenceKeySpecifier = ('currentValue' | 'default' | 'key' | 'summary' | 'title' | 'visible' | CheckBoxPreferenceKeySpecifier)[];
|
||||
export type CheckBoxPreferenceKeySpecifier = ('currentValue' | 'default' | 'enabled' | 'key' | 'summary' | 'title' | 'visible' | CheckBoxPreferenceKeySpecifier)[];
|
||||
export type CheckBoxPreferenceFieldPolicy = {
|
||||
currentValue?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
default?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
enabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
key?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
summary?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
title?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -245,12 +246,13 @@ export type EdgeFieldPolicy = {
|
||||
cursor?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
node?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type EditTextPreferenceKeySpecifier = ('currentValue' | 'default' | 'dialogMessage' | 'dialogTitle' | 'key' | 'summary' | 'text' | 'title' | 'visible' | EditTextPreferenceKeySpecifier)[];
|
||||
export type EditTextPreferenceKeySpecifier = ('currentValue' | 'default' | 'dialogMessage' | 'dialogTitle' | 'enabled' | 'key' | 'summary' | 'text' | 'title' | 'visible' | EditTextPreferenceKeySpecifier)[];
|
||||
export type EditTextPreferenceFieldPolicy = {
|
||||
currentValue?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
default?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
dialogMessage?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
dialogTitle?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
enabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
key?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
summary?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
text?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -295,11 +297,12 @@ export type ExtensionTypeFieldPolicy = {
|
||||
versionCode?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
versionName?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type FetchChapterPagesPayloadKeySpecifier = ('chapter' | 'clientMutationId' | 'pages' | FetchChapterPagesPayloadKeySpecifier)[];
|
||||
export type FetchChapterPagesPayloadKeySpecifier = ('chapter' | 'clientMutationId' | 'pages' | 'syncConflict' | FetchChapterPagesPayloadKeySpecifier)[];
|
||||
export type FetchChapterPagesPayloadFieldPolicy = {
|
||||
chapter?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
pages?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
pages?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
syncConflict?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type FetchChaptersPayloadKeySpecifier = ('chapters' | 'clientMutationId' | FetchChaptersPayloadKeySpecifier)[];
|
||||
export type FetchChaptersPayloadFieldPolicy = {
|
||||
@@ -353,6 +356,19 @@ export type InstallExternalExtensionPayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
extension?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type KoSyncConnectPayloadKeySpecifier = ('clientMutationId' | 'message' | 'settings' | 'success' | 'username' | KoSyncConnectPayloadKeySpecifier)[];
|
||||
export type KoSyncConnectPayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
message?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
settings?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
success?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
username?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type KoSyncStatusPayloadKeySpecifier = ('isLoggedIn' | 'username' | KoSyncStatusPayloadKeySpecifier)[];
|
||||
export type KoSyncStatusPayloadFieldPolicy = {
|
||||
isLoggedIn?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
username?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type LastUpdateTimestampPayloadKeySpecifier = ('timestamp' | LastUpdateTimestampPayloadKeySpecifier)[];
|
||||
export type LastUpdateTimestampPayloadFieldPolicy = {
|
||||
timestamp?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
@@ -363,10 +379,11 @@ export type LibraryUpdateStatusFieldPolicy = {
|
||||
jobsInfo?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
mangaUpdates?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type ListPreferenceKeySpecifier = ('currentValue' | 'default' | 'entries' | 'entryValues' | 'key' | 'summary' | 'title' | 'visible' | ListPreferenceKeySpecifier)[];
|
||||
export type ListPreferenceKeySpecifier = ('currentValue' | 'default' | 'enabled' | 'entries' | 'entryValues' | 'key' | 'summary' | 'title' | 'visible' | ListPreferenceKeySpecifier)[];
|
||||
export type ListPreferenceFieldPolicy = {
|
||||
currentValue?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
default?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
enabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
entries?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
entryValues?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
key?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -374,6 +391,12 @@ export type ListPreferenceFieldPolicy = {
|
||||
title?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
visible?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type LoginPayloadKeySpecifier = ('accessToken' | 'clientMutationId' | 'refreshToken' | LoginPayloadKeySpecifier)[];
|
||||
export type LoginPayloadFieldPolicy = {
|
||||
accessToken?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
refreshToken?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type LoginTrackerCredentialsPayloadKeySpecifier = ('clientMutationId' | 'isLoggedIn' | 'tracker' | LoginTrackerCredentialsPayloadKeySpecifier)[];
|
||||
export type LoginTrackerCredentialsPayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -386,6 +409,12 @@ export type LoginTrackerOAuthPayloadFieldPolicy = {
|
||||
isLoggedIn?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
tracker?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type LogoutKoSyncAccountPayloadKeySpecifier = ('clientMutationId' | 'settings' | 'success' | LogoutKoSyncAccountPayloadKeySpecifier)[];
|
||||
export type LogoutKoSyncAccountPayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
settings?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
success?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type LogoutTrackerPayloadKeySpecifier = ('clientMutationId' | 'isLoggedIn' | 'tracker' | LogoutTrackerPayloadKeySpecifier)[];
|
||||
export type LogoutTrackerPayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -464,12 +493,13 @@ export type MetaTypeFieldPolicy = {
|
||||
key?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
value?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type MultiSelectListPreferenceKeySpecifier = ('currentValue' | 'default' | 'dialogMessage' | 'dialogTitle' | 'entries' | 'entryValues' | 'key' | 'summary' | 'title' | 'visible' | MultiSelectListPreferenceKeySpecifier)[];
|
||||
export type MultiSelectListPreferenceKeySpecifier = ('currentValue' | 'default' | 'dialogMessage' | 'dialogTitle' | 'enabled' | 'entries' | 'entryValues' | 'key' | 'summary' | 'title' | 'visible' | MultiSelectListPreferenceKeySpecifier)[];
|
||||
export type MultiSelectListPreferenceFieldPolicy = {
|
||||
currentValue?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
default?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
dialogMessage?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
dialogTitle?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
enabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
entries?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
entryValues?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
key?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -477,11 +507,12 @@ export type MultiSelectListPreferenceFieldPolicy = {
|
||||
title?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
visible?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type MutationKeySpecifier = ('bindTrack' | 'clearCachedImages' | 'clearDownloader' | 'createBackup' | 'createCategory' | 'deleteCategory' | 'deleteCategoryMeta' | 'deleteChapterMeta' | 'deleteDownloadedChapter' | 'deleteDownloadedChapters' | 'deleteGlobalMeta' | 'deleteMangaMeta' | 'deleteSourceMeta' | 'dequeueChapterDownload' | 'dequeueChapterDownloads' | 'enqueueChapterDownload' | 'enqueueChapterDownloads' | 'fetchChapterPages' | 'fetchChapters' | 'fetchExtensions' | 'fetchManga' | 'fetchSourceManga' | 'fetchTrack' | 'installExternalExtension' | 'loginTrackerCredentials' | 'loginTrackerOAuth' | 'logoutTracker' | 'reorderChapterDownload' | 'resetSettings' | 'resetWebUIUpdateStatus' | 'restoreBackup' | 'setCategoryMeta' | 'setChapterMeta' | 'setGlobalMeta' | 'setMangaMeta' | 'setSettings' | 'setSourceMeta' | 'startDownloader' | 'stopDownloader' | 'trackProgress' | 'unbindTrack' | 'updateCategories' | 'updateCategory' | 'updateCategoryManga' | 'updateCategoryOrder' | 'updateChapter' | 'updateChapters' | 'updateExtension' | 'updateExtensions' | 'updateLibrary' | 'updateLibraryManga' | 'updateManga' | 'updateMangaCategories' | 'updateMangas' | 'updateMangasCategories' | 'updateSourcePreference' | 'updateStop' | 'updateTrack' | 'updateWebUI' | MutationKeySpecifier)[];
|
||||
export type MutationKeySpecifier = ('bindTrack' | 'clearCachedImages' | 'clearDownloader' | 'connectKoSyncAccount' | 'createBackup' | 'createCategory' | 'deleteCategory' | 'deleteCategoryMeta' | 'deleteChapterMeta' | 'deleteDownloadedChapter' | 'deleteDownloadedChapters' | 'deleteGlobalMeta' | 'deleteMangaMeta' | 'deleteSourceMeta' | 'dequeueChapterDownload' | 'dequeueChapterDownloads' | 'enqueueChapterDownload' | 'enqueueChapterDownloads' | 'fetchChapterPages' | 'fetchChapters' | 'fetchExtensions' | 'fetchManga' | 'fetchSourceManga' | 'fetchTrack' | 'installExternalExtension' | 'login' | 'loginTrackerCredentials' | 'loginTrackerOAuth' | 'logoutKoSyncAccount' | 'logoutTracker' | 'refreshToken' | 'reorderChapterDownload' | 'resetSettings' | 'resetWebUIUpdateStatus' | 'restoreBackup' | 'setCategoryMeta' | 'setChapterMeta' | 'setGlobalMeta' | 'setMangaMeta' | 'setSettings' | 'setSourceMeta' | 'startDownloader' | 'stopDownloader' | 'trackProgress' | 'unbindTrack' | 'updateCategories' | 'updateCategory' | 'updateCategoryManga' | 'updateCategoryOrder' | 'updateChapter' | 'updateChapters' | 'updateExtension' | 'updateExtensions' | 'updateLibrary' | 'updateLibraryManga' | 'updateManga' | 'updateMangaCategories' | 'updateMangas' | 'updateMangasCategories' | 'updateSourcePreference' | 'updateStop' | 'updateTrack' | 'updateWebUI' | MutationKeySpecifier)[];
|
||||
export type MutationFieldPolicy = {
|
||||
bindTrack?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
clearCachedImages?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
clearDownloader?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
connectKoSyncAccount?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
createBackup?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
createCategory?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
deleteCategory?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -503,9 +534,12 @@ export type MutationFieldPolicy = {
|
||||
fetchSourceManga?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
fetchTrack?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
installExternalExtension?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
login?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
loginTrackerCredentials?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
loginTrackerOAuth?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
logoutKoSyncAccount?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
logoutTracker?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
refreshToken?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
reorderChapterDownload?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
resetSettings?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
resetWebUIUpdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -553,7 +587,7 @@ export type PageInfoFieldPolicy = {
|
||||
hasPreviousPage?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
startCursor?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type PartialSettingsTypeKeySpecifier = ('authMode' | 'authPassword' | 'authUsername' | 'autoDownloadAheadLimit' | 'autoDownloadIgnoreReUploads' | 'autoDownloadNewChapters' | 'autoDownloadNewChaptersLimit' | 'backupInterval' | 'backupPath' | 'backupTTL' | 'backupTime' | 'basicAuthEnabled' | 'basicAuthPassword' | 'basicAuthUsername' | 'debugLogsEnabled' | 'downloadAsCbz' | 'downloadConversions' | 'downloadsPath' | 'electronPath' | 'excludeCompleted' | 'excludeEntryWithUnreadChapters' | 'excludeNotStarted' | 'excludeUnreadChapters' | 'extensionRepos' | 'flareSolverrAsResponseFallback' | 'flareSolverrEnabled' | 'flareSolverrSessionName' | 'flareSolverrSessionTtl' | 'flareSolverrTimeout' | 'flareSolverrUrl' | 'globalUpdateInterval' | 'gqlDebugLogsEnabled' | 'initialOpenInBrowserEnabled' | 'ip' | 'localSourcePath' | 'maxLogFileSize' | 'maxLogFiles' | 'maxLogFolderSize' | 'maxSourcesInParallel' | 'opdsChapterSortOrder' | 'opdsEnablePageReadProgress' | 'opdsItemsPerPage' | 'opdsMarkAsReadOnDownload' | 'opdsShowOnlyDownloadedChapters' | 'opdsShowOnlyUnreadChapters' | 'opdsUseBinaryFileSizes' | 'port' | 'socksProxyEnabled' | 'socksProxyHost' | 'socksProxyPassword' | 'socksProxyPort' | 'socksProxyUsername' | 'socksProxyVersion' | 'systemTrayEnabled' | 'updateMangas' | 'webUIChannel' | 'webUIFlavor' | 'webUIInterface' | 'webUIUpdateCheckInterval' | PartialSettingsTypeKeySpecifier)[];
|
||||
export type PartialSettingsTypeKeySpecifier = ('authMode' | 'authPassword' | 'authUsername' | 'autoDownloadAheadLimit' | 'autoDownloadIgnoreReUploads' | 'autoDownloadNewChapters' | 'autoDownloadNewChaptersLimit' | 'backupInterval' | 'backupPath' | 'backupTTL' | 'backupTime' | 'basicAuthEnabled' | 'basicAuthPassword' | 'basicAuthUsername' | 'debugLogsEnabled' | 'downloadAsCbz' | 'downloadConversions' | 'downloadsPath' | 'electronPath' | 'excludeCompleted' | 'excludeEntryWithUnreadChapters' | 'excludeNotStarted' | 'excludeUnreadChapters' | 'extensionRepos' | 'flareSolverrAsResponseFallback' | 'flareSolverrEnabled' | 'flareSolverrSessionName' | 'flareSolverrSessionTtl' | 'flareSolverrTimeout' | 'flareSolverrUrl' | 'globalUpdateInterval' | 'gqlDebugLogsEnabled' | 'initialOpenInBrowserEnabled' | 'ip' | 'jwtAudience' | 'jwtRefreshExpiry' | 'jwtTokenExpiry' | 'koreaderSyncChecksumMethod' | 'koreaderSyncDeviceId' | 'koreaderSyncPercentageTolerance' | 'koreaderSyncServerUrl' | 'koreaderSyncStrategy' | 'koreaderSyncUserkey' | 'koreaderSyncUsername' | 'localSourcePath' | 'maxLogFileSize' | 'maxLogFiles' | 'maxLogFolderSize' | 'maxSourcesInParallel' | 'opdsChapterSortOrder' | 'opdsEnablePageReadProgress' | 'opdsItemsPerPage' | 'opdsMarkAsReadOnDownload' | 'opdsShowOnlyDownloadedChapters' | 'opdsShowOnlyUnreadChapters' | 'opdsUseBinaryFileSizes' | 'port' | 'socksProxyEnabled' | 'socksProxyHost' | 'socksProxyPassword' | 'socksProxyPort' | 'socksProxyUsername' | 'socksProxyVersion' | 'systemTrayEnabled' | 'updateMangas' | 'webUIChannel' | 'webUIFlavor' | 'webUIInterface' | 'webUIUpdateCheckInterval' | PartialSettingsTypeKeySpecifier)[];
|
||||
export type PartialSettingsTypeFieldPolicy = {
|
||||
authMode?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
authPassword?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -589,6 +623,16 @@ export type PartialSettingsTypeFieldPolicy = {
|
||||
gqlDebugLogsEnabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
initialOpenInBrowserEnabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
ip?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
jwtAudience?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
jwtRefreshExpiry?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
jwtTokenExpiry?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncChecksumMethod?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncDeviceId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncPercentageTolerance?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncServerUrl?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncStrategy?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncUserkey?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncUsername?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
localSourcePath?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
maxLogFileSize?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
maxLogFiles?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -615,7 +659,7 @@ export type PartialSettingsTypeFieldPolicy = {
|
||||
webUIInterface?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
webUIUpdateCheckInterval?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type QueryKeySpecifier = ('aboutServer' | 'aboutWebUI' | 'categories' | 'category' | 'chapter' | 'chapters' | 'checkForServerUpdates' | 'checkForWebUIUpdate' | 'downloadStatus' | 'extension' | 'extensions' | 'getWebUIUpdateStatus' | 'lastUpdateTimestamp' | 'libraryUpdateStatus' | 'manga' | 'mangas' | 'meta' | 'metas' | 'restoreStatus' | 'searchTracker' | 'settings' | 'source' | 'sources' | 'trackRecord' | 'trackRecords' | 'tracker' | 'trackers' | 'updateStatus' | 'validateBackup' | QueryKeySpecifier)[];
|
||||
export type QueryKeySpecifier = ('aboutServer' | 'aboutWebUI' | 'categories' | 'category' | 'chapter' | 'chapters' | 'checkForServerUpdates' | 'checkForWebUIUpdate' | 'downloadStatus' | 'extension' | 'extensions' | 'getWebUIUpdateStatus' | 'koSyncStatus' | 'lastUpdateTimestamp' | 'libraryUpdateStatus' | 'manga' | 'mangas' | 'meta' | 'metas' | 'restoreStatus' | 'searchTracker' | 'settings' | 'source' | 'sources' | 'trackRecord' | 'trackRecords' | 'tracker' | 'trackers' | 'updateStatus' | 'validateBackup' | QueryKeySpecifier)[];
|
||||
export type QueryFieldPolicy = {
|
||||
aboutServer?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
aboutWebUI?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -629,6 +673,7 @@ export type QueryFieldPolicy = {
|
||||
extension?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
extensions?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
getWebUIUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>>,
|
||||
koSyncStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
lastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
libraryUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetUpdateStatusQueryVariables>>,
|
||||
manga?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetMangaScreenQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetMangaScreenQueryVariables>>,
|
||||
@@ -647,6 +692,11 @@ export type QueryFieldPolicy = {
|
||||
updateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
validateBackup?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type RefreshTokenPayloadKeySpecifier = ('accessToken' | 'clientMutationId' | RefreshTokenPayloadKeySpecifier)[];
|
||||
export type RefreshTokenPayloadFieldPolicy = {
|
||||
accessToken?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type ReorderChapterDownloadPayloadKeySpecifier = ('clientMutationId' | 'downloadStatus' | ReorderChapterDownloadPayloadKeySpecifier)[];
|
||||
export type ReorderChapterDownloadPayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -707,7 +757,7 @@ export type SetSourceMetaPayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
meta?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type SettingsKeySpecifier = ('authMode' | 'authPassword' | 'authUsername' | 'autoDownloadAheadLimit' | 'autoDownloadIgnoreReUploads' | 'autoDownloadNewChapters' | 'autoDownloadNewChaptersLimit' | 'backupInterval' | 'backupPath' | 'backupTTL' | 'backupTime' | 'basicAuthEnabled' | 'basicAuthPassword' | 'basicAuthUsername' | 'debugLogsEnabled' | 'downloadAsCbz' | 'downloadConversions' | 'downloadsPath' | 'electronPath' | 'excludeCompleted' | 'excludeEntryWithUnreadChapters' | 'excludeNotStarted' | 'excludeUnreadChapters' | 'extensionRepos' | 'flareSolverrAsResponseFallback' | 'flareSolverrEnabled' | 'flareSolverrSessionName' | 'flareSolverrSessionTtl' | 'flareSolverrTimeout' | 'flareSolverrUrl' | 'globalUpdateInterval' | 'gqlDebugLogsEnabled' | 'initialOpenInBrowserEnabled' | 'ip' | 'localSourcePath' | 'maxLogFileSize' | 'maxLogFiles' | 'maxLogFolderSize' | 'maxSourcesInParallel' | 'opdsChapterSortOrder' | 'opdsEnablePageReadProgress' | 'opdsItemsPerPage' | 'opdsMarkAsReadOnDownload' | 'opdsShowOnlyDownloadedChapters' | 'opdsShowOnlyUnreadChapters' | 'opdsUseBinaryFileSizes' | 'port' | 'socksProxyEnabled' | 'socksProxyHost' | 'socksProxyPassword' | 'socksProxyPort' | 'socksProxyUsername' | 'socksProxyVersion' | 'systemTrayEnabled' | 'updateMangas' | 'webUIChannel' | 'webUIFlavor' | 'webUIInterface' | 'webUIUpdateCheckInterval' | SettingsKeySpecifier)[];
|
||||
export type SettingsKeySpecifier = ('authMode' | 'authPassword' | 'authUsername' | 'autoDownloadAheadLimit' | 'autoDownloadIgnoreReUploads' | 'autoDownloadNewChapters' | 'autoDownloadNewChaptersLimit' | 'backupInterval' | 'backupPath' | 'backupTTL' | 'backupTime' | 'basicAuthEnabled' | 'basicAuthPassword' | 'basicAuthUsername' | 'debugLogsEnabled' | 'downloadAsCbz' | 'downloadConversions' | 'downloadsPath' | 'electronPath' | 'excludeCompleted' | 'excludeEntryWithUnreadChapters' | 'excludeNotStarted' | 'excludeUnreadChapters' | 'extensionRepos' | 'flareSolverrAsResponseFallback' | 'flareSolverrEnabled' | 'flareSolverrSessionName' | 'flareSolverrSessionTtl' | 'flareSolverrTimeout' | 'flareSolverrUrl' | 'globalUpdateInterval' | 'gqlDebugLogsEnabled' | 'initialOpenInBrowserEnabled' | 'ip' | 'jwtAudience' | 'jwtRefreshExpiry' | 'jwtTokenExpiry' | 'koreaderSyncChecksumMethod' | 'koreaderSyncDeviceId' | 'koreaderSyncPercentageTolerance' | 'koreaderSyncServerUrl' | 'koreaderSyncStrategy' | 'koreaderSyncUserkey' | 'koreaderSyncUsername' | 'localSourcePath' | 'maxLogFileSize' | 'maxLogFiles' | 'maxLogFolderSize' | 'maxSourcesInParallel' | 'opdsChapterSortOrder' | 'opdsEnablePageReadProgress' | 'opdsItemsPerPage' | 'opdsMarkAsReadOnDownload' | 'opdsShowOnlyDownloadedChapters' | 'opdsShowOnlyUnreadChapters' | 'opdsUseBinaryFileSizes' | 'port' | 'socksProxyEnabled' | 'socksProxyHost' | 'socksProxyPassword' | 'socksProxyPort' | 'socksProxyUsername' | 'socksProxyVersion' | 'systemTrayEnabled' | 'updateMangas' | 'webUIChannel' | 'webUIFlavor' | 'webUIInterface' | 'webUIUpdateCheckInterval' | SettingsKeySpecifier)[];
|
||||
export type SettingsFieldPolicy = {
|
||||
authMode?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
authPassword?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -743,6 +793,16 @@ export type SettingsFieldPolicy = {
|
||||
gqlDebugLogsEnabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
initialOpenInBrowserEnabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
ip?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
jwtAudience?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
jwtRefreshExpiry?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
jwtTokenExpiry?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncChecksumMethod?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncDeviceId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncPercentageTolerance?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncServerUrl?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncStrategy?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncUserkey?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncUsername?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
localSourcePath?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
maxLogFileSize?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
maxLogFiles?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -781,7 +841,7 @@ export type SettingsDownloadConversionTypeFieldPolicy = {
|
||||
mimeType?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
target?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type SettingsTypeKeySpecifier = ('authMode' | 'authPassword' | 'authUsername' | 'autoDownloadAheadLimit' | 'autoDownloadIgnoreReUploads' | 'autoDownloadNewChapters' | 'autoDownloadNewChaptersLimit' | 'backupInterval' | 'backupPath' | 'backupTTL' | 'backupTime' | 'basicAuthEnabled' | 'basicAuthPassword' | 'basicAuthUsername' | 'debugLogsEnabled' | 'downloadAsCbz' | 'downloadConversions' | 'downloadsPath' | 'electronPath' | 'excludeCompleted' | 'excludeEntryWithUnreadChapters' | 'excludeNotStarted' | 'excludeUnreadChapters' | 'extensionRepos' | 'flareSolverrAsResponseFallback' | 'flareSolverrEnabled' | 'flareSolverrSessionName' | 'flareSolverrSessionTtl' | 'flareSolverrTimeout' | 'flareSolverrUrl' | 'globalUpdateInterval' | 'gqlDebugLogsEnabled' | 'initialOpenInBrowserEnabled' | 'ip' | 'localSourcePath' | 'maxLogFileSize' | 'maxLogFiles' | 'maxLogFolderSize' | 'maxSourcesInParallel' | 'opdsChapterSortOrder' | 'opdsEnablePageReadProgress' | 'opdsItemsPerPage' | 'opdsMarkAsReadOnDownload' | 'opdsShowOnlyDownloadedChapters' | 'opdsShowOnlyUnreadChapters' | 'opdsUseBinaryFileSizes' | 'port' | 'socksProxyEnabled' | 'socksProxyHost' | 'socksProxyPassword' | 'socksProxyPort' | 'socksProxyUsername' | 'socksProxyVersion' | 'systemTrayEnabled' | 'updateMangas' | 'webUIChannel' | 'webUIFlavor' | 'webUIInterface' | 'webUIUpdateCheckInterval' | SettingsTypeKeySpecifier)[];
|
||||
export type SettingsTypeKeySpecifier = ('authMode' | 'authPassword' | 'authUsername' | 'autoDownloadAheadLimit' | 'autoDownloadIgnoreReUploads' | 'autoDownloadNewChapters' | 'autoDownloadNewChaptersLimit' | 'backupInterval' | 'backupPath' | 'backupTTL' | 'backupTime' | 'basicAuthEnabled' | 'basicAuthPassword' | 'basicAuthUsername' | 'debugLogsEnabled' | 'downloadAsCbz' | 'downloadConversions' | 'downloadsPath' | 'electronPath' | 'excludeCompleted' | 'excludeEntryWithUnreadChapters' | 'excludeNotStarted' | 'excludeUnreadChapters' | 'extensionRepos' | 'flareSolverrAsResponseFallback' | 'flareSolverrEnabled' | 'flareSolverrSessionName' | 'flareSolverrSessionTtl' | 'flareSolverrTimeout' | 'flareSolverrUrl' | 'globalUpdateInterval' | 'gqlDebugLogsEnabled' | 'initialOpenInBrowserEnabled' | 'ip' | 'jwtAudience' | 'jwtRefreshExpiry' | 'jwtTokenExpiry' | 'koreaderSyncChecksumMethod' | 'koreaderSyncDeviceId' | 'koreaderSyncPercentageTolerance' | 'koreaderSyncServerUrl' | 'koreaderSyncStrategy' | 'koreaderSyncUserkey' | 'koreaderSyncUsername' | 'localSourcePath' | 'maxLogFileSize' | 'maxLogFiles' | 'maxLogFolderSize' | 'maxSourcesInParallel' | 'opdsChapterSortOrder' | 'opdsEnablePageReadProgress' | 'opdsItemsPerPage' | 'opdsMarkAsReadOnDownload' | 'opdsShowOnlyDownloadedChapters' | 'opdsShowOnlyUnreadChapters' | 'opdsUseBinaryFileSizes' | 'port' | 'socksProxyEnabled' | 'socksProxyHost' | 'socksProxyPassword' | 'socksProxyPort' | 'socksProxyUsername' | 'socksProxyVersion' | 'systemTrayEnabled' | 'updateMangas' | 'webUIChannel' | 'webUIFlavor' | 'webUIInterface' | 'webUIUpdateCheckInterval' | SettingsTypeKeySpecifier)[];
|
||||
export type SettingsTypeFieldPolicy = {
|
||||
authMode?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
authPassword?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -817,6 +877,16 @@ export type SettingsTypeFieldPolicy = {
|
||||
gqlDebugLogsEnabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
initialOpenInBrowserEnabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
ip?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
jwtAudience?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
jwtRefreshExpiry?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
jwtTokenExpiry?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncChecksumMethod?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncDeviceId?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncPercentageTolerance?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncServerUrl?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncStrategy?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncUserkey?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
koreaderSyncUsername?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
localSourcePath?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
maxLogFileSize?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
maxLogFiles?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -873,8 +943,9 @@ export type SourceNodeListFieldPolicy = {
|
||||
pageInfo?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
totalCount?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type SourceTypeKeySpecifier = ('displayName' | 'extension' | 'filters' | 'iconUrl' | 'id' | 'isConfigurable' | 'isNsfw' | 'lang' | 'manga' | 'meta' | 'name' | 'preferences' | 'supportsLatest' | SourceTypeKeySpecifier)[];
|
||||
export type SourceTypeKeySpecifier = ('baseUrl' | 'displayName' | 'extension' | 'filters' | 'iconUrl' | 'id' | 'isConfigurable' | 'isNsfw' | 'lang' | 'manga' | 'meta' | 'name' | 'preferences' | 'supportsLatest' | SourceTypeKeySpecifier)[];
|
||||
export type SourceTypeFieldPolicy = {
|
||||
baseUrl?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
displayName?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
extension?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
filters?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -907,15 +978,21 @@ export type SubscriptionFieldPolicy = {
|
||||
updateStatusChanged?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
webUIUpdateStatusChange?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type SwitchPreferenceKeySpecifier = ('currentValue' | 'default' | 'key' | 'summary' | 'title' | 'visible' | SwitchPreferenceKeySpecifier)[];
|
||||
export type SwitchPreferenceKeySpecifier = ('currentValue' | 'default' | 'enabled' | 'key' | 'summary' | 'title' | 'visible' | SwitchPreferenceKeySpecifier)[];
|
||||
export type SwitchPreferenceFieldPolicy = {
|
||||
currentValue?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
default?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
enabled?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
key?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
summary?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
title?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
visible?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type SyncConflictInfoTypeKeySpecifier = ('deviceName' | 'remotePage' | SyncConflictInfoTypeKeySpecifier)[];
|
||||
export type SyncConflictInfoTypeFieldPolicy = {
|
||||
deviceName?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
remotePage?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};
|
||||
export type TextFilterKeySpecifier = ('default' | 'name' | TextFilterKeySpecifier)[];
|
||||
export type TextFilterFieldPolicy = {
|
||||
default?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -1396,6 +1473,14 @@ export type StrictTypedTypePolicies = {
|
||||
keyFields?: false | InstallExternalExtensionPayloadKeySpecifier | (() => undefined | InstallExternalExtensionPayloadKeySpecifier),
|
||||
fields?: InstallExternalExtensionPayloadFieldPolicy,
|
||||
},
|
||||
KoSyncConnectPayload?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | KoSyncConnectPayloadKeySpecifier | (() => undefined | KoSyncConnectPayloadKeySpecifier),
|
||||
fields?: KoSyncConnectPayloadFieldPolicy,
|
||||
},
|
||||
KoSyncStatusPayload?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | KoSyncStatusPayloadKeySpecifier | (() => undefined | KoSyncStatusPayloadKeySpecifier),
|
||||
fields?: KoSyncStatusPayloadFieldPolicy,
|
||||
},
|
||||
LastUpdateTimestampPayload?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | LastUpdateTimestampPayloadKeySpecifier | (() => undefined | LastUpdateTimestampPayloadKeySpecifier),
|
||||
fields?: LastUpdateTimestampPayloadFieldPolicy,
|
||||
@@ -1408,6 +1493,10 @@ export type StrictTypedTypePolicies = {
|
||||
keyFields?: false | ListPreferenceKeySpecifier | (() => undefined | ListPreferenceKeySpecifier),
|
||||
fields?: ListPreferenceFieldPolicy,
|
||||
},
|
||||
LoginPayload?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | LoginPayloadKeySpecifier | (() => undefined | LoginPayloadKeySpecifier),
|
||||
fields?: LoginPayloadFieldPolicy,
|
||||
},
|
||||
LoginTrackerCredentialsPayload?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | LoginTrackerCredentialsPayloadKeySpecifier | (() => undefined | LoginTrackerCredentialsPayloadKeySpecifier),
|
||||
fields?: LoginTrackerCredentialsPayloadFieldPolicy,
|
||||
@@ -1416,6 +1505,10 @@ export type StrictTypedTypePolicies = {
|
||||
keyFields?: false | LoginTrackerOAuthPayloadKeySpecifier | (() => undefined | LoginTrackerOAuthPayloadKeySpecifier),
|
||||
fields?: LoginTrackerOAuthPayloadFieldPolicy,
|
||||
},
|
||||
LogoutKoSyncAccountPayload?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | LogoutKoSyncAccountPayloadKeySpecifier | (() => undefined | LogoutKoSyncAccountPayloadKeySpecifier),
|
||||
fields?: LogoutKoSyncAccountPayloadFieldPolicy,
|
||||
},
|
||||
LogoutTrackerPayload?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | LogoutTrackerPayloadKeySpecifier | (() => undefined | LogoutTrackerPayloadKeySpecifier),
|
||||
fields?: LogoutTrackerPayloadFieldPolicy,
|
||||
@@ -1472,6 +1565,10 @@ export type StrictTypedTypePolicies = {
|
||||
keyFields?: false | QueryKeySpecifier | (() => undefined | QueryKeySpecifier),
|
||||
fields?: QueryFieldPolicy,
|
||||
},
|
||||
RefreshTokenPayload?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | RefreshTokenPayloadKeySpecifier | (() => undefined | RefreshTokenPayloadKeySpecifier),
|
||||
fields?: RefreshTokenPayloadFieldPolicy,
|
||||
},
|
||||
ReorderChapterDownloadPayload?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | ReorderChapterDownloadPayloadKeySpecifier | (() => undefined | ReorderChapterDownloadPayloadKeySpecifier),
|
||||
fields?: ReorderChapterDownloadPayloadFieldPolicy,
|
||||
@@ -1576,6 +1673,10 @@ export type StrictTypedTypePolicies = {
|
||||
keyFields?: false | SwitchPreferenceKeySpecifier | (() => undefined | SwitchPreferenceKeySpecifier),
|
||||
fields?: SwitchPreferenceFieldPolicy,
|
||||
},
|
||||
SyncConflictInfoType?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | SyncConflictInfoTypeKeySpecifier | (() => undefined | SyncConflictInfoTypeKeySpecifier),
|
||||
fields?: SyncConflictInfoTypeFieldPolicy,
|
||||
},
|
||||
TextFilter?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | TextFilterKeySpecifier | (() => undefined | TextFilterKeySpecifier),
|
||||
fields?: TextFilterFieldPolicy,
|
||||
|
||||
@@ -13,6 +13,7 @@ export type Scalars = {
|
||||
Int: { input: number; output: number; }
|
||||
Float: { input: number; output: number; }
|
||||
Cursor: { input: string; output: string; }
|
||||
Duration: { input: any; output: any; }
|
||||
LongString: { input: string; output: string; }
|
||||
Upload: { input: any; output: any; }
|
||||
};
|
||||
@@ -38,7 +39,8 @@ export type AboutWebUi = {
|
||||
export enum AuthMode {
|
||||
BasicAuth = 'BASIC_AUTH',
|
||||
None = 'NONE',
|
||||
SimpleLogin = 'SIMPLE_LOGIN'
|
||||
SimpleLogin = 'SIMPLE_LOGIN',
|
||||
UiLogin = 'UI_LOGIN'
|
||||
}
|
||||
|
||||
export enum BackupRestoreState {
|
||||
@@ -287,6 +289,7 @@ export type CheckBoxPreference = {
|
||||
__typename?: 'CheckBoxPreference';
|
||||
currentValue?: Maybe<Scalars['Boolean']['output']>;
|
||||
default: Scalars['Boolean']['output'];
|
||||
enabled: Scalars['Boolean']['output'];
|
||||
key: Scalars['String']['output'];
|
||||
summary?: Maybe<Scalars['String']['output']>;
|
||||
title?: Maybe<Scalars['String']['output']>;
|
||||
@@ -325,6 +328,12 @@ export type ClearDownloaderPayload = {
|
||||
downloadStatus: DownloadStatus;
|
||||
};
|
||||
|
||||
export type ConnectKoSyncAccountInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
password: Scalars['String']['input'];
|
||||
username: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type CreateBackupInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
includeCategories?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
@@ -576,6 +585,7 @@ export type EditTextPreference = {
|
||||
default?: Maybe<Scalars['String']['output']>;
|
||||
dialogMessage?: Maybe<Scalars['String']['output']>;
|
||||
dialogTitle?: Maybe<Scalars['String']['output']>;
|
||||
enabled: Scalars['Boolean']['output'];
|
||||
key: Scalars['String']['output'];
|
||||
summary?: Maybe<Scalars['String']['output']>;
|
||||
text?: Maybe<Scalars['String']['output']>;
|
||||
@@ -691,6 +701,7 @@ export type FetchChapterPagesPayload = {
|
||||
chapter: ChapterType;
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
pages: Array<Scalars['String']['output']>;
|
||||
syncConflict?: Maybe<SyncConflictInfoType>;
|
||||
};
|
||||
|
||||
export type FetchChaptersInput = {
|
||||
@@ -853,6 +864,34 @@ export type IntFilterInput = {
|
||||
notIn?: InputMaybe<Array<Scalars['Int']['input']>>;
|
||||
};
|
||||
|
||||
export type KoSyncConnectPayload = {
|
||||
__typename?: 'KoSyncConnectPayload';
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
message?: Maybe<Scalars['String']['output']>;
|
||||
settings: SettingsType;
|
||||
success: Scalars['Boolean']['output'];
|
||||
username?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type KoSyncStatusPayload = {
|
||||
__typename?: 'KoSyncStatusPayload';
|
||||
isLoggedIn: Scalars['Boolean']['output'];
|
||||
username?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export enum KoreaderSyncChecksumMethod {
|
||||
Binary = 'BINARY',
|
||||
Filename = 'FILENAME'
|
||||
}
|
||||
|
||||
export enum KoreaderSyncStrategy {
|
||||
Disabled = 'DISABLED',
|
||||
Prompt = 'PROMPT',
|
||||
Receive = 'RECEIVE',
|
||||
Send = 'SEND',
|
||||
Silent = 'SILENT'
|
||||
}
|
||||
|
||||
export type LastUpdateTimestampPayload = {
|
||||
__typename?: 'LastUpdateTimestampPayload';
|
||||
timestamp: Scalars['LongString']['output'];
|
||||
@@ -874,6 +913,7 @@ export type ListPreference = {
|
||||
__typename?: 'ListPreference';
|
||||
currentValue?: Maybe<Scalars['String']['output']>;
|
||||
default?: Maybe<Scalars['String']['output']>;
|
||||
enabled: Scalars['Boolean']['output'];
|
||||
entries: Array<Scalars['String']['output']>;
|
||||
entryValues: Array<Scalars['String']['output']>;
|
||||
key: Scalars['String']['output'];
|
||||
@@ -882,6 +922,19 @@ export type ListPreference = {
|
||||
visible: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type LoginInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
password: Scalars['String']['input'];
|
||||
username: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type LoginPayload = {
|
||||
__typename?: 'LoginPayload';
|
||||
accessToken: Scalars['String']['output'];
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
refreshToken: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type LoginTrackerCredentialsInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
password: Scalars['String']['input'];
|
||||
@@ -909,6 +962,17 @@ export type LoginTrackerOAuthPayload = {
|
||||
tracker: TrackerType;
|
||||
};
|
||||
|
||||
export type LogoutKoSyncAccountInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type LogoutKoSyncAccountPayload = {
|
||||
__typename?: 'LogoutKoSyncAccountPayload';
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
settings: SettingsType;
|
||||
success: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type LogoutTrackerInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
trackerId: Scalars['Int']['input'];
|
||||
@@ -1143,6 +1207,7 @@ export type MultiSelectListPreference = {
|
||||
default?: Maybe<Array<Scalars['String']['output']>>;
|
||||
dialogMessage?: Maybe<Scalars['String']['output']>;
|
||||
dialogTitle?: Maybe<Scalars['String']['output']>;
|
||||
enabled: Scalars['Boolean']['output'];
|
||||
entries: Array<Scalars['String']['output']>;
|
||||
entryValues: Array<Scalars['String']['output']>;
|
||||
key: Scalars['String']['output'];
|
||||
@@ -1156,6 +1221,7 @@ export type Mutation = {
|
||||
bindTrack: BindTrackPayload;
|
||||
clearCachedImages: ClearCachedImagesPayload;
|
||||
clearDownloader?: Maybe<ClearDownloaderPayload>;
|
||||
connectKoSyncAccount: KoSyncConnectPayload;
|
||||
createBackup: CreateBackupPayload;
|
||||
createCategory?: Maybe<CreateCategoryPayload>;
|
||||
deleteCategory?: Maybe<DeleteCategoryPayload>;
|
||||
@@ -1177,9 +1243,12 @@ export type Mutation = {
|
||||
fetchSourceManga?: Maybe<FetchSourceMangaPayload>;
|
||||
fetchTrack: FetchTrackPayload;
|
||||
installExternalExtension?: Maybe<InstallExternalExtensionPayload>;
|
||||
login: LoginPayload;
|
||||
loginTrackerCredentials: LoginTrackerCredentialsPayload;
|
||||
loginTrackerOAuth: LoginTrackerOAuthPayload;
|
||||
logoutKoSyncAccount: LogoutKoSyncAccountPayload;
|
||||
logoutTracker: LogoutTrackerPayload;
|
||||
refreshToken: RefreshTokenPayload;
|
||||
reorderChapterDownload?: Maybe<ReorderChapterDownloadPayload>;
|
||||
resetSettings: ResetSettingsPayload;
|
||||
resetWebUIUpdateStatus?: Maybe<WebUiUpdateStatus>;
|
||||
@@ -1230,6 +1299,11 @@ export type MutationClearDownloaderArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationConnectKoSyncAccountArgs = {
|
||||
input: ConnectKoSyncAccountInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateBackupArgs = {
|
||||
input?: InputMaybe<CreateBackupInput>;
|
||||
};
|
||||
@@ -1335,6 +1409,11 @@ export type MutationInstallExternalExtensionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationLoginArgs = {
|
||||
input: LoginInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationLoginTrackerCredentialsArgs = {
|
||||
input: LoginTrackerCredentialsInput;
|
||||
};
|
||||
@@ -1345,11 +1424,21 @@ export type MutationLoginTrackerOAuthArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationLogoutKoSyncAccountArgs = {
|
||||
input: LogoutKoSyncAccountInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationLogoutTrackerArgs = {
|
||||
input: LogoutTrackerInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationRefreshTokenArgs = {
|
||||
input: RefreshTokenInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationReorderChapterDownloadArgs = {
|
||||
input: ReorderChapterDownloadInput;
|
||||
};
|
||||
@@ -1570,6 +1659,16 @@ export type PartialSettingsType = Settings & {
|
||||
gqlDebugLogsEnabled?: Maybe<Scalars['Boolean']['output']>;
|
||||
initialOpenInBrowserEnabled?: Maybe<Scalars['Boolean']['output']>;
|
||||
ip?: Maybe<Scalars['String']['output']>;
|
||||
jwtAudience?: Maybe<Scalars['String']['output']>;
|
||||
jwtRefreshExpiry?: Maybe<Scalars['Duration']['output']>;
|
||||
jwtTokenExpiry?: Maybe<Scalars['Duration']['output']>;
|
||||
koreaderSyncChecksumMethod?: Maybe<KoreaderSyncChecksumMethod>;
|
||||
koreaderSyncDeviceId?: Maybe<Scalars['String']['output']>;
|
||||
koreaderSyncPercentageTolerance?: Maybe<Scalars['Float']['output']>;
|
||||
koreaderSyncServerUrl?: Maybe<Scalars['String']['output']>;
|
||||
koreaderSyncStrategy?: Maybe<KoreaderSyncStrategy>;
|
||||
koreaderSyncUserkey?: Maybe<Scalars['String']['output']>;
|
||||
koreaderSyncUsername?: Maybe<Scalars['String']['output']>;
|
||||
localSourcePath?: Maybe<Scalars['String']['output']>;
|
||||
maxLogFileSize?: Maybe<Scalars['String']['output']>;
|
||||
maxLogFiles?: Maybe<Scalars['Int']['output']>;
|
||||
@@ -1627,6 +1726,16 @@ export type PartialSettingsTypeInput = {
|
||||
globalUpdateInterval?: InputMaybe<Scalars['Float']['input']>;
|
||||
initialOpenInBrowserEnabled?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
ip?: InputMaybe<Scalars['String']['input']>;
|
||||
jwtAudience?: InputMaybe<Scalars['String']['input']>;
|
||||
jwtRefreshExpiry?: InputMaybe<Scalars['Duration']['input']>;
|
||||
jwtTokenExpiry?: InputMaybe<Scalars['Duration']['input']>;
|
||||
koreaderSyncChecksumMethod?: InputMaybe<KoreaderSyncChecksumMethod>;
|
||||
koreaderSyncDeviceId?: InputMaybe<Scalars['String']['input']>;
|
||||
koreaderSyncPercentageTolerance?: InputMaybe<Scalars['Float']['input']>;
|
||||
koreaderSyncServerUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
koreaderSyncStrategy?: InputMaybe<KoreaderSyncStrategy>;
|
||||
koreaderSyncUserkey?: InputMaybe<Scalars['String']['input']>;
|
||||
koreaderSyncUsername?: InputMaybe<Scalars['String']['input']>;
|
||||
localSourcePath?: InputMaybe<Scalars['String']['input']>;
|
||||
maxLogFileSize?: InputMaybe<Scalars['String']['input']>;
|
||||
maxLogFiles?: InputMaybe<Scalars['Int']['input']>;
|
||||
@@ -1670,6 +1779,7 @@ export type Query = {
|
||||
extension: ExtensionType;
|
||||
extensions: ExtensionNodeList;
|
||||
getWebUIUpdateStatus: WebUiUpdateStatus;
|
||||
koSyncStatus: KoSyncStatusPayload;
|
||||
lastUpdateTimestamp: LastUpdateTimestampPayload;
|
||||
libraryUpdateStatus: LibraryUpdateStatus;
|
||||
manga: MangaType;
|
||||
@@ -1856,6 +1966,17 @@ export type QueryValidateBackupArgs = {
|
||||
input: ValidateBackupInput;
|
||||
};
|
||||
|
||||
export type RefreshTokenInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
refreshToken: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type RefreshTokenPayload = {
|
||||
__typename?: 'RefreshTokenPayload';
|
||||
accessToken: Scalars['String']['output'];
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type ReorderChapterDownloadInput = {
|
||||
chapterId: Scalars['Int']['input'];
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -2018,6 +2139,16 @@ export type Settings = {
|
||||
gqlDebugLogsEnabled?: Maybe<Scalars['Boolean']['output']>;
|
||||
initialOpenInBrowserEnabled?: Maybe<Scalars['Boolean']['output']>;
|
||||
ip?: Maybe<Scalars['String']['output']>;
|
||||
jwtAudience?: Maybe<Scalars['String']['output']>;
|
||||
jwtRefreshExpiry?: Maybe<Scalars['Duration']['output']>;
|
||||
jwtTokenExpiry?: Maybe<Scalars['Duration']['output']>;
|
||||
koreaderSyncChecksumMethod?: Maybe<KoreaderSyncChecksumMethod>;
|
||||
koreaderSyncDeviceId?: Maybe<Scalars['String']['output']>;
|
||||
koreaderSyncPercentageTolerance?: Maybe<Scalars['Float']['output']>;
|
||||
koreaderSyncServerUrl?: Maybe<Scalars['String']['output']>;
|
||||
koreaderSyncStrategy?: Maybe<KoreaderSyncStrategy>;
|
||||
koreaderSyncUserkey?: Maybe<Scalars['String']['output']>;
|
||||
koreaderSyncUsername?: Maybe<Scalars['String']['output']>;
|
||||
localSourcePath?: Maybe<Scalars['String']['output']>;
|
||||
maxLogFileSize?: Maybe<Scalars['String']['output']>;
|
||||
maxLogFiles?: Maybe<Scalars['Int']['output']>;
|
||||
@@ -2105,6 +2236,16 @@ export type SettingsType = Settings & {
|
||||
gqlDebugLogsEnabled: Scalars['Boolean']['output'];
|
||||
initialOpenInBrowserEnabled: Scalars['Boolean']['output'];
|
||||
ip: Scalars['String']['output'];
|
||||
jwtAudience: Scalars['String']['output'];
|
||||
jwtRefreshExpiry: Scalars['Duration']['output'];
|
||||
jwtTokenExpiry: Scalars['Duration']['output'];
|
||||
koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod;
|
||||
koreaderSyncDeviceId: Scalars['String']['output'];
|
||||
koreaderSyncPercentageTolerance: Scalars['Float']['output'];
|
||||
koreaderSyncServerUrl: Scalars['String']['output'];
|
||||
koreaderSyncStrategy: KoreaderSyncStrategy;
|
||||
koreaderSyncUserkey: Scalars['String']['output'];
|
||||
koreaderSyncUsername: Scalars['String']['output'];
|
||||
localSourcePath: Scalars['String']['output'];
|
||||
maxLogFileSize: Scalars['String']['output'];
|
||||
maxLogFiles: Scalars['Int']['output'];
|
||||
@@ -2226,6 +2367,7 @@ export type SourcePreferenceChangeInput = {
|
||||
|
||||
export type SourceType = {
|
||||
__typename?: 'SourceType';
|
||||
baseUrl?: Maybe<Scalars['String']['output']>;
|
||||
displayName: Scalars['String']['output'];
|
||||
extension: ExtensionType;
|
||||
filters: Array<Filter>;
|
||||
@@ -2362,12 +2504,19 @@ export type SwitchPreference = {
|
||||
__typename?: 'SwitchPreference';
|
||||
currentValue?: Maybe<Scalars['Boolean']['output']>;
|
||||
default: Scalars['Boolean']['output'];
|
||||
enabled: Scalars['Boolean']['output'];
|
||||
key: Scalars['String']['output'];
|
||||
summary?: Maybe<Scalars['String']['output']>;
|
||||
title?: Maybe<Scalars['String']['output']>;
|
||||
visible: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type SyncConflictInfoType = {
|
||||
__typename?: 'SyncConflictInfoType';
|
||||
deviceName: Scalars['String']['output'];
|
||||
remotePage: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type TextFilter = {
|
||||
__typename?: 'TextFilter';
|
||||
default: Scalars['String']['output'];
|
||||
@@ -2994,7 +3143,7 @@ export type MangaScreenFieldsFragment = { __typename?: 'MangaType', artist?: str
|
||||
|
||||
export type MangaLibraryDuplicateScreenFieldsFragment = { __typename?: 'MangaType', description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number } };
|
||||
|
||||
export type ServerSettingsFragment = { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyVersion: number, socksProxyHost: string, socksProxyPort: string, socksProxyUsername: string, socksProxyPassword: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadNewChaptersLimit: number, autoDownloadIgnoreReUploads: boolean, extensionRepos: Array<string>, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, authMode: AuthMode, authPassword: string, authUsername: string, debugLogsEnabled: boolean, systemTrayEnabled: boolean, maxLogFileSize: string, maxLogFiles: number, maxLogFolderSize: string, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string, flareSolverrEnabled: boolean, flareSolverrUrl: string, flareSolverrTimeout: number, flareSolverrSessionName: string, flareSolverrSessionTtl: number, flareSolverrAsResponseFallback: boolean, opdsUseBinaryFileSizes: boolean, opdsItemsPerPage: number, opdsEnablePageReadProgress: boolean, opdsMarkAsReadOnDownload: boolean, opdsShowOnlyUnreadChapters: boolean, opdsShowOnlyDownloadedChapters: boolean, opdsChapterSortOrder: SortOrder, downloadConversions: Array<{ __typename?: 'SettingsDownloadConversionType', mimeType: string, target: string, compressionLevel?: number | null }> };
|
||||
export type ServerSettingsFragment = { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyVersion: number, socksProxyHost: string, socksProxyPort: string, socksProxyUsername: string, socksProxyPassword: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadNewChaptersLimit: number, autoDownloadIgnoreReUploads: boolean, extensionRepos: Array<string>, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, authMode: AuthMode, authPassword: string, authUsername: string, jwtAudience: string, jwtTokenExpiry: any, jwtRefreshExpiry: any, debugLogsEnabled: boolean, systemTrayEnabled: boolean, maxLogFileSize: string, maxLogFiles: number, maxLogFolderSize: string, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string, flareSolverrEnabled: boolean, flareSolverrUrl: string, flareSolverrTimeout: number, flareSolverrSessionName: string, flareSolverrSessionTtl: number, flareSolverrAsResponseFallback: boolean, opdsUseBinaryFileSizes: boolean, opdsItemsPerPage: number, opdsEnablePageReadProgress: boolean, opdsMarkAsReadOnDownload: boolean, opdsShowOnlyUnreadChapters: boolean, opdsShowOnlyDownloadedChapters: boolean, opdsChapterSortOrder: SortOrder, downloadConversions: Array<{ __typename?: 'SettingsDownloadConversionType', mimeType: string, target: string, compressionLevel?: number | null }> };
|
||||
|
||||
export type SourceMetaFieldsFragment = { __typename?: 'SourceMetaType', sourceId: string, key: string, value: string };
|
||||
|
||||
@@ -3356,14 +3505,14 @@ export type ResetServerSettingsMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ResetServerSettingsMutation = { __typename?: 'Mutation', resetSettings: { __typename?: 'ResetSettingsPayload', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyVersion: number, socksProxyHost: string, socksProxyPort: string, socksProxyUsername: string, socksProxyPassword: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadNewChaptersLimit: number, autoDownloadIgnoreReUploads: boolean, extensionRepos: Array<string>, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, authMode: AuthMode, authPassword: string, authUsername: string, debugLogsEnabled: boolean, systemTrayEnabled: boolean, maxLogFileSize: string, maxLogFiles: number, maxLogFolderSize: string, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string, flareSolverrEnabled: boolean, flareSolverrUrl: string, flareSolverrTimeout: number, flareSolverrSessionName: string, flareSolverrSessionTtl: number, flareSolverrAsResponseFallback: boolean, opdsUseBinaryFileSizes: boolean, opdsItemsPerPage: number, opdsEnablePageReadProgress: boolean, opdsMarkAsReadOnDownload: boolean, opdsShowOnlyUnreadChapters: boolean, opdsShowOnlyDownloadedChapters: boolean, opdsChapterSortOrder: SortOrder, downloadConversions: Array<{ __typename?: 'SettingsDownloadConversionType', mimeType: string, target: string, compressionLevel?: number | null }> } } };
|
||||
export type ResetServerSettingsMutation = { __typename?: 'Mutation', resetSettings: { __typename?: 'ResetSettingsPayload', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyVersion: number, socksProxyHost: string, socksProxyPort: string, socksProxyUsername: string, socksProxyPassword: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadNewChaptersLimit: number, autoDownloadIgnoreReUploads: boolean, extensionRepos: Array<string>, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, authMode: AuthMode, authPassword: string, authUsername: string, jwtAudience: string, jwtTokenExpiry: any, jwtRefreshExpiry: any, debugLogsEnabled: boolean, systemTrayEnabled: boolean, maxLogFileSize: string, maxLogFiles: number, maxLogFolderSize: string, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string, flareSolverrEnabled: boolean, flareSolverrUrl: string, flareSolverrTimeout: number, flareSolverrSessionName: string, flareSolverrSessionTtl: number, flareSolverrAsResponseFallback: boolean, opdsUseBinaryFileSizes: boolean, opdsItemsPerPage: number, opdsEnablePageReadProgress: boolean, opdsMarkAsReadOnDownload: boolean, opdsShowOnlyUnreadChapters: boolean, opdsShowOnlyDownloadedChapters: boolean, opdsChapterSortOrder: SortOrder, downloadConversions: Array<{ __typename?: 'SettingsDownloadConversionType', mimeType: string, target: string, compressionLevel?: number | null }> } } };
|
||||
|
||||
export type UpdateServerSettingsMutationVariables = Exact<{
|
||||
input: SetSettingsInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateServerSettingsMutation = { __typename?: 'Mutation', setSettings: { __typename?: 'SetSettingsPayload', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyVersion: number, socksProxyHost: string, socksProxyPort: string, socksProxyUsername: string, socksProxyPassword: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadNewChaptersLimit: number, autoDownloadIgnoreReUploads: boolean, extensionRepos: Array<string>, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, authMode: AuthMode, authPassword: string, authUsername: string, debugLogsEnabled: boolean, systemTrayEnabled: boolean, maxLogFileSize: string, maxLogFiles: number, maxLogFolderSize: string, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string, flareSolverrEnabled: boolean, flareSolverrUrl: string, flareSolverrTimeout: number, flareSolverrSessionName: string, flareSolverrSessionTtl: number, flareSolverrAsResponseFallback: boolean, opdsUseBinaryFileSizes: boolean, opdsItemsPerPage: number, opdsEnablePageReadProgress: boolean, opdsMarkAsReadOnDownload: boolean, opdsShowOnlyUnreadChapters: boolean, opdsShowOnlyDownloadedChapters: boolean, opdsChapterSortOrder: SortOrder, downloadConversions: Array<{ __typename?: 'SettingsDownloadConversionType', mimeType: string, target: string, compressionLevel?: number | null }> } } };
|
||||
export type UpdateServerSettingsMutation = { __typename?: 'Mutation', setSettings: { __typename?: 'SetSettingsPayload', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyVersion: number, socksProxyHost: string, socksProxyPort: string, socksProxyUsername: string, socksProxyPassword: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadNewChaptersLimit: number, autoDownloadIgnoreReUploads: boolean, extensionRepos: Array<string>, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, authMode: AuthMode, authPassword: string, authUsername: string, jwtAudience: string, jwtTokenExpiry: any, jwtRefreshExpiry: any, debugLogsEnabled: boolean, systemTrayEnabled: boolean, maxLogFileSize: string, maxLogFiles: number, maxLogFolderSize: string, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string, flareSolverrEnabled: boolean, flareSolverrUrl: string, flareSolverrTimeout: number, flareSolverrSessionName: string, flareSolverrSessionTtl: number, flareSolverrAsResponseFallback: boolean, opdsUseBinaryFileSizes: boolean, opdsItemsPerPage: number, opdsEnablePageReadProgress: boolean, opdsMarkAsReadOnDownload: boolean, opdsShowOnlyUnreadChapters: boolean, opdsShowOnlyDownloadedChapters: boolean, opdsChapterSortOrder: SortOrder, downloadConversions: Array<{ __typename?: 'SettingsDownloadConversionType', mimeType: string, target: string, compressionLevel?: number | null }> } } };
|
||||
|
||||
export type GetSourceMangasFetchMutationVariables = Exact<{
|
||||
input: FetchSourceMangaInput;
|
||||
@@ -3456,6 +3605,21 @@ export type StopUpdaterMutationVariables = Exact<{
|
||||
|
||||
export type StopUpdaterMutation = { __typename?: 'Mutation', updateStop: { __typename?: 'UpdateStopPayload', clientMutationId?: string | null } };
|
||||
|
||||
export type UserLoginMutationVariables = Exact<{
|
||||
password: Scalars['String']['input'];
|
||||
username: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type UserLoginMutation = { __typename?: 'Mutation', login: { __typename?: 'LoginPayload', accessToken: string, refreshToken: string } };
|
||||
|
||||
export type UserRefreshMutationVariables = Exact<{
|
||||
refreshToken: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type UserRefreshMutation = { __typename?: 'Mutation', refreshToken: { __typename?: 'RefreshTokenPayload', accessToken: string } };
|
||||
|
||||
export type ValidateBackupQueryVariables = Exact<{
|
||||
backup: Scalars['Upload']['input'];
|
||||
}>;
|
||||
@@ -3747,7 +3911,7 @@ export type GetWebuiUpdateStatusQuery = { __typename?: 'Query', getWebUIUpdateSt
|
||||
export type GetServerSettingsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetServerSettingsQuery = { __typename?: 'Query', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyVersion: number, socksProxyHost: string, socksProxyPort: string, socksProxyUsername: string, socksProxyPassword: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadNewChaptersLimit: number, autoDownloadIgnoreReUploads: boolean, extensionRepos: Array<string>, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, authMode: AuthMode, authPassword: string, authUsername: string, debugLogsEnabled: boolean, systemTrayEnabled: boolean, maxLogFileSize: string, maxLogFiles: number, maxLogFolderSize: string, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string, flareSolverrEnabled: boolean, flareSolverrUrl: string, flareSolverrTimeout: number, flareSolverrSessionName: string, flareSolverrSessionTtl: number, flareSolverrAsResponseFallback: boolean, opdsUseBinaryFileSizes: boolean, opdsItemsPerPage: number, opdsEnablePageReadProgress: boolean, opdsMarkAsReadOnDownload: boolean, opdsShowOnlyUnreadChapters: boolean, opdsShowOnlyDownloadedChapters: boolean, opdsChapterSortOrder: SortOrder, downloadConversions: Array<{ __typename?: 'SettingsDownloadConversionType', mimeType: string, target: string, compressionLevel?: number | null }> } };
|
||||
export type GetServerSettingsQuery = { __typename?: 'Query', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyVersion: number, socksProxyHost: string, socksProxyPort: string, socksProxyUsername: string, socksProxyPassword: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadNewChaptersLimit: number, autoDownloadIgnoreReUploads: boolean, extensionRepos: Array<string>, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, authMode: AuthMode, authPassword: string, authUsername: string, jwtAudience: string, jwtTokenExpiry: any, jwtRefreshExpiry: any, debugLogsEnabled: boolean, systemTrayEnabled: boolean, maxLogFileSize: string, maxLogFiles: number, maxLogFolderSize: string, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string, flareSolverrEnabled: boolean, flareSolverrUrl: string, flareSolverrTimeout: number, flareSolverrSessionName: string, flareSolverrSessionTtl: number, flareSolverrAsResponseFallback: boolean, opdsUseBinaryFileSizes: boolean, opdsItemsPerPage: number, opdsEnablePageReadProgress: boolean, opdsMarkAsReadOnDownload: boolean, opdsShowOnlyUnreadChapters: boolean, opdsShowOnlyDownloadedChapters: boolean, opdsChapterSortOrder: SortOrder, downloadConversions: Array<{ __typename?: 'SettingsDownloadConversionType', mimeType: string, target: string, compressionLevel?: number | null }> } };
|
||||
|
||||
export type GetSourceBrowseQueryVariables = Exact<{
|
||||
id: Scalars['LongString']['input'];
|
||||
|
||||
26
src/lib/graphql/mutations/UserMutation.ts
Normal file
26
src/lib/graphql/mutations/UserMutation.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const USER_LOGIN = gql`
|
||||
mutation USER_LOGIN($password: String!, $username: String!) {
|
||||
login(input: { password: $password, username: $username }) {
|
||||
accessToken
|
||||
refreshToken
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const USER_REFRESH = gql`
|
||||
mutation USER_REFRESH($refreshToken: String!) {
|
||||
refreshToken(input: { refreshToken: $refreshToken }) {
|
||||
accessToken
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -212,6 +212,10 @@ import {
|
||||
GetExtensionQuery,
|
||||
GetExtensionQueryVariables,
|
||||
DownloaderState,
|
||||
UserLoginMutation,
|
||||
UserLoginMutationVariables,
|
||||
UserRefreshMutation,
|
||||
UserRefreshMutationVariables,
|
||||
} from '@/lib/graphql/generated/graphql.ts';
|
||||
import { GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
|
||||
import { DELETE_GLOBAL_METADATA, SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
|
||||
@@ -327,6 +331,8 @@ import { CHAPTER_META_FIELDS } from '@/lib/graphql/fragments/ChapterFragments.ts
|
||||
import { MetadataMigrationSettings } from '@/features/migration/Migration.types.ts';
|
||||
import { MangaIdInfo } from '@/features/manga/Manga.types.ts';
|
||||
import { updateMetadataList } from '@/features/metadata/services/MetadataApolloCacheHandler.ts';
|
||||
import { USER_LOGIN, USER_REFRESH } from '@/lib/graphql/mutations/UserMutation.ts';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
|
||||
enum GQLMethod {
|
||||
QUERY = 'QUERY',
|
||||
@@ -434,9 +440,9 @@ export const SPECIAL_ED_SOURCES = {
|
||||
export class RequestManager {
|
||||
public static readonly API_VERSION = '/api/v1/';
|
||||
|
||||
public readonly graphQLClient = new GraphQLClient();
|
||||
public readonly graphQLClient = new GraphQLClient(this.refreshUser.bind(this));
|
||||
|
||||
private readonly restClient: RestClient = new RestClient();
|
||||
private readonly restClient: RestClient = new RestClient(this.refreshUser.bind(this));
|
||||
|
||||
private readonly cache = new CustomCache();
|
||||
|
||||
@@ -452,6 +458,8 @@ export class RequestManager {
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
AuthManager.setAuthRequired(null);
|
||||
AuthManager.removeTokens();
|
||||
this.graphQLClient.client.resetStore();
|
||||
this.graphQLClient.terminateSubscriptions();
|
||||
this.cache.clear();
|
||||
@@ -1020,7 +1028,7 @@ export class RequestManager {
|
||||
} = {},
|
||||
): ImageRequest {
|
||||
const finalOptions = {
|
||||
useFetchApi: false,
|
||||
useFetchApi: AuthManager.isAuthRequired(),
|
||||
shouldDecode: false,
|
||||
disableCors: false,
|
||||
...Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined)),
|
||||
@@ -3231,6 +3239,24 @@ export class RequestManager {
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
public useLoginUser(
|
||||
options?: MutationHookOptions<UserLoginMutation, UserLoginMutationVariables>,
|
||||
): AbortableApolloUseMutationResponse<UserLoginMutation, UserLoginMutationVariables> {
|
||||
return this.doRequest(GQLMethod.USE_MUTATION, USER_LOGIN, undefined, options);
|
||||
}
|
||||
|
||||
public refreshUser(
|
||||
refreshToken: string,
|
||||
options?: MutationOptions<UserRefreshMutation, UserRefreshMutationVariables>,
|
||||
): AbortableApolloMutationResponse<UserRefreshMutation> {
|
||||
return this.doRequest<UserRefreshMutation, UserRefreshMutationVariables>(
|
||||
GQLMethod.MUTATION,
|
||||
USER_REFRESH,
|
||||
{ refreshToken: refreshToken ?? undefined },
|
||||
options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const requestManager = new RequestManager();
|
||||
|
||||
@@ -7,12 +7,60 @@
|
||||
*/
|
||||
|
||||
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||
import { UserRefreshMutation } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
import { AbortableApolloMutationResponse } from '@/lib/requests/RequestManager.ts';
|
||||
|
||||
export abstract class BaseClient<Client, ClientConfig, Fetcher> {
|
||||
protected abstract client: Client;
|
||||
|
||||
public abstract readonly fetcher: Fetcher;
|
||||
|
||||
private static activeTokenRefreshPromise: Promise<UserRefreshMutation | null | undefined> | null = null;
|
||||
|
||||
protected static async refreshAccessToken(
|
||||
refreshFn: (refreshToken: string) => AbortableApolloMutationResponse<UserRefreshMutation>,
|
||||
): Promise<UserRefreshMutation | null | undefined> {
|
||||
const refreshToken = AuthManager.getRefreshToken();
|
||||
|
||||
if (!AuthManager.isAuthRequired()) {
|
||||
AuthManager.setAuthRequired(true);
|
||||
}
|
||||
|
||||
if (!refreshToken) {
|
||||
throw new Error('No refresh token found');
|
||||
}
|
||||
|
||||
if (this.activeTokenRefreshPromise) {
|
||||
return this.activeTokenRefreshPromise;
|
||||
}
|
||||
|
||||
const refreshRequest = refreshFn(refreshToken).response;
|
||||
this.activeTokenRefreshPromise = refreshRequest.then((result) => result.data);
|
||||
|
||||
try {
|
||||
const result = await refreshRequest;
|
||||
const { data } = result;
|
||||
|
||||
if (!data) {
|
||||
throw new Error('No refreshed access token returned');
|
||||
}
|
||||
|
||||
AuthManager.setAccessToken(data.refreshToken.accessToken);
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
AuthManager.removeTokens();
|
||||
throw e;
|
||||
} finally {
|
||||
this.activeTokenRefreshPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected constructor(
|
||||
protected handleRefreshToken: (refreshToken: string) => AbortableApolloMutationResponse<UserRefreshMutation>,
|
||||
) {}
|
||||
|
||||
public getBaseUrl(): string {
|
||||
const { hostname, port, protocol } = window.location;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { onError } from '@apollo/client/link/error';
|
||||
import { setContext } from '@apollo/client/link/context';
|
||||
import {
|
||||
ApolloClient,
|
||||
ApolloClientOptions,
|
||||
@@ -14,6 +16,7 @@ import {
|
||||
NormalizedCacheObject,
|
||||
split,
|
||||
from,
|
||||
fromPromise,
|
||||
} from '@apollo/client';
|
||||
import createUploadLink from 'apollo-upload-client/createUploadLink.mjs';
|
||||
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
|
||||
@@ -23,6 +26,9 @@ import { TypePolicies } from '@apollo/client/cache';
|
||||
import { removeTypenameFromVariables } from '@apollo/client/link/remove-typename';
|
||||
import { BaseClient } from '@/lib/requests/client/BaseClient.ts';
|
||||
import { StrictTypedTypePolicies } from '@/lib/graphql/generated/apollo-helpers.ts';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
import { UserRefreshMutation } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { AbortableApolloMutationResponse } from '@/lib/requests/RequestManager.ts';
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const typePolicies: StrictTypedTypePolicies = {
|
||||
@@ -187,8 +193,8 @@ export class GraphQLClient extends BaseClient<
|
||||
|
||||
private wsClient!: Client;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(handleRefreshToken: (refreshToken: string) => AbortableApolloMutationResponse<UserRefreshMutation>) {
|
||||
super(handleRefreshToken);
|
||||
|
||||
this.createClient();
|
||||
}
|
||||
@@ -201,8 +207,44 @@ export class GraphQLClient extends BaseClient<
|
||||
this.wsClient.terminate();
|
||||
}
|
||||
|
||||
private createErrorLink() {
|
||||
return onError(({ graphQLErrors, operation, forward }) => {
|
||||
if (!graphQLErrors) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isAuthError = graphQLErrors.some((graphQLError) =>
|
||||
graphQLError.message.includes('suwayomi.tachidesk.server.user.UnauthorizedException'),
|
||||
);
|
||||
if (!isAuthError) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return fromPromise(BaseClient.refreshAccessToken(this.handleRefreshToken))
|
||||
.filter(Boolean)
|
||||
.flatMap(() => forward(operation));
|
||||
});
|
||||
}
|
||||
|
||||
private createAuthLink() {
|
||||
return setContext((_, { headers }) => {
|
||||
const isAuthRequired = AuthManager.isAuthRequired();
|
||||
const accessToken = AuthManager.getAccessToken();
|
||||
|
||||
return {
|
||||
headers: {
|
||||
credentials: 'include',
|
||||
...headers,
|
||||
Authorization: isAuthRequired && accessToken ? `Bearer ${accessToken}` : '',
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private createUploadLink() {
|
||||
return createUploadLink({ uri: () => this.getBaseUrl(), credentials: 'include' });
|
||||
return createUploadLink({
|
||||
uri: () => this.getBaseUrl(),
|
||||
});
|
||||
}
|
||||
|
||||
private createWSLink() {
|
||||
@@ -218,8 +260,13 @@ export class GraphQLClient extends BaseClient<
|
||||
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
|
||||
},
|
||||
this.createWSLink(),
|
||||
from([
|
||||
this.createErrorLink(),
|
||||
this.createAuthLink(),
|
||||
removeTypenameLink,
|
||||
// apollo-upload-client dependency is outdated (see 134e47763faae9e62db4d4e3a8387a74e32e5568) and thus types are not matching, but they are still correct
|
||||
from([removeTypenameLink, this.createUploadLink() as unknown as ApolloLink]),
|
||||
this.createUploadLink() as unknown as ApolloLink,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -230,6 +277,14 @@ export class GraphQLClient extends BaseClient<
|
||||
url: () => this.getBaseUrl().replace(/http(|s)/g, 'ws'),
|
||||
keepAlive: heartbeatInterval,
|
||||
retryAttempts: 10,
|
||||
connectionParams: () => {
|
||||
const isAuthRequired = AuthManager.isAuthRequired();
|
||||
const accessToken = AuthManager.getAccessToken();
|
||||
|
||||
return {
|
||||
Authorization: isAuthRequired && accessToken ? accessToken : undefined,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
let lastHeartbeat: number = 0;
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
*/
|
||||
|
||||
import { BaseClient } from '@/lib/requests/client/BaseClient.ts';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
import { UserRefreshMutation } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { AbortableApolloMutationResponse } from '@/lib/requests/RequestManager.ts';
|
||||
|
||||
export enum HttpMethod {
|
||||
GET = 'GET',
|
||||
@@ -48,17 +51,28 @@ export class RestClient
|
||||
} = {},
|
||||
): Promise<Response> => {
|
||||
const updatedUrl = url.startsWith('http') ? url : `${this.getBaseUrl()}${url}`;
|
||||
const accessToken = AuthManager.getAccessToken();
|
||||
|
||||
let result: Response;
|
||||
|
||||
switch (httpMethod) {
|
||||
case HttpMethod.GET:
|
||||
result = await this.client(updatedUrl, { ...this.config, ...config, method: httpMethod });
|
||||
result = await this.client(updatedUrl, {
|
||||
...this.config,
|
||||
...config,
|
||||
method: httpMethod,
|
||||
headers: {
|
||||
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
|
||||
...this.config.headers,
|
||||
...config?.headers,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case HttpMethod.POST:
|
||||
case HttpMethod.PATCH:
|
||||
case HttpMethod.DELETE:
|
||||
result = await this.client(updatedUrl, {
|
||||
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
|
||||
...this.config,
|
||||
...config,
|
||||
method: httpMethod,
|
||||
@@ -69,6 +83,11 @@ export class RestClient
|
||||
throw new Error(`Unexpected HttpMethod "${httpMethod}"`);
|
||||
}
|
||||
|
||||
if (result.status === 401) {
|
||||
await BaseClient.refreshAccessToken(this.handleRefreshToken);
|
||||
return this.fetcher(url, { data, httpMethod, config, checkResponseIsJson });
|
||||
}
|
||||
|
||||
if (result.status !== 200) {
|
||||
throw new Error(`status ${result.status}: ${result.statusText}`);
|
||||
}
|
||||
@@ -80,8 +99,8 @@ export class RestClient
|
||||
return result;
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(handleRefreshToken: (refreshToken: string) => AbortableApolloMutationResponse<UserRefreshMutation>) {
|
||||
super(handleRefreshToken);
|
||||
|
||||
this.createClient();
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ const fixTypingOfQueryTypePolicies = format(
|
||||
\textension?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\textensions?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tgetWebUIUpdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tkoSyncStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tlastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tlibraryUpdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tmanga?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -93,6 +94,7 @@ const fixTypingOfQueryTypePolicies = format(
|
||||
\textension?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\textensions?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tgetWebUIUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>>,
|
||||
\tkoSyncStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tlastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tlibraryUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetUpdateStatusQueryVariables>>,
|
||||
\tmanga?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetMangaScreenQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetMangaScreenQueryVariables>>,
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@@ -2877,6 +2877,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/extract-files/-/extract-files-13.0.1.tgz#3ec057a3fa25f778245a76a17271d23b71ee31d7"
|
||||
integrity sha512-/fRbzc2lAd7jDJSSnxWiUyXWjdUZZ4HbISLJzVgt1AvrdOa7U49YRPcvuCUywkmURZ7uwJOheDjx19itbQ5KvA==
|
||||
|
||||
"@types/js-cookie@^3.0.6":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.6.tgz#a04ca19e877687bd449f5ad37d33b104b71fdf95"
|
||||
integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==
|
||||
|
||||
"@types/js-yaml@^4.0.0":
|
||||
version "4.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2"
|
||||
@@ -6513,6 +6518,11 @@ jpeg-js@^0.4.4:
|
||||
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa"
|
||||
integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==
|
||||
|
||||
js-cookie@^3.0.5:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
|
||||
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
|
||||
Reference in New Issue
Block a user