@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,285 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { shallow } from 'zustand/shallow';
|
|
3
|
+
import type { OxyServices } from '../../core';
|
|
4
|
+
|
|
5
|
+
export interface QuickAccount {
|
|
6
|
+
sessionId: string;
|
|
7
|
+
userId?: string; // User ID for deduplication
|
|
8
|
+
username: string;
|
|
9
|
+
displayName: string;
|
|
10
|
+
avatar?: string;
|
|
11
|
+
avatarUrl?: string; // Cached avatar URL to prevent recalculation
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface AccountState {
|
|
15
|
+
// Account data
|
|
16
|
+
accounts: Record<string, QuickAccount>;
|
|
17
|
+
accountOrder: string[]; // Maintain order for display
|
|
18
|
+
accountsArray: QuickAccount[]; // Cached array to prevent infinite loops
|
|
19
|
+
|
|
20
|
+
// Loading states
|
|
21
|
+
loading: boolean;
|
|
22
|
+
loadingSessionIds: Set<string>;
|
|
23
|
+
|
|
24
|
+
// Error state
|
|
25
|
+
error: string | null;
|
|
26
|
+
|
|
27
|
+
// Actions
|
|
28
|
+
setAccounts: (accounts: QuickAccount[]) => void;
|
|
29
|
+
addAccount: (account: QuickAccount) => void;
|
|
30
|
+
updateAccount: (sessionId: string, updates: Partial<QuickAccount>) => void;
|
|
31
|
+
removeAccount: (sessionId: string) => void;
|
|
32
|
+
moveAccountToTop: (sessionId: string) => void;
|
|
33
|
+
|
|
34
|
+
// Loading actions
|
|
35
|
+
setLoading: (loading: boolean) => void;
|
|
36
|
+
setLoadingSession: (sessionId: string, loading: boolean) => void;
|
|
37
|
+
|
|
38
|
+
// Error actions
|
|
39
|
+
setError: (error: string | null) => void;
|
|
40
|
+
|
|
41
|
+
// Load accounts from API
|
|
42
|
+
loadAccounts: (sessionIds: string[], oxyServices: OxyServices, existingAccounts?: QuickAccount[], preserveOrder?: boolean) => Promise<void>;
|
|
43
|
+
|
|
44
|
+
// Reset store
|
|
45
|
+
reset: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const initialState = {
|
|
49
|
+
accounts: {} as Record<string, QuickAccount>,
|
|
50
|
+
accountOrder: [] as string[],
|
|
51
|
+
accountsArray: [] as QuickAccount[],
|
|
52
|
+
loading: false,
|
|
53
|
+
loadingSessionIds: new Set<string>(),
|
|
54
|
+
error: null,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Helper: Build accounts array from accounts map and order
|
|
58
|
+
const buildAccountsArray = (accounts: Record<string, QuickAccount>, order: string[]): QuickAccount[] => {
|
|
59
|
+
const result: QuickAccount[] = [];
|
|
60
|
+
for (const id of order) {
|
|
61
|
+
const account = accounts[id];
|
|
62
|
+
if (account) result.push(account);
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Helper: Create QuickAccount from user data
|
|
68
|
+
const createQuickAccount = (sessionId: string, userData: any, existingAccount?: QuickAccount, oxyServices?: OxyServices): QuickAccount => {
|
|
69
|
+
const displayName = userData.name?.full || userData.name?.first || userData.username || 'Account';
|
|
70
|
+
const userId = userData.id || userData._id?.toString();
|
|
71
|
+
|
|
72
|
+
// Preserve existing avatarUrl if avatar hasn't changed (prevents image reload)
|
|
73
|
+
let avatarUrl: string | undefined;
|
|
74
|
+
if (existingAccount && existingAccount.avatar === userData.avatar && existingAccount.avatarUrl) {
|
|
75
|
+
avatarUrl = existingAccount.avatarUrl; // Reuse existing URL
|
|
76
|
+
} else if (userData.avatar && oxyServices) {
|
|
77
|
+
avatarUrl = oxyServices.getFileDownloadUrl(userData.avatar, 'thumb');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
sessionId,
|
|
82
|
+
userId,
|
|
83
|
+
username: userData.username || '',
|
|
84
|
+
displayName,
|
|
85
|
+
avatar: userData.avatar,
|
|
86
|
+
avatarUrl,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const useAccountStore = create<AccountState>((set, get) => ({
|
|
91
|
+
...initialState,
|
|
92
|
+
|
|
93
|
+
setAccounts: (accounts) => set((state) => {
|
|
94
|
+
const accountMap: Record<string, QuickAccount> = {};
|
|
95
|
+
const order: string[] = [];
|
|
96
|
+
const seenSessionIds = new Set<string>();
|
|
97
|
+
|
|
98
|
+
for (const account of accounts) {
|
|
99
|
+
if (seenSessionIds.has(account.sessionId)) continue;
|
|
100
|
+
seenSessionIds.add(account.sessionId);
|
|
101
|
+
accountMap[account.sessionId] = account;
|
|
102
|
+
order.push(account.sessionId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const accountsArray = buildAccountsArray(accountMap, order);
|
|
106
|
+
const sameOrder = order.length === state.accountOrder.length &&
|
|
107
|
+
order.every((id, i) => id === state.accountOrder[i]);
|
|
108
|
+
const sameAccounts = sameOrder &&
|
|
109
|
+
order.every(id => {
|
|
110
|
+
const existing = state.accounts[id];
|
|
111
|
+
const newAccount = accountMap[id];
|
|
112
|
+
return existing &&
|
|
113
|
+
existing.sessionId === newAccount.sessionId &&
|
|
114
|
+
existing.userId === newAccount.userId &&
|
|
115
|
+
existing.avatar === newAccount.avatar &&
|
|
116
|
+
existing.avatarUrl === newAccount.avatarUrl;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (sameAccounts) return {} as any;
|
|
120
|
+
|
|
121
|
+
return { accounts: accountMap, accountOrder: order, accountsArray };
|
|
122
|
+
}),
|
|
123
|
+
|
|
124
|
+
addAccount: (account) => set((state) => {
|
|
125
|
+
// Check if account with same sessionId exists
|
|
126
|
+
if (state.accounts[account.sessionId]) {
|
|
127
|
+
// Update existing
|
|
128
|
+
const existing = state.accounts[account.sessionId];
|
|
129
|
+
if (existing.avatar === account.avatar && existing.avatarUrl === account.avatarUrl) {
|
|
130
|
+
return {} as any; // No change
|
|
131
|
+
}
|
|
132
|
+
const newAccounts = { ...state.accounts, [account.sessionId]: account };
|
|
133
|
+
return {
|
|
134
|
+
accounts: newAccounts,
|
|
135
|
+
accountsArray: buildAccountsArray(newAccounts, state.accountOrder),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const newAccounts = { ...state.accounts, [account.sessionId]: account };
|
|
140
|
+
const newOrder = [account.sessionId, ...state.accountOrder];
|
|
141
|
+
return {
|
|
142
|
+
accounts: newAccounts,
|
|
143
|
+
accountOrder: newOrder,
|
|
144
|
+
accountsArray: buildAccountsArray(newAccounts, newOrder),
|
|
145
|
+
};
|
|
146
|
+
}),
|
|
147
|
+
|
|
148
|
+
updateAccount: (sessionId, updates) => set((state) => {
|
|
149
|
+
const existing = state.accounts[sessionId];
|
|
150
|
+
if (!existing) return {} as any;
|
|
151
|
+
|
|
152
|
+
const updated = { ...existing, ...updates };
|
|
153
|
+
if (existing.avatar === updated.avatar && existing.avatarUrl === updated.avatarUrl) {
|
|
154
|
+
return {} as any; // No change
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const newAccounts = { ...state.accounts, [sessionId]: updated };
|
|
158
|
+
return {
|
|
159
|
+
accounts: newAccounts,
|
|
160
|
+
accountsArray: buildAccountsArray(newAccounts, state.accountOrder),
|
|
161
|
+
};
|
|
162
|
+
}),
|
|
163
|
+
|
|
164
|
+
removeAccount: (sessionId) => set((state) => {
|
|
165
|
+
if (!state.accounts[sessionId]) return {} as any;
|
|
166
|
+
|
|
167
|
+
const { [sessionId]: _removed, ...rest } = state.accounts;
|
|
168
|
+
const newOrder = state.accountOrder.filter(id => id !== sessionId);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
accounts: rest,
|
|
172
|
+
accountOrder: newOrder,
|
|
173
|
+
accountsArray: buildAccountsArray(rest, newOrder),
|
|
174
|
+
};
|
|
175
|
+
}),
|
|
176
|
+
|
|
177
|
+
moveAccountToTop: (sessionId) => set((state) => {
|
|
178
|
+
if (!state.accounts[sessionId]) return {} as any;
|
|
179
|
+
|
|
180
|
+
const filtered = state.accountOrder.filter(id => id !== sessionId);
|
|
181
|
+
const newOrder = [sessionId, ...filtered];
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
accountOrder: newOrder,
|
|
185
|
+
accountsArray: buildAccountsArray(state.accounts, newOrder),
|
|
186
|
+
};
|
|
187
|
+
}),
|
|
188
|
+
|
|
189
|
+
setLoading: (loading) => set({ loading }),
|
|
190
|
+
|
|
191
|
+
setLoadingSession: (sessionId, loading) => set((state) => {
|
|
192
|
+
const newSet = new Set(state.loadingSessionIds);
|
|
193
|
+
if (loading) {
|
|
194
|
+
newSet.add(sessionId);
|
|
195
|
+
} else {
|
|
196
|
+
newSet.delete(sessionId);
|
|
197
|
+
}
|
|
198
|
+
return { loadingSessionIds: newSet };
|
|
199
|
+
}),
|
|
200
|
+
|
|
201
|
+
setError: (error) => set({ error }),
|
|
202
|
+
|
|
203
|
+
loadAccounts: async (sessionIds, oxyServices, existingAccounts = [], preserveOrder = true) => {
|
|
204
|
+
const state = get();
|
|
205
|
+
|
|
206
|
+
const uniqueSessionIds = Array.from(new Set(sessionIds));
|
|
207
|
+
if (uniqueSessionIds.length === 0) {
|
|
208
|
+
get().setAccounts([]);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const existingMap = new Map(existingAccounts.map(a => [a.sessionId, a]));
|
|
213
|
+
for (const account of Object.values(state.accounts)) {
|
|
214
|
+
existingMap.set(account.sessionId, account);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const missingSessionIds = uniqueSessionIds.filter(id => !existingMap.has(id));
|
|
218
|
+
|
|
219
|
+
if (missingSessionIds.length === 0) {
|
|
220
|
+
const ordered = uniqueSessionIds
|
|
221
|
+
.map(id => existingMap.get(id))
|
|
222
|
+
.filter((acc): acc is QuickAccount => acc !== undefined);
|
|
223
|
+
get().setAccounts(ordered);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (state.loading) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
set({ loading: true, error: null });
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const batchResults = await oxyServices.getUsersBySessions(missingSessionIds);
|
|
235
|
+
|
|
236
|
+
const accountMap = new Map<string, QuickAccount>();
|
|
237
|
+
|
|
238
|
+
for (const { sessionId, user: userData } of batchResults) {
|
|
239
|
+
if (userData && !accountMap.has(sessionId)) {
|
|
240
|
+
const existing = existingMap.get(sessionId);
|
|
241
|
+
accountMap.set(sessionId, createQuickAccount(sessionId, userData, existing, oxyServices));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for (const [sessionId, account] of accountMap) {
|
|
246
|
+
existingMap.set(sessionId, account);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const orderToUse = preserveOrder ? uniqueSessionIds : [...uniqueSessionIds, ...state.accountOrder];
|
|
250
|
+
const seen = new Set<string>();
|
|
251
|
+
const ordered: QuickAccount[] = [];
|
|
252
|
+
|
|
253
|
+
for (const sessionId of orderToUse) {
|
|
254
|
+
if (seen.has(sessionId)) continue;
|
|
255
|
+
seen.add(sessionId);
|
|
256
|
+
|
|
257
|
+
const account = existingMap.get(sessionId);
|
|
258
|
+
if (account) ordered.push(account);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
get().setAccounts(ordered);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to load accounts';
|
|
264
|
+
if (__DEV__) {
|
|
265
|
+
console.error('AccountStore: Failed to load accounts:', error);
|
|
266
|
+
}
|
|
267
|
+
set({ error: errorMessage });
|
|
268
|
+
} finally {
|
|
269
|
+
set({ loading: false });
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
reset: () => set(initialState),
|
|
274
|
+
}));
|
|
275
|
+
|
|
276
|
+
// Selectors for performance - return cached array to prevent infinite loops
|
|
277
|
+
export const useAccounts = (): QuickAccount[] => {
|
|
278
|
+
return useAccountStore(state => state.accountsArray);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export const useAccountLoading = () => useAccountStore(s => s.loading);
|
|
282
|
+
export const useAccountError = () => useAccountStore(s => s.error);
|
|
283
|
+
export const useAccountLoadingSession = (sessionId: string) =>
|
|
284
|
+
useAccountStore(s => s.loadingSessionIds.has(sessionId));
|
|
285
|
+
|
|
@@ -56,7 +56,8 @@ export const useAuthStore = create<AuthState>((set: (state: Partial<AuthState>)
|
|
|
56
56
|
try {
|
|
57
57
|
await oxyServices.updateProfile(updates);
|
|
58
58
|
// Immediately fetch the latest user data after update
|
|
59
|
-
|
|
59
|
+
// Use arrow function to preserve 'this' context
|
|
60
|
+
await useAuthStore.getState().fetchUser({ getCurrentUser: () => oxyServices.getCurrentUser() }, true);
|
|
60
61
|
} catch (error) {
|
|
61
62
|
const errorMessage = error instanceof Error ? error.message : 'Failed to update user';
|
|
62
63
|
if (__DEV__) {
|
|
@@ -191,13 +191,20 @@ export const createAuthStyles = (colors: AuthThemeColors, theme: string) => Styl
|
|
|
191
191
|
paddingHorizontal: 32,
|
|
192
192
|
borderRadius: 16,
|
|
193
193
|
marginVertical: 8,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
194
|
+
...Platform.select({
|
|
195
|
+
web: {
|
|
196
|
+
boxShadow: '0 4px 8px rgba(0,0,0,0.3)',
|
|
197
|
+
},
|
|
198
|
+
default: {
|
|
199
|
+
shadowOffset: {
|
|
200
|
+
width: 0,
|
|
201
|
+
height: 4,
|
|
202
|
+
},
|
|
203
|
+
shadowOpacity: 0.3,
|
|
204
|
+
shadowRadius: 8,
|
|
205
|
+
elevation: 6,
|
|
206
|
+
}
|
|
207
|
+
}),
|
|
201
208
|
gap: 8,
|
|
202
209
|
width: '100%',
|
|
203
210
|
},
|
package/src/utils/asyncUtils.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Async utilities for common asynchronous patterns and error handling
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { TTLCache, registerCacheForCleanup } from './cache';
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Wrapper for async operations with automatic error handling
|
|
7
9
|
*/
|
|
@@ -196,36 +198,20 @@ export async function withTimeout<T>(
|
|
|
196
198
|
|
|
197
199
|
/**
|
|
198
200
|
* Cache async operation results
|
|
201
|
+
* @deprecated Use TTLCache from '../utils/cache' instead
|
|
202
|
+
* This function is kept for backward compatibility but will be removed in a future version
|
|
199
203
|
*/
|
|
200
204
|
export function createAsyncCache<T>(
|
|
201
205
|
ttl: number = 5 * 60 * 1000 // 5 minutes default
|
|
202
206
|
) {
|
|
203
|
-
const cache = new
|
|
207
|
+
const cache = new TTLCache<T>(ttl);
|
|
208
|
+
registerCacheForCleanup(cache);
|
|
204
209
|
|
|
205
210
|
return {
|
|
206
|
-
get: (key: string): T | null =>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (Date.now() - item.timestamp > ttl) {
|
|
211
|
-
cache.delete(key);
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return item.data;
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
set: (key: string, data: T): void => {
|
|
219
|
-
cache.set(key, { data, timestamp: Date.now() });
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
clear: (): void => {
|
|
223
|
-
cache.clear();
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
delete: (key: string): boolean => {
|
|
227
|
-
return cache.delete(key);
|
|
228
|
-
}
|
|
211
|
+
get: (key: string): T | null => cache.get(key),
|
|
212
|
+
set: (key: string, data: T): void => cache.set(key, data),
|
|
213
|
+
clear: (): void => cache.clear(),
|
|
214
|
+
delete: (key: string): boolean => cache.delete(key),
|
|
229
215
|
};
|
|
230
216
|
}
|
|
231
217
|
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized cache utility with TTL support
|
|
3
|
+
*
|
|
4
|
+
* This is a production-ready cache implementation used across the codebase
|
|
5
|
+
* for consistent caching behavior and performance optimization.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Cache entry with expiration tracking
|
|
10
|
+
*/
|
|
11
|
+
interface CacheEntry<T> {
|
|
12
|
+
data: T;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
expiresAt: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Cache statistics
|
|
19
|
+
*/
|
|
20
|
+
export interface CacheStats {
|
|
21
|
+
size: number;
|
|
22
|
+
hits: number;
|
|
23
|
+
misses: number;
|
|
24
|
+
hitRate: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* TTL-based cache implementation
|
|
29
|
+
*
|
|
30
|
+
* Features:
|
|
31
|
+
* - Automatic expiration based on TTL
|
|
32
|
+
* - Manual cleanup of expired entries
|
|
33
|
+
* - Statistics tracking (hits, misses, hit rate)
|
|
34
|
+
* - Type-safe generic interface
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const cache = new TTLCache<string>(5 * 60 * 1000); // 5 minutes
|
|
39
|
+
*
|
|
40
|
+
* // Set with default TTL
|
|
41
|
+
* cache.set('key', 'value');
|
|
42
|
+
*
|
|
43
|
+
* // Set with custom TTL
|
|
44
|
+
* cache.set('key', 'value', 10 * 60 * 1000); // 10 minutes
|
|
45
|
+
*
|
|
46
|
+
* // Get value
|
|
47
|
+
* const value = cache.get('key');
|
|
48
|
+
*
|
|
49
|
+
* // Get statistics
|
|
50
|
+
* const stats = cache.getStats();
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export class TTLCache<T> {
|
|
54
|
+
private cache = new Map<string, CacheEntry<T>>();
|
|
55
|
+
private defaultTTL: number;
|
|
56
|
+
private hits = 0;
|
|
57
|
+
private misses = 0;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a new TTL cache
|
|
61
|
+
* @param defaultTTL Default TTL in milliseconds (default: 5 minutes)
|
|
62
|
+
*/
|
|
63
|
+
constructor(defaultTTL: number = 5 * 60 * 1000) {
|
|
64
|
+
this.defaultTTL = defaultTTL;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get a value from cache
|
|
69
|
+
* @param key Cache key
|
|
70
|
+
* @returns Cached value or null if not found or expired
|
|
71
|
+
*/
|
|
72
|
+
get(key: string): T | null {
|
|
73
|
+
const entry = this.cache.get(key);
|
|
74
|
+
if (!entry) {
|
|
75
|
+
this.misses++;
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
if (now > entry.expiresAt) {
|
|
81
|
+
this.cache.delete(key);
|
|
82
|
+
this.misses++;
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.hits++;
|
|
87
|
+
return entry.data;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Set a value in cache
|
|
92
|
+
* @param key Cache key
|
|
93
|
+
* @param data Data to cache
|
|
94
|
+
* @param ttl Optional TTL override (uses default if not provided)
|
|
95
|
+
*/
|
|
96
|
+
set(key: string, data: T, ttl?: number): void {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
const expiresAt = now + (ttl || this.defaultTTL);
|
|
99
|
+
this.cache.set(key, { data, timestamp: now, expiresAt });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Delete a specific cache entry
|
|
104
|
+
* @param key Cache key
|
|
105
|
+
* @returns true if entry was deleted, false if not found
|
|
106
|
+
*/
|
|
107
|
+
delete(key: string): boolean {
|
|
108
|
+
return this.cache.delete(key);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Clear all cache entries
|
|
113
|
+
*/
|
|
114
|
+
clear(): void {
|
|
115
|
+
this.cache.clear();
|
|
116
|
+
this.hits = 0;
|
|
117
|
+
this.misses = 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if a key exists and is not expired
|
|
122
|
+
* @param key Cache key
|
|
123
|
+
* @returns true if key exists and is valid
|
|
124
|
+
*/
|
|
125
|
+
has(key: string): boolean {
|
|
126
|
+
const entry = this.cache.get(key);
|
|
127
|
+
if (!entry) return false;
|
|
128
|
+
|
|
129
|
+
if (Date.now() > entry.expiresAt) {
|
|
130
|
+
this.cache.delete(key);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get all valid cache keys
|
|
139
|
+
* @returns Array of valid cache keys
|
|
140
|
+
*/
|
|
141
|
+
keys(): string[] {
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const validKeys: string[] = [];
|
|
144
|
+
|
|
145
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
146
|
+
if (now <= entry.expiresAt) {
|
|
147
|
+
validKeys.push(key);
|
|
148
|
+
} else {
|
|
149
|
+
this.cache.delete(key);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return validKeys;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Clean up expired entries
|
|
158
|
+
* @returns Number of entries removed
|
|
159
|
+
*/
|
|
160
|
+
cleanup(): number {
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
let removed = 0;
|
|
163
|
+
|
|
164
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
165
|
+
if (now > entry.expiresAt) {
|
|
166
|
+
this.cache.delete(key);
|
|
167
|
+
removed++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return removed;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get cache size (number of entries)
|
|
176
|
+
*/
|
|
177
|
+
size(): number {
|
|
178
|
+
return this.cache.size;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get cache statistics
|
|
183
|
+
*/
|
|
184
|
+
getStats(): CacheStats {
|
|
185
|
+
const total = this.hits + this.misses;
|
|
186
|
+
return {
|
|
187
|
+
size: this.cache.size,
|
|
188
|
+
hits: this.hits,
|
|
189
|
+
misses: this.misses,
|
|
190
|
+
hitRate: total > 0 ? this.hits / total : 0,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Reset statistics (keeps cache entries)
|
|
196
|
+
*/
|
|
197
|
+
resetStats(): void {
|
|
198
|
+
this.hits = 0;
|
|
199
|
+
this.misses = 0;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a TTL cache instance (convenience function)
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```typescript
|
|
208
|
+
* const cache = createCache<string>(5 * 60 * 1000);
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
export function createCache<T>(ttl: number = 5 * 60 * 1000): TTLCache<T> {
|
|
212
|
+
return new TTLCache<T>(ttl);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Global cache cleanup interval (runs every minute)
|
|
217
|
+
* This helps prevent memory leaks from expired cache entries
|
|
218
|
+
*/
|
|
219
|
+
let cleanupInterval: NodeJS.Timeout | null = null;
|
|
220
|
+
const activeCaches = new Set<TTLCache<any>>();
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Register a cache for automatic cleanup
|
|
224
|
+
* @param cache Cache instance to register
|
|
225
|
+
*/
|
|
226
|
+
export function registerCacheForCleanup(cache: TTLCache<any>): void {
|
|
227
|
+
activeCaches.add(cache);
|
|
228
|
+
|
|
229
|
+
// Start cleanup interval if not already running
|
|
230
|
+
if (!cleanupInterval) {
|
|
231
|
+
cleanupInterval = setInterval(() => {
|
|
232
|
+
for (const cache of activeCaches) {
|
|
233
|
+
cache.cleanup();
|
|
234
|
+
}
|
|
235
|
+
}, 60000); // Every minute
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Unregister a cache from automatic cleanup
|
|
241
|
+
* @param cache Cache instance to unregister
|
|
242
|
+
*/
|
|
243
|
+
export function unregisterCacheFromCleanup(cache: TTLCache<any>): void {
|
|
244
|
+
activeCaches.delete(cache);
|
|
245
|
+
|
|
246
|
+
// Stop cleanup interval if no caches are registered
|
|
247
|
+
if (activeCaches.size === 0 && cleanupInterval) {
|
|
248
|
+
clearInterval(cleanupInterval);
|
|
249
|
+
cleanupInterval = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Stop all cache cleanup intervals
|
|
255
|
+
* Useful for tests to prevent Jest from hanging
|
|
256
|
+
*/
|
|
257
|
+
export function stopAllCacheCleanup(): void {
|
|
258
|
+
if (cleanupInterval) {
|
|
259
|
+
clearInterval(cleanupInterval);
|
|
260
|
+
cleanupInterval = null;
|
|
261
|
+
}
|
|
262
|
+
activeCaches.clear();
|
|
263
|
+
}
|
|
264
|
+
|
package/src/utils/index.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
export { DeviceManager } from './deviceManager';
|
|
2
2
|
export type { DeviceFingerprint, StoredDeviceInfo } from './deviceManager';
|
|
3
|
+
|
|
4
|
+
// Request utilities
|
|
5
|
+
export { RequestDeduplicator, RequestQueue, SimpleLogger } from './requestUtils';
|
|
6
|
+
|
|
7
|
+
// Cache utilities
|
|
8
|
+
export { TTLCache, createCache, registerCacheForCleanup, unregisterCacheFromCleanup, stopAllCacheCleanup } from './cache';
|
|
9
|
+
export type { CacheStats } from './cache';
|
|
10
|
+
|
|
11
|
+
// Session utilities
|
|
12
|
+
export {
|
|
13
|
+
normalizeSession,
|
|
14
|
+
sortSessions,
|
|
15
|
+
deduplicateSessions,
|
|
16
|
+
deduplicateSessionsByUserId,
|
|
17
|
+
normalizeAndSortSessions,
|
|
18
|
+
mergeSessions,
|
|
19
|
+
sessionsEqual,
|
|
20
|
+
sessionsArraysEqual
|
|
21
|
+
} from './sessionUtils';
|