@deepseek-ai/dsh-deepseek-account-platform 0.1.7-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +96 -0
- package/README.zh.md +96 -0
- package/lib/index.js +891 -0
- package/lib/types/details.d.ts +17 -0
- package/lib/types/details.js +55 -0
- package/lib/types/index.d.ts +132 -0
- package/lib/types/index.js +488 -0
- package/lib/types/logout.d.ts +17 -0
- package/lib/types/logout.js +34 -0
- package/lib/types/protocol.d.ts +78 -0
- package/lib/types/protocol.js +210 -0
- package/lib/types/types.d.ts +3 -0
- package/lib/types/types.js +2 -0
- package/package.json +52 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { arch, platform, release } from "node:os";
|
|
3
|
+
import { finished } from "node:stream/promises";
|
|
4
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
5
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { DeepSeekAccount, desktopClientHeaders, mergePlatformCookies } from "@deepseek-ai/dsh-deepseek-account";
|
|
8
|
+
import { credentialKey } from "@deepseek-ai/dsh-credentials";
|
|
9
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
10
|
+
//#region lib/types/protocol.js
|
|
11
|
+
/** Validated platform HTTP messages and restricted browser destinations. */
|
|
12
|
+
/** Protocol errors expose a stable code, never a response body or authorization URL. */
|
|
13
|
+
var PlatformAuthError = class extends Error {
|
|
14
|
+
code;
|
|
15
|
+
/** @param code - safe error classification. */
|
|
16
|
+
constructor(code) {
|
|
17
|
+
super(`account: ${code}`);
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Accept HTTPS platform endpoints, or explicitly configured loopback development HTTP.
|
|
23
|
+
* @param value - configured origin.
|
|
24
|
+
* @param allowLoopbackHttp - development-only opt-in.
|
|
25
|
+
* @returns normalized origin.
|
|
26
|
+
*/
|
|
27
|
+
function platformOrigin(value, allowLoopbackHttp) {
|
|
28
|
+
const url = new URL(value);
|
|
29
|
+
const loopback = [
|
|
30
|
+
"localhost",
|
|
31
|
+
"127.0.0.1",
|
|
32
|
+
"[::1]"
|
|
33
|
+
].includes(url.hostname);
|
|
34
|
+
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash || !(url.protocol === "https:" || allowLoopbackHttp && loopback && url.protocol === "http:")) throw new Error("account: platformOrigin must be an HTTPS origin or explicitly enabled loopback HTTP origin");
|
|
35
|
+
return url.origin;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate platform-owned browser destinations without forwarding arbitrary URLs.
|
|
39
|
+
* @param value - returned browser URL.
|
|
40
|
+
* @param origin - configured platform origin.
|
|
41
|
+
* @param path - fixed authorize or completion path.
|
|
42
|
+
* @param rewriteOrigin - map validated browser pages to the configured development origin.
|
|
43
|
+
* @returns normalized URL on the configured origin.
|
|
44
|
+
*/
|
|
45
|
+
function browserUrl(value, origin, path, rewriteOrigin = false) {
|
|
46
|
+
let url;
|
|
47
|
+
try {
|
|
48
|
+
url = new URL(value);
|
|
49
|
+
} catch {
|
|
50
|
+
console.info("[deepseek-account] browser URL rejected", {
|
|
51
|
+
path,
|
|
52
|
+
reason: "invalid-url"
|
|
53
|
+
});
|
|
54
|
+
throw new PlatformAuthError("protocol");
|
|
55
|
+
}
|
|
56
|
+
const allowedOrigin = url.origin === origin || rewriteOrigin && url.protocol === "https:";
|
|
57
|
+
if (!allowedOrigin || url.pathname !== path || url.username || url.password || url.hash) {
|
|
58
|
+
console.info("[deepseek-account] browser URL rejected", {
|
|
59
|
+
path,
|
|
60
|
+
originMismatch: !allowedOrigin,
|
|
61
|
+
pathMismatch: url.pathname !== path,
|
|
62
|
+
hasCredentials: Boolean(url.username || url.password),
|
|
63
|
+
hasFragment: Boolean(url.hash)
|
|
64
|
+
});
|
|
65
|
+
throw new PlatformAuthError("protocol");
|
|
66
|
+
}
|
|
67
|
+
return rewriteOrigin ? `${origin}${url.pathname}${url.search}` : url.href;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Validate Host-only deployment headers without exposing their values in diagnostics.
|
|
71
|
+
* @param values - configured headers for the Platform origin.
|
|
72
|
+
* @returns normalized headers; authorization, routing and framing remain provider-owned.
|
|
73
|
+
*/
|
|
74
|
+
function platformHeaders(values) {
|
|
75
|
+
const headers = new Headers();
|
|
76
|
+
const names = /* @__PURE__ */ new Set();
|
|
77
|
+
for (const [name, value] of Object.entries(values)) {
|
|
78
|
+
const key = name.toLowerCase();
|
|
79
|
+
if ([
|
|
80
|
+
"authorization",
|
|
81
|
+
"x-dsh-auth-token",
|
|
82
|
+
"host",
|
|
83
|
+
"content-length",
|
|
84
|
+
"transfer-encoding",
|
|
85
|
+
"connection",
|
|
86
|
+
"content-type"
|
|
87
|
+
].includes(key) || names.has(key)) throw new Error("account: requestHeaders contains a reserved or duplicate header");
|
|
88
|
+
names.add(key);
|
|
89
|
+
try {
|
|
90
|
+
headers.set(name, value);
|
|
91
|
+
} catch {
|
|
92
|
+
throw new Error("account: requestHeaders contains an invalid header");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return Object.fromEntries(headers);
|
|
96
|
+
}
|
|
97
|
+
const envelope = z.object({
|
|
98
|
+
code: z.literal(0),
|
|
99
|
+
data: z.object({
|
|
100
|
+
biz_code: z.number().int(),
|
|
101
|
+
biz_data: z.unknown()
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
/** Successful initialization response. */
|
|
105
|
+
const initialization = z.object({
|
|
106
|
+
authorize_url: z.url(),
|
|
107
|
+
authorize_id: z.string().min(1),
|
|
108
|
+
expires_in: z.number().positive()
|
|
109
|
+
});
|
|
110
|
+
/** Successful code exchange response. */
|
|
111
|
+
const exchange = z.object({
|
|
112
|
+
token: z.string().regex(/^[\x21-\x7e]+$/),
|
|
113
|
+
authorized_url: z.url(),
|
|
114
|
+
user: z.unknown().optional()
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* Read one bounded platform response with stable, non-secret diagnostics.
|
|
118
|
+
* @param origin - validated platform origin.
|
|
119
|
+
* @param method - platform endpoint suffix.
|
|
120
|
+
* @param body - protocol request, never logged.
|
|
121
|
+
* @param signal - attempt cancellation and timeout.
|
|
122
|
+
* @param headers - validated deployment headers for this origin.
|
|
123
|
+
* @returns successful business payload, validated by its caller.
|
|
124
|
+
*/
|
|
125
|
+
async function requestPlatform(origin, method, body, signal, headers) {
|
|
126
|
+
return platformRequest(`${origin}/auth-api/v0/dsh/${method}`, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: {
|
|
129
|
+
...headers,
|
|
130
|
+
"content-type": "application/json"
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify(body)
|
|
133
|
+
}, signal);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Fetch a fixed Platform account endpoint with the grant kept in Host request headers.
|
|
137
|
+
* @param origin - configured and grant-matched origin.
|
|
138
|
+
* @param path - account endpoint.
|
|
139
|
+
* @param token - account grant.
|
|
140
|
+
* @param signal - credential lifetime and request timeout.
|
|
141
|
+
* @param headers - validated deployment headers for this origin.
|
|
142
|
+
* @returns successful business payload.
|
|
143
|
+
*/
|
|
144
|
+
function requestAccount(origin, path, token, signal, headers) {
|
|
145
|
+
return platformRequest(`${origin}${path}`, {
|
|
146
|
+
method: "GET",
|
|
147
|
+
headers: {
|
|
148
|
+
...headers,
|
|
149
|
+
"x-dsh-auth-token": token
|
|
150
|
+
}
|
|
151
|
+
}, signal);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* End the Platform session using its existing logout endpoint.
|
|
155
|
+
* @param origin - configured origin matching the grant issuer.
|
|
156
|
+
* @param token - stored account token.
|
|
157
|
+
* @param signal - logout request deadline.
|
|
158
|
+
* @param headers - validated deployment headers for this origin.
|
|
159
|
+
* @returns after Platform confirms logout.
|
|
160
|
+
*/
|
|
161
|
+
async function logoutAccount(origin, token, signal, headers) {
|
|
162
|
+
await platformRequest(`${origin}/auth-api/v0/users/logout`, {
|
|
163
|
+
method: "POST",
|
|
164
|
+
headers: {
|
|
165
|
+
...headers,
|
|
166
|
+
"x-dsh-auth-token": token
|
|
167
|
+
}
|
|
168
|
+
}, signal);
|
|
169
|
+
}
|
|
170
|
+
async function platformRequest(url, init, signal) {
|
|
171
|
+
const path = new URL(url).pathname;
|
|
172
|
+
console.info("[deepseek-account] request", {
|
|
173
|
+
path,
|
|
174
|
+
method: init.method
|
|
175
|
+
});
|
|
176
|
+
let response;
|
|
177
|
+
try {
|
|
178
|
+
response = await fetch(url, {
|
|
179
|
+
...init,
|
|
180
|
+
redirect: "error",
|
|
181
|
+
signal
|
|
182
|
+
});
|
|
183
|
+
} catch {
|
|
184
|
+
console.info("[deepseek-account] request failed", {
|
|
185
|
+
path,
|
|
186
|
+
errorCode: "network",
|
|
187
|
+
aborted: signal.aborted
|
|
188
|
+
});
|
|
189
|
+
throw new PlatformAuthError("network");
|
|
190
|
+
}
|
|
191
|
+
console.info("[deepseek-account] response", {
|
|
192
|
+
path,
|
|
193
|
+
status: response.status
|
|
194
|
+
});
|
|
195
|
+
if (!response.ok || response.body === null) {
|
|
196
|
+
await response.body?.cancel();
|
|
197
|
+
throw new PlatformAuthError("network");
|
|
198
|
+
}
|
|
199
|
+
const reader = response.body.getReader();
|
|
200
|
+
const chunks = [];
|
|
201
|
+
let size = 0;
|
|
202
|
+
let stage = "read-body";
|
|
203
|
+
try {
|
|
204
|
+
while (true) {
|
|
205
|
+
const next = await reader.read();
|
|
206
|
+
if (next.done) break;
|
|
207
|
+
size += next.value.byteLength;
|
|
208
|
+
if (size > 65536) {
|
|
209
|
+
stage = "body-limit";
|
|
210
|
+
throw new PlatformAuthError("protocol");
|
|
211
|
+
}
|
|
212
|
+
chunks.push(next.value);
|
|
213
|
+
}
|
|
214
|
+
stage = "parse-json";
|
|
215
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
216
|
+
const codes = z.object({
|
|
217
|
+
code: z.number().int(),
|
|
218
|
+
data: z.object({ biz_code: z.number().int() }).optional()
|
|
219
|
+
}).safeParse(payload);
|
|
220
|
+
if (codes.success) console.info("[deepseek-account] response codes", {
|
|
221
|
+
path,
|
|
222
|
+
code: codes.data.code,
|
|
223
|
+
bizCode: codes.data.data?.biz_code
|
|
224
|
+
});
|
|
225
|
+
stage = "envelope";
|
|
226
|
+
const parsed = envelope.safeParse(payload);
|
|
227
|
+
if (!parsed.success) {
|
|
228
|
+
console.info("[deepseek-account] envelope rejected", {
|
|
229
|
+
path,
|
|
230
|
+
issues: parsed.error.issues.map((issue) => ({
|
|
231
|
+
path: issue.path,
|
|
232
|
+
code: issue.code
|
|
233
|
+
}))
|
|
234
|
+
});
|
|
235
|
+
throw new PlatformAuthError("protocol");
|
|
236
|
+
}
|
|
237
|
+
stage = "business-code";
|
|
238
|
+
if (parsed.data.data.biz_code !== 0) throw new PlatformAuthError("protocol");
|
|
239
|
+
return parsed.data.data.biz_data;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.info("[deepseek-account] response rejected", {
|
|
242
|
+
path,
|
|
243
|
+
stage,
|
|
244
|
+
errorCode: "protocol"
|
|
245
|
+
});
|
|
246
|
+
if (error instanceof PlatformAuthError) throw error;
|
|
247
|
+
throw new PlatformAuthError("protocol");
|
|
248
|
+
} finally {
|
|
249
|
+
await reader.cancel().catch(() => void 0);
|
|
250
|
+
reader.releaseLock();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Accept a browser-accessible loopback HTTP origin for local or SSH-forwarded login.
|
|
255
|
+
* @param value - loopback HTTP origin with an explicit port supplied by the authenticated initiating client.
|
|
256
|
+
* @returns normalized origin; remote domains and path-based proxies are unsupported.
|
|
257
|
+
*/
|
|
258
|
+
function loginOrigin(value) {
|
|
259
|
+
let url;
|
|
260
|
+
try {
|
|
261
|
+
url = new URL(value);
|
|
262
|
+
} catch {
|
|
263
|
+
throw new PlatformAuthError("protocol");
|
|
264
|
+
}
|
|
265
|
+
const explicitPort = /^http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):([0-9]+)\/?$/i.exec(value)?.[1];
|
|
266
|
+
if (explicitPort === void 0 || Number(explicitPort) === 0 || url.protocol !== "http:" || ![
|
|
267
|
+
"localhost",
|
|
268
|
+
"127.0.0.1",
|
|
269
|
+
"[::1]"
|
|
270
|
+
].includes(url.hostname) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new PlatformAuthError("protocol");
|
|
271
|
+
return `${url.protocol}//${url.hostname}:${Number(explicitPort)}`;
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region lib/types/details.js
|
|
275
|
+
/** Platform Web profile and wallet queries projected for account UI consumers. */
|
|
276
|
+
const user = z.object({
|
|
277
|
+
id: z.string().nullish(),
|
|
278
|
+
email: z.string(),
|
|
279
|
+
mobile: z.string().optional(),
|
|
280
|
+
mobile_number: z.string().optional(),
|
|
281
|
+
id_profile: z.object({
|
|
282
|
+
name: z.string().nullable(),
|
|
283
|
+
picture: z.string().nullish()
|
|
284
|
+
}).nullish()
|
|
285
|
+
});
|
|
286
|
+
const wallet = z.object({
|
|
287
|
+
currency: z.enum(["CNY", "USD"]),
|
|
288
|
+
balance: z.string().regex(/^-?\d+(?:\.\d+)?$/)
|
|
289
|
+
});
|
|
290
|
+
const summary = z.object({
|
|
291
|
+
normal_wallets: z.array(wallet),
|
|
292
|
+
bonus_wallets: z.array(wallet)
|
|
293
|
+
});
|
|
294
|
+
/** Project Platform user data without retaining credentials or unneeded fields.
|
|
295
|
+
* @param value - current or exchange user response.
|
|
296
|
+
* @returns UI account profile.
|
|
297
|
+
*/
|
|
298
|
+
function profile(value) {
|
|
299
|
+
const parsed = user.safeParse(value);
|
|
300
|
+
if (!parsed.success) throw new PlatformAuthError("protocol");
|
|
301
|
+
const { email, mobile, mobile_number: mobileNumber, id_profile: identity } = parsed.data;
|
|
302
|
+
return {
|
|
303
|
+
id: parsed.data.id == null ? null : parsed.data.id,
|
|
304
|
+
avatarUrl: identity?.picture || null,
|
|
305
|
+
name: identity?.name || null,
|
|
306
|
+
contact: mobile || mobileNumber || email || null
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const queries = {
|
|
310
|
+
profile: {
|
|
311
|
+
path: "/auth-api/v0/users/current",
|
|
312
|
+
parse: (value) => ({
|
|
313
|
+
status: "ready",
|
|
314
|
+
value: profile(value)
|
|
315
|
+
})
|
|
316
|
+
},
|
|
317
|
+
balance: {
|
|
318
|
+
path: "/api/v0/users/get_user_summary",
|
|
319
|
+
parse: (value) => {
|
|
320
|
+
const parsed = summary.safeParse(value);
|
|
321
|
+
if (!parsed.success) throw new PlatformAuthError("protocol");
|
|
322
|
+
return {
|
|
323
|
+
status: "ready",
|
|
324
|
+
value: parsed.data.normal_wallets,
|
|
325
|
+
bonusWallets: parsed.data.bonus_wallets
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
/**
|
|
331
|
+
* Read one Platform account field without waiting for the other query.
|
|
332
|
+
* @param field - profile or recharge and bonus wallet balances.
|
|
333
|
+
* @param origin - configured origin matching the stored grant issuer.
|
|
334
|
+
* @param token - stored authorization token; response tokens are discarded.
|
|
335
|
+
* @param signal - request and credential lifetime.
|
|
336
|
+
* @param headers - validated deployment headers for the configured origin.
|
|
337
|
+
* @returns sanitized query outcome; failure never becomes a zero balance.
|
|
338
|
+
*/
|
|
339
|
+
async function readAccountDetail(field, origin, token, signal, headers) {
|
|
340
|
+
const query = queries[field];
|
|
341
|
+
try {
|
|
342
|
+
return query.parse(await requestAccount(origin, query.path, token, signal, headers));
|
|
343
|
+
} catch {
|
|
344
|
+
return { status: "failed" };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region lib/types/logout.js
|
|
349
|
+
/** Bounded, memory-only revocation of a token already removed from local credentials. */
|
|
350
|
+
/**
|
|
351
|
+
* Revoke one captured grant without changing local account state.
|
|
352
|
+
* @param origin - Issuer verified before local grant removal.
|
|
353
|
+
* @param token - Removed grant, never replaced with a later login's token.
|
|
354
|
+
* @param policy - Request deadline and exponential retry limits.
|
|
355
|
+
* @param signal - Provider lifetime; shutdown aborts requests and delays.
|
|
356
|
+
* @param headers - Validated issuer-only deployment headers.
|
|
357
|
+
* @returns After success, exhaustion, or cancellation; remote failures stay in the background.
|
|
358
|
+
*/
|
|
359
|
+
async function revokeAccount(origin, token, policy, signal, headers) {
|
|
360
|
+
for (let attempt = 0; attempt <= policy.maxRetries; attempt++) {
|
|
361
|
+
if (signal.aborted) return;
|
|
362
|
+
if (attempt > 0) try {
|
|
363
|
+
await setTimeout$1(policy.delayMs * 2 ** (attempt - 1), void 0, {
|
|
364
|
+
signal,
|
|
365
|
+
ref: false
|
|
366
|
+
});
|
|
367
|
+
} catch {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
await logoutAccount(origin, token, AbortSignal.any([signal, AbortSignal.timeout(policy.requestTimeoutMs)]), headers);
|
|
372
|
+
return;
|
|
373
|
+
} catch {}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
//#endregion
|
|
377
|
+
//#region lib/types/index.js
|
|
378
|
+
/** Platform PKCE account provider; browser approval never bypasses local cancellation. */
|
|
379
|
+
const KEY = credentialKey("deepseek-account-platform", "default");
|
|
380
|
+
const DEVICE = credentialKey("deepseek-account-platform", "device");
|
|
381
|
+
const grant = z.object({
|
|
382
|
+
version: z.literal(1),
|
|
383
|
+
token: z.string().min(1),
|
|
384
|
+
issuer: z.url()
|
|
385
|
+
});
|
|
386
|
+
const device = z.object({ id: z.uuid() });
|
|
387
|
+
/** Validated deployment choices. */
|
|
388
|
+
const Config = Schema.object({
|
|
389
|
+
platformOrigin: Schema.string().default("https://platform.deepseek.com"),
|
|
390
|
+
desktopPlatform: Schema.union([
|
|
391
|
+
Schema.const("darwin"),
|
|
392
|
+
Schema.const("win32"),
|
|
393
|
+
Schema.const(null)
|
|
394
|
+
]).default(null),
|
|
395
|
+
embeddedPageDist: Schema.string().default(""),
|
|
396
|
+
inferenceOrigin: Schema.string().default("https://api.deepseek.com"),
|
|
397
|
+
allowLoopbackHttp: Schema.boolean().default(false),
|
|
398
|
+
rewriteBrowserOrigin: Schema.boolean().default(false),
|
|
399
|
+
requestHeaders: Schema.dict(Schema.string().role("secret")).default({}),
|
|
400
|
+
accountRequestHeaders: Schema.dict(Schema.string().role("secret")).default({}),
|
|
401
|
+
requestTimeoutMs: Schema.number().min(1).max(12e4).default(3e4),
|
|
402
|
+
logoutMaxRetries: Schema.number().min(0).max(5).step(1).default(5),
|
|
403
|
+
logoutRetryDelayMs: Schema.number().min(1).max(6e4).default(1e3),
|
|
404
|
+
attemptTimeoutMs: Schema.number().min(1).max(36e5).default(6e5)
|
|
405
|
+
});
|
|
406
|
+
/** The platform implementation owns login state and its opaque stored grant. */
|
|
407
|
+
var PlatformAccount = class extends DeepSeekAccount {
|
|
408
|
+
static inject = ["credentials", "authorization"];
|
|
409
|
+
static Config = Config;
|
|
410
|
+
origin;
|
|
411
|
+
embeddedPageDist;
|
|
412
|
+
inferenceOrigin;
|
|
413
|
+
rewriteBrowserOrigin;
|
|
414
|
+
clientHeaders;
|
|
415
|
+
requestHeaders;
|
|
416
|
+
accountRequestHeaders;
|
|
417
|
+
requestTimeout;
|
|
418
|
+
attemptTimeout;
|
|
419
|
+
logoutPolicy;
|
|
420
|
+
logoutLifetime = new AbortController();
|
|
421
|
+
revocations = /* @__PURE__ */ new Set();
|
|
422
|
+
attempt;
|
|
423
|
+
listeners = /* @__PURE__ */ new Set();
|
|
424
|
+
lastProfile;
|
|
425
|
+
detailsLifetime = new AbortController();
|
|
426
|
+
closed = false;
|
|
427
|
+
removing;
|
|
428
|
+
/** @param ctx - Host with authorization and credentials services. @param config - deployment options. */
|
|
429
|
+
constructor(ctx, config = {}) {
|
|
430
|
+
super(ctx);
|
|
431
|
+
const resolved = Config(config);
|
|
432
|
+
this.embeddedPageDist = resolved.embeddedPageDist;
|
|
433
|
+
this.origin = platformOrigin(resolved.platformOrigin, resolved.allowLoopbackHttp);
|
|
434
|
+
const inference = new URL(resolved.inferenceOrigin);
|
|
435
|
+
if (!["http:", "https:"].includes(inference.protocol) || inference.username || inference.password || inference.pathname !== "/" || inference.search || inference.hash) throw new Error("account: inferenceOrigin must be an HTTP(S) origin without credentials, path, query or fragment");
|
|
436
|
+
this.inferenceOrigin = inference.origin;
|
|
437
|
+
this.rewriteBrowserOrigin = resolved.rewriteBrowserOrigin;
|
|
438
|
+
this.clientHeaders = desktopClientHeaders(resolved.desktopPlatform);
|
|
439
|
+
this.requestHeaders = platformHeaders(resolved.requestHeaders);
|
|
440
|
+
const accountHeaders = platformHeaders(resolved.accountRequestHeaders);
|
|
441
|
+
this.accountRequestHeaders = {
|
|
442
|
+
...this.requestHeaders,
|
|
443
|
+
...accountHeaders
|
|
444
|
+
};
|
|
445
|
+
if (accountHeaders.cookie !== void 0) this.accountRequestHeaders.cookie = mergePlatformCookies(this.requestHeaders.cookie ?? "", accountHeaders.cookie);
|
|
446
|
+
this.requestTimeout = resolved.requestTimeoutMs;
|
|
447
|
+
this.attemptTimeout = resolved.attemptTimeoutMs;
|
|
448
|
+
this.logoutPolicy = {
|
|
449
|
+
maxRetries: resolved.logoutMaxRetries,
|
|
450
|
+
delayMs: resolved.logoutRetryDelayMs,
|
|
451
|
+
requestTimeoutMs: this.requestTimeout
|
|
452
|
+
};
|
|
453
|
+
ctx.authorization.registerFlow({
|
|
454
|
+
key: KEY,
|
|
455
|
+
label: "DeepSeek",
|
|
456
|
+
methods: [{
|
|
457
|
+
id: "browser",
|
|
458
|
+
label: "DeepSeek"
|
|
459
|
+
}],
|
|
460
|
+
run: (session) => {
|
|
461
|
+
const attempt = this.attempt;
|
|
462
|
+
if (attempt === void 0) return Promise.reject(new PlatformAuthError("protocol"));
|
|
463
|
+
attempt.running = this.run(session, attempt);
|
|
464
|
+
return attempt.running;
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
ctx.on("credentials/record-updated", (key) => {
|
|
468
|
+
if (key !== KEY) return;
|
|
469
|
+
this.invalidateDetails();
|
|
470
|
+
this.changed();
|
|
471
|
+
});
|
|
472
|
+
ctx.effect(() => async () => {
|
|
473
|
+
this.closed = true;
|
|
474
|
+
this.logoutLifetime.abort();
|
|
475
|
+
this.invalidateDetails();
|
|
476
|
+
const active = this.attempt;
|
|
477
|
+
if (active !== void 0) {
|
|
478
|
+
if (active.view.phase !== "committing") active.controller.abort();
|
|
479
|
+
await active.done;
|
|
480
|
+
}
|
|
481
|
+
await this.removing;
|
|
482
|
+
await Promise.all(this.revocations);
|
|
483
|
+
this.changed();
|
|
484
|
+
}, "account: active attempt lifetime");
|
|
485
|
+
}
|
|
486
|
+
async [Service.init]() {
|
|
487
|
+
const record = await this.ctx.credentials.readRecord(KEY);
|
|
488
|
+
if (record === void 0) return;
|
|
489
|
+
if (record.kind !== "grant") throw new PlatformAuthError("storage");
|
|
490
|
+
const parsed = grant.safeParse(record.payload);
|
|
491
|
+
if (!parsed.success) throw new PlatformAuthError("storage");
|
|
492
|
+
if (parsed.data.issuer === this.origin) return;
|
|
493
|
+
await this.ctx.credentials.deleteRecord(KEY);
|
|
494
|
+
console.info("[deepseek-account] stored grant discarded", { reason: "issuer-mismatch" });
|
|
495
|
+
}
|
|
496
|
+
async getState() {
|
|
497
|
+
const record = await this.ctx.credentials.readRecord(KEY);
|
|
498
|
+
if (record !== void 0 && (record.kind !== "grant" || !grant.safeParse(record.payload).success)) throw new PlatformAuthError("storage");
|
|
499
|
+
return {
|
|
500
|
+
status: record === void 0 ? "signed-out" : "credential-stored",
|
|
501
|
+
attempt: this.attempt?.view ?? null,
|
|
502
|
+
links: {
|
|
503
|
+
usageUrl: new URL("/usage", this.origin).href,
|
|
504
|
+
topUpUrl: new URL("/top_up", this.origin).href
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
async getProfile() {
|
|
509
|
+
const lifetime = this.detailsLifetime;
|
|
510
|
+
const result = await this.getDetail("profile");
|
|
511
|
+
if (this.detailsLifetime !== lifetime) return null;
|
|
512
|
+
if (result?.status === "ready") this.lastProfile = result;
|
|
513
|
+
return result?.status === "failed" ? this.lastProfile ?? result : result;
|
|
514
|
+
}
|
|
515
|
+
getBalance() {
|
|
516
|
+
return this.getDetail("balance");
|
|
517
|
+
}
|
|
518
|
+
async getDetail(field) {
|
|
519
|
+
const lifetime = this.detailsLifetime;
|
|
520
|
+
const stored = await this.readCurrentGrant(lifetime);
|
|
521
|
+
if (stored === null || lifetime.signal.aborted) return null;
|
|
522
|
+
if (field === "profile" && this.attempt?.initialProfile?.token === stored.token) {
|
|
523
|
+
const initial = this.attempt.initialProfile.value;
|
|
524
|
+
delete this.attempt.initialProfile;
|
|
525
|
+
return initial;
|
|
526
|
+
}
|
|
527
|
+
const details = await readAccountDetail(field, this.origin, stored.token, AbortSignal.any([lifetime.signal, AbortSignal.timeout(this.requestTimeout)]), {
|
|
528
|
+
...this.accountRequestHeaders,
|
|
529
|
+
...this.clientHeaders
|
|
530
|
+
});
|
|
531
|
+
return this.detailsLifetime !== lifetime ? null : details;
|
|
532
|
+
}
|
|
533
|
+
async getPlatformSession() {
|
|
534
|
+
const lifetime = this.detailsLifetime;
|
|
535
|
+
const stored = await this.readCurrentGrant(lifetime);
|
|
536
|
+
if (stored === null || lifetime.signal.aborted) return null;
|
|
537
|
+
const requestHeaders = {
|
|
538
|
+
...this.accountRequestHeaders,
|
|
539
|
+
...this.clientHeaders
|
|
540
|
+
};
|
|
541
|
+
return {
|
|
542
|
+
origin: this.origin,
|
|
543
|
+
token: stored.token,
|
|
544
|
+
...this.embeddedPageDist ? { embeddedPageDist: this.embeddedPageDist } : {},
|
|
545
|
+
...Object.keys(requestHeaders).length ? { requestHeaders } : {}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
async readCurrentGrant(lifetime) {
|
|
549
|
+
if (this.closed) return null;
|
|
550
|
+
const record = await this.ctx.credentials.readRecord(KEY);
|
|
551
|
+
if (record === void 0 || lifetime.signal.aborted) return null;
|
|
552
|
+
if (record.kind !== "grant") throw new PlatformAuthError("storage");
|
|
553
|
+
const parsed = grant.safeParse(record.payload);
|
|
554
|
+
if (!parsed.success) throw new PlatformAuthError("storage");
|
|
555
|
+
if (parsed.data.issuer !== this.origin) throw new PlatformAuthError("protocol");
|
|
556
|
+
return parsed.data;
|
|
557
|
+
}
|
|
558
|
+
async resolveToken(url) {
|
|
559
|
+
const destination = new URL(url);
|
|
560
|
+
if (destination.origin !== this.inferenceOrigin || destination.username || destination.password) return void 0;
|
|
561
|
+
const record = await this.ctx.credentials.readRecord(KEY);
|
|
562
|
+
if (record === void 0) return void 0;
|
|
563
|
+
if (record.kind !== "grant") throw new PlatformAuthError("storage");
|
|
564
|
+
const result = grant.safeParse(record.payload);
|
|
565
|
+
if (!result.success) throw new PlatformAuthError("storage");
|
|
566
|
+
const issuer = new URL(result.data.issuer);
|
|
567
|
+
if (result.data.token.startsWith("dsh_mock_")) return void 0;
|
|
568
|
+
if (this.inferenceOrigin === "https://api.deepseek.com") {
|
|
569
|
+
if ([
|
|
570
|
+
"localhost",
|
|
571
|
+
"127.0.0.1",
|
|
572
|
+
"[::1]"
|
|
573
|
+
].includes(issuer.hostname)) return void 0;
|
|
574
|
+
} else if (issuer.origin !== this.origin) return void 0;
|
|
575
|
+
return result.data.token;
|
|
576
|
+
}
|
|
577
|
+
async startSignIn(locale, callbackOrigin, loginSource) {
|
|
578
|
+
if (this.removing !== void 0) await this.removing;
|
|
579
|
+
const origin = loginOrigin(callbackOrigin);
|
|
580
|
+
if (this.closed) throw new PlatformAuthError("protocol");
|
|
581
|
+
if (this.attempt !== void 0 && [
|
|
582
|
+
"initializing",
|
|
583
|
+
"waiting-browser",
|
|
584
|
+
"exchanging",
|
|
585
|
+
"committing"
|
|
586
|
+
].includes(this.attempt.view.phase)) return this.getState();
|
|
587
|
+
const previous = this.attempt;
|
|
588
|
+
if (previous !== void 0) {
|
|
589
|
+
await previous.done;
|
|
590
|
+
if (this.closed) throw new PlatformAuthError("protocol");
|
|
591
|
+
if (this.attempt !== previous) return this.getState();
|
|
592
|
+
}
|
|
593
|
+
const attempt = {
|
|
594
|
+
origin,
|
|
595
|
+
loginSource,
|
|
596
|
+
locale: locale.toLowerCase().split(/[-_]/)[0] === "zh" ? "zh_CN" : "en_US",
|
|
597
|
+
view: {
|
|
598
|
+
id: randomUUID(),
|
|
599
|
+
phase: "initializing"
|
|
600
|
+
},
|
|
601
|
+
controller: new AbortController(),
|
|
602
|
+
done: Promise.resolve(),
|
|
603
|
+
running: Promise.resolve()
|
|
604
|
+
};
|
|
605
|
+
this.attempt = attempt;
|
|
606
|
+
attempt.done = this.ctx.authorization.begin({
|
|
607
|
+
key: KEY,
|
|
608
|
+
signal: attempt.controller.signal,
|
|
609
|
+
interaction: {
|
|
610
|
+
notify: () => void 0,
|
|
611
|
+
prompt: () => Promise.reject(new PlatformAuthError("protocol"))
|
|
612
|
+
}
|
|
613
|
+
}).then((outcome) => {
|
|
614
|
+
this.update(attempt, { phase: outcome.status === "authorized" ? "succeeded" : "cancelled" });
|
|
615
|
+
if (outcome.status === "authorized" && attempt.completionUrl !== void 0) attempt.callback?.writeHead(302, {
|
|
616
|
+
location: attempt.completionUrl,
|
|
617
|
+
"cache-control": "no-store"
|
|
618
|
+
}).end();
|
|
619
|
+
else attempt.callback?.writeHead(204, { "cache-control": "no-store" }).end();
|
|
620
|
+
}).catch((error) => {
|
|
621
|
+
const code = error instanceof PlatformAuthError ? error.code : "protocol";
|
|
622
|
+
console.info("[deepseek-account] sign-in failed", { errorCode: code });
|
|
623
|
+
this.update(attempt, {
|
|
624
|
+
phase: code === "expired" ? "expired" : "failed",
|
|
625
|
+
errorCode: code
|
|
626
|
+
});
|
|
627
|
+
this.finishFailedCallback(attempt);
|
|
628
|
+
}).then(async () => {
|
|
629
|
+
await attempt.running.catch(() => void 0);
|
|
630
|
+
if (attempt.callback !== void 0) await finished(attempt.callback, { cleanup: true }).catch(() => void 0);
|
|
631
|
+
await attempt.disposeCallback?.();
|
|
632
|
+
});
|
|
633
|
+
this.changed();
|
|
634
|
+
return this.getState();
|
|
635
|
+
}
|
|
636
|
+
async cancelSignIn(id) {
|
|
637
|
+
const attempt = this.attempt;
|
|
638
|
+
if (attempt?.view.id === id) {
|
|
639
|
+
if (attempt.view.phase !== "committing") {
|
|
640
|
+
attempt.controller.abort();
|
|
641
|
+
this.ctx.authorization.cancel(KEY);
|
|
642
|
+
}
|
|
643
|
+
await attempt.done;
|
|
644
|
+
}
|
|
645
|
+
return this.getState();
|
|
646
|
+
}
|
|
647
|
+
signOut() {
|
|
648
|
+
this.removing ??= (async () => {
|
|
649
|
+
if (this.closed) throw new PlatformAuthError("protocol");
|
|
650
|
+
if (this.attempt !== void 0) await this.cancelSignIn(this.attempt.view.id);
|
|
651
|
+
const record = await this.ctx.credentials.readRecord(KEY);
|
|
652
|
+
if (record !== void 0) {
|
|
653
|
+
if (record.kind !== "grant") throw new PlatformAuthError("storage");
|
|
654
|
+
const parsed = grant.safeParse(record.payload);
|
|
655
|
+
if (!parsed.success) throw new PlatformAuthError("storage");
|
|
656
|
+
if (parsed.data.issuer !== this.origin) throw new PlatformAuthError("protocol");
|
|
657
|
+
await this.ctx.credentials.deleteRecord(KEY);
|
|
658
|
+
this.revoke(parsed.data.token);
|
|
659
|
+
}
|
|
660
|
+
this.attempt = void 0;
|
|
661
|
+
this.changed();
|
|
662
|
+
return this.getState();
|
|
663
|
+
})().finally(() => {
|
|
664
|
+
this.removing = void 0;
|
|
665
|
+
});
|
|
666
|
+
return this.removing;
|
|
667
|
+
}
|
|
668
|
+
async *watch(signal) {
|
|
669
|
+
let dirty = true;
|
|
670
|
+
let wake;
|
|
671
|
+
const changed = () => {
|
|
672
|
+
dirty = true;
|
|
673
|
+
wake?.();
|
|
674
|
+
};
|
|
675
|
+
this.listeners.add(changed);
|
|
676
|
+
signal.addEventListener("abort", changed, { once: true });
|
|
677
|
+
try {
|
|
678
|
+
while (!this.closed && !signal.aborted) {
|
|
679
|
+
if (dirty) {
|
|
680
|
+
dirty = false;
|
|
681
|
+
yield await this.getState();
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
await new Promise((resolve) => {
|
|
685
|
+
wake = resolve;
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
} finally {
|
|
689
|
+
this.listeners.delete(changed);
|
|
690
|
+
signal.removeEventListener("abort", changed);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
revoke(token) {
|
|
694
|
+
if (this.closed) return;
|
|
695
|
+
const revocation = revokeAccount(this.origin, token, this.logoutPolicy, this.logoutLifetime.signal, {
|
|
696
|
+
...this.requestHeaders,
|
|
697
|
+
...this.clientHeaders
|
|
698
|
+
}).finally(() => {
|
|
699
|
+
this.revocations.delete(revocation);
|
|
700
|
+
});
|
|
701
|
+
this.revocations.add(revocation);
|
|
702
|
+
}
|
|
703
|
+
invalidateDetails() {
|
|
704
|
+
this.lastProfile = void 0;
|
|
705
|
+
this.detailsLifetime.abort();
|
|
706
|
+
this.detailsLifetime = new AbortController();
|
|
707
|
+
}
|
|
708
|
+
changed() {
|
|
709
|
+
for (const listener of this.listeners) listener();
|
|
710
|
+
}
|
|
711
|
+
update(attempt, value) {
|
|
712
|
+
const { authorizeUrl: _url, ...rest } = attempt.view;
|
|
713
|
+
attempt.view = {
|
|
714
|
+
...rest,
|
|
715
|
+
...value
|
|
716
|
+
};
|
|
717
|
+
this.changed();
|
|
718
|
+
}
|
|
719
|
+
async run(session, attempt) {
|
|
720
|
+
const webServer = this.ctx.get("webServer");
|
|
721
|
+
if (webServer === void 0) throw new PlatformAuthError("protocol");
|
|
722
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
723
|
+
const state = randomBytes(32).toString("base64url");
|
|
724
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
725
|
+
let authorizeId;
|
|
726
|
+
const code = Promise.withResolvers();
|
|
727
|
+
code.promise.catch(() => void 0);
|
|
728
|
+
const deadline = new AbortController();
|
|
729
|
+
let expiresAt = Date.now() + this.attemptTimeout;
|
|
730
|
+
const signal = AbortSignal.any([session.signal, deadline.signal]);
|
|
731
|
+
let timer = setTimeout(() => {
|
|
732
|
+
deadline.abort();
|
|
733
|
+
}, this.attemptTimeout);
|
|
734
|
+
const abort = () => {
|
|
735
|
+
code.reject(new PlatformAuthError("expired"));
|
|
736
|
+
};
|
|
737
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
738
|
+
try {
|
|
739
|
+
attempt.disposeCallback = this.ctx.effect(() => webServer.register({
|
|
740
|
+
kind: "exact",
|
|
741
|
+
path: "/oauth/callback",
|
|
742
|
+
handler: (req, res) => {
|
|
743
|
+
let url;
|
|
744
|
+
try {
|
|
745
|
+
url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
746
|
+
} catch {
|
|
747
|
+
res.writeHead(400, { "cache-control": "no-store" }).end();
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const receivedCode = url.searchParams.get("code");
|
|
751
|
+
const receivedState = url.searchParams.get("state") ?? "";
|
|
752
|
+
const validState = Buffer.byteLength(receivedState) === Buffer.byteLength(state) && timingSafeEqual(Buffer.from(receivedState), Buffer.from(state));
|
|
753
|
+
if (req.method !== "GET" || url.pathname !== "/oauth/callback" || !validState || !receivedCode || url.searchParams.getAll("state").length !== 1 || url.searchParams.getAll("code").length !== 1) {
|
|
754
|
+
res.writeHead(400, { "cache-control": "no-store" }).end();
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (signal.aborted || attempt.callback !== void 0 || attempt.view.phase !== "waiting-browser") {
|
|
758
|
+
res.writeHead(410, { "cache-control": "no-store" }).end();
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
attempt.callback = res;
|
|
762
|
+
code.resolve(receivedCode);
|
|
763
|
+
}
|
|
764
|
+
}), "account: browser callback");
|
|
765
|
+
signal.throwIfAborted();
|
|
766
|
+
const redirectUri = `${attempt.origin}/oauth/callback`;
|
|
767
|
+
const init = initialization.safeParse(await this.request("auth_init", {
|
|
768
|
+
code_challenge: challenge,
|
|
769
|
+
code_challenge_method: "S256",
|
|
770
|
+
state,
|
|
771
|
+
redirect_uri: redirectUri,
|
|
772
|
+
locale: attempt.locale,
|
|
773
|
+
login_source: attempt.loginSource
|
|
774
|
+
}, signal), { reportInput: true });
|
|
775
|
+
if (!init.success) this.rejectPayload("auth_init", init.error);
|
|
776
|
+
const authorizeUrl = browserUrl(init.data.authorize_url, this.origin, "/dsh/authorize", this.rewriteBrowserOrigin);
|
|
777
|
+
authorizeId = init.data.authorize_id;
|
|
778
|
+
signal.throwIfAborted();
|
|
779
|
+
const now = Date.now();
|
|
780
|
+
expiresAt = Math.min(expiresAt, now + init.data.expires_in * 1e3);
|
|
781
|
+
const remaining = expiresAt - now;
|
|
782
|
+
if (remaining <= 0) {
|
|
783
|
+
deadline.abort();
|
|
784
|
+
signal.throwIfAborted();
|
|
785
|
+
}
|
|
786
|
+
clearTimeout(timer);
|
|
787
|
+
timer = setTimeout(() => {
|
|
788
|
+
deadline.abort();
|
|
789
|
+
}, remaining);
|
|
790
|
+
this.update(attempt, {
|
|
791
|
+
phase: "waiting-browser",
|
|
792
|
+
authorizeUrl,
|
|
793
|
+
expiresAt
|
|
794
|
+
});
|
|
795
|
+
const receivedCode = await code.promise;
|
|
796
|
+
signal.throwIfAborted();
|
|
797
|
+
this.update(attempt, { phase: "exchanging" });
|
|
798
|
+
const deviceRecord = await this.ctx.credentials.modifyRecord(DEVICE, (current) => Promise.resolve(current === void 0 ? {
|
|
799
|
+
kind: "grant",
|
|
800
|
+
payload: { id: randomUUID() }
|
|
801
|
+
} : void 0));
|
|
802
|
+
if (deviceRecord?.kind !== "grant") throw new PlatformAuthError("storage");
|
|
803
|
+
const identity = device.parse(deviceRecord.payload);
|
|
804
|
+
const result = exchange.safeParse(await this.request("auth_exchange", {
|
|
805
|
+
code: receivedCode,
|
|
806
|
+
code_verifier: verifier,
|
|
807
|
+
redirect_uri: redirectUri,
|
|
808
|
+
device_id: identity.id,
|
|
809
|
+
device_model: `${platform()}-${arch()}`,
|
|
810
|
+
os_version: `${platform()} ${release()}`
|
|
811
|
+
}, signal), { reportInput: true });
|
|
812
|
+
if (!result.success) this.rejectPayload("auth_exchange", result.error);
|
|
813
|
+
const completionUrl = new URL(browserUrl(result.data.authorized_url, this.origin, "/dsh/authorized", this.rewriteBrowserOrigin));
|
|
814
|
+
completionUrl.searchParams.set("login_source", attempt.loginSource);
|
|
815
|
+
attempt.completionUrl = completionUrl.href;
|
|
816
|
+
if (Date.now() >= expiresAt) deadline.abort();
|
|
817
|
+
signal.throwIfAborted();
|
|
818
|
+
if (result.data.user != null) try {
|
|
819
|
+
attempt.initialProfile = {
|
|
820
|
+
token: result.data.token,
|
|
821
|
+
value: {
|
|
822
|
+
status: "ready",
|
|
823
|
+
value: profile(result.data.user)
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
} catch {}
|
|
827
|
+
this.update(attempt, { phase: "committing" });
|
|
828
|
+
clearTimeout(timer);
|
|
829
|
+
try {
|
|
830
|
+
await session.commit({
|
|
831
|
+
kind: "grant",
|
|
832
|
+
payload: {
|
|
833
|
+
version: 1,
|
|
834
|
+
token: result.data.token,
|
|
835
|
+
issuer: this.origin
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
} catch {
|
|
839
|
+
throw new PlatformAuthError("storage");
|
|
840
|
+
}
|
|
841
|
+
} catch (error) {
|
|
842
|
+
if (deadline.signal.aborted) throw new PlatformAuthError("expired");
|
|
843
|
+
throw error;
|
|
844
|
+
} finally {
|
|
845
|
+
if (signal.aborted && authorizeId !== void 0) this.cancelRequest(authorizeId, verifier);
|
|
846
|
+
clearTimeout(timer);
|
|
847
|
+
signal.removeEventListener("abort", abort);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
rejectPayload(stage, error) {
|
|
851
|
+
console.info("[deepseek-account] payload rejected", {
|
|
852
|
+
stage,
|
|
853
|
+
issues: error.issues.map((issue) => ({
|
|
854
|
+
path: issue.path,
|
|
855
|
+
code: issue.code,
|
|
856
|
+
receivedType: issue.input === null ? "null" : Array.isArray(issue.input) ? "array" : typeof issue.input
|
|
857
|
+
}))
|
|
858
|
+
});
|
|
859
|
+
throw new PlatformAuthError("protocol");
|
|
860
|
+
}
|
|
861
|
+
cancelRequest(authorizeId, verifier) {
|
|
862
|
+
if (this.closed) return;
|
|
863
|
+
const cancellation = this.request("auth_cancel", {
|
|
864
|
+
authorize_id: authorizeId,
|
|
865
|
+
code_verifier: verifier
|
|
866
|
+
}, this.logoutLifetime.signal).then(() => void 0, () => {}).finally(() => {
|
|
867
|
+
this.revocations.delete(cancellation);
|
|
868
|
+
});
|
|
869
|
+
this.revocations.add(cancellation);
|
|
870
|
+
}
|
|
871
|
+
request(method, body, signal) {
|
|
872
|
+
return requestPlatform(this.origin, method, body, AbortSignal.any([signal, AbortSignal.timeout(this.requestTimeout)]), {
|
|
873
|
+
...this.requestHeaders,
|
|
874
|
+
...this.clientHeaders
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
finishFailedCallback(attempt) {
|
|
878
|
+
if (attempt.loginSource === "web") {
|
|
879
|
+
const nonce = randomBytes(16).toString("base64url");
|
|
880
|
+
const message = attempt.locale === "zh_CN" ? "登录失败,请关闭此标签页并在原页面重试。" : "Sign-in failed. Close this tab and try again in the original tab.";
|
|
881
|
+
attempt.callback?.writeHead(200, {
|
|
882
|
+
"content-type": "text/html; charset=utf-8",
|
|
883
|
+
"cache-control": "no-store",
|
|
884
|
+
"content-security-policy": `default-src 'none'; script-src 'nonce-${nonce}'; frame-ancestors 'none'`,
|
|
885
|
+
"referrer-policy": "no-referrer"
|
|
886
|
+
}).end(`<!doctype html><html lang="${attempt.locale === "zh_CN" ? "zh-CN" : "en"}"><meta charset="utf-8"><title>${message}</title><body><p>${message}</p><script nonce="${nonce}">window.close()<\/script></body></html>`);
|
|
887
|
+
} else attempt.callback?.writeHead(204, { "cache-control": "no-store" }).end();
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
//#endregion
|
|
891
|
+
export { Config, PlatformAccount, PlatformAccount as default };
|