{"record":{"id":"c7be065e129faa18","repo":"AlistGo/alist","slug":"refresh-token-is-empty-c7be06","errorCode":null,"errorMessage":"refresh token is empty","messagePattern":"refresh token is empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/thunder_browser/driver.go","lineNumber":644,"sourceCode":"}\n\n// RefreshToken 刷新Token\nfunc (xc *XunLeiBrowserCommon) RefreshToken(refreshToken string) (*TokenResp, error) {\n\tvar resp TokenResp\n\t_, err := xc.Common.Request(XLUSER_API_URL+\"/auth/token\", http.MethodPost, func(req *resty.Request) {\n\t\treq.SetBody(&base.Json{\n\t\t\t\"grant_type\":    \"refresh_token\",\n\t\t\t\"refresh_token\": refreshToken,\n\t\t\t\"client_id\":     xc.ClientID,\n\t\t\t\"client_secret\": xc.ClientSecret,\n\t\t})\n\t}, &resp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif resp.RefreshToken == \"\" {\n\t\treturn nil, errors.New(\"refresh token is empty\")\n\t}\n\treturn &resp, nil\n}\n\n// GetSafeAccessToken 获取 超级保险柜 AccessToken\nfunc (xc *XunLeiBrowserCommon) GetSafeAccessToken(safePassword string) (string, error) {\n\tvar resp TokenResp\n\t_, err := xc.Request(XLUSER_API_URL+\"/password/check\", http.MethodPost, func(req *resty.Request) {\n\t\treq.SetBody(&base.Json{\n\t\t\t\"scene\":    \"box\",\n\t\t\t\"password\": EncryptPassword(safePassword),\n\t\t})\n\t}, &resp)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif resp.Token == \"\" {","sourceCodeStart":626,"sourceCodeEnd":662,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/thunder_browser/driver.go#L626-L662","documentation":"Returned by Xunlei (Thunder) browser driver's token refresh when the XLUSER /auth/token call technically succeeded but the response carries no refresh_token. The driver deliberately fails instead of proceeding with a token that cannot be refreshed later, which would silently log the user out on next expiry.","triggerScenarios":"POST to XLUSER_API_URL /auth/token with grant_type=refresh_token where resp.RefreshToken is empty — typically the server rejected the old refresh token but responded 200 with an error payload, or the account was logged in elsewhere invalidating the session.","commonSituations":"Logging into the same Xunlei account from multiple tools/devices causing token invalidation; Long-idle storage whose refresh token already expired (Xunlei tokens are short-lived); Xunlei server-side policy changes or risk-control blocks on the client_id used by third-party drivers","solutions":["Remove and re-add the Thunder browser storage in alist so a fresh sign-in flow issues new tokens","Ensure only one active client uses this Xunlei account; sign out other third-party clients","If it recurs rapidly, re-login in the official Xunlei web client first to clear any risk-control state, then re-add the storage"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"tok, err := xc.RefreshToken(token)\nif err != nil {\n    if strings.Contains(err.Error(), \"refresh token is empty\") {\n        // hard stop: interactive re-login required; do NOT loop retries\n        return nil, fmt.Errorf(\"xunlei session expired: re-add the storage to trigger a fresh login\")\n    }\n    return nil, err\n}","preventionTips":["Treat an empty refresh_token as terminal — re-login instead of retrying","Keep a single active client per Xunlei account","Refresh tokens on a short timer before expiry instead of on failure"],"tags":["thunder","xunlei","oauth","refresh-token","auth"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}