2023-09-01 20:21:59 +02:00
|
|
|
/*
|
|
|
|
|
* 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/.
|
|
|
|
|
*/
|
|
|
|
|
|
2023-11-25 15:35:26 +01:00
|
|
|
import { ApolloClient, ApolloClientOptions, InMemoryCache, NormalizedCacheObject, split } from '@apollo/client';
|
2023-09-01 20:21:59 +02:00
|
|
|
import { createUploadLink } from 'apollo-upload-client';
|
2023-10-04 20:50:11 +02:00
|
|
|
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
|
|
|
|
|
import { Client, createClient } from 'graphql-ws';
|
|
|
|
|
import { getMainDefinition } from '@apollo/client/utilities';
|
2023-12-25 21:20:07 +01:00
|
|
|
import { TypePolicies } from '@apollo/client/cache';
|
2023-09-01 20:21:59 +02:00
|
|
|
import { BaseClient } from '@/lib/requests/client/BaseClient.ts';
|
|
|
|
|
import { StrictTypedTypePolicies } from '@/lib/graphql/generated/apollo-helpers.ts';
|
|
|
|
|
|
2023-09-30 16:55:35 +02:00
|
|
|
/* eslint-disable no-underscore-dangle */
|
2023-09-01 20:21:59 +02:00
|
|
|
const typePolicies: StrictTypedTypePolicies = {
|
|
|
|
|
GlobalMetaType: { keyFields: ['key'] },
|
2023-12-25 21:16:04 +01:00
|
|
|
ExtensionType: { keyFields: ['pkgName'] },
|
2023-11-24 01:33:56 +01:00
|
|
|
AboutServerPayload: { keyFields: [] },
|
2023-12-09 23:08:27 +01:00
|
|
|
AboutWebUI: { keyFields: [] },
|
2023-11-24 01:33:56 +01:00
|
|
|
WebUIUpdateInfo: { keyFields: [] },
|
2023-12-09 23:08:27 +01:00
|
|
|
WebUIUpdateCheck: { keyFields: [] },
|
2023-10-27 23:39:05 +02:00
|
|
|
SettingsType: { keyFields: [] },
|
2023-12-13 02:05:03 +01:00
|
|
|
DownloadStatus: { keyFields: [] },
|
|
|
|
|
DownloadType: { keyFields: ['chapter'] },
|
2024-01-01 22:40:39 +01:00
|
|
|
WebUIUpdateStatus: { keyFields: [] },
|
|
|
|
|
UpdateStatus: { keyFields: [] },
|
2023-09-30 16:55:35 +02:00
|
|
|
Query: {
|
|
|
|
|
fields: {
|
2023-12-25 21:20:07 +01:00
|
|
|
manga(_, { args, toReference }) {
|
|
|
|
|
return toReference({
|
|
|
|
|
__typename: 'MangaType',
|
|
|
|
|
id: args?.id,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
category(_, { args, toReference }) {
|
|
|
|
|
return toReference({
|
|
|
|
|
__typename: 'CategoryType',
|
|
|
|
|
id: args?.id,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
source(_, { args, toReference }) {
|
|
|
|
|
return toReference({
|
|
|
|
|
__typename: 'SourceType',
|
|
|
|
|
id: args?.id,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
extension(_, { args, toReference }) {
|
|
|
|
|
return toReference({
|
|
|
|
|
__typename: 'ExtensionType',
|
|
|
|
|
apkName: args?.pkgName,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
meta(_, { args, toReference }) {
|
|
|
|
|
return toReference({
|
|
|
|
|
__typename: 'GlobalMetaType',
|
|
|
|
|
key: args?.key,
|
|
|
|
|
});
|
|
|
|
|
},
|
2024-01-01 22:40:39 +01:00
|
|
|
downloadStatus(_, { toReference }) {
|
|
|
|
|
return toReference({
|
|
|
|
|
__typename: 'DownloadStatus',
|
|
|
|
|
key: {},
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
getWebUIUpdateStatus(_, { toReference }) {
|
|
|
|
|
return toReference({
|
|
|
|
|
__typename: 'WebUIUpdateStatus',
|
|
|
|
|
key: {},
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
updateStatus(_, { toReference }) {
|
|
|
|
|
return toReference({ __typename: 'UpdateStatus', key: {} });
|
|
|
|
|
},
|
2023-09-30 16:55:35 +02:00
|
|
|
chapters: {
|
|
|
|
|
keyArgs: ['condition', 'filter', 'orderBy', 'orderByType'],
|
|
|
|
|
merge(existing, incoming) {
|
2023-11-25 15:35:26 +01:00
|
|
|
if (existing == null) {
|
|
|
|
|
return incoming;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isReFetch = !incoming.pageInfo.hasPreviousPage;
|
|
|
|
|
const hasLessItems = existing.nodes.length > incoming.nodes.length;
|
|
|
|
|
|
|
|
|
|
const useIncomingResponse = isReFetch && !hasLessItems;
|
|
|
|
|
if (useIncomingResponse) {
|
|
|
|
|
return incoming;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const replaceExistingItems = isReFetch && hasLessItems;
|
|
|
|
|
if (replaceExistingItems) {
|
|
|
|
|
const existingWithReplacedIncoming: typeof incoming = {
|
|
|
|
|
...existing,
|
|
|
|
|
pageInfo: {
|
|
|
|
|
...existing.pageInfo,
|
|
|
|
|
startCursor: incoming.pageInfo.startCursor,
|
|
|
|
|
},
|
|
|
|
|
nodes: [...incoming.nodes, ...existing.nodes.slice(incoming.nodes.length)],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return existingWithReplacedIncoming;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingWithAppendedIncoming: typeof incoming = {
|
2023-09-30 16:55:35 +02:00
|
|
|
...existing,
|
2023-11-25 15:35:26 +01:00
|
|
|
pageInfo: {
|
|
|
|
|
...existing.pageInfo,
|
|
|
|
|
endCursor: incoming.pageInfo.endCursor,
|
|
|
|
|
hasNextPage: incoming.pageInfo.hasNextPage,
|
|
|
|
|
},
|
|
|
|
|
nodes: [...existing.nodes, ...incoming.nodes],
|
2023-09-30 16:55:35 +02:00
|
|
|
};
|
2023-11-25 15:35:26 +01:00
|
|
|
|
|
|
|
|
return existingWithAppendedIncoming;
|
2023-09-30 16:55:35 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-09-01 20:21:59 +02:00
|
|
|
};
|
2023-09-30 16:55:35 +02:00
|
|
|
/* eslint-enable no-underscore-dangle */
|
2023-09-01 20:21:59 +02:00
|
|
|
|
|
|
|
|
export class GraphQLClient extends BaseClient<
|
|
|
|
|
ApolloClient<NormalizedCacheObject>,
|
|
|
|
|
ApolloClientOptions<NormalizedCacheObject>,
|
|
|
|
|
null
|
|
|
|
|
> {
|
|
|
|
|
readonly fetcher = null;
|
|
|
|
|
|
|
|
|
|
public declare client: ApolloClient<NormalizedCacheObject>;
|
|
|
|
|
|
2023-10-04 20:50:11 +02:00
|
|
|
private wsClient!: Client;
|
|
|
|
|
|
2023-09-01 20:21:59 +02:00
|
|
|
public override getBaseUrl(): string {
|
|
|
|
|
return `${super.getBaseUrl()}/api/graphql`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private createUploadLink() {
|
|
|
|
|
return createUploadLink({ uri: () => this.getBaseUrl() });
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 20:50:11 +02:00
|
|
|
private createWSLink() {
|
|
|
|
|
return new GraphQLWsLink(this.wsClient);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:21:59 +02:00
|
|
|
private createLink() {
|
2023-10-04 20:50:11 +02:00
|
|
|
return split(
|
|
|
|
|
({ query }) => {
|
|
|
|
|
const definition = getMainDefinition(query);
|
|
|
|
|
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
|
|
|
|
|
},
|
|
|
|
|
this.createWSLink(),
|
|
|
|
|
this.createUploadLink(),
|
|
|
|
|
);
|
2023-09-01 20:21:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected createClient() {
|
2023-10-04 20:50:11 +02:00
|
|
|
this.wsClient = createClient({
|
|
|
|
|
url: () => this.getBaseUrl().replace(/http(|s)/g, 'ws'),
|
|
|
|
|
keepAlive: 20000,
|
|
|
|
|
});
|
|
|
|
|
|
2023-09-01 20:21:59 +02:00
|
|
|
this.client = new ApolloClient({
|
|
|
|
|
cache: new InMemoryCache({
|
2023-12-25 21:20:07 +01:00
|
|
|
// for whatever reason there is some weird TypeError complaining that
|
|
|
|
|
// "FieldReadFunction<Reference, Reference, FieldFunctionOptions<SomeObject, Record<string, any>>"
|
|
|
|
|
// is not compatible with "FieldReadFunction<any, any, FieldFunctionOptions<Record<string, any, Record<string, any>>"
|
|
|
|
|
// Since "typePolicies" is correctly typed as StrictTypedTypePolicies, and it is working as expected,
|
|
|
|
|
// the TypeError can just be ignored
|
|
|
|
|
typePolicies: typePolicies as TypePolicies,
|
2023-09-01 20:21:59 +02:00
|
|
|
}),
|
|
|
|
|
connectToDevTools: true,
|
|
|
|
|
link: this.createLink(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override updateConfig() {}
|
|
|
|
|
}
|