yaver-feedback-react-native 0.5.3 → 0.5.5

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.
Files changed (52) hide show
  1. package/README.md +30 -0
  2. package/dist/AuthOverlay.d.ts +16 -0
  3. package/dist/AuthOverlay.js +104 -0
  4. package/dist/BlackBox.d.ts +154 -0
  5. package/dist/BlackBox.js +395 -0
  6. package/dist/ConnectionScreen.d.ts +13 -0
  7. package/dist/ConnectionScreen.js +373 -0
  8. package/dist/Discovery.d.ts +59 -0
  9. package/dist/Discovery.js +293 -0
  10. package/dist/FeedbackModal.d.ts +11 -0
  11. package/dist/FeedbackModal.js +623 -0
  12. package/dist/FixReport.d.ts +23 -0
  13. package/dist/FixReport.js +282 -0
  14. package/dist/FloatingButton.d.ts +71 -0
  15. package/dist/FloatingButton.js +778 -0
  16. package/dist/LoginScreen.d.ts +14 -0
  17. package/dist/LoginScreen.js +317 -0
  18. package/dist/MachinePickerScreen.d.ts +19 -0
  19. package/dist/MachinePickerScreen.js +175 -0
  20. package/dist/P2PClient.d.ts +136 -0
  21. package/dist/P2PClient.js +357 -0
  22. package/dist/ShakeDetector.d.ts +39 -0
  23. package/dist/ShakeDetector.js +133 -0
  24. package/dist/YaverFeedback.d.ts +198 -0
  25. package/dist/YaverFeedback.js +707 -0
  26. package/dist/YaverUpdates.d.ts +78 -0
  27. package/dist/YaverUpdates.js +272 -0
  28. package/dist/__tests__/Discovery.test.d.ts +1 -0
  29. package/dist/__tests__/Discovery.test.js +164 -0
  30. package/dist/__tests__/P2PClient.test.d.ts +1 -0
  31. package/dist/__tests__/P2PClient.test.js +169 -0
  32. package/dist/__tests__/SDKToken.test.d.ts +1 -0
  33. package/dist/__tests__/SDKToken.test.js +215 -0
  34. package/dist/__tests__/YaverFeedback.test.d.ts +1 -0
  35. package/dist/__tests__/YaverFeedback.test.js +161 -0
  36. package/dist/__tests__/types.test.d.ts +1 -0
  37. package/dist/__tests__/types.test.js +219 -0
  38. package/dist/auth.d.ts +105 -0
  39. package/dist/auth.js +282 -0
  40. package/dist/capture.d.ts +27 -0
  41. package/dist/capture.js +74 -0
  42. package/dist/expo.d.ts +15 -0
  43. package/dist/expo.js +62 -0
  44. package/dist/index.d.ts +48 -0
  45. package/dist/index.js +80 -0
  46. package/dist/types.d.ts +282 -0
  47. package/dist/types.js +2 -0
  48. package/dist/upload.d.ts +13 -0
  49. package/dist/upload.js +59 -0
  50. package/package.json +6 -3
  51. package/src/ShakeDetector.ts +22 -1
  52. package/src/YaverFeedback.ts +29 -0
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ export interface YaverLoginScreenProps {
3
+ /** Invoked once a session token is issued and the user is loaded. */
4
+ onLoggedIn: (token: string) => void;
5
+ /** Optional cancel button shown in header. */
6
+ onCancel?: () => void;
7
+ }
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.
13
+ */
14
+ export declare const YaverLoginScreen: React.FC<YaverLoginScreenProps>;
@@ -0,0 +1,317 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.YaverLoginScreen = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
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
+ /**
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.
52
+ */
53
+ 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');
61
+ const [fullName, setFullName] = (0, react_1.useState)('');
62
+ const [email, setEmail] = (0, react_1.useState)('');
63
+ const [password, setPassword] = (0, react_1.useState)('');
64
+ 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);
69
+ }
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;
79
+ }
80
+ if (expiredTimerRef.current) {
81
+ clearTimeout(expiredTimerRef.current);
82
+ expiredTimerRef.current = null;
83
+ }
84
+ };
85
+ const beginDeviceCode = async (preferredProvider) => {
86
+ setStarting(true);
87
+ setCodeError(null);
88
+ stopPolling();
89
+ 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()));
117
+ }
118
+ catch (err) {
119
+ setCodeError(err instanceof Error ? err.message : String(err));
120
+ }
121
+ finally {
122
+ setStarting(false);
123
+ }
124
+ };
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
+ const handleEmailSubmit = async () => {
133
+ setEmailError(null);
134
+ if (!email.trim() || !password) {
135
+ setEmailError('E-posta ve parola zorunlu.');
136
+ return;
137
+ }
138
+ setEmailBusy(true);
139
+ try {
140
+ const result = emailMode === 'signup'
141
+ ? await (0, auth_1.signupWithEmail)(fullName.trim() || email.trim(), email.trim(), password)
142
+ : 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);
148
+ }
149
+ catch (err) {
150
+ setEmailError(err instanceof Error ? err.message : String(err));
151
+ }
152
+ finally {
153
+ setEmailBusy(false);
154
+ }
155
+ };
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>
164
+
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>
177
+
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 aç 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>
186
+ </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>
190
+
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>)}
204
+
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>
214
+
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"/>
218
+ </>)}
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>
235
+ </react_native_1.SafeAreaView>);
236
+ };
237
+ exports.YaverLoginScreen = YaverLoginScreen;
238
+ 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,
246
+ },
247
+ title: { fontSize: 22, fontWeight: '700', color: '#e0e0e0' },
248
+ cancel: { padding: 8 },
249
+ 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)',
274
+ 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',
291
+ borderRadius: 12,
292
+ paddingVertical: 14,
293
+ alignItems: 'center',
294
+ marginTop: 8,
295
+ },
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,
303
+ },
304
+ providerButtonText: { color: '#e0e0e0', fontSize: 13, fontWeight: '600' },
305
+ label: { color: '#9ca3af', fontSize: 12, marginTop: 14, marginBottom: 6 },
306
+ input: {
307
+ backgroundColor: 'rgba(255,255,255,0.08)',
308
+ borderWidth: 1,
309
+ borderColor: 'rgba(255,255,255,0.15)',
310
+ borderRadius: 10,
311
+ paddingHorizontal: 14,
312
+ paddingVertical: 12,
313
+ color: '#e0e0e0',
314
+ fontSize: 15,
315
+ },
316
+ error: { color: '#ef4444', fontSize: 13, marginTop: 12 },
317
+ });
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { RemoteDevice } from './auth';
3
+ export interface YaverMachinePickerProps {
4
+ token: string;
5
+ /** Currently-selected deviceId (from config / cache) — highlighted. */
6
+ currentDeviceId?: string;
7
+ onPick: (device: RemoteDevice) => void;
8
+ onCancel?: () => void;
9
+ }
10
+ /**
11
+ * List of remote dev machines the signed-in user can reach. Split into
12
+ * - Owned machines (user is the host)
13
+ * - Shared machines (host invited them as a guest)
14
+ *
15
+ * Tapping a device persists it to AsyncStorage and invokes `onPick`. The
16
+ * SDK then uses that device's deviceId for agent discovery (LAN probe +
17
+ * relay fallback through Convex).
18
+ */
19
+ export declare const YaverMachinePickerScreen: React.FC<YaverMachinePickerProps>;
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.YaverMachinePickerScreen = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const auth_1 = require("./auth");
40
+ /**
41
+ * List of remote dev machines the signed-in user can reach. Split into
42
+ * - Owned machines (user is the host)
43
+ * - Shared machines (host invited them as a guest)
44
+ *
45
+ * Tapping a device persists it to AsyncStorage and invokes `onPick`. The
46
+ * SDK then uses that device's deviceId for agent discovery (LAN probe +
47
+ * relay fallback through Convex).
48
+ */
49
+ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, }) => {
50
+ const [loading, setLoading] = (0, react_1.useState)(true);
51
+ const [refreshing, setRefreshing] = (0, react_1.useState)(false);
52
+ const [error, setError] = (0, react_1.useState)(null);
53
+ const [list, setList] = (0, react_1.useState)({ owned: [], shared: [] });
54
+ const load = (0, react_1.useCallback)(async (silent = false) => {
55
+ if (!silent)
56
+ setLoading(true);
57
+ setError(null);
58
+ try {
59
+ const result = await (0, auth_1.listReachableDevices)(token);
60
+ setList(result);
61
+ if (result.owned.length === 0 && result.shared.length === 0) {
62
+ setError('Hiç makine bulunamadı — önce bir makinede `yaver auth` + `yaver serve` çalıştır.');
63
+ }
64
+ }
65
+ catch (err) {
66
+ setError(err instanceof Error ? err.message : String(err));
67
+ }
68
+ finally {
69
+ setLoading(false);
70
+ setRefreshing(false);
71
+ }
72
+ }, [token]);
73
+ (0, react_1.useEffect)(() => {
74
+ void load();
75
+ }, [load]);
76
+ const handlePick = async (device) => {
77
+ await (0, auth_1.saveSelectedDeviceId)(device.deviceId);
78
+ onPick(device);
79
+ };
80
+ const renderDevice = (device) => {
81
+ const selected = device.deviceId === currentDeviceId;
82
+ const stale = Date.now() - device.lastHeartbeat > 60000;
83
+ const healthColor = !device.isOnline
84
+ ? '#ef4444'
85
+ : device.needsAuth || device.runnerDown || stale
86
+ ? '#f59e0b'
87
+ : '#22c55e';
88
+ return (<react_native_1.TouchableOpacity key={device.deviceId} style={[styles.deviceRow, selected && styles.deviceSelected]} onPress={() => handlePick(device)}>
89
+ <react_native_1.View style={[styles.health, { backgroundColor: healthColor }]}/>
90
+ <react_native_1.View style={{ flex: 1 }}>
91
+ <react_native_1.Text style={styles.deviceName}>{device.name || device.deviceId}</react_native_1.Text>
92
+ <react_native_1.Text style={styles.deviceMeta}>
93
+ {device.platform}
94
+ {device.isGuest && device.hostEmail ? ` • ${device.hostEmail}` : ''}
95
+ {device.accessScope === 'shared-scoped' ? ' • paylaşılan' : ''}
96
+ </react_native_1.Text>
97
+ </react_native_1.View>
98
+ {selected && <react_native_1.Text style={styles.selectedBadge}>seçili</react_native_1.Text>}
99
+ </react_native_1.TouchableOpacity>);
100
+ };
101
+ return (<react_native_1.SafeAreaView style={styles.container}>
102
+ <react_native_1.View style={styles.header}>
103
+ <react_native_1.Text style={styles.title}>Makine Seç</react_native_1.Text>
104
+ {onCancel && (<react_native_1.TouchableOpacity onPress={onCancel} style={styles.cancel}>
105
+ <react_native_1.Text style={styles.cancelText}>Kapat</react_native_1.Text>
106
+ </react_native_1.TouchableOpacity>)}
107
+ </react_native_1.View>
108
+
109
+ <react_native_1.ScrollView contentContainerStyle={styles.content} refreshControl={<react_native_1.RefreshControl refreshing={refreshing} onRefresh={() => {
110
+ setRefreshing(true);
111
+ void load(true);
112
+ }} tintColor="#6366f1"/>}>
113
+ {loading ? (<react_native_1.ActivityIndicator color="#6366f1" style={{ marginTop: 60 }}/>) : (<>
114
+ {list.owned.length > 0 && (<react_native_1.View style={styles.section}>
115
+ <react_native_1.Text style={styles.sectionTitle}>Kendi makinelerim</react_native_1.Text>
116
+ {list.owned.map(renderDevice)}
117
+ </react_native_1.View>)}
118
+ {list.shared.length > 0 && (<react_native_1.View style={styles.section}>
119
+ <react_native_1.Text style={styles.sectionTitle}>Paylaşılan (guest)</react_native_1.Text>
120
+ {list.shared.map(renderDevice)}
121
+ </react_native_1.View>)}
122
+ {error && <react_native_1.Text style={styles.error}>{error}</react_native_1.Text>}
123
+ </>)}
124
+ </react_native_1.ScrollView>
125
+ </react_native_1.SafeAreaView>);
126
+ };
127
+ exports.YaverMachinePickerScreen = YaverMachinePickerScreen;
128
+ const styles = react_native_1.StyleSheet.create({
129
+ container: { flex: 1, backgroundColor: '#1a1a2e' },
130
+ header: {
131
+ flexDirection: 'row',
132
+ alignItems: 'center',
133
+ justifyContent: 'space-between',
134
+ paddingHorizontal: 20,
135
+ paddingVertical: 16,
136
+ },
137
+ title: { color: '#e0e0e0', fontSize: 20, fontWeight: '700' },
138
+ cancel: { padding: 8 },
139
+ cancelText: { color: '#9ca3af', fontSize: 14 },
140
+ content: { padding: 20, paddingTop: 0 },
141
+ section: { marginBottom: 24 },
142
+ sectionTitle: {
143
+ color: '#9ca3af',
144
+ fontSize: 12,
145
+ fontWeight: '600',
146
+ textTransform: 'uppercase',
147
+ letterSpacing: 1,
148
+ marginBottom: 10,
149
+ },
150
+ deviceRow: {
151
+ flexDirection: 'row',
152
+ alignItems: 'center',
153
+ gap: 12,
154
+ padding: 14,
155
+ backgroundColor: 'rgba(255,255,255,0.05)',
156
+ borderRadius: 12,
157
+ marginBottom: 8,
158
+ borderWidth: 1,
159
+ borderColor: 'transparent',
160
+ },
161
+ deviceSelected: {
162
+ borderColor: 'rgba(99,102,241,0.5)',
163
+ backgroundColor: 'rgba(99,102,241,0.15)',
164
+ },
165
+ health: { width: 10, height: 10, borderRadius: 5 },
166
+ deviceName: { color: '#e0e0e0', fontSize: 15, fontWeight: '600' },
167
+ deviceMeta: { color: '#9ca3af', fontSize: 12, marginTop: 2 },
168
+ selectedBadge: {
169
+ color: '#a5b4fc',
170
+ fontSize: 11,
171
+ fontWeight: '700',
172
+ textTransform: 'uppercase',
173
+ },
174
+ error: { color: '#ef4444', fontSize: 13, marginTop: 16, textAlign: 'center' },
175
+ });
@@ -0,0 +1,136 @@
1
+ import { FeedbackBundle, TestSession, VoiceCapability } from './types';
2
+ export interface FeedbackEvent {
3
+ type: string;
4
+ timestamp: string;
5
+ data: any;
6
+ }
7
+ /**
8
+ * Lightweight P2P HTTP client for communicating with a Yaver agent.
9
+ *
10
+ * Reuses the same endpoint patterns as the main upload module but adds
11
+ * support for streaming feedback, listing builds, and triggering builds.
12
+ */
13
+ export declare class P2PClient {
14
+ private baseUrl;
15
+ private authToken;
16
+ constructor(baseUrl: string, authToken: string);
17
+ /** Update the base URL (e.g. after re-discovery). */
18
+ setBaseUrl(url: string): void;
19
+ /** Update the auth token. */
20
+ setAuthToken(token: string): void;
21
+ /** Health check — returns true if the agent is reachable. */
22
+ health(): Promise<boolean>;
23
+ /** Get agent info (hostname, version, platform). */
24
+ info(): Promise<{
25
+ hostname: string;
26
+ version: string;
27
+ platform: string;
28
+ }>;
29
+ /**
30
+ * Upload a feedback bundle via multipart POST.
31
+ * @returns The feedback report ID from the agent.
32
+ */
33
+ uploadFeedback(bundle: FeedbackBundle): Promise<string>;
34
+ /**
35
+ * Stream feedback events to the agent in live mode.
36
+ * Sends each event as a JSON POST to `/feedback/stream`.
37
+ */
38
+ streamFeedback(events: AsyncIterable<FeedbackEvent>): Promise<void>;
39
+ /** List available builds from the agent. */
40
+ listBuilds(): Promise<any[]>;
41
+ /** Start a build for the given platform. */
42
+ startBuild(platform: string): Promise<any>;
43
+ /**
44
+ * Get voice capability info from the agent.
45
+ * voiceInputEnabled is always true — mobile can always record and send audio.
46
+ * s2sProvider/sttProvider indicate whether transcription is available.
47
+ */
48
+ voiceStatus(): Promise<VoiceCapability>;
49
+ /**
50
+ * Send voice audio to the agent for transcription.
51
+ * Works with any configured STT or S2S provider on the agent.
52
+ * If no provider is configured, audio is saved for manual review.
53
+ * @returns Transcribed text (or empty string if no provider available).
54
+ */
55
+ transcribeVoice(audioUri: string): Promise<{
56
+ text: string;
57
+ provider: string;
58
+ audioFile?: string;
59
+ }>;
60
+ /**
61
+ * Trigger a reload of the third-party app.
62
+ * In dev mode, this tells the dev server to hot-reload.
63
+ * In bundle mode, this rebuilds the native bundle and pushes it.
64
+ * The reload command is also broadcast to all connected SDK devices
65
+ * via the BlackBox command channel.
66
+ * @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
67
+ */
68
+ reloadApp(mode?: 'dev' | 'bundle'): Promise<{
69
+ ok: boolean;
70
+ }>;
71
+ /** Get the download URL for a build artifact. */
72
+ getArtifactUrl(buildId: string): string;
73
+ /**
74
+ * Start an autonomous test session.
75
+ * The agent reads the codebase for context, then navigates the app
76
+ * on the connected device/emulator, catches exceptions via BlackBox,
77
+ * writes fixes, and hot reloads — all without committing.
78
+ */
79
+ startTestSession(): Promise<{
80
+ sessionId: string;
81
+ }>;
82
+ /** Stop a running test session. */
83
+ stopTestSession(): Promise<void>;
84
+ /** Get the current test session status and list of fixes. */
85
+ getTestSession(): Promise<TestSession>;
86
+ /**
87
+ * Rotate the SDK token. The old token stays valid for 5 minutes (grace period).
88
+ * After rotation, the client automatically uses the new token.
89
+ * @returns The new token and its expiry time.
90
+ */
91
+ rotateToken(): Promise<{
92
+ token: string;
93
+ expiresAt: number;
94
+ }>;
95
+ /**
96
+ * Evaluate every flag for a userId. Hits /flags/eval which uses
97
+ * SHA256 bucketing against rolloutPercent — stable per user per
98
+ * flag. Results are the dev's source of truth; the SDK caches
99
+ * for 30s in getFlagsCached().
100
+ */
101
+ flagsEvaluate(userId?: string): Promise<Record<string, unknown>>;
102
+ /** Evaluate a single flag by key — shortcut when you only need one. */
103
+ flagsEvaluateOne<T = unknown>(key: string, userId?: string): Promise<T | undefined>;
104
+ /**
105
+ * Ask what bundle this device should run. Returns the latest
106
+ * release in the channel plus a rollout gate. The mobile app
107
+ * uses this on cold start to decide whether to download a new
108
+ * bundle from /releases/bundle.
109
+ */
110
+ releasesLatest(channel?: string, deviceId?: string): Promise<{
111
+ ok: boolean;
112
+ channel: string;
113
+ semver?: string;
114
+ size?: number;
115
+ md5?: string;
116
+ hermesBcVersion?: number;
117
+ bundleUrl?: string;
118
+ rolloutPercent: number;
119
+ inRollout: boolean;
120
+ reason?: string;
121
+ } | null>;
122
+ /** Download a specific bundle as raw bytes. */
123
+ releasesDownload(channel: string, semver: string): Promise<ArrayBuffer | null>;
124
+ /**
125
+ * Fire-and-forget track event. Most callers should use
126
+ * `BlackBox.track()` which fans through the streaming channel;
127
+ * this method is the fallback for surfaces without a live SSE.
128
+ */
129
+ analyticsIngest(name: string, props?: Record<string, string>, opts?: {
130
+ deviceId?: string;
131
+ route?: string;
132
+ timestamp?: number;
133
+ }): Promise<boolean>;
134
+ /** Internal helper for authenticated GET/POST requests. */
135
+ private request;
136
+ }