yaver-feedback-react-native 0.5.4 → 0.6.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/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 optional peer dependencies:
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
- ## Quick Start (0.5+: zero-config auth)
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)
35
55
 
36
- Starting with **0.5.0**, the SDK ships its own login screen + remote machine picker. Drop in `<FeedbackModal />` and the first time a user triggers feedback without a cached session, they get:
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.
37
57
 
38
- 1. A full-screen login modal Apple / Google / GitHub / GitLab / Microsoft via [device-code flow](https://yaver.io/auth/device), or inline email + password.
58
+ Drop in `<FeedbackModal />` and the first time a user triggers feedback without a cached session, they get:
59
+
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.
@@ -574,12 +596,34 @@ YaverFeedback.init({
574
596
  });
575
597
  ```
576
598
 
599
+ ## Running Inside the Yaver Mobile App (super-host)
600
+
601
+ When a third-party app is loaded through Yaver's Hermes-push flow (so the
602
+ Yaver mobile app is the runtime container for your app), the SDK detects
603
+ that situation via the `YaverInfo` native module and **automatically
604
+ no-ops `YaverFeedback.init()`** — no ShakeDetector, no FeedbackModal, no
605
+ BlackBox stream from inside the guest. The user only ever sees Yaver's
606
+ native shake overlay ("Reload" + "Back to Yaver") and uses Yaver's
607
+ built-in feedback flow instead.
608
+
609
+ Standalone installs (TestFlight / App Store / Play from your own dev
610
+ account) are unaffected — the SDK behaves exactly as it did before.
611
+
612
+ If you need to opt out of the auto-suppression in a specific build (for
613
+ example, to compare behaviour in a super-host smoke test), set
614
+ `enabled: true` explicitly — the Yaver-host gate runs before the
615
+ `enabled` flag is evaluated on purpose.
616
+
577
617
  ## Trigger Modes
578
618
 
579
619
  ### Shake (default)
580
620
 
581
621
  Shake the device to open the feedback modal. Uses the built-in shake event on iOS and `ShakeEvent` on Android.
582
622
 
623
+ When the app is loaded inside the Yaver mobile app (super-host), the
624
+ SDK yields shake handling to Yaver's native overlay — see the
625
+ "Running Inside the Yaver Mobile App" section above.
626
+
583
627
  ```typescript
584
628
  YaverFeedback.init({ authToken, trigger: 'shake' });
585
629
  ```
@@ -6,9 +6,8 @@ export interface YaverLoginScreenProps {
6
6
  onCancel?: () => void;
7
7
  }
8
8
  /**
9
- * Full-screen in-SDK login. Device-code is the default flow users sign in
10
- * with any OAuth provider (Apple/Google/GitHub/GitLab/Microsoft) or email on
11
- * yaver.io and the SDK polls for the issued session token. Email/password is
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>;
@@ -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. Device-code is the default flow users sign in
49
- * with any OAuth provider (Apple/Google/GitHub/GitLab/Microsoft) or email on
50
- * yaver.io and the SDK polls for the issued session token. Email/password is
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 [mode, setMode] = (0, react_1.useState)('device');
55
- const [code, setCode] = (0, react_1.useState)(null);
56
- const [codeError, setCodeError] = (0, react_1.useState)(null);
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)(null);
66
- (0, react_1.useEffect)(() => {
67
- if (mode === 'device' && !code && !starting) {
68
- void beginDeviceCode(undefined);
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
- return () => {
71
- stopPolling();
72
- };
73
- // eslint-disable-next-line react-hooks/exhaustive-deps
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
- if (expiredTimerRef.current) {
81
- clearTimeout(expiredTimerRef.current);
82
- expiredTimerRef.current = null;
73
+ finally {
74
+ setBusyProvider(null);
83
75
  }
84
76
  };
85
- const beginDeviceCode = async (preferredProvider) => {
86
- setStarting(true);
87
- setCodeError(null);
88
- stopPolling();
77
+ const handleOAuth = async (provider) => {
78
+ setBusyProvider(provider);
89
79
  try {
90
- const result = await (0, auth_1.startDeviceCode)({
91
- platform: react_native_1.Platform.OS,
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 (err) {
119
- setCodeError(err instanceof Error ? err.message : String(err));
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
- setStarting(false);
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(null);
134
- if (!email.trim() || !password) {
135
- setEmailError('E-posta ve parola zorunlu.');
136
- return;
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 = emailMode === 'signup'
141
- ? await (0, auth_1.signupWithEmail)(fullName.trim() || email.trim(), email.trim(), password)
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
- const user = await (0, auth_1.validateToken)(result.token);
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 (err) {
150
- setEmailError(err instanceof Error ? err.message : String(err));
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
- return (<react_native_1.SafeAreaView style={styles.container}>
157
- <react_native_1.ScrollView contentContainerStyle={styles.content} keyboardShouldPersistTaps="handled">
158
- <react_native_1.View style={styles.header}>
159
- <react_native_1.Text style={styles.title}>Yaver Girişi</react_native_1.Text>
160
- {onCancel && (<react_native_1.TouchableOpacity onPress={onCancel} style={styles.cancel}>
161
- <react_native_1.Text style={styles.cancelText}>İptal</react_native_1.Text>
162
- </react_native_1.TouchableOpacity>)}
163
- </react_native_1.View>
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
- <react_native_1.View style={styles.tabRow}>
166
- <react_native_1.TouchableOpacity style={[styles.tab, mode === 'device' && styles.tabActive]} onPress={() => setMode('device')}>
167
- <react_native_1.Text style={[styles.tabText, mode === 'device' && styles.tabTextActive]}>
168
- Hızlı Giriş (OAuth)
169
- </react_native_1.Text>
170
- </react_native_1.TouchableOpacity>
171
- <react_native_1.TouchableOpacity style={[styles.tab, mode === 'email' && styles.tabActive]} onPress={() => setMode('email')}>
172
- <react_native_1.Text style={[styles.tabText, mode === 'email' && styles.tabTextActive]}>
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
- {mode === 'device' && (<react_native_1.View style={styles.section}>
179
- {starting ? (<react_native_1.ActivityIndicator color="#6366f1" style={{ marginVertical: 40 }}/>) : code ? (<>
180
- <react_native_1.Text style={styles.hint}>
181
- Tarayıcıda yaver.io/auth/device ve aşağıdaki kodu gir.
182
- Sağlayıcıyla giriş yaptığında buraya otomatik dönecek.
183
- </react_native_1.Text>
184
- <react_native_1.View style={styles.codeBox}>
185
- <react_native_1.Text style={styles.codeText}>{code.userCode}</react_native_1.Text>
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.TouchableOpacity style={styles.primaryButton} onPress={openVerification}>
188
- <react_native_1.Text style={styles.primaryButtonText}>Tarayıcıda Aç</react_native_1.Text>
189
- </react_native_1.TouchableOpacity>
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={[styles.hint, { marginTop: 20 }]}>
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
- {mode === 'email' && (<react_native_1.View style={styles.section}>
206
- <react_native_1.View style={styles.tabRow}>
207
- <react_native_1.TouchableOpacity style={[styles.subTab, emailMode === 'login' && styles.subTabActive]} onPress={() => setEmailMode('login')}>
208
- <react_native_1.Text style={styles.subTabText}>Giriş</react_native_1.Text>
209
- </react_native_1.TouchableOpacity>
210
- <react_native_1.TouchableOpacity style={[styles.subTab, emailMode === 'signup' && styles.subTabActive]} onPress={() => setEmailMode('signup')}>
211
- <react_native_1.Text style={styles.subTabText}>Kayıt</react_native_1.Text>
212
- </react_native_1.TouchableOpacity>
213
- </react_native_1.View>
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
- {emailMode === 'signup' && (<>
216
- <react_native_1.Text style={styles.label}>Ad Soyad</react_native_1.Text>
217
- <react_native_1.TextInput style={styles.input} value={fullName} onChangeText={setFullName} placeholder="Adın Soyadın" placeholderTextColor="#666" autoCapitalize="words"/>
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
- <react_native_1.Text style={styles.label}>E-posta</react_native_1.Text>
221
- <react_native_1.TextInput style={styles.input} value={email} onChangeText={setEmail} placeholder="you@example.com" placeholderTextColor="#666" keyboardType="email-address" autoCapitalize="none" autoCorrect={false}/>
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
- container: { flex: 1, backgroundColor: '#1a1a2e' },
240
- content: { padding: 24, paddingTop: 16 },
241
- header: {
242
- flexDirection: 'row',
243
- alignItems: 'center',
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
- title: { fontSize: 22, fontWeight: '700', color: '#e0e0e0' },
248
- cancel: { padding: 8 },
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
- tabRow: { flexDirection: 'row', gap: 8, marginBottom: 16 },
251
- tab: {
252
- flex: 1,
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(99,102,241,0.35)',
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
- marginTop: 8,
210
+ justifyContent: 'center',
295
211
  },
296
- primaryButtonText: { color: '#fff', fontWeight: '700', fontSize: 15 },
297
- providerRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 10 },
298
- providerButton: {
299
- backgroundColor: 'rgba(255,255,255,0.08)',
300
- borderRadius: 10,
301
- paddingVertical: 10,
302
- paddingHorizontal: 14,
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
- providerButtonText: { color: '#e0e0e0', fontSize: 13, fontWeight: '600' },
305
- label: { color: '#9ca3af', fontSize: 12, marginTop: 14, marginBottom: 6 },
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.08)',
223
+ backgroundColor: 'rgba(255,255,255,0.06)',
308
224
  borderWidth: 1,
309
- borderColor: 'rgba(255,255,255,0.15)',
310
- borderRadius: 10,
225
+ borderColor: 'rgba(255,255,255,0.12)',
226
+ borderRadius: 12,
311
227
  paddingHorizontal: 14,
312
- paddingVertical: 12,
228
+ paddingVertical: 13,
313
229
  color: '#e0e0e0',
314
230
  fontSize: 15,
315
231
  },
316
- error: { color: '#ef4444', fontSize: 13, marginTop: 12 },
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
  });
@@ -2,6 +2,22 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ShakeDetector = void 0;
4
4
  const react_native_1 = require("react-native");
5
+ // Detects that the surrounding runtime is Yaver's super-host bridge. The
6
+ // YaverInfo native module is only registered inside Yaver's container
7
+ // (see mobile/ios/Yaver/YaverInfo.{swift,m} and the Android equivalent),
8
+ // so it is undefined in a standalone third-party app. When the SDK runs
9
+ // inside Yaver we yield shake handling to Yaver's native
10
+ // ShakeDetectingWindow — the user should only ever see the 2-button
11
+ // "Reload / Back to Yaver" overlay, never a FeedbackModal popped from
12
+ // inside the guest bundle.
13
+ function isRunningInsideYaverHost() {
14
+ try {
15
+ return !!react_native_1.NativeModules?.YaverInfo;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
5
21
  const SHAKE_TIMEOUT_MS = 1000; // minimum time between shakes
6
22
  const ACCEL_THRESHOLD_G = 1.8; // peak g-force that qualifies as a shake event
7
23
  const ACCEL_MIN_HITS = 3; // peaks within the window before we fire
@@ -34,6 +50,12 @@ class ShakeDetector {
34
50
  }
35
51
  start(onShake) {
36
52
  this.stop();
53
+ // When the app is loaded inside Yaver's super-host (Hermes push),
54
+ // Yaver owns the shake gesture and shows its own "Reload / Back to
55
+ // Yaver" overlay. We must not also fire the guest-side callback,
56
+ // otherwise the user gets both UIs at once.
57
+ if (isRunningInsideYaverHost())
58
+ return;
37
59
  this.subscribeDevMenu(onShake);
38
60
  this.subscribeAccelerometer(onShake);
39
61
  }
@@ -1,11 +1,34 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.YaverFeedback = void 0;
4
+ const react_native_1 = require("react-native");
4
5
  const Discovery_1 = require("./Discovery");
5
6
  const BlackBox_1 = require("./BlackBox");
6
7
  const P2PClient_1 = require("./P2PClient");
7
8
  const ShakeDetector_1 = require("./ShakeDetector");
8
9
  const auth_1 = require("./auth");
10
+ // Is this JS runtime the Yaver mobile app's super-host bridge? The
11
+ // YaverInfo native module is only registered inside Yaver's container
12
+ // (mobile/ios/Yaver/YaverInfo.{swift,m} + Android counterpart); a
13
+ // standalone app bundled by its own developer has no such module.
14
+ // When the SDK is loaded through Yaver's Hermes-push guest runtime we
15
+ // deliberately no-op every public entry point — Yaver owns the shake
16
+ // gesture ("Reload / Back to Yaver" overlay), the feedback capture
17
+ // flow, and the BlackBox streaming; running a second copy from inside
18
+ // the guest just produces duplicate UIs and double P2P sessions.
19
+ function isRunningInsideYaverHost() {
20
+ try {
21
+ return !!react_native_1.NativeModules?.YaverInfo;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ // Suppresses SDK activation when inside Yaver super-host. Callers of
28
+ // YaverFeedback.init / startReport / startBatchRecording / … early-out
29
+ // by checking this first so the SDK's side effects (accelerometer,
30
+ // BlackBox HTTP, SSE command channel, FeedbackModal mount) never start.
31
+ const YAVER_HOST_SUPPRESS = isRunningInsideYaverHost();
9
32
  let config = null;
10
33
  let enabled = false;
11
34
  let p2pClient = null;
@@ -34,6 +57,11 @@ class YaverFeedback {
34
57
  * via `YaverDiscovery` on the first `startReport()` call.
35
58
  */
36
59
  static init(cfg) {
60
+ if (YAVER_HOST_SUPPRESS) {
61
+ // Running inside Yaver's super-host — yield to Yaver's native UX.
62
+ enabled = false;
63
+ return;
64
+ }
37
65
  config = {
38
66
  trigger: 'shake',
39
67
  maxRecordingDuration: 120,