{"record":{"id":"ae15f4925cc9e954","repo":"hmjz100/LinkSwift","slug":"error-ae15f4","errorCode":null,"errorMessage":"[迅雷云盘] 获取令牌失败","messagePattern":"\\[迅雷云盘\\] 获取令牌失败","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"（改）网盘直链下载助手.user.js","lineNumber":7731,"sourceCode":"\t\t\t\t\t\tcap = { token: res.captcha_token, expires_at: new Date(timestamp + res.expires_in * 1000).toString() };\r\n\t\t\t\t\t\tbase.setStorage(capKey, cap);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tthrow new Error(`错误：<br/>令牌刷新失败，${res?.error_description || res?.error || \"未知错误\"}`);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (!creds || !cap || !clientId || !deviceId) {\r\n\t\t\t\t\tthrow new Error(`错误：<br/>请先登录网盘后再获取文件呢~`);\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn { credentials: creds, captcha: cap, device_id: deviceId };\r\n\t\t\t});\r\n\t\t},\r\n\t\tasync getFileUrl(item, index, isRetry = false) {\r\n\t\t\tif (item.downloadUrl) return { index, downloadUrl: item.downloadUrl };\r\n\r\n\t\t\t// 获取当前内存中的 token\r\n\t\t\tconst token = await this.getToken(false, false).catch(e => {\r\n\t\t\t\tif (e instanceof Error) throw e;\r\n\t\t\t\tthrow new Error(e?.message || e || \"[迅雷云盘] 获取令牌失败\");\r\n\t\t\t});\r\n\r\n\t\t\tconst res = await base.get(config.$xunlei.api.getLink + item.id, {\r\n\t\t\t\t\"Authorization\": `${token.credentials?.token_type} ${token.credentials?.access_token}`,\r\n\t\t\t\t\"Content-Type\": \"application/json\",\r\n\t\t\t\t\"X-Captcha-Token\": token.captcha.token,\r\n\t\t\t\t\"X-Device-Id\": token.device_id\r\n\t\t\t});\r\n\r\n\t\t\tif (res.web_content_link) {\r\n\t\t\t\treturn { index, downloadUrl: res.web_content_link };\r\n\t\t\t} else {\r\n\t\t\t\t// 令牌过期\r\n\t\t\t\tif (res.error_code == 9) {\r\n\t\t\t\t\t// 递归重试一次，isRetry=true 防止死循环\r\n\t\t\t\t\tif (!isRetry) {\r\n\t\t\t\t\t\tawait this.getToken(true, false);\r\n\t\t\t\t\t\treturn await this.getFileUrl(item, index, true);\r","sourceCodeStart":7713,"sourceCodeEnd":7749,"githubUrl":"https://github.com/hmjz100/LinkSwift/blob/417ea5e28a3e1a90339710e17356e9fd22b8a34b/（改）网盘直链下载助手.user.js#L7713-L7749","documentation":"Wrapper error in 迅雷云盘 (Xunlei Cloud) getFileUrl: before fetching a file's download URL it calls this.getToken(false, false) to obtain the in-memory token. If that call rejects with a non-Error value, it is re-thrown as '[迅雷云盘] 获取令牌失败'. The real reason (expired token, failed refresh) is in the original rejection value.","triggerScenarios":"getFileUrl(item, index) called for a file without a cached downloadUrl; getToken(false, false) rejects with a non-Error — usually because the cached access token expired and the silent refresh path returned a non-Error rejection.","commonSituations":"Long-lived browser tab where the Xunlei access token expired mid-session; Xunlei API returning a JSON error object (not an Error) on refresh; rate limiting on the token endpoint; user logged out on another device invalidating the token.","solutions":["Reload the cloud drive page to rebuild the in-memory token state, then retry","Log in to Xunlei Cloud again in the userscript to refresh credentials","Check the console for the original rejection payload to identify the true refresh failure","Fix the token source so it throws Error instances with meaningful messages instead of raw objects"],"exampleFix":"// before\ntokenPromise.catch(e => { throw e; });\n// after\ntokenPromise.catch(e => { throw e instanceof Error ? e : new Error(\"获取令牌失败: \" + JSON.stringify(e)); });","handlingStrategy":"retry","validationCode":"const token = getCachedXunleiToken();\nif (!token || token.expiresAt <= Date.now()) { await refreshToken(); }","typeGuard":"function hasValidToken(t) { return Boolean(t?.credentials?.access_token && t?.captcha?.token); }","tryCatchPattern":"try { return await getFileUrl(item, i); } catch (e) { if (e.message.includes('获取令牌失败')) { await refreshToken(); return getFileUrl(item, i); } throw e; }","preventionTips":["Proactively refresh tokens before expiry","Reload long-lived tabs periodically","Log raw rejection payloads","Rate-limit token refresh calls"],"tags":["authentication","token-expired","xunlei","error-wrapping"],"backgroundTag":"access-token-expired","analyzedSha":"417ea5e28a3e1a90339710e17356e9fd22b8a34b","analyzedAt":"2026-09-02T18:13:32.837Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T21:17:11.164Z"}