Add comprehensive API error handling for sensor attachment
Improved error handling in the attachDeviceToBeneficiary method with: - Structured ApiResponse return type with detailed error codes - User-friendly error messages for different failure scenarios: - DEPLOYMENT_NOT_FOUND: Beneficiary has no deployment configured - UNAUTHORIZED: Missing or expired authentication credentials - NOT_FOUND: Sensor or deployment not found (404) - SERVER_ERROR: Legacy API server error (500) - SERVICE_UNAVAILABLE: Service temporarily unavailable (503+) - LEGACY_API_ERROR: Error from Legacy API response body - NETWORK_ERROR: Network connectivity issues - EXCEPTION: Unexpected errors - Enhanced error messages in setup-wifi.tsx to display API error details - Fixed navigator.onLine check to work in test environment - Added comprehensive test suite with 11 test cases covering all error scenarios All tests passing (11/11). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0cc82b24b0
commit
30df915433
386
__tests__/services/api-sensor-attachment-errors.test.ts
Normal file
386
__tests__/services/api-sensor-attachment-errors.test.ts
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
import { api } from '@/services/api';
|
||||||
|
import * as SecureStore from 'expo-secure-store';
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
jest.mock('expo-secure-store');
|
||||||
|
jest.mock('expo-file-system');
|
||||||
|
|
||||||
|
// Mock fetch globally
|
||||||
|
global.fetch = jest.fn();
|
||||||
|
|
||||||
|
describe('API - Sensor Attachment Error Handling', () => {
|
||||||
|
const mockBeneficiaryId = '123';
|
||||||
|
const mockWellId = 497;
|
||||||
|
const mockDeviceMac = '142B2F81A14C';
|
||||||
|
const mockDeploymentId = 22;
|
||||||
|
// Use 'robster' to match DEMO_LEGACY_USER in api.ts
|
||||||
|
const mockUserName = 'robster';
|
||||||
|
|
||||||
|
// Create a mock JWT with expiration far in the future
|
||||||
|
const createMockJWT = () => {
|
||||||
|
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
|
||||||
|
const payload = btoa(JSON.stringify({
|
||||||
|
sub: mockUserName,
|
||||||
|
exp: Math.floor(Date.now() / 1000) + 3600, // expires in 1 hour
|
||||||
|
}));
|
||||||
|
const signature = 'mock-signature';
|
||||||
|
return `${header}.${payload}.${signature}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockToken = createMockJWT();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Reset SecureStore mock to default implementation
|
||||||
|
// legacyAccessToken needs to be a valid JWT format with valid expiration
|
||||||
|
(SecureStore.getItemAsync as jest.Mock).mockImplementation((key: string) => {
|
||||||
|
if (key === 'accessToken') return Promise.resolve('mock-jwt-token');
|
||||||
|
if (key === 'userId') return Promise.resolve('user-123');
|
||||||
|
if (key === 'userEmail') return Promise.resolve('test@example.com');
|
||||||
|
if (key === 'legacyAccessToken') return Promise.resolve(mockToken);
|
||||||
|
if (key === 'legacyUserName') return Promise.resolve(mockUserName);
|
||||||
|
if (key === 'legacyUserId') return Promise.resolve('legacy-user-123');
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('attachDeviceToBeneficiary', () => {
|
||||||
|
it('should handle deployment not found error', async () => {
|
||||||
|
// Mock getDeploymentForBeneficiary to return beneficiary without deployment
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
lastName: 'Garcia',
|
||||||
|
// No deploymentId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.error?.code).toBe('DEPLOYMENT_NOT_FOUND');
|
||||||
|
expect(result.error?.message).toContain('Could not find beneficiary deployment');
|
||||||
|
expect(result.error?.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing authentication credentials', async () => {
|
||||||
|
// Mock successful beneficiary lookup
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock missing legacy credentials
|
||||||
|
(SecureStore.getItemAsync as jest.Mock).mockImplementation((key: string) => {
|
||||||
|
if (key === 'accessToken') return Promise.resolve('mock-jwt-token');
|
||||||
|
// Return null for legacy credentials
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.error?.code).toBe('UNAUTHORIZED');
|
||||||
|
expect(result.error?.message).toContain('Not authenticated');
|
||||||
|
expect(result.error?.status).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle 401 Unauthorized from Legacy API', async () => {
|
||||||
|
// Mock successful beneficiary lookup
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Mock 401 response from Legacy API
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: false,
|
||||||
|
status: 401,
|
||||||
|
json: async () => ({ message: 'Invalid credentials' })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.error?.code).toBe('UNAUTHORIZED');
|
||||||
|
expect(result.error?.message).toMatch(/Authentication expired|Not authenticated/);
|
||||||
|
expect(result.error?.status).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle 404 Not Found from Legacy API', async () => {
|
||||||
|
// Mock successful beneficiary lookup
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Mock 404 response from Legacy API
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: false,
|
||||||
|
status: 404,
|
||||||
|
json: async () => ({ message: 'Device not found' })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.error?.code).toBe('NOT_FOUND');
|
||||||
|
expect(result.error?.message).toContain('Sensor or deployment not found');
|
||||||
|
expect(result.error?.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle 500 Server Error from Legacy API', async () => {
|
||||||
|
// Mock successful beneficiary lookup
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Mock 500 response from Legacy API
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: false,
|
||||||
|
status: 500,
|
||||||
|
json: async () => ({ message: 'Internal server error' })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.error?.code).toBe('SERVER_ERROR');
|
||||||
|
expect(result.error?.message).toContain('Server error');
|
||||||
|
expect(result.error?.status).toBe(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle 503 Service Unavailable from Legacy API', async () => {
|
||||||
|
// Mock successful beneficiary lookup
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Mock 503 response from Legacy API
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: false,
|
||||||
|
status: 503,
|
||||||
|
json: async () => ({ message: 'Service unavailable' })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.error?.code).toBe('SERVICE_UNAVAILABLE');
|
||||||
|
expect(result.error?.message).toContain('Service unavailable');
|
||||||
|
expect(result.error?.status).toBe(503);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle Legacy API error status in response body', async () => {
|
||||||
|
// Mock successful beneficiary lookup
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Mock successful HTTP but error status in body
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({
|
||||||
|
status: '400 Bad Request',
|
||||||
|
message: 'Invalid device MAC address format'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.error?.code).toBe('LEGACY_API_ERROR');
|
||||||
|
expect(result.error?.message).toBe('Invalid device MAC address format');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle network errors', async () => {
|
||||||
|
// Mock successful beneficiary lookup first
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Then mock network error on Legacy API call
|
||||||
|
.mockRejectedValueOnce(
|
||||||
|
new Error('fetch failed')
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.error?.code).toBe('NETWORK_ERROR');
|
||||||
|
expect(result.error?.message).toContain('No internet connection');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle unexpected exceptions', async () => {
|
||||||
|
// Mock successful beneficiary lookup first
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Then mock unexpected error
|
||||||
|
.mockRejectedValueOnce(
|
||||||
|
new Error('Unexpected error occurred')
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.error?.code).toBe('EXCEPTION');
|
||||||
|
expect(result.error?.message).toContain('unexpected error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should successfully attach device when all is well', async () => {
|
||||||
|
// Mock successful beneficiary lookup
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Mock successful device_form response
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({
|
||||||
|
status: '200 OK',
|
||||||
|
message: 'Device attached successfully'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
expect(result.data).toEqual({ success: true });
|
||||||
|
expect(result.error).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send correct request parameters to Legacy API', async () => {
|
||||||
|
// Mock successful beneficiary lookup
|
||||||
|
(global.fetch as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
id: 123,
|
||||||
|
firstName: 'Maria',
|
||||||
|
deploymentId: mockDeploymentId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Mock successful device_form response
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({
|
||||||
|
status: '200 OK'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
await api.attachDeviceToBeneficiary(
|
||||||
|
mockBeneficiaryId,
|
||||||
|
mockWellId,
|
||||||
|
mockDeviceMac
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the second fetch call (device_form)
|
||||||
|
const secondCall = (global.fetch as jest.Mock).mock.calls[1];
|
||||||
|
expect(secondCall[0]).toBe('https://eluxnetworks.net/function/well-api/api');
|
||||||
|
expect(secondCall[1].method).toBe('POST');
|
||||||
|
expect(secondCall[1].headers['Content-Type']).toBe('application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
// Verify request body contains correct parameters
|
||||||
|
const bodyString = secondCall[1].body;
|
||||||
|
expect(bodyString).toContain('function=device_form');
|
||||||
|
expect(bodyString).toContain(`well_id=${mockWellId}`);
|
||||||
|
expect(bodyString).toContain(`device_mac=${mockDeviceMac.toUpperCase()}`);
|
||||||
|
expect(bodyString).toContain(`deployment_id=${mockDeploymentId}`);
|
||||||
|
expect(bodyString).toContain(`user_name=${mockUserName}`);
|
||||||
|
// Token will be URL encoded in the body, so just check it contains the token
|
||||||
|
expect(bodyString).toContain('token=');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -271,8 +271,9 @@ export default function SetupWiFiScreen() {
|
|||||||
mac
|
mac
|
||||||
);
|
);
|
||||||
if (!attachResponse.ok) {
|
if (!attachResponse.ok) {
|
||||||
const errorDetail = attachResponse.error || 'Unknown API error';
|
// Use the error message from the API response
|
||||||
throw new Error(`Failed to register sensor: ${errorDetail}`);
|
const errorMessage = attachResponse.error?.message || 'Failed to register sensor';
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateSensorStep(deviceId, 'attach', 'completed');
|
updateSensorStep(deviceId, 'attach', 'completed');
|
||||||
|
|||||||
@ -1935,19 +1935,33 @@ class ApiService {
|
|||||||
beneficiaryId: string,
|
beneficiaryId: string,
|
||||||
wellId: number,
|
wellId: number,
|
||||||
deviceMac: string
|
deviceMac: string
|
||||||
) {
|
): Promise<ApiResponse<{ success: true }>> {
|
||||||
try {
|
try {
|
||||||
// Get deployment ID for beneficiary
|
// Get deployment ID for beneficiary
|
||||||
const deploymentResponse = await this.getDeploymentForBeneficiary(beneficiaryId);
|
const deploymentResponse = await this.getDeploymentForBeneficiary(beneficiaryId);
|
||||||
if (!deploymentResponse.ok) {
|
if (!deploymentResponse.ok) {
|
||||||
throw new Error(deploymentResponse.error.message);
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: {
|
||||||
|
message: 'Could not find beneficiary deployment',
|
||||||
|
code: 'DEPLOYMENT_NOT_FOUND',
|
||||||
|
status: 404,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const deploymentId = deploymentResponse.data;
|
const deploymentId = deploymentResponse.data;
|
||||||
|
|
||||||
const creds = await this.getLegacyCredentials();
|
const creds = await this.getLegacyCredentials();
|
||||||
if (!creds) {
|
if (!creds) {
|
||||||
throw new Error('Not authenticated with Legacy API');
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: {
|
||||||
|
message: 'Not authenticated. Please log in again.',
|
||||||
|
code: 'UNAUTHORIZED',
|
||||||
|
status: 401,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use device_form to attach device to deployment
|
// Use device_form to attach device to deployment
|
||||||
@ -1968,18 +1982,66 @@ class ApiService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!attachResponse.ok) {
|
if (!attachResponse.ok) {
|
||||||
throw new Error(`Failed to attach device: HTTP ${attachResponse.status}`);
|
// Provide more specific error messages based on HTTP status
|
||||||
|
let errorMessage = 'Could not register sensor. Please try again.';
|
||||||
|
let errorCode = 'API_ERROR';
|
||||||
|
|
||||||
|
if (attachResponse.status === 401 || attachResponse.status === 403) {
|
||||||
|
errorMessage = 'Authentication expired. Please log in again.';
|
||||||
|
errorCode = 'UNAUTHORIZED';
|
||||||
|
} else if (attachResponse.status === 404) {
|
||||||
|
errorMessage = 'Sensor or deployment not found.';
|
||||||
|
errorCode = 'NOT_FOUND';
|
||||||
|
} else if (attachResponse.status === 500) {
|
||||||
|
errorMessage = 'Server error. Please try again later.';
|
||||||
|
errorCode = 'SERVER_ERROR';
|
||||||
|
} else if (attachResponse.status >= 500) {
|
||||||
|
errorMessage = 'Service unavailable. Please check your internet connection.';
|
||||||
|
errorCode = 'SERVICE_UNAVAILABLE';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: {
|
||||||
|
message: errorMessage,
|
||||||
|
code: errorCode,
|
||||||
|
status: attachResponse.status,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await attachResponse.json();
|
const data = await attachResponse.json();
|
||||||
|
|
||||||
if (data.status !== '200 OK') {
|
if (data.status !== '200 OK') {
|
||||||
throw new Error(data.message || `Legacy API error: ${data.status}`);
|
// Parse Legacy API error response
|
||||||
|
const errorMessage = data.message || 'Failed to register sensor';
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: {
|
||||||
|
message: errorMessage,
|
||||||
|
code: 'LEGACY_API_ERROR',
|
||||||
|
status: attachResponse.status,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ok: true };
|
return { ok: true, data: { success: true } };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return { ok: false, error: error.message };
|
// Handle network errors and unexpected exceptions
|
||||||
|
const isNetworkError = error.message?.includes('network') ||
|
||||||
|
error.message?.includes('fetch') ||
|
||||||
|
(typeof navigator !== 'undefined' && !navigator.onLine);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: {
|
||||||
|
message: isNetworkError
|
||||||
|
? 'No internet connection. Please check your network.'
|
||||||
|
: 'An unexpected error occurred. Please try again.',
|
||||||
|
code: isNetworkError ? 'NETWORK_ERROR' : 'EXCEPTION',
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user