@go-avro/avro-js 0.0.2-beta.144 → 0.0.2-beta.145

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.
@@ -1,4 +1,4 @@
1
- import { Tokens } from '../types/auth';
1
+ import { AuthState, Tokens } from '../types/auth';
2
2
  import { Cache, CacheData } from '../types/cache';
3
3
  export declare class AuthManager {
4
4
  private storages;
@@ -9,12 +9,12 @@ export declare class AuthManager {
9
9
  baseUrl: string;
10
10
  storage: Cache | Cache[];
11
11
  });
12
- isAuthenticated(): Promise<boolean>;
12
+ isAuthenticated(): Promise<AuthState>;
13
13
  fetchNewTokens(): Promise<Tokens>;
14
14
  accessToken(): Promise<string | undefined>;
15
15
  onTokenRefreshed(callback: (accessToken: string) => void): void;
16
16
  onTokenRefreshFailed(callback: () => void): void;
17
- refreshTokens(): Promise<Tokens | null>;
17
+ refreshTokens(): Promise<Tokens>;
18
18
  setTokens(tokens: Tokens): Promise<void>;
19
19
  setCache(data: Partial<CacheData>): Promise<void>;
20
20
  getCache(key?: keyof CacheData): Promise<CacheData | string | null>;
@@ -1,3 +1,4 @@
1
+ import { AuthState } from '../types/auth';
1
2
  import { StandardError } from '../types/error';
2
3
  export class AuthManager {
3
4
  constructor({ baseUrl, storage, }) {
@@ -30,7 +31,7 @@ export class AuthManager {
30
31
  },
31
32
  });
32
33
  if (response.ok) {
33
- return true;
34
+ return AuthState.AUTHENTICATED;
34
35
  }
35
36
  else {
36
37
  // Attempt token refresh if validation fails
@@ -43,7 +44,7 @@ export class AuthManager {
43
44
  'Authorization': `Bearer ${newTokens.access_token}`,
44
45
  },
45
46
  });
46
- return retryResponse.ok;
47
+ return retryResponse.ok ? AuthState.AUTHENTICATED : AuthState.UNAUTHENTICATED;
47
48
  }
48
49
  }
49
50
  }
@@ -52,7 +53,7 @@ export class AuthManager {
52
53
  }
53
54
  }
54
55
  }
55
- return false;
56
+ return AuthState.UNAUTHENTICATED;
56
57
  }
57
58
  async fetchNewTokens() {
58
59
  for (const storage of this.storages) {
@@ -98,22 +99,13 @@ export class AuthManager {
98
99
  this.tokenRefreshFailedCallbacks.push(callback);
99
100
  }
100
101
  async refreshTokens() {
101
- try {
102
- const newToken = await this.fetchNewTokens();
103
- await Promise.all(this.storages.map(s => s.set(newToken)));
104
- this.tokenRefreshedCallbacks.forEach(cb => {
105
- if (newToken?.access_token)
106
- cb(newToken.access_token);
107
- });
108
- return newToken;
109
- }
110
- catch (error) {
111
- this.tokenRefreshFailedCallbacks.forEach(cb => cb());
112
- if (error?.status !== 410) {
113
- console.error('Failed to refresh tokens:', error);
114
- }
115
- return null;
116
- }
102
+ const newToken = await this.fetchNewTokens();
103
+ await Promise.all(this.storages.map(s => s.set(newToken)));
104
+ this.tokenRefreshedCallbacks.forEach(cb => {
105
+ if (newToken?.access_token)
106
+ cb(newToken.access_token);
107
+ });
108
+ return newToken;
117
109
  }
118
110
  async setTokens(tokens) {
119
111
  await Promise.all(this.storages.map(s => s.set(tokens)));
@@ -2,7 +2,7 @@ import { Socket } from 'socket.io-client';
2
2
  import { InfiniteData, QueryClient, UseInfiniteQueryResult, useMutation, UseQueryResult } from '@tanstack/react-query';
3
3
  import { AuthManager } from '../auth/AuthManager';
4
4
  import { _Event, ApiInfo, Avro, Bill, Break, Chat, Company, FinancialInsightData, Job, EventInsightData, LoginResponse, Message, Plan, Route, ServiceMonth, Session, Team, User, UserCompanyAssociation, Skill, Group, Label } from '../types/api';
5
- import { Tokens } from '../types/auth';
5
+ import { AuthState, Tokens } from '../types/auth';
6
6
  import { CancelToken, RetryStrategy } from '../types/client';
7
7
  import { StandardError } from '../types/error';
8
8
  import { CacheData } from '../types/cache';
@@ -102,6 +102,7 @@ declare module '../client/QueryClient' {
102
102
  }, total: number, onProgress?: (fraction: number) => void): UseQueryResult<Group[], StandardError>;
103
103
  useGetPlans(code: string): UseQueryResult<Plan[], StandardError>;
104
104
  useGetCompanies(options?: {}): UseQueryResult<Company[], StandardError>;
105
+ useGetProposal(proposal_id: string): UseQueryResult<any, StandardError>;
105
106
  useGetAnalytics(): UseQueryResult<any, StandardError>;
106
107
  useFinanceAnalytics({ periods, cumulative }: {
107
108
  periods: number[][];
@@ -122,6 +123,12 @@ declare module '../client/QueryClient' {
122
123
  useGetUserSessions(): UseQueryResult<Session[], StandardError>;
123
124
  useGetAvro(): UseQueryResult<Avro, StandardError>;
124
125
  useSearchUsers(searchUsername: string): UseQueryResult<User[], StandardError>;
126
+ useCreateProposal(): ReturnType<typeof useMutation<{
127
+ id: string;
128
+ }, StandardError, {
129
+ job_id: string;
130
+ data: Record<string, any>;
131
+ }>>;
125
132
  useCreateGroup(): ReturnType<typeof useMutation<{
126
133
  id: string;
127
134
  }, StandardError, {
@@ -345,17 +352,11 @@ declare module '../client/QueryClient' {
345
352
  export declare class AvroQueryClient {
346
353
  protected config: Required<AvroQueryClientConfig>;
347
354
  readonly socket: Socket;
348
- _isAuthenticated: boolean;
355
+ _authState: AuthState;
349
356
  companyId: string | null;
350
357
  company: Company | null;
351
- private initialized;
352
- private initializedListeners;
358
+ private authStateListeners;
353
359
  constructor(config: AvroQueryClientConfig);
354
- private computeInitialized;
355
- private updateInitialized;
356
- onInitializedChange(cb: (v: boolean) => void): () => void;
357
- offInitializedChange(cb: (v: boolean) => void): void;
358
- isInitialized(): boolean;
359
360
  emit(eventName: string, data: unknown): void;
360
361
  on<T>(eventName: string, callback: (data: T) => void): void;
361
362
  off(eventName: string, callback?: Function): void;
@@ -393,8 +394,11 @@ export declare class AvroQueryClient {
393
394
  setCompanyId(companyId: string): Promise<void[]>;
394
395
  getCompanyId(): Promise<string | null>;
395
396
  clearCache(): Promise<void>;
396
- isAuthenticated(): boolean;
397
- isAuthenticatedAsync(): Promise<boolean>;
397
+ onAuthStateChange(cb: (v: AuthState) => void): () => void;
398
+ offAuthStateChange(cb: (v: AuthState) => void): void;
399
+ setAuthState(state: AuthState): void;
400
+ getAuthState(): AuthState;
401
+ getAuthStateAsync(): Promise<AuthState>;
398
402
  getQueryClient(): QueryClient;
399
403
  useLogout(): ReturnType<typeof useMutation<void, StandardError, CancelToken | undefined>>;
400
404
  fetchJobs(body?: {
@@ -1,14 +1,14 @@
1
1
  import io from 'socket.io-client';
2
2
  import { useMutation, useQueryClient } from '@tanstack/react-query';
3
3
  import { LoginResponse } from '../types/api';
4
+ import { AuthState } from '../types/auth';
4
5
  import { StandardError } from '../types/error';
5
6
  export class AvroQueryClient {
6
7
  constructor(config) {
7
- this._isAuthenticated = false;
8
+ this._authState = AuthState.UNKNOWN;
8
9
  this.companyId = null;
9
10
  this.company = null;
10
- this.initialized = false;
11
- this.initializedListeners = [];
11
+ this.authStateListeners = [];
12
12
  this.config = {
13
13
  baseUrl: config.baseUrl,
14
14
  authManager: config.authManager,
@@ -18,80 +18,45 @@ export class AvroQueryClient {
18
18
  };
19
19
  this.socket = io(config.baseUrl, { autoConnect: false, transports: ["websocket"], });
20
20
  config.authManager.isAuthenticated().then(isAuth => {
21
- this._isAuthenticated = isAuth;
21
+ this.setAuthState(isAuth);
22
22
  this.getCompanyId().then(id => {
23
23
  this.companyId = id;
24
- this.updateInitialized();
25
24
  });
25
+ if (!this.socket.connected && isAuth === AuthState.AUTHENTICATED) {
26
+ this.config.authManager.accessToken().then(token => {
27
+ console.log('Initializing socket connection with token:', token);
28
+ this.socket.auth = { token: token };
29
+ this.socket.connect();
30
+ }).catch(err => {
31
+ console.error('Not logged in:', err);
32
+ });
33
+ }
26
34
  });
27
- if (!this.socket.connected) {
28
- this.config.authManager.accessToken().then(token => {
29
- console.log('Initializing socket connection with token...');
30
- this.socket.auth = { token: token };
31
- this.socket.connect();
32
- });
33
- }
34
35
  this.socket.on('connect', () => {
35
- this._isAuthenticated = true;
36
+ this.setAuthState(AuthState.AUTHENTICATED);
36
37
  console.log(`Socket connected with ID: ${this.socket?.id}`);
37
- this.updateInitialized();
38
38
  });
39
39
  this.socket.on('disconnect', (reason) => {
40
40
  console.log(`Socket disconnected: ${reason}`);
41
- this.updateInitialized();
42
41
  });
43
42
  this.socket.on('connect_error', (err) => {
44
43
  console.error(`Socket connection error: ${err.message}`);
45
44
  });
46
45
  this.config.authManager.onTokenRefreshed((newAccessToken) => {
47
46
  if (this.socket && newAccessToken) {
48
- this._isAuthenticated = true;
47
+ this.setAuthState(AuthState.AUTHENTICATED);
49
48
  console.log('Access token refreshed, updating socket auth...');
50
49
  this.socket.auth = { token: newAccessToken };
51
50
  this.socket.disconnect().connect();
52
51
  }
53
52
  });
54
53
  config.authManager.onTokenRefreshFailed(() => {
55
- this._isAuthenticated = false;
54
+ this.setAuthState(AuthState.UNAUTHENTICATED);
56
55
  if (this.socket && this.socket.connected) {
57
56
  this.socket.disconnect();
58
57
  }
59
58
  });
60
59
  }
61
- computeInitialized() {
62
- return !!(this.socket?.connected && this.companyId !== null);
63
- }
64
- updateInitialized() {
65
- const next = this.computeInitialized();
66
- if (this.initialized !== next) {
67
- this.initialized = next;
68
- this.initializedListeners.forEach(cb => {
69
- try {
70
- cb(next);
71
- }
72
- catch (_) {
73
- console.error(_);
74
- }
75
- });
76
- }
77
- }
78
- onInitializedChange(cb) {
79
- this.initializedListeners.push(cb);
80
- // call immediately with current value
81
- try {
82
- cb(this.initialized);
83
- }
84
- catch (_) {
85
- console.error(_);
86
- }
87
- return () => this.offInitializedChange(cb);
88
- }
89
- offInitializedChange(cb) {
90
- this.initializedListeners = this.initializedListeners.filter(c => c !== cb);
91
- }
92
- isInitialized() {
93
- return this.initialized;
94
- }
95
60
  emit(eventName, data) {
96
61
  if (!this.socket?.connected) {
97
62
  console.error('Socket is not connected. Cannot emit event.');
@@ -128,7 +93,7 @@ export class AvroQueryClient {
128
93
  }
129
94
  throw new StandardError(401, 'Invalid login response');
130
95
  }
131
- this._isAuthenticated = true;
96
+ this.setAuthState(AuthState.AUTHENTICATED);
132
97
  this.socket.auth = { token: resp.access_token };
133
98
  if (!this.socket.connected) {
134
99
  this.socket.connect();
@@ -185,7 +150,7 @@ export class AvroQueryClient {
185
150
  }
186
151
  throw new StandardError(401, 'Invalid Google login response');
187
152
  }
188
- this._isAuthenticated = true;
153
+ this.setAuthState(AuthState.AUTHENTICATED);
189
154
  this.socket.auth = { token: resp.access_token };
190
155
  if (!this.socket.connected) {
191
156
  this.socket.connect();
@@ -213,7 +178,6 @@ export class AvroQueryClient {
213
178
  }
214
179
  setCompanyId(companyId) {
215
180
  this.companyId = companyId;
216
- this.updateInitialized();
217
181
  return this.config.authManager.setCompanyId(companyId);
218
182
  }
219
183
  getCompanyId() {
@@ -225,10 +189,28 @@ export class AvroQueryClient {
225
189
  clearCache() {
226
190
  return this.config.authManager.clearCache();
227
191
  }
228
- isAuthenticated() {
229
- return this._isAuthenticated;
192
+ onAuthStateChange(cb) {
193
+ this.authStateListeners.push(cb);
194
+ // call immediately with current value
195
+ try {
196
+ cb(this._authState);
197
+ }
198
+ catch (_) {
199
+ console.error(_);
200
+ }
201
+ return () => this.offAuthStateChange(cb);
202
+ }
203
+ offAuthStateChange(cb) {
204
+ this.authStateListeners = this.authStateListeners.filter(c => c !== cb);
205
+ }
206
+ setAuthState(state) {
207
+ this.authStateListeners.forEach(cb => cb(state));
208
+ this._authState = state;
209
+ }
210
+ getAuthState() {
211
+ return this._authState;
230
212
  }
231
- isAuthenticatedAsync() {
213
+ getAuthStateAsync() {
232
214
  return this.config.authManager.isAuthenticated();
233
215
  }
234
216
  getQueryClient() {
@@ -245,7 +227,7 @@ export class AvroQueryClient {
245
227
  }
246
228
  },
247
229
  onSettled: () => {
248
- this._isAuthenticated = false;
230
+ this.setAuthState(AuthState.UNAUTHENTICATED);
249
231
  this.clearCache();
250
232
  queryClient.invalidateQueries();
251
233
  },
@@ -1,5 +1,6 @@
1
1
  import { AvroQueryClient } from '../../client/QueryClient';
2
2
  import { StandardError } from '../../types/error';
3
+ import { AuthState } from '../../types/auth';
3
4
  AvroQueryClient.prototype._fetch = async function (method, path, body, cancelToken, headers = {}, isIdempotent = false, retryCount = 0) {
4
5
  const checkCancelled = () => {
5
6
  try {
@@ -13,7 +14,7 @@ AvroQueryClient.prototype._fetch = async function (method, path, body, cancelTok
13
14
  };
14
15
  try {
15
16
  checkCancelled();
16
- const token = await this.config.authManager.accessToken();
17
+ const token = this.getAuthState() === AuthState.AUTHENTICATED ? await this.config.authManager.accessToken() : undefined;
17
18
  checkCancelled();
18
19
  const url = this.config.baseUrl + path;
19
20
  const requestHeaders = {
@@ -37,7 +38,7 @@ AvroQueryClient.prototype._fetch = async function (method, path, body, cancelTok
37
38
  await this.config.authManager.refreshTokens();
38
39
  return this._fetch(method, path, body, cancelToken, headers, isIdempotent, 1);
39
40
  }
40
- if (retryCount < this.config.maxRetries) {
41
+ if (response.status !== 401 && retryCount < this.config.maxRetries) {
41
42
  const delay = this.getDelay(this.config.retryStrategy, retryCount);
42
43
  await this.sleep(delay);
43
44
  return this._fetch(method, path, body, cancelToken, headers, isIdempotent, retryCount + 1);
@@ -57,6 +58,7 @@ AvroQueryClient.prototype._fetch = async function (method, path, body, cancelTok
57
58
  throw error;
58
59
  }
59
60
  const message = error instanceof Error ? error.message : String(error);
61
+ console.error('Fetch error:', message);
60
62
  throw new StandardError(0, `Request failed: ${message}`);
61
63
  }
62
64
  };
@@ -1,5 +1,6 @@
1
1
  import { AvroQueryClient } from '../../client/QueryClient';
2
2
  import { StandardError } from '../../types/error';
3
+ import { AuthState } from '../../types/auth';
3
4
  AvroQueryClient.prototype._xhr = async function (method, path, body, cancelToken, headers = {}, isIdempotent = false, retryCount = 0, progressUpdateCallback) {
4
5
  const checkCancelled = () => {
5
6
  if (cancelToken?.isCancelled()) {
@@ -9,7 +10,7 @@ AvroQueryClient.prototype._xhr = async function (method, path, body, cancelToken
9
10
  for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
10
11
  try {
11
12
  checkCancelled();
12
- const token = await this.config.authManager.accessToken();
13
+ const token = this.getAuthState() === AuthState.AUTHENTICATED ? await this.config.authManager.accessToken() : undefined;
13
14
  checkCancelled();
14
15
  const result = await new Promise((resolve, reject) => {
15
16
  const xhr = new XMLHttpRequest();
@@ -60,11 +61,14 @@ AvroQueryClient.prototype._xhr = async function (method, path, body, cancelToken
60
61
  return result;
61
62
  }
62
63
  catch (error) {
64
+ console.error('xhr error:', error);
63
65
  if (!(error instanceof StandardError)) {
64
66
  const message = error instanceof Error ? error.message : String(error);
67
+ console.error('Non-StandardError caught:', message);
65
68
  throw new StandardError(0, `An unexpected error occurred: ${message}`);
66
69
  }
67
70
  if (error.status === 401 && this.config.authManager.refreshTokens && attempt === 0) {
71
+ console.log('Attempting to refresh tokens due to 401 response');
68
72
  try {
69
73
  await this.config.authManager.refreshTokens();
70
74
  continue;
@@ -73,10 +77,12 @@ AvroQueryClient.prototype._xhr = async function (method, path, body, cancelToken
73
77
  throw new StandardError(401, 'Unauthorized (refresh failed)');
74
78
  }
75
79
  }
76
- if (attempt >= this.config.maxRetries) {
80
+ if (error.status === 401 || attempt >= this.config.maxRetries) {
81
+ console.error('Not retrying request, throwing error.');
77
82
  throw error;
78
83
  }
79
84
  const delay = this.getDelay(this.config.retryStrategy, attempt);
85
+ console.error(`Retrying request in ${delay} ms...`);
80
86
  await this.sleep(delay);
81
87
  }
82
88
  }
@@ -1,9 +1,11 @@
1
1
  import { useMutation, useQuery } from '@tanstack/react-query';
2
2
  import { AvroQueryClient } from '../../client/QueryClient';
3
+ import { AuthState } from '../../types/auth';
3
4
  AvroQueryClient.prototype.useGetCompanies = function (options = {}) {
4
5
  return useQuery({
5
6
  queryKey: ['/company/list'],
6
7
  queryFn: () => this.get('/company/list'),
8
+ enabled: this.getAuthState() === AuthState.AUTHENTICATED,
7
9
  ...options,
8
10
  });
9
11
  };
@@ -33,6 +35,7 @@ AvroQueryClient.prototype.useGetCurrentCompany = function () {
33
35
  this.company = await this.get(`/company/${this.companyId}`);
34
36
  return this.company;
35
37
  },
38
+ enabled: this.getAuthState() === AuthState.AUTHENTICATED,
36
39
  });
37
40
  };
38
41
  AvroQueryClient.prototype.useCreateCompany = function () {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { AvroQueryClient } from "../../client/QueryClient";
2
+ import { useMutation, useQuery } from "@tanstack/react-query";
3
+ AvroQueryClient.prototype.useCreateProposal = function () {
4
+ return useMutation({
5
+ mutationFn: async ({ job_id, data }) => {
6
+ return this.post(`/job/${job_id}/proposal`, JSON.stringify(data), undefined, { "Content-Type": "application/json" });
7
+ },
8
+ });
9
+ };
10
+ AvroQueryClient.prototype.useGetProposal = function (proposal_id) {
11
+ return useQuery({
12
+ queryKey: ['proposal', proposal_id],
13
+ queryFn: async () => this.get(`/proposal/${proposal_id}`),
14
+ });
15
+ };
@@ -1,5 +1,6 @@
1
1
  import { useMutation, useQuery } from "@tanstack/react-query";
2
2
  import { AvroQueryClient } from "../../client/QueryClient";
3
+ import { AuthState } from "../../types/auth";
3
4
  AvroQueryClient.prototype.useGetUser = function (userId) {
4
5
  return useQuery({
5
6
  queryKey: ['user', userId],
@@ -22,7 +23,7 @@ AvroQueryClient.prototype.useGetSelf = function () {
22
23
  return useQuery({
23
24
  queryKey: ['user'],
24
25
  queryFn: () => this.get(`/user`),
25
- enabled: Boolean(this),
26
+ enabled: this.getAuthState() === AuthState.AUTHENTICATED,
26
27
  });
27
28
  };
28
29
  AvroQueryClient.prototype.useCreateSelf = function () {
package/dist/index.d.ts CHANGED
@@ -23,6 +23,7 @@ import './client/hooks/teams';
23
23
  import './client/hooks/labels';
24
24
  import './client/hooks/groups';
25
25
  import './client/hooks/skills';
26
+ import './client/hooks/proposal';
26
27
  export * from './types/api';
27
28
  export * from './types/auth';
28
29
  export * from './types/cache';
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ import './client/hooks/teams';
23
23
  import './client/hooks/labels';
24
24
  import './client/hooks/groups';
25
25
  import './client/hooks/skills';
26
+ import './client/hooks/proposal';
26
27
  export * from './types/api';
27
28
  export * from './types/auth';
28
29
  export * from './types/cache';
@@ -2,3 +2,9 @@ export interface Tokens {
2
2
  access_token: string;
3
3
  refresh_token: string;
4
4
  }
5
+ export declare const AuthState: {
6
+ readonly AUTHENTICATED: 1;
7
+ readonly UNAUTHENTICATED: 2;
8
+ readonly UNKNOWN: 3;
9
+ };
10
+ export type AuthState = typeof AuthState[keyof typeof AuthState];
@@ -1 +1,5 @@
1
- export {};
1
+ export const AuthState = {
2
+ AUTHENTICATED: 1,
3
+ UNAUTHENTICATED: 2,
4
+ UNKNOWN: 3,
5
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-avro/avro-js",
3
- "version": "0.0.2-beta.144",
3
+ "version": "0.0.2-beta.145",
4
4
  "description": "JS client for Avro backend integration.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",