@go-avro/avro-js 0.0.2-beta.6 → 0.0.2-beta.61
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.
- package/README.md +1 -0
- package/dist/auth/AuthManager.d.ts +2 -0
- package/dist/auth/AuthManager.js +8 -0
- package/dist/client/QueryClient.d.ts +256 -10
- package/dist/client/QueryClient.js +167 -174
- package/dist/client/core/fetch.d.ts +1 -0
- package/dist/client/core/fetch.js +67 -0
- package/dist/client/core/utils.d.ts +1 -0
- package/dist/client/core/utils.js +13 -0
- package/dist/client/core/xhr.d.ts +1 -0
- package/dist/client/core/xhr.js +87 -0
- package/dist/client/hooks/bills.d.ts +1 -0
- package/dist/client/hooks/bills.js +141 -0
- package/dist/client/hooks/companies.d.ts +1 -0
- package/dist/client/hooks/companies.js +51 -0
- package/dist/client/hooks/events.d.ts +1 -0
- package/dist/client/hooks/events.js +307 -0
- package/dist/client/hooks/jobs.d.ts +1 -0
- package/dist/client/hooks/jobs.js +184 -0
- package/dist/client/hooks/months.d.ts +1 -0
- package/dist/client/hooks/months.js +92 -0
- package/dist/client/hooks/root.d.ts +1 -0
- package/dist/client/hooks/root.js +8 -0
- package/dist/client/hooks/routes.d.ts +1 -0
- package/dist/client/hooks/routes.js +127 -0
- package/dist/client/hooks/sessions.d.ts +1 -0
- package/dist/client/hooks/sessions.js +154 -0
- package/dist/client/hooks/users.d.ts +1 -0
- package/dist/client/hooks/users.js +93 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/types/api.d.ts +106 -23
- package/dist/types/api.js +6 -1
- package/package.json +6 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
1
2
|
import { StandardError } from '../types/error';
|
|
3
|
+
import io from 'socket.io-client';
|
|
2
4
|
export class AvroQueryClient {
|
|
3
5
|
constructor(config) {
|
|
4
6
|
this.config = {
|
|
@@ -8,173 +10,55 @@ export class AvroQueryClient {
|
|
|
8
10
|
retryStrategy: config.retryStrategy ?? 'fixed',
|
|
9
11
|
timeout: config.timeout ?? 0,
|
|
10
12
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return 1000;
|
|
18
|
-
}
|
|
19
|
-
else if (strategy === 'exponential') {
|
|
20
|
-
return Math.pow(2, attempt) * 100;
|
|
21
|
-
}
|
|
22
|
-
throw new Error(`Invalid retry strategy: ${strategy}`);
|
|
23
|
-
}
|
|
24
|
-
_xhr(method, path, body, cancelToken, headers = {}, isIdempotent = false, retryCount = 0) {
|
|
25
|
-
const checkCancelled = () => {
|
|
26
|
-
if (cancelToken?.isCancelled()) {
|
|
27
|
-
return new StandardError(0, 'Request cancelled');
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
};
|
|
31
|
-
return this.config.authManager.accessToken().then(token => {
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
const cancelErr = checkCancelled();
|
|
34
|
-
if (cancelErr)
|
|
35
|
-
return reject(cancelErr);
|
|
36
|
-
const xhr = new XMLHttpRequest();
|
|
37
|
-
const url = this.config.baseUrl + path;
|
|
38
|
-
xhr.open(method, url, true);
|
|
39
|
-
if (token) {
|
|
40
|
-
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
|
41
|
-
}
|
|
42
|
-
Object.entries(headers).forEach(([key, value]) => {
|
|
43
|
-
xhr.setRequestHeader(key, value);
|
|
44
|
-
});
|
|
45
|
-
xhr.onload = () => {
|
|
46
|
-
const cancelErr = checkCancelled();
|
|
47
|
-
if (cancelErr)
|
|
48
|
-
return reject(cancelErr);
|
|
49
|
-
if (xhr.status === 401 && this.config.authManager.refreshTokens && retryCount === 0) {
|
|
50
|
-
this.config.authManager
|
|
51
|
-
.refreshTokens()
|
|
52
|
-
.then(() => {
|
|
53
|
-
this._xhr(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1).then(resolve, reject);
|
|
54
|
-
})
|
|
55
|
-
.catch(() => {
|
|
56
|
-
reject(new StandardError(401, 'Unauthorized (refresh failed)'));
|
|
57
|
-
});
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (xhr.status >= 200 && xhr.status < 300) {
|
|
61
|
-
try {
|
|
62
|
-
resolve(JSON.parse(xhr.responseText));
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
resolve(xhr.responseText);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
if (retryCount < this.config.maxRetries) {
|
|
70
|
-
const delay = this.getDelay(this.config.retryStrategy, retryCount);
|
|
71
|
-
setTimeout(() => {
|
|
72
|
-
this._xhr(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1).then(resolve, reject);
|
|
73
|
-
}, delay);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
let msg = xhr.statusText;
|
|
77
|
-
try {
|
|
78
|
-
const parsed = JSON.parse(xhr.responseText);
|
|
79
|
-
msg = parsed.message || msg;
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
console.warn('Failed to parse error response:', xhr.responseText);
|
|
83
|
-
}
|
|
84
|
-
reject(new StandardError(xhr.status, msg));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
xhr.onerror = () => {
|
|
89
|
-
if (retryCount < this.config.maxRetries) {
|
|
90
|
-
const delay = this.getDelay(this.config.retryStrategy, retryCount);
|
|
91
|
-
setTimeout(() => {
|
|
92
|
-
this._xhr(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1).then(resolve, reject);
|
|
93
|
-
}, delay);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
reject(new StandardError(0, 'Network Error'));
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
if (this.config.timeout) {
|
|
100
|
-
xhr.timeout = this.config.timeout;
|
|
101
|
-
xhr.ontimeout = () => reject(new StandardError(0, 'Request timed out'));
|
|
102
|
-
}
|
|
103
|
-
xhr.send(body);
|
|
13
|
+
this.socket = io(config.baseUrl, { autoConnect: false, transports: ["websocket"], });
|
|
14
|
+
if (!this.socket.connected) {
|
|
15
|
+
this.config.authManager.accessToken().then(token => {
|
|
16
|
+
console.log('Initializing socket connection with token...');
|
|
17
|
+
this.socket.auth = { token: token };
|
|
18
|
+
this.socket.connect();
|
|
104
19
|
});
|
|
20
|
+
}
|
|
21
|
+
this.socket.on('connect', () => {
|
|
22
|
+
console.log(`Socket connected with ID: ${this.socket?.id}`);
|
|
105
23
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return Promise.reject(cancelErr);
|
|
118
|
-
const url = this.config.baseUrl + path;
|
|
119
|
-
const requestHeaders = {
|
|
120
|
-
'Content-Type': 'application/json',
|
|
121
|
-
...headers,
|
|
122
|
-
};
|
|
123
|
-
if (token) {
|
|
124
|
-
requestHeaders['Authorization'] = `Bearer ${token}`;
|
|
24
|
+
this.socket.on('disconnect', (reason) => {
|
|
25
|
+
console.log(`Socket disconnected: ${reason}`);
|
|
26
|
+
});
|
|
27
|
+
this.socket.on('connect_error', (err) => {
|
|
28
|
+
console.error(`Socket connection error: ${err.message}`);
|
|
29
|
+
});
|
|
30
|
+
this.config.authManager.onTokenRefreshed((newAccessToken) => {
|
|
31
|
+
if (this.socket && newAccessToken) {
|
|
32
|
+
console.log('Access token refreshed, updating socket auth...');
|
|
33
|
+
this.socket.auth = { token: newAccessToken };
|
|
34
|
+
this.socket.disconnect().connect();
|
|
125
35
|
}
|
|
126
|
-
const options = {
|
|
127
|
-
method,
|
|
128
|
-
headers: requestHeaders,
|
|
129
|
-
body: body ? JSON.stringify(body) : null,
|
|
130
|
-
};
|
|
131
|
-
return fetch(url, options).then(response => {
|
|
132
|
-
if (response.status === 401 && this.config.authManager.refreshTokens && retryCount === 0) {
|
|
133
|
-
return this.config.authManager
|
|
134
|
-
.refreshTokens()
|
|
135
|
-
.then(() => this._fetch(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1))
|
|
136
|
-
.catch(() => Promise.reject(new StandardError(401, 'Unauthorized (refresh failed)')));
|
|
137
|
-
}
|
|
138
|
-
if (!response.ok) {
|
|
139
|
-
if (retryCount < this.config.maxRetries) {
|
|
140
|
-
const delay = this.getDelay(this.config.retryStrategy, retryCount);
|
|
141
|
-
return new Promise((resolve, reject) => {
|
|
142
|
-
setTimeout(() => {
|
|
143
|
-
this._fetch(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1)
|
|
144
|
-
.then(resolve)
|
|
145
|
-
.catch(reject);
|
|
146
|
-
}, delay);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
return response.text().then(text => {
|
|
151
|
-
let msg = response.statusText;
|
|
152
|
-
try {
|
|
153
|
-
const parsed = JSON.parse(text);
|
|
154
|
-
msg = parsed.message || msg;
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
console.warn('Failed to parse error response:', text);
|
|
158
|
-
}
|
|
159
|
-
throw new StandardError(response.status, msg);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return response.json();
|
|
164
|
-
});
|
|
165
36
|
});
|
|
166
37
|
}
|
|
167
|
-
|
|
168
|
-
|
|
38
|
+
emit(eventName, data) {
|
|
39
|
+
if (!this.socket?.connected) {
|
|
40
|
+
console.error('Socket is not connected. Cannot emit event.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.socket.emit(eventName, data);
|
|
44
|
+
}
|
|
45
|
+
on(eventName, callback) {
|
|
46
|
+
this.socket?.on(eventName, callback);
|
|
47
|
+
}
|
|
48
|
+
off(eventName, callback) {
|
|
49
|
+
this.socket?.off(eventName, callback);
|
|
169
50
|
}
|
|
170
|
-
|
|
171
|
-
return this._xhr('
|
|
51
|
+
get(path, cancelToken, headers = {}, progressUpdateCallback) {
|
|
52
|
+
return this._xhr('GET', path, null, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
|
|
172
53
|
}
|
|
173
|
-
|
|
174
|
-
return this._xhr('
|
|
54
|
+
post(path, data, cancelToken, headers = {}, progressUpdateCallback) {
|
|
55
|
+
return this._xhr('POST', path, data, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
|
|
175
56
|
}
|
|
176
|
-
|
|
177
|
-
return this._xhr('
|
|
57
|
+
put(path, data, cancelToken, headers = {}, progressUpdateCallback) {
|
|
58
|
+
return this._xhr('PUT', path, data, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
|
|
59
|
+
}
|
|
60
|
+
delete(path, cancelToken, headers = {}, progressUpdateCallback) {
|
|
61
|
+
return this._xhr('DELETE', path, null, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
|
|
178
62
|
}
|
|
179
63
|
login(data, cancelToken) {
|
|
180
64
|
return this._fetch('POST', '/login', data, cancelToken)
|
|
@@ -182,6 +66,10 @@ export class AvroQueryClient {
|
|
|
182
66
|
if (!tokens || !tokens.access_token || !tokens.refresh_token) {
|
|
183
67
|
throw new StandardError(401, 'Invalid login response');
|
|
184
68
|
}
|
|
69
|
+
this.socket.auth = { token: tokens.access_token };
|
|
70
|
+
if (!this.socket.connected) {
|
|
71
|
+
this.socket.connect();
|
|
72
|
+
}
|
|
185
73
|
return this.config.authManager.setTokens(tokens).then(() => true);
|
|
186
74
|
})
|
|
187
75
|
.catch(err => {
|
|
@@ -189,25 +77,37 @@ export class AvroQueryClient {
|
|
|
189
77
|
throw new StandardError(401, 'Login failed');
|
|
190
78
|
});
|
|
191
79
|
}
|
|
192
|
-
|
|
193
|
-
return this.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
80
|
+
setTokens(tokens) {
|
|
81
|
+
return this.config.authManager.setTokens(tokens);
|
|
82
|
+
}
|
|
83
|
+
clearTokens() {
|
|
84
|
+
return this.config.authManager.clearTokens();
|
|
85
|
+
}
|
|
86
|
+
useLogout() {
|
|
87
|
+
const queryClient = useQueryClient();
|
|
88
|
+
return useMutation({
|
|
89
|
+
mutationFn: async (cancelToken) => {
|
|
90
|
+
await this._fetch('POST', '/logout', null, cancelToken);
|
|
91
|
+
await this.config.authManager.clearTokens();
|
|
92
|
+
if (this.socket && this.socket.connected) {
|
|
93
|
+
this.socket.disconnect();
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
onSettled: () => {
|
|
97
|
+
queryClient.invalidateQueries();
|
|
98
|
+
},
|
|
99
|
+
onError: (err) => {
|
|
100
|
+
this.config.authManager.clearTokens();
|
|
101
|
+
console.error('Logout failed:', err);
|
|
102
|
+
throw new StandardError(500, 'Logout failed');
|
|
103
|
+
}
|
|
198
104
|
});
|
|
199
105
|
}
|
|
200
|
-
fetchJobs(companyGuid,
|
|
201
|
-
|
|
202
|
-
amt,
|
|
203
|
-
known_ids: knownIds,
|
|
204
|
-
unknown_ids: unknownIds,
|
|
205
|
-
query: keyword,
|
|
206
|
-
};
|
|
207
|
-
if (!companyGuid) {
|
|
106
|
+
fetchJobs(companyGuid, body = {}, cancelToken, headers = {}) {
|
|
107
|
+
if (!companyGuid || companyGuid.trim() === '') {
|
|
208
108
|
return Promise.reject(new StandardError(400, 'Company GUID is required'));
|
|
209
109
|
}
|
|
210
|
-
return this._fetch('POST', `/company/${companyGuid}/jobs
|
|
110
|
+
return this._fetch('POST', `/company/${companyGuid}/jobs`, body, cancelToken, headers)
|
|
211
111
|
.then(response => {
|
|
212
112
|
if (!response || !Array.isArray(response)) {
|
|
213
113
|
throw new StandardError(400, 'Invalid jobs response');
|
|
@@ -219,4 +119,97 @@ export class AvroQueryClient {
|
|
|
219
119
|
throw new StandardError(500, 'Failed to fetch jobs');
|
|
220
120
|
});
|
|
221
121
|
}
|
|
122
|
+
fetchEvents(companyGuid, body = {}, cancelToken, headers = {}) {
|
|
123
|
+
if (!companyGuid || companyGuid.trim() === '') {
|
|
124
|
+
return Promise.reject(new StandardError(400, 'Company GUID is required'));
|
|
125
|
+
}
|
|
126
|
+
return this._fetch('POST', `/company/${companyGuid}/events`, body, cancelToken, headers)
|
|
127
|
+
.then(response => {
|
|
128
|
+
if (!response || !Array.isArray(response)) {
|
|
129
|
+
throw new StandardError(400, 'Invalid events response');
|
|
130
|
+
}
|
|
131
|
+
return response;
|
|
132
|
+
})
|
|
133
|
+
.catch(err => {
|
|
134
|
+
console.error('Failed to fetch events:', err);
|
|
135
|
+
throw new StandardError(500, 'Failed to fetch events');
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
fetchMonths(companyGuid, body = {}, cancelToken, headers = {}) {
|
|
139
|
+
if (!companyGuid || companyGuid.trim() === '') {
|
|
140
|
+
return Promise.reject(new StandardError(400, 'Company GUID is required'));
|
|
141
|
+
}
|
|
142
|
+
return this._fetch('POST', `/company/${companyGuid}/months`, body, cancelToken, headers)
|
|
143
|
+
.then(response => {
|
|
144
|
+
if (!response || !Array.isArray(response)) {
|
|
145
|
+
throw new StandardError(400, 'Invalid months response');
|
|
146
|
+
}
|
|
147
|
+
return response;
|
|
148
|
+
})
|
|
149
|
+
.catch(err => {
|
|
150
|
+
console.error('Failed to fetch months:', err);
|
|
151
|
+
throw new StandardError(500, 'Failed to fetch months');
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
fetchBills(companyGuid, body = {}, cancelToken, headers = {}) {
|
|
155
|
+
if (!companyGuid || companyGuid.trim() === '') {
|
|
156
|
+
return Promise.reject(new StandardError(400, 'Company GUID is required'));
|
|
157
|
+
}
|
|
158
|
+
return this._fetch('POST', `/company/${companyGuid}/bills`, body, cancelToken, headers)
|
|
159
|
+
.then(response => {
|
|
160
|
+
if (!response || !Array.isArray(response)) {
|
|
161
|
+
throw new StandardError(400, 'Invalid bills response');
|
|
162
|
+
}
|
|
163
|
+
return response;
|
|
164
|
+
})
|
|
165
|
+
.catch(err => {
|
|
166
|
+
console.error('Failed to fetch bills:', err);
|
|
167
|
+
throw new StandardError(500, 'Failed to fetch bills');
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
fetchRoutes(companyGuid, body = {}, cancelToken, headers = {}) {
|
|
171
|
+
if (!companyGuid || companyGuid.trim() === '') {
|
|
172
|
+
return Promise.reject(new StandardError(400, 'Company GUID is required'));
|
|
173
|
+
}
|
|
174
|
+
return this._fetch('POST', `/company/${companyGuid}/routes`, body, cancelToken, headers)
|
|
175
|
+
.then(response => {
|
|
176
|
+
if (!response || !Array.isArray(response)) {
|
|
177
|
+
throw new StandardError(400, 'Invalid routes response');
|
|
178
|
+
}
|
|
179
|
+
return response;
|
|
180
|
+
})
|
|
181
|
+
.catch(err => {
|
|
182
|
+
console.error('Failed to fetch routes:', err);
|
|
183
|
+
throw new StandardError(500, 'Failed to fetch routes');
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
sendEmail(emailId, formData, progressUpdateCallback) {
|
|
187
|
+
try {
|
|
188
|
+
return this.post(`/email/${emailId}`, formData, undefined, {}, progressUpdateCallback);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
throw new StandardError(500, `Failed to send email: ${error}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
createBill(companyGuid, data, cancelToken) {
|
|
195
|
+
if (!companyGuid) {
|
|
196
|
+
return Promise.reject(new StandardError(400, 'Company GUID is required'));
|
|
197
|
+
}
|
|
198
|
+
const body = {
|
|
199
|
+
events: data.line_items.filter(item => item.line_item_type === 'EVENT').map(item => item.id),
|
|
200
|
+
months: data.line_items.filter(item => item.line_item_type === 'SERVICE_MONTH').map(item => item.id),
|
|
201
|
+
line_items: data.line_items.filter(item => item.line_item_type === 'CUSTOM'),
|
|
202
|
+
manual_emails: data.custom_emails,
|
|
203
|
+
users: data.users,
|
|
204
|
+
due_date: data.due_date,
|
|
205
|
+
};
|
|
206
|
+
try {
|
|
207
|
+
return this._xhr('POST', `/company/${companyGuid}/bill`, JSON.stringify(body), cancelToken, {
|
|
208
|
+
'Content-Type': 'application/json',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
throw new StandardError(500, `Failed to create bill: ${error}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
222
215
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AvroQueryClient } from '../../client/QueryClient';
|
|
2
|
+
import { StandardError } from '../../types/error';
|
|
3
|
+
AvroQueryClient.prototype._fetch = function (method, path, body, cancelToken, headers = {}, isIdempotent = false, retryCount = 0) {
|
|
4
|
+
const checkCancelled = () => {
|
|
5
|
+
try {
|
|
6
|
+
if (cancelToken?.isCancelled()) {
|
|
7
|
+
return new StandardError(0, 'Request cancelled');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
throw new StandardError(0, `Error checking cancellation (${typeof cancelToken}): ${error}`);
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
};
|
|
15
|
+
return this.config.authManager.accessToken().then(token => {
|
|
16
|
+
const cancelErr = checkCancelled();
|
|
17
|
+
if (cancelErr)
|
|
18
|
+
return Promise.reject(cancelErr);
|
|
19
|
+
const url = this.config.baseUrl + path;
|
|
20
|
+
const requestHeaders = {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
...headers,
|
|
23
|
+
};
|
|
24
|
+
if (token) {
|
|
25
|
+
requestHeaders['Authorization'] = `Bearer ${token}`;
|
|
26
|
+
}
|
|
27
|
+
const options = {
|
|
28
|
+
method,
|
|
29
|
+
headers: requestHeaders,
|
|
30
|
+
body: body ? JSON.stringify(body) : null,
|
|
31
|
+
};
|
|
32
|
+
return fetch(url, options).then(response => {
|
|
33
|
+
if (response.status === 401 && this.config.authManager.refreshTokens && retryCount === 0) {
|
|
34
|
+
return this.config.authManager
|
|
35
|
+
.refreshTokens()
|
|
36
|
+
.then(() => this._fetch(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1))
|
|
37
|
+
.catch(() => Promise.reject(new StandardError(401, 'Unauthorized (refresh failed)')));
|
|
38
|
+
}
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
if (retryCount < this.config.maxRetries) {
|
|
41
|
+
const delay = this.getDelay(this.config.retryStrategy, retryCount);
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
this._fetch(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1)
|
|
45
|
+
.then(resolve)
|
|
46
|
+
.catch(reject);
|
|
47
|
+
}, delay);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return response.text().then(text => {
|
|
52
|
+
let msg = response.statusText;
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(text);
|
|
55
|
+
msg = parsed.message || msg;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
console.warn('Failed to parse error response:', text);
|
|
59
|
+
}
|
|
60
|
+
throw new StandardError(response.status, msg);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return response.json();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AvroQueryClient } from '../../client/QueryClient';
|
|
2
|
+
AvroQueryClient.prototype.getDelay = function (strategy, attempt) {
|
|
3
|
+
if (typeof strategy === 'function') {
|
|
4
|
+
return strategy(attempt);
|
|
5
|
+
}
|
|
6
|
+
else if (strategy === 'fixed') {
|
|
7
|
+
return 1000;
|
|
8
|
+
}
|
|
9
|
+
else if (strategy === 'exponential') {
|
|
10
|
+
return Math.pow(2, attempt) * 100;
|
|
11
|
+
}
|
|
12
|
+
throw new Error(`Invalid retry strategy: ${strategy}`);
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { AvroQueryClient } from '../../client/QueryClient';
|
|
2
|
+
import { StandardError } from '../../types/error';
|
|
3
|
+
AvroQueryClient.prototype._xhr = function (method, path, body, cancelToken, headers = {}, isIdempotent = false, retryCount = 0, progressUpdateCallback) {
|
|
4
|
+
const checkCancelled = () => {
|
|
5
|
+
if (cancelToken?.isCancelled()) {
|
|
6
|
+
return new StandardError(0, 'Request cancelled');
|
|
7
|
+
}
|
|
8
|
+
return null;
|
|
9
|
+
};
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
this.config.authManager.accessToken().then(token => {
|
|
12
|
+
const cancelErr = checkCancelled();
|
|
13
|
+
if (cancelErr)
|
|
14
|
+
return reject(cancelErr);
|
|
15
|
+
const xhr = new XMLHttpRequest();
|
|
16
|
+
const url = this.config.baseUrl + path;
|
|
17
|
+
xhr.open(method, url, true);
|
|
18
|
+
if (token)
|
|
19
|
+
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
|
20
|
+
Object.entries(headers).forEach(([key, value]) => xhr.setRequestHeader(key, value));
|
|
21
|
+
xhr.onload = () => {
|
|
22
|
+
const cancelErr = checkCancelled();
|
|
23
|
+
if (cancelErr)
|
|
24
|
+
return reject(cancelErr);
|
|
25
|
+
if (xhr.status === 401 && this.config.authManager.refreshTokens && retryCount === 0) {
|
|
26
|
+
this.config.authManager
|
|
27
|
+
.refreshTokens()
|
|
28
|
+
.then(() => {
|
|
29
|
+
this._xhr(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1).then(resolve, reject);
|
|
30
|
+
})
|
|
31
|
+
.catch(() => reject(new StandardError(401, 'Unauthorized (refresh failed)')));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
35
|
+
try {
|
|
36
|
+
resolve(JSON.parse(xhr.responseText));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
resolve(xhr.responseText);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (retryCount < this.config.maxRetries) {
|
|
44
|
+
const delay = this.getDelay(this.config.retryStrategy, retryCount);
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
this._xhr(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1).then(resolve, reject);
|
|
47
|
+
}, delay);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
let msg = xhr.statusText;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(xhr.responseText);
|
|
53
|
+
msg = parsed.msg || msg;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
console.warn('Failed to parse error response:', xhr.responseText);
|
|
57
|
+
}
|
|
58
|
+
reject(new StandardError(xhr.status, msg));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
if (progressUpdateCallback && xhr.upload) {
|
|
63
|
+
xhr.upload.onprogress = (event) => {
|
|
64
|
+
if (event.lengthComputable) {
|
|
65
|
+
progressUpdateCallback(event.loaded, event.total);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
xhr.onerror = () => {
|
|
70
|
+
if (retryCount < this.config.maxRetries) {
|
|
71
|
+
const delay = this.getDelay(this.config.retryStrategy, retryCount);
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
this._xhr(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1).then(resolve, reject);
|
|
74
|
+
}, delay);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
reject(new StandardError(0, 'Network Error'));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
if (this.config.timeout) {
|
|
81
|
+
xhr.timeout = this.config.timeout;
|
|
82
|
+
xhr.ontimeout = () => reject(new StandardError(0, 'Request timed out'));
|
|
83
|
+
}
|
|
84
|
+
xhr.send(body);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|