@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.
Files changed (204) hide show
  1. package/README.md +71 -0
  2. package/lib/commonjs/core/HttpClient.js +238 -0
  3. package/lib/commonjs/core/HttpClient.js.map +1 -0
  4. package/lib/commonjs/core/OxyServices.js +538 -332
  5. package/lib/commonjs/core/OxyServices.js.map +1 -1
  6. package/lib/commonjs/core/RequestManager.js +199 -0
  7. package/lib/commonjs/core/RequestManager.js.map +1 -0
  8. package/lib/commonjs/core/index.js +38 -1
  9. package/lib/commonjs/core/index.js.map +1 -1
  10. package/lib/commonjs/index.js +36 -0
  11. package/lib/commonjs/index.js.map +1 -1
  12. package/lib/commonjs/ui/components/Avatar.js +94 -27
  13. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  14. package/lib/commonjs/ui/components/FollowButton.js +1 -0
  15. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  16. package/lib/commonjs/ui/components/internal/TextField.js +13 -8
  17. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  18. package/lib/commonjs/ui/context/OxyContext.js +183 -224
  19. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  20. package/lib/commonjs/ui/hooks/useSessionSocket.js +80 -22
  21. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  22. package/lib/commonjs/ui/index.js +4 -1
  23. package/lib/commonjs/ui/index.js.map +1 -1
  24. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +32 -2
  25. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  26. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +101 -59
  27. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  28. package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -2
  29. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  30. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +75 -117
  31. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
  32. package/lib/commonjs/ui/screens/SignInScreen.js +0 -11
  33. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  34. package/lib/commonjs/ui/screens/SignUpScreen.js +14 -16
  35. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  36. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +50 -18
  37. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  38. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +10 -10
  39. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  40. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +16 -26
  41. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  42. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +104 -212
  43. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  44. package/lib/commonjs/ui/stores/accountStore.js +237 -0
  45. package/lib/commonjs/ui/stores/accountStore.js.map +1 -0
  46. package/lib/commonjs/ui/stores/authStore.js +2 -1
  47. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  48. package/lib/commonjs/ui/styles/authStyles.js +14 -7
  49. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  50. package/lib/commonjs/utils/asyncUtils.js +9 -22
  51. package/lib/commonjs/utils/asyncUtils.js.map +1 -1
  52. package/lib/commonjs/utils/cache.js +259 -0
  53. package/lib/commonjs/utils/cache.js.map +1 -0
  54. package/lib/commonjs/utils/index.js +99 -0
  55. package/lib/commonjs/utils/index.js.map +1 -1
  56. package/lib/commonjs/utils/languageUtils.js +159 -0
  57. package/lib/commonjs/utils/languageUtils.js.map +1 -0
  58. package/lib/commonjs/utils/requestUtils.js +217 -0
  59. package/lib/commonjs/utils/requestUtils.js.map +1 -0
  60. package/lib/commonjs/utils/sessionUtils.js +191 -0
  61. package/lib/commonjs/utils/sessionUtils.js.map +1 -0
  62. package/lib/module/core/HttpClient.js +232 -0
  63. package/lib/module/core/HttpClient.js.map +1 -0
  64. package/lib/module/core/OxyServices.js +536 -326
  65. package/lib/module/core/OxyServices.js.map +1 -1
  66. package/lib/module/core/RequestManager.js +194 -0
  67. package/lib/module/core/RequestManager.js.map +1 -0
  68. package/lib/module/core/index.js +2 -0
  69. package/lib/module/core/index.js.map +1 -1
  70. package/lib/module/index.js +2 -0
  71. package/lib/module/index.js.map +1 -1
  72. package/lib/module/ui/components/Avatar.js +94 -27
  73. package/lib/module/ui/components/Avatar.js.map +1 -1
  74. package/lib/module/ui/components/FollowButton.js +1 -0
  75. package/lib/module/ui/components/FollowButton.js.map +1 -1
  76. package/lib/module/ui/components/internal/TextField.js +13 -8
  77. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  78. package/lib/module/ui/context/OxyContext.js +182 -223
  79. package/lib/module/ui/context/OxyContext.js.map +1 -1
  80. package/lib/module/ui/hooks/useSessionSocket.js +80 -22
  81. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  82. package/lib/module/ui/index.js +4 -2
  83. package/lib/module/ui/index.js.map +1 -1
  84. package/lib/module/ui/screens/AccountSettingsScreen.js +33 -2
  85. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  86. package/lib/module/ui/screens/AccountSwitcherScreen.js +102 -60
  87. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  88. package/lib/module/ui/screens/FileManagementScreen.js +3 -2
  89. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  90. package/lib/module/ui/screens/LanguageSelectorScreen.js +73 -117
  91. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
  92. package/lib/module/ui/screens/SignInScreen.js +0 -11
  93. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  94. package/lib/module/ui/screens/SignUpScreen.js +14 -16
  95. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  96. package/lib/module/ui/screens/WelcomeNewUserScreen.js +50 -18
  97. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  98. package/lib/module/ui/screens/internal/SignInPasswordStep.js +10 -10
  99. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  100. package/lib/module/ui/screens/steps/SignInPasswordStep.js +16 -26
  101. package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  102. package/lib/module/ui/screens/steps/SignInUsernameStep.js +105 -214
  103. package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  104. package/lib/module/ui/stores/accountStore.js +229 -0
  105. package/lib/module/ui/stores/accountStore.js.map +1 -0
  106. package/lib/module/ui/stores/authStore.js +2 -1
  107. package/lib/module/ui/stores/authStore.js.map +1 -1
  108. package/lib/module/ui/styles/authStyles.js +14 -7
  109. package/lib/module/ui/styles/authStyles.js.map +1 -1
  110. package/lib/module/utils/asyncUtils.js +10 -22
  111. package/lib/module/utils/asyncUtils.js.map +1 -1
  112. package/lib/module/utils/cache.js +250 -0
  113. package/lib/module/utils/cache.js.map +1 -0
  114. package/lib/module/utils/index.js +7 -0
  115. package/lib/module/utils/index.js.map +1 -1
  116. package/lib/module/utils/languageUtils.js +151 -0
  117. package/lib/module/utils/languageUtils.js.map +1 -0
  118. package/lib/module/utils/requestUtils.js +210 -0
  119. package/lib/module/utils/requestUtils.js.map +1 -0
  120. package/lib/module/utils/sessionUtils.js +180 -0
  121. package/lib/module/utils/sessionUtils.js.map +1 -0
  122. package/lib/typescript/core/HttpClient.d.ts +64 -0
  123. package/lib/typescript/core/HttpClient.d.ts.map +1 -0
  124. package/lib/typescript/core/OxyServices.d.ts +88 -71
  125. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  126. package/lib/typescript/core/RequestManager.d.ts +67 -0
  127. package/lib/typescript/core/RequestManager.d.ts.map +1 -0
  128. package/lib/typescript/core/index.d.ts +2 -0
  129. package/lib/typescript/core/index.d.ts.map +1 -1
  130. package/lib/typescript/index.d.ts +2 -0
  131. package/lib/typescript/index.d.ts.map +1 -1
  132. package/lib/typescript/models/interfaces.d.ts +15 -0
  133. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  134. package/lib/typescript/models/session.d.ts +1 -0
  135. package/lib/typescript/models/session.d.ts.map +1 -1
  136. package/lib/typescript/ui/components/Avatar.d.ts +6 -7
  137. package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
  138. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  139. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  140. package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
  141. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  142. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  143. package/lib/typescript/ui/index.d.ts +2 -2
  144. package/lib/typescript/ui/index.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  146. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +3 -3
  148. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
  149. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
  154. package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
  155. package/lib/typescript/ui/stores/accountStore.d.ts +34 -0
  156. package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -0
  157. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  158. package/lib/typescript/ui/styles/authStyles.d.ts +18 -2
  159. package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
  160. package/lib/typescript/utils/asyncUtils.d.ts +2 -0
  161. package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
  162. package/lib/typescript/utils/cache.d.ts +128 -0
  163. package/lib/typescript/utils/cache.d.ts.map +1 -0
  164. package/lib/typescript/utils/index.d.ts +4 -0
  165. package/lib/typescript/utils/index.d.ts.map +1 -1
  166. package/lib/typescript/utils/languageUtils.d.ts +38 -0
  167. package/lib/typescript/utils/languageUtils.d.ts.map +1 -0
  168. package/lib/typescript/utils/requestUtils.d.ts +122 -0
  169. package/lib/typescript/utils/requestUtils.d.ts.map +1 -0
  170. package/lib/typescript/utils/sessionUtils.d.ts +55 -0
  171. package/lib/typescript/utils/sessionUtils.d.ts.map +1 -0
  172. package/package.json +1 -1
  173. package/src/core/HttpClient.ts +277 -0
  174. package/src/core/OxyServices.ts +466 -351
  175. package/src/core/RequestManager.ts +240 -0
  176. package/src/core/index.ts +10 -0
  177. package/src/index.ts +10 -0
  178. package/src/models/interfaces.ts +19 -0
  179. package/src/models/session.ts +1 -1
  180. package/src/ui/components/Avatar.tsx +151 -35
  181. package/src/ui/components/FollowButton.tsx +1 -0
  182. package/src/ui/components/internal/TextField.tsx +7 -6
  183. package/src/ui/context/OxyContext.tsx +213 -217
  184. package/src/ui/hooks/useSessionSocket.ts +72 -18
  185. package/src/ui/index.ts +4 -1
  186. package/src/ui/screens/AccountSettingsScreen.tsx +34 -2
  187. package/src/ui/screens/AccountSwitcherScreen.tsx +102 -68
  188. package/src/ui/screens/FileManagementScreen.tsx +1 -1
  189. package/src/ui/screens/LanguageSelectorScreen.tsx +86 -143
  190. package/src/ui/screens/SignInScreen.tsx +0 -7
  191. package/src/ui/screens/SignUpScreen.tsx +14 -15
  192. package/src/ui/screens/WelcomeNewUserScreen.tsx +52 -15
  193. package/src/ui/screens/internal/SignInPasswordStep.tsx +4 -6
  194. package/src/ui/screens/steps/SignInPasswordStep.tsx +4 -8
  195. package/src/ui/screens/steps/SignInUsernameStep.tsx +110 -256
  196. package/src/ui/stores/accountStore.ts +285 -0
  197. package/src/ui/stores/authStore.ts +2 -1
  198. package/src/ui/styles/authStyles.ts +14 -7
  199. package/src/utils/asyncUtils.ts +10 -24
  200. package/src/utils/cache.ts +264 -0
  201. package/src/utils/index.ts +19 -0
  202. package/src/utils/languageUtils.ts +174 -0
  203. package/src/utils/requestUtils.ts +234 -0
  204. 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,
@@ -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 {
@@ -3,8 +3,8 @@ export interface ClientSession {
3
3
  deviceId: string;
4
4
  expiresAt: string;
5
5
  lastActive: string;
6
- // Only userId for identification, do not store username
7
6
  userId?: string;
7
+ isCurrent?: boolean;
8
8
  }
9
9
 
10
10
  export interface StorageKeys {
@@ -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 (takes first letter)
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 the first letter of the name if no image is provided
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
- // Use the primary color from theme as default background if not specified
91
- const bgColor = backgroundColor || colors.primary;
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
- // Calculate font size based on avatar size
94
- const fontSize = Math.floor(size * 0.4);
169
+ // Memoize computed values to avoid recalculation on every render
170
+ const bgColor = useMemo(
171
+ () => generatedBgColor,
172
+ [generatedBgColor]
173
+ );
95
174
 
96
- // Determine what text to display for fallback
97
- const displayText = text ||
98
- (name ? name.charAt(0).toUpperCase() : '');
175
+ const fontSize = useMemo(
176
+ () => calculateFontSize(size),
177
+ [size]
178
+ );
99
179
 
100
- // Style for container based on size
101
- const containerStyle = {
102
- width: size,
103
- height: size,
104
- borderRadius: size / 2, // Make it circular
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 style={[styles.container, containerStyle, { backgroundColor: colors.inputBackground }, style]}>
110
- <ActivityIndicator color={colors.primary} size={size > 50 ? 'large' : 'small'} />
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
- // If an image URL is provided, use Image component with optional background
224
+ // Image avatar
116
225
  if (uri) {
117
226
  return (
118
- <View style={[styles.container, containerStyle, { backgroundColor: bgColor }, style]}>
227
+ <View
228
+ style={[
229
+ styles.container,
230
+ containerStyle,
231
+ { backgroundColor: bgColor },
232
+ style
233
+ ]}
234
+ >
119
235
  <Image
120
- source={{ uri: 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
- // Otherwise show text avatar
244
+ // Text avatar with initials
128
245
  return (
129
- <View style={[styles.container, containerStyle, { backgroundColor: bgColor }, style]}>
130
- <Text style={[
131
- styles.text,
132
- {
133
- fontSize,
134
- fontFamily: fontFamilies.phuduBold,
135
- },
136
- { color: textColor },
137
- textStyle
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
- // Font family is applied directly in the component to use the constants
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
- export default Avatar;
276
+ // Memoize component to prevent unnecessary re-renders when props haven't changed
277
+ export default memo(Avatar);
@@ -153,6 +153,7 @@ const FollowButton: React.FC<FollowButtonProps> = ({
153
153
  boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
154
154
  },
155
155
  default: {
156
+ shadowColor: '#000',
156
157
  shadowOffset: { width: 0, height: 2 },
157
158
  shadowOpacity: 0.1,
158
159
  shadowRadius: 4,
@@ -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
- // Avoid spreading props directly to satisfy TS complaining about non-object spread sources
273
- return React.cloneElement(element as any, { color });
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} pointerEvents="none" />
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} pointerEvents="none">
754
+ <View style={[styles.labelContainer, { pointerEvents: 'none' }]}>
754
755
  <Animated.Text
755
756
  style={[
756
757
  styles.label,