@oxyhq/services 13.3.0 → 14.0.1

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 (245) hide show
  1. package/lib/commonjs/index.js +11 -16
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/components/AccountMenu.js +103 -65
  4. package/lib/commonjs/ui/components/AccountMenu.js.map +1 -1
  5. package/lib/commonjs/ui/components/AccountSwitcher.js +84 -126
  6. package/lib/commonjs/ui/components/AccountSwitcher.js.map +1 -1
  7. package/lib/commonjs/ui/components/ProfileMenu.js +140 -153
  8. package/lib/commonjs/ui/components/ProfileMenu.js.map +1 -1
  9. package/lib/commonjs/ui/components/SignInModal.js +4 -4
  10. package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
  11. package/lib/commonjs/ui/context/OxyContext.js +506 -466
  12. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  13. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +59 -57
  14. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  15. package/lib/commonjs/ui/context/inSessionTokenRefresh.js +23 -42
  16. package/lib/commonjs/ui/context/inSessionTokenRefresh.js.map +1 -1
  17. package/lib/commonjs/ui/context/silentSessionRestore.js +12 -21
  18. package/lib/commonjs/ui/context/silentSessionRestore.js.map +1 -1
  19. package/lib/commonjs/ui/hooks/useAuth.js +4 -3
  20. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  21. package/lib/commonjs/ui/hooks/useOxyAuthSession.js +12 -7
  22. package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/useSessionManagement.js +17 -65
  24. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  25. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +288 -0
  26. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -0
  27. package/lib/commonjs/ui/hooks/useWebSSO.js +12 -191
  28. package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
  29. package/lib/commonjs/ui/screens/OxyAuthScreen.js +2 -2
  30. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  31. package/lib/commonjs/ui/session/createSessionClient.js +28 -0
  32. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -0
  33. package/lib/commonjs/ui/session/index.js +52 -0
  34. package/lib/commonjs/ui/session/index.js.map +1 -0
  35. package/lib/commonjs/ui/session/projectSessionState.js +86 -0
  36. package/lib/commonjs/ui/session/projectSessionState.js.map +1 -0
  37. package/lib/commonjs/ui/session/sessionClientHost.js +30 -0
  38. package/lib/commonjs/ui/session/sessionClientHost.js.map +1 -0
  39. package/lib/commonjs/ui/session/tokenTransport.js +77 -0
  40. package/lib/commonjs/ui/session/tokenTransport.js.map +1 -0
  41. package/lib/commonjs/ui/utils/activeAuthuser.js +35 -79
  42. package/lib/commonjs/ui/utils/activeAuthuser.js.map +1 -1
  43. package/lib/commonjs/ui/utils/sessionHelpers.js +1 -51
  44. package/lib/commonjs/ui/utils/sessionHelpers.js.map +1 -1
  45. package/lib/commonjs/utils/deviceFlowSignIn.js +70 -15
  46. package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
  47. package/lib/module/index.js +5 -4
  48. package/lib/module/index.js.map +1 -1
  49. package/lib/module/ui/components/AccountMenu.js +103 -65
  50. package/lib/module/ui/components/AccountMenu.js.map +1 -1
  51. package/lib/module/ui/components/AccountSwitcher.js +85 -127
  52. package/lib/module/ui/components/AccountSwitcher.js.map +1 -1
  53. package/lib/module/ui/components/ProfileMenu.js +143 -156
  54. package/lib/module/ui/components/ProfileMenu.js.map +1 -1
  55. package/lib/module/ui/components/SignInModal.js +4 -4
  56. package/lib/module/ui/components/SignInModal.js.map +1 -1
  57. package/lib/module/ui/context/OxyContext.js +510 -470
  58. package/lib/module/ui/context/OxyContext.js.map +1 -1
  59. package/lib/module/ui/context/hooks/useAuthOperations.js +61 -60
  60. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  61. package/lib/module/ui/context/inSessionTokenRefresh.js +24 -43
  62. package/lib/module/ui/context/inSessionTokenRefresh.js.map +1 -1
  63. package/lib/module/ui/context/silentSessionRestore.js +12 -20
  64. package/lib/module/ui/context/silentSessionRestore.js.map +1 -1
  65. package/lib/module/ui/hooks/useAuth.js +4 -3
  66. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  67. package/lib/module/ui/hooks/useOxyAuthSession.js +12 -7
  68. package/lib/module/ui/hooks/useOxyAuthSession.js.map +1 -1
  69. package/lib/module/ui/hooks/useSessionManagement.js +18 -66
  70. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  71. package/lib/module/ui/hooks/useSwitchableAccounts.js +283 -0
  72. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -0
  73. package/lib/module/ui/hooks/useWebSSO.js +13 -192
  74. package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
  75. package/lib/module/ui/screens/OxyAuthScreen.js +2 -2
  76. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  77. package/lib/module/ui/session/createSessionClient.js +25 -0
  78. package/lib/module/ui/session/createSessionClient.js.map +1 -0
  79. package/lib/module/ui/session/index.js +17 -0
  80. package/lib/module/ui/session/index.js.map +1 -0
  81. package/lib/module/ui/session/projectSessionState.js +79 -0
  82. package/lib/module/ui/session/projectSessionState.js.map +1 -0
  83. package/lib/module/ui/session/sessionClientHost.js +26 -0
  84. package/lib/module/ui/session/sessionClientHost.js.map +1 -0
  85. package/lib/module/ui/session/tokenTransport.js +74 -0
  86. package/lib/module/ui/session/tokenTransport.js.map +1 -0
  87. package/lib/module/ui/utils/activeAuthuser.js +34 -76
  88. package/lib/module/ui/utils/activeAuthuser.js.map +1 -1
  89. package/lib/module/ui/utils/sessionHelpers.js +0 -49
  90. package/lib/module/ui/utils/sessionHelpers.js.map +1 -1
  91. package/lib/module/utils/deviceFlowSignIn.js +71 -16
  92. package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
  93. package/lib/typescript/commonjs/index.d.ts +2 -3
  94. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +4 -2
  96. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +7 -8
  98. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +12 -9
  100. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +16 -13
  102. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +27 -3
  104. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts +22 -17
  106. package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts +12 -18
  108. package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
  110. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +1 -1
  112. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +2 -2
  114. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
  116. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +162 -0
  117. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +13 -52
  119. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
  120. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +15 -0
  121. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -0
  122. package/lib/typescript/commonjs/ui/session/index.d.ts +15 -0
  123. package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -0
  124. package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts +45 -0
  125. package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts.map +1 -0
  126. package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts +14 -0
  127. package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +24 -0
  129. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -0
  130. package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts +21 -29
  131. package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts.map +1 -1
  132. package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts +0 -19
  133. package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts.map +1 -1
  134. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +51 -19
  135. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
  136. package/lib/typescript/module/index.d.ts +2 -3
  137. package/lib/typescript/module/index.d.ts.map +1 -1
  138. package/lib/typescript/module/ui/components/AccountMenu.d.ts +4 -2
  139. package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +1 -1
  140. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +7 -8
  141. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +1 -1
  142. package/lib/typescript/module/ui/components/ProfileMenu.d.ts +12 -9
  143. package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +1 -1
  144. package/lib/typescript/module/ui/context/OxyContext.d.ts +16 -13
  145. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  146. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +27 -3
  147. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  148. package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts +22 -17
  149. package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts.map +1 -1
  150. package/lib/typescript/module/ui/context/silentSessionRestore.d.ts +12 -18
  151. package/lib/typescript/module/ui/context/silentSessionRestore.d.ts.map +1 -1
  152. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
  153. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  154. package/lib/typescript/module/ui/hooks/useAuth.d.ts +1 -1
  155. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  156. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +2 -2
  157. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +1 -1
  158. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
  159. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +162 -0
  160. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -0
  161. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +13 -52
  162. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
  163. package/lib/typescript/module/ui/session/createSessionClient.d.ts +15 -0
  164. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -0
  165. package/lib/typescript/module/ui/session/index.d.ts +15 -0
  166. package/lib/typescript/module/ui/session/index.d.ts.map +1 -0
  167. package/lib/typescript/module/ui/session/projectSessionState.d.ts +45 -0
  168. package/lib/typescript/module/ui/session/projectSessionState.d.ts.map +1 -0
  169. package/lib/typescript/module/ui/session/sessionClientHost.d.ts +14 -0
  170. package/lib/typescript/module/ui/session/sessionClientHost.d.ts.map +1 -0
  171. package/lib/typescript/module/ui/session/tokenTransport.d.ts +24 -0
  172. package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -0
  173. package/lib/typescript/module/ui/utils/activeAuthuser.d.ts +21 -29
  174. package/lib/typescript/module/ui/utils/activeAuthuser.d.ts.map +1 -1
  175. package/lib/typescript/module/ui/utils/sessionHelpers.d.ts +0 -19
  176. package/lib/typescript/module/ui/utils/sessionHelpers.d.ts.map +1 -1
  177. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +51 -19
  178. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
  179. package/package.json +4 -4
  180. package/src/index.ts +9 -8
  181. package/src/ui/components/AccountMenu.tsx +117 -70
  182. package/src/ui/components/AccountSwitcher.tsx +88 -134
  183. package/src/ui/components/ProfileMenu.tsx +158 -162
  184. package/src/ui/components/SignInModal.tsx +5 -5
  185. package/src/ui/context/OxyContext.tsx +560 -475
  186. package/src/ui/context/hooks/useAuthOperations.ts +72 -63
  187. package/src/ui/context/inSessionTokenRefresh.ts +23 -42
  188. package/src/ui/context/silentSessionRestore.ts +12 -26
  189. package/src/ui/hooks/useAuth.ts +4 -3
  190. package/src/ui/hooks/useOxyAuthSession.ts +13 -8
  191. package/src/ui/hooks/useSessionManagement.ts +16 -87
  192. package/src/ui/hooks/useSwitchableAccounts.ts +397 -0
  193. package/src/ui/hooks/useWebSSO.ts +13 -227
  194. package/src/ui/screens/OxyAuthScreen.tsx +2 -2
  195. package/src/ui/session/__tests__/createSessionClient.test.ts +41 -0
  196. package/src/ui/session/__tests__/projectSessionState.test.ts +114 -0
  197. package/src/ui/session/__tests__/sessionClientHost.test.ts +43 -0
  198. package/src/ui/session/__tests__/tokenTransport.test.ts +142 -0
  199. package/src/ui/session/createSessionClient.ts +21 -0
  200. package/src/ui/session/index.ts +19 -0
  201. package/src/ui/session/projectSessionState.ts +85 -0
  202. package/src/ui/session/sessionClientHost.ts +27 -0
  203. package/src/ui/session/tokenTransport.ts +76 -0
  204. package/src/ui/utils/activeAuthuser.ts +34 -76
  205. package/src/ui/utils/sessionHelpers.ts +0 -66
  206. package/src/utils/__tests__/deviceFlowSignIn.test.ts +243 -34
  207. package/src/utils/deviceFlowSignIn.ts +98 -21
  208. package/lib/commonjs/ui/components/accountMenuRows.js +0 -33
  209. package/lib/commonjs/ui/components/accountMenuRows.js.map +0 -1
  210. package/lib/commonjs/ui/hooks/useDeviceAccounts.js +0 -280
  211. package/lib/commonjs/ui/hooks/useDeviceAccounts.js.map +0 -1
  212. package/lib/commonjs/ui/hooks/useSessionSocket.js +0 -203
  213. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +0 -1
  214. package/lib/commonjs/utils/silentGuardKey.js +0 -54
  215. package/lib/commonjs/utils/silentGuardKey.js.map +0 -1
  216. package/lib/module/ui/components/accountMenuRows.js +0 -29
  217. package/lib/module/ui/components/accountMenuRows.js.map +0 -1
  218. package/lib/module/ui/hooks/useDeviceAccounts.js +0 -276
  219. package/lib/module/ui/hooks/useDeviceAccounts.js.map +0 -1
  220. package/lib/module/ui/hooks/useSessionSocket.js +0 -199
  221. package/lib/module/ui/hooks/useSessionSocket.js.map +0 -1
  222. package/lib/module/utils/silentGuardKey.js +0 -49
  223. package/lib/module/utils/silentGuardKey.js.map +0 -1
  224. package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts +0 -34
  225. package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts.map +0 -1
  226. package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts +0 -133
  227. package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts.map +0 -1
  228. package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts +0 -14
  229. package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts.map +0 -1
  230. package/lib/typescript/commonjs/utils/silentGuardKey.d.ts +0 -31
  231. package/lib/typescript/commonjs/utils/silentGuardKey.d.ts.map +0 -1
  232. package/lib/typescript/module/ui/components/accountMenuRows.d.ts +0 -34
  233. package/lib/typescript/module/ui/components/accountMenuRows.d.ts.map +0 -1
  234. package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts +0 -133
  235. package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts.map +0 -1
  236. package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts +0 -14
  237. package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts.map +0 -1
  238. package/lib/typescript/module/utils/silentGuardKey.d.ts +0 -31
  239. package/lib/typescript/module/utils/silentGuardKey.d.ts.map +0 -1
  240. package/src/ui/components/accountMenuRows.ts +0 -48
  241. package/src/ui/hooks/useDeviceAccounts.ts +0 -344
  242. package/src/ui/hooks/useSessionSocket.ts +0 -233
  243. package/src/ui/utils/__tests__/activeAuthuser.test.ts +0 -75
  244. package/src/utils/__tests__/silentGuardKey.test.ts +0 -82
  245. 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
- }