@oxyhq/services 10.1.0 → 10.2.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.
- package/README.md +2 -2
- package/lib/commonjs/index.js +10 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/AccountMenu.js +297 -226
- package/lib/commonjs/ui/components/AccountMenu.js.map +1 -1
- package/lib/commonjs/ui/components/AccountMenuButton.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +1 -1
- package/lib/commonjs/ui/components/SignInModal.js +23 -14
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/components/accountMenuRows.js +18 -30
- package/lib/commonjs/ui/components/accountMenuRows.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +33 -65
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +7 -13
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +9 -39
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js +285 -0
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js.map +1 -0
- package/lib/commonjs/ui/hooks/useSessionManagement.js +5 -6
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +4 -5
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/commonjs/ui/hooks/useWebSSO.js +1 -1
- package/lib/commonjs/ui/navigation/routes.js +7 -7
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +21 -12
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +18 -20
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +4 -4
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/{karma/KarmaAboutScreen.js → trust/TrustAboutScreen.js} +11 -11
- package/lib/commonjs/ui/screens/trust/TrustAboutScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/{karma/KarmaCenterScreen.js → trust/TrustCenterScreen.js} +91 -41
- package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/{karma/KarmaFAQScreen.js → trust/TrustFAQScreen.js} +11 -11
- package/lib/commonjs/ui/screens/{karma/KarmaFAQScreen.js.map → trust/TrustFAQScreen.js.map} +1 -1
- package/lib/commonjs/ui/screens/{karma/KarmaLeaderboardScreen.js → trust/TrustLeaderboardScreen.js} +63 -42
- package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/{karma/KarmaRewardsScreen.js → trust/TrustRewardsScreen.js} +54 -54
- package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/{karma/KarmaRulesScreen.js → trust/TrustRulesScreen.js} +45 -16
- package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/trust/trustTier.js +23 -0
- package/lib/commonjs/ui/screens/trust/trustTier.js.map +1 -0
- package/lib/commonjs/utils/deviceFlowSignIn.js +12 -10
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/AccountMenu.js +297 -226
- package/lib/module/ui/components/AccountMenu.js.map +1 -1
- package/lib/module/ui/components/AccountMenuButton.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +1 -1
- package/lib/module/ui/components/SignInModal.js +23 -14
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/components/accountMenuRows.js +18 -30
- package/lib/module/ui/components/accountMenuRows.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +33 -65
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +7 -13
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/hooks/useAuth.js +9 -39
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/useDeviceAccounts.js +281 -0
- package/lib/module/ui/hooks/useDeviceAccounts.js.map +1 -0
- package/lib/module/ui/hooks/useSessionManagement.js +5 -6
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/hooks/useSessionSocket.js +4 -5
- package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/module/ui/hooks/useWebSSO.js +1 -1
- package/lib/module/ui/navigation/routes.js +7 -7
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +21 -12
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +18 -20
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +4 -4
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/{karma/KarmaAboutScreen.js → trust/TrustAboutScreen.js} +11 -11
- package/lib/module/ui/screens/trust/TrustAboutScreen.js.map +1 -0
- package/lib/module/ui/screens/{karma/KarmaCenterScreen.js → trust/TrustCenterScreen.js} +92 -42
- package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -0
- package/lib/module/ui/screens/{karma/KarmaFAQScreen.js → trust/TrustFAQScreen.js} +11 -11
- package/lib/module/ui/screens/{karma/KarmaFAQScreen.js.map → trust/TrustFAQScreen.js.map} +1 -1
- package/lib/module/ui/screens/{karma/KarmaLeaderboardScreen.js → trust/TrustLeaderboardScreen.js} +63 -42
- package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -0
- package/lib/module/ui/screens/{karma/KarmaRewardsScreen.js → trust/TrustRewardsScreen.js} +54 -54
- package/lib/module/ui/screens/trust/TrustRewardsScreen.js.map +1 -0
- package/lib/module/ui/screens/{karma/KarmaRulesScreen.js → trust/TrustRulesScreen.js} +45 -16
- package/lib/module/ui/screens/trust/TrustRulesScreen.js.map +1 -0
- package/lib/module/ui/screens/trust/trustTier.js +19 -0
- package/lib/module/ui/screens/trust/trustTier.js.map +1 -0
- package/lib/module/utils/deviceFlowSignIn.js +13 -10
- package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +30 -10
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +1 -1
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts +19 -12
- package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +3 -3
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +1 -7
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +3 -7
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts +133 -0
- package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/trust/TrustAboutScreen.d.ts +5 -0
- package/lib/typescript/commonjs/ui/screens/{karma/KarmaAboutScreen.d.ts.map → trust/TrustAboutScreen.d.ts.map} +1 -1
- package/lib/typescript/commonjs/ui/screens/trust/TrustCenterScreen.d.ts +5 -0
- package/lib/typescript/commonjs/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -0
- package/lib/typescript/{module/ui/screens/karma/KarmaFAQScreen.d.ts → commonjs/ui/screens/trust/TrustFAQScreen.d.ts} +1 -1
- package/lib/typescript/commonjs/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts +5 -0
- package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/trust/TrustRewardsScreen.d.ts +5 -0
- package/lib/typescript/commonjs/ui/screens/{karma/KarmaRewardsScreen.d.ts.map → trust/TrustRewardsScreen.d.ts.map} +1 -1
- package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts +5 -0
- package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts +9 -0
- package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +1 -1
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +11 -9
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AccountMenu.d.ts +30 -10
- package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SignInModal.d.ts +1 -1
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/accountMenuRows.d.ts +19 -12
- package/lib/typescript/module/ui/components/accountMenuRows.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +3 -3
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +1 -7
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts +3 -7
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts +133 -0
- package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/trust/TrustAboutScreen.d.ts +5 -0
- package/lib/typescript/module/ui/screens/{karma/KarmaAboutScreen.d.ts.map → trust/TrustAboutScreen.d.ts.map} +1 -1
- package/lib/typescript/module/ui/screens/trust/TrustCenterScreen.d.ts +5 -0
- package/lib/typescript/module/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -0
- package/lib/typescript/{commonjs/ui/screens/karma/KarmaFAQScreen.d.ts → module/ui/screens/trust/TrustFAQScreen.d.ts} +1 -1
- package/lib/typescript/module/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts +5 -0
- package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/trust/TrustRewardsScreen.d.ts +5 -0
- package/lib/typescript/module/ui/screens/{karma/KarmaRewardsScreen.d.ts.map → trust/TrustRewardsScreen.d.ts.map} +1 -1
- package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts +5 -0
- package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/trust/trustTier.d.ts +9 -0
- package/lib/typescript/module/ui/screens/trust/trustTier.d.ts.map +1 -0
- package/lib/typescript/module/ui/types/navigation.d.ts +1 -1
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +11 -9
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +10 -1
- package/src/ui/components/AccountMenu.tsx +311 -253
- package/src/ui/components/AccountMenuButton.tsx +2 -2
- package/src/ui/components/OxySignInButton.tsx +1 -1
- package/src/ui/components/SignInModal.tsx +26 -16
- package/src/ui/components/accountMenuRows.ts +28 -40
- package/src/ui/context/OxyContext.tsx +43 -61
- package/src/ui/context/hooks/useAuthOperations.ts +7 -13
- package/src/ui/hooks/useAuth.ts +11 -48
- package/src/ui/hooks/useDeviceAccounts.ts +348 -0
- package/src/ui/hooks/useSessionManagement.ts +5 -14
- package/src/ui/hooks/useSessionSocket.ts +4 -5
- package/src/ui/hooks/useWebSSO.ts +1 -1
- package/src/ui/navigation/routes.ts +13 -13
- package/src/ui/screens/OxyAuthScreen.tsx +21 -12
- package/src/ui/screens/ProfileScreen.tsx +15 -17
- package/src/ui/screens/WelcomeNewUserScreen.tsx +2 -2
- package/src/ui/screens/{karma/KarmaAboutScreen.tsx → trust/TrustAboutScreen.tsx} +15 -15
- package/src/ui/screens/{karma/KarmaCenterScreen.tsx → trust/TrustCenterScreen.tsx} +87 -41
- package/src/ui/screens/{karma/KarmaFAQScreen.tsx → trust/TrustFAQScreen.tsx} +10 -10
- package/src/ui/screens/trust/TrustLeaderboardScreen.tsx +101 -0
- package/src/ui/screens/{karma/KarmaRewardsScreen.tsx → trust/TrustRewardsScreen.tsx} +54 -54
- package/src/ui/screens/{karma/KarmaRulesScreen.tsx → trust/TrustRulesScreen.tsx} +27 -13
- package/src/ui/screens/trust/trustTier.ts +20 -0
- package/src/ui/types/navigation.ts +1 -2
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +2 -3
- package/src/utils/deviceFlowSignIn.ts +18 -12
- package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +0 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +0 -1
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +0 -1
- package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +0 -1
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +0 -1
- package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +0 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +0 -1
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +0 -1
- package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +0 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +0 -1
- package/lib/typescript/commonjs/ui/screens/karma/KarmaAboutScreen.d.ts +0 -5
- package/lib/typescript/commonjs/ui/screens/karma/KarmaCenterScreen.d.ts +0 -5
- package/lib/typescript/commonjs/ui/screens/karma/KarmaCenterScreen.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/screens/karma/KarmaFAQScreen.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/screens/karma/KarmaLeaderboardScreen.d.ts +0 -5
- package/lib/typescript/commonjs/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/screens/karma/KarmaRewardsScreen.d.ts +0 -5
- package/lib/typescript/commonjs/ui/screens/karma/KarmaRulesScreen.d.ts +0 -5
- package/lib/typescript/commonjs/ui/screens/karma/KarmaRulesScreen.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/karma/KarmaAboutScreen.d.ts +0 -5
- package/lib/typescript/module/ui/screens/karma/KarmaCenterScreen.d.ts +0 -5
- package/lib/typescript/module/ui/screens/karma/KarmaCenterScreen.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/karma/KarmaFAQScreen.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/karma/KarmaLeaderboardScreen.d.ts +0 -5
- package/lib/typescript/module/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/karma/KarmaRewardsScreen.d.ts +0 -5
- package/lib/typescript/module/ui/screens/karma/KarmaRulesScreen.d.ts +0 -5
- package/lib/typescript/module/ui/screens/karma/KarmaRulesScreen.d.ts.map +0 -1
- package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +0 -88
|
@@ -3,7 +3,7 @@ import { useCallback, useRef, useState } from 'react';
|
|
|
3
3
|
import { TouchableOpacity, StyleSheet, Platform, type LayoutChangeEvent } from 'react-native';
|
|
4
4
|
import { getAccountDisplayName } from '@oxyhq/core';
|
|
5
5
|
import Avatar from './Avatar';
|
|
6
|
-
import AccountMenu from './AccountMenu';
|
|
6
|
+
import AccountMenu, { type AccountMenuAnchor } from './AccountMenu';
|
|
7
7
|
import { useOxy } from '../context/OxyContext';
|
|
8
8
|
import { useI18n } from '../hooks/useI18n';
|
|
9
9
|
|
|
@@ -33,7 +33,7 @@ const AccountMenuButton: React.FC<AccountMenuButtonProps> = ({
|
|
|
33
33
|
const { user, oxyServices, isAuthenticated } = useOxy();
|
|
34
34
|
const { t, locale } = useI18n();
|
|
35
35
|
const [open, setOpen] = useState(false);
|
|
36
|
-
const [anchor, setAnchor] = useState<
|
|
36
|
+
const [anchor, setAnchor] = useState<AccountMenuAnchor | null>(null);
|
|
37
37
|
const triggerRef = useRef<React.ComponentRef<typeof TouchableOpacity>>(null);
|
|
38
38
|
|
|
39
39
|
const measureAnchor = useCallback(() => {
|
|
@@ -100,7 +100,7 @@ export const OxySignInButton: React.FC<OxySignInButtonProps> = ({
|
|
|
100
100
|
}, []);
|
|
101
101
|
|
|
102
102
|
// Handle button press
|
|
103
|
-
// - Web:
|
|
103
|
+
// - Web: full-screen modal (dialog UX fits desktop / browser)
|
|
104
104
|
// - Native: bottom sheet (sheet UX fits iOS/Android)
|
|
105
105
|
const handlePress = useCallback(() => {
|
|
106
106
|
if (onPress) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A semi-transparent full-screen modal that displays:
|
|
5
5
|
* - QR code for scanning with Oxy Accounts app
|
|
6
|
-
* - Button to open Oxy Auth
|
|
6
|
+
* - Button to open the Oxy Auth approval page
|
|
7
7
|
*
|
|
8
8
|
* Animates with fade-in effect.
|
|
9
9
|
*/
|
|
@@ -143,12 +143,11 @@ const SignInModal: React.FC = () => {
|
|
|
143
143
|
// this is the device-flow equivalent of OAuth's code-for-token
|
|
144
144
|
// exchange (RFC 8628 §3.4).
|
|
145
145
|
//
|
|
146
|
-
// Without that exchange the SDK has no bearer token and
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
// the normal `switchSession` path.
|
|
146
|
+
// Without that exchange the SDK has no bearer token and the app never
|
|
147
|
+
// becomes authenticated even though the session is authorized server-side.
|
|
148
|
+
// Once `claimSessionByToken` plants the tokens in the HttpService, the rest
|
|
149
|
+
// of the session wiring (state, persistence, language preference) flows
|
|
150
|
+
// through the normal `switchSession` path.
|
|
152
151
|
const handleAuthSuccess = useCallback(async (sessionId: string, sessionToken: string) => {
|
|
153
152
|
if (isProcessingRef.current) return;
|
|
154
153
|
isProcessingRef.current = true;
|
|
@@ -233,8 +232,14 @@ const SignInModal: React.FC = () => {
|
|
|
233
232
|
});
|
|
234
233
|
}, [oxyServices, handleAuthSuccess, cleanup]);
|
|
235
234
|
|
|
236
|
-
// Start polling for authorization
|
|
235
|
+
// Start polling for authorization.
|
|
236
|
+
//
|
|
237
|
+
// Idempotent: if a poll interval is already running this is a no-op, so the
|
|
238
|
+
// `connect_error` path (which also calls this) cannot stack a second
|
|
239
|
+
// interval on top of the always-on poll started in `generateAuthSession`.
|
|
237
240
|
const startPolling = useCallback((sessionToken: string) => {
|
|
241
|
+
if (pollingIntervalRef.current) return;
|
|
242
|
+
|
|
238
243
|
pollingIntervalRef.current = setInterval(async () => {
|
|
239
244
|
if (isProcessingRef.current) return;
|
|
240
245
|
|
|
@@ -292,13 +297,18 @@ const SignInModal: React.FC = () => {
|
|
|
292
297
|
|
|
293
298
|
setAuthSession({ sessionToken, expiresAt });
|
|
294
299
|
setIsWaiting(true);
|
|
300
|
+
// Socket is the fast path; the poll is a transport-independent
|
|
301
|
+
// backstop that guarantees completion even if the socket connects
|
|
302
|
+
// but silently never delivers auth_update (RN transport /
|
|
303
|
+
// idle-timeout).
|
|
295
304
|
connectSocket(sessionToken);
|
|
305
|
+
startPolling(sessionToken);
|
|
296
306
|
} catch (err: unknown) {
|
|
297
307
|
setError((err instanceof Error ? err.message : null) || 'Failed to create auth session');
|
|
298
308
|
} finally {
|
|
299
309
|
setIsLoading(false);
|
|
300
310
|
}
|
|
301
|
-
}, [oxyServices, connectSocket, clientId]);
|
|
311
|
+
}, [oxyServices, connectSocket, startPolling, clientId]);
|
|
302
312
|
|
|
303
313
|
// Generate a cryptographically random session token.
|
|
304
314
|
// 16 random bytes -> 32 hex chars (128 bits of entropy) — unguessable.
|
|
@@ -316,8 +326,8 @@ const SignInModal: React.FC = () => {
|
|
|
316
326
|
return `oxyauth://${authSession.sessionToken}`;
|
|
317
327
|
};
|
|
318
328
|
|
|
319
|
-
// Open Oxy Auth
|
|
320
|
-
const
|
|
329
|
+
// Open Oxy Auth approval page for this device-flow session.
|
|
330
|
+
const handleOpenAuthApproval = useCallback(async () => {
|
|
321
331
|
if (!authSession) return;
|
|
322
332
|
|
|
323
333
|
const baseURL = oxyServices.getBaseURL();
|
|
@@ -343,7 +353,7 @@ const SignInModal: React.FC = () => {
|
|
|
343
353
|
webUrl.searchParams.set('token', authSession.sessionToken);
|
|
344
354
|
|
|
345
355
|
if (Platform.OS === 'web') {
|
|
346
|
-
// Open
|
|
356
|
+
// Open a separate approval window on web for the device-flow token.
|
|
347
357
|
const width = 500;
|
|
348
358
|
const height = 650;
|
|
349
359
|
const screenWidth = window.screen?.width ?? width;
|
|
@@ -353,8 +363,8 @@ const SignInModal: React.FC = () => {
|
|
|
353
363
|
|
|
354
364
|
window.open(
|
|
355
365
|
webUrl.toString(),
|
|
356
|
-
'oxy-auth-
|
|
357
|
-
`width=${width},height=${height},left=${left},top=${top}
|
|
366
|
+
'oxy-auth-approval',
|
|
367
|
+
`width=${width},height=${height},left=${left},top=${top}`
|
|
358
368
|
);
|
|
359
369
|
} else {
|
|
360
370
|
// Open in browser on native
|
|
@@ -438,9 +448,9 @@ const SignInModal: React.FC = () => {
|
|
|
438
448
|
<View style={[styles.divider, { backgroundColor: 'rgba(255,255,255,0.3)' }]} />
|
|
439
449
|
</View>
|
|
440
450
|
|
|
441
|
-
{/* Open
|
|
451
|
+
{/* Open approval window */}
|
|
442
452
|
<Button
|
|
443
|
-
onPress={
|
|
453
|
+
onPress={handleOpenAuthApproval}
|
|
444
454
|
icon={<OxyLogo variant="icon" size={20} fillColor={theme.colors.card} />}
|
|
445
455
|
>
|
|
446
456
|
Open Oxy Auth
|
|
@@ -1,60 +1,48 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { getAccountDisplayName, getAccountFallbackHandle } from '@oxyhq/core';
|
|
1
|
+
import type { DeviceAccount, DeviceAccountUser } from '../hooks/useDeviceAccounts';
|
|
3
2
|
|
|
4
3
|
export interface AccountRow {
|
|
5
4
|
sessionId: string;
|
|
5
|
+
/** Device-local refresh-cookie slot index (web silent-switch). */
|
|
6
|
+
authuser?: number;
|
|
6
7
|
isActive: boolean;
|
|
7
8
|
displayName: string;
|
|
8
9
|
secondary: string | null;
|
|
9
10
|
avatarUri?: string;
|
|
10
|
-
user:
|
|
11
|
+
user: DeviceAccountUser | null;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export interface BuildAccountRowsInput {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Per-device account entries from {@link useDeviceAccounts}. Each entry
|
|
17
|
+
* already carries real per-account `displayName` / `email` / `avatarUrl` /
|
|
18
|
+
* `color`, so EVERY row (not just the active one) renders full identity.
|
|
19
|
+
*/
|
|
20
|
+
accounts: DeviceAccount[];
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Pure builder for `AccountMenu` rows. Extracted so the multi-account display
|
|
23
25
|
* logic can be unit-tested without rendering React Native.
|
|
24
26
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* Maps each {@link DeviceAccount} (sourced from `useDeviceAccounts()`, which
|
|
28
|
+
* hydrates EVERY account with real name/email/avatar/color from the shared
|
|
29
|
+
* apex `refresh-all` path or the local fallback) into an `AccountRow`.
|
|
30
|
+
*
|
|
31
|
+
* `secondary` is the account's real email when present; otherwise it falls
|
|
32
|
+
* back to the `@handle` line. A missing email is NEVER synthesized into a fake
|
|
33
|
+
* `username@oxy.so` — the device-account layer already resolved `email` to the
|
|
34
|
+
* real value or the `@handle` fallback.
|
|
29
35
|
*/
|
|
30
36
|
export function buildAccountRows({
|
|
31
|
-
|
|
32
|
-
activeSessionId,
|
|
33
|
-
user,
|
|
34
|
-
locale,
|
|
35
|
-
getAvatarUrl,
|
|
37
|
+
accounts,
|
|
36
38
|
}: BuildAccountRowsInput): AccountRow[] {
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
?? (handle && candidate?.username ? `@${handle}` : handle)
|
|
47
|
-
?? null;
|
|
48
|
-
const avatarUri = candidate?.avatar
|
|
49
|
-
? getAvatarUrl(candidate.avatar)
|
|
50
|
-
: undefined;
|
|
51
|
-
return {
|
|
52
|
-
sessionId: session.sessionId,
|
|
53
|
-
isActive,
|
|
54
|
-
displayName,
|
|
55
|
-
secondary,
|
|
56
|
-
avatarUri,
|
|
57
|
-
user: isActive ? user ?? null : null,
|
|
58
|
-
};
|
|
59
|
-
});
|
|
39
|
+
return accounts.map((account: DeviceAccount): AccountRow => ({
|
|
40
|
+
sessionId: account.sessionId,
|
|
41
|
+
authuser: account.authuser,
|
|
42
|
+
isActive: account.isCurrent,
|
|
43
|
+
displayName: account.displayName,
|
|
44
|
+
secondary: account.email,
|
|
45
|
+
avatarUri: account.avatarUrl,
|
|
46
|
+
user: account.user,
|
|
47
|
+
}));
|
|
60
48
|
}
|
|
@@ -89,10 +89,10 @@ export interface OxyContextState {
|
|
|
89
89
|
signIn: (publicKey: string, deviceName?: string) => Promise<User>;
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
* Handle session
|
|
93
|
-
* Updates auth state, persists session to storage
|
|
92
|
+
* Handle a session returned by web SSO.
|
|
93
|
+
* Updates auth state, persists session metadata to storage.
|
|
94
94
|
*/
|
|
95
|
-
|
|
95
|
+
handleWebSession: (session: SessionLoginResponse) => Promise<void>;
|
|
96
96
|
|
|
97
97
|
// Session management
|
|
98
98
|
logout: (targetSessionId?: string) => Promise<void>;
|
|
@@ -206,7 +206,7 @@ function silentColdBootKey(oxyServices: OxyServices): string {
|
|
|
206
206
|
* iframe never posts a message, so the full wait would be dead latency in front
|
|
207
207
|
* of the terminal `/sso` bounce. `silentSignIn` already fails fast on a load
|
|
208
208
|
* error via `iframe.onerror`; this caps the no-message case. 2.5s is well above
|
|
209
|
-
* a same-origin iframe handshake
|
|
209
|
+
* a same-origin iframe handshake without blocking cold boot for several seconds.
|
|
210
210
|
*/
|
|
211
211
|
const SILENT_IFRAME_TIMEOUT = 2500;
|
|
212
212
|
|
|
@@ -403,8 +403,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
403
403
|
// plumbing and regardless of which auth code path fired.
|
|
404
404
|
//
|
|
405
405
|
// When the app passed the singleton itself as `oxyServices` (Mention's
|
|
406
|
-
// pattern), `oxyServices === oxyClient`, so we skip the redundant self-write
|
|
407
|
-
// and the subscription is a no-op mirror — fully backward compatible.
|
|
406
|
+
// pattern), `oxyServices === oxyClient`, so we skip the redundant self-write.
|
|
408
407
|
useEffect(() => {
|
|
409
408
|
if (oxyServices === oxyClient) {
|
|
410
409
|
return;
|
|
@@ -635,7 +634,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
635
634
|
// init. Callers MUST invoke this BEFORE any work that can trigger a route
|
|
636
635
|
// navigation (`onAuthStateChange`) — navigation can interrupt a still-pending
|
|
637
636
|
// async write, which is exactly what once left `session_ids` empty after a
|
|
638
|
-
|
|
637
|
+
// successful sign-in. Shared by the FedCM/SSO path and the cold-boot
|
|
639
638
|
// refresh-cookie restore so both land the same durable record.
|
|
640
639
|
const persistSessionDurably = useCallback(async (sessionId: string): Promise<void> => {
|
|
641
640
|
const readyStorage = await getReadyStorage();
|
|
@@ -663,7 +662,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
663
662
|
// Idempotent and monotonic via `authResolvedRef`: the first call wins and the
|
|
664
663
|
// setters fire at most once, so the restore `finally` backstop becomes a no-op
|
|
665
664
|
// once a commit site has already marked resolution. Called from EVERY place a
|
|
666
|
-
// user is actually committed (the FedCM/iframe/
|
|
665
|
+
// user is actually committed (the FedCM/iframe/SSO path
|
|
667
666
|
// `handleWebSSOSession`, the cookie-restore path, and the stored-session path)
|
|
668
667
|
// so the common reload case unblocks the loading gate without sitting behind
|
|
669
668
|
// the remaining (now-skipped) cold-boot steps.
|
|
@@ -691,10 +690,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
691
690
|
// Calls `oxyServices.refreshAllSessions()` → `POST /auth/refresh-all` with
|
|
692
691
|
// `credentials: 'include'`. The server rotates every device-local
|
|
693
692
|
// `oxy_rt_${authuser}` cookie in parallel and returns one entry per valid
|
|
694
|
-
// account (Google-style multi-account).
|
|
695
|
-
// multi-account endpoint, the SDK transparently falls back to the legacy
|
|
696
|
-
// `/auth/refresh` single-account path and wraps the result in the same
|
|
697
|
-
// shape, so this caller doesn't branch.
|
|
693
|
+
// account (Google-style multi-account).
|
|
698
694
|
//
|
|
699
695
|
// Active-account selection: the persisted `oxy_active_authuser` slot index
|
|
700
696
|
// wins when it matches a returned account; otherwise the lowest `authuser`
|
|
@@ -787,23 +783,31 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
787
783
|
// Native (and offline) stored-session restore — the ONLY restore path that
|
|
788
784
|
// runs on React Native, and the web fallback when no cross-domain step won.
|
|
789
785
|
//
|
|
790
|
-
//
|
|
791
|
-
//
|
|
792
|
-
//
|
|
793
|
-
// stored
|
|
794
|
-
// is platform-agnostic and gated by NO `enabled()` predicate so it runs on
|
|
795
|
-
// every platform — on native it is reached unconditionally (every web-only
|
|
796
|
-
// step ahead of it is disabled by `isWebBrowser()`), so native restore is
|
|
797
|
-
// exactly this and nothing else (no FedCM / iframe / refresh-all /
|
|
798
|
-
// handleAuthCallback).
|
|
786
|
+
// Stored-session restore. Web uses this only as a fast local winner after
|
|
787
|
+
// URL-return handling; native uses it as the durable SecureStore path. Native
|
|
788
|
+
// first plants the shared access token from KeyManager, then validates the
|
|
789
|
+
// stored session ids with the bearer already in memory.
|
|
799
790
|
const restoreStoredSession = useCallback(async (): Promise<boolean> => {
|
|
800
791
|
if (!storage) {
|
|
801
792
|
return false;
|
|
802
793
|
}
|
|
803
794
|
|
|
804
795
|
const storedSessionIdsJson = await storage.getItem(storageKeys.sessionIds);
|
|
805
|
-
const
|
|
806
|
-
|
|
796
|
+
const storedSessionIdsFromStorage: string[] = storedSessionIdsJson ? JSON.parse(storedSessionIdsJson) : [];
|
|
797
|
+
let storedActiveSessionId = await storage.getItem(storageKeys.activeSessionId);
|
|
798
|
+
|
|
799
|
+
const nativeSharedSession = !isWebBrowser()
|
|
800
|
+
? await KeyManager.getSharedSession().catch(() => null)
|
|
801
|
+
: null;
|
|
802
|
+
if (nativeSharedSession?.accessToken) {
|
|
803
|
+
oxyServices.setTokens(nativeSharedSession.accessToken);
|
|
804
|
+
storedActiveSessionId = storedActiveSessionId ?? nativeSharedSession.sessionId;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const storedSessionIds = Array.from(new Set([
|
|
808
|
+
...storedSessionIdsFromStorage,
|
|
809
|
+
...(nativeSharedSession?.sessionId ? [nativeSharedSession.sessionId] : []),
|
|
810
|
+
]));
|
|
807
811
|
|
|
808
812
|
let validSessions: ClientSession[] = [];
|
|
809
813
|
|
|
@@ -964,7 +968,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
964
968
|
// web-only step is gated by `isWebBrowser()`, so on native ONLY
|
|
965
969
|
// `stored-session` runs.
|
|
966
970
|
//
|
|
967
|
-
// Order (web):
|
|
971
|
+
// Order (web): SSO return → stored session → FedCM silent
|
|
968
972
|
// (central) → silent iframe (per-apex, the durable reload path) → cookie
|
|
969
973
|
// restore → SSO bounce (terminal).
|
|
970
974
|
//
|
|
@@ -972,8 +976,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
972
976
|
// (`fedcm-silent`, `silent-iframe`, `cookie-restore`). On a normal reload the
|
|
973
977
|
// local bearer validates in one round-trip and wins, so `runColdBoot`
|
|
974
978
|
// short-circuits and never sits through those probes' timeouts (the prior
|
|
975
|
-
// serial sum was a ~20-30s stall). `
|
|
976
|
-
//
|
|
979
|
+
// serial sum was a ~20-30s stall). `sso-return` MUST stay first — it consumes
|
|
980
|
+
// the URL fragment before anything can strip it. On a
|
|
977
981
|
// first visit with no local session, `stored-session` skips and the
|
|
978
982
|
// cross-domain fallback chain (fedcm → iframe → cookie → sso-bounce) runs
|
|
979
983
|
// exactly as before; the per-apex silent iframe still restores a durable
|
|
@@ -1006,26 +1010,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1006
1010
|
const outcome = await runColdBoot<true>({
|
|
1007
1011
|
steps: [
|
|
1008
1012
|
{
|
|
1009
|
-
// 0)
|
|
1010
|
-
// back on this page with `access_token`/`session_id` query params.
|
|
1011
|
-
// `handleAuthCallback` plants the token but returns a PLACEHOLDER
|
|
1012
|
-
// user (empty id), so we hydrate the REAL user via `getCurrentUser`
|
|
1013
|
-
// and commit through `handleWebSSOSession` before claiming a
|
|
1014
|
-
// session — never expose a placeholder user (R4).
|
|
1015
|
-
id: 'redirect',
|
|
1016
|
-
enabled: () => isWebBrowser(),
|
|
1017
|
-
run: async () => {
|
|
1018
|
-
const callbackSession = oxyServices.handleAuthCallback?.();
|
|
1019
|
-
if (!callbackSession || !commitWebSession) {
|
|
1020
|
-
return { kind: 'skip' };
|
|
1021
|
-
}
|
|
1022
|
-
const fullUser = await oxyServices.getCurrentUser();
|
|
1023
|
-
await commitWebSession({ ...callbackSession, user: fullUser });
|
|
1024
|
-
return { kind: 'session', session: true };
|
|
1025
|
-
},
|
|
1026
|
-
},
|
|
1027
|
-
{
|
|
1028
|
-
// 1) Central SSO return: we are landing back from an `auth.oxy.so/sso`
|
|
1013
|
+
// 0) Central SSO return: we are landing back from an `auth.oxy.so/sso`
|
|
1029
1014
|
// bounce with the result in the URL fragment. Parse it, validate the
|
|
1030
1015
|
// CSRF state, exchange the opaque code, and commit. On any non-ok
|
|
1031
1016
|
// outcome `runSsoReturn` sets the per-origin NO_SESSION flag so the
|
|
@@ -1048,8 +1033,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1048
1033
|
// normal reload the local bearer validates in one round-trip and
|
|
1049
1034
|
// wins; `runColdBoot` then short-circuits and never even evaluates
|
|
1050
1035
|
// the slow no-redirect probes that would otherwise time out (the
|
|
1051
|
-
// ~20-30s serial stall). The `
|
|
1052
|
-
//
|
|
1036
|
+
// ~20-30s serial stall). The `sso-return` step stays AHEAD of this
|
|
1037
|
+
// one — it must consume the URL fragment before any
|
|
1053
1038
|
// later step (or anything else) strips it. On a first visit with no
|
|
1054
1039
|
// local session this step skips and the cross-domain fallback chain
|
|
1055
1040
|
// (fedcm → iframe → cookie → sso-bounce) runs exactly as before.
|
|
@@ -1348,8 +1333,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1348
1333
|
});
|
|
1349
1334
|
}, []);
|
|
1350
1335
|
|
|
1351
|
-
// Web SSO:
|
|
1352
|
-
//
|
|
1336
|
+
// Web SSO: automatically check for cross-domain session on web platforms.
|
|
1337
|
+
// Updates all state and persists session metadata.
|
|
1353
1338
|
const handleWebSSOSession = useCallback(async (session: SessionLoginResponse) => {
|
|
1354
1339
|
if (!session?.user || !session?.sessionId) {
|
|
1355
1340
|
if (__DEV__) {
|
|
@@ -1358,12 +1343,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1358
1343
|
return;
|
|
1359
1344
|
}
|
|
1360
1345
|
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
oxyServices.httpService.setTokens(session.accessToken);
|
|
1364
|
-
} else {
|
|
1365
|
-
await oxyServices.getTokenBySession(session.sessionId);
|
|
1346
|
+
if (!session.accessToken) {
|
|
1347
|
+
throw new Error('Session response did not include an access token');
|
|
1366
1348
|
}
|
|
1349
|
+
oxyServices.httpService.setTokens(session.accessToken);
|
|
1367
1350
|
|
|
1368
1351
|
const clientSession = {
|
|
1369
1352
|
sessionId: session.sessionId,
|
|
@@ -1405,14 +1388,14 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1405
1388
|
fullUser = session.user as unknown as User;
|
|
1406
1389
|
}
|
|
1407
1390
|
loginSuccess(fullUser);
|
|
1408
|
-
// A session is now committed (FedCM silent / per-apex iframe /
|
|
1409
|
-
// SSO-return
|
|
1391
|
+
// A session is now committed (FedCM silent / per-apex iframe /
|
|
1392
|
+
// SSO-return all funnel through here) — unblock the auth-resolution
|
|
1410
1393
|
// gate immediately, ahead of the cold-boot chain returning (idempotent).
|
|
1411
1394
|
markAuthResolvedRef.current();
|
|
1412
1395
|
onAuthStateChange?.(fullUser);
|
|
1413
1396
|
}, [oxyServices, updateSessions, setActiveSessionId, loginSuccess, onAuthStateChange, persistSessionDurably]);
|
|
1414
1397
|
|
|
1415
|
-
// Expose `handleWebSSOSession` to the cold-boot FedCM/iframe/
|
|
1398
|
+
// Expose `handleWebSSOSession` to the cold-boot FedCM/iframe/SSO steps,
|
|
1416
1399
|
// which reference it through a ref because they are declared above this
|
|
1417
1400
|
// callback. Assigned synchronously on every render so the ref is populated
|
|
1418
1401
|
// before the cold-boot effect (gated on `storage`/`initialized`) can fire.
|
|
@@ -1670,7 +1653,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1670
1653
|
hasIdentity,
|
|
1671
1654
|
getPublicKey,
|
|
1672
1655
|
signIn,
|
|
1673
|
-
|
|
1656
|
+
handleWebSession: handleWebSSOSession,
|
|
1674
1657
|
logout,
|
|
1675
1658
|
logoutAll,
|
|
1676
1659
|
switchSession: switchSessionForContext,
|
|
@@ -1777,7 +1760,7 @@ const LOADING_STATE: OxyContextState = {
|
|
|
1777
1760
|
hasIdentity: () => Promise.resolve(false),
|
|
1778
1761
|
getPublicKey: () => Promise.resolve(null),
|
|
1779
1762
|
signIn: () => rejectMissingProvider<User>(),
|
|
1780
|
-
|
|
1763
|
+
handleWebSession: () => rejectMissingProvider<void>(),
|
|
1781
1764
|
logout: () => rejectMissingProvider<void>(),
|
|
1782
1765
|
logoutAll: () => rejectMissingProvider<void>(),
|
|
1783
1766
|
switchSession: () => rejectMissingProvider<User>(),
|
|
@@ -1809,4 +1792,3 @@ export const useOxy = (): OxyContextState => {
|
|
|
1809
1792
|
};
|
|
1810
1793
|
|
|
1811
1794
|
export default OxyContext;
|
|
1812
|
-
|
|
@@ -184,12 +184,9 @@ export const useAuthOperations = ({
|
|
|
184
184
|
// Verify and create session. `verifyChallenge` plants the first
|
|
185
185
|
// access token (and refresh token) from the `/auth/verify` response
|
|
186
186
|
// body internally — mirroring `claimSessionByToken` — so the client is
|
|
187
|
-
// authenticated as soon as this resolves.
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
// 401s, which previously broke the entire new-identity onboarding
|
|
191
|
-
// flow. A token-less verify response simply leaves the client without
|
|
192
|
-
// a bearer here rather than triggering that 401.
|
|
187
|
+
// authenticated as soon as this resolves. Session IDs are not public
|
|
188
|
+
// token-minting credentials; a token-less verify response simply leaves
|
|
189
|
+
// the client without a bearer here.
|
|
193
190
|
sessionResponse = await oxyServices.verifyChallenge(
|
|
194
191
|
publicKey,
|
|
195
192
|
challenge,
|
|
@@ -302,11 +299,8 @@ export const useAuthOperations = ({
|
|
|
302
299
|
|
|
303
300
|
try {
|
|
304
301
|
const sessionToLogout = targetSessionId || activeSessionId;
|
|
305
|
-
// Web multi-account
|
|
306
|
-
//
|
|
307
|
-
// the cookie-cleared logout endpoint so the server can `Set-Cookie`
|
|
308
|
-
// an immediate expiry alongside revoking the family. Native and
|
|
309
|
-
// legacy sessions (no `authuser` plumbed yet) fall through to the
|
|
302
|
+
// Web multi-account sessions carry an `authuser` slot index backed by
|
|
303
|
+
// an httpOnly `oxy_rt_${n}` cookie. Native sessions fall through to the
|
|
310
304
|
// bearer-protected endpoint.
|
|
311
305
|
const targetSession = sessionsRef.current.find((s) => s.sessionId === sessionToLogout);
|
|
312
306
|
const targetAuthuser = targetSession?.authuser;
|
|
@@ -378,8 +372,8 @@ export const useAuthOperations = ({
|
|
|
378
372
|
// - Web: "Sign out of all accounts" = sign out every device-local
|
|
379
373
|
// account on THIS device. The cookie endpoint is the only path
|
|
380
374
|
// that can `Set-Cookie` an immediate expiry on every
|
|
381
|
-
// `oxy_rt_${n}`
|
|
382
|
-
//
|
|
375
|
+
// `oxy_rt_${n}` slots AND revoke every presented family server-side.
|
|
376
|
+
// The bearer-protected
|
|
383
377
|
// `logoutAllSessions(activeSessionId)` would only revoke the
|
|
384
378
|
// active user's sessions across devices and leave sibling
|
|
385
379
|
// accounts' cookies sitting on this device — wrong UX for the
|
package/src/ui/hooks/useAuth.ts
CHANGED
|
@@ -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()
|
|
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:
|
|
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
|
|
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
|
|
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):
|
|
140
|
-
//
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
179
|
+
}, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet, oxyServices]);
|
|
216
180
|
|
|
217
181
|
const signOut = useCallback(async (): Promise<void> => {
|
|
218
182
|
await logout();
|
|
@@ -247,4 +211,3 @@ export function useAuth(): UseAuthReturn {
|
|
|
247
211
|
openAvatarPicker,
|
|
248
212
|
};
|
|
249
213
|
}
|
|
250
|
-
|