@cranberry-money/shared-services 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.
- package/README.md +288 -0
- package/dist/adapters/MobileApiClient.d.ts +68 -0
- package/dist/adapters/MobileApiClient.d.ts.map +1 -0
- package/dist/adapters/MobileApiClient.js +240 -0
- package/dist/adapters/MobileTokenStorage.d.ts +43 -0
- package/dist/adapters/MobileTokenStorage.d.ts.map +1 -0
- package/dist/adapters/MobileTokenStorage.js +128 -0
- package/dist/adapters/WebApiClient.d.ts +28 -0
- package/dist/adapters/WebApiClient.d.ts.map +1 -0
- package/dist/adapters/WebApiClient.js +119 -0
- package/dist/adapters/WebTokenStorage.d.ts +38 -0
- package/dist/adapters/WebTokenStorage.d.ts.map +1 -0
- package/dist/adapters/WebTokenStorage.js +86 -0
- package/dist/auth/AuthManager.d.ts +81 -0
- package/dist/auth/AuthManager.d.ts.map +1 -0
- package/dist/auth/AuthManager.js +223 -0
- package/dist/auth/createAuthManager.d.ts +63 -0
- package/dist/auth/createAuthManager.d.ts.map +1 -0
- package/dist/auth/createAuthManager.js +103 -0
- package/dist/auth/useAuthManager.d.ts +66 -0
- package/dist/auth/useAuthManager.d.ts.map +1 -0
- package/dist/auth/useAuthManager.js +133 -0
- package/dist/core/BaseApiClient.d.ts +82 -0
- package/dist/core/BaseApiClient.d.ts.map +1 -0
- package/dist/core/BaseApiClient.js +89 -0
- package/dist/core/TokenStorage.d.ts +45 -0
- package/dist/core/TokenStorage.d.ts.map +1 -0
- package/dist/core/TokenStorage.js +23 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/query/QueryClient.d.ts +82 -0
- package/dist/query/QueryClient.d.ts.map +1 -0
- package/dist/query/QueryClient.js +136 -0
- package/dist/query/useAuth.d.ts +64 -0
- package/dist/query/useAuth.d.ts.map +1 -0
- package/dist/query/useAuth.js +144 -0
- package/dist/query/usePortfolios.d.ts +79 -0
- package/dist/query/usePortfolios.d.ts.map +1 -0
- package/dist/query/usePortfolios.js +172 -0
- package/dist/services/AuthService.d.ts +75 -0
- package/dist/services/AuthService.d.ts.map +1 -0
- package/dist/services/AuthService.js +83 -0
- package/dist/services/BaseService.d.ts +48 -0
- package/dist/services/BaseService.d.ts.map +1 -0
- package/dist/services/BaseService.js +51 -0
- package/dist/services/PortfolioService.d.ts +100 -0
- package/dist/services/PortfolioService.d.ts.map +1 -0
- package/dist/services/PortfolioService.js +68 -0
- package/package.json +56 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base API Client abstraction for cross-platform HTTP requests
|
|
3
|
+
*
|
|
4
|
+
* This provides a platform-agnostic interface for making HTTP requests
|
|
5
|
+
* with consistent error handling, authentication, and response processing.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class for API clients
|
|
9
|
+
* Platform-specific implementations should extend this class
|
|
10
|
+
*/
|
|
11
|
+
export class BaseApiClient {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.baseURL = config.baseURL;
|
|
14
|
+
this.timeout = config.timeout || 30000;
|
|
15
|
+
this.defaultHeaders = config.defaultHeaders || {};
|
|
16
|
+
this.withCredentials = config.withCredentials || false;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Set token storage implementation
|
|
20
|
+
*/
|
|
21
|
+
setTokenStorage(storage) {
|
|
22
|
+
this.tokenStorage = storage;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Convenience methods for common HTTP operations
|
|
26
|
+
*/
|
|
27
|
+
async get(url, params) {
|
|
28
|
+
return this.request({ url, method: 'GET', params });
|
|
29
|
+
}
|
|
30
|
+
async post(url, data) {
|
|
31
|
+
return this.request({ url, method: 'POST', data });
|
|
32
|
+
}
|
|
33
|
+
async put(url, data) {
|
|
34
|
+
return this.request({ url, method: 'PUT', data });
|
|
35
|
+
}
|
|
36
|
+
async patch(url, data) {
|
|
37
|
+
return this.request({ url, method: 'PATCH', data });
|
|
38
|
+
}
|
|
39
|
+
async delete(url) {
|
|
40
|
+
return this.request({ url, method: 'DELETE' });
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build full URL from relative path
|
|
44
|
+
*/
|
|
45
|
+
buildUrl(url) {
|
|
46
|
+
if (url.startsWith('http')) {
|
|
47
|
+
return url;
|
|
48
|
+
}
|
|
49
|
+
return `${this.baseURL.replace(/\/$/, '')}/${url.replace(/^\//, '')}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Merge headers with defaults and authentication
|
|
53
|
+
*/
|
|
54
|
+
async buildHeaders(headers = {}) {
|
|
55
|
+
const mergedHeaders = { ...this.defaultHeaders, ...headers };
|
|
56
|
+
// Add authentication header if token storage is available and tokens exist
|
|
57
|
+
if (this.tokenStorage) {
|
|
58
|
+
const tokens = await this.tokenStorage.retrieveTokens();
|
|
59
|
+
if (tokens?.access) {
|
|
60
|
+
mergedHeaders['Authorization'] = `Bearer ${tokens.access}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return mergedHeaders;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Handle API errors consistently
|
|
67
|
+
*/
|
|
68
|
+
createError(message, status, code, data) {
|
|
69
|
+
return {
|
|
70
|
+
message,
|
|
71
|
+
status,
|
|
72
|
+
code,
|
|
73
|
+
data,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if error is authentication related
|
|
78
|
+
*/
|
|
79
|
+
isAuthError(error) {
|
|
80
|
+
return error.status === 401 || error.code === 'UNAUTHORIZED';
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Handle token refresh logic (to be implemented by platform adapters)
|
|
84
|
+
*/
|
|
85
|
+
async handleTokenRefresh() {
|
|
86
|
+
// Default implementation - override in platform-specific clients
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token storage abstraction for cross-platform authentication
|
|
3
|
+
*
|
|
4
|
+
* This interface provides a consistent API for storing authentication tokens
|
|
5
|
+
* across different platforms (web cookies, mobile secure storage, etc.)
|
|
6
|
+
*/
|
|
7
|
+
export interface TokenPair {
|
|
8
|
+
access: string;
|
|
9
|
+
refresh: string;
|
|
10
|
+
}
|
|
11
|
+
export interface TokenStorage {
|
|
12
|
+
/**
|
|
13
|
+
* Store authentication tokens securely
|
|
14
|
+
* @param tokens - The access and refresh tokens to store
|
|
15
|
+
*/
|
|
16
|
+
storeTokens(tokens: TokenPair): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Retrieve stored authentication tokens
|
|
19
|
+
* @returns The stored tokens or null if none exist
|
|
20
|
+
*/
|
|
21
|
+
retrieveTokens(): Promise<TokenPair | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Clear all stored authentication tokens
|
|
24
|
+
*/
|
|
25
|
+
clearTokens(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Check if tokens are currently stored
|
|
28
|
+
* @returns True if tokens exist, false otherwise
|
|
29
|
+
*/
|
|
30
|
+
hasTokens(): Promise<boolean>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Base implementation with common utilities
|
|
34
|
+
*/
|
|
35
|
+
export declare abstract class BaseTokenStorage implements TokenStorage {
|
|
36
|
+
abstract storeTokens(tokens: TokenPair): Promise<void>;
|
|
37
|
+
abstract retrieveTokens(): Promise<TokenPair | null>;
|
|
38
|
+
abstract clearTokens(): Promise<void>;
|
|
39
|
+
hasTokens(): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Validate token format (basic validation)
|
|
42
|
+
*/
|
|
43
|
+
protected validateTokens(tokens: TokenPair): boolean;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=TokenStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenStorage.d.ts","sourceRoot":"","sources":["../../src/core/TokenStorage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAE5C;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;;OAGG;IACH,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/B;AAED;;GAEG;AACH,8BAAsB,gBAAiB,YAAW,YAAY;IAC5D,QAAQ,CAAC,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IACtD,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IACpD,QAAQ,CAAC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAE/B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAKnC;;OAEG;IACH,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO;CAKrD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token storage abstraction for cross-platform authentication
|
|
3
|
+
*
|
|
4
|
+
* This interface provides a consistent API for storing authentication tokens
|
|
5
|
+
* across different platforms (web cookies, mobile secure storage, etc.)
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Base implementation with common utilities
|
|
9
|
+
*/
|
|
10
|
+
export class BaseTokenStorage {
|
|
11
|
+
async hasTokens() {
|
|
12
|
+
const tokens = await this.retrieveTokens();
|
|
13
|
+
return tokens !== null && tokens.access !== '' && tokens.refresh !== '';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate token format (basic validation)
|
|
17
|
+
*/
|
|
18
|
+
validateTokens(tokens) {
|
|
19
|
+
return !!(tokens.access && tokens.refresh &&
|
|
20
|
+
typeof tokens.access === 'string' &&
|
|
21
|
+
typeof tokens.refresh === 'string');
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized export of all shared services
|
|
3
|
+
*/
|
|
4
|
+
export * from './core/TokenStorage';
|
|
5
|
+
export * from './core/BaseApiClient';
|
|
6
|
+
export * from './adapters/WebTokenStorage';
|
|
7
|
+
export * from './adapters/WebApiClient';
|
|
8
|
+
export * from './adapters/MobileTokenStorage';
|
|
9
|
+
export * from './adapters/MobileApiClient';
|
|
10
|
+
export * from './services/BaseService';
|
|
11
|
+
export * from './services/AuthService';
|
|
12
|
+
export * from './services/PortfolioService';
|
|
13
|
+
export * from './query/QueryClient';
|
|
14
|
+
export * from './query/useAuth';
|
|
15
|
+
export * from './query/usePortfolios';
|
|
16
|
+
export * from './auth/AuthManager';
|
|
17
|
+
export * from './auth/createAuthManager';
|
|
18
|
+
export * from './auth/useAuthManager';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAG3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAG5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AAGtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized export of all shared services
|
|
3
|
+
*/
|
|
4
|
+
// Core abstractions
|
|
5
|
+
export * from './core/TokenStorage';
|
|
6
|
+
export * from './core/BaseApiClient';
|
|
7
|
+
// Platform adapters
|
|
8
|
+
export * from './adapters/WebTokenStorage';
|
|
9
|
+
export * from './adapters/WebApiClient';
|
|
10
|
+
export * from './adapters/MobileTokenStorage';
|
|
11
|
+
export * from './adapters/MobileApiClient';
|
|
12
|
+
// Services
|
|
13
|
+
export * from './services/BaseService';
|
|
14
|
+
export * from './services/AuthService';
|
|
15
|
+
export * from './services/PortfolioService';
|
|
16
|
+
// React Query integration
|
|
17
|
+
export * from './query/QueryClient';
|
|
18
|
+
export * from './query/useAuth';
|
|
19
|
+
export * from './query/usePortfolios';
|
|
20
|
+
// Authentication Manager
|
|
21
|
+
export * from './auth/AuthManager';
|
|
22
|
+
export * from './auth/createAuthManager';
|
|
23
|
+
export * from './auth/useAuthManager';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared React Query client configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides platform-optimized React Query configurations for web and mobile
|
|
5
|
+
* with appropriate caching, retry, and offline behavior settings.
|
|
6
|
+
*/
|
|
7
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
8
|
+
export type Platform = 'web' | 'mobile';
|
|
9
|
+
export interface CreateQueryClientOptions {
|
|
10
|
+
platform: Platform;
|
|
11
|
+
defaultStaleTime?: number;
|
|
12
|
+
defaultRetryCount?: number;
|
|
13
|
+
enableOfflineSupport?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a QueryClient with platform-optimized defaults
|
|
17
|
+
*/
|
|
18
|
+
export declare const createQueryClient: (options: CreateQueryClientOptions) => QueryClient;
|
|
19
|
+
/**
|
|
20
|
+
* Default query keys factory
|
|
21
|
+
* Provides consistent query key structure across the application
|
|
22
|
+
*/
|
|
23
|
+
export declare const queryKeys: {
|
|
24
|
+
readonly auth: {
|
|
25
|
+
readonly profile: () => readonly ["auth", "profile"];
|
|
26
|
+
readonly status: () => readonly ["auth", "status"];
|
|
27
|
+
};
|
|
28
|
+
readonly portfolios: {
|
|
29
|
+
readonly all: () => readonly ["portfolios"];
|
|
30
|
+
readonly detail: (id: string) => readonly ["portfolios", string];
|
|
31
|
+
readonly holdings: (id: string) => readonly ["portfolios", string, "holdings"];
|
|
32
|
+
readonly allocations: (id: string) => readonly ["portfolios", string, "allocations"];
|
|
33
|
+
};
|
|
34
|
+
readonly instruments: {
|
|
35
|
+
readonly all: (filters?: Record<string, unknown>) => readonly ["instruments", Record<string, unknown> | undefined];
|
|
36
|
+
readonly detail: (id: string) => readonly ["instruments", string];
|
|
37
|
+
readonly search: (query: string) => readonly ["instruments", "search", string];
|
|
38
|
+
};
|
|
39
|
+
readonly trades: {
|
|
40
|
+
readonly all: (filters?: Record<string, unknown>) => readonly ["trades", Record<string, unknown> | undefined];
|
|
41
|
+
readonly target: (filters?: Record<string, unknown>) => readonly ["trades", "target", Record<string, unknown> | undefined];
|
|
42
|
+
readonly detail: (id: string) => readonly ["trades", string];
|
|
43
|
+
};
|
|
44
|
+
readonly reference: {
|
|
45
|
+
readonly countries: () => readonly ["reference", "countries"];
|
|
46
|
+
readonly sectors: () => readonly ["reference", "sectors"];
|
|
47
|
+
readonly industries: () => readonly ["reference", "industries"];
|
|
48
|
+
readonly exchanges: () => readonly ["reference", "exchanges"];
|
|
49
|
+
};
|
|
50
|
+
readonly users: {
|
|
51
|
+
readonly profile: () => readonly ["users", "profile"];
|
|
52
|
+
readonly preferences: () => readonly ["users", "preferences"];
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Common query options factory
|
|
57
|
+
*/
|
|
58
|
+
export declare const createQueryOptions: {
|
|
59
|
+
/**
|
|
60
|
+
* Create options for frequently updated data
|
|
61
|
+
*/
|
|
62
|
+
realtime: (platform: Platform) => {
|
|
63
|
+
staleTime: number;
|
|
64
|
+
refetchInterval: number;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Create options for static reference data
|
|
68
|
+
*/
|
|
69
|
+
reference: () => {
|
|
70
|
+
staleTime: number;
|
|
71
|
+
gcTime: number;
|
|
72
|
+
refetchOnWindowFocus: boolean;
|
|
73
|
+
refetchOnReconnect: boolean;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Create options for user-specific data
|
|
77
|
+
*/
|
|
78
|
+
user: (platform: Platform) => {
|
|
79
|
+
staleTime: number;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=QueryClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueryClient.d.ts","sourceRoot":"","sources":["../../src/query/QueryClient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAA0B,MAAM,uBAAuB,CAAC;AAE5E,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAAC;AAExC,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,QAAQ,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,wBAAwB,KAAG,WA4CrE,CAAC;AA2BF;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;;8BAUL,MAAM;gCACJ,MAAM;mCACH,MAAM;;;iCAKR,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;8BAC1B,MAAM;iCACH,MAAM;;;iCAKN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;oCACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;8BAC7B,MAAM;;;;;;;;;;;;CAgBb,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,kBAAkB;IAC7B;;OAEG;yBACkB,QAAQ;;;;IAK7B;;OAEG;;;;;;;IAQH;;OAEG;qBACc,QAAQ;;;CAG1B,CAAC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared React Query client configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides platform-optimized React Query configurations for web and mobile
|
|
5
|
+
* with appropriate caching, retry, and offline behavior settings.
|
|
6
|
+
*/
|
|
7
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
8
|
+
/**
|
|
9
|
+
* Create a QueryClient with platform-optimized defaults
|
|
10
|
+
*/
|
|
11
|
+
export const createQueryClient = (options) => {
|
|
12
|
+
const { platform, defaultStaleTime, defaultRetryCount, enableOfflineSupport = platform === 'mobile', } = options;
|
|
13
|
+
// Platform-specific defaults
|
|
14
|
+
const platformDefaults = getPlatformDefaults(platform);
|
|
15
|
+
const config = {
|
|
16
|
+
defaultOptions: {
|
|
17
|
+
queries: {
|
|
18
|
+
// Stale time - how long data stays fresh
|
|
19
|
+
staleTime: defaultStaleTime ?? platformDefaults.staleTime,
|
|
20
|
+
// Cache time - how long data stays in cache after becoming stale
|
|
21
|
+
gcTime: platformDefaults.gcTime,
|
|
22
|
+
// Retry configuration
|
|
23
|
+
retry: defaultRetryCount ?? platformDefaults.retry,
|
|
24
|
+
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
|
25
|
+
// Refetch behavior
|
|
26
|
+
refetchOnWindowFocus: platformDefaults.refetchOnWindowFocus,
|
|
27
|
+
refetchOnReconnect: platformDefaults.refetchOnReconnect,
|
|
28
|
+
refetchOnMount: true,
|
|
29
|
+
// Network mode for offline support
|
|
30
|
+
networkMode: enableOfflineSupport ? 'offlineFirst' : 'online',
|
|
31
|
+
},
|
|
32
|
+
mutations: {
|
|
33
|
+
// Retry mutations on network errors
|
|
34
|
+
retry: platform === 'mobile' ? 2 : 1,
|
|
35
|
+
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000),
|
|
36
|
+
// Network mode for offline support
|
|
37
|
+
networkMode: enableOfflineSupport ? 'offlineFirst' : 'online',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
return new QueryClient(config);
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Get platform-specific default configurations
|
|
45
|
+
*/
|
|
46
|
+
function getPlatformDefaults(platform) {
|
|
47
|
+
if (platform === 'mobile') {
|
|
48
|
+
return {
|
|
49
|
+
// Mobile: Longer stale times for better offline experience
|
|
50
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
51
|
+
gcTime: 10 * 60 * 1000, // 10 minutes
|
|
52
|
+
retry: 3, // More retries for unstable connections
|
|
53
|
+
refetchOnWindowFocus: false, // No window focus on mobile
|
|
54
|
+
refetchOnReconnect: true, // Refetch when network reconnects
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
return {
|
|
59
|
+
// Web: Shorter stale times for more responsive experience
|
|
60
|
+
staleTime: 30 * 1000, // 30 seconds
|
|
61
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
62
|
+
retry: 1, // Single retry for stable connections
|
|
63
|
+
refetchOnWindowFocus: true, // Refetch when user returns to tab
|
|
64
|
+
refetchOnReconnect: true, // Refetch when network reconnects
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Default query keys factory
|
|
70
|
+
* Provides consistent query key structure across the application
|
|
71
|
+
*/
|
|
72
|
+
export const queryKeys = {
|
|
73
|
+
// Authentication
|
|
74
|
+
auth: {
|
|
75
|
+
profile: () => ['auth', 'profile'],
|
|
76
|
+
status: () => ['auth', 'status'],
|
|
77
|
+
},
|
|
78
|
+
// Portfolios
|
|
79
|
+
portfolios: {
|
|
80
|
+
all: () => ['portfolios'],
|
|
81
|
+
detail: (id) => ['portfolios', id],
|
|
82
|
+
holdings: (id) => ['portfolios', id, 'holdings'],
|
|
83
|
+
allocations: (id) => ['portfolios', id, 'allocations'],
|
|
84
|
+
},
|
|
85
|
+
// Instruments
|
|
86
|
+
instruments: {
|
|
87
|
+
all: (filters) => ['instruments', filters],
|
|
88
|
+
detail: (id) => ['instruments', id],
|
|
89
|
+
search: (query) => ['instruments', 'search', query],
|
|
90
|
+
},
|
|
91
|
+
// Trades
|
|
92
|
+
trades: {
|
|
93
|
+
all: (filters) => ['trades', filters],
|
|
94
|
+
target: (filters) => ['trades', 'target', filters],
|
|
95
|
+
detail: (id) => ['trades', id],
|
|
96
|
+
},
|
|
97
|
+
// Reference data
|
|
98
|
+
reference: {
|
|
99
|
+
countries: () => ['reference', 'countries'],
|
|
100
|
+
sectors: () => ['reference', 'sectors'],
|
|
101
|
+
industries: () => ['reference', 'industries'],
|
|
102
|
+
exchanges: () => ['reference', 'exchanges'],
|
|
103
|
+
},
|
|
104
|
+
// Users
|
|
105
|
+
users: {
|
|
106
|
+
profile: () => ['users', 'profile'],
|
|
107
|
+
preferences: () => ['users', 'preferences'],
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Common query options factory
|
|
112
|
+
*/
|
|
113
|
+
export const createQueryOptions = {
|
|
114
|
+
/**
|
|
115
|
+
* Create options for frequently updated data
|
|
116
|
+
*/
|
|
117
|
+
realtime: (platform) => ({
|
|
118
|
+
staleTime: platform === 'mobile' ? 1 * 60 * 1000 : 10 * 1000, // 1min mobile, 10s web
|
|
119
|
+
refetchInterval: platform === 'mobile' ? 2 * 60 * 1000 : 30 * 1000, // 2min mobile, 30s web
|
|
120
|
+
}),
|
|
121
|
+
/**
|
|
122
|
+
* Create options for static reference data
|
|
123
|
+
*/
|
|
124
|
+
reference: () => ({
|
|
125
|
+
staleTime: 24 * 60 * 60 * 1000, // 24 hours
|
|
126
|
+
gcTime: 48 * 60 * 60 * 1000, // 48 hours
|
|
127
|
+
refetchOnWindowFocus: false,
|
|
128
|
+
refetchOnReconnect: false,
|
|
129
|
+
}),
|
|
130
|
+
/**
|
|
131
|
+
* Create options for user-specific data
|
|
132
|
+
*/
|
|
133
|
+
user: (platform) => ({
|
|
134
|
+
staleTime: platform === 'mobile' ? 10 * 60 * 1000 : 2 * 60 * 1000, // 10min mobile, 2min web
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared authentication React Query hooks
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent authentication state management and operations
|
|
5
|
+
* across web and mobile platforms with proper query invalidation.
|
|
6
|
+
*/
|
|
7
|
+
import { type UseMutationOptions } from '@tanstack/react-query';
|
|
8
|
+
import type { AuthService, SigninPayload, SignupPayload, EmailVerificationPayload } from '../services/AuthService';
|
|
9
|
+
export interface UseAuthOptions {
|
|
10
|
+
authService: AuthService;
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface AuthMutationOptions<TData, TVariables> extends Omit<UseMutationOptions<TData, Error, TVariables>, 'mutationFn'> {
|
|
14
|
+
authService: AuthService;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get current user profile with authentication status
|
|
18
|
+
*/
|
|
19
|
+
export declare function useProfile({ authService, enabled }: UseAuthOptions): import("@tanstack/react-query").UseQueryResult<import("..").UserProfile, Error>;
|
|
20
|
+
/**
|
|
21
|
+
* Check authentication status
|
|
22
|
+
*/
|
|
23
|
+
export declare function useAuthStatus({ authService, enabled }: UseAuthOptions): import("@tanstack/react-query").UseQueryResult<{
|
|
24
|
+
authenticated: boolean;
|
|
25
|
+
}, Error>;
|
|
26
|
+
/**
|
|
27
|
+
* Sign in mutation
|
|
28
|
+
*/
|
|
29
|
+
export declare function useSignin(options: AuthMutationOptions<unknown, SigninPayload>): import("@tanstack/react-query").UseMutationResult<unknown, Error, SigninPayload, unknown>;
|
|
30
|
+
/**
|
|
31
|
+
* Sign out mutation
|
|
32
|
+
*/
|
|
33
|
+
export declare function useSignout(options: AuthMutationOptions<unknown, void>): import("@tanstack/react-query").UseMutationResult<unknown, Error, void, unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* Sign up mutation
|
|
36
|
+
*/
|
|
37
|
+
export declare function useSignup(options: AuthMutationOptions<unknown, SignupPayload>): import("@tanstack/react-query").UseMutationResult<unknown, Error, SignupPayload, unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* Email verification mutation
|
|
40
|
+
*/
|
|
41
|
+
export declare function useVerifyEmail(options: AuthMutationOptions<unknown, EmailVerificationPayload>): import("@tanstack/react-query").UseMutationResult<unknown, Error, EmailVerificationPayload, unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* Resend verification code mutation
|
|
44
|
+
*/
|
|
45
|
+
export declare function useResendVerification(options: AuthMutationOptions<unknown, void>): import("@tanstack/react-query").UseMutationResult<unknown, Error, void, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* Composite auth hook that provides complete authentication state
|
|
48
|
+
*/
|
|
49
|
+
export declare function useAuth(options: UseAuthOptions): {
|
|
50
|
+
user: import("..").UserProfile | undefined;
|
|
51
|
+
isAuthenticated: boolean;
|
|
52
|
+
isLoading: boolean;
|
|
53
|
+
isLoadingProfile: boolean;
|
|
54
|
+
isLoadingStatus: boolean;
|
|
55
|
+
error: Error | null;
|
|
56
|
+
profileError: Error | null;
|
|
57
|
+
statusError: Error | null;
|
|
58
|
+
refetchProfile: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("..").UserProfile, Error>>;
|
|
59
|
+
refetchStatus: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<{
|
|
60
|
+
authenticated: boolean;
|
|
61
|
+
}, Error>>;
|
|
62
|
+
refetch: () => void;
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=useAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/query/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAyC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACvG,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAGnH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB,CAAC,KAAK,EAAE,UAAU,CAAE,SACtD,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,EAAE,YAAY,CAAC;IAChE,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,WAAW,EAAE,OAAc,EAAE,EAAE,cAAc,mFAczE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,WAAW,EAAE,OAAc,EAAE,EAAE,cAAc;;UAQ5E;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,6FAgB7E;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,oFAerE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,6FAe7E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,EAAE,wBAAwB,CAAC,wGAe7F;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,oFAOhF;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc;;;;;;;;;;;;;;EA2B9C"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared authentication React Query hooks
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent authentication state management and operations
|
|
5
|
+
* across web and mobile platforms with proper query invalidation.
|
|
6
|
+
*/
|
|
7
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
8
|
+
import { queryKeys } from './QueryClient';
|
|
9
|
+
/**
|
|
10
|
+
* Get current user profile with authentication status
|
|
11
|
+
*/
|
|
12
|
+
export function useProfile({ authService, enabled = true }) {
|
|
13
|
+
return useQuery({
|
|
14
|
+
queryKey: queryKeys.auth.profile(),
|
|
15
|
+
queryFn: () => authService.getProfile(),
|
|
16
|
+
enabled,
|
|
17
|
+
retry: (failureCount, error) => {
|
|
18
|
+
// Don't retry on authentication errors
|
|
19
|
+
if (error && 'status' in error && error.status === 401) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return failureCount < 3;
|
|
23
|
+
},
|
|
24
|
+
staleTime: 2 * 60 * 1000, // 2 minutes
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check authentication status
|
|
29
|
+
*/
|
|
30
|
+
export function useAuthStatus({ authService, enabled = true }) {
|
|
31
|
+
return useQuery({
|
|
32
|
+
queryKey: queryKeys.auth.status(),
|
|
33
|
+
queryFn: () => authService.checkStatus(),
|
|
34
|
+
enabled,
|
|
35
|
+
retry: false, // Don't retry auth status checks
|
|
36
|
+
staleTime: 1 * 60 * 1000, // 1 minute
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Sign in mutation
|
|
41
|
+
*/
|
|
42
|
+
export function useSignin(options) {
|
|
43
|
+
const queryClient = useQueryClient();
|
|
44
|
+
const { authService, ...mutationOptions } = options;
|
|
45
|
+
return useMutation({
|
|
46
|
+
mutationFn: (data) => authService.signin(data),
|
|
47
|
+
onSuccess: (...args) => {
|
|
48
|
+
// Invalidate auth-related queries
|
|
49
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.auth.profile() });
|
|
50
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.auth.status() });
|
|
51
|
+
// Call user's onSuccess if provided
|
|
52
|
+
mutationOptions.onSuccess?.(...args);
|
|
53
|
+
},
|
|
54
|
+
...mutationOptions,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sign out mutation
|
|
59
|
+
*/
|
|
60
|
+
export function useSignout(options) {
|
|
61
|
+
const queryClient = useQueryClient();
|
|
62
|
+
const { authService, ...mutationOptions } = options;
|
|
63
|
+
return useMutation({
|
|
64
|
+
mutationFn: () => authService.signout(),
|
|
65
|
+
onSuccess: (...args) => {
|
|
66
|
+
// Clear all cached data on signout
|
|
67
|
+
queryClient.clear();
|
|
68
|
+
// Call user's onSuccess if provided
|
|
69
|
+
mutationOptions.onSuccess?.(...args);
|
|
70
|
+
},
|
|
71
|
+
...mutationOptions,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Sign up mutation
|
|
76
|
+
*/
|
|
77
|
+
export function useSignup(options) {
|
|
78
|
+
const { authService, ...mutationOptions } = options;
|
|
79
|
+
return useMutation({
|
|
80
|
+
mutationFn: (data) => authService.signup(data),
|
|
81
|
+
onSuccess: (...args) => {
|
|
82
|
+
// Optionally invalidate auth queries if signup auto-signs in
|
|
83
|
+
// const queryClient = useQueryClient();
|
|
84
|
+
// queryClient.invalidateQueries({ queryKey: queryKeys.auth.profile() });
|
|
85
|
+
// Call user's onSuccess if provided
|
|
86
|
+
mutationOptions.onSuccess?.(...args);
|
|
87
|
+
},
|
|
88
|
+
...mutationOptions,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Email verification mutation
|
|
93
|
+
*/
|
|
94
|
+
export function useVerifyEmail(options) {
|
|
95
|
+
const queryClient = useQueryClient();
|
|
96
|
+
const { authService, ...mutationOptions } = options;
|
|
97
|
+
return useMutation({
|
|
98
|
+
mutationFn: (data) => authService.verifyEmail(data),
|
|
99
|
+
onSuccess: (...args) => {
|
|
100
|
+
// Invalidate profile to get updated verification status
|
|
101
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.auth.profile() });
|
|
102
|
+
// Call user's onSuccess if provided
|
|
103
|
+
mutationOptions.onSuccess?.(...args);
|
|
104
|
+
},
|
|
105
|
+
...mutationOptions,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resend verification code mutation
|
|
110
|
+
*/
|
|
111
|
+
export function useResendVerification(options) {
|
|
112
|
+
const { authService, ...mutationOptions } = options;
|
|
113
|
+
return useMutation({
|
|
114
|
+
mutationFn: () => authService.resendVerificationCode(),
|
|
115
|
+
...mutationOptions,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Composite auth hook that provides complete authentication state
|
|
120
|
+
*/
|
|
121
|
+
export function useAuth(options) {
|
|
122
|
+
const profileQuery = useProfile(options);
|
|
123
|
+
const statusQuery = useAuthStatus(options);
|
|
124
|
+
return {
|
|
125
|
+
// Data
|
|
126
|
+
user: profileQuery.data,
|
|
127
|
+
isAuthenticated: !!(statusQuery.data && profileQuery.data),
|
|
128
|
+
// Loading states
|
|
129
|
+
isLoading: profileQuery.isLoading || statusQuery.isLoading,
|
|
130
|
+
isLoadingProfile: profileQuery.isLoading,
|
|
131
|
+
isLoadingStatus: statusQuery.isLoading,
|
|
132
|
+
// Error states
|
|
133
|
+
error: profileQuery.error || statusQuery.error,
|
|
134
|
+
profileError: profileQuery.error,
|
|
135
|
+
statusError: statusQuery.error,
|
|
136
|
+
// Refetch functions
|
|
137
|
+
refetchProfile: profileQuery.refetch,
|
|
138
|
+
refetchStatus: statusQuery.refetch,
|
|
139
|
+
refetch: () => {
|
|
140
|
+
profileQuery.refetch();
|
|
141
|
+
statusQuery.refetch();
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|