@oxyhq/services 5.3.2 → 5.3.4

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 (29) hide show
  1. package/lib/commonjs/ui/context/OxyContext.js +58 -7
  2. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  3. package/lib/commonjs/ui/navigation/OxyRouter.js +5 -0
  4. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  5. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +70 -26
  6. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  7. package/lib/commonjs/ui/screens/ModernAccountSwitcherScreen.js +532 -0
  8. package/lib/commonjs/ui/screens/ModernAccountSwitcherScreen.js.map +1 -0
  9. package/lib/module/ui/context/OxyContext.js +58 -7
  10. package/lib/module/ui/context/OxyContext.js.map +1 -1
  11. package/lib/module/ui/navigation/OxyRouter.js +5 -0
  12. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  13. package/lib/module/ui/screens/AccountSwitcherScreen.js +72 -28
  14. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  15. package/lib/module/ui/screens/ModernAccountSwitcherScreen.js +527 -0
  16. package/lib/module/ui/screens/ModernAccountSwitcherScreen.js.map +1 -0
  17. package/lib/typescript/models/secureSession.d.ts +2 -0
  18. package/lib/typescript/models/secureSession.d.ts.map +1 -1
  19. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  20. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  21. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  22. package/lib/typescript/ui/screens/ModernAccountSwitcherScreen.d.ts +5 -0
  23. package/lib/typescript/ui/screens/ModernAccountSwitcherScreen.d.ts.map +1 -0
  24. package/package.json +1 -1
  25. package/src/models/secureSession.ts +3 -0
  26. package/src/ui/context/OxyContext.tsx +66 -7
  27. package/src/ui/navigation/OxyRouter.tsx +5 -0
  28. package/src/ui/screens/AccountSwitcherScreen.tsx +85 -25
  29. package/src/ui/screens/ModernAccountSwitcherScreen.tsx +552 -0
@@ -161,10 +161,43 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
161
161
 
162
162
  if (sessionsData) {
163
163
  const parsedSessions: SecureClientSession[] = JSON.parse(sessionsData);
164
- setSessions(parsedSessions);
164
+
165
+ // Migrate old session format to include user info
166
+ const migratedSessions: SecureClientSession[] = [];
167
+ let shouldUpdateStorage = false;
168
+
169
+ for (const session of parsedSessions) {
170
+ if (!session.userId || !session.username) {
171
+ // Session is missing user info, try to fetch it
172
+ try {
173
+ const sessionUser = await oxyServices.getUserBySession(session.sessionId);
174
+ migratedSessions.push({
175
+ ...session,
176
+ userId: sessionUser.id,
177
+ username: sessionUser.username
178
+ });
179
+ shouldUpdateStorage = true;
180
+ console.log(`Migrated session ${session.sessionId} for user ${sessionUser.username}`);
181
+ } catch (error) {
182
+ // Session might be invalid, skip it
183
+ console.log(`Removing invalid session ${session.sessionId}:`, error);
184
+ shouldUpdateStorage = true;
185
+ }
186
+ } else {
187
+ // Session already has user info
188
+ migratedSessions.push(session);
189
+ }
190
+ }
191
+
192
+ // Update storage if we made changes
193
+ if (shouldUpdateStorage) {
194
+ await saveSessionsToStorage(migratedSessions);
195
+ }
196
+
197
+ setSessions(migratedSessions);
165
198
 
166
- if (storedActiveSessionId && parsedSessions.length > 0) {
167
- const activeSession = parsedSessions.find(s => s.sessionId === storedActiveSessionId);
199
+ if (storedActiveSessionId && migratedSessions.length > 0) {
200
+ const activeSession = migratedSessions.find(s => s.sessionId === storedActiveSessionId);
168
201
 
169
202
  if (activeSession) {
170
203
  console.log('SecureAuth - activeSession found:', activeSession);
@@ -317,16 +350,42 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
317
350
  deviceFingerprint
318
351
  );
319
352
 
320
- // Create client session object
353
+ // Create client session object with user info for duplicate detection
321
354
  const clientSession: SecureClientSession = {
322
355
  sessionId: response.sessionId,
323
356
  deviceId: response.deviceId,
324
357
  expiresAt: response.expiresAt,
325
- lastActive: new Date().toISOString()
358
+ lastActive: new Date().toISOString(),
359
+ userId: response.user.id,
360
+ username: response.user.username
326
361
  };
327
362
 
328
- // Add to sessions list
329
- const updatedSessions = [...sessions, clientSession];
363
+ // Check if this user already has a session (prevent duplicate accounts)
364
+ const existingUserSessionIndex = sessions.findIndex(s =>
365
+ s.userId === response.user.id || s.username === response.user.username
366
+ );
367
+
368
+ let updatedSessions: SecureClientSession[];
369
+
370
+ if (existingUserSessionIndex !== -1) {
371
+ // User already has a session - replace it with the new one (reused session scenario)
372
+ const existingSession = sessions[existingUserSessionIndex];
373
+ updatedSessions = [...sessions];
374
+ updatedSessions[existingUserSessionIndex] = clientSession;
375
+
376
+ console.log(`Reusing/updating existing session for user ${response.user.username}. Previous session: ${existingSession.sessionId}, New session: ${response.sessionId}`);
377
+
378
+ // If the replaced session was the active one, update active session
379
+ if (activeSessionId === existingSession.sessionId) {
380
+ setActiveSessionId(response.sessionId);
381
+ await saveActiveSessionId(response.sessionId);
382
+ }
383
+ } else {
384
+ // Add new session for new user
385
+ updatedSessions = [...sessions, clientSession];
386
+ console.log(`Added new session for user ${response.user.username} on device ${response.deviceId}`);
387
+ }
388
+
330
389
  setSessions(updatedSessions);
331
390
  await saveSessionsToStorage(updatedSessions);
332
391
 
@@ -7,6 +7,7 @@ import SignInScreen from '../screens/SignInScreen';
7
7
  import SignUpScreen from '../screens/SignUpScreen';
8
8
  import AccountCenterScreen from '../screens/AccountCenterScreen';
9
9
  import AccountSwitcherScreen from '../screens/AccountSwitcherScreen';
10
+ import ModernAccountSwitcherScreen from '../screens/ModernAccountSwitcherScreen';
10
11
  import SessionManagementScreen from '../screens/SessionManagementScreen';
11
12
  import AccountOverviewScreen from '../screens/AccountOverviewScreen';
12
13
  import AccountSettingsScreen from '../screens/AccountSettingsScreen';
@@ -40,6 +41,10 @@ const routes: Record<string, RouteConfig> = {
40
41
  component: AccountSwitcherScreen,
41
42
  snapPoints: ['70%', '100%'],
42
43
  },
44
+ ModernAccountSwitcher: {
45
+ component: ModernAccountSwitcherScreen,
46
+ snapPoints: ['70%', '100%'],
47
+ },
43
48
  SessionManagement: {
44
49
  component: SessionManagementScreen,
45
50
  snapPoints: ['70%', '100%'],
@@ -8,17 +8,26 @@ import {
8
8
  ScrollView,
9
9
  Alert,
10
10
  Platform,
11
+ Image,
12
+ Dimensions,
11
13
  } from 'react-native';
12
14
  import { BaseScreenProps } from '../navigation/types';
13
15
  import { useOxy } from '../context/OxyContext';
14
16
  import { SecureClientSession } from '../../models/secureSession';
15
17
  import { fontFamilies } from '../styles/fonts';
18
+ import { User } from '../../models/interfaces';
19
+
20
+ interface SessionWithUser extends SecureClientSession {
21
+ userProfile?: User;
22
+ isLoadingProfile?: boolean;
23
+ }
16
24
 
17
25
  const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
18
26
  onClose,
19
27
  theme,
20
28
  navigate,
21
29
  goBack,
30
+ oxyServices,
22
31
  }) => {
23
32
  const {
24
33
  user,
@@ -30,17 +39,68 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
30
39
  isLoading
31
40
  } = useOxy();
32
41
 
42
+ const [sessionsWithUsers, setSessionsWithUsers] = useState<SessionWithUser[]>([]);
33
43
  const [switchingToUserId, setSwitchingToUserId] = useState<string | null>(null);
34
44
  const [removingUserId, setRemovingUserId] = useState<string | null>(null);
35
45
 
46
+ const screenWidth = Dimensions.get('window').width;
36
47
  const isDarkTheme = theme === 'dark';
37
- const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
38
- const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
39
- const secondaryBackgroundColor = isDarkTheme ? '#222222' : '#F5F5F5';
40
- const borderColor = isDarkTheme ? '#444444' : '#E0E0E0';
41
- const primaryColor = '#0066CC';
42
- const dangerColor = '#D32F2F';
43
- const successColor = '#2E7D32';
48
+
49
+ // Modern color scheme
50
+ const colors = {
51
+ background: isDarkTheme ? '#000000' : '#FFFFFF',
52
+ surface: isDarkTheme ? '#1C1C1E' : '#F2F2F7',
53
+ card: isDarkTheme ? '#2C2C2E' : '#FFFFFF',
54
+ text: isDarkTheme ? '#FFFFFF' : '#000000',
55
+ secondaryText: isDarkTheme ? '#8E8E93' : '#6D6D70',
56
+ accent: '#007AFF',
57
+ destructive: '#FF3B30',
58
+ success: '#34C759',
59
+ border: isDarkTheme ? '#38383A' : '#C6C6C8',
60
+ activeCard: isDarkTheme ? '#0A84FF20' : '#007AFF15',
61
+ shadow: isDarkTheme ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0.1)',
62
+ };
63
+
64
+ // Load user profiles for sessions
65
+ useEffect(() => {
66
+ const loadUserProfiles = async () => {
67
+ if (!sessions.length || !oxyServices) return;
68
+
69
+ const updatedSessions: SessionWithUser[] = sessions.map(session => ({
70
+ ...session,
71
+ isLoadingProfile: true,
72
+ }));
73
+ setSessionsWithUsers(updatedSessions);
74
+
75
+ // Load profiles for each session
76
+ for (let i = 0; i < sessions.length; i++) {
77
+ const session = sessions[i];
78
+ try {
79
+ // Try to get user profile using the session
80
+ const userProfile = await oxyServices.getUserBySession(session.sessionId);
81
+
82
+ setSessionsWithUsers(prev =>
83
+ prev.map(s =>
84
+ s.sessionId === session.sessionId
85
+ ? { ...s, userProfile, isLoadingProfile: false }
86
+ : s
87
+ )
88
+ );
89
+ } catch (error) {
90
+ console.error(`Failed to load profile for session ${session.sessionId}:`, error);
91
+ setSessionsWithUsers(prev =>
92
+ prev.map(s =>
93
+ s.sessionId === session.sessionId
94
+ ? { ...s, isLoadingProfile: false }
95
+ : s
96
+ )
97
+ );
98
+ }
99
+ }
100
+ };
101
+
102
+ loadUserProfiles();
103
+ }, [sessions, oxyServices]);
44
104
 
45
105
  const handleSwitchSession = async (sessionId: string) => {
46
106
  if (sessionId === user?.sessionId) return; // Already active session
@@ -134,20 +194,20 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
134
194
  style={[
135
195
  styles.userItem,
136
196
  {
137
- backgroundColor: isActive ? primaryColor + '20' : secondaryBackgroundColor,
138
- borderColor: isActive ? primaryColor : borderColor,
197
+ backgroundColor: isActive ? colors.activeCard : colors.surface,
198
+ borderColor: isActive ? colors.accent : colors.border,
139
199
  },
140
200
  ]}
141
201
  >
142
202
  <View style={styles.userInfo}>
143
- <Text style={[styles.username, { color: textColor }]}>
203
+ <Text style={[styles.username, { color: colors.text }]}>
144
204
  {isActive ? user?.username || 'Current Account' : 'Account Session'}
145
205
  </Text>
146
206
  <Text style={[styles.email, { color: isDarkTheme ? '#BBBBBB' : '#666666' }]}>
147
207
  Last active: {new Date(session.lastActive).toLocaleDateString()}
148
208
  </Text>
149
209
  {isActive && (
150
- <View style={[styles.activeBadge, { backgroundColor: successColor }]}>
210
+ <View style={[styles.activeBadge, { backgroundColor: colors.success }]}>
151
211
  <Text style={styles.activeBadgeText}>Active</Text>
152
212
  </View>
153
213
  )}
@@ -156,27 +216,27 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
156
216
  <View style={styles.userActions}>
157
217
  {!isActive && (
158
218
  <TouchableOpacity
159
- style={[styles.switchButton, { borderColor: primaryColor }]}
219
+ style={[styles.switchButton, { borderColor: colors.accent }]}
160
220
  onPress={() => handleSwitchSession(session.sessionId)}
161
221
  disabled={isSwitching || isRemoving}
162
222
  >
163
223
  {isSwitching ? (
164
- <ActivityIndicator color={primaryColor} size="small" />
224
+ <ActivityIndicator color={colors.accent} size="small" />
165
225
  ) : (
166
- <Text style={[styles.switchButtonText, { color: primaryColor }]}>Switch</Text>
226
+ <Text style={[styles.switchButtonText, { color: colors.accent }]}>Switch</Text>
167
227
  )}
168
228
  </TouchableOpacity>
169
229
  )}
170
230
 
171
231
  <TouchableOpacity
172
- style={[styles.removeButton, { borderColor: dangerColor }]}
232
+ style={[styles.removeButton, { borderColor: colors.destructive }]}
173
233
  onPress={() => handleRemoveSession(session.sessionId)}
174
234
  disabled={isSwitching || isRemoving || sessions.length === 1}
175
235
  >
176
236
  {isRemoving ? (
177
- <ActivityIndicator color={dangerColor} size="small" />
237
+ <ActivityIndicator color={colors.destructive} size="small" />
178
238
  ) : (
179
- <Text style={[styles.removeButtonText, { color: dangerColor }]}>Remove</Text>
239
+ <Text style={[styles.removeButtonText, { color: colors.destructive }]}>Remove</Text>
180
240
  )}
181
241
  </TouchableOpacity>
182
242
  </View>
@@ -185,12 +245,12 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
185
245
  };
186
246
 
187
247
  return (
188
- <View style={[styles.container, { backgroundColor }]}>
248
+ <View style={[styles.container, { backgroundColor: colors.background }]}>
189
249
  <View style={styles.header}>
190
250
  <TouchableOpacity style={styles.backButton} onPress={goBack}>
191
- <Text style={[styles.backButtonText, { color: primaryColor }]}>‹ Back</Text>
251
+ <Text style={[styles.backButtonText, { color: colors.accent }]}>‹ Back</Text>
192
252
  </TouchableOpacity>
193
- <Text style={[styles.title, { color: textColor }]}>Account Switcher</Text>
253
+ <Text style={[styles.title, { color: colors.text }]}>Account Switcher</Text>
194
254
  <View style={styles.placeholder} />
195
255
  </View>
196
256
 
@@ -202,7 +262,7 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
202
262
  </View>
203
263
 
204
264
  <View style={styles.usersContainer}>
205
- <Text style={[styles.sectionTitle, { color: textColor }]}>
265
+ <Text style={[styles.sectionTitle, { color: colors.text }]}>
206
266
  Sessions ({sessions.length})
207
267
  </Text>
208
268
 
@@ -211,10 +271,10 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
211
271
 
212
272
  <View style={styles.actionsContainer}>
213
273
  <TouchableOpacity
214
- style={[styles.actionButton, { borderColor }]}
274
+ style={[styles.actionButton, { borderColor: colors.border }]}
215
275
  onPress={() => navigate('SignIn')}
216
276
  >
217
- <Text style={[styles.actionButtonText, { color: textColor }]}>
277
+ <Text style={[styles.actionButtonText, { color: colors.text }]}>
218
278
  Add Another Account
219
279
  </Text>
220
280
  </TouchableOpacity>
@@ -226,9 +286,9 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
226
286
  disabled={isLoading}
227
287
  >
228
288
  {isLoading ? (
229
- <ActivityIndicator color={dangerColor} size="small" />
289
+ <ActivityIndicator color={colors.destructive} size="small" />
230
290
  ) : (
231
- <Text style={[styles.dangerButtonText, { color: dangerColor }]}>
291
+ <Text style={[styles.dangerButtonText, { color: colors.destructive }]}>
232
292
  Sign Out All Sessions
233
293
  </Text>
234
294
  )}