Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions components/SuperDebugPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ export function SuperDebugPanel({
idRef.current = 0;

const baseUrl = buildUrl();
const portNum = port.trim() ? parseInt(port, 10) : undefined;
const controller = new AbortController();
abortRef.current = controller;
setRunning(true);
Expand Down Expand Up @@ -312,7 +313,7 @@ export function SuperDebugPanel({
const loginBody = await loginResp.text();
const bodyTrimmed = loginBody.trim();

if (bodyTrimmed === 'Ok.' || bodyTrimmed === 'Ok') {
if (bodyTrimmed === 'Ok.' || bodyTrimmed === 'Ok' || loginResp.status === 204) {
addEntry('LOGIN', `Login successful — "${bodyTrimmed}" (HTTP ${loginResp.status}, ${loginLatency}ms)`, 'success');
} else if (bodyTrimmed === 'Fails.' || bodyTrimmed === 'Fails') {
addEntry('LOGIN', `Login REJECTED — "${bodyTrimmed}" (HTTP ${loginResp.status}, ${loginLatency}ms)`, 'error');
Expand Down Expand Up @@ -349,9 +350,11 @@ export function SuperDebugPanel({
loginResp.headers.get('SET-COOKIE');

if (setCookieHeader) {
const sidMatch = setCookieHeader.match(/SID=([^;]+)/i);
// regex handles SID with and without the port info
const re = new RegExp(`SID?(?:_${portNum})=([^;]+)`, 'i');
const sidMatch = setCookieHeader.match(re);
if (sidMatch) {
sidCookie = `SID=${sidMatch[1]}`;
sidCookie = `QBT_SID_${portNum}=${sidMatch[1]}`;
const truncated = sidMatch[1].length > 12
? sidMatch[1].substring(0, 12) + '...'
: sidMatch[1];
Expand Down
23 changes: 11 additions & 12 deletions services/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,25 @@ export const authApi = {
// Clear any existing cookies before login
apiClient.clearCookies();
clogInfo('AUTH', `Login attempt for user "${username}"`);

const response = await apiClient.postUrlEncoded(`/api/${API_VERSION}/auth/login`, {
const [response, responseStatus] = await apiClient.postUrlEncoded(`/api/${API_VERSION}/auth/login`, {
username,
password,
}, signal);

// Check if cookies were set after login
const cookies = apiClient.getCookies();

const responsePreview = typeof response === 'string' ? response.substring(0, 50) : 'non-string response';
console.log('[Auth] Login response:', responsePreview);
console.log('[Auth] Login response status:', responseStatus);
console.log('[Auth] Cookies received:', cookies ? 'Yes (' + cookies.length + ' chars)' : 'No');

clogDebug('AUTH', `Response: "${responsePreview}" | Cookies: ${cookies ? 'Yes (' + cookies.length + ' chars)' : 'No'}`);
clogDebug('AUTH', `Response [${responseStatus}]: "${responsePreview}" | Cookies: ${cookies ? 'Yes (' + cookies.length + ' chars)' : 'No'}`);

// qBittorrent returns 'Ok.' on success, 'Fails.' on failure
// on newer API versions the response data is empty and success is indicated with a 204 status code
// Handle both string and trimmed string responses
const responseStr = typeof response === 'string' ? response.trim() : String(response).trim();

if (responseStr === 'Ok.' || responseStr === 'Ok') {
if (responseStr === 'Ok.' || responseStr === 'Ok' || responseStatus === 204) {
// Successful login - verify we have session cookies
if (!cookies || cookies.length === 0) {
console.warn('[Auth] Warning: Login succeeded but no cookies received. This may cause issues with qBittorrent 5.x');
Expand All @@ -43,7 +42,7 @@ export const authApi = {
clogInfo('AUTH', 'Login successful');
return { status: 'Ok' };
}

console.warn('[Auth] Login failed with response:', responseStr);
clogWarn('AUTH', `Login failed — server responded: "${responseStr}"`);
return { status: 'Fails' };
Expand All @@ -60,15 +59,15 @@ export const authApi = {
const statusHint = axiosErr?.response?.status ? ` (HTTP ${axiosErr.response.status})` : '';
console.warn('[Auth] Login error:', message, statusHint);
clogError('AUTH', `Login error: ${message}${statusHint}`);

if (message.includes('timeout') || message.includes('Connection') || message.includes('Network')) {
throw error;
}

if (axiosErr?.response?.status === 403) {
throw new Error('Authentication failed. Please check your username and password.');
}

return { status: 'Fails' };
}
},
Expand Down
2 changes: 1 addition & 1 deletion services/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ class ApiClient {

// Let the interceptor handle headers (it already sets Content-Type) and baseURL
const response = await this.client.post(url, body, { signal });
return response.data;
return [response.data, response.status];
}

private isRetriableError(error: unknown): boolean {
Expand Down
12 changes: 6 additions & 6 deletions services/api/torrents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ export const torrentsApi = {
if (hashes && hashes.length > 0) params.hashes = hashes.join('|');

const response = await apiClient.get(`/api/${API_VERSION}/torrents/info`, params);

if (Array.isArray(response)) {
return response as TorrentInfo[];
}

return [];
},

Expand Down Expand Up @@ -108,7 +108,7 @@ export const torrentsApi = {
async pauseTorrents(hashes: string[]): Promise<void> {
const hashString = hashes.join('|');
try {
const response = await apiClient.postUrlEncoded(`/api/${API_VERSION}/torrents/stop`, {
const [response, responseStatus] = await apiClient.postUrlEncoded(`/api/${API_VERSION}/torrents/stop`, {
hashes: hashString,
});
// console.log('Pause response:', response);
Expand All @@ -131,7 +131,7 @@ export const torrentsApi = {
async resumeTorrents(hashes: string[]): Promise<void> {
const hashString = hashes.join('|');
try {
const response = await apiClient.postUrlEncoded(`/api/${API_VERSION}/torrents/start`, {
const [response, responseStatus] = await apiClient.postUrlEncoded(`/api/${API_VERSION}/torrents/start`, {
hashes: hashString,
});
} catch (error: unknown) {
Expand Down Expand Up @@ -199,7 +199,7 @@ export const torrentsApi = {
}
): Promise<void> {
const formData = new FormData();

if (Array.isArray(urls)) {
urls.forEach((url) => {
formData.append('urls', url);
Expand Down Expand Up @@ -274,7 +274,7 @@ export const torrentsApi = {
}
): Promise<void> {
const formData = new FormData();

// Add the torrent file
// @ts-expect-error React Native FormData accepts { uri, type, name } objects for file uploads
formData.append('torrents', {
Expand Down