yaver-feedback-react-native 0.5.5 → 0.6.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 +26 -4
- package/dist/LoginScreen.d.ts +3 -4
- package/dist/LoginScreen.js +155 -224
- package/dist/YaverFeedback.js +3 -0
- package/dist/auth.d.ts +47 -34
- package/dist/auth.js +138 -53
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -3
- package/dist/types.d.ts +21 -1
- package/package.json +7 -2
- package/src/LoginScreen.tsx +250 -313
- package/src/YaverFeedback.ts +4 -0
- package/src/auth.ts +171 -68
- package/src/index.ts +3 -4
- package/src/types.ts +21 -1
package/README.md
CHANGED
|
@@ -16,11 +16,16 @@ Manual fallback:
|
|
|
16
16
|
npm install yaver-feedback-react-native
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
> **Mobile only.** This SDK targets React Native (iOS + Android). A `yaver-feedback-web` package exists for browser apps but currently expects a bring-your-own auth token — the equivalent in-app sign-in UX (Apple / Google / GitHub / GitLab / Microsoft / email) for the web SDK will land in a future release. Open an issue if you need it sooner.
|
|
20
|
+
|
|
19
21
|
### Peer dependencies
|
|
20
22
|
|
|
21
|
-
For full functionality, install these
|
|
23
|
+
For full functionality, install these peer dependencies:
|
|
22
24
|
|
|
23
25
|
```bash
|
|
26
|
+
# Auth (in-app browser OAuth + native Apple Sign-In)
|
|
27
|
+
npm install expo-web-browser expo-apple-authentication
|
|
28
|
+
|
|
24
29
|
# Device discovery (stored connections)
|
|
25
30
|
npm install @react-native-async-storage/async-storage
|
|
26
31
|
|
|
@@ -31,11 +36,28 @@ npm install react-native-view-shot
|
|
|
31
36
|
npm install react-native-audio-recorder-player
|
|
32
37
|
```
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
`expo-apple-authentication` is optional — if it's missing, "Continue with Apple" falls through to in-app browser OAuth on Android. iOS hosts that want true native Apple Sign-In must also enable the **Sign in with Apple** capability in Xcode.
|
|
40
|
+
|
|
41
|
+
Android hosts must register the OAuth callback in `AndroidManifest.xml`:
|
|
42
|
+
|
|
43
|
+
```xml
|
|
44
|
+
<intent-filter>
|
|
45
|
+
<action android:name="android.intent.action.VIEW" />
|
|
46
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
47
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
48
|
+
<data android:scheme="yaver" android:host="oauth-callback" />
|
|
49
|
+
</intent-filter>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
iOS does not require any URL scheme registration — `ASWebAuthenticationSession` intercepts the redirect inside the auth session.
|
|
53
|
+
|
|
54
|
+
## Quick Start (0.6+: native auth)
|
|
55
|
+
|
|
56
|
+
Starting with **0.6.0**, the SDK login screen mirrors the Yaver mobile app: native Apple Sign-In on iOS, in-app browser OAuth for Google / GitHub / GitLab / Microsoft (no codes, no leaving the app), and inline email + password. The user never sees a verification code or has to switch to a web browser.
|
|
35
57
|
|
|
36
|
-
|
|
58
|
+
Drop in `<FeedbackModal />` and the first time a user triggers feedback without a cached session, they get:
|
|
37
59
|
|
|
38
|
-
1. A full-screen login modal — Apple / Google / GitHub / GitLab / Microsoft
|
|
60
|
+
1. A full-screen login modal — Apple (native) / Google / GitHub / GitLab / Microsoft (in-app browser) / email + password (inline).
|
|
39
61
|
2. A picker of the machines they can reach — **their own dev boxes** plus **guest-shared machines** (where another user invited them).
|
|
40
62
|
|
|
41
63
|
Both screens persist their selection to `AsyncStorage`, so subsequent launches reconnect silently.
|
package/dist/LoginScreen.d.ts
CHANGED
|
@@ -6,9 +6,8 @@ export interface YaverLoginScreenProps {
|
|
|
6
6
|
onCancel?: () => void;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Full-screen in-SDK login.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* available as an inline fallback for headless environments.
|
|
9
|
+
* Full-screen in-SDK login. Mirrors the Yaver mobile app login UX: native
|
|
10
|
+
* Apple Sign-In on iOS, in-app browser OAuth for Google/GitHub/GitLab/
|
|
11
|
+
* Microsoft (no codes, no leaving the app), and inline email/password.
|
|
13
12
|
*/
|
|
14
13
|
export declare const YaverLoginScreen: React.FC<YaverLoginScreenProps>;
|
package/dist/LoginScreen.js
CHANGED
|
@@ -37,281 +37,212 @@ exports.YaverLoginScreen = void 0;
|
|
|
37
37
|
const react_1 = __importStar(require("react"));
|
|
38
38
|
const react_native_1 = require("react-native");
|
|
39
39
|
const auth_1 = require("./auth");
|
|
40
|
-
const PROVIDERS = [
|
|
41
|
-
{ id: 'apple', label: 'Apple', emoji: '' },
|
|
42
|
-
{ id: 'google', label: 'Google', emoji: 'G' },
|
|
43
|
-
{ id: 'github', label: 'GitHub', emoji: '' },
|
|
44
|
-
{ id: 'gitlab', label: 'GitLab', emoji: '' },
|
|
45
|
-
{ id: 'microsoft', label: 'Microsoft', emoji: 'M' },
|
|
46
|
-
];
|
|
47
40
|
/**
|
|
48
|
-
* Full-screen in-SDK login.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* available as an inline fallback for headless environments.
|
|
41
|
+
* Full-screen in-SDK login. Mirrors the Yaver mobile app login UX: native
|
|
42
|
+
* Apple Sign-In on iOS, in-app browser OAuth for Google/GitHub/GitLab/
|
|
43
|
+
* Microsoft (no codes, no leaving the app), and inline email/password.
|
|
52
44
|
*/
|
|
53
45
|
const YaverLoginScreen = ({ onLoggedIn, onCancel, }) => {
|
|
54
|
-
const [
|
|
55
|
-
const [
|
|
56
|
-
const [
|
|
57
|
-
const [starting, setStarting] = (0, react_1.useState)(false);
|
|
58
|
-
const pollRef = (0, react_1.useRef)(null);
|
|
59
|
-
const expiredTimerRef = (0, react_1.useRef)(null);
|
|
60
|
-
const [emailMode, setEmailMode] = (0, react_1.useState)('login');
|
|
46
|
+
const [busyProvider, setBusyProvider] = (0, react_1.useState)(null);
|
|
47
|
+
const [showEmailForm, setShowEmailForm] = (0, react_1.useState)(false);
|
|
48
|
+
const [isSignUp, setIsSignUp] = (0, react_1.useState)(false);
|
|
61
49
|
const [fullName, setFullName] = (0, react_1.useState)('');
|
|
62
50
|
const [email, setEmail] = (0, react_1.useState)('');
|
|
63
51
|
const [password, setPassword] = (0, react_1.useState)('');
|
|
52
|
+
const [confirmPassword, setConfirmPassword] = (0, react_1.useState)('');
|
|
64
53
|
const [emailBusy, setEmailBusy] = (0, react_1.useState)(false);
|
|
65
|
-
const [emailError, setEmailError] = (0, react_1.useState)(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
54
|
+
const [emailError, setEmailError] = (0, react_1.useState)('');
|
|
55
|
+
const finish = async (token) => {
|
|
56
|
+
const user = await (0, auth_1.validateToken)(token);
|
|
57
|
+
await (0, auth_1.saveToken)(token);
|
|
58
|
+
if (user)
|
|
59
|
+
await (0, auth_1.saveUser)(user);
|
|
60
|
+
onLoggedIn(token);
|
|
61
|
+
};
|
|
62
|
+
const handleApple = async () => {
|
|
63
|
+
setBusyProvider('apple');
|
|
64
|
+
try {
|
|
65
|
+
const { token } = await (0, auth_1.signInWithApple)();
|
|
66
|
+
await finish(token);
|
|
69
67
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}, [mode]);
|
|
75
|
-
const stopPolling = () => {
|
|
76
|
-
if (pollRef.current) {
|
|
77
|
-
clearInterval(pollRef.current);
|
|
78
|
-
pollRef.current = null;
|
|
68
|
+
catch (e) {
|
|
69
|
+
const msg = e instanceof Error ? e.message : 'Apple Sign-In failed';
|
|
70
|
+
if (msg !== 'cancelled')
|
|
71
|
+
react_native_1.Alert.alert('Sign In Failed', msg);
|
|
79
72
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
expiredTimerRef.current = null;
|
|
73
|
+
finally {
|
|
74
|
+
setBusyProvider(null);
|
|
83
75
|
}
|
|
84
76
|
};
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
setCodeError(null);
|
|
88
|
-
stopPolling();
|
|
77
|
+
const handleOAuth = async (provider) => {
|
|
78
|
+
setBusyProvider(provider);
|
|
89
79
|
try {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
machineName: `feedback-sdk-${react_native_1.Platform.OS}`,
|
|
93
|
-
preferredProvider,
|
|
94
|
-
});
|
|
95
|
-
setCode(result);
|
|
96
|
-
pollRef.current = setInterval(async () => {
|
|
97
|
-
const poll = await (0, auth_1.pollDeviceCode)(result.deviceCode);
|
|
98
|
-
if (poll.status === 'authorized') {
|
|
99
|
-
stopPolling();
|
|
100
|
-
const user = await (0, auth_1.validateToken)(poll.token);
|
|
101
|
-
await (0, auth_1.saveToken)(poll.token);
|
|
102
|
-
if (user)
|
|
103
|
-
await (0, auth_1.saveUser)(user);
|
|
104
|
-
onLoggedIn(poll.token);
|
|
105
|
-
}
|
|
106
|
-
else if (poll.status === 'expired') {
|
|
107
|
-
stopPolling();
|
|
108
|
-
setCodeError('Kod doldu — tekrar başlat.');
|
|
109
|
-
setCode(null);
|
|
110
|
-
}
|
|
111
|
-
}, 3000);
|
|
112
|
-
expiredTimerRef.current = setTimeout(() => {
|
|
113
|
-
stopPolling();
|
|
114
|
-
setCode(null);
|
|
115
|
-
setCodeError('Kod süresi doldu.');
|
|
116
|
-
}, Math.max(0, result.expiresAt - Date.now()));
|
|
80
|
+
const { token } = await (0, auth_1.signInWithOAuth)(provider);
|
|
81
|
+
await finish(token);
|
|
117
82
|
}
|
|
118
|
-
catch (
|
|
119
|
-
|
|
83
|
+
catch (e) {
|
|
84
|
+
const msg = e instanceof Error ? e.message : 'Sign-in failed';
|
|
85
|
+
if (msg !== 'cancelled')
|
|
86
|
+
react_native_1.Alert.alert('Sign In Failed', msg);
|
|
120
87
|
}
|
|
121
88
|
finally {
|
|
122
|
-
|
|
89
|
+
setBusyProvider(null);
|
|
123
90
|
}
|
|
124
91
|
};
|
|
125
|
-
const openVerification = () => {
|
|
126
|
-
if (!code)
|
|
127
|
-
return;
|
|
128
|
-
react_native_1.Linking.openURL(code.verificationUrl).catch(() => {
|
|
129
|
-
setCodeError('Tarayıcı açılamadı — URL’yi elle aç.');
|
|
130
|
-
});
|
|
131
|
-
};
|
|
132
92
|
const handleEmailSubmit = async () => {
|
|
133
|
-
setEmailError(
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
93
|
+
setEmailError('');
|
|
94
|
+
if (isSignUp) {
|
|
95
|
+
if (!fullName.trim())
|
|
96
|
+
return setEmailError('Full name is required');
|
|
97
|
+
if (password !== confirmPassword)
|
|
98
|
+
return setEmailError('Passwords do not match');
|
|
99
|
+
if (password.length < 8)
|
|
100
|
+
return setEmailError('Password must be at least 8 characters');
|
|
137
101
|
}
|
|
102
|
+
if (!email.trim() || !password)
|
|
103
|
+
return setEmailError('Email and password are required');
|
|
138
104
|
setEmailBusy(true);
|
|
139
105
|
try {
|
|
140
|
-
const result =
|
|
141
|
-
? await (0, auth_1.signupWithEmail)(fullName.trim()
|
|
106
|
+
const result = isSignUp
|
|
107
|
+
? await (0, auth_1.signupWithEmail)(fullName.trim(), email.trim(), password)
|
|
142
108
|
: await (0, auth_1.loginWithEmail)(email.trim(), password);
|
|
143
|
-
|
|
144
|
-
await (0, auth_1.saveToken)(result.token);
|
|
145
|
-
if (user)
|
|
146
|
-
await (0, auth_1.saveUser)(user);
|
|
147
|
-
onLoggedIn(result.token);
|
|
109
|
+
await finish(result.token);
|
|
148
110
|
}
|
|
149
|
-
catch (
|
|
150
|
-
setEmailError(
|
|
111
|
+
catch (e) {
|
|
112
|
+
setEmailError(e instanceof Error ? e.message : 'Something went wrong');
|
|
151
113
|
}
|
|
152
114
|
finally {
|
|
153
115
|
setEmailBusy(false);
|
|
154
116
|
}
|
|
155
117
|
};
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
118
|
+
const renderProvider = (id, label, onPress) => (<react_native_1.Pressable key={id} style={({ pressed }) => [
|
|
119
|
+
styles.button,
|
|
120
|
+
pressed && styles.buttonPressed,
|
|
121
|
+
busyProvider === id && { opacity: 0.6 },
|
|
122
|
+
]} onPress={onPress} disabled={busyProvider !== null}>
|
|
123
|
+
{busyProvider === id ? (<react_native_1.ActivityIndicator color="#e0e0e0"/>) : (<react_native_1.Text style={styles.buttonText}>{label}</react_native_1.Text>)}
|
|
124
|
+
</react_native_1.Pressable>);
|
|
125
|
+
return (<react_native_1.SafeAreaView style={styles.safeArea}>
|
|
126
|
+
<react_native_1.KeyboardAvoidingView style={{ flex: 1 }} behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : undefined}>
|
|
127
|
+
<react_native_1.ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled">
|
|
128
|
+
<react_native_1.View style={styles.header}>
|
|
129
|
+
<react_native_1.Text style={styles.logo}>Yaver</react_native_1.Text>
|
|
130
|
+
<react_native_1.Text style={styles.subtitle}>Sign in to send feedback</react_native_1.Text>
|
|
131
|
+
{onCancel && (<react_native_1.Pressable onPress={onCancel} style={styles.cancel}>
|
|
132
|
+
<react_native_1.Text style={styles.cancelText}>Cancel</react_native_1.Text>
|
|
133
|
+
</react_native_1.Pressable>)}
|
|
134
|
+
</react_native_1.View>
|
|
164
135
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
E-posta
|
|
174
|
-
</react_native_1.Text>
|
|
175
|
-
</react_native_1.TouchableOpacity>
|
|
176
|
-
</react_native_1.View>
|
|
136
|
+
<react_native_1.View style={styles.buttons}>
|
|
137
|
+
{react_native_1.Platform.OS === 'ios'
|
|
138
|
+
? renderProvider('apple', 'Continue with Apple', handleApple)
|
|
139
|
+
: renderProvider('apple', 'Continue with Apple', () => handleOAuth('apple'))}
|
|
140
|
+
{renderProvider('google', 'Continue with Google', () => handleOAuth('google'))}
|
|
141
|
+
{renderProvider('github', 'Continue with GitHub', () => handleOAuth('github'))}
|
|
142
|
+
{renderProvider('gitlab', 'Continue with GitLab', () => handleOAuth('gitlab'))}
|
|
143
|
+
{renderProvider('microsoft', 'Continue with Microsoft', () => handleOAuth('microsoft'))}
|
|
177
144
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
<react_native_1.View style={styles.
|
|
185
|
-
<react_native_1.
|
|
145
|
+
{!showEmailForm ? (<react_native_1.Pressable style={({ pressed }) => [
|
|
146
|
+
styles.button,
|
|
147
|
+
pressed && styles.buttonPressed,
|
|
148
|
+
]} onPress={() => setShowEmailForm(true)} disabled={busyProvider !== null}>
|
|
149
|
+
<react_native_1.Text style={styles.buttonText}>Continue with Email</react_native_1.Text>
|
|
150
|
+
</react_native_1.Pressable>) : (<>
|
|
151
|
+
<react_native_1.View style={styles.divider}>
|
|
152
|
+
<react_native_1.View style={styles.dividerLine}/>
|
|
153
|
+
<react_native_1.Text style={styles.dividerText}>email</react_native_1.Text>
|
|
154
|
+
<react_native_1.View style={styles.dividerLine}/>
|
|
186
155
|
</react_native_1.View>
|
|
187
|
-
<react_native_1.
|
|
188
|
-
|
|
189
|
-
|
|
156
|
+
{isSignUp && (<react_native_1.TextInput style={styles.input} placeholder="Full Name" placeholderTextColor="#666" value={fullName} onChangeText={setFullName} autoCapitalize="words" autoCorrect={false}/>)}
|
|
157
|
+
<react_native_1.TextInput style={styles.input} placeholder="Email" placeholderTextColor="#666" value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" autoCorrect={false}/>
|
|
158
|
+
<react_native_1.TextInput style={styles.input} placeholder="Password" placeholderTextColor="#666" value={password} onChangeText={setPassword} secureTextEntry autoCapitalize="none"/>
|
|
159
|
+
{isSignUp && (<react_native_1.TextInput style={styles.input} placeholder="Confirm Password" placeholderTextColor="#666" value={confirmPassword} onChangeText={setConfirmPassword} secureTextEntry/>)}
|
|
190
160
|
|
|
191
|
-
<react_native_1.Text style={
|
|
192
|
-
Sağlayıcıyı önceden seçmek istersen:
|
|
193
|
-
</react_native_1.Text>
|
|
194
|
-
<react_native_1.View style={styles.providerRow}>
|
|
195
|
-
{PROVIDERS.map((p) => (<react_native_1.TouchableOpacity key={p.id} style={styles.providerButton} onPress={() => beginDeviceCode(p.id)}>
|
|
196
|
-
<react_native_1.Text style={styles.providerButtonText}>{p.label}</react_native_1.Text>
|
|
197
|
-
</react_native_1.TouchableOpacity>))}
|
|
198
|
-
</react_native_1.View>
|
|
199
|
-
</>) : (<react_native_1.TouchableOpacity style={styles.primaryButton} onPress={() => beginDeviceCode(undefined)}>
|
|
200
|
-
<react_native_1.Text style={styles.primaryButtonText}>Tekrar Başlat</react_native_1.Text>
|
|
201
|
-
</react_native_1.TouchableOpacity>)}
|
|
202
|
-
{codeError && <react_native_1.Text style={styles.error}>{codeError}</react_native_1.Text>}
|
|
203
|
-
</react_native_1.View>)}
|
|
161
|
+
{emailError ? (<react_native_1.Text style={styles.errorText}>{emailError}</react_native_1.Text>) : null}
|
|
204
162
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
163
|
+
<react_native_1.Pressable style={({ pressed }) => [
|
|
164
|
+
styles.submitButton,
|
|
165
|
+
pressed && styles.buttonPressed,
|
|
166
|
+
emailBusy && { opacity: 0.6 },
|
|
167
|
+
]} onPress={handleEmailSubmit} disabled={emailBusy}>
|
|
168
|
+
{emailBusy ? (<react_native_1.ActivityIndicator color="#fff"/>) : (<react_native_1.Text style={styles.submitButtonText}>
|
|
169
|
+
{isSignUp ? 'Create Account' : 'Sign In'}
|
|
170
|
+
</react_native_1.Text>)}
|
|
171
|
+
</react_native_1.Pressable>
|
|
214
172
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
173
|
+
<react_native_1.Pressable onPress={() => {
|
|
174
|
+
setIsSignUp(!isSignUp);
|
|
175
|
+
setEmailError('');
|
|
176
|
+
}}>
|
|
177
|
+
<react_native_1.Text style={styles.toggleText}>
|
|
178
|
+
{isSignUp
|
|
179
|
+
? 'Already have an account? Sign In'
|
|
180
|
+
: "Don't have an account? Sign Up"}
|
|
181
|
+
</react_native_1.Text>
|
|
182
|
+
</react_native_1.Pressable>
|
|
218
183
|
</>)}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
<react_native_1.Text style={styles.label}>Parola</react_native_1.Text>
|
|
224
|
-
<react_native_1.TextInput style={styles.input} value={password} onChangeText={setPassword} placeholder="••••••••" placeholderTextColor="#666" secureTextEntry autoCapitalize="none"/>
|
|
225
|
-
|
|
226
|
-
<react_native_1.TouchableOpacity style={styles.primaryButton} onPress={handleEmailSubmit} disabled={emailBusy}>
|
|
227
|
-
{emailBusy ? (<react_native_1.ActivityIndicator color="#fff"/>) : (<react_native_1.Text style={styles.primaryButtonText}>
|
|
228
|
-
{emailMode === 'signup' ? 'Kayıt Ol' : 'Giriş Yap'}
|
|
229
|
-
</react_native_1.Text>)}
|
|
230
|
-
</react_native_1.TouchableOpacity>
|
|
231
|
-
|
|
232
|
-
{emailError && <react_native_1.Text style={styles.error}>{emailError}</react_native_1.Text>}
|
|
233
|
-
</react_native_1.View>)}
|
|
234
|
-
</react_native_1.ScrollView>
|
|
184
|
+
</react_native_1.View>
|
|
185
|
+
</react_native_1.ScrollView>
|
|
186
|
+
</react_native_1.KeyboardAvoidingView>
|
|
235
187
|
</react_native_1.SafeAreaView>);
|
|
236
188
|
};
|
|
237
189
|
exports.YaverLoginScreen = YaverLoginScreen;
|
|
238
190
|
const styles = react_native_1.StyleSheet.create({
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
justifyContent: 'space-between',
|
|
245
|
-
marginBottom: 20,
|
|
191
|
+
safeArea: { flex: 1, backgroundColor: '#1a1a2e' },
|
|
192
|
+
scrollContainer: {
|
|
193
|
+
flexGrow: 1,
|
|
194
|
+
paddingHorizontal: 24,
|
|
195
|
+
justifyContent: 'center',
|
|
246
196
|
},
|
|
247
|
-
|
|
248
|
-
|
|
197
|
+
header: { alignItems: 'center', marginBottom: 40 },
|
|
198
|
+
logo: { fontSize: 44, fontWeight: '800', color: '#e0e0e0', letterSpacing: -1 },
|
|
199
|
+
subtitle: { fontSize: 15, color: '#9ca3af', marginTop: 6 },
|
|
200
|
+
cancel: { position: 'absolute', right: 0, top: 0, padding: 8 },
|
|
249
201
|
cancelText: { color: '#9ca3af', fontSize: 14 },
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
paddingVertical: 12,
|
|
254
|
-
borderRadius: 10,
|
|
255
|
-
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
256
|
-
alignItems: 'center',
|
|
257
|
-
},
|
|
258
|
-
tabActive: { backgroundColor: 'rgba(99,102,241,0.25)' },
|
|
259
|
-
tabText: { color: '#9ca3af', fontSize: 14, fontWeight: '600' },
|
|
260
|
-
tabTextActive: { color: '#e0e0e0' },
|
|
261
|
-
subTab: {
|
|
262
|
-
flex: 1,
|
|
263
|
-
paddingVertical: 8,
|
|
264
|
-
borderRadius: 8,
|
|
265
|
-
alignItems: 'center',
|
|
266
|
-
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
267
|
-
},
|
|
268
|
-
subTabActive: { backgroundColor: 'rgba(99,102,241,0.2)' },
|
|
269
|
-
subTabText: { color: '#e0e0e0', fontSize: 13 },
|
|
270
|
-
section: { marginTop: 4 },
|
|
271
|
-
hint: { color: '#9ca3af', fontSize: 13, lineHeight: 18 },
|
|
272
|
-
codeBox: {
|
|
273
|
-
backgroundColor: 'rgba(99,102,241,0.15)',
|
|
202
|
+
buttons: { gap: 12 },
|
|
203
|
+
button: {
|
|
204
|
+
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
274
205
|
borderWidth: 1,
|
|
275
|
-
borderColor: 'rgba(
|
|
276
|
-
borderRadius: 14,
|
|
277
|
-
paddingVertical: 24,
|
|
278
|
-
alignItems: 'center',
|
|
279
|
-
marginTop: 16,
|
|
280
|
-
marginBottom: 16,
|
|
281
|
-
},
|
|
282
|
-
codeText: {
|
|
283
|
-
color: '#e0e7ff',
|
|
284
|
-
fontSize: 36,
|
|
285
|
-
fontWeight: '800',
|
|
286
|
-
letterSpacing: 6,
|
|
287
|
-
fontVariant: ['tabular-nums'],
|
|
288
|
-
},
|
|
289
|
-
primaryButton: {
|
|
290
|
-
backgroundColor: '#6366f1',
|
|
206
|
+
borderColor: 'rgba(255,255,255,0.12)',
|
|
291
207
|
borderRadius: 12,
|
|
292
208
|
paddingVertical: 14,
|
|
293
209
|
alignItems: 'center',
|
|
294
|
-
|
|
210
|
+
justifyContent: 'center',
|
|
295
211
|
},
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
212
|
+
buttonPressed: { opacity: 0.7 },
|
|
213
|
+
buttonText: { color: '#e0e0e0', fontSize: 15, fontWeight: '600' },
|
|
214
|
+
divider: {
|
|
215
|
+
flexDirection: 'row',
|
|
216
|
+
alignItems: 'center',
|
|
217
|
+
marginTop: 16,
|
|
218
|
+
marginBottom: 8,
|
|
303
219
|
},
|
|
304
|
-
|
|
305
|
-
|
|
220
|
+
dividerLine: { flex: 1, height: 1, backgroundColor: 'rgba(255,255,255,0.12)' },
|
|
221
|
+
dividerText: { marginHorizontal: 14, fontSize: 12, color: '#6b7280' },
|
|
306
222
|
input: {
|
|
307
|
-
backgroundColor: 'rgba(255,255,255,0.
|
|
223
|
+
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
308
224
|
borderWidth: 1,
|
|
309
|
-
borderColor: 'rgba(255,255,255,0.
|
|
310
|
-
borderRadius:
|
|
225
|
+
borderColor: 'rgba(255,255,255,0.12)',
|
|
226
|
+
borderRadius: 12,
|
|
311
227
|
paddingHorizontal: 14,
|
|
312
|
-
paddingVertical:
|
|
228
|
+
paddingVertical: 13,
|
|
313
229
|
color: '#e0e0e0',
|
|
314
230
|
fontSize: 15,
|
|
315
231
|
},
|
|
316
|
-
|
|
232
|
+
errorText: { color: '#ef4444', fontSize: 13, textAlign: 'center' },
|
|
233
|
+
submitButton: {
|
|
234
|
+
backgroundColor: '#6366f1',
|
|
235
|
+
borderRadius: 12,
|
|
236
|
+
paddingVertical: 14,
|
|
237
|
+
alignItems: 'center',
|
|
238
|
+
justifyContent: 'center',
|
|
239
|
+
marginTop: 4,
|
|
240
|
+
},
|
|
241
|
+
submitButtonText: { color: '#fff', fontSize: 15, fontWeight: '700' },
|
|
242
|
+
toggleText: {
|
|
243
|
+
color: '#818cf8',
|
|
244
|
+
fontSize: 14,
|
|
245
|
+
textAlign: 'center',
|
|
246
|
+
marginTop: 4,
|
|
247
|
+
},
|
|
317
248
|
});
|
package/dist/YaverFeedback.js
CHANGED
|
@@ -76,6 +76,9 @@ class YaverFeedback {
|
|
|
76
76
|
convexSiteUrl: cfg.authConvexSiteUrl,
|
|
77
77
|
webBaseUrl: cfg.authWebBaseUrl,
|
|
78
78
|
});
|
|
79
|
+
// Compile-time lockdown: refuse any browser-hop / device-code fallback
|
|
80
|
+
// and force ASWebAuthenticationSession in ephemeral mode for OAuth.
|
|
81
|
+
(0, auth_1.setStrictNativeAuth)(cfg.strictNativeAuth === true);
|
|
79
82
|
// If no explicit convexUrl was set but we have an auth site URL, use it
|
|
80
83
|
// so Discovery.discoverFromConvex() has somewhere to look up the user's
|
|
81
84
|
// machines (works for both LAN-direct and off-LAN relay paths).
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Authentication + device/agent discovery API used by the Yaver Feedback SDK.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* covers what the embedded login/machine-picker flow needs:
|
|
4
|
+
* Mirrors mobile/src/lib/auth.ts:
|
|
6
5
|
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* - Native Apple Sign-In (`POST /auth/apple-native`) on iOS via
|
|
7
|
+
* `expo-apple-authentication`.
|
|
8
|
+
* - In-app browser OAuth for Google/Microsoft/GitHub/GitLab via
|
|
9
|
+
* `expo-web-browser`'s `openAuthSessionAsync` — same callback URL
|
|
10
|
+
* (`yaver://oauth-callback`) the Yaver mobile app uses.
|
|
10
11
|
* - Email / password sign-up + login (no 2FA flow — for SDK simplicity).
|
|
11
|
-
* - Token validation
|
|
12
|
+
* - Token validation.
|
|
12
13
|
* - `/devices/list` → owned + shared (guest) remote dev machines.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Token persistence uses `@react-native-async-storage/async-storage` (already
|
|
18
|
-
* a peer dep). SecureStore is intentionally avoided to keep the SDK portable
|
|
19
|
-
* to any RN host app.
|
|
15
|
+
* Mobile-only. A web equivalent will ship as a separate `yaver-web-feedback`
|
|
16
|
+
* package; do not import this module from a browser bundle.
|
|
20
17
|
*/
|
|
21
18
|
export declare const DEFAULT_CONVEX_SITE_URL = "https://shocking-echidna-394.eu-west-1.convex.site";
|
|
22
19
|
export declare const DEFAULT_WEB_BASE_URL = "https://yaver.io";
|
|
@@ -25,6 +22,13 @@ export declare function configureAuthEndpoints(opts: {
|
|
|
25
22
|
convexSiteUrl?: string;
|
|
26
23
|
webBaseUrl?: string;
|
|
27
24
|
}): void;
|
|
25
|
+
/**
|
|
26
|
+
* Enable strict native auth: refuse any fallback that would redirect the
|
|
27
|
+
* user to an external browser (Safari / Chrome) or show a device code.
|
|
28
|
+
* See FeedbackConfig.strictNativeAuth for rationale.
|
|
29
|
+
*/
|
|
30
|
+
export declare function setStrictNativeAuth(enabled: boolean): void;
|
|
31
|
+
export declare function isStrictNativeAuth(): boolean;
|
|
28
32
|
export declare function getConvexSiteUrl(): string;
|
|
29
33
|
export declare function getWebBaseUrl(): string;
|
|
30
34
|
export type OAuthProvider = 'google' | 'microsoft' | 'apple' | 'github' | 'gitlab';
|
|
@@ -44,31 +48,40 @@ export declare function getSelectedDeviceId(): Promise<string | null>;
|
|
|
44
48
|
export declare function saveSelectedDeviceId(deviceId: string): Promise<void>;
|
|
45
49
|
export declare function clearSelectedDeviceId(): Promise<void>;
|
|
46
50
|
export declare function validateToken(token: string): Promise<User | null>;
|
|
47
|
-
export interface DeviceCodeStart {
|
|
48
|
-
userCode: string;
|
|
49
|
-
deviceCode: string;
|
|
50
|
-
expiresAt: number;
|
|
51
|
-
verificationUrl: string;
|
|
52
|
-
}
|
|
53
51
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
52
|
+
* Sign in with Apple using the native ASAuthorization flow. Requires
|
|
53
|
+
* `expo-apple-authentication` installed and the host app's bundle to have
|
|
54
|
+
* the "Sign in with Apple" capability enabled. iOS only.
|
|
55
|
+
*
|
|
56
|
+
* Throws `cancelled` if the user dismisses the sheet.
|
|
57
|
+
*/
|
|
58
|
+
export declare function signInWithApple(): Promise<{
|
|
59
|
+
token: string;
|
|
60
|
+
userId: string;
|
|
61
|
+
}>;
|
|
62
|
+
/**
|
|
63
|
+
* Default OAuth redirect — the same callback the Yaver mobile app uses
|
|
64
|
+
* (`yaver://oauth-callback`). `WebBrowser.openAuthSessionAsync` intercepts
|
|
65
|
+
* this redirect inside the auth session, so the host app does not need to
|
|
66
|
+
* register the scheme on iOS. On Android, add an `<intent-filter>` for
|
|
67
|
+
* `yaver://oauth-callback` in the host app's AndroidManifest.xml.
|
|
57
68
|
*/
|
|
58
|
-
export declare
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
export declare const DEFAULT_OAUTH_REDIRECT = "yaver://oauth-callback";
|
|
70
|
+
/**
|
|
71
|
+
* Sign in through the in-app browser via yaver.io. Opens
|
|
72
|
+
* `https://yaver.io/api/auth/oauth/<provider>?client=mobile`, the user picks
|
|
73
|
+
* an OAuth provider, and the web callback redirects back to
|
|
74
|
+
* `yaver://oauth-callback?token=...` which `openAuthSessionAsync` captures
|
|
75
|
+
* inside the auth session. No deep-link wiring required on iOS.
|
|
76
|
+
*
|
|
77
|
+
* Throws `cancelled` if the user dismisses the browser.
|
|
78
|
+
*/
|
|
79
|
+
export declare function signInWithOAuth(provider: OAuthProvider, opts?: {
|
|
80
|
+
redirectUrl?: string;
|
|
81
|
+
preferEphemeralSession?: boolean;
|
|
82
|
+
}): Promise<{
|
|
67
83
|
token: string;
|
|
68
|
-
}
|
|
69
|
-
status: 'expired';
|
|
70
|
-
};
|
|
71
|
-
export declare function pollDeviceCode(deviceCode: string): Promise<DeviceCodePoll>;
|
|
84
|
+
}>;
|
|
72
85
|
export declare function signupWithEmail(fullName: string, email: string, password: string): Promise<{
|
|
73
86
|
token: string;
|
|
74
87
|
userId: string;
|