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/src/LoginScreen.tsx
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import {
|
|
3
|
-
View,
|
|
4
|
-
Text,
|
|
5
|
-
TextInput,
|
|
6
|
-
TouchableOpacity,
|
|
7
|
-
StyleSheet,
|
|
8
3
|
ActivityIndicator,
|
|
4
|
+
Alert,
|
|
5
|
+
KeyboardAvoidingView,
|
|
6
|
+
Platform,
|
|
7
|
+
Pressable,
|
|
9
8
|
SafeAreaView,
|
|
10
9
|
ScrollView,
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
StyleSheet,
|
|
11
|
+
Text,
|
|
12
|
+
TextInput,
|
|
13
|
+
View,
|
|
13
14
|
} from 'react-native';
|
|
14
15
|
import {
|
|
15
16
|
loginWithEmail,
|
|
16
|
-
signupWithEmail,
|
|
17
|
-
pollDeviceCode,
|
|
18
|
-
startDeviceCode,
|
|
19
|
-
validateToken,
|
|
20
17
|
saveToken,
|
|
21
18
|
saveUser,
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
signInWithApple,
|
|
20
|
+
signInWithOAuth,
|
|
21
|
+
signupWithEmail,
|
|
22
|
+
validateToken,
|
|
23
|
+
type OAuthProvider,
|
|
24
24
|
} from './auth';
|
|
25
25
|
|
|
26
26
|
export interface YaverLoginScreenProps {
|
|
@@ -30,366 +30,303 @@ export interface YaverLoginScreenProps {
|
|
|
30
30
|
onCancel?: () => void;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
type Mode = 'device' | 'email';
|
|
34
|
-
|
|
35
|
-
const PROVIDERS: { id: OAuthProvider; label: string; emoji: string }[] = [
|
|
36
|
-
{ id: 'apple', label: 'Apple', emoji: '' },
|
|
37
|
-
{ id: 'google', label: 'Google', emoji: 'G' },
|
|
38
|
-
{ id: 'github', label: 'GitHub', emoji: '' },
|
|
39
|
-
{ id: 'gitlab', label: 'GitLab', emoji: '' },
|
|
40
|
-
{ id: 'microsoft', label: 'Microsoft', emoji: 'M' },
|
|
41
|
-
];
|
|
42
|
-
|
|
43
33
|
/**
|
|
44
|
-
* Full-screen in-SDK login.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* available as an inline fallback for headless environments.
|
|
34
|
+
* Full-screen in-SDK login. Mirrors the Yaver mobile app login UX: native
|
|
35
|
+
* Apple Sign-In on iOS, in-app browser OAuth for Google/GitHub/GitLab/
|
|
36
|
+
* Microsoft (no codes, no leaving the app), and inline email/password.
|
|
48
37
|
*/
|
|
49
38
|
export const YaverLoginScreen: React.FC<YaverLoginScreenProps> = ({
|
|
50
39
|
onLoggedIn,
|
|
51
40
|
onCancel,
|
|
52
41
|
}) => {
|
|
53
|
-
const [
|
|
54
|
-
|
|
55
|
-
const [
|
|
56
|
-
const [codeError, setCodeError] = useState<string | null>(null);
|
|
57
|
-
const [starting, setStarting] = useState(false);
|
|
58
|
-
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
59
|
-
const expiredTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
60
|
-
|
|
61
|
-
const [emailMode, setEmailMode] = useState<'login' | 'signup'>('login');
|
|
42
|
+
const [busyProvider, setBusyProvider] = useState<OAuthProvider | 'apple' | null>(null);
|
|
43
|
+
const [showEmailForm, setShowEmailForm] = useState(false);
|
|
44
|
+
const [isSignUp, setIsSignUp] = useState(false);
|
|
62
45
|
const [fullName, setFullName] = useState('');
|
|
63
46
|
const [email, setEmail] = useState('');
|
|
64
47
|
const [password, setPassword] = useState('');
|
|
48
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
65
49
|
const [emailBusy, setEmailBusy] = useState(false);
|
|
66
|
-
const [emailError, setEmailError] = useState
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (mode === 'device' && !code && !starting) {
|
|
70
|
-
void beginDeviceCode(undefined);
|
|
71
|
-
}
|
|
72
|
-
return () => {
|
|
73
|
-
stopPolling();
|
|
74
|
-
};
|
|
75
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
76
|
-
}, [mode]);
|
|
50
|
+
const [emailError, setEmailError] = useState('');
|
|
77
51
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (expiredTimerRef.current) {
|
|
84
|
-
clearTimeout(expiredTimerRef.current);
|
|
85
|
-
expiredTimerRef.current = null;
|
|
86
|
-
}
|
|
52
|
+
const finish = async (token: string) => {
|
|
53
|
+
const user = await validateToken(token);
|
|
54
|
+
await saveToken(token);
|
|
55
|
+
if (user) await saveUser(user);
|
|
56
|
+
onLoggedIn(token);
|
|
87
57
|
};
|
|
88
58
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
setCodeError(null);
|
|
92
|
-
stopPolling();
|
|
59
|
+
const handleApple = async () => {
|
|
60
|
+
setBusyProvider('apple');
|
|
93
61
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
setCode(result);
|
|
100
|
-
|
|
101
|
-
pollRef.current = setInterval(async () => {
|
|
102
|
-
const poll = await pollDeviceCode(result.deviceCode);
|
|
103
|
-
if (poll.status === 'authorized') {
|
|
104
|
-
stopPolling();
|
|
105
|
-
const user = await validateToken(poll.token);
|
|
106
|
-
await saveToken(poll.token);
|
|
107
|
-
if (user) await saveUser(user);
|
|
108
|
-
onLoggedIn(poll.token);
|
|
109
|
-
} else if (poll.status === 'expired') {
|
|
110
|
-
stopPolling();
|
|
111
|
-
setCodeError('Kod doldu — tekrar başlat.');
|
|
112
|
-
setCode(null);
|
|
113
|
-
}
|
|
114
|
-
}, 3_000);
|
|
115
|
-
|
|
116
|
-
expiredTimerRef.current = setTimeout(() => {
|
|
117
|
-
stopPolling();
|
|
118
|
-
setCode(null);
|
|
119
|
-
setCodeError('Kod süresi doldu.');
|
|
120
|
-
}, Math.max(0, result.expiresAt - Date.now()));
|
|
121
|
-
} catch (err) {
|
|
122
|
-
setCodeError(err instanceof Error ? err.message : String(err));
|
|
62
|
+
const { token } = await signInWithApple();
|
|
63
|
+
await finish(token);
|
|
64
|
+
} catch (e: unknown) {
|
|
65
|
+
const msg = e instanceof Error ? e.message : 'Apple Sign-In failed';
|
|
66
|
+
if (msg !== 'cancelled') Alert.alert('Sign In Failed', msg);
|
|
123
67
|
} finally {
|
|
124
|
-
|
|
68
|
+
setBusyProvider(null);
|
|
125
69
|
}
|
|
126
70
|
};
|
|
127
71
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
72
|
+
const handleOAuth = async (provider: OAuthProvider) => {
|
|
73
|
+
setBusyProvider(provider);
|
|
74
|
+
try {
|
|
75
|
+
const { token } = await signInWithOAuth(provider);
|
|
76
|
+
await finish(token);
|
|
77
|
+
} catch (e: unknown) {
|
|
78
|
+
const msg = e instanceof Error ? e.message : 'Sign-in failed';
|
|
79
|
+
if (msg !== 'cancelled') Alert.alert('Sign In Failed', msg);
|
|
80
|
+
} finally {
|
|
81
|
+
setBusyProvider(null);
|
|
82
|
+
}
|
|
133
83
|
};
|
|
134
84
|
|
|
135
85
|
const handleEmailSubmit = async () => {
|
|
136
|
-
setEmailError(
|
|
137
|
-
if (
|
|
138
|
-
setEmailError('
|
|
139
|
-
|
|
86
|
+
setEmailError('');
|
|
87
|
+
if (isSignUp) {
|
|
88
|
+
if (!fullName.trim()) return setEmailError('Full name is required');
|
|
89
|
+
if (password !== confirmPassword)
|
|
90
|
+
return setEmailError('Passwords do not match');
|
|
91
|
+
if (password.length < 8)
|
|
92
|
+
return setEmailError('Password must be at least 8 characters');
|
|
140
93
|
}
|
|
94
|
+
if (!email.trim() || !password)
|
|
95
|
+
return setEmailError('Email and password are required');
|
|
96
|
+
|
|
141
97
|
setEmailBusy(true);
|
|
142
98
|
try {
|
|
143
|
-
const result =
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (user) await saveUser(user);
|
|
150
|
-
onLoggedIn(result.token);
|
|
151
|
-
} catch (err) {
|
|
152
|
-
setEmailError(err instanceof Error ? err.message : String(err));
|
|
99
|
+
const result = isSignUp
|
|
100
|
+
? await signupWithEmail(fullName.trim(), email.trim(), password)
|
|
101
|
+
: await loginWithEmail(email.trim(), password);
|
|
102
|
+
await finish(result.token);
|
|
103
|
+
} catch (e: unknown) {
|
|
104
|
+
setEmailError(e instanceof Error ? e.message : 'Something went wrong');
|
|
153
105
|
} finally {
|
|
154
106
|
setEmailBusy(false);
|
|
155
107
|
}
|
|
156
108
|
};
|
|
157
109
|
|
|
110
|
+
const renderProvider = (
|
|
111
|
+
id: OAuthProvider | 'apple',
|
|
112
|
+
label: string,
|
|
113
|
+
onPress: () => void,
|
|
114
|
+
) => (
|
|
115
|
+
<Pressable
|
|
116
|
+
key={id}
|
|
117
|
+
style={({ pressed }) => [
|
|
118
|
+
styles.button,
|
|
119
|
+
pressed && styles.buttonPressed,
|
|
120
|
+
busyProvider === id && { opacity: 0.6 },
|
|
121
|
+
]}
|
|
122
|
+
onPress={onPress}
|
|
123
|
+
disabled={busyProvider !== null}
|
|
124
|
+
>
|
|
125
|
+
{busyProvider === id ? (
|
|
126
|
+
<ActivityIndicator color="#e0e0e0" />
|
|
127
|
+
) : (
|
|
128
|
+
<Text style={styles.buttonText}>{label}</Text>
|
|
129
|
+
)}
|
|
130
|
+
</Pressable>
|
|
131
|
+
);
|
|
132
|
+
|
|
158
133
|
return (
|
|
159
|
-
<SafeAreaView style={styles.
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
134
|
+
<SafeAreaView style={styles.safeArea}>
|
|
135
|
+
<KeyboardAvoidingView
|
|
136
|
+
style={{ flex: 1 }}
|
|
137
|
+
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
163
138
|
>
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
</
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
style={[styles.tab, mode === 'device' && styles.tabActive]}
|
|
176
|
-
onPress={() => setMode('device')}
|
|
177
|
-
>
|
|
178
|
-
<Text
|
|
179
|
-
style={[styles.tabText, mode === 'device' && styles.tabTextActive]}
|
|
180
|
-
>
|
|
181
|
-
Hızlı Giriş (OAuth)
|
|
182
|
-
</Text>
|
|
183
|
-
</TouchableOpacity>
|
|
184
|
-
<TouchableOpacity
|
|
185
|
-
style={[styles.tab, mode === 'email' && styles.tabActive]}
|
|
186
|
-
onPress={() => setMode('email')}
|
|
187
|
-
>
|
|
188
|
-
<Text
|
|
189
|
-
style={[styles.tabText, mode === 'email' && styles.tabTextActive]}
|
|
190
|
-
>
|
|
191
|
-
E-posta
|
|
192
|
-
</Text>
|
|
193
|
-
</TouchableOpacity>
|
|
194
|
-
</View>
|
|
195
|
-
|
|
196
|
-
{mode === 'device' && (
|
|
197
|
-
<View style={styles.section}>
|
|
198
|
-
{starting ? (
|
|
199
|
-
<ActivityIndicator color="#6366f1" style={{ marginVertical: 40 }} />
|
|
200
|
-
) : code ? (
|
|
201
|
-
<>
|
|
202
|
-
<Text style={styles.hint}>
|
|
203
|
-
Tarayıcıda yaver.io/auth/device aç ve aşağıdaki kodu gir.
|
|
204
|
-
Sağlayıcıyla giriş yaptığında buraya otomatik dönecek.
|
|
205
|
-
</Text>
|
|
206
|
-
<View style={styles.codeBox}>
|
|
207
|
-
<Text style={styles.codeText}>{code.userCode}</Text>
|
|
208
|
-
</View>
|
|
209
|
-
<TouchableOpacity style={styles.primaryButton} onPress={openVerification}>
|
|
210
|
-
<Text style={styles.primaryButtonText}>Tarayıcıda Aç</Text>
|
|
211
|
-
</TouchableOpacity>
|
|
212
|
-
|
|
213
|
-
<Text style={[styles.hint, { marginTop: 20 }]}>
|
|
214
|
-
Sağlayıcıyı önceden seçmek istersen:
|
|
215
|
-
</Text>
|
|
216
|
-
<View style={styles.providerRow}>
|
|
217
|
-
{PROVIDERS.map((p) => (
|
|
218
|
-
<TouchableOpacity
|
|
219
|
-
key={p.id}
|
|
220
|
-
style={styles.providerButton}
|
|
221
|
-
onPress={() => beginDeviceCode(p.id)}
|
|
222
|
-
>
|
|
223
|
-
<Text style={styles.providerButtonText}>{p.label}</Text>
|
|
224
|
-
</TouchableOpacity>
|
|
225
|
-
))}
|
|
226
|
-
</View>
|
|
227
|
-
</>
|
|
228
|
-
) : (
|
|
229
|
-
<TouchableOpacity
|
|
230
|
-
style={styles.primaryButton}
|
|
231
|
-
onPress={() => beginDeviceCode(undefined)}
|
|
232
|
-
>
|
|
233
|
-
<Text style={styles.primaryButtonText}>Tekrar Başlat</Text>
|
|
234
|
-
</TouchableOpacity>
|
|
139
|
+
<ScrollView
|
|
140
|
+
contentContainerStyle={styles.scrollContainer}
|
|
141
|
+
keyboardShouldPersistTaps="handled"
|
|
142
|
+
>
|
|
143
|
+
<View style={styles.header}>
|
|
144
|
+
<Text style={styles.logo}>Yaver</Text>
|
|
145
|
+
<Text style={styles.subtitle}>Sign in to send feedback</Text>
|
|
146
|
+
{onCancel && (
|
|
147
|
+
<Pressable onPress={onCancel} style={styles.cancel}>
|
|
148
|
+
<Text style={styles.cancelText}>Cancel</Text>
|
|
149
|
+
</Pressable>
|
|
235
150
|
)}
|
|
236
|
-
{codeError && <Text style={styles.error}>{codeError}</Text>}
|
|
237
151
|
</View>
|
|
238
|
-
)}
|
|
239
152
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
153
|
+
<View style={styles.buttons}>
|
|
154
|
+
{Platform.OS === 'ios'
|
|
155
|
+
? renderProvider('apple', 'Continue with Apple', handleApple)
|
|
156
|
+
: renderProvider('apple', 'Continue with Apple', () =>
|
|
157
|
+
handleOAuth('apple'),
|
|
158
|
+
)}
|
|
159
|
+
{renderProvider('google', 'Continue with Google', () =>
|
|
160
|
+
handleOAuth('google'),
|
|
161
|
+
)}
|
|
162
|
+
{renderProvider('github', 'Continue with GitHub', () =>
|
|
163
|
+
handleOAuth('github'),
|
|
164
|
+
)}
|
|
165
|
+
{renderProvider('gitlab', 'Continue with GitLab', () =>
|
|
166
|
+
handleOAuth('gitlab'),
|
|
167
|
+
)}
|
|
168
|
+
{renderProvider('microsoft', 'Continue with Microsoft', () =>
|
|
169
|
+
handleOAuth('microsoft'),
|
|
170
|
+
)}
|
|
256
171
|
|
|
257
|
-
{
|
|
172
|
+
{!showEmailForm ? (
|
|
173
|
+
<Pressable
|
|
174
|
+
style={({ pressed }) => [
|
|
175
|
+
styles.button,
|
|
176
|
+
pressed && styles.buttonPressed,
|
|
177
|
+
]}
|
|
178
|
+
onPress={() => setShowEmailForm(true)}
|
|
179
|
+
disabled={busyProvider !== null}
|
|
180
|
+
>
|
|
181
|
+
<Text style={styles.buttonText}>Continue with Email</Text>
|
|
182
|
+
</Pressable>
|
|
183
|
+
) : (
|
|
258
184
|
<>
|
|
259
|
-
<
|
|
185
|
+
<View style={styles.divider}>
|
|
186
|
+
<View style={styles.dividerLine} />
|
|
187
|
+
<Text style={styles.dividerText}>email</Text>
|
|
188
|
+
<View style={styles.dividerLine} />
|
|
189
|
+
</View>
|
|
190
|
+
{isSignUp && (
|
|
191
|
+
<TextInput
|
|
192
|
+
style={styles.input}
|
|
193
|
+
placeholder="Full Name"
|
|
194
|
+
placeholderTextColor="#666"
|
|
195
|
+
value={fullName}
|
|
196
|
+
onChangeText={setFullName}
|
|
197
|
+
autoCapitalize="words"
|
|
198
|
+
autoCorrect={false}
|
|
199
|
+
/>
|
|
200
|
+
)}
|
|
260
201
|
<TextInput
|
|
261
202
|
style={styles.input}
|
|
262
|
-
|
|
263
|
-
onChangeText={setFullName}
|
|
264
|
-
placeholder="Adın Soyadın"
|
|
203
|
+
placeholder="Email"
|
|
265
204
|
placeholderTextColor="#666"
|
|
266
|
-
|
|
205
|
+
value={email}
|
|
206
|
+
onChangeText={setEmail}
|
|
207
|
+
keyboardType="email-address"
|
|
208
|
+
autoCapitalize="none"
|
|
209
|
+
autoCorrect={false}
|
|
267
210
|
/>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
211
|
+
<TextInput
|
|
212
|
+
style={styles.input}
|
|
213
|
+
placeholder="Password"
|
|
214
|
+
placeholderTextColor="#666"
|
|
215
|
+
value={password}
|
|
216
|
+
onChangeText={setPassword}
|
|
217
|
+
secureTextEntry
|
|
218
|
+
autoCapitalize="none"
|
|
219
|
+
/>
|
|
220
|
+
{isSignUp && (
|
|
221
|
+
<TextInput
|
|
222
|
+
style={styles.input}
|
|
223
|
+
placeholder="Confirm Password"
|
|
224
|
+
placeholderTextColor="#666"
|
|
225
|
+
value={confirmPassword}
|
|
226
|
+
onChangeText={setConfirmPassword}
|
|
227
|
+
secureTextEntry
|
|
228
|
+
/>
|
|
229
|
+
)}
|
|
282
230
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
value={password}
|
|
287
|
-
onChangeText={setPassword}
|
|
288
|
-
placeholder="••••••••"
|
|
289
|
-
placeholderTextColor="#666"
|
|
290
|
-
secureTextEntry
|
|
291
|
-
autoCapitalize="none"
|
|
292
|
-
/>
|
|
231
|
+
{emailError ? (
|
|
232
|
+
<Text style={styles.errorText}>{emailError}</Text>
|
|
233
|
+
) : null}
|
|
293
234
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
{
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
235
|
+
<Pressable
|
|
236
|
+
style={({ pressed }) => [
|
|
237
|
+
styles.submitButton,
|
|
238
|
+
pressed && styles.buttonPressed,
|
|
239
|
+
emailBusy && { opacity: 0.6 },
|
|
240
|
+
]}
|
|
241
|
+
onPress={handleEmailSubmit}
|
|
242
|
+
disabled={emailBusy}
|
|
243
|
+
>
|
|
244
|
+
{emailBusy ? (
|
|
245
|
+
<ActivityIndicator color="#fff" />
|
|
246
|
+
) : (
|
|
247
|
+
<Text style={styles.submitButtonText}>
|
|
248
|
+
{isSignUp ? 'Create Account' : 'Sign In'}
|
|
249
|
+
</Text>
|
|
250
|
+
)}
|
|
251
|
+
</Pressable>
|
|
307
252
|
|
|
308
|
-
|
|
253
|
+
<Pressable
|
|
254
|
+
onPress={() => {
|
|
255
|
+
setIsSignUp(!isSignUp);
|
|
256
|
+
setEmailError('');
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<Text style={styles.toggleText}>
|
|
260
|
+
{isSignUp
|
|
261
|
+
? 'Already have an account? Sign In'
|
|
262
|
+
: "Don't have an account? Sign Up"}
|
|
263
|
+
</Text>
|
|
264
|
+
</Pressable>
|
|
265
|
+
</>
|
|
266
|
+
)}
|
|
309
267
|
</View>
|
|
310
|
-
|
|
311
|
-
</
|
|
268
|
+
</ScrollView>
|
|
269
|
+
</KeyboardAvoidingView>
|
|
312
270
|
</SafeAreaView>
|
|
313
271
|
);
|
|
314
272
|
};
|
|
315
273
|
|
|
316
274
|
const styles = StyleSheet.create({
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
justifyContent: 'space-between',
|
|
323
|
-
marginBottom: 20,
|
|
275
|
+
safeArea: { flex: 1, backgroundColor: '#1a1a2e' },
|
|
276
|
+
scrollContainer: {
|
|
277
|
+
flexGrow: 1,
|
|
278
|
+
paddingHorizontal: 24,
|
|
279
|
+
justifyContent: 'center',
|
|
324
280
|
},
|
|
325
|
-
|
|
326
|
-
|
|
281
|
+
header: { alignItems: 'center', marginBottom: 40 },
|
|
282
|
+
logo: { fontSize: 44, fontWeight: '800', color: '#e0e0e0', letterSpacing: -1 },
|
|
283
|
+
subtitle: { fontSize: 15, color: '#9ca3af', marginTop: 6 },
|
|
284
|
+
cancel: { position: 'absolute', right: 0, top: 0, padding: 8 },
|
|
327
285
|
cancelText: { color: '#9ca3af', fontSize: 14 },
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
paddingVertical: 12,
|
|
332
|
-
borderRadius: 10,
|
|
333
|
-
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
334
|
-
alignItems: 'center',
|
|
335
|
-
},
|
|
336
|
-
tabActive: { backgroundColor: 'rgba(99,102,241,0.25)' },
|
|
337
|
-
tabText: { color: '#9ca3af', fontSize: 14, fontWeight: '600' },
|
|
338
|
-
tabTextActive: { color: '#e0e0e0' },
|
|
339
|
-
subTab: {
|
|
340
|
-
flex: 1,
|
|
341
|
-
paddingVertical: 8,
|
|
342
|
-
borderRadius: 8,
|
|
343
|
-
alignItems: 'center',
|
|
344
|
-
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
345
|
-
},
|
|
346
|
-
subTabActive: { backgroundColor: 'rgba(99,102,241,0.2)' },
|
|
347
|
-
subTabText: { color: '#e0e0e0', fontSize: 13 },
|
|
348
|
-
section: { marginTop: 4 },
|
|
349
|
-
hint: { color: '#9ca3af', fontSize: 13, lineHeight: 18 },
|
|
350
|
-
codeBox: {
|
|
351
|
-
backgroundColor: 'rgba(99,102,241,0.15)',
|
|
286
|
+
buttons: { gap: 12 },
|
|
287
|
+
button: {
|
|
288
|
+
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
352
289
|
borderWidth: 1,
|
|
353
|
-
borderColor: 'rgba(
|
|
354
|
-
borderRadius: 14,
|
|
355
|
-
paddingVertical: 24,
|
|
356
|
-
alignItems: 'center',
|
|
357
|
-
marginTop: 16,
|
|
358
|
-
marginBottom: 16,
|
|
359
|
-
},
|
|
360
|
-
codeText: {
|
|
361
|
-
color: '#e0e7ff',
|
|
362
|
-
fontSize: 36,
|
|
363
|
-
fontWeight: '800',
|
|
364
|
-
letterSpacing: 6,
|
|
365
|
-
fontVariant: ['tabular-nums'],
|
|
366
|
-
},
|
|
367
|
-
primaryButton: {
|
|
368
|
-
backgroundColor: '#6366f1',
|
|
290
|
+
borderColor: 'rgba(255,255,255,0.12)',
|
|
369
291
|
borderRadius: 12,
|
|
370
292
|
paddingVertical: 14,
|
|
371
293
|
alignItems: 'center',
|
|
372
|
-
|
|
294
|
+
justifyContent: 'center',
|
|
373
295
|
},
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
296
|
+
buttonPressed: { opacity: 0.7 },
|
|
297
|
+
buttonText: { color: '#e0e0e0', fontSize: 15, fontWeight: '600' },
|
|
298
|
+
divider: {
|
|
299
|
+
flexDirection: 'row',
|
|
300
|
+
alignItems: 'center',
|
|
301
|
+
marginTop: 16,
|
|
302
|
+
marginBottom: 8,
|
|
381
303
|
},
|
|
382
|
-
|
|
383
|
-
|
|
304
|
+
dividerLine: { flex: 1, height: 1, backgroundColor: 'rgba(255,255,255,0.12)' },
|
|
305
|
+
dividerText: { marginHorizontal: 14, fontSize: 12, color: '#6b7280' },
|
|
384
306
|
input: {
|
|
385
|
-
backgroundColor: 'rgba(255,255,255,0.
|
|
307
|
+
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
386
308
|
borderWidth: 1,
|
|
387
|
-
borderColor: 'rgba(255,255,255,0.
|
|
388
|
-
borderRadius:
|
|
309
|
+
borderColor: 'rgba(255,255,255,0.12)',
|
|
310
|
+
borderRadius: 12,
|
|
389
311
|
paddingHorizontal: 14,
|
|
390
|
-
paddingVertical:
|
|
312
|
+
paddingVertical: 13,
|
|
391
313
|
color: '#e0e0e0',
|
|
392
314
|
fontSize: 15,
|
|
393
315
|
},
|
|
394
|
-
|
|
316
|
+
errorText: { color: '#ef4444', fontSize: 13, textAlign: 'center' },
|
|
317
|
+
submitButton: {
|
|
318
|
+
backgroundColor: '#6366f1',
|
|
319
|
+
borderRadius: 12,
|
|
320
|
+
paddingVertical: 14,
|
|
321
|
+
alignItems: 'center',
|
|
322
|
+
justifyContent: 'center',
|
|
323
|
+
marginTop: 4,
|
|
324
|
+
},
|
|
325
|
+
submitButtonText: { color: '#fff', fontSize: 15, fontWeight: '700' },
|
|
326
|
+
toggleText: {
|
|
327
|
+
color: '#818cf8',
|
|
328
|
+
fontSize: 14,
|
|
329
|
+
textAlign: 'center',
|
|
330
|
+
marginTop: 4,
|
|
331
|
+
},
|
|
395
332
|
});
|
package/src/YaverFeedback.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { P2PClient } from './P2PClient';
|
|
|
6
6
|
import { ShakeDetector } from './ShakeDetector';
|
|
7
7
|
import {
|
|
8
8
|
configureAuthEndpoints,
|
|
9
|
+
setStrictNativeAuth,
|
|
9
10
|
getToken,
|
|
10
11
|
getSelectedDeviceId,
|
|
11
12
|
clearToken,
|
|
@@ -88,6 +89,9 @@ export class YaverFeedback {
|
|
|
88
89
|
convexSiteUrl: cfg.authConvexSiteUrl,
|
|
89
90
|
webBaseUrl: cfg.authWebBaseUrl,
|
|
90
91
|
});
|
|
92
|
+
// Compile-time lockdown: refuse any browser-hop / device-code fallback
|
|
93
|
+
// and force ASWebAuthenticationSession in ephemeral mode for OAuth.
|
|
94
|
+
setStrictNativeAuth(cfg.strictNativeAuth === true);
|
|
91
95
|
// If no explicit convexUrl was set but we have an auth site URL, use it
|
|
92
96
|
// so Discovery.discoverFromConvex() has somewhere to look up the user's
|
|
93
97
|
// machines (works for both LAN-direct and off-LAN relay paths).
|