@oxyhq/services 10.2.0 → 10.2.2

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 (246) hide show
  1. package/README.md +9 -13
  2. package/lib/commonjs/index.js +10 -0
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/ui/components/AccountMenu.js +297 -226
  5. package/lib/commonjs/ui/components/AccountMenu.js.map +1 -1
  6. package/lib/commonjs/ui/components/AccountMenuButton.js.map +1 -1
  7. package/lib/commonjs/ui/components/FollowButton.js +3 -1
  8. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  9. package/lib/commonjs/ui/components/OxySignInButton.js +1 -1
  10. package/lib/commonjs/ui/components/SignInModal.js +11 -12
  11. package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
  12. package/lib/commonjs/ui/components/accountMenuRows.js +18 -30
  13. package/lib/commonjs/ui/components/accountMenuRows.js.map +1 -1
  14. package/lib/commonjs/ui/context/OxyContext.js +57 -78
  15. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  16. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +7 -13
  17. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  18. package/lib/commonjs/ui/hooks/useAuth.js +10 -40
  19. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  20. package/lib/commonjs/ui/hooks/useDeviceAccounts.js +285 -0
  21. package/lib/commonjs/ui/hooks/useDeviceAccounts.js.map +1 -0
  22. package/lib/commonjs/ui/hooks/useFollow.js +21 -7
  23. package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
  24. package/lib/commonjs/ui/hooks/useSessionManagement.js +5 -6
  25. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  26. package/lib/commonjs/ui/hooks/useSessionSocket.js +4 -5
  27. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  28. package/lib/commonjs/ui/hooks/useWebSSO.js +1 -1
  29. package/lib/commonjs/ui/navigation/routes.js +7 -7
  30. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  31. package/lib/commonjs/ui/screens/OxyAuthScreen.js +6 -7
  32. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/ProfileScreen.js +18 -20
  34. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +4 -4
  36. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/{karma/KarmaAboutScreen.js → trust/TrustAboutScreen.js} +11 -11
  38. package/lib/commonjs/ui/screens/trust/TrustAboutScreen.js.map +1 -0
  39. package/lib/commonjs/ui/screens/{karma/KarmaCenterScreen.js → trust/TrustCenterScreen.js} +91 -41
  40. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -0
  41. package/lib/commonjs/ui/screens/{karma/KarmaFAQScreen.js → trust/TrustFAQScreen.js} +11 -11
  42. package/lib/commonjs/ui/screens/{karma/KarmaFAQScreen.js.map → trust/TrustFAQScreen.js.map} +1 -1
  43. package/lib/commonjs/ui/screens/{karma/KarmaLeaderboardScreen.js → trust/TrustLeaderboardScreen.js} +63 -42
  44. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -0
  45. package/lib/commonjs/ui/screens/{karma/KarmaRewardsScreen.js → trust/TrustRewardsScreen.js} +54 -54
  46. package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js.map +1 -0
  47. package/lib/commonjs/ui/screens/{karma/KarmaRulesScreen.js → trust/TrustRulesScreen.js} +45 -16
  48. package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js.map +1 -0
  49. package/lib/commonjs/ui/screens/trust/trustTier.js +23 -0
  50. package/lib/commonjs/ui/screens/trust/trustTier.js.map +1 -0
  51. package/lib/commonjs/utils/deviceFlowSignIn.js +12 -10
  52. package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
  53. package/lib/module/index.js +3 -0
  54. package/lib/module/index.js.map +1 -1
  55. package/lib/module/ui/components/AccountMenu.js +297 -226
  56. package/lib/module/ui/components/AccountMenu.js.map +1 -1
  57. package/lib/module/ui/components/AccountMenuButton.js.map +1 -1
  58. package/lib/module/ui/components/FollowButton.js +3 -1
  59. package/lib/module/ui/components/FollowButton.js.map +1 -1
  60. package/lib/module/ui/components/OxySignInButton.js +1 -1
  61. package/lib/module/ui/components/SignInModal.js +11 -12
  62. package/lib/module/ui/components/SignInModal.js.map +1 -1
  63. package/lib/module/ui/components/accountMenuRows.js +18 -30
  64. package/lib/module/ui/components/accountMenuRows.js.map +1 -1
  65. package/lib/module/ui/context/OxyContext.js +58 -79
  66. package/lib/module/ui/context/OxyContext.js.map +1 -1
  67. package/lib/module/ui/context/hooks/useAuthOperations.js +7 -13
  68. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  69. package/lib/module/ui/hooks/useAuth.js +10 -40
  70. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  71. package/lib/module/ui/hooks/useDeviceAccounts.js +281 -0
  72. package/lib/module/ui/hooks/useDeviceAccounts.js.map +1 -0
  73. package/lib/module/ui/hooks/useFollow.js +21 -7
  74. package/lib/module/ui/hooks/useFollow.js.map +1 -1
  75. package/lib/module/ui/hooks/useSessionManagement.js +5 -6
  76. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  77. package/lib/module/ui/hooks/useSessionSocket.js +4 -5
  78. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  79. package/lib/module/ui/hooks/useWebSSO.js +1 -1
  80. package/lib/module/ui/navigation/routes.js +7 -7
  81. package/lib/module/ui/navigation/routes.js.map +1 -1
  82. package/lib/module/ui/screens/OxyAuthScreen.js +6 -7
  83. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  84. package/lib/module/ui/screens/ProfileScreen.js +18 -20
  85. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  86. package/lib/module/ui/screens/WelcomeNewUserScreen.js +4 -4
  87. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  88. package/lib/module/ui/screens/{karma/KarmaAboutScreen.js → trust/TrustAboutScreen.js} +11 -11
  89. package/lib/module/ui/screens/trust/TrustAboutScreen.js.map +1 -0
  90. package/lib/module/ui/screens/{karma/KarmaCenterScreen.js → trust/TrustCenterScreen.js} +92 -42
  91. package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -0
  92. package/lib/module/ui/screens/{karma/KarmaFAQScreen.js → trust/TrustFAQScreen.js} +11 -11
  93. package/lib/module/ui/screens/{karma/KarmaFAQScreen.js.map → trust/TrustFAQScreen.js.map} +1 -1
  94. package/lib/module/ui/screens/{karma/KarmaLeaderboardScreen.js → trust/TrustLeaderboardScreen.js} +63 -42
  95. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -0
  96. package/lib/module/ui/screens/{karma/KarmaRewardsScreen.js → trust/TrustRewardsScreen.js} +54 -54
  97. package/lib/module/ui/screens/trust/TrustRewardsScreen.js.map +1 -0
  98. package/lib/module/ui/screens/{karma/KarmaRulesScreen.js → trust/TrustRulesScreen.js} +45 -16
  99. package/lib/module/ui/screens/trust/TrustRulesScreen.js.map +1 -0
  100. package/lib/module/ui/screens/trust/trustTier.js +19 -0
  101. package/lib/module/ui/screens/trust/trustTier.js.map +1 -0
  102. package/lib/module/utils/deviceFlowSignIn.js +13 -10
  103. package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
  104. package/lib/typescript/commonjs/index.d.ts +3 -1
  105. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +30 -10
  107. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +1 -1
  109. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts +19 -12
  111. package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +3 -3
  113. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  114. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +1 -7
  116. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +3 -7
  118. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts +133 -0
  120. package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/ui/hooks/useFollow.d.ts +1 -1
  122. package/lib/typescript/commonjs/ui/hooks/useFollow.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
  124. package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +1 -1
  126. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  127. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  128. package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/ui/screens/trust/TrustAboutScreen.d.ts +5 -0
  130. package/lib/typescript/commonjs/ui/screens/{karma/KarmaAboutScreen.d.ts.map → trust/TrustAboutScreen.d.ts.map} +1 -1
  131. package/lib/typescript/commonjs/ui/screens/trust/TrustCenterScreen.d.ts +5 -0
  132. package/lib/typescript/commonjs/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -0
  133. package/lib/typescript/{module/ui/screens/karma/KarmaFAQScreen.d.ts → commonjs/ui/screens/trust/TrustFAQScreen.d.ts} +1 -1
  134. package/lib/typescript/commonjs/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts +5 -0
  136. package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/ui/screens/trust/TrustRewardsScreen.d.ts +5 -0
  138. package/lib/typescript/commonjs/ui/screens/{karma/KarmaRewardsScreen.d.ts.map → trust/TrustRewardsScreen.d.ts.map} +1 -1
  139. package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts +5 -0
  140. package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts +9 -0
  142. package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts.map +1 -0
  143. package/lib/typescript/commonjs/ui/types/navigation.d.ts +1 -1
  144. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +11 -9
  145. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
  146. package/lib/typescript/module/index.d.ts +3 -1
  147. package/lib/typescript/module/index.d.ts.map +1 -1
  148. package/lib/typescript/module/ui/components/AccountMenu.d.ts +30 -10
  149. package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +1 -1
  150. package/lib/typescript/module/ui/components/SignInModal.d.ts +1 -1
  151. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  152. package/lib/typescript/module/ui/components/accountMenuRows.d.ts +19 -12
  153. package/lib/typescript/module/ui/components/accountMenuRows.d.ts.map +1 -1
  154. package/lib/typescript/module/ui/context/OxyContext.d.ts +3 -3
  155. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  156. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  157. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +1 -7
  158. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  159. package/lib/typescript/module/ui/hooks/useAuth.d.ts +3 -7
  160. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  161. package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts +133 -0
  162. package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts.map +1 -0
  163. package/lib/typescript/module/ui/hooks/useFollow.d.ts +1 -1
  164. package/lib/typescript/module/ui/hooks/useFollow.d.ts.map +1 -1
  165. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
  166. package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts.map +1 -1
  167. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +1 -1
  168. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  169. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  170. package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
  171. package/lib/typescript/module/ui/screens/trust/TrustAboutScreen.d.ts +5 -0
  172. package/lib/typescript/module/ui/screens/{karma/KarmaAboutScreen.d.ts.map → trust/TrustAboutScreen.d.ts.map} +1 -1
  173. package/lib/typescript/module/ui/screens/trust/TrustCenterScreen.d.ts +5 -0
  174. package/lib/typescript/module/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -0
  175. package/lib/typescript/{commonjs/ui/screens/karma/KarmaFAQScreen.d.ts → module/ui/screens/trust/TrustFAQScreen.d.ts} +1 -1
  176. package/lib/typescript/module/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -0
  177. package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts +5 -0
  178. package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -0
  179. package/lib/typescript/module/ui/screens/trust/TrustRewardsScreen.d.ts +5 -0
  180. package/lib/typescript/module/ui/screens/{karma/KarmaRewardsScreen.d.ts.map → trust/TrustRewardsScreen.d.ts.map} +1 -1
  181. package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts +5 -0
  182. package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -0
  183. package/lib/typescript/module/ui/screens/trust/trustTier.d.ts +9 -0
  184. package/lib/typescript/module/ui/screens/trust/trustTier.d.ts.map +1 -0
  185. package/lib/typescript/module/ui/types/navigation.d.ts +1 -1
  186. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +11 -9
  187. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
  188. package/package.json +2 -2
  189. package/src/index.ts +10 -1
  190. package/src/ui/components/AccountMenu.tsx +311 -253
  191. package/src/ui/components/AccountMenuButton.tsx +2 -2
  192. package/src/ui/components/FollowButton.tsx +2 -2
  193. package/src/ui/components/OxySignInButton.tsx +1 -1
  194. package/src/ui/components/SignInModal.tsx +13 -14
  195. package/src/ui/components/accountMenuRows.ts +28 -40
  196. package/src/ui/context/OxyContext.tsx +71 -74
  197. package/src/ui/context/hooks/useAuthOperations.ts +7 -13
  198. package/src/ui/hooks/useAuth.ts +12 -49
  199. package/src/ui/hooks/useDeviceAccounts.ts +348 -0
  200. package/src/ui/hooks/useFollow.ts +16 -8
  201. package/src/ui/hooks/useSessionManagement.ts +5 -14
  202. package/src/ui/hooks/useSessionSocket.ts +4 -5
  203. package/src/ui/hooks/useWebSSO.ts +1 -1
  204. package/src/ui/navigation/routes.ts +13 -13
  205. package/src/ui/screens/OxyAuthScreen.tsx +6 -7
  206. package/src/ui/screens/ProfileScreen.tsx +15 -17
  207. package/src/ui/screens/WelcomeNewUserScreen.tsx +2 -2
  208. package/src/ui/screens/{karma/KarmaAboutScreen.tsx → trust/TrustAboutScreen.tsx} +15 -15
  209. package/src/ui/screens/{karma/KarmaCenterScreen.tsx → trust/TrustCenterScreen.tsx} +87 -41
  210. package/src/ui/screens/{karma/KarmaFAQScreen.tsx → trust/TrustFAQScreen.tsx} +10 -10
  211. package/src/ui/screens/trust/TrustLeaderboardScreen.tsx +101 -0
  212. package/src/ui/screens/{karma/KarmaRewardsScreen.tsx → trust/TrustRewardsScreen.tsx} +54 -54
  213. package/src/ui/screens/{karma/KarmaRulesScreen.tsx → trust/TrustRulesScreen.tsx} +27 -13
  214. package/src/ui/screens/trust/trustTier.ts +20 -0
  215. package/src/ui/types/navigation.ts +1 -2
  216. package/src/utils/__tests__/deviceFlowSignIn.test.ts +2 -3
  217. package/src/utils/deviceFlowSignIn.ts +18 -12
  218. package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +0 -1
  219. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +0 -1
  220. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +0 -1
  221. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +0 -1
  222. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +0 -1
  223. package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +0 -1
  224. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +0 -1
  225. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +0 -1
  226. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +0 -1
  227. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +0 -1
  228. package/lib/typescript/commonjs/ui/screens/karma/KarmaAboutScreen.d.ts +0 -5
  229. package/lib/typescript/commonjs/ui/screens/karma/KarmaCenterScreen.d.ts +0 -5
  230. package/lib/typescript/commonjs/ui/screens/karma/KarmaCenterScreen.d.ts.map +0 -1
  231. package/lib/typescript/commonjs/ui/screens/karma/KarmaFAQScreen.d.ts.map +0 -1
  232. package/lib/typescript/commonjs/ui/screens/karma/KarmaLeaderboardScreen.d.ts +0 -5
  233. package/lib/typescript/commonjs/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +0 -1
  234. package/lib/typescript/commonjs/ui/screens/karma/KarmaRewardsScreen.d.ts +0 -5
  235. package/lib/typescript/commonjs/ui/screens/karma/KarmaRulesScreen.d.ts +0 -5
  236. package/lib/typescript/commonjs/ui/screens/karma/KarmaRulesScreen.d.ts.map +0 -1
  237. package/lib/typescript/module/ui/screens/karma/KarmaAboutScreen.d.ts +0 -5
  238. package/lib/typescript/module/ui/screens/karma/KarmaCenterScreen.d.ts +0 -5
  239. package/lib/typescript/module/ui/screens/karma/KarmaCenterScreen.d.ts.map +0 -1
  240. package/lib/typescript/module/ui/screens/karma/KarmaFAQScreen.d.ts.map +0 -1
  241. package/lib/typescript/module/ui/screens/karma/KarmaLeaderboardScreen.d.ts +0 -5
  242. package/lib/typescript/module/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +0 -1
  243. package/lib/typescript/module/ui/screens/karma/KarmaRewardsScreen.d.ts +0 -5
  244. package/lib/typescript/module/ui/screens/karma/KarmaRulesScreen.d.ts +0 -5
  245. package/lib/typescript/module/ui/screens/karma/KarmaRulesScreen.d.ts.map +0 -1
  246. package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +0 -88
@@ -20,7 +20,7 @@
20
20
  * Cross-domain SSO:
21
21
  * - Web: Automatic via FedCM (Chrome 108+, Safari 16.4+)
22
22
  * - Native: Automatic via shared Keychain/Account Manager
23
- * - Manual sign-in: signIn() opens popup (web) or auth sheet (native)
23
+ * - Manual sign-in: signIn() redirects to the IdP (web) or opens the auth sheet (native)
24
24
  */
25
25
 
26
26
  import { useCallback, useState } from 'react';
@@ -60,16 +60,12 @@ export interface AuthState {
60
60
  export interface AuthActions {
61
61
  /**
62
62
  * Sign in
63
- * - Web: Opens popup to auth.oxy.so (no public key needed)
63
+ * - Web: Redirects to auth.oxy.so (no public key needed)
64
64
  * - Native: Uses cryptographic identity from keychain
65
65
  *
66
66
  * @param publicKey - Native: identity public key. Ignored on web.
67
- * @param preOpenedPopup - Web only: a popup the caller already opened
68
- * SYNCHRONOUSLY on the raw click via `oxyServices.openBlankPopup()`. This
69
- * keeps Chrome's user-activation alive across any prior `await` and is
70
- * the only reliable way to avoid the popup blocker on cross-domain flows.
71
67
  */
72
- signIn: (publicKey?: string, preOpenedPopup?: Window | null) => Promise<User>;
68
+ signIn: (publicKey?: string) => Promise<User>;
73
69
 
74
70
  /**
75
71
  * Sign out current session
@@ -114,7 +110,6 @@ export function useAuth(): UseAuthReturn {
114
110
  isAuthResolved,
115
111
  error,
116
112
  signIn: oxySignIn,
117
- handlePopupSession,
118
113
  logout,
119
114
  logoutAll,
120
115
  refreshSessions,
@@ -125,7 +120,7 @@ export function useAuth(): UseAuthReturn {
125
120
  openAvatarPicker,
126
121
  } = useOxy();
127
122
 
128
- const signIn = useCallback(async (publicKey?: string, preOpenedPopup?: Window | null): Promise<User> => {
123
+ const signIn = useCallback(async (publicKey?: string): Promise<User> => {
129
124
  // Check if we're on the identity provider itself
130
125
  // Only the IdP has local login forms - other apps are client apps
131
126
  const authWebUrl = oxyServices.config?.authWebUrl;
@@ -136,44 +131,13 @@ export function useAuth(): UseAuthReturn {
136
131
  const isIdentityProvider = isWebBrowser() &&
137
132
  window.location.hostname === idpHostname;
138
133
 
139
- // Web (not on IdP): Use popup-based authentication
140
- // We go straight to popup to preserve the "user gesture" (click event)
141
- // FedCM silent SSO already runs on page load via useWebSSO
142
- // If user is clicking "Sign In", they need interactive auth NOW
134
+ // Web (not on IdP): use the tokenless redirect SSO flow. FedCM / silent SSO
135
+ // already run on page load; an explicit click needs interactive auth.
143
136
  if (isWebBrowser() && !publicKey && !isIdentityProvider) {
144
- // Open the popup SYNCHRONOUSLY before any `await` runs so Chrome's
145
- // transient user-activation is preserved across the async chain
146
- // (React state updates + the `await` into `signInWithPopup`). If the
147
- // caller already pre-opened one (e.g. they did so to be extra safe
148
- // ahead of their own awaits), reuse it.
149
- const popup: Window | null = preOpenedPopup
150
- ?? oxyServices.openBlankPopup?.()
151
- ?? null;
152
- try {
153
- const popupSession = await oxyServices.signInWithPopup?.({
154
- popup,
155
- });
156
- if (popupSession?.user) {
157
- // The popup auth flow fetches full user data, so the session user
158
- // contains full User fields even though the base type is MinimalUserData.
159
- // Cast to the expected shape for handlePopupSession.
160
- const sessionWithUser = {
161
- ...popupSession,
162
- user: popupSession.user as unknown as User,
163
- };
164
- await handlePopupSession(sessionWithUser);
165
- return sessionWithUser.user;
166
- }
167
- throw new Error('Sign-in failed. Please try again.');
168
- } catch (popupError) {
169
- if (popup && !popup.closed) {
170
- popup.close();
171
- }
172
- if (popupError instanceof Error && popupError.message.includes('blocked')) {
173
- throw new Error('Popup blocked. Please allow popups for this site.');
174
- }
175
- throw popupError;
176
- }
137
+ oxyServices.signInWithRedirect?.({
138
+ redirectUri: window.location.href,
139
+ });
140
+ return new Promise<User>(() => undefined);
177
141
  }
178
142
 
179
143
  // Native: Use cryptographic identity
@@ -212,7 +176,7 @@ export function useAuth(): UseAuthReturn {
212
176
  }
213
177
 
214
178
  throw new Error('No authentication method available');
215
- }, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet, oxyServices, handlePopupSession]);
179
+ }, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet, oxyServices]);
216
180
 
217
181
  const signOut = useCallback(async (): Promise<void> => {
218
182
  await logout();
@@ -230,7 +194,7 @@ export function useAuth(): UseAuthReturn {
230
194
  // State
231
195
  user,
232
196
  isAuthenticated,
233
- isLoading,
197
+ isLoading: isLoading || !isAuthResolved,
234
198
  isReady: isTokenReady,
235
199
  isAuthResolved,
236
200
  error,
@@ -247,4 +211,3 @@ export function useAuth(): UseAuthReturn {
247
211
  openAvatarPicker,
248
212
  };
249
213
  }
250
-
@@ -0,0 +1,348 @@
1
+ import { useMemo } from 'react';
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import {
4
+ getAccountDisplayName,
5
+ getAccountFallbackHandle,
6
+ } from '@oxyhq/core';
7
+ import type {
8
+ RefreshAllAccountUser,
9
+ User,
10
+ ClientSession,
11
+ } from '@oxyhq/core';
12
+ import { useOxy } from '../context/OxyContext';
13
+ import { useI18n } from './useI18n';
14
+ import { queryKeys } from './queries/queryKeys';
15
+
16
+ /**
17
+ * The per-account user shape carried by a {@link DeviceAccount}. Either:
18
+ * - the minimal projection returned by `POST /auth/refresh-all`
19
+ * ({@link RefreshAllAccountUser}, the shared apex path), or
20
+ * - the SDK's canonical {@link User} document (the active row on the local
21
+ * fallback path, which already has the full user loaded in `useOxy()`).
22
+ *
23
+ * Both satisfy `getAccountDisplayName`'s `DisplayNameUserShape`, so display
24
+ * resolution is uniform across paths.
25
+ */
26
+ export type DeviceAccountUser = RefreshAllAccountUser | User;
27
+
28
+ /**
29
+ * One signed-in account on this device, fully hydrated for the account
30
+ * chooser. Unlike the old `AccountMenu` behaviour (which only carried the
31
+ * ACTIVE session's user), EVERY entry here carries real per-account
32
+ * `displayName` / `email` / `avatarUrl` / `color`.
33
+ */
34
+ export interface DeviceAccount {
35
+ /** Session id used to switch to this account on native. */
36
+ sessionId: string;
37
+ /**
38
+ * Device-local refresh-cookie slot index (0..N-1), present only on the
39
+ * shared apex path. Web silent-switch (`refreshTokenViaCookie({ authuser }`)
40
+ * keys off this; absent on the local fallback and on native.
41
+ */
42
+ authuser?: number;
43
+ /** Whether this account is the currently-active session. */
44
+ isCurrent: boolean;
45
+ /** Friendly display name (never blank — falls back to a handle/sentinel). */
46
+ displayName: string;
47
+ /**
48
+ * Real account email, or `null` when the account genuinely has none.
49
+ * NEVER a synthesized `username@oxy.so` — a missing email stays `null` and
50
+ * the UI falls back to the `@handle` secondary line.
51
+ */
52
+ email: string | null;
53
+ /** Resolved avatar thumbnail URL, or `undefined` when the account has no avatar. */
54
+ avatarUrl?: string;
55
+ /** Account's preferred Bloom color preset, or `null` when unset. */
56
+ color: string | null;
57
+ /** The underlying per-account user payload (shared projection or full User). */
58
+ user: DeviceAccountUser;
59
+ }
60
+
61
+ export interface UseDeviceAccountsResult {
62
+ /** Every account signed in on this device, current one(s) flagged `isCurrent`. */
63
+ accounts: DeviceAccount[];
64
+ /** True until the first detection settles. */
65
+ isLoading: boolean;
66
+ /** The currently-active session id (mirrors `useOxy().activeSessionId`). */
67
+ currentSessionId: string | null;
68
+ /**
69
+ * `true` when the list came from the shared apex path
70
+ * (`refreshAllSessions()` returned >0 accounts — the cross-domain SSO cookie
71
+ * set on `*.oxy.so`). `false` when it came from the local `useOxy()`
72
+ * fallback (native, or cross-domain web where the apex cookie is absent).
73
+ */
74
+ fromSharedApex: boolean;
75
+ }
76
+
77
+ /**
78
+ * Resolve which entries are the current account, robustly.
79
+ *
80
+ * Primary signal: `entry.sessionId === activeSessionId`. On the web shared-apex
81
+ * path, `refreshAllSessions()` returns SERVER-side session ids that may not
82
+ * equal the locally-stored `activeSessionId` (a different storage namespace /
83
+ * server perspective), so a pure `sessionId` match can flag NO row as current —
84
+ * the bug that surfaced as "Not signed in" even while authenticated.
85
+ *
86
+ * Fallbacks, applied only when the `sessionId` match found nothing AND the user
87
+ * is authenticated:
88
+ * 1. Match the live `user.id` against each entry's per-account user id.
89
+ * 2. If still nothing and there is exactly one account, mark that one current.
90
+ *
91
+ * At most ONE entry is ever marked current.
92
+ *
93
+ * Pure & side-effect free so it is unit-testable without rendering React.
94
+ */
95
+ export function markCurrentAccount(
96
+ accounts: DeviceAccount[],
97
+ activeSessionId: string | null | undefined,
98
+ liveUserId: string | null | undefined,
99
+ isAuthenticated: boolean,
100
+ ): DeviceAccount[] {
101
+ const bySession = accounts.map((account): DeviceAccount => ({
102
+ ...account,
103
+ isCurrent: Boolean(activeSessionId) && account.sessionId === activeSessionId,
104
+ }));
105
+
106
+ if (bySession.some((account) => account.isCurrent) || !isAuthenticated) {
107
+ return bySession;
108
+ }
109
+
110
+ // No row matched by session id: fall back to the live user's id, marking at
111
+ // most the FIRST matching entry current (never more than one).
112
+ if (liveUserId) {
113
+ let matched = false;
114
+ const byUser = bySession.map((account): DeviceAccount => {
115
+ if (!matched && account.user.id === liveUserId) {
116
+ matched = true;
117
+ return { ...account, isCurrent: true };
118
+ }
119
+ return account;
120
+ });
121
+ if (matched) {
122
+ return byUser;
123
+ }
124
+ }
125
+
126
+ // Authenticated, nothing matched, but there is exactly one account → it must
127
+ // be the current one (single-account shared-apex / cross-domain case).
128
+ if (bySession.length === 1) {
129
+ return [{ ...bySession[0], isCurrent: true }];
130
+ }
131
+
132
+ return bySession;
133
+ }
134
+
135
+ /**
136
+ * Resolve every account signed in on this device for the unified account
137
+ * switcher, with real per-account name / email / avatar / color.
138
+ *
139
+ * ## Data sources (web vs native)
140
+ *
141
+ * - **Web on a `*.oxy.so` host** → `oxyServices.refreshAllSessions()` returns
142
+ * the full apex device-account list (identical to what `auth.oxy.so` sees,
143
+ * because the `Domain=oxy.so` refresh cookies reach `api.oxy.so`). Used
144
+ * directly — this is the `fromSharedApex: true` path.
145
+ * - **Cross-domain web (non-`oxy.so` apex)** OR **native (no browser cookies)**
146
+ * → `refreshAllSessions()` yields `{ accounts: [] }` (the apex cookies never
147
+ * reach the request: cross-domain by `Domain`, native by having no cookie
148
+ * jar at all; a 401 is also normalised to `{ accounts: [] }` inside the
149
+ * core mixin). In that case we FALL BACK to the SDK's local multi-account
150
+ * list from `useOxy()` (`sessions` + `activeSessionId` + the active `user`).
151
+ * This is the `fromSharedApex: false` path.
152
+ *
153
+ * The fallback decision is purely data-driven: **if `refreshAllSessions()`
154
+ * returns >0 accounts, use the shared path; otherwise fall back to local
155
+ * `useOxy()` sessions.** No host sniffing is needed — the cookie scoping does
156
+ * the discrimination for us, and the same code path works on native (where the
157
+ * fetch returns `{ accounts: [] }`).
158
+ *
159
+ * ## Why React Query (not a `useRef`/`useState` start-once like the auth app)
160
+ *
161
+ * The auth app's `use-device-accounts.ts` hand-rolls a `startedRef` because it
162
+ * has no React Query. The SDK does. `refreshAllSessions()` ROTATES single-use
163
+ * refresh cookies on every call, so it must run AT MOST ONCE per page load:
164
+ * we model that with `staleTime: Infinity` + `gcTime: Infinity` +
165
+ * `refetchOnWindowFocus/Reconnect/Mount: false` + `retry: false`. React Query
166
+ * dedupes concurrent mounts and caches the single result for the page's
167
+ * lifetime — the exact "run once" guarantee the auth app documents, but
168
+ * without the manual ref/state machinery.
169
+ *
170
+ * ## Validation note (intentionally NO zod re-parse)
171
+ *
172
+ * The auth app's hook calls `fetch` directly, so IT must `safeParse` the wire
173
+ * response with `@oxyhq/contracts`. This hook calls
174
+ * `oxyServices.refreshAllSessions()`, whose core mixin ALREADY validates and
175
+ * normalises the wire response (skips entries missing required fields,
176
+ * normalises `authuser` null→0, builds the `RefreshAllAccountUser` shape) — the
177
+ * mixin is the single source of truth. Re-validating the SDK's own already-typed
178
+ * output here would be redundant double-validation, so we do not re-parse.
179
+ *
180
+ * ## Error handling
181
+ *
182
+ * `refreshAllSessions()` already maps 401/404/abort to `{ accounts: [] }`
183
+ * internally. Any OTHER failure (network, 5xx) propagates as a thrown error and
184
+ * is surfaced by React Query (`query.isError`) — never swallowed. Callers that
185
+ * don't care can ignore it; the hook still returns the local fallback list so
186
+ * the chooser stays usable.
187
+ */
188
+ export function useDeviceAccounts(): UseDeviceAccountsResult {
189
+ const {
190
+ oxyServices,
191
+ sessions,
192
+ activeSessionId,
193
+ user,
194
+ isAuthenticated,
195
+ } = useOxy();
196
+ const { locale } = useI18n();
197
+
198
+ // Stable, per-API-origin query key. `refreshAllSessions` resolves against
199
+ // the session base url derived from `getBaseURL()`, so keying on it scopes
200
+ // the cached result to the API the provider is pointed at.
201
+ const baseURL = oxyServices.getBaseURL();
202
+
203
+ const query = useQuery({
204
+ queryKey: [...queryKeys.accounts.all, 'deviceAccounts', baseURL] as const,
205
+ queryFn: () => oxyServices.refreshAllSessions(),
206
+ // Only attempt the shared apex path while signed in. When logged out
207
+ // there is nothing to enumerate and the fallback (also empty) is used.
208
+ enabled: isAuthenticated,
209
+ // Single-use cookie rotation → run at most once per page load.
210
+ staleTime: Number.POSITIVE_INFINITY,
211
+ gcTime: Number.POSITIVE_INFINITY,
212
+ refetchOnWindowFocus: false,
213
+ refetchOnReconnect: false,
214
+ refetchOnMount: false,
215
+ retry: false,
216
+ });
217
+
218
+ const sharedAccounts = query.data?.accounts ?? [];
219
+ const fromSharedApex = sharedAccounts.length > 0;
220
+
221
+ const accounts = useMemo<DeviceAccount[]>(() => {
222
+ const resolveAvatarUrl = (avatar: string | null | undefined): string | undefined =>
223
+ avatar ? oxyServices.getFileDownloadUrl(avatar, 'thumb') : undefined;
224
+
225
+ // Build a single current-account row from the live `useOxy().user`. Used
226
+ // as a last-resort fallback when neither the shared-apex probe nor the
227
+ // local session store yielded a row but the user IS authenticated (e.g.
228
+ // a cross-domain host where the apex probe returned empty before the
229
+ // local session store hydrated). The signed-in user must ALWAYS be
230
+ // represented. Email is the REAL email or the `@handle` line — never a
231
+ // synthesized `username@oxy.so`.
232
+ const liveUserRow = (): DeviceAccount[] => {
233
+ if (!isAuthenticated || !user || !activeSessionId) {
234
+ return [];
235
+ }
236
+ const displayName = getAccountDisplayName(user, locale);
237
+ const handle = getAccountFallbackHandle(user);
238
+ const secondaryHandle = handle ? `@${handle}` : null;
239
+ return [{
240
+ sessionId: activeSessionId,
241
+ authuser: undefined,
242
+ isCurrent: true,
243
+ displayName,
244
+ email: user.email ?? secondaryHandle,
245
+ avatarUrl: resolveAvatarUrl(user.avatar),
246
+ color: user.color ?? null,
247
+ user,
248
+ }];
249
+ };
250
+
251
+ let built: DeviceAccount[];
252
+
253
+ if (fromSharedApex) {
254
+ // Shared apex path: every entry carries a real per-account user.
255
+ built = sharedAccounts.map((entry): DeviceAccount => {
256
+ // `entry.user` is non-null on the refresh-all path; the core
257
+ // mixin skips entries without a valid user. The fallback below
258
+ // keeps rendering defensive if a server response is incomplete.
259
+ const accountUser: DeviceAccountUser = entry.user ?? {
260
+ id: '',
261
+ username: '',
262
+ };
263
+ const displayName = getAccountDisplayName(accountUser, locale);
264
+ const handle = getAccountFallbackHandle(accountUser);
265
+ const email = entry.user?.email ?? null;
266
+ const secondaryHandle = handle ? `@${handle}` : null;
267
+ return {
268
+ sessionId: entry.sessionId,
269
+ authuser: entry.authuser,
270
+ // Provisional; finalised by `markCurrentAccount` below so the
271
+ // shared-apex path is robust to server/local session-id skew.
272
+ isCurrent: false,
273
+ displayName,
274
+ // Real email, or null (NEVER synthesized). The UI uses the
275
+ // `@handle` line only when email is genuinely absent.
276
+ email: email ?? secondaryHandle,
277
+ avatarUrl: resolveAvatarUrl(entry.user?.avatar),
278
+ color: entry.user?.color ?? null,
279
+ user: accountUser,
280
+ };
281
+ });
282
+ } else {
283
+ // Local fallback path: build from the SDK's multi-session store. The
284
+ // active session row gets the full loaded `user`; inactive fallback
285
+ // rows carry only what the `ClientSession` exposes (no synthesized
286
+ // identity — they show the active user's data only when active).
287
+ built = (sessions ?? []).map((session: ClientSession): DeviceAccount => {
288
+ const isCurrent = session.sessionId === activeSessionId;
289
+ const accountUser: DeviceAccountUser = isCurrent && user
290
+ ? user
291
+ : { id: session.userId ?? '', username: '' };
292
+ const displayName = getAccountDisplayName(accountUser, locale);
293
+ const handle = getAccountFallbackHandle(accountUser);
294
+ const email = isCurrent && user?.email ? user.email : null;
295
+ const secondaryHandle = handle ? `@${handle}` : null;
296
+ const avatar = isCurrent && user ? user.avatar : undefined;
297
+ const color = isCurrent && user?.color ? user.color : null;
298
+ return {
299
+ sessionId: session.sessionId,
300
+ authuser: session.authuser,
301
+ isCurrent,
302
+ displayName,
303
+ email: email ?? secondaryHandle,
304
+ avatarUrl: resolveAvatarUrl(avatar),
305
+ color,
306
+ user: accountUser,
307
+ };
308
+ });
309
+ }
310
+
311
+ // Robust current-account detection: tolerate server/local session-id
312
+ // skew on the shared-apex path by falling back to the live user's id and
313
+ // the single-account heuristic.
314
+ const flagged = markCurrentAccount(
315
+ built,
316
+ activeSessionId,
317
+ user?.id ?? null,
318
+ isAuthenticated,
319
+ );
320
+
321
+ // The signed-in user must ALWAYS be represented. If detection produced
322
+ // an empty list yet the user is authenticated, synthesize a single
323
+ // current row from the live `useOxy().user`.
324
+ if (flagged.length === 0) {
325
+ return liveUserRow();
326
+ }
327
+ return flagged;
328
+ }, [
329
+ fromSharedApex,
330
+ sharedAccounts,
331
+ sessions,
332
+ activeSessionId,
333
+ user,
334
+ isAuthenticated,
335
+ locale,
336
+ oxyServices,
337
+ ]);
338
+
339
+ return {
340
+ accounts,
341
+ // `isLoading` only reflects the shared probe while it's the relevant
342
+ // source. Once we know we're on the fallback (probe settled with 0
343
+ // accounts) the local list is synchronously available.
344
+ isLoading: isAuthenticated && query.isLoading,
345
+ currentSessionId: activeSessionId ?? null,
346
+ fromSharedApex,
347
+ };
348
+ }
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useMemo, useEffect } from 'react';
2
2
  import { useFollowStore } from '../stores/followStore';
3
3
  import { useOxy } from '../context/OxyContext';
4
- import type { OxyServices } from '@oxyhq/core';
4
+ import { logger as loggerUtil, type OxyServices } from '@oxyhq/core';
5
5
  import { useShallow } from 'zustand/react/shallow';
6
6
 
7
7
  /**
@@ -18,7 +18,8 @@ import { useShallow } from 'zustand/react/shallow';
18
18
  * them in selectors would cause unnecessary selector recalculations).
19
19
  */
20
20
  export const useFollow = (userId?: string | string[]) => {
21
- const { oxyServices } = useOxy();
21
+ const { oxyServices, isAuthenticated, isAuthResolved, isTokenReady } = useOxy();
22
+ const canUsePrivateApi = isAuthResolved && isTokenReady && isAuthenticated;
22
23
  const userIds = useMemo(() => (Array.isArray(userId) ? userId : userId ? [userId] : []), [userId]);
23
24
  const isSingleUser = typeof userId === 'string';
24
25
 
@@ -75,9 +76,10 @@ export const useFollow = (userId?: string | string[]) => {
75
76
  // Store actions are accessed via getState() to avoid subscribing to them.
76
77
  const toggleFollow = useCallback(async () => {
77
78
  if (!isSingleUser || !userId) throw new Error('toggleFollow is only available for single user mode');
79
+ if (!canUsePrivateApi) throw new Error('Authentication is required to follow users');
78
80
  const currentlyFollowing = useFollowStore.getState().followingUsers[userId] ?? false;
79
81
  await useFollowStore.getState().toggleFollowUser(userId, oxyServices, currentlyFollowing);
80
- }, [isSingleUser, userId, oxyServices]);
82
+ }, [isSingleUser, userId, canUsePrivateApi, oxyServices]);
81
83
 
82
84
  const setFollowStatus = useCallback((following: boolean) => {
83
85
  if (!isSingleUser || !userId) throw new Error('setFollowStatus is only available for single user mode');
@@ -86,8 +88,9 @@ export const useFollow = (userId?: string | string[]) => {
86
88
 
87
89
  const fetchStatus = useCallback(async () => {
88
90
  if (!isSingleUser || !userId) throw new Error('fetchStatus is only available for single user mode');
91
+ if (!canUsePrivateApi) return;
89
92
  await useFollowStore.getState().fetchFollowStatus(userId, oxyServices);
90
- }, [isSingleUser, userId, oxyServices]);
93
+ }, [isSingleUser, userId, canUsePrivateApi, oxyServices]);
91
94
 
92
95
  const clearError = useCallback(() => {
93
96
  if (!isSingleUser || !userId) throw new Error('clearError is only available for single user mode');
@@ -114,28 +117,33 @@ export const useFollow = (userId?: string | string[]) => {
114
117
  if (!isSingleUser || !userId) return;
115
118
 
116
119
  if ((followerCount === null || followingCount === null) && !isLoadingCounts) {
117
- fetchUserCounts().catch((err: unknown) => console.warn('useFollow: fetchUserCounts failed', err));
120
+ fetchUserCounts().catch((error: unknown) => {
121
+ loggerUtil.warn('useFollow: fetchUserCounts failed', { component: 'useFollow' }, error);
122
+ });
118
123
  }
119
124
  }, [isSingleUser, userId, followerCount, followingCount, isLoadingCounts, fetchUserCounts]);
120
125
 
121
126
  // Multi-user callbacks
122
127
  const toggleFollowForUser = useCallback(async (targetUserId: string) => {
128
+ if (!canUsePrivateApi) throw new Error('Authentication is required to follow users');
123
129
  const currentState = useFollowStore.getState().followingUsers[targetUserId] ?? false;
124
130
  await useFollowStore.getState().toggleFollowUser(targetUserId, oxyServices, currentState);
125
- }, [oxyServices]);
131
+ }, [canUsePrivateApi, oxyServices]);
126
132
 
127
133
  const setFollowStatusForUser = useCallback((targetUserId: string, following: boolean) => {
128
134
  useFollowStore.getState().setFollowingStatus(targetUserId, following);
129
135
  }, []);
130
136
 
131
137
  const fetchStatusForUser = useCallback(async (targetUserId: string) => {
138
+ if (!canUsePrivateApi) return;
132
139
  await useFollowStore.getState().fetchFollowStatus(targetUserId, oxyServices);
133
- }, [oxyServices]);
140
+ }, [canUsePrivateApi, oxyServices]);
134
141
 
135
142
  const fetchAllStatuses = useCallback(async () => {
143
+ if (!canUsePrivateApi) return;
136
144
  const store = useFollowStore.getState();
137
145
  await Promise.all(userIds.map(uid => store.fetchFollowStatus(uid, oxyServices)));
138
- }, [userIds, oxyServices]);
146
+ }, [canUsePrivateApi, userIds, oxyServices]);
139
147
 
140
148
  const clearErrorForUser = useCallback((targetUserId: string) => {
141
149
  useFollowStore.getState().clearFollowError(targetUserId);
@@ -199,7 +199,6 @@ export const useSessionManagement = ({
199
199
 
200
200
  const activateSession = useCallback(
201
201
  async (sessionId: string, user: User): Promise<void> => {
202
- await oxyServices.getTokenBySession(sessionId);
203
202
  setTokenReady?.(true);
204
203
  setActiveSessionId(sessionId);
205
204
  loginSuccess(user);
@@ -207,14 +206,7 @@ export const useSessionManagement = ({
207
206
  await applyLanguagePreference(user);
208
207
  onAuthStateChange?.(user);
209
208
  },
210
- [
211
- applyLanguagePreference,
212
- loginSuccess,
213
- onAuthStateChange,
214
- oxyServices,
215
- saveActiveSessionId,
216
- setTokenReady,
217
- ],
209
+ [applyLanguagePreference, loginSuccess, onAuthStateChange, saveActiveSessionId, setTokenReady],
218
210
  );
219
211
 
220
212
  const removalTimerIdsRef = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
@@ -271,10 +263,10 @@ export const useSessionManagement = ({
271
263
  // `refreshAllSessions` it carries its `authuser` slot index. We
272
264
  // proactively plant that slot's access token via the httpOnly
273
265
  // refresh cookie BEFORE validating, so the bearer-protected
274
- // validate/getCurrentUser calls don't need a token that was never
275
- // in memory (cold reload / different browser tab). The native path
276
- // has no per-session cookies and continues to rely on
277
- // `validateSession` + `getTokenBySession` directly.
266
+ // validate/getCurrentUser calls have the correct in-memory token
267
+ // after a cold reload or account switch in another tab. The native
268
+ // path arrives here only after a bearer has been planted by
269
+ // `claimSessionByToken` or secure shared-session restore.
278
270
  if (isWebBrowser()) {
279
271
  const targetSession = sessionsRef.current.find((s) => s.sessionId === sessionId);
280
272
  const targetAuthuser = targetSession?.authuser;
@@ -449,4 +441,3 @@ export const useSessionManagement = ({
449
441
  };
450
442
  };
451
443
 
452
-
@@ -149,8 +149,7 @@ export function useSessionSocket({
149
149
  // appear in the switch. Anything unknown falls through to `default`,
150
150
  // which is intentionally a no-op for session lifecycle — it only logs
151
151
  // in dev. This guards against future server-side event additions
152
- // (e.g. `session_created`) accidentally triggering sign-out via a
153
- // "legacy fallback" branch.
152
+ // accidentally triggering sign-out.
154
153
  switch (data.type) {
155
154
  case 'session_removed': {
156
155
  if (data.sessionId && onSessionRemovedRef.current) {
@@ -199,9 +198,9 @@ export function useSessionSocket({
199
198
  case 'session_created':
200
199
  case 'session_update': {
201
200
  // Lifecycle event for the current user. Just resync the sessions
202
- // list — never sign out. Historically this branch had a legacy
203
- // fallback that compared `data.sessionId === currentActiveSessionId`
204
- // and signed the user out if true, which was catastrophic: a
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
205
204
  // `session_created` event fired immediately after a successful
206
205
  // sign-in carries the user's NEW (now-active) session id, which
207
206
  // matched and triggered an instant remote sign-out toast on every
@@ -8,7 +8,7 @@
8
8
  * without relying on third-party cookies.
9
9
  *
10
10
  * For browsers without FedCM support, users will need to click a sign-in button
11
- * which triggers a popup-based authentication flow.
11
+ * which triggers redirect authentication.
12
12
  *
13
13
  * This is called automatically by OxyContext on web platforms.
14
14
  *
@@ -28,12 +28,12 @@ export type RouteName =
28
28
  | 'SavesCollections'
29
29
  | 'EditProfileField' // Dedicated screen for editing a single profile field
30
30
  | 'LearnMoreUsernames' // Informational screen about usernames
31
- | 'KarmaCenter'
32
- | 'KarmaLeaderboard'
33
- | 'KarmaRewards'
34
- | 'KarmaRules'
35
- | 'AboutKarma'
36
- | 'KarmaFAQ'
31
+ | 'TrustCenter'
32
+ | 'TrustLeaderboard'
33
+ | 'TrustRewards'
34
+ | 'TrustRules'
35
+ | 'AboutTrust'
36
+ | 'TrustFAQ'
37
37
  | 'FollowersList' // List of user's followers
38
38
  | 'FollowingList' // List of users being followed
39
39
  | 'CreateManagedAccount' // Create a new managed sub-account
@@ -67,13 +67,13 @@ const screenLoaders: Record<RouteName, () => ComponentType<BaseScreenProps>> = {
67
67
  EditProfileField: () => require('../screens/EditProfileFieldScreen').default,
68
68
  // Informational screens
69
69
  LearnMoreUsernames: () => require('../screens/LearnMoreUsernamesScreen').default,
70
- // Karma screens
71
- KarmaCenter: () => require('../screens/karma/KarmaCenterScreen').default,
72
- KarmaLeaderboard: () => require('../screens/karma/KarmaLeaderboardScreen').default,
73
- KarmaRewards: () => require('../screens/karma/KarmaRewardsScreen').default,
74
- KarmaRules: () => require('../screens/karma/KarmaRulesScreen').default,
75
- AboutKarma: () => require('../screens/karma/KarmaAboutScreen').default,
76
- KarmaFAQ: () => require('../screens/karma/KarmaFAQScreen').default,
70
+ // Oxy Trust screens
71
+ TrustCenter: () => require('../screens/trust/TrustCenterScreen').default,
72
+ TrustLeaderboard: () => require('../screens/trust/TrustLeaderboardScreen').default,
73
+ TrustRewards: () => require('../screens/trust/TrustRewardsScreen').default,
74
+ TrustRules: () => require('../screens/trust/TrustRulesScreen').default,
75
+ AboutTrust: () => require('../screens/trust/TrustAboutScreen').default,
76
+ TrustFAQ: () => require('../screens/trust/TrustFAQScreen').default,
77
77
  // User list screens (followers/following)
78
78
  FollowersList: () => require('../screens/FollowersListScreen').default,
79
79
  FollowingList: () => require('../screens/FollowingListScreen').default,