@go-avro/avro-js 0.0.2-beta.2 → 0.0.2-beta.20

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.
@@ -6,7 +6,7 @@ export declare class AuthManager {
6
6
  baseUrl: string;
7
7
  storage: TokenStorage | TokenStorage[];
8
8
  });
9
- hasTokens(): Promise<boolean>;
9
+ isAuthenticated(): Promise<boolean>;
10
10
  fetchNewTokens(): Promise<Tokens>;
11
11
  accessToken(): Promise<string | undefined>;
12
12
  refreshTokens(): Promise<Tokens | null>;
@@ -11,41 +11,70 @@ export class AuthManager {
11
11
  });
12
12
  this.baseUrl = baseUrl;
13
13
  }
14
- async hasTokens() {
14
+ async isAuthenticated() {
15
+ if (!this.storages.length) {
16
+ throw new Error('No token storages initialized');
17
+ }
15
18
  for (const storage of this.storages) {
16
19
  const tokens = await storage.get();
17
20
  if (tokens && tokens.access_token) {
18
- return true;
21
+ try {
22
+ const response = await fetch(`${this.baseUrl}/validate`, {
23
+ method: 'POST',
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ 'Authorization': `Bearer ${tokens.access_token}`,
27
+ },
28
+ });
29
+ if (response.ok) {
30
+ return true;
31
+ }
32
+ else {
33
+ // Attempt token refresh if validation fails
34
+ const newTokens = await this.refreshTokens();
35
+ if (newTokens && newTokens.access_token) {
36
+ const retryResponse = await fetch(`${this.baseUrl}/validate`, {
37
+ method: 'POST',
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ 'Authorization': `Bearer ${newTokens.access_token}`,
41
+ },
42
+ });
43
+ return retryResponse.ok;
44
+ }
45
+ }
46
+ }
47
+ catch (error) {
48
+ console.error('Error validating access token:', error);
49
+ }
19
50
  }
20
51
  }
21
52
  return false;
22
53
  }
23
54
  async fetchNewTokens() {
24
- const refreshToken = await new Promise((resolve, reject) => {
25
- this.storages.map(async (storage) => {
26
- const tokens = await storage.get();
27
- if (tokens) {
28
- resolve(tokens.refresh_token);
55
+ for (const storage of this.storages) {
56
+ const tokens = await storage.get();
57
+ if (!tokens || !tokens.refresh_token)
58
+ continue;
59
+ try {
60
+ const response = await fetch(`${this.baseUrl}/refresh`, {
61
+ method: 'POST',
62
+ headers: {
63
+ 'Content-Type': 'application/json',
64
+ 'Accept': 'application/json',
65
+ 'Authorization': `Bearer ${tokens.refresh_token}`,
66
+ },
67
+ });
68
+ if (response.ok) {
69
+ const newTokens = await response.json();
70
+ return newTokens;
29
71
  }
30
- });
31
- reject(new Error('No valid refresh token found'));
32
- });
33
- if (!refreshToken) {
34
- throw new Error('No refresh token available');
35
- }
36
- const response = await fetch(`${this.baseUrl}/refresh`, {
37
- method: 'POST',
38
- headers: {
39
- 'Content-Type': 'application/json',
40
- 'Accept': 'application/json',
41
- 'Authorization': `Bearer ${refreshToken}`,
42
- },
43
- });
44
- if (!response.ok) {
45
- throw new Error('Failed to refresh tokens');
72
+ }
73
+ catch (error) {
74
+ storage.clear();
75
+ }
46
76
  }
47
- const tokens = await response.json();
48
- return tokens;
77
+ throw new Error('Failed to refresh tokens from all storages');
49
78
  }
50
79
  async accessToken() {
51
80
  if (!this.storages.length) {
@@ -1,4 +1,5 @@
1
1
  import { AuthManager } from '../auth/AuthManager';
2
+ import { LineItem } from '../types/api';
2
3
  import { CancelToken, RetryStrategy } from '../types/client';
3
4
  export interface AvroQueryClientConfig {
4
5
  baseUrl: string;
@@ -21,4 +22,16 @@ export declare class AvroQueryClient {
21
22
  username: string;
22
23
  password: string;
23
24
  }, cancelToken?: CancelToken): Promise<Boolean>;
25
+ logout(cancelToken?: CancelToken): Promise<void>;
26
+ fetchJobs(companyGuid: string, amt?: number, knownIds?: string[], unknownIds?: string[], keyword?: string, offset?: number, cancelToken?: CancelToken, headers?: Record<string, string>): Promise<any>;
27
+ fetchEvents(companyGuid: string, amt?: number, knownIds?: string[], unknownIds?: string[], keyword?: string, offset?: number, unbilled?: boolean, billed?: boolean, paid?: boolean, jobId?: string | null, cancelToken?: CancelToken, headers?: Record<string, string>): Promise<any>;
28
+ fetchMonths(companyGuid: string, amt?: number, knownIds?: string[], unknownIds?: string[], keyword?: string, offset?: number, unbilled?: boolean, billed?: boolean, paid?: boolean, jobId?: string | null, cancelToken?: CancelToken, headers?: Record<string, string>): Promise<any>;
29
+ fetchBills(companyGuid: string, amt?: number, knownIds?: string[], unknownIds?: string[], keyword?: string, offset?: number, paid?: boolean, cancelToken?: CancelToken, headers?: Record<string, string>): Promise<any>;
30
+ sendEmail(emailId: string, formData: FormData): Promise<void>;
31
+ createBill(companyGuid: string, data: {
32
+ line_items: LineItem[];
33
+ due_date: number;
34
+ users: string[];
35
+ custom_emails: [string, string][];
36
+ }): Promise<any>;
24
37
  }
@@ -106,8 +106,13 @@ export class AvroQueryClient {
106
106
  }
107
107
  _fetch(method, path, body, cancelToken, headers = {}, isIdempotent = false, retryCount = 0) {
108
108
  const checkCancelled = () => {
109
- if (cancelToken?.isCancelled()) {
110
- return new StandardError(0, 'Request cancelled');
109
+ try {
110
+ if (cancelToken?.isCancelled()) {
111
+ return new StandardError(0, 'Request cancelled');
112
+ }
113
+ }
114
+ catch (error) {
115
+ throw new StandardError(0, `Error checking cancellation (${typeof cancelToken}): ${error}`);
111
116
  }
112
117
  return null;
113
118
  };
@@ -176,7 +181,6 @@ export class AvroQueryClient {
176
181
  delete(path, cancelToken, headers = {}) {
177
182
  return this._xhr('DELETE', path, null, cancelToken, headers, false);
178
183
  }
179
- // add login that uses fetch and sets the tokens in the auth manager
180
184
  login(data, cancelToken) {
181
185
  return this._fetch('POST', '/login', data, cancelToken)
182
186
  .then(tokens => {
@@ -190,4 +194,135 @@ export class AvroQueryClient {
190
194
  throw new StandardError(401, 'Login failed');
191
195
  });
192
196
  }
197
+ logout(cancelToken) {
198
+ return this._fetch('POST', '/logout', null, cancelToken)
199
+ .then(() => this.config.authManager.clearTokens())
200
+ .catch(err => {
201
+ console.error('Logout failed:', err);
202
+ throw new StandardError(500, 'Logout failed');
203
+ });
204
+ }
205
+ fetchJobs(companyGuid, amt = 50, knownIds = [], unknownIds = [], keyword = '', offset = 0, cancelToken, headers = {}) {
206
+ const body = {
207
+ amt,
208
+ known_ids: knownIds,
209
+ unknown_ids: unknownIds,
210
+ query: keyword,
211
+ };
212
+ if (!companyGuid) {
213
+ return Promise.reject(new StandardError(400, 'Company GUID is required'));
214
+ }
215
+ return this._fetch('POST', `/company/${companyGuid}/jobs?amt=${amt}&offset=${offset}`, body, cancelToken, headers)
216
+ .then(response => {
217
+ if (!response || !Array.isArray(response)) {
218
+ throw new StandardError(400, 'Invalid jobs response');
219
+ }
220
+ return response;
221
+ })
222
+ .catch(err => {
223
+ console.error('Failed to fetch jobs:', err);
224
+ throw new StandardError(500, 'Failed to fetch jobs');
225
+ });
226
+ }
227
+ fetchEvents(companyGuid, amt = 50, knownIds = [], unknownIds = [], keyword = '', offset = 0, unbilled = true, billed = true, paid = true, jobId = null, cancelToken, headers = {}) {
228
+ const body = {
229
+ amt,
230
+ known_ids: knownIds,
231
+ unknown_ids: unknownIds,
232
+ query: keyword,
233
+ job_id: jobId,
234
+ };
235
+ if (!companyGuid) {
236
+ return Promise.reject(new StandardError(400, 'Company GUID is required'));
237
+ }
238
+ return this._fetch('POST', `/company/${companyGuid}/events?amt=${amt}&offset=${offset}&unbilled=${unbilled}&billed=${billed}&paid=${paid}`, body, cancelToken, headers)
239
+ .then(response => {
240
+ if (!response || !Array.isArray(response)) {
241
+ throw new StandardError(400, 'Invalid events response');
242
+ }
243
+ return response;
244
+ })
245
+ .catch(err => {
246
+ console.error('Failed to fetch events:', err);
247
+ throw new StandardError(500, 'Failed to fetch events');
248
+ });
249
+ }
250
+ fetchMonths(companyGuid, amt = 50, knownIds = [], unknownIds = [], keyword = '', offset = 0, unbilled = true, billed = true, paid = true, jobId = null, cancelToken, headers = {}) {
251
+ const body = {
252
+ amt,
253
+ known_ids: knownIds,
254
+ unknown_ids: unknownIds,
255
+ query: keyword,
256
+ job_id: jobId,
257
+ };
258
+ if (!companyGuid) {
259
+ return Promise.reject(new StandardError(400, 'Company GUID is required'));
260
+ }
261
+ return this._fetch('POST', `/company/${companyGuid}/months?amt=${amt}&offset=${offset}&unbilled=${unbilled}&billed=${billed}&paid=${paid}`, body, cancelToken, headers)
262
+ .then(response => {
263
+ if (!response || !Array.isArray(response)) {
264
+ throw new StandardError(400, 'Invalid months response');
265
+ }
266
+ return response;
267
+ })
268
+ .catch(err => {
269
+ console.error('Failed to fetch months:', err);
270
+ throw new StandardError(500, 'Failed to fetch months');
271
+ });
272
+ }
273
+ fetchBills(companyGuid, amt = 50, knownIds = [], unknownIds = [], keyword = '', offset = 0, paid = true, cancelToken, headers = {}) {
274
+ const body = {
275
+ amt,
276
+ known_ids: knownIds,
277
+ unknown_ids: unknownIds,
278
+ query: keyword,
279
+ };
280
+ if (!companyGuid) {
281
+ return Promise.reject(new StandardError(400, 'Company GUID is required'));
282
+ }
283
+ return this._fetch('POST', `/company/${companyGuid}/bills?amt=${amt}&offset=${offset}&paid=${paid}`, body, cancelToken, headers)
284
+ .then(response => {
285
+ if (!response || !Array.isArray(response)) {
286
+ throw new StandardError(400, 'Invalid bills response');
287
+ }
288
+ return response;
289
+ })
290
+ .catch(err => {
291
+ console.error('Failed to fetch bills:', err);
292
+ throw new StandardError(500, 'Failed to fetch bills');
293
+ });
294
+ }
295
+ sendEmail(emailId, formData) {
296
+ try {
297
+ return this._xhr('POST', `/email/${emailId}`, formData);
298
+ }
299
+ catch (error) {
300
+ console.error('Failed to send email', error);
301
+ throw error;
302
+ }
303
+ }
304
+ createBill(companyGuid, data) {
305
+ if (!companyGuid) {
306
+ return Promise.reject(new StandardError(400, 'Company GUID is required'));
307
+ }
308
+ const body = {
309
+ events: data.line_items.filter(item => item.line_item_type === 'EVENT').map(item => item.id),
310
+ months: data.line_items.filter(item => item.line_item_type === 'SERVICE_MONTH').map(item => item.id),
311
+ line_items: data.line_items.filter(item => item.line_item_type === 'CUSTOM'),
312
+ manual_emails: data.custom_emails,
313
+ users: data.users,
314
+ due_date: data.due_date,
315
+ };
316
+ return this._fetch('POST', `/company/${companyGuid}/bills`, body)
317
+ .then(response => {
318
+ if (!response || !Array.isArray(response)) {
319
+ throw new StandardError(400, 'Invalid bills response');
320
+ }
321
+ return response;
322
+ })
323
+ .catch(err => {
324
+ console.error('Failed to create bill:', err);
325
+ throw new StandardError(500, 'Failed to create bill');
326
+ });
327
+ }
193
328
  }
@@ -68,6 +68,16 @@ export interface MemberState {
68
68
  }
69
69
  export interface LineItem {
70
70
  id: string;
71
+ line_item_type: "CUSTOM" | "ADDITIONAL_CHARGE" | "EVENT" | "SERVICE_MONTH";
72
+ name: string;
73
+ description: string;
74
+ cost: number | null;
75
+ amount: number | null;
76
+ time_created: number;
77
+ }
78
+ export interface CustomLineItem {
79
+ id: string;
80
+ line_item_type: "CUSTOM";
71
81
  name: string;
72
82
  description: string;
73
83
  cost: number | null;
@@ -211,13 +221,17 @@ export interface Break {
211
221
  }
212
222
  export interface ServiceMonth {
213
223
  id: string;
224
+ line_item_type: "SERVICE_MONTH";
214
225
  job_name: string;
215
226
  job_id: string | null;
227
+ job_address: string;
228
+ job_labels: string[];
216
229
  bill_id: string | null;
217
230
  cost: number;
218
231
  billed: boolean;
219
232
  paid: boolean;
220
233
  amount: number;
234
+ tasks: string[];
221
235
  time_created: number;
222
236
  time_updated: number | null;
223
237
  }
@@ -301,7 +315,7 @@ export interface Bill {
301
315
  intent_created_at: number;
302
316
  intent_last_created_at: number;
303
317
  payment: BillPayment | null;
304
- line_items: LineItem[];
318
+ line_items: CustomLineItem[];
305
319
  months: string[];
306
320
  due_date: number;
307
321
  }
@@ -473,14 +487,17 @@ export interface taskEndInfo {
473
487
  }
474
488
  export interface AdditionalCharge {
475
489
  id: string;
490
+ line_item_type: "ADDITIONAL_CHARGE";
476
491
  time_created: number;
477
492
  time_updated: number | null;
478
493
  name: string;
494
+ description: string;
479
495
  amount: number;
480
496
  }
481
497
  export interface _Event {
482
498
  breaks: string[];
483
499
  id: string;
500
+ line_item_type: "EVENT";
484
501
  name: string;
485
502
  internal_notes: string;
486
503
  external_notes: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-avro/avro-js",
3
- "version": "0.0.2-beta.2",
3
+ "version": "0.0.2-beta.20",
4
4
  "description": "JS client for Avro backend integration.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",