proofio-sdk 1.0.0

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.
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * Competitors Resource
4
+ *
5
+ * Handles competitor comparison functionality
6
+ *
7
+ * Note: Competitor comparison requires authentication and is typically
8
+ * accessed via the dashboard API. This resource provides a placeholder
9
+ * structure that matches the SDK API style.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.CompetitorsResource = void 0;
13
+ class CompetitorsResource {
14
+ constructor(client) {
15
+ this.client = client;
16
+ }
17
+ /**
18
+ * Compare with a competitor
19
+ *
20
+ * Generates an AI-powered comparison between your product and a competitor.
21
+ *
22
+ * Note: This endpoint requires dashboard API access (authentication).
23
+ * The public API doesn't support competitor comparisons directly.
24
+ * This method is provided for API consistency but may require
25
+ * additional authentication setup.
26
+ *
27
+ * @param competitorId - Competitor ID
28
+ * @param options - Optional parameters (e.g., force refresh)
29
+ * @returns Promise with competitor comparison
30
+ */
31
+ async compare(competitorId, options) {
32
+ const queryParams = {};
33
+ if (options?.force) {
34
+ queryParams.force = 'true';
35
+ }
36
+ // Note: This endpoint typically requires authentication
37
+ // The public API doesn't expose competitor comparisons
38
+ // This is a placeholder that matches the expected API structure
39
+ const response = await this.client.get(`/api/dashboard/competitors/${competitorId}/comparison`, {
40
+ queryParams,
41
+ });
42
+ return response.data;
43
+ }
44
+ }
45
+ exports.CompetitorsResource = CompetitorsResource;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ /**
3
+ * Insights Resource
4
+ *
5
+ * Handles insights, summaries, and trend analysis
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.InsightsResource = void 0;
9
+ class InsightsResource {
10
+ constructor(client) {
11
+ this.client = client;
12
+ }
13
+ /**
14
+ * Get insight summary
15
+ *
16
+ * Returns aggregated statistics including:
17
+ * - Total reviews
18
+ * - Average rating
19
+ * - Rating distribution
20
+ * - Sentiment distribution
21
+ * - Source breakdown
22
+ * - AI summary (if available for paid plans)
23
+ *
24
+ * @returns Promise with insight summary
25
+ */
26
+ async summary() {
27
+ const response = await this.client.get('/api/v1/public/aggregations');
28
+ return response.data;
29
+ }
30
+ /**
31
+ * Get trend analysis
32
+ *
33
+ * Returns detailed trend data including:
34
+ * - Trend over time (last 30 days)
35
+ * - Review volume (7, 30, 90 days)
36
+ * - Top topics
37
+ * - Key takeaways
38
+ * - Recent changes
39
+ *
40
+ * Note: The public API provides limited trend data. For full trend analysis
41
+ * including trendOverTime, topTopics, and recentChanges, you need access
42
+ * to the dashboard API (requires authentication).
43
+ *
44
+ * This method returns available data from the public aggregations endpoint
45
+ * and sets default/empty values for fields not available in the public API.
46
+ *
47
+ * @returns Promise with trend data
48
+ */
49
+ async trends() {
50
+ // Get data from public aggregations endpoint
51
+ const response = await this.client.get('/api/v1/public/aggregations');
52
+ const data = response.data;
53
+ // Calculate positive percentage
54
+ const positivePercentage = data.totalReviews > 0
55
+ ? (data.sentimentDistribution.positive / data.totalReviews) * 100
56
+ : 0;
57
+ // Transform to InsightTrends format
58
+ // Note: The public API doesn't provide all trend fields,
59
+ // so we provide what's available and set defaults for missing fields
60
+ return {
61
+ totalReviews: data.totalReviews,
62
+ averageRating: data.averageRating,
63
+ ratingDistribution: data.ratingDistribution,
64
+ sentimentDistribution: data.sentimentDistribution,
65
+ thisWeekCount: 0, // Not available in public API
66
+ thisWeekChange: 0, // Not available in public API
67
+ positivePercentage: Math.round(positivePercentage * 10) / 10,
68
+ reviewVolume: {
69
+ last7Days: 0, // Not available in public API
70
+ last30Days: 0, // Not available in public API
71
+ last90Days: 0, // Not available in public API
72
+ total: data.totalReviews,
73
+ },
74
+ sourceBreakdown: data.sources.reduce((acc, source) => {
75
+ acc[source.id] = {
76
+ count: source.total,
77
+ avgRating: source.averageRating,
78
+ sentiment: {
79
+ positive: 0, // Not available in public API
80
+ neutral: 0,
81
+ negative: 0,
82
+ },
83
+ };
84
+ return acc;
85
+ }, {}),
86
+ sourcesMap: data.sources.reduce((acc, source) => {
87
+ acc[source.id] = {
88
+ name: source.name || source.type || 'Unknown',
89
+ type: source.type || 'UNKNOWN',
90
+ };
91
+ return acc;
92
+ }, {}),
93
+ trendOverTime: [], // Not available in public API - requires dashboard API
94
+ topTopics: [], // Not available in public API - requires dashboard API
95
+ keyTakeaways: [], // Not available in public API - requires dashboard API
96
+ recentChanges: [], // Not available in public API - requires dashboard API
97
+ lastSync: null, // Not available in public API
98
+ };
99
+ }
100
+ }
101
+ exports.InsightsResource = InsightsResource;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ /**
3
+ * Reviews Resource
4
+ *
5
+ * Handles all review-related API calls
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ReviewsResource = void 0;
9
+ class ReviewsResource {
10
+ constructor(client) {
11
+ this.client = client;
12
+ }
13
+ /**
14
+ * List reviews
15
+ *
16
+ * @param options - Filter and pagination options
17
+ * @returns Promise with array of reviews
18
+ */
19
+ async list(options) {
20
+ const queryParams = {};
21
+ if (options?.limit !== undefined) {
22
+ queryParams.limit = Math.min(options.limit, 100); // Max 100 per API
23
+ }
24
+ else {
25
+ queryParams.limit = 10; // Default
26
+ }
27
+ if (options?.offset !== undefined) {
28
+ queryParams.offset = options.offset;
29
+ }
30
+ if (options?.minRating !== undefined) {
31
+ queryParams.minRating = options.minRating;
32
+ }
33
+ if (options?.maxRating !== undefined) {
34
+ queryParams.maxRating = options.maxRating;
35
+ }
36
+ if (options?.sentiment) {
37
+ queryParams.sentiment = options.sentiment;
38
+ }
39
+ if (options?.language) {
40
+ queryParams.language = options.language;
41
+ }
42
+ if (options?.sourceId) {
43
+ queryParams.sourceId = options.sourceId;
44
+ }
45
+ if (options?.since) {
46
+ queryParams.since = options.since;
47
+ }
48
+ const response = await this.client.get('/api/v1/public/reviews', {
49
+ queryParams,
50
+ });
51
+ return response.data;
52
+ }
53
+ /**
54
+ * Get a single review by ID
55
+ *
56
+ * Note: The API doesn't have a direct GET /reviews/:id endpoint,
57
+ * so we fetch the list and filter. This is a convenience method.
58
+ *
59
+ * @param id - Review ID
60
+ * @returns Promise with review or null if not found
61
+ */
62
+ async get(id) {
63
+ // Since there's no direct GET endpoint, we need to fetch and filter
64
+ // In a real implementation, you might want to cache reviews or use a different approach
65
+ const reviews = await this.list({ limit: 100 });
66
+ return reviews.find((review) => review.id === id) || null;
67
+ }
68
+ }
69
+ exports.ReviewsResource = ReviewsResource;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * Widget Resource
4
+ *
5
+ * Handles widget-related API calls
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.WidgetResource = void 0;
9
+ class WidgetResource {
10
+ constructor(client) {
11
+ this.client = client;
12
+ }
13
+ /**
14
+ * Get widget data
15
+ *
16
+ * Returns widget statistics and settings including:
17
+ * - Total reviews
18
+ * - Average rating
19
+ * - Number of platforms
20
+ * - Widget configuration (language, theme, badges, branding)
21
+ *
22
+ * @returns Promise with widget data
23
+ */
24
+ async get() {
25
+ const response = await this.client.get('/api/v1/public/widget');
26
+ return response.data;
27
+ }
28
+ /**
29
+ * Get widget configuration
30
+ *
31
+ * Returns only the widget settings/configuration.
32
+ * This is a convenience method that extracts settings from get().
33
+ *
34
+ * @returns Promise with widget settings
35
+ */
36
+ async config() {
37
+ const data = await this.get();
38
+ return data.settings;
39
+ }
40
+ /**
41
+ * Update widget configuration
42
+ *
43
+ * Note: Widget configuration updates typically require dashboard API access.
44
+ * This method is provided for API consistency but may require
45
+ * additional authentication setup.
46
+ *
47
+ * @param options - Widget configuration options
48
+ * @returns Promise with updated widget settings
49
+ */
50
+ async updateConfig(options) {
51
+ // Note: This endpoint typically requires authentication
52
+ // The public API doesn't support widget config updates
53
+ // This is a placeholder that matches the expected API structure
54
+ const response = await this.client.post('/api/dashboard/widget-settings', options);
55
+ return response.data;
56
+ }
57
+ }
58
+ exports.WidgetResource = WidgetResource;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * TypeScript Types für Proofio SDK
4
+ *
5
+ * Alle API Response Types basierend auf der tatsächlichen API-Struktur
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * Proofio Error Handling
4
+ *
5
+ * Normalized error class for all SDK errors
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ProofioError = void 0;
9
+ class ProofioError extends Error {
10
+ constructor(data) {
11
+ super(data.message);
12
+ this.name = 'ProofioError';
13
+ this.status = data.status;
14
+ this.code = data.code;
15
+ this.requestId = data.requestId;
16
+ this.retryAfter = data.retryAfter;
17
+ this.rateLimitRemaining = data.rateLimitRemaining;
18
+ this.rateLimitReset = data.rateLimitReset;
19
+ // Maintains proper stack trace for where our error was thrown (only available on V8/Node.js)
20
+ if (typeof Error.captureStackTrace === 'function') {
21
+ Error.captureStackTrace(this, ProofioError);
22
+ }
23
+ }
24
+ /**
25
+ * Check if error is retryable
26
+ */
27
+ isRetryable() {
28
+ return (this.status >= 500 ||
29
+ this.status === 429 ||
30
+ this.status === 408 ||
31
+ this.status === 0 // Network error
32
+ );
33
+ }
34
+ /**
35
+ * Convert to JSON (sanitized, no sensitive data)
36
+ */
37
+ toJSON() {
38
+ return {
39
+ name: this.name,
40
+ message: this.message,
41
+ status: this.status,
42
+ code: this.code,
43
+ requestId: this.requestId,
44
+ retryAfter: this.retryAfter,
45
+ rateLimitRemaining: this.rateLimitRemaining,
46
+ rateLimitReset: this.rateLimitReset,
47
+ };
48
+ }
49
+ }
50
+ exports.ProofioError = ProofioError;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Core API Client für Proofio SDK
3
+ *
4
+ * Handles:
5
+ * - API Key Authentication
6
+ * - Base URL Management
7
+ * - Request/Response Handling
8
+ * - Error Normalization
9
+ * - Rate Limit Handling
10
+ * - Retry Logic
11
+ */
12
+ import type { RequestOptions, Response } from '../types';
13
+ export interface ProofioConfig {
14
+ apiKey: string;
15
+ baseURL?: string;
16
+ timeout?: number;
17
+ maxRetries?: number;
18
+ retryDelay?: number;
19
+ }
20
+ export declare class ApiClient {
21
+ private apiKey;
22
+ private baseURL;
23
+ private timeout;
24
+ private maxRetries;
25
+ private retryDelay;
26
+ constructor(config: ProofioConfig);
27
+ /**
28
+ * Sanitize API key for logging (never log full key)
29
+ */
30
+ private sanitizeApiKey;
31
+ /**
32
+ * Build headers for requests
33
+ */
34
+ private buildHeaders;
35
+ /**
36
+ * Build full URL
37
+ */
38
+ private buildURL;
39
+ /**
40
+ * Handle rate limit errors
41
+ */
42
+ private handleRateLimit;
43
+ /**
44
+ * Parse error response
45
+ */
46
+ private parseErrorResponse;
47
+ /**
48
+ * Sleep helper for retries
49
+ */
50
+ private sleep;
51
+ /**
52
+ * Check if error is retryable
53
+ */
54
+ private isRetryableError;
55
+ /**
56
+ * Execute request with retry logic
57
+ */
58
+ private executeWithRetry;
59
+ /**
60
+ * Make GET request
61
+ */
62
+ get<T>(path: string, options?: RequestOptions): Promise<Response<T>>;
63
+ /**
64
+ * Make POST request
65
+ */
66
+ post<T>(path: string, body?: any, options?: RequestOptions): Promise<Response<T>>;
67
+ }
68
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/client/api-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAQ;gBAEd,MAAM,EAAE,aAAa;IAyBjC;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAYpB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAchB;;OAEG;YACW,eAAe;IAwB7B;;OAEG;YACW,kBAAkB;IAyBhC;;OAEG;IACH,OAAO,CAAC,KAAK;IAIb;;OAEG;IACH,OAAO,CAAC,gBAAgB;IASxB;;OAEG;YACW,gBAAgB;IAgC9B;;OAEG;IACG,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IA6D1E;;OAEG;IACG,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;CA6DxF"}
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Core API Client für Proofio SDK
3
+ *
4
+ * Handles:
5
+ * - API Key Authentication
6
+ * - Base URL Management
7
+ * - Request/Response Handling
8
+ * - Error Normalization
9
+ * - Rate Limit Handling
10
+ * - Retry Logic
11
+ */
12
+ import { ProofioError } from '../utils/errors';
13
+ export class ApiClient {
14
+ constructor(config) {
15
+ if (!config.apiKey || typeof config.apiKey !== 'string' || config.apiKey.trim().length === 0) {
16
+ throw new ProofioError({
17
+ message: 'API key is required',
18
+ status: 0,
19
+ code: 'INVALID_API_KEY',
20
+ });
21
+ }
22
+ // Validate API key format (basic validation)
23
+ if (config.apiKey.length < 10) {
24
+ throw new ProofioError({
25
+ message: 'API key format is invalid',
26
+ status: 0,
27
+ code: 'INVALID_API_KEY',
28
+ });
29
+ }
30
+ this.apiKey = config.apiKey.trim();
31
+ this.baseURL = config.baseURL || 'https://proofio.app';
32
+ this.timeout = config.timeout || 30000; // 30 seconds
33
+ this.maxRetries = config.maxRetries ?? 3;
34
+ this.retryDelay = config.retryDelay ?? 1000; // 1 second
35
+ }
36
+ /**
37
+ * Sanitize API key for logging (never log full key)
38
+ */
39
+ sanitizeApiKey() {
40
+ if (this.apiKey.length <= 8) {
41
+ return '***';
42
+ }
43
+ return `${this.apiKey.substring(0, 4)}...${this.apiKey.substring(this.apiKey.length - 4)}`;
44
+ }
45
+ /**
46
+ * Build headers for requests
47
+ */
48
+ buildHeaders(customHeaders) {
49
+ const headers = new Headers({
50
+ 'Content-Type': 'application/json',
51
+ 'x-api-key': this.apiKey,
52
+ 'User-Agent': 'proofio-sdk/1.0.0',
53
+ ...customHeaders,
54
+ });
55
+ // Make headers immutable by creating new Headers object
56
+ return new Headers(headers);
57
+ }
58
+ /**
59
+ * Build full URL
60
+ */
61
+ buildURL(path, queryParams) {
62
+ const url = new URL(path, this.baseURL);
63
+ if (queryParams) {
64
+ Object.entries(queryParams).forEach(([key, value]) => {
65
+ if (value !== undefined && value !== null) {
66
+ url.searchParams.append(key, String(value));
67
+ }
68
+ });
69
+ }
70
+ return url.toString();
71
+ }
72
+ /**
73
+ * Handle rate limit errors
74
+ */
75
+ async handleRateLimit(response) {
76
+ const retryAfter = response.headers.get('Retry-After');
77
+ const rateLimitRemaining = response.headers.get('X-RateLimit-Remaining');
78
+ const rateLimitReset = response.headers.get('X-RateLimit-Reset');
79
+ const errorData = {
80
+ message: 'Rate limit exceeded',
81
+ status: 429,
82
+ code: 'RATE_LIMIT_EXCEEDED',
83
+ };
84
+ if (retryAfter) {
85
+ errorData.retryAfter = parseInt(retryAfter, 10);
86
+ }
87
+ if (rateLimitRemaining !== null) {
88
+ errorData.rateLimitRemaining = parseInt(rateLimitRemaining, 10);
89
+ }
90
+ if (rateLimitReset) {
91
+ errorData.rateLimitReset = parseInt(rateLimitReset, 10);
92
+ }
93
+ throw new ProofioError(errorData);
94
+ }
95
+ /**
96
+ * Parse error response
97
+ */
98
+ async parseErrorResponse(response) {
99
+ let errorData = {
100
+ message: `API request failed with status ${response.status}`,
101
+ status: response.status,
102
+ code: 'API_ERROR',
103
+ };
104
+ try {
105
+ const contentType = response.headers.get('content-type');
106
+ if (contentType && contentType.includes('application/json')) {
107
+ const data = await response.json();
108
+ errorData = {
109
+ ...errorData,
110
+ message: data.error || data.message || errorData.message,
111
+ code: data.code || errorData.code,
112
+ requestId: data.requestId,
113
+ };
114
+ }
115
+ }
116
+ catch {
117
+ // If JSON parsing fails, use default error
118
+ }
119
+ return new ProofioError(errorData);
120
+ }
121
+ /**
122
+ * Sleep helper for retries
123
+ */
124
+ sleep(ms) {
125
+ return new Promise((resolve) => setTimeout(resolve, ms));
126
+ }
127
+ /**
128
+ * Check if error is retryable
129
+ */
130
+ isRetryableError(status, error) {
131
+ // Retry on network errors, timeouts, and 5xx errors
132
+ if (!status)
133
+ return true; // Network error
134
+ if (status >= 500)
135
+ return true; // Server errors
136
+ if (status === 429)
137
+ return true; // Rate limit (with backoff)
138
+ if (status === 408)
139
+ return true; // Request timeout
140
+ return false;
141
+ }
142
+ /**
143
+ * Execute request with retry logic
144
+ */
145
+ async executeWithRetry(requestFn, retryCount = 0) {
146
+ try {
147
+ const response = await requestFn();
148
+ // If rate limited, throw immediately (don't retry immediately)
149
+ if (response.status === 429) {
150
+ await this.handleRateLimit(response);
151
+ }
152
+ // If error is retryable and we haven't exceeded max retries
153
+ if (!response.ok && this.isRetryableError(response.status) && retryCount < this.maxRetries) {
154
+ // Exponential backoff with jitter
155
+ const delay = this.retryDelay * Math.pow(2, retryCount) + Math.random() * 1000;
156
+ await this.sleep(delay);
157
+ return this.executeWithRetry(requestFn, retryCount + 1);
158
+ }
159
+ return response;
160
+ }
161
+ catch (error) {
162
+ // Network errors are retryable
163
+ if (retryCount < this.maxRetries && this.isRetryableError(0, error)) {
164
+ const delay = this.retryDelay * Math.pow(2, retryCount) + Math.random() * 1000;
165
+ await this.sleep(delay);
166
+ return this.executeWithRetry(requestFn, retryCount + 1);
167
+ }
168
+ throw error;
169
+ }
170
+ }
171
+ /**
172
+ * Make GET request
173
+ */
174
+ async get(path, options) {
175
+ const url = this.buildURL(path, options?.queryParams);
176
+ const headers = this.buildHeaders(options?.headers);
177
+ const controller = new AbortController();
178
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
179
+ try {
180
+ const response = await this.executeWithRetry(async () => {
181
+ return fetch(url, {
182
+ method: 'GET',
183
+ headers,
184
+ signal: controller.signal,
185
+ });
186
+ });
187
+ clearTimeout(timeoutId);
188
+ if (!response.ok) {
189
+ if (response.status === 429) {
190
+ await this.handleRateLimit(response);
191
+ }
192
+ throw await this.parseErrorResponse(response);
193
+ }
194
+ const data = await response.json();
195
+ // Convert Headers to plain object
196
+ const headers = {};
197
+ response.headers.forEach((value, key) => {
198
+ headers[key] = value;
199
+ });
200
+ return {
201
+ data,
202
+ status: response.status,
203
+ headers,
204
+ };
205
+ }
206
+ catch (error) {
207
+ clearTimeout(timeoutId);
208
+ if (error instanceof ProofioError) {
209
+ throw error;
210
+ }
211
+ if (error instanceof Error && error.name === 'AbortError') {
212
+ throw new ProofioError({
213
+ message: 'Request timeout',
214
+ status: 408,
215
+ code: 'TIMEOUT',
216
+ });
217
+ }
218
+ throw new ProofioError({
219
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
220
+ status: 0,
221
+ code: 'NETWORK_ERROR',
222
+ });
223
+ }
224
+ }
225
+ /**
226
+ * Make POST request
227
+ */
228
+ async post(path, body, options) {
229
+ const url = this.buildURL(path, options?.queryParams);
230
+ const headers = this.buildHeaders(options?.headers);
231
+ const controller = new AbortController();
232
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
233
+ try {
234
+ const response = await this.executeWithRetry(async () => {
235
+ return fetch(url, {
236
+ method: 'POST',
237
+ headers,
238
+ body: body ? JSON.stringify(body) : undefined,
239
+ signal: controller.signal,
240
+ });
241
+ });
242
+ clearTimeout(timeoutId);
243
+ if (!response.ok) {
244
+ if (response.status === 429) {
245
+ await this.handleRateLimit(response);
246
+ }
247
+ throw await this.parseErrorResponse(response);
248
+ }
249
+ const data = await response.json();
250
+ // Convert Headers to plain object
251
+ const headers = {};
252
+ response.headers.forEach((value, key) => {
253
+ headers[key] = value;
254
+ });
255
+ return {
256
+ data,
257
+ status: response.status,
258
+ headers,
259
+ };
260
+ }
261
+ catch (error) {
262
+ clearTimeout(timeoutId);
263
+ if (error instanceof ProofioError) {
264
+ throw error;
265
+ }
266
+ if (error instanceof Error && error.name === 'AbortError') {
267
+ throw new ProofioError({
268
+ message: 'Request timeout',
269
+ status: 408,
270
+ code: 'TIMEOUT',
271
+ });
272
+ }
273
+ throw new ProofioError({
274
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
275
+ status: 0,
276
+ code: 'NETWORK_ERROR',
277
+ });
278
+ }
279
+ }
280
+ }