@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.
- package/lib/commonjs/ui/context/OxyContext.js +58 -7
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/navigation/OxyRouter.js +5 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +70 -26
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ModernAccountSwitcherScreen.js +532 -0
- package/lib/commonjs/ui/screens/ModernAccountSwitcherScreen.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +58 -7
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/navigation/OxyRouter.js +5 -0
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +72 -28
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/ModernAccountSwitcherScreen.js +527 -0
- package/lib/module/ui/screens/ModernAccountSwitcherScreen.js.map +1 -0
- package/lib/typescript/models/secureSession.d.ts +2 -0
- package/lib/typescript/models/secureSession.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/ModernAccountSwitcherScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/ModernAccountSwitcherScreen.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/models/secureSession.ts +3 -0
- package/src/ui/context/OxyContext.tsx +66 -7
- package/src/ui/navigation/OxyRouter.tsx +5 -0
- package/src/ui/screens/AccountSwitcherScreen.tsx +85 -25
- 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
|
-
|
|
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 &&
|
|
167
|
-
const activeSession =
|
|
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
|
-
//
|
|
329
|
-
const
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 ?
|
|
138
|
-
borderColor: isActive ?
|
|
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:
|
|
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:
|
|
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:
|
|
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={
|
|
224
|
+
<ActivityIndicator color={colors.accent} size="small" />
|
|
165
225
|
) : (
|
|
166
|
-
<Text style={[styles.switchButtonText, { color:
|
|
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:
|
|
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={
|
|
237
|
+
<ActivityIndicator color={colors.destructive} size="small" />
|
|
178
238
|
) : (
|
|
179
|
-
<Text style={[styles.removeButtonText, { color:
|
|
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:
|
|
251
|
+
<Text style={[styles.backButtonText, { color: colors.accent }]}>‹ Back</Text>
|
|
192
252
|
</TouchableOpacity>
|
|
193
|
-
<Text style={[styles.title, { color:
|
|
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:
|
|
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:
|
|
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={
|
|
289
|
+
<ActivityIndicator color={colors.destructive} size="small" />
|
|
230
290
|
) : (
|
|
231
|
-
<Text style={[styles.dangerButtonText, { color:
|
|
291
|
+
<Text style={[styles.dangerButtonText, { color: colors.destructive }]}>
|
|
232
292
|
Sign Out All Sessions
|
|
233
293
|
</Text>
|
|
234
294
|
)}
|