@oxyhq/services 5.13.1 → 5.13.3
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 +71 -0
- package/lib/commonjs/core/HttpClient.js +238 -0
- package/lib/commonjs/core/HttpClient.js.map +1 -0
- package/lib/commonjs/core/OxyServices.js +538 -332
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/RequestManager.js +199 -0
- package/lib/commonjs/core/RequestManager.js.map +1 -0
- package/lib/commonjs/core/index.js +38 -1
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/index.js +36 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/Avatar.js +94 -27
- package/lib/commonjs/ui/components/Avatar.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +1 -0
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/internal/TextField.js +13 -8
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +183 -224
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +80 -22
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/commonjs/ui/index.js +4 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +32 -2
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +101 -59
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -2
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +75 -117
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +0 -11
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +14 -16
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +50 -18
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +10 -10
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +16 -26
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +104 -212
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/stores/accountStore.js +237 -0
- package/lib/commonjs/ui/stores/accountStore.js.map +1 -0
- package/lib/commonjs/ui/stores/authStore.js +2 -1
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/styles/authStyles.js +14 -7
- package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
- package/lib/commonjs/utils/asyncUtils.js +9 -22
- package/lib/commonjs/utils/asyncUtils.js.map +1 -1
- package/lib/commonjs/utils/cache.js +259 -0
- package/lib/commonjs/utils/cache.js.map +1 -0
- package/lib/commonjs/utils/index.js +99 -0
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/commonjs/utils/languageUtils.js +159 -0
- package/lib/commonjs/utils/languageUtils.js.map +1 -0
- package/lib/commonjs/utils/requestUtils.js +217 -0
- package/lib/commonjs/utils/requestUtils.js.map +1 -0
- package/lib/commonjs/utils/sessionUtils.js +191 -0
- package/lib/commonjs/utils/sessionUtils.js.map +1 -0
- package/lib/module/core/HttpClient.js +232 -0
- package/lib/module/core/HttpClient.js.map +1 -0
- package/lib/module/core/OxyServices.js +536 -326
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/RequestManager.js +194 -0
- package/lib/module/core/RequestManager.js.map +1 -0
- package/lib/module/core/index.js +2 -0
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/Avatar.js +94 -27
- package/lib/module/ui/components/Avatar.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +1 -0
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/internal/TextField.js +13 -8
- package/lib/module/ui/components/internal/TextField.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +182 -223
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useSessionSocket.js +80 -22
- package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/module/ui/index.js +4 -2
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +33 -2
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +102 -60
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +3 -2
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js +73 -117
- package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +0 -11
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +14 -16
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +50 -18
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInPasswordStep.js +10 -10
- package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInPasswordStep.js +16 -26
- package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInUsernameStep.js +105 -214
- package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/stores/accountStore.js +229 -0
- package/lib/module/ui/stores/accountStore.js.map +1 -0
- package/lib/module/ui/stores/authStore.js +2 -1
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/styles/authStyles.js +14 -7
- package/lib/module/ui/styles/authStyles.js.map +1 -1
- package/lib/module/utils/asyncUtils.js +10 -22
- package/lib/module/utils/asyncUtils.js.map +1 -1
- package/lib/module/utils/cache.js +250 -0
- package/lib/module/utils/cache.js.map +1 -0
- package/lib/module/utils/index.js +7 -0
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/languageUtils.js +151 -0
- package/lib/module/utils/languageUtils.js.map +1 -0
- package/lib/module/utils/requestUtils.js +210 -0
- package/lib/module/utils/requestUtils.js.map +1 -0
- package/lib/module/utils/sessionUtils.js +180 -0
- package/lib/module/utils/sessionUtils.js.map +1 -0
- package/lib/typescript/core/HttpClient.d.ts +64 -0
- package/lib/typescript/core/HttpClient.d.ts.map +1 -0
- package/lib/typescript/core/OxyServices.d.ts +88 -71
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/core/RequestManager.d.ts +67 -0
- package/lib/typescript/core/RequestManager.d.ts.map +1 -0
- package/lib/typescript/core/index.d.ts +2 -0
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +15 -0
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/models/session.d.ts +1 -0
- package/lib/typescript/models/session.d.ts.map +1 -1
- package/lib/typescript/ui/components/Avatar.d.ts +6 -7
- package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/lib/typescript/ui/index.d.ts +2 -2
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +3 -3
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/stores/accountStore.d.ts +34 -0
- package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -0
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/styles/authStyles.d.ts +18 -2
- package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
- package/lib/typescript/utils/asyncUtils.d.ts +2 -0
- package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
- package/lib/typescript/utils/cache.d.ts +128 -0
- package/lib/typescript/utils/cache.d.ts.map +1 -0
- package/lib/typescript/utils/index.d.ts +4 -0
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/lib/typescript/utils/languageUtils.d.ts +38 -0
- package/lib/typescript/utils/languageUtils.d.ts.map +1 -0
- package/lib/typescript/utils/requestUtils.d.ts +122 -0
- package/lib/typescript/utils/requestUtils.d.ts.map +1 -0
- package/lib/typescript/utils/sessionUtils.d.ts +55 -0
- package/lib/typescript/utils/sessionUtils.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/core/HttpClient.ts +277 -0
- package/src/core/OxyServices.ts +466 -351
- package/src/core/RequestManager.ts +240 -0
- package/src/core/index.ts +10 -0
- package/src/index.ts +10 -0
- package/src/models/interfaces.ts +19 -0
- package/src/models/session.ts +1 -1
- package/src/ui/components/Avatar.tsx +151 -35
- package/src/ui/components/FollowButton.tsx +1 -0
- package/src/ui/components/internal/TextField.tsx +7 -6
- package/src/ui/context/OxyContext.tsx +213 -217
- package/src/ui/hooks/useSessionSocket.ts +72 -18
- package/src/ui/index.ts +4 -1
- package/src/ui/screens/AccountSettingsScreen.tsx +34 -2
- package/src/ui/screens/AccountSwitcherScreen.tsx +102 -68
- package/src/ui/screens/FileManagementScreen.tsx +1 -1
- package/src/ui/screens/LanguageSelectorScreen.tsx +86 -143
- package/src/ui/screens/SignInScreen.tsx +0 -7
- package/src/ui/screens/SignUpScreen.tsx +14 -15
- package/src/ui/screens/WelcomeNewUserScreen.tsx +52 -15
- package/src/ui/screens/internal/SignInPasswordStep.tsx +4 -6
- package/src/ui/screens/steps/SignInPasswordStep.tsx +4 -8
- package/src/ui/screens/steps/SignInUsernameStep.tsx +110 -256
- package/src/ui/stores/accountStore.ts +285 -0
- package/src/ui/stores/authStore.ts +2 -1
- package/src/ui/styles/authStyles.ts +14 -7
- package/src/utils/asyncUtils.ts +10 -24
- package/src/utils/cache.ts +264 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/languageUtils.ts +174 -0
- package/src/utils/requestUtils.ts +234 -0
- package/src/utils/sessionUtils.ts +206 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Manager
|
|
3
|
+
*
|
|
4
|
+
* Handles request-level optimizations: caching, deduplication, queuing, and retry.
|
|
5
|
+
* Works on top of HttpClient to add performance features.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AxiosInstance } from 'axios';
|
|
9
|
+
import { TTLCache, registerCacheForCleanup } from '../utils/cache';
|
|
10
|
+
import { RequestDeduplicator, RequestQueue, SimpleLogger } from '../utils/requestUtils';
|
|
11
|
+
import { retryWithBackoff } from '../utils/errorUtils';
|
|
12
|
+
import type { OxyConfig } from '../models/interfaces';
|
|
13
|
+
|
|
14
|
+
export interface RequestOptions {
|
|
15
|
+
cache?: boolean;
|
|
16
|
+
cacheTTL?: number;
|
|
17
|
+
deduplicate?: boolean;
|
|
18
|
+
retry?: boolean;
|
|
19
|
+
maxRetries?: number;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Request Manager
|
|
26
|
+
*
|
|
27
|
+
* Manages request-level optimizations while delegating actual HTTP calls to HttpClient.
|
|
28
|
+
*/
|
|
29
|
+
export class RequestManager {
|
|
30
|
+
private cache: TTLCache<any>;
|
|
31
|
+
private deduplicator: RequestDeduplicator;
|
|
32
|
+
private requestQueue: RequestQueue;
|
|
33
|
+
private logger: SimpleLogger;
|
|
34
|
+
private config: OxyConfig;
|
|
35
|
+
private httpClient: { request: (config: any) => Promise<any> };
|
|
36
|
+
|
|
37
|
+
// Performance monitoring
|
|
38
|
+
private requestMetrics = {
|
|
39
|
+
totalRequests: 0,
|
|
40
|
+
successfulRequests: 0,
|
|
41
|
+
failedRequests: 0,
|
|
42
|
+
cacheHits: 0,
|
|
43
|
+
cacheMisses: 0,
|
|
44
|
+
averageResponseTime: 0,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
httpClient: { request: (config: any) => Promise<any> },
|
|
49
|
+
config: OxyConfig
|
|
50
|
+
) {
|
|
51
|
+
this.httpClient = httpClient;
|
|
52
|
+
this.config = config;
|
|
53
|
+
|
|
54
|
+
// Initialize performance infrastructure
|
|
55
|
+
this.cache = new TTLCache<any>(config.cacheTTL || 5 * 60 * 1000);
|
|
56
|
+
registerCacheForCleanup(this.cache);
|
|
57
|
+
this.deduplicator = new RequestDeduplicator();
|
|
58
|
+
this.requestQueue = new RequestQueue(
|
|
59
|
+
config.maxConcurrentRequests || 10,
|
|
60
|
+
config.requestQueueSize || 100
|
|
61
|
+
);
|
|
62
|
+
this.logger = new SimpleLogger(
|
|
63
|
+
config.enableLogging || false,
|
|
64
|
+
config.logLevel || 'error',
|
|
65
|
+
'RequestManager'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Make a request with all performance optimizations
|
|
71
|
+
*/
|
|
72
|
+
async request<T>(
|
|
73
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
|
74
|
+
url: string,
|
|
75
|
+
data?: any,
|
|
76
|
+
options: RequestOptions = {}
|
|
77
|
+
): Promise<T> {
|
|
78
|
+
const {
|
|
79
|
+
cache = method === 'GET', // Cache GET requests by default
|
|
80
|
+
cacheTTL,
|
|
81
|
+
deduplicate = true,
|
|
82
|
+
retry = this.config.enableRetry !== false,
|
|
83
|
+
maxRetries = this.config.maxRetries || 3,
|
|
84
|
+
timeout,
|
|
85
|
+
signal,
|
|
86
|
+
} = options;
|
|
87
|
+
|
|
88
|
+
// Generate cache key
|
|
89
|
+
const cacheKey = cache ? `${method}:${url}:${JSON.stringify(data || {})}` : null;
|
|
90
|
+
|
|
91
|
+
// Check cache first
|
|
92
|
+
if (cache && cacheKey) {
|
|
93
|
+
const cached = this.cache.get(cacheKey) as T | null;
|
|
94
|
+
if (cached !== null) {
|
|
95
|
+
this.requestMetrics.cacheHits++;
|
|
96
|
+
this.logger.debug('Cache hit:', url);
|
|
97
|
+
return cached;
|
|
98
|
+
}
|
|
99
|
+
this.requestMetrics.cacheMisses++;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Request function that uses HttpClient
|
|
103
|
+
const requestFn = async (): Promise<T> => {
|
|
104
|
+
const startTime = Date.now();
|
|
105
|
+
try {
|
|
106
|
+
const result = await this.httpClient.request({
|
|
107
|
+
method,
|
|
108
|
+
url,
|
|
109
|
+
data: method !== 'GET' ? data : undefined,
|
|
110
|
+
params: method === 'GET' ? data : undefined,
|
|
111
|
+
timeout: timeout || this.config.requestTimeout || 5000,
|
|
112
|
+
signal,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const duration = Date.now() - startTime;
|
|
116
|
+
this.updateMetrics(true, duration);
|
|
117
|
+
this.config.onRequestEnd?.(url, method, duration, true);
|
|
118
|
+
|
|
119
|
+
return result as T;
|
|
120
|
+
} catch (error: any) {
|
|
121
|
+
const duration = Date.now() - startTime;
|
|
122
|
+
this.updateMetrics(false, duration);
|
|
123
|
+
this.config.onRequestEnd?.(url, method, duration, false);
|
|
124
|
+
this.config.onRequestError?.(url, method, error);
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Wrap with retry if enabled
|
|
130
|
+
const requestWithRetry = retry
|
|
131
|
+
? () => this.retryWithBackoff(requestFn, maxRetries)
|
|
132
|
+
: requestFn;
|
|
133
|
+
|
|
134
|
+
// Wrap with deduplication if enabled
|
|
135
|
+
const dedupeKey = deduplicate ? `${method}:${url}:${JSON.stringify(data || {})}` : null;
|
|
136
|
+
const finalRequest = dedupeKey
|
|
137
|
+
? () => this.deduplicator.deduplicate(dedupeKey, requestWithRetry)
|
|
138
|
+
: requestWithRetry;
|
|
139
|
+
|
|
140
|
+
// Execute request (with queue if needed)
|
|
141
|
+
const result = await this.requestQueue.enqueue(finalRequest);
|
|
142
|
+
|
|
143
|
+
// Cache the result if caching is enabled
|
|
144
|
+
if (cache && cacheKey && result) {
|
|
145
|
+
this.cache.set(cacheKey, result, cacheTTL);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Exponential backoff retry logic with 4xx error handling
|
|
153
|
+
*/
|
|
154
|
+
private async retryWithBackoff<T>(
|
|
155
|
+
fn: () => Promise<T>,
|
|
156
|
+
maxRetries: number = 3,
|
|
157
|
+
baseDelay: number = 1000
|
|
158
|
+
): Promise<T> {
|
|
159
|
+
let lastError: any;
|
|
160
|
+
|
|
161
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
162
|
+
try {
|
|
163
|
+
return await fn();
|
|
164
|
+
} catch (error: any) {
|
|
165
|
+
lastError = error;
|
|
166
|
+
|
|
167
|
+
// Don't retry on 4xx errors (client errors)
|
|
168
|
+
if (error.response?.status >= 400 && error.response?.status < 500) {
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Don't retry on last attempt
|
|
173
|
+
if (attempt === maxRetries) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Calculate delay with exponential backoff and jitter
|
|
178
|
+
const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
|
|
179
|
+
this.logger.debug(`Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`);
|
|
180
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
throw lastError;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Update request metrics
|
|
189
|
+
*/
|
|
190
|
+
private updateMetrics(success: boolean, duration: number): void {
|
|
191
|
+
this.requestMetrics.totalRequests++;
|
|
192
|
+
if (success) {
|
|
193
|
+
this.requestMetrics.successfulRequests++;
|
|
194
|
+
} else {
|
|
195
|
+
this.requestMetrics.failedRequests++;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Update average response time (exponential moving average)
|
|
199
|
+
const alpha = 0.1; // Smoothing factor
|
|
200
|
+
this.requestMetrics.averageResponseTime =
|
|
201
|
+
this.requestMetrics.averageResponseTime * (1 - alpha) + duration * alpha;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get performance metrics
|
|
206
|
+
*/
|
|
207
|
+
getMetrics(): typeof this.requestMetrics {
|
|
208
|
+
return { ...this.requestMetrics };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Clear request cache
|
|
213
|
+
*/
|
|
214
|
+
clearCache(): void {
|
|
215
|
+
this.cache.clear();
|
|
216
|
+
this.logger.debug('Cache cleared');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Clear specific cache entry
|
|
221
|
+
*/
|
|
222
|
+
clearCacheEntry(key: string): void {
|
|
223
|
+
this.cache.delete(key);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get cache statistics
|
|
228
|
+
*/
|
|
229
|
+
getCacheStats(): { size: number; hits: number; misses: number; hitRate: number } {
|
|
230
|
+
const cacheStats = this.cache.getStats();
|
|
231
|
+
const total = this.requestMetrics.cacheHits + this.requestMetrics.cacheMisses;
|
|
232
|
+
return {
|
|
233
|
+
size: cacheStats.size,
|
|
234
|
+
hits: this.requestMetrics.cacheHits,
|
|
235
|
+
misses: this.requestMetrics.cacheMisses,
|
|
236
|
+
hitRate: total > 0 ? this.requestMetrics.cacheHits / total : 0,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
package/src/core/index.ts
CHANGED
|
@@ -17,6 +17,16 @@ export * from '../models/session';
|
|
|
17
17
|
export { DeviceManager } from '../utils/deviceManager';
|
|
18
18
|
export type { DeviceFingerprint, StoredDeviceInfo } from '../utils/deviceManager';
|
|
19
19
|
|
|
20
|
+
// Export language utilities
|
|
21
|
+
export {
|
|
22
|
+
SUPPORTED_LANGUAGES,
|
|
23
|
+
getLanguageMetadata,
|
|
24
|
+
getLanguageName,
|
|
25
|
+
getNativeLanguageName,
|
|
26
|
+
normalizeLanguageCode
|
|
27
|
+
} from '../utils/languageUtils';
|
|
28
|
+
export type { LanguageMetadata } from '../utils/languageUtils';
|
|
29
|
+
|
|
20
30
|
// Import for default export
|
|
21
31
|
import { OxyServices } from './OxyServices';
|
|
22
32
|
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,16 @@ export { default as OxyProvider } from './ui/components/OxyProvider';
|
|
|
23
23
|
export { DeviceManager } from './utils/deviceManager';
|
|
24
24
|
export type { DeviceFingerprint, StoredDeviceInfo } from './utils/deviceManager';
|
|
25
25
|
|
|
26
|
+
// Language utilities
|
|
27
|
+
export {
|
|
28
|
+
SUPPORTED_LANGUAGES,
|
|
29
|
+
getLanguageMetadata,
|
|
30
|
+
getLanguageName,
|
|
31
|
+
getNativeLanguageName,
|
|
32
|
+
normalizeLanguageCode
|
|
33
|
+
} from './utils/languageUtils';
|
|
34
|
+
export type { LanguageMetadata } from './utils/languageUtils';
|
|
35
|
+
|
|
26
36
|
// Type exports
|
|
27
37
|
export type {
|
|
28
38
|
OxyConfig,
|
package/src/models/interfaces.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
export interface OxyConfig {
|
|
2
2
|
baseURL: string;
|
|
3
|
+
cloudURL?: string;
|
|
4
|
+
// Performance & caching options
|
|
5
|
+
enableCache?: boolean;
|
|
6
|
+
cacheTTL?: number; // Cache TTL in milliseconds (default: 5 minutes)
|
|
7
|
+
enableRequestDeduplication?: boolean;
|
|
8
|
+
enableRetry?: boolean;
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
retryDelay?: number;
|
|
11
|
+
requestTimeout?: number; // Default timeout in milliseconds (default: 5000)
|
|
12
|
+
// Rate limiting
|
|
13
|
+
maxConcurrentRequests?: number;
|
|
14
|
+
requestQueueSize?: number;
|
|
15
|
+
// Logging
|
|
16
|
+
enableLogging?: boolean;
|
|
17
|
+
logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
18
|
+
// Performance monitoring
|
|
19
|
+
onRequestStart?: (url: string, method: string) => void;
|
|
20
|
+
onRequestEnd?: (url: string, method: string, duration: number, success: boolean) => void;
|
|
21
|
+
onRequestError?: (url: string, method: string, error: Error) => void;
|
|
3
22
|
}
|
|
4
23
|
|
|
5
24
|
export interface User {
|
package/src/models/session.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
+
import { memo, useMemo } from 'react';
|
|
2
3
|
import { View, Text, Image, StyleSheet, type StyleProp, type ViewStyle, type ImageStyle, type TextStyle, ActivityIndicator, Platform } from 'react-native';
|
|
3
4
|
import { useThemeColors } from '../styles';
|
|
4
5
|
import { fontFamilies } from '../styles/fonts';
|
|
@@ -16,7 +17,10 @@ export interface AvatarProps {
|
|
|
16
17
|
text?: string;
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
|
-
* Full name to derive the initials from
|
|
20
|
+
* Full name to derive the initials from
|
|
21
|
+
* For single word: takes first letter
|
|
22
|
+
* For multiple words: takes first letter of first and last word (e.g., "John Doe" -> "JD")
|
|
23
|
+
* For usernames starting with @: takes first two letters after @
|
|
20
24
|
*/
|
|
21
25
|
name?: string;
|
|
22
26
|
|
|
@@ -67,9 +71,68 @@ export interface AvatarProps {
|
|
|
67
71
|
isLoading?: boolean;
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Extracts initials from a name string
|
|
76
|
+
* - Single word: first letter (e.g., "John" -> "J")
|
|
77
|
+
* - Multiple words: first letter of first and last word (e.g., "John Doe" -> "JD")
|
|
78
|
+
* - Username starting with @: first two letters after @ (e.g., "@johndoe" -> "JO")
|
|
79
|
+
*/
|
|
80
|
+
const getInitials = (nameStr: string): string => {
|
|
81
|
+
if (!nameStr?.trim()) return '';
|
|
82
|
+
|
|
83
|
+
const trimmed = nameStr.trim();
|
|
84
|
+
const parts = trimmed.split(/\s+/).filter(part => part.length > 0);
|
|
85
|
+
|
|
86
|
+
if (parts.length === 0) return '';
|
|
87
|
+
|
|
88
|
+
if (parts.length === 1) {
|
|
89
|
+
const firstPart = parts[0];
|
|
90
|
+
// Handle username format (@username)
|
|
91
|
+
if (firstPart.length >= 2 && firstPart.startsWith('@')) {
|
|
92
|
+
return firstPart.substring(1, 3).toUpperCase();
|
|
93
|
+
}
|
|
94
|
+
return firstPart.charAt(0).toUpperCase();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Multiple words: first letter of first and last word
|
|
98
|
+
return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Generates a consistent color from a string (name)
|
|
103
|
+
* Uses a simple hash function to create a deterministic color
|
|
104
|
+
*/
|
|
105
|
+
const generateColorFromString = (str: string): string => {
|
|
106
|
+
let hash = 0;
|
|
107
|
+
for (let i = 0; i < str.length; i++) {
|
|
108
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Generate a vibrant color with good contrast
|
|
112
|
+
const hue = Math.abs(hash) % 360;
|
|
113
|
+
// Use high saturation and medium lightness for vibrant, readable colors
|
|
114
|
+
return `hsl(${hue}, 65%, 55%)`;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Calculates font size based on avatar size for letter avatars
|
|
119
|
+
* Uses a larger ratio for better visibility
|
|
120
|
+
*/
|
|
121
|
+
const calculateFontSize = (size: number): number => {
|
|
122
|
+
// Use larger font size for letter avatars (50-55% of size)
|
|
123
|
+
return size <= 40
|
|
124
|
+
? Math.floor(size * 0.5)
|
|
125
|
+
: Math.floor(size * 0.45);
|
|
126
|
+
};
|
|
127
|
+
|
|
70
128
|
/**
|
|
71
129
|
* Avatar component that displays either an image or text avatar
|
|
72
|
-
* Falls back to displaying
|
|
130
|
+
* Falls back to displaying initials (1-2 letters) derived from the name if no image is provided
|
|
131
|
+
* Supports flexible sizing via the size prop (default: 40px)
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* <Avatar name="John Doe" size={100} theme="light" />
|
|
135
|
+
* <Avatar uri="https://example.com/avatar.jpg" size={50} />
|
|
73
136
|
*/
|
|
74
137
|
const Avatar: React.FC<AvatarProps> = ({
|
|
75
138
|
uri,
|
|
@@ -84,58 +147,111 @@ const Avatar: React.FC<AvatarProps> = ({
|
|
|
84
147
|
textStyle,
|
|
85
148
|
isLoading = false,
|
|
86
149
|
}) => {
|
|
87
|
-
// Get theme colors
|
|
88
150
|
const colors = useThemeColors(theme);
|
|
89
151
|
|
|
90
|
-
|
|
91
|
-
|
|
152
|
+
const displayText = useMemo(
|
|
153
|
+
() => text || (name ? getInitials(name) : ''),
|
|
154
|
+
[text, name]
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Generate a color from the name for letter avatars (only when no backgroundColor is provided and no uri)
|
|
158
|
+
const generatedBgColor = useMemo(
|
|
159
|
+
() => {
|
|
160
|
+
if (backgroundColor) return backgroundColor;
|
|
161
|
+
if (uri) return colors.primary; // Use primary for image avatars as fallback
|
|
162
|
+
// Generate color from name for letter avatars
|
|
163
|
+
const nameForColor = name || text || 'User';
|
|
164
|
+
return generateColorFromString(nameForColor);
|
|
165
|
+
},
|
|
166
|
+
[backgroundColor, uri, name, text, colors.primary]
|
|
167
|
+
);
|
|
92
168
|
|
|
93
|
-
//
|
|
94
|
-
const
|
|
169
|
+
// Memoize computed values to avoid recalculation on every render
|
|
170
|
+
const bgColor = useMemo(
|
|
171
|
+
() => generatedBgColor,
|
|
172
|
+
[generatedBgColor]
|
|
173
|
+
);
|
|
95
174
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
175
|
+
const fontSize = useMemo(
|
|
176
|
+
() => calculateFontSize(size),
|
|
177
|
+
[size]
|
|
178
|
+
);
|
|
99
179
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
180
|
+
const containerStyle = useMemo(
|
|
181
|
+
() => ({
|
|
182
|
+
width: size,
|
|
183
|
+
height: size,
|
|
184
|
+
borderRadius: size / 2,
|
|
185
|
+
}),
|
|
186
|
+
[size]
|
|
187
|
+
);
|
|
106
188
|
|
|
189
|
+
const textStyleComputed = useMemo(
|
|
190
|
+
() => [
|
|
191
|
+
styles.text,
|
|
192
|
+
{
|
|
193
|
+
fontSize,
|
|
194
|
+
fontFamily: fontFamilies.phuduBold,
|
|
195
|
+
color: textColor,
|
|
196
|
+
textAlign: 'center' as const,
|
|
197
|
+
lineHeight: size, // Match line height to size for perfect vertical centering
|
|
198
|
+
...(Platform.OS === 'android' && { includeFontPadding: false }), // Remove extra padding for better centering (Android only)
|
|
199
|
+
},
|
|
200
|
+
textStyle,
|
|
201
|
+
],
|
|
202
|
+
[fontSize, textColor, textStyle, size]
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Early return for loading state
|
|
107
206
|
if (isLoading) {
|
|
108
207
|
return (
|
|
109
|
-
<View
|
|
110
|
-
|
|
208
|
+
<View
|
|
209
|
+
style={[
|
|
210
|
+
styles.container,
|
|
211
|
+
containerStyle,
|
|
212
|
+
{ backgroundColor: colors.inputBackground },
|
|
213
|
+
style
|
|
214
|
+
]}
|
|
215
|
+
>
|
|
216
|
+
<ActivityIndicator
|
|
217
|
+
color={colors.primary}
|
|
218
|
+
size={size > 50 ? 'large' : 'small'}
|
|
219
|
+
/>
|
|
111
220
|
</View>
|
|
112
221
|
);
|
|
113
222
|
}
|
|
114
223
|
|
|
115
|
-
//
|
|
224
|
+
// Image avatar
|
|
116
225
|
if (uri) {
|
|
117
226
|
return (
|
|
118
|
-
<View
|
|
227
|
+
<View
|
|
228
|
+
style={[
|
|
229
|
+
styles.container,
|
|
230
|
+
containerStyle,
|
|
231
|
+
{ backgroundColor: bgColor },
|
|
232
|
+
style
|
|
233
|
+
]}
|
|
234
|
+
>
|
|
119
235
|
<Image
|
|
120
|
-
source={{ uri
|
|
236
|
+
source={{ uri }}
|
|
121
237
|
style={[styles.image, containerStyle, imageStyle]}
|
|
238
|
+
resizeMode="cover"
|
|
122
239
|
/>
|
|
123
240
|
</View>
|
|
124
241
|
);
|
|
125
242
|
}
|
|
126
243
|
|
|
127
|
-
//
|
|
244
|
+
// Text avatar with initials
|
|
128
245
|
return (
|
|
129
|
-
<View
|
|
130
|
-
|
|
131
|
-
styles.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
]}>
|
|
246
|
+
<View
|
|
247
|
+
style={[
|
|
248
|
+
styles.container,
|
|
249
|
+
containerStyle,
|
|
250
|
+
{ backgroundColor: bgColor },
|
|
251
|
+
style
|
|
252
|
+
]}
|
|
253
|
+
>
|
|
254
|
+
<Text style={textStyleComputed}>
|
|
139
255
|
{displayText}
|
|
140
256
|
</Text>
|
|
141
257
|
</View>
|
|
@@ -153,9 +269,9 @@ const styles = StyleSheet.create({
|
|
|
153
269
|
height: '100%',
|
|
154
270
|
},
|
|
155
271
|
text: {
|
|
156
|
-
|
|
157
|
-
fontWeight: Platform.OS === 'web' ? 'bold' : undefined, // Only apply fontWeight on web
|
|
272
|
+
fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
|
|
158
273
|
},
|
|
159
274
|
});
|
|
160
275
|
|
|
161
|
-
|
|
276
|
+
// Memoize component to prevent unnecessary re-renders when props haven't changed
|
|
277
|
+
export default memo(Avatar);
|
|
@@ -267,10 +267,12 @@ const TextField = forwardRef<TextInput, TextFieldProps>(({
|
|
|
267
267
|
: surfaceScale(0.62);
|
|
268
268
|
|
|
269
269
|
// Helper function to clone React elements with updated color
|
|
270
|
+
// React 19: ref is now a regular prop, accessed via element.props.ref
|
|
270
271
|
const cloneWithColor = (element: React.ReactNode, color: string): React.ReactNode => {
|
|
271
272
|
if (React.isValidElement(element) && element.type) {
|
|
272
|
-
//
|
|
273
|
-
|
|
273
|
+
// React 19: ref is now in props, so we spread all props and override color
|
|
274
|
+
const elementProps = (element as any).props || {};
|
|
275
|
+
return React.cloneElement(element as any, { ...elementProps, color });
|
|
274
276
|
}
|
|
275
277
|
return element;
|
|
276
278
|
};
|
|
@@ -737,20 +739,19 @@ const TextField = forwardRef<TextInput, TextFieldProps>(({
|
|
|
737
739
|
{/* Underline for filled/standard variants */}
|
|
738
740
|
{(variant === 'filled' || variant === 'standard') && (
|
|
739
741
|
<>
|
|
740
|
-
<View style={styles.underline
|
|
742
|
+
<View style={[styles.underline, { pointerEvents: 'none' }]} />
|
|
741
743
|
<Animated.View
|
|
742
744
|
style={[
|
|
743
745
|
styles.underlineFocused,
|
|
744
|
-
{ transform: [{ scaleX: focusAnimation }] }
|
|
746
|
+
{ transform: [{ scaleX: focusAnimation }], pointerEvents: 'none' }
|
|
745
747
|
]}
|
|
746
|
-
pointerEvents="none"
|
|
747
748
|
/>
|
|
748
749
|
</>
|
|
749
750
|
)}
|
|
750
751
|
|
|
751
752
|
{/* Label */}
|
|
752
753
|
{label && (
|
|
753
|
-
<View style={styles.labelContainer
|
|
754
|
+
<View style={[styles.labelContainer, { pointerEvents: 'none' }]}>
|
|
754
755
|
<Animated.Text
|
|
755
756
|
style={[
|
|
756
757
|
styles.label,
|