@oxy.so/services 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/ui/components/OxyAuthChooser.js +95 -140
- package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +8 -1
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/SignInEntryView.js +35 -25
- package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/SignInRequestView.js +2 -3
- package/lib/commonjs/ui/components/authChooser/SignInRequestView.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/SignUpView.js +12 -127
- package/lib/commonjs/ui/components/authChooser/SignUpView.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/signInFailureMessage.js +40 -0
- package/lib/commonjs/ui/components/authChooser/signInFailureMessage.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/types.js +4 -6
- package/lib/commonjs/ui/components/authChooser/types.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +15 -2
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/hooks/accountDialogSnapshot.js +3 -1
- package/lib/commonjs/ui/hooks/accountDialogSnapshot.js.map +1 -1
- package/lib/commonjs/ui/hooks/useProfileEditing.js +14 -0
- package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +225 -9
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +10 -0
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/components/OxyAuthChooser.js +97 -142
- package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/module/ui/components/authChooser/AccountsMenuView.js +8 -1
- package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -1
- package/lib/module/ui/components/authChooser/SignInEntryView.js +35 -25
- package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -1
- package/lib/module/ui/components/authChooser/SignInRequestView.js +2 -3
- package/lib/module/ui/components/authChooser/SignInRequestView.js.map +1 -1
- package/lib/module/ui/components/authChooser/SignUpView.js +14 -129
- package/lib/module/ui/components/authChooser/SignUpView.js.map +1 -1
- package/lib/module/ui/components/authChooser/signInFailureMessage.js +36 -0
- package/lib/module/ui/components/authChooser/signInFailureMessage.js.map +1 -0
- package/lib/module/ui/components/authChooser/types.js +4 -6
- package/lib/module/ui/components/authChooser/types.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +15 -2
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/hooks/accountDialogSnapshot.js +3 -1
- package/lib/module/ui/hooks/accountDialogSnapshot.js.map +1 -1
- package/lib/module/ui/hooks/useProfileEditing.js +14 -0
- package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/module/ui/screens/EditProfileFieldScreen.js +225 -9
- package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/module/ui/screens/ManageAccountScreen.js +12 -2
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +3 -4
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts +11 -12
- package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts +10 -13
- package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/signInFailureMessage.d.ts +16 -0
- package/lib/typescript/commonjs/ui/components/authChooser/signInFailureMessage.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +11 -13
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +3 -2
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +20 -2
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +3 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts +1 -0
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +3 -4
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts +11 -12
- package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts +10 -13
- package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/signInFailureMessage.d.ts +16 -0
- package/lib/typescript/module/ui/components/authChooser/signInFailureMessage.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/types.d.ts +11 -13
- package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +3 -2
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +20 -2
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +3 -0
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts +1 -0
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -1
- package/src/ui/components/OxyAuthChooser.tsx +106 -156
- package/src/ui/components/authChooser/AccountsMenuView.tsx +7 -0
- package/src/ui/components/authChooser/SignInEntryView.tsx +41 -44
- package/src/ui/components/authChooser/SignInRequestView.tsx +1 -4
- package/src/ui/components/authChooser/SignUpView.tsx +16 -114
- package/src/ui/components/authChooser/signInFailureMessage.ts +39 -0
- package/src/ui/components/authChooser/types.ts +11 -13
- package/src/ui/context/OxyContext.tsx +1 -1
- package/src/ui/context/hooks/useAuthOperations.ts +10 -5
- package/src/ui/context/oxyContextTypes.ts +16 -2
- package/src/ui/hooks/accountDialogSnapshot.ts +2 -0
- package/src/ui/hooks/useProfileEditing.ts +15 -0
- package/src/ui/screens/EditProfileFieldScreen.tsx +202 -7
- package/src/ui/screens/ManageAccountScreen.tsx +12 -0
- package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js +0 -50
- package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js.map +0 -1
- package/lib/module/ui/components/authChooser/useUsernameAvailability.js +0 -46
- package/lib/module/ui/components/authChooser/useUsernameAvailability.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts +0 -16
- package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts +0 -16
- package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts.map +0 -1
- package/src/ui/components/authChooser/useUsernameAvailability.ts +0 -60
|
@@ -1,37 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Account creation (`signup` view).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* Web: the account AND its self-custody identity are created at the identity
|
|
5
|
+
* origin (`id.oxy.so`), in the same window a passkey sign-in uses — never on
|
|
6
|
+
* this page, whatever its origin (one identity, two carriers). The sign-in
|
|
7
|
+
* entry's "create an account" link opens that window directly; this view is
|
|
8
|
+
* what `openAccountDialog('signup')` lands on, and offers the same one action.
|
|
9
|
+
*
|
|
10
|
+
* Native: Commons owns identity creation. Deep-link straight in when Commons is
|
|
11
|
+
* installed, else lead with the "Get Commons" acquisition CTA.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type React from 'react';
|
|
15
|
-
import {
|
|
16
|
-
import { TextInput, View } from 'react-native';
|
|
15
|
+
import { View } from 'react-native';
|
|
17
16
|
import { Button } from '@oxy.so/bloom/button';
|
|
18
17
|
import { Text } from '@oxy.so/bloom/typography';
|
|
19
|
-
import type { AccountDialogSnapshot
|
|
20
|
-
import {
|
|
21
|
-
import { Dividerish, SubtleLink } from './primitives';
|
|
18
|
+
import type { AccountDialogSnapshot } from '@oxy.so/core';
|
|
19
|
+
import { SubtleLink } from './primitives';
|
|
22
20
|
import { authChooserStyles as styles } from './styles';
|
|
23
21
|
import type { PasskeyMode, Theme, Translate } from './types';
|
|
24
|
-
import { useUsernameAvailability, type UsernameStatus } from './useUsernameAvailability';
|
|
25
22
|
|
|
26
23
|
interface SignUpViewProps {
|
|
27
24
|
snapshot: AccountDialogSnapshot;
|
|
28
25
|
theme: Theme;
|
|
29
26
|
t: Translate;
|
|
30
|
-
oxyServices: OxyServices;
|
|
31
27
|
passkeyMode: PasskeyMode;
|
|
32
|
-
|
|
33
|
-
createPending: boolean;
|
|
34
|
-
/** Open the auth.oxy.so hub popup (b2) — the 'hub' mode's only path. */
|
|
28
|
+
/** Open the identity-origin window — web's only creation path. */
|
|
35
29
|
onOpenHub: () => void;
|
|
36
30
|
/** Deep-link into Commons' own identity-creation screen (installed only). */
|
|
37
31
|
onCreateIdentityInCommons: () => void;
|
|
@@ -44,22 +38,13 @@ const SignUpView: React.FC<SignUpViewProps> = ({
|
|
|
44
38
|
snapshot,
|
|
45
39
|
theme,
|
|
46
40
|
t,
|
|
47
|
-
oxyServices,
|
|
48
41
|
passkeyMode,
|
|
49
|
-
onCreateWithPasskey,
|
|
50
|
-
createPending,
|
|
51
42
|
onOpenHub,
|
|
52
43
|
onCreateIdentityInCommons,
|
|
53
44
|
onGetCommons,
|
|
54
45
|
onBackToSignIn,
|
|
55
46
|
}) => {
|
|
56
|
-
|
|
57
|
-
const { status: usernameStatus, check: checkUsername } = useUsernameAvailability(oxyServices, t);
|
|
58
|
-
const canSubmit = username.trim().length >= 3 && usernameStatus === 'available' && !createPending;
|
|
59
|
-
|
|
60
|
-
// Native — Commons owns identity creation. Deep-link straight in when it is
|
|
61
|
-
// installed, else lead with the same "Get Commons" acquisition CTA.
|
|
62
|
-
if (!isWebBrowser()) {
|
|
47
|
+
if (passkeyMode === 'none') {
|
|
63
48
|
const commonsInstalled = snapshot.commonsAvailability === 'available';
|
|
64
49
|
return (
|
|
65
50
|
<View style={styles.centeredBlock}>
|
|
@@ -75,100 +60,17 @@ const SignUpView: React.FC<SignUpViewProps> = ({
|
|
|
75
60
|
);
|
|
76
61
|
}
|
|
77
62
|
|
|
78
|
-
// Web, first-party Oxy origin. Commons leads with a "Get Commons" CTA; passkey
|
|
79
|
-
// creation is the de-emphasized alternative underneath for someone who doesn't
|
|
80
|
-
// want to install anything — a secondary button introduced by its own "No
|
|
81
|
-
// Commons?" framing, the same subordination the sign-in surfaces use.
|
|
82
|
-
if (passkeyMode === 'direct') {
|
|
83
|
-
return (
|
|
84
|
-
<View style={styles.signInBlock}>
|
|
85
|
-
<Text style={[styles.qrHeadline, { color: theme.colors.text }]}>
|
|
86
|
-
{t('signup.commonsHeadline')}
|
|
87
|
-
</Text>
|
|
88
|
-
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
89
|
-
{t('signup.commonsExplainer')}
|
|
90
|
-
</Text>
|
|
91
|
-
<Button variant="primary" onPress={onGetCommons} style={styles.primaryButton}>
|
|
92
|
-
{t('accountSwitcher.getCommons')}
|
|
93
|
-
</Button>
|
|
94
|
-
|
|
95
|
-
<Dividerish theme={theme} label={t('signin.or')} />
|
|
96
|
-
|
|
97
|
-
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
98
|
-
{t('signup.passkeyAlternative')}
|
|
99
|
-
</Text>
|
|
100
|
-
<TextInput
|
|
101
|
-
style={[styles.usernameInput, { borderColor: theme.colors.border, color: theme.colors.text }]}
|
|
102
|
-
placeholder={t('signup.usernamePlaceholder')}
|
|
103
|
-
placeholderTextColor={theme.colors.textSecondary}
|
|
104
|
-
value={username}
|
|
105
|
-
autoCapitalize="none"
|
|
106
|
-
autoCorrect={false}
|
|
107
|
-
autoComplete="username"
|
|
108
|
-
onChangeText={(value) => {
|
|
109
|
-
setUsername(value);
|
|
110
|
-
checkUsername(value.trim());
|
|
111
|
-
}}
|
|
112
|
-
testID="signup-username-input"
|
|
113
|
-
/>
|
|
114
|
-
<UsernameStatusText status={usernameStatus} theme={theme} t={t} />
|
|
115
|
-
<Button
|
|
116
|
-
variant="secondary"
|
|
117
|
-
onPress={() => onCreateWithPasskey(username.trim())}
|
|
118
|
-
disabled={!canSubmit}
|
|
119
|
-
style={styles.secondaryButton}
|
|
120
|
-
testID="signup-create-button"
|
|
121
|
-
>
|
|
122
|
-
{createPending ? t('signup.creating') : t('signup.createAccount')}
|
|
123
|
-
</Button>
|
|
124
|
-
<SubtleLink label={t('signup.backToSignInLink')} theme={theme} onPress={onBackToSignIn} />
|
|
125
|
-
</View>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Web, non-Oxy origin (b2): a passkey ceremony cannot run locally here — the
|
|
130
|
-
// same WebAuthn RP-ID boundary the sign-in surfaces hit — so this opens the
|
|
131
|
-
// auth.oxy.so hub popup. Completing EITHER a sign-in OR a fresh sign-up there
|
|
132
|
-
// relays the resulting session back through the SAME poll/socket engine.
|
|
133
63
|
return (
|
|
134
64
|
<View style={styles.centeredBlock}>
|
|
135
65
|
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
136
|
-
{t('
|
|
66
|
+
{t('accountSwitcher.passkeyHint')}
|
|
137
67
|
</Text>
|
|
138
|
-
<Button variant="primary" onPress={onOpenHub} style={styles.primaryButton}>
|
|
139
|
-
{t('signup.
|
|
68
|
+
<Button variant="primary" onPress={onOpenHub} style={styles.primaryButton} testID="signup-open-identity">
|
|
69
|
+
{t('signup.createAccount')}
|
|
140
70
|
</Button>
|
|
141
71
|
<SubtleLink label={t('signup.backToSignInLink')} theme={theme} onPress={onBackToSignIn} />
|
|
142
72
|
</View>
|
|
143
73
|
);
|
|
144
74
|
};
|
|
145
75
|
|
|
146
|
-
/** The inline availability verdict. Network FAILURES are toasted, never shown here. */
|
|
147
|
-
const UsernameStatusText: React.FC<{ status: UsernameStatus; theme: Theme; t: Translate }> = ({
|
|
148
|
-
status,
|
|
149
|
-
theme,
|
|
150
|
-
t,
|
|
151
|
-
}) => {
|
|
152
|
-
if (status === 'idle') return null;
|
|
153
|
-
if (status === 'checking') {
|
|
154
|
-
return (
|
|
155
|
-
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
156
|
-
{t('signup.usernameChecking')}
|
|
157
|
-
</Text>
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
if (status === 'available') {
|
|
161
|
-
return (
|
|
162
|
-
<Text style={[styles.mutedText, { color: theme.colors.success }]}>
|
|
163
|
-
{t('signup.usernameAvailable')}
|
|
164
|
-
</Text>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
return (
|
|
168
|
-
<Text style={[styles.mutedText, { color: theme.colors.error }]}>
|
|
169
|
-
{t('signup.usernameTaken')}
|
|
170
|
-
</Text>
|
|
171
|
-
);
|
|
172
|
-
};
|
|
173
|
-
|
|
174
76
|
export default SignUpView;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The localized toast for a failed sign-in attempt.
|
|
3
|
+
*
|
|
4
|
+
* One responsibility: turn the controller's machine-readable
|
|
5
|
+
* `signIn.failure` into copy. The controller ships no user-facing prose — its
|
|
6
|
+
* raw `signIn.error` is English or a server string, kept for diagnostics only.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SignInFailureReason } from '@oxy.so/core';
|
|
10
|
+
import type { Translate } from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The toast for `failure`, or `null` when there is nothing to report: a
|
|
14
|
+
* `'cancelled'` attempt is the user's own choice (they closed the window), and
|
|
15
|
+
* the surface already offers "Try again" without scolding them for it.
|
|
16
|
+
*/
|
|
17
|
+
export function signInFailureMessage(
|
|
18
|
+
failure: SignInFailureReason | null,
|
|
19
|
+
t: Translate,
|
|
20
|
+
): string | null {
|
|
21
|
+
switch (failure) {
|
|
22
|
+
case 'cancelled':
|
|
23
|
+
return null;
|
|
24
|
+
case 'denied':
|
|
25
|
+
return t('accountSwitcher.signInFailures.denied');
|
|
26
|
+
case 'expired':
|
|
27
|
+
return t('accountSwitcher.signInFailures.expired');
|
|
28
|
+
case 'network':
|
|
29
|
+
return t('accountSwitcher.signInFailures.network');
|
|
30
|
+
case 'not-configured':
|
|
31
|
+
return t('accountSwitcher.signInFailures.notConfigured');
|
|
32
|
+
case 'unsupported-flow':
|
|
33
|
+
return t('accountSwitcher.signInFailures.unsupportedFlow');
|
|
34
|
+
case 'claim-failed':
|
|
35
|
+
return t('accountSwitcher.signInFailures.claimFailed');
|
|
36
|
+
default:
|
|
37
|
+
return t('accountSwitcher.signInFailures.generic');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -95,6 +95,11 @@ export interface AccountsMenuActions {
|
|
|
95
95
|
onPrivacy: () => void;
|
|
96
96
|
onTerms: () => void;
|
|
97
97
|
onSignOut: () => void;
|
|
98
|
+
/**
|
|
99
|
+
* Open the person's identity at the identity origin (recovery phrase,
|
|
100
|
+
* recovery, account deletion). Web only — `undefined` hides the row.
|
|
101
|
+
*/
|
|
102
|
+
onOpenIdentity?: () => void;
|
|
98
103
|
customItems: readonly {
|
|
99
104
|
key: string;
|
|
100
105
|
label: string;
|
|
@@ -104,15 +109,13 @@ export interface AccountsMenuActions {
|
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
/**
|
|
107
|
-
* Where a
|
|
112
|
+
* Where a passkey sign-in or sign-up can run from the current surface.
|
|
108
113
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* `'
|
|
112
|
-
* `'hub'` = any other web origin; it runs in the auth.oxy.so popup instead.
|
|
113
|
-
* `'none'` = native, where Commons owns identity.
|
|
114
|
+
* `'hub'` = web: it runs at the identity origin (`id.oxy.so`) in a popup, on
|
|
115
|
+
* every web origin, first-party or not.
|
|
116
|
+
* `'none'` = native, where Commons owns identity.
|
|
114
117
|
*/
|
|
115
|
-
export type PasskeyMode = '
|
|
118
|
+
export type PasskeyMode = 'hub' | 'none';
|
|
116
119
|
|
|
117
120
|
/**
|
|
118
121
|
* Everything that is NOT the sign-in surface's one primary action.
|
|
@@ -125,17 +128,12 @@ export type PasskeyMode = 'direct' | 'hub' | 'none';
|
|
|
125
128
|
export interface SignInAlternatives {
|
|
126
129
|
/** Whether a WebAuthn ceremony is reachable from this origin at all. */
|
|
127
130
|
passkeyAvailable: boolean;
|
|
128
|
-
/**
|
|
129
|
-
* `true` while a DIRECT ceremony is in flight. The hub-popup variant reports
|
|
130
|
-
* its progress through `snapshot.signIn` instead, so it never sets this.
|
|
131
|
-
*/
|
|
132
|
-
passkeyPending: boolean;
|
|
133
131
|
onSignInWithPasskey: () => void;
|
|
134
132
|
/** Fall back to the cross-device QR handoff (restarts the request). */
|
|
135
133
|
onShowQr: () => void;
|
|
136
134
|
/** Open the Commons store listing / landing page for this platform. */
|
|
137
135
|
onGetCommons: () => void;
|
|
138
|
-
/**
|
|
136
|
+
/** Create an account: the identity-origin window on web, the Commons flow on native. */
|
|
139
137
|
onCreateAccount: () => void;
|
|
140
138
|
}
|
|
141
139
|
|
|
@@ -104,7 +104,7 @@ import {
|
|
|
104
104
|
import { queryKeys } from '../hooks/queries/queryKeys';
|
|
105
105
|
import { useOxyAccountGraph } from './useOxyAccountGraph';
|
|
106
106
|
|
|
107
|
-
export type { OxyContextState, OxyRuntimeProviderProps } from './oxyContextTypes';
|
|
107
|
+
export type { LogoutResult, OxyContextState, OxyRuntimeProviderProps } from './oxyContextTypes';
|
|
108
108
|
|
|
109
109
|
const OxyRuntimeContext = createContext<OxyContextState | null>(null);
|
|
110
110
|
|
|
@@ -2,6 +2,7 @@ import { useCallback } from 'react';
|
|
|
2
2
|
import type { ApiError, AuthStateStore, IdentityBinding, SessionClient, User } from '@oxy.so/core';
|
|
3
3
|
import type { ClientSession, SessionLoginResponse } from '@oxy.so/core';
|
|
4
4
|
import type { OxyRuntime } from '../../runtime';
|
|
5
|
+
import type { LogoutResult } from '../oxyContextTypes';
|
|
5
6
|
import { DeviceManager } from '@oxy.so/core';
|
|
6
7
|
import { fetchSessionsWithFallback } from '../../utils/sessionHelpers';
|
|
7
8
|
import { handleAuthError, isInvalidSessionError } from '../../utils/errorHandlers';
|
|
@@ -56,8 +57,8 @@ export interface UseAuthOperationsOptions {
|
|
|
56
57
|
export interface UseAuthOperationsResult {
|
|
57
58
|
/** Sign in with existing public key */
|
|
58
59
|
signIn: (publicKey: string, deviceName?: string) => Promise<User>;
|
|
59
|
-
/** Logout from current session */
|
|
60
|
-
logout: (targetSessionId?: string) => Promise<
|
|
60
|
+
/** Logout from current session. Never rejects — see {@link LogoutResult}. */
|
|
61
|
+
logout: (targetSessionId?: string) => Promise<LogoutResult>;
|
|
61
62
|
/** Logout from all sessions */
|
|
62
63
|
logoutAll: () => Promise<void>;
|
|
63
64
|
}
|
|
@@ -292,9 +293,9 @@ export const useAuthOperations = ({
|
|
|
292
293
|
* Logout from session
|
|
293
294
|
*/
|
|
294
295
|
const logout = useCallback(
|
|
295
|
-
async (targetSessionId?: string): Promise<
|
|
296
|
+
async (targetSessionId?: string): Promise<LogoutResult> => {
|
|
296
297
|
const activeSessionId = runtime.getSnapshot().activeSessionId;
|
|
297
|
-
if (!activeSessionId) return;
|
|
298
|
+
if (!activeSessionId) return { status: 'signed-out' };
|
|
298
299
|
|
|
299
300
|
const sessionToLogout = targetSessionId || activeSessionId;
|
|
300
301
|
|
|
@@ -326,6 +327,7 @@ export const useAuthOperations = ({
|
|
|
326
327
|
clearPersistedAuthSafe(store, logger);
|
|
327
328
|
await clearSessionState();
|
|
328
329
|
}
|
|
330
|
+
return { status: 'signed-out' };
|
|
329
331
|
} catch (error) {
|
|
330
332
|
const isInvalid = isInvalidSessionError(error);
|
|
331
333
|
|
|
@@ -337,7 +339,7 @@ export const useAuthOperations = ({
|
|
|
337
339
|
// The active session is invalid → full sign-out; clear persisted state.
|
|
338
340
|
clearPersistedAuthSafe(store, logger);
|
|
339
341
|
await clearSessionState();
|
|
340
|
-
return;
|
|
342
|
+
return { status: 'signed-out' };
|
|
341
343
|
}
|
|
342
344
|
|
|
343
345
|
handleAuthError(error, {
|
|
@@ -348,6 +350,9 @@ export const useAuthOperations = ({
|
|
|
348
350
|
logger,
|
|
349
351
|
status: isInvalid ? 401 : undefined,
|
|
350
352
|
});
|
|
353
|
+
// Nothing was revoked and the session is still installed here. Say so,
|
|
354
|
+
// rather than letting a caller close its surface as if it had worked.
|
|
355
|
+
return { status: 'failed', error };
|
|
351
356
|
}
|
|
352
357
|
},
|
|
353
358
|
[
|
|
@@ -11,6 +11,19 @@ import type {
|
|
|
11
11
|
import type { WebAuthMode, WebOAuthSignInResult } from '../oauth/types';
|
|
12
12
|
import type { StorageInterface } from '../utils/storageHelpers';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The outcome of `logout()`. It never rejects: a caller reads this instead.
|
|
16
|
+
*
|
|
17
|
+
* - `signed-out` — the server confirmed the sign-out, or the session was
|
|
18
|
+
* already invalid and was cleaned up locally. Either way, this device no
|
|
19
|
+
* longer holds it.
|
|
20
|
+
* - `failed` — the revocation did not go through (typically no connection).
|
|
21
|
+
* The session is still installed on this device, and nothing about the
|
|
22
|
+
* outcome may be presented as revoked. The error is also reported through
|
|
23
|
+
* the provider's `onError`, as before.
|
|
24
|
+
*/
|
|
25
|
+
export type LogoutResult = { status: 'signed-out' } | { status: 'failed'; error: unknown };
|
|
26
|
+
|
|
14
27
|
export interface OxyContextState {
|
|
15
28
|
user: User | null;
|
|
16
29
|
sessions: ClientSession[];
|
|
@@ -125,10 +138,11 @@ export interface OxyContextState {
|
|
|
125
138
|
*/
|
|
126
139
|
requestOAuthConsent: (options: RequestOAuthConsentOptions) => Promise<OAuthConsentResult>;
|
|
127
140
|
|
|
128
|
-
logout: (targetSessionId?: string) => Promise<
|
|
141
|
+
logout: (targetSessionId?: string) => Promise<LogoutResult>;
|
|
129
142
|
logoutAll: () => Promise<void>;
|
|
130
143
|
switchSession: (sessionId: string) => Promise<User>;
|
|
131
|
-
|
|
144
|
+
/** The same operation as `logout(sessionId)`, with the same {@link LogoutResult}. */
|
|
145
|
+
removeSession: (sessionId: string) => Promise<LogoutResult>;
|
|
132
146
|
refreshSessions: () => Promise<void>;
|
|
133
147
|
setLanguage: (languageId: string) => Promise<void>;
|
|
134
148
|
getDeviceSessions: () => Promise<
|
|
@@ -27,11 +27,13 @@ export const EMPTY_ACCOUNT_DIALOG_SNAPSHOT: AccountDialogSnapshot = {
|
|
|
27
27
|
qrPayload: null,
|
|
28
28
|
expiresAt: null,
|
|
29
29
|
error: null,
|
|
30
|
+
failure: null,
|
|
30
31
|
route: null,
|
|
31
32
|
routeFailed: false,
|
|
32
33
|
pushSentAt: null,
|
|
33
34
|
openedAt: null,
|
|
34
35
|
progress: 'idle',
|
|
36
|
+
attempt: 0,
|
|
35
37
|
},
|
|
36
38
|
commonsAvailability: 'unknown',
|
|
37
39
|
};
|
|
@@ -31,6 +31,7 @@ export interface ProfileUpdateData {
|
|
|
31
31
|
phone?: string;
|
|
32
32
|
address?: string;
|
|
33
33
|
birthday?: string;
|
|
34
|
+
dateOfBirth?: string;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
type ProfileFieldValue = string | ProfileLocation[] | ProfileLinkMetadata[];
|
|
@@ -87,6 +88,13 @@ export const useProfileEditing = () => {
|
|
|
87
88
|
if (updates.birthday !== undefined) {
|
|
88
89
|
updateData.birthday = updates.birthday;
|
|
89
90
|
}
|
|
91
|
+
if (updates.dateOfBirth !== undefined) {
|
|
92
|
+
// '' clears it — `updateData.dateOfBirth` is `string | null`
|
|
93
|
+
// per the contract, and there is no separate blank sentinel for
|
|
94
|
+
// a structured date the way `blankToNull` gives every free-text
|
|
95
|
+
// field.
|
|
96
|
+
updateData.dateOfBirth = updates.dateOfBirth === '' ? null : updates.dateOfBirth;
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
// Handle name field
|
|
92
100
|
if (updates.firstName !== undefined || updates.lastName !== undefined) {
|
|
@@ -142,6 +150,13 @@ export const useProfileEditing = () => {
|
|
|
142
150
|
if (typeof value !== 'string') return false;
|
|
143
151
|
updates.birthday = value;
|
|
144
152
|
break;
|
|
153
|
+
case 'dateOfBirth':
|
|
154
|
+
// '' clears it (the picker's "no complete date selected"
|
|
155
|
+
// state) — `updateUserProfile`'s `dateOfBirth` block treats
|
|
156
|
+
// an empty string the same as `null`.
|
|
157
|
+
if (typeof value !== 'string') return false;
|
|
158
|
+
updates.dateOfBirth = value;
|
|
159
|
+
break;
|
|
145
160
|
case 'location':
|
|
146
161
|
if (!isProfileLocationArray(value)) return false;
|
|
147
162
|
updates.locations = value;
|