@oxyhq/services 13.3.0 → 14.0.0
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/index.js +11 -17
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/AccountMenu.js +103 -65
- package/lib/commonjs/ui/components/AccountMenu.js.map +1 -1
- package/lib/commonjs/ui/components/AccountSwitcher.js +84 -126
- package/lib/commonjs/ui/components/AccountSwitcher.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileMenu.js +140 -153
- package/lib/commonjs/ui/components/ProfileMenu.js.map +1 -1
- package/lib/commonjs/ui/components/SignInModal.js +4 -4
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +506 -466
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +59 -57
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js +23 -42
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js.map +1 -1
- package/lib/commonjs/ui/context/silentSessionRestore.js +12 -21
- package/lib/commonjs/ui/context/silentSessionRestore.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +4 -3
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js +12 -7
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js +17 -65
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +288 -0
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -0
- package/lib/commonjs/ui/hooks/useWebSSO.js +12 -191
- package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +2 -2
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +28 -0
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -0
- package/lib/commonjs/ui/session/index.js +52 -0
- package/lib/commonjs/ui/session/index.js.map +1 -0
- package/lib/commonjs/ui/session/projectSessionState.js +86 -0
- package/lib/commonjs/ui/session/projectSessionState.js.map +1 -0
- package/lib/commonjs/ui/session/sessionClientHost.js +30 -0
- package/lib/commonjs/ui/session/sessionClientHost.js.map +1 -0
- package/lib/commonjs/ui/session/tokenTransport.js +77 -0
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -0
- package/lib/commonjs/ui/utils/activeAuthuser.js +35 -79
- package/lib/commonjs/ui/utils/activeAuthuser.js.map +1 -1
- package/lib/commonjs/ui/utils/sessionHelpers.js +1 -51
- package/lib/commonjs/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/commonjs/utils/deviceFlowSignIn.js +70 -15
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/module/index.js +5 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/AccountMenu.js +103 -65
- package/lib/module/ui/components/AccountMenu.js.map +1 -1
- package/lib/module/ui/components/AccountSwitcher.js +85 -127
- package/lib/module/ui/components/AccountSwitcher.js.map +1 -1
- package/lib/module/ui/components/ProfileMenu.js +143 -156
- package/lib/module/ui/components/ProfileMenu.js.map +1 -1
- package/lib/module/ui/components/SignInModal.js +4 -4
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +510 -470
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +61 -60
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/inSessionTokenRefresh.js +24 -43
- package/lib/module/ui/context/inSessionTokenRefresh.js.map +1 -1
- package/lib/module/ui/context/silentSessionRestore.js +12 -20
- package/lib/module/ui/context/silentSessionRestore.js.map +1 -1
- package/lib/module/ui/hooks/useAuth.js +4 -3
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/useOxyAuthSession.js +12 -7
- package/lib/module/ui/hooks/useOxyAuthSession.js.map +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js +18 -66
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js +283 -0
- package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -0
- package/lib/module/ui/hooks/useWebSSO.js +13 -192
- package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/module/ui/index.js +0 -1
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +2 -2
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +25 -0
- package/lib/module/ui/session/createSessionClient.js.map +1 -0
- package/lib/module/ui/session/index.js +17 -0
- package/lib/module/ui/session/index.js.map +1 -0
- package/lib/module/ui/session/projectSessionState.js +79 -0
- package/lib/module/ui/session/projectSessionState.js.map +1 -0
- package/lib/module/ui/session/sessionClientHost.js +26 -0
- package/lib/module/ui/session/sessionClientHost.js.map +1 -0
- package/lib/module/ui/session/tokenTransport.js +74 -0
- package/lib/module/ui/session/tokenTransport.js.map +1 -0
- package/lib/module/ui/utils/activeAuthuser.js +34 -76
- package/lib/module/ui/utils/activeAuthuser.js.map +1 -1
- package/lib/module/ui/utils/sessionHelpers.js +0 -49
- package/lib/module/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/module/utils/deviceFlowSignIn.js +71 -16
- package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +4 -2
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +7 -8
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +12 -9
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +16 -13
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +27 -3
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts +22 -17
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts +12 -18
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +2 -2
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +162 -0
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +13 -52
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +15 -0
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/index.d.ts +15 -0
- package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts +45 -0
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts +14 -0
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +24 -0
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts +21 -29
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts +0 -19
- package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +51 -19
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AccountMenu.d.ts +4 -2
- package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +7 -8
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts +12 -9
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +16 -13
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +27 -3
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts +22 -17
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts +12 -18
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +2 -2
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +162 -0
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +13 -52
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +15 -0
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/index.d.ts +15 -0
- package/lib/typescript/module/ui/session/index.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/projectSessionState.d.ts +45 -0
- package/lib/typescript/module/ui/session/projectSessionState.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts +14 -0
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +24 -0
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -0
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts +21 -29
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/sessionHelpers.d.ts +0 -19
- package/lib/typescript/module/ui/utils/sessionHelpers.d.ts.map +1 -1
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +51 -19
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +9 -9
- package/src/ui/components/AccountMenu.tsx +117 -70
- package/src/ui/components/AccountSwitcher.tsx +88 -134
- package/src/ui/components/ProfileMenu.tsx +158 -162
- package/src/ui/components/SignInModal.tsx +5 -5
- package/src/ui/context/OxyContext.tsx +560 -475
- package/src/ui/context/hooks/useAuthOperations.ts +72 -63
- package/src/ui/context/inSessionTokenRefresh.ts +23 -42
- package/src/ui/context/silentSessionRestore.ts +12 -26
- package/src/ui/hooks/useAuth.ts +4 -3
- package/src/ui/hooks/useOxyAuthSession.ts +13 -8
- package/src/ui/hooks/useSessionManagement.ts +16 -87
- package/src/ui/hooks/useSwitchableAccounts.ts +397 -0
- package/src/ui/hooks/useWebSSO.ts +13 -227
- package/src/ui/index.ts +0 -1
- package/src/ui/screens/OxyAuthScreen.tsx +2 -2
- package/src/ui/session/__tests__/createSessionClient.test.ts +41 -0
- package/src/ui/session/__tests__/projectSessionState.test.ts +114 -0
- package/src/ui/session/__tests__/sessionClientHost.test.ts +43 -0
- package/src/ui/session/__tests__/tokenTransport.test.ts +142 -0
- package/src/ui/session/createSessionClient.ts +21 -0
- package/src/ui/session/index.ts +19 -0
- package/src/ui/session/projectSessionState.ts +85 -0
- package/src/ui/session/sessionClientHost.ts +27 -0
- package/src/ui/session/tokenTransport.ts +76 -0
- package/src/ui/utils/activeAuthuser.ts +34 -76
- package/src/ui/utils/sessionHelpers.ts +0 -66
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +243 -34
- package/src/utils/deviceFlowSignIn.ts +98 -21
- package/lib/commonjs/ui/components/accountMenuRows.js +0 -33
- package/lib/commonjs/ui/components/accountMenuRows.js.map +0 -1
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js +0 -280
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js.map +0 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +0 -203
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +0 -1
- package/lib/commonjs/utils/silentGuardKey.js +0 -54
- package/lib/commonjs/utils/silentGuardKey.js.map +0 -1
- package/lib/module/ui/components/accountMenuRows.js +0 -29
- package/lib/module/ui/components/accountMenuRows.js.map +0 -1
- package/lib/module/ui/hooks/useDeviceAccounts.js +0 -276
- package/lib/module/ui/hooks/useDeviceAccounts.js.map +0 -1
- package/lib/module/ui/hooks/useSessionSocket.js +0 -199
- package/lib/module/ui/hooks/useSessionSocket.js.map +0 -1
- package/lib/module/utils/silentGuardKey.js +0 -49
- package/lib/module/utils/silentGuardKey.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts +0 -34
- package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts +0 -133
- package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts +0 -14
- package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/silentGuardKey.d.ts +0 -31
- package/lib/typescript/commonjs/utils/silentGuardKey.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/accountMenuRows.d.ts +0 -34
- package/lib/typescript/module/ui/components/accountMenuRows.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts +0 -133
- package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts +0 -14
- package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts.map +0 -1
- package/lib/typescript/module/utils/silentGuardKey.d.ts +0 -31
- package/lib/typescript/module/utils/silentGuardKey.d.ts.map +0 -1
- package/src/ui/components/accountMenuRows.ts +0 -48
- package/src/ui/hooks/useDeviceAccounts.ts +0 -344
- package/src/ui/hooks/useSessionSocket.ts +0 -233
- package/src/ui/utils/__tests__/activeAuthuser.test.ts +0 -75
- package/src/utils/__tests__/silentGuardKey.test.ts +0 -82
- package/src/utils/silentGuardKey.ts +0 -46
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import io, { type Socket } from 'socket.io-client';
|
|
3
|
-
import { toast } from '@oxyhq/bloom';
|
|
4
|
-
import { logger } from '@oxyhq/core';
|
|
5
|
-
import { createDebugLogger } from '@oxyhq/core';
|
|
6
|
-
|
|
7
|
-
const debug = createDebugLogger('SessionSocket');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Server-emitted event types on the `session_update` channel.
|
|
11
|
-
*
|
|
12
|
-
* `session_removed`, `device_removed`, `sessions_removed` are the ONLY
|
|
13
|
-
* events that may sign the user out. Anything else (e.g. `session_created`
|
|
14
|
-
* fired right after a successful sign-in) MUST NOT trigger sign-out — see
|
|
15
|
-
* the comment on the `default` branch below.
|
|
16
|
-
*/
|
|
17
|
-
type SessionEventType =
|
|
18
|
-
| 'session_removed'
|
|
19
|
-
| 'device_removed'
|
|
20
|
-
| 'sessions_removed'
|
|
21
|
-
| 'session_created'
|
|
22
|
-
| 'session_update';
|
|
23
|
-
|
|
24
|
-
interface SessionUpdatePayload {
|
|
25
|
-
type: SessionEventType | string;
|
|
26
|
-
sessionId?: string;
|
|
27
|
-
deviceId?: string;
|
|
28
|
-
sessionIds?: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface UseSessionSocketProps {
|
|
32
|
-
userId: string | null | undefined;
|
|
33
|
-
activeSessionId: string | null | undefined;
|
|
34
|
-
currentDeviceId: string | null | undefined;
|
|
35
|
-
refreshSessions: () => Promise<void>;
|
|
36
|
-
clearSessionState: () => Promise<void>;
|
|
37
|
-
baseURL: string;
|
|
38
|
-
getAccessToken: () => string | null;
|
|
39
|
-
onRemoteSignOut?: () => void;
|
|
40
|
-
onSessionRemoved?: (sessionId: string) => void;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function useSessionSocket({
|
|
44
|
-
userId,
|
|
45
|
-
activeSessionId,
|
|
46
|
-
currentDeviceId,
|
|
47
|
-
refreshSessions,
|
|
48
|
-
clearSessionState,
|
|
49
|
-
baseURL,
|
|
50
|
-
getAccessToken,
|
|
51
|
-
onRemoteSignOut,
|
|
52
|
-
onSessionRemoved,
|
|
53
|
-
}: UseSessionSocketProps) {
|
|
54
|
-
const socketRef = useRef<Socket | null>(null);
|
|
55
|
-
|
|
56
|
-
// Store callbacks in refs to avoid reconnecting when they change
|
|
57
|
-
const refreshSessionsRef = useRef(refreshSessions);
|
|
58
|
-
const clearSessionStateRef = useRef(clearSessionState);
|
|
59
|
-
const onRemoteSignOutRef = useRef(onRemoteSignOut);
|
|
60
|
-
const onSessionRemovedRef = useRef(onSessionRemoved);
|
|
61
|
-
const activeSessionIdRef = useRef(activeSessionId);
|
|
62
|
-
const currentDeviceIdRef = useRef(currentDeviceId);
|
|
63
|
-
const getAccessTokenRef = useRef(getAccessToken);
|
|
64
|
-
|
|
65
|
-
// Update refs when callbacks change
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
refreshSessionsRef.current = refreshSessions;
|
|
68
|
-
clearSessionStateRef.current = clearSessionState;
|
|
69
|
-
onRemoteSignOutRef.current = onRemoteSignOut;
|
|
70
|
-
onSessionRemovedRef.current = onSessionRemoved;
|
|
71
|
-
activeSessionIdRef.current = activeSessionId;
|
|
72
|
-
currentDeviceIdRef.current = currentDeviceId;
|
|
73
|
-
getAccessTokenRef.current = getAccessToken;
|
|
74
|
-
}, [refreshSessions, clearSessionState, onRemoteSignOut, onSessionRemoved, activeSessionId, currentDeviceId, getAccessToken]);
|
|
75
|
-
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
if (!userId || !baseURL) {
|
|
78
|
-
// Clean up if userId or baseURL becomes invalid
|
|
79
|
-
if (socketRef.current) {
|
|
80
|
-
socketRef.current.disconnect();
|
|
81
|
-
socketRef.current = null;
|
|
82
|
-
}
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Disconnect previous socket if switching users
|
|
87
|
-
if (socketRef.current) {
|
|
88
|
-
socketRef.current.disconnect();
|
|
89
|
-
socketRef.current = null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Connect with auth token; use callback so reconnections get a fresh token
|
|
93
|
-
socketRef.current = io(baseURL, {
|
|
94
|
-
transports: ['websocket'],
|
|
95
|
-
auth: (cb) => {
|
|
96
|
-
const token = getAccessTokenRef.current();
|
|
97
|
-
cb({ token: token ?? '' });
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
const socket = socketRef.current;
|
|
101
|
-
|
|
102
|
-
// Server auto-joins the user to `user:<userId>` room on connection
|
|
103
|
-
const handleConnect = () => {
|
|
104
|
-
debug.log('Socket connected:', socket.id);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const refreshSessionsSafe = () => {
|
|
108
|
-
refreshSessionsRef.current().catch((error: unknown) => {
|
|
109
|
-
// Refresh errors shouldn't break the socket handler. They happen
|
|
110
|
-
// routinely when sessions are removed mid-fetch — log and move on.
|
|
111
|
-
if (__DEV__) {
|
|
112
|
-
logger.debug(
|
|
113
|
-
'Failed to refresh sessions after session_update',
|
|
114
|
-
{ component: 'useSessionSocket' },
|
|
115
|
-
error,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const triggerLocalSignOut = async (toastMessage: string, errorContext: string) => {
|
|
122
|
-
if (onRemoteSignOutRef.current) {
|
|
123
|
-
onRemoteSignOutRef.current();
|
|
124
|
-
} else {
|
|
125
|
-
toast.info(toastMessage);
|
|
126
|
-
}
|
|
127
|
-
// Clear local state since the server has already removed the session.
|
|
128
|
-
// Await so storage cleanup completes before any subsequent navigation.
|
|
129
|
-
try {
|
|
130
|
-
await clearSessionStateRef.current();
|
|
131
|
-
} catch (error) {
|
|
132
|
-
if (__DEV__) {
|
|
133
|
-
logger.error(
|
|
134
|
-
`Failed to clear session state after ${errorContext}`,
|
|
135
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
136
|
-
{ component: 'useSessionSocket' },
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const handleSessionUpdate = async (data: SessionUpdatePayload) => {
|
|
143
|
-
debug.log('Received session_update:', data);
|
|
144
|
-
|
|
145
|
-
const currentActiveSessionId = activeSessionIdRef.current;
|
|
146
|
-
const currentDeviceIdValue = currentDeviceIdRef.current;
|
|
147
|
-
|
|
148
|
-
// Strict whitelist. Every event type that may sign the user out must
|
|
149
|
-
// appear in the switch. Anything unknown falls through to `default`,
|
|
150
|
-
// which is intentionally a no-op for session lifecycle — it only logs
|
|
151
|
-
// in dev. This guards against future server-side event additions
|
|
152
|
-
// accidentally triggering sign-out.
|
|
153
|
-
switch (data.type) {
|
|
154
|
-
case 'session_removed': {
|
|
155
|
-
if (data.sessionId && onSessionRemovedRef.current) {
|
|
156
|
-
onSessionRemovedRef.current(data.sessionId);
|
|
157
|
-
}
|
|
158
|
-
if (data.sessionId && data.sessionId === currentActiveSessionId) {
|
|
159
|
-
await triggerLocalSignOut('You have been signed out remotely.', 'session_removed');
|
|
160
|
-
} else {
|
|
161
|
-
refreshSessionsSafe();
|
|
162
|
-
}
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
case 'device_removed': {
|
|
166
|
-
if (data.sessionIds && onSessionRemovedRef.current) {
|
|
167
|
-
for (const sessionId of data.sessionIds) {
|
|
168
|
-
onSessionRemovedRef.current(sessionId);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
if (data.deviceId && currentDeviceIdValue && data.deviceId === currentDeviceIdValue) {
|
|
172
|
-
await triggerLocalSignOut(
|
|
173
|
-
'This device has been removed. You have been signed out.',
|
|
174
|
-
'device_removed',
|
|
175
|
-
);
|
|
176
|
-
} else {
|
|
177
|
-
refreshSessionsSafe();
|
|
178
|
-
}
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
case 'sessions_removed': {
|
|
182
|
-
if (data.sessionIds && onSessionRemovedRef.current) {
|
|
183
|
-
for (const sessionId of data.sessionIds) {
|
|
184
|
-
onSessionRemovedRef.current(sessionId);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (
|
|
188
|
-
data.sessionIds &&
|
|
189
|
-
currentActiveSessionId &&
|
|
190
|
-
data.sessionIds.includes(currentActiveSessionId)
|
|
191
|
-
) {
|
|
192
|
-
await triggerLocalSignOut('You have been signed out remotely.', 'sessions_removed');
|
|
193
|
-
} else {
|
|
194
|
-
refreshSessionsSafe();
|
|
195
|
-
}
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
case 'session_created':
|
|
199
|
-
case 'session_update': {
|
|
200
|
-
// Lifecycle event for the current user. Just resync the sessions
|
|
201
|
-
// list — never sign out. A prior implementation compared
|
|
202
|
-
// `data.sessionId === currentActiveSessionId` and signed the user
|
|
203
|
-
// out if true, which was catastrophic: a
|
|
204
|
-
// `session_created` event fired immediately after a successful
|
|
205
|
-
// sign-in carries the user's NEW (now-active) session id, which
|
|
206
|
-
// matched and triggered an instant remote sign-out toast on every
|
|
207
|
-
// login. Whitelist explicitly; never fall through.
|
|
208
|
-
refreshSessionsSafe();
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
default: {
|
|
212
|
-
if (__DEV__) {
|
|
213
|
-
logger.warn('Unknown session socket event type', {
|
|
214
|
-
component: 'useSessionSocket',
|
|
215
|
-
type: data.type,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
socket.on('connect', handleConnect);
|
|
224
|
-
socket.on('session_update', handleSessionUpdate);
|
|
225
|
-
|
|
226
|
-
return () => {
|
|
227
|
-
socket.off('connect', handleConnect);
|
|
228
|
-
socket.off('session_update', handleSessionUpdate);
|
|
229
|
-
socket.disconnect();
|
|
230
|
-
socketRef.current = null;
|
|
231
|
-
};
|
|
232
|
-
}, [userId, baseURL]); // Only depend on userId and baseURL - callbacks are in refs
|
|
233
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fail-safe guarantees for the web `activeAuthuser` storage helpers.
|
|
3
|
-
*
|
|
4
|
-
* The PROPERTY ACCESS `window.localStorage` can throw a `SecurityError`
|
|
5
|
-
* synchronously in opaque-origin / sandboxed iframes or when storage is
|
|
6
|
-
* disabled — not just `getItem`/`setItem`. These helpers run during cold boot
|
|
7
|
-
* (and feed render-phase gate values in the providers), so they MUST never
|
|
8
|
-
* propagate that throw: reads fail safe to a benign default and writes no-op.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
readActiveAuthuser,
|
|
13
|
-
writeActiveAuthuser,
|
|
14
|
-
clearActiveAuthuser,
|
|
15
|
-
markSignedOut,
|
|
16
|
-
clearSignedOut,
|
|
17
|
-
isSilentRestoreSuppressed,
|
|
18
|
-
} from '../activeAuthuser';
|
|
19
|
-
|
|
20
|
-
describe('activeAuthuser helpers — localStorage fail-safe', () => {
|
|
21
|
-
const realDescriptor = Object.getOwnPropertyDescriptor(window, 'localStorage');
|
|
22
|
-
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
if (realDescriptor) {
|
|
25
|
-
Object.defineProperty(window, 'localStorage', realDescriptor);
|
|
26
|
-
}
|
|
27
|
-
window.localStorage?.clear?.();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
/** Replace the `localStorage` accessor so reading the property throws. */
|
|
31
|
-
function makeLocalStorageGetterThrow(): void {
|
|
32
|
-
Object.defineProperty(window, 'localStorage', {
|
|
33
|
-
configurable: true,
|
|
34
|
-
get() {
|
|
35
|
-
throw new DOMException('denied', 'SecurityError');
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
it('readActiveAuthuser returns null (never throws) when the getter throws', () => {
|
|
41
|
-
makeLocalStorageGetterThrow();
|
|
42
|
-
expect(() => readActiveAuthuser()).not.toThrow();
|
|
43
|
-
expect(readActiveAuthuser()).toBeNull();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('isSilentRestoreSuppressed returns false (never throws) when the getter throws', () => {
|
|
47
|
-
makeLocalStorageGetterThrow();
|
|
48
|
-
expect(() => isSilentRestoreSuppressed()).not.toThrow();
|
|
49
|
-
expect(isSilentRestoreSuppressed()).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('writeActiveAuthuser / clearActiveAuthuser / markSignedOut / clearSignedOut no-op when the getter throws', () => {
|
|
53
|
-
makeLocalStorageGetterThrow();
|
|
54
|
-
expect(() => {
|
|
55
|
-
writeActiveAuthuser(2);
|
|
56
|
-
clearActiveAuthuser();
|
|
57
|
-
markSignedOut();
|
|
58
|
-
clearSignedOut();
|
|
59
|
-
}).not.toThrow();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('round-trips normally when storage works (zero behavior change)', () => {
|
|
63
|
-
writeActiveAuthuser(3);
|
|
64
|
-
expect(readActiveAuthuser()).toBe(3);
|
|
65
|
-
|
|
66
|
-
expect(isSilentRestoreSuppressed()).toBe(false);
|
|
67
|
-
markSignedOut();
|
|
68
|
-
expect(isSilentRestoreSuppressed()).toBe(true);
|
|
69
|
-
clearSignedOut();
|
|
70
|
-
expect(isSilentRestoreSuppressed()).toBe(false);
|
|
71
|
-
|
|
72
|
-
clearActiveAuthuser();
|
|
73
|
-
expect(readActiveAuthuser()).toBeNull();
|
|
74
|
-
});
|
|
75
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment node
|
|
3
|
-
*
|
|
4
|
-
* Regression coverage for the native session-restore crash:
|
|
5
|
-
*
|
|
6
|
-
* W [component:OxyContext]: Failed to restore sessions from storage
|
|
7
|
-
* [TypeError: Cannot read property 'origin' of undefined]
|
|
8
|
-
*
|
|
9
|
-
* `silentColdBootKey` (OxyContext) and `ssoSignature` (useWebSSO) both build an
|
|
10
|
-
* `origin|baseURL` guard signature UNCONDITIONALLY at the top of the cold-boot
|
|
11
|
-
* path, on every platform. React Native aliases a global `window` (so
|
|
12
|
-
* `typeof window !== 'undefined'` is `true`) but provides NO `window.location`.
|
|
13
|
-
* The previous `typeof window`-only guard then read `window.location.origin`
|
|
14
|
-
* and threw `Cannot read property 'origin' of undefined`, escaping session
|
|
15
|
-
* restore entirely. Both call sites now delegate to the shared, guarded
|
|
16
|
-
* `buildSilentGuardKey`, verified here under all three platform shapes.
|
|
17
|
-
*
|
|
18
|
-
* Runs in the `node` environment so `window` is genuinely controllable — under
|
|
19
|
-
* jsdom `window.location` is non-configurable and cannot be removed, so the
|
|
20
|
-
* native shape (window present, location absent) is not reproducible there.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { buildSilentGuardKey, safeWindowOrigin } from '../silentGuardKey';
|
|
24
|
-
|
|
25
|
-
describe('silentGuardKey native safety', () => {
|
|
26
|
-
const globalRef = globalThis as { window?: unknown };
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
delete globalRef.window;
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('safeWindowOrigin', () => {
|
|
33
|
-
it('returns "no-origin" when there is no window (Node / SSR)', () => {
|
|
34
|
-
delete globalRef.window;
|
|
35
|
-
expect(safeWindowOrigin()).toBe('no-origin');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('returns "no-origin" on React Native (window present, no location)', () => {
|
|
39
|
-
// EXACT native shape: RN aliases a global `window` to the JS global, but
|
|
40
|
-
// there is no `window.location`. The old `typeof window`-only guard threw
|
|
41
|
-
// here; the new guard must return the sentinel without throwing.
|
|
42
|
-
globalRef.window = {};
|
|
43
|
-
expect(() => safeWindowOrigin()).not.toThrow();
|
|
44
|
-
expect(safeWindowOrigin()).toBe('no-origin');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('returns the browser origin on web', () => {
|
|
48
|
-
globalRef.window = { location: { origin: 'https://app.mention.earth' } };
|
|
49
|
-
expect(safeWindowOrigin()).toBe('https://app.mention.earth');
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('buildSilentGuardKey', () => {
|
|
54
|
-
it('does not throw and composes "no-origin|" on React Native', () => {
|
|
55
|
-
globalRef.window = {};
|
|
56
|
-
const getBaseURL = () => 'https://api.mention.earth';
|
|
57
|
-
expect(() => buildSilentGuardKey(getBaseURL)).not.toThrow();
|
|
58
|
-
expect(buildSilentGuardKey(getBaseURL)).toBe('no-origin|https://api.mention.earth');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('composes "origin|baseURL" on web', () => {
|
|
62
|
-
globalRef.window = { location: { origin: 'https://app.mention.earth' } };
|
|
63
|
-
expect(buildSilentGuardKey(() => 'https://api.mention.earth')).toBe(
|
|
64
|
-
'https://app.mention.earth|https://api.mention.earth',
|
|
65
|
-
);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('degrades baseURL to empty when getBaseURL is absent', () => {
|
|
69
|
-
globalRef.window = {};
|
|
70
|
-
expect(buildSilentGuardKey()).toBe('no-origin|');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('degrades baseURL to empty when getBaseURL throws', () => {
|
|
74
|
-
globalRef.window = {};
|
|
75
|
-
const throwing = (): string => {
|
|
76
|
-
throw new Error('client not initialised');
|
|
77
|
-
};
|
|
78
|
-
expect(() => buildSilentGuardKey(throwing)).not.toThrow();
|
|
79
|
-
expect(buildSilentGuardKey(throwing)).toBe('no-origin|');
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared, pure helpers for building the `origin|baseURL` signature used as the
|
|
3
|
-
* module-level run-once guard key for cold-boot silent-SSO probes
|
|
4
|
-
* (`silentColdBootKey` in `OxyContext`, `ssoSignature` in `useWebSSO`).
|
|
5
|
-
*
|
|
6
|
-
* NATIVE SAFETY (the bug this fixes): React Native aliases a global `window`
|
|
7
|
-
* (it points at the JS global object), so `typeof window !== 'undefined'` is
|
|
8
|
-
* `true` on native — but `window.location` is `undefined`. Reading
|
|
9
|
-
* `window.location.origin` after only a `typeof window` check therefore throws
|
|
10
|
-
* `TypeError: Cannot read property 'origin' of undefined` on native. Because
|
|
11
|
-
* the key is built UNCONDITIONALLY at the top of the cold-boot path (before its
|
|
12
|
-
* try/catch), that throw escaped session restore entirely and broke
|
|
13
|
-
* cross-session restore on native. Both prior copies of the guard had the same
|
|
14
|
-
* insufficient `typeof window` check and were prone to drift, so the read is
|
|
15
|
-
* consolidated here behind a guard that also verifies `window.location`.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Read `window.location.origin` safely on every platform.
|
|
20
|
-
*
|
|
21
|
-
* Returns the browser origin on web, and the sentinel `'no-origin'` anywhere
|
|
22
|
-
* `window.location` is absent (React Native, SSR/Node). Never throws.
|
|
23
|
-
*/
|
|
24
|
-
export function safeWindowOrigin(): string {
|
|
25
|
-
if (typeof window !== 'undefined' && typeof window.location !== 'undefined') {
|
|
26
|
-
return window.location.origin;
|
|
27
|
-
}
|
|
28
|
-
return 'no-origin';
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Build the stable `origin|baseURL` signature for the silent-SSO run-once
|
|
33
|
-
* guard. Two providers pointed at the same API from the same origin share one
|
|
34
|
-
* attempt. `getBaseURL` is invoked defensively (it may be absent or throw on a
|
|
35
|
-
* partially-initialised client); any failure degrades to an empty baseURL.
|
|
36
|
-
*/
|
|
37
|
-
export function buildSilentGuardKey(getBaseURL?: () => string | undefined): string {
|
|
38
|
-
const origin = safeWindowOrigin();
|
|
39
|
-
let baseURL = '';
|
|
40
|
-
try {
|
|
41
|
-
baseURL = getBaseURL?.() ?? '';
|
|
42
|
-
} catch {
|
|
43
|
-
baseURL = '';
|
|
44
|
-
}
|
|
45
|
-
return `${origin}|${baseURL}`;
|
|
46
|
-
}
|