|
1 | | -import { contract } from './contracts'; |
2 | | -import { initClient, tsRestFetchApi } from '@ts-rest/core'; |
3 | | -import { TOKEN_ERROR_CODE } from '../../error/errorCode'; |
4 | | -import { getNanoid } from '../../string/tools'; |
5 | | -import { type ApiFetcherArgs } from '@ts-rest/core'; |
6 | | -import { AnyResponseSchema } from '../../type'; |
7 | | -import { ZodError } from 'zod'; |
8 | | -import { getWebReqUrl } from '../../../../web/common/system/utils'; |
9 | | - |
10 | | -export const client = initClient(contract, { |
11 | | - baseUrl: getWebReqUrl('/api'), |
12 | | - throwOnUnknownStatus: true, |
13 | | - validateResponse: false, |
14 | | - credentials: 'include', |
15 | | - baseHeaders: { |
16 | | - 'Content-Type': 'application/json;charset=utf-8' |
17 | | - }, |
18 | | - api: async (args: BeforeFetchOptions) => { |
19 | | - const prepare = beforeFetch(args); |
20 | | - const response = await tsRestFetchApi(args); |
21 | | - return afterFetch(response, prepare); |
22 | | - } |
23 | | -}); |
24 | | - |
25 | | -const WHITE_LIST = ['/chat/share', '/chat', '/login']; |
26 | | -async function isTokenExpired() { |
27 | | - if (WHITE_LIST.includes(window.location.pathname)) return; |
28 | | - |
29 | | - await client.support.user.account.logout(); |
30 | | - const lastRoute = encodeURIComponent(location.pathname + location.search); |
31 | | - window.location.replace(getWebReqUrl(`/login?lastRoute=${lastRoute}`)); |
32 | | -} |
33 | | - |
34 | | -export function checkBusinessCode(code: number) { |
35 | | - if (code in TOKEN_ERROR_CODE) { |
36 | | - isTokenExpired(); |
37 | | - return; |
38 | | - } |
39 | | -} |
40 | | - |
41 | | -type Item = { id: string; controller: AbortController }; |
42 | | -const queue = new Map<string, Item[]>(); |
43 | | -function checkMaxRequestLimitation(options: { url: string; max: number }): { |
44 | | - id: string; |
45 | | - signal: AbortSignal; |
46 | | - release: () => void; |
47 | | -} { |
48 | | - const { url, max } = options; |
49 | | - const id = getNanoid(); |
50 | | - const controller = new AbortController(); |
51 | | - const item = queue.get(url); |
52 | | - |
53 | | - const current = item ?? []; |
54 | | - if (current.length >= max) { |
55 | | - const first = current.shift()!; |
56 | | - first.controller.abort(); |
57 | | - } |
58 | | - current.push({ id, controller }); |
59 | | - if (!item) queue.set(url, current); |
60 | | - |
61 | | - const release = () => { |
62 | | - const item = queue.get(url); |
63 | | - if (!item) return; |
64 | | - |
65 | | - const index = item.findIndex((item) => item.id === id); |
66 | | - if (index !== -1) { |
67 | | - item.splice(index, 1); |
68 | | - } |
69 | | - |
70 | | - if (item.length <= 0) { |
71 | | - queue.delete(url); |
72 | | - } |
73 | | - }; |
74 | | - |
75 | | - return { id, signal: controller.signal, release }; |
76 | | -} |
77 | | - |
78 | | -function checkHttpStatus(status: number): status is 200 { |
79 | | - if (status !== 200) return false; |
80 | | - return true; |
81 | | -} |
82 | | - |
83 | | -type BeforeFetchOptions = ApiFetcherArgs & { max?: number }; |
84 | | -function beforeFetch(options: BeforeFetchOptions): |
85 | | - | { |
86 | | - limit: { id: string; url: string; release: () => void }; |
87 | | - } |
88 | | - | undefined { |
89 | | - const { max, ...args } = options; |
90 | | - if (!max || max <= 0) return; |
91 | | - |
92 | | - const { id, signal, release } = checkMaxRequestLimitation({ url: args.path, max }); |
93 | | - args.fetchOptions ??= {}; |
94 | | - args.fetchOptions.signal = signal; |
95 | | - |
96 | | - return { |
97 | | - limit: { id, url: args.path, release } |
98 | | - }; |
| 1 | +import { initClient } from '@ts-rest/core'; |
| 2 | +import { fastgptContract } from './contracts'; |
| 3 | + |
| 4 | +export function createFastGPTClient(options: { |
| 5 | + baseUrl: string; |
| 6 | + baseHeaders?: Record<string, string>; |
| 7 | + api?: any; |
| 8 | + credentials?: RequestCredentials; |
| 9 | + throwOnUnknownStatus?: boolean; |
| 10 | + validateResponse?: boolean; |
| 11 | +}) { |
| 12 | + return initClient(fastgptContract, options); |
99 | 13 | } |
100 | | - |
101 | | -function afterFetch( |
102 | | - response: Awaited<ReturnType<typeof tsRestFetchApi>>, |
103 | | - prepare?: ReturnType<typeof beforeFetch> |
104 | | -) { |
105 | | - if (checkHttpStatus(response.status)) { |
106 | | - try { |
107 | | - const body = AnyResponseSchema.parse(response.body); |
108 | | - |
109 | | - response.body = body.data; |
110 | | - |
111 | | - if (prepare?.limit) { |
112 | | - prepare.limit.release(); |
113 | | - } |
114 | | - |
115 | | - return response; |
116 | | - } catch (error) { |
117 | | - if (error instanceof ZodError) { |
118 | | - throw new Error(error.message); |
119 | | - } |
120 | | - |
121 | | - throw new Error('Unknown error while intercept response'); |
122 | | - } |
123 | | - } else { |
124 | | - throw new Error(`HTTP error, status: ${response.status}`); |
125 | | - } |
126 | | -} |
127 | | - |
128 | | -type Client = typeof client; |
129 | | -type U<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : U<T[K]> }[keyof T]; |
130 | | -export type Endpoints = U<Client>; |
131 | | -type _Options<T extends Endpoints> = NonNullable<Parameters<T>[0]>; |
132 | | -type ExtractBodySchema<T extends Endpoints> = 'body' extends keyof _Options<T> |
133 | | - ? _Options<T>['body'] |
134 | | - : never; |
135 | | -type ExtractQuerySchema<T extends Endpoints> = 'query' extends keyof _Options<T> |
136 | | - ? _Options<T>['query'] |
137 | | - : never; |
138 | | -export type Params<T extends Endpoints> = (ExtractBodySchema<T> extends never |
139 | | - ? {} |
140 | | - : ExtractBodySchema<T>) & |
141 | | - (ExtractQuerySchema<T> extends never ? {} : ExtractQuerySchema<T>); |
142 | | -export type Options<T extends Endpoints> = Omit<_Options<T>, 'body' | 'query'>; |
143 | | -type Body<T extends Endpoints> = Extract<Awaited<ReturnType<T>>, { status: 200 }>['body']; |
144 | | -type RestAPIResult<T extends Endpoints> = Body<T>; |
145 | | - |
146 | | -const call = async <T extends Endpoints>( |
147 | | - api: T, |
148 | | - options: _Options<T> |
149 | | -): Promise<RestAPIResult<T>> => { |
150 | | - const res = await api(options as any); |
151 | | - |
152 | | - if (res.status !== 200) { |
153 | | - throw new Error(`Unexpected status: ${res.status}`); |
154 | | - } |
155 | | - |
156 | | - return res.body as RestAPIResult<T>; |
157 | | -}; |
158 | | - |
159 | | -export const RestAPI = <T extends Endpoints>( |
160 | | - endpoint: T, |
161 | | - transform?: (params: Params<T>) => { |
162 | | - body?: ExtractBodySchema<T> extends never ? any : ExtractBodySchema<T>; |
163 | | - query?: ExtractQuerySchema<T> extends never ? any : ExtractQuerySchema<T>; |
164 | | - } |
165 | | -) => { |
166 | | - return (params?: Params<T>, options?: Options<T>) => { |
167 | | - const transformedData = params && transform ? transform(params) : {}; |
168 | | - const finalOptions = { ...options, ...transformedData } as _Options<T>; |
169 | | - |
170 | | - return call(endpoint, finalOptions); |
171 | | - }; |
172 | | -}; |
0 commit comments