yaver-feedback-react-native 0.2.0 → 0.4.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 +132 -26
- package/android/src/main/java/io/yaver/feedback/YaverHotReloadModule.java +188 -0
- package/android/src/main/java/io/yaver/feedback/YaverHotReloadPackage.java +33 -0
- package/app.plugin.js +265 -7
- package/ios/YaverHotReload.m +16 -0
- package/ios/YaverHotReload.swift +112 -0
- package/package.json +3 -1
- package/src/BlackBox.ts +150 -0
- package/src/Discovery.ts +125 -5
- package/src/FeedbackModal.tsx +2 -2
- package/src/FloatingButton.tsx +19 -7
- package/src/P2PClient.ts +26 -0
- package/src/YaverFeedback.ts +130 -0
- package/src/index.ts +1 -1
- package/src/types.ts +24 -0
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# yaver-feedback-react-native
|
|
2
2
|
|
|
3
3
|
Visual feedback SDK for Yaver. Lets testers and developers shake their phone (or tap a floating button) to capture screenshots, record voice notes, and send bug reports directly to a Yaver agent running on a dev machine. Built for vibe coding workflows where feedback needs to flow fast.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install
|
|
8
|
+
npm install yaver-feedback-react-native
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
### Peer dependencies
|
|
@@ -26,7 +26,7 @@ npm install react-native-audio-recorder-player
|
|
|
26
26
|
## Quick Start
|
|
27
27
|
|
|
28
28
|
```tsx
|
|
29
|
-
import { YaverFeedback, BlackBox, FeedbackModal } from '
|
|
29
|
+
import { YaverFeedback, BlackBox, FeedbackModal } from 'yaver-feedback-react-native';
|
|
30
30
|
|
|
31
31
|
// Initialize once at app startup
|
|
32
32
|
YaverFeedback.init({
|
|
@@ -164,7 +164,7 @@ yaver sdk-token create --expires 24h
|
|
|
164
164
|
Continuous streaming of app events to the agent. The agent keeps a ring buffer (last 1000 events per device) and injects context into fix prompts — so the AI agent already knows what the app was doing when you ask for a fix.
|
|
165
165
|
|
|
166
166
|
```tsx
|
|
167
|
-
import { BlackBox } from '
|
|
167
|
+
import { BlackBox } from 'yaver-feedback-react-native';
|
|
168
168
|
|
|
169
169
|
// Start streaming (call after YaverFeedback.init)
|
|
170
170
|
BlackBox.start({
|
|
@@ -219,6 +219,102 @@ BlackBox.isStreaming; // check if active
|
|
|
219
219
|
| `state` | State mutations | `BlackBox.stateChange('theme toggled')` |
|
|
220
220
|
| `render` | Component render with duration | `BlackBox.render('FlatList', 32.1)` |
|
|
221
221
|
|
|
222
|
+
## Remote Reload (Agent Command Channel)
|
|
223
|
+
|
|
224
|
+
The SDK maintains a persistent SSE connection to the agent that receives commands. The primary use case: a vibe coder is away from their desk, coding on their phone via the Yaver mobile app, and wants to hot-reload the third-party app (with this SDK) running on the same or a different device.
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
Yaver Mobile App Agent Third-Party App (SDK)
|
|
228
|
+
tap "Reload"
|
|
229
|
+
───POST /dev/reload-app──► process reload
|
|
230
|
+
├─ dev server reload
|
|
231
|
+
└─ BroadcastCommand("reload")
|
|
232
|
+
───SSE push──────────────► BlackBox receives command
|
|
233
|
+
├─ onReload() callback
|
|
234
|
+
└─ DevSettings.reload()
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Works over both direct LAN and relay connections since it's standard HTTP/SSE.
|
|
238
|
+
|
|
239
|
+
### Setup
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import { YaverFeedback, BlackBox } from 'yaver-feedback-react-native';
|
|
243
|
+
|
|
244
|
+
YaverFeedback.init({
|
|
245
|
+
authToken: 'your-sdk-token',
|
|
246
|
+
agentUrl: 'http://192.168.1.10:18080',
|
|
247
|
+
|
|
248
|
+
// Called when the vibe coder triggers reload from Yaver mobile app
|
|
249
|
+
onReload: () => {
|
|
250
|
+
console.log('Remote reload triggered!');
|
|
251
|
+
// Default behavior if omitted: DevSettings.reload() in dev mode
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// Called when agent pushes a new native bundle
|
|
255
|
+
onReloadBundle: (bundleUrl, assetsUrl) => {
|
|
256
|
+
console.log('New bundle available at:', bundleUrl);
|
|
257
|
+
// Default: no-op. Implement custom bundle loading if needed.
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Start BlackBox — this also connects the command channel
|
|
262
|
+
BlackBox.start({ appName: 'AcmeStore' });
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Custom Command Handlers
|
|
266
|
+
|
|
267
|
+
For advanced use cases, register handlers directly on BlackBox:
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
// Listen for any command from the agent
|
|
271
|
+
const unsubscribe = BlackBox.onCommand((cmd) => {
|
|
272
|
+
switch (cmd.command) {
|
|
273
|
+
case 'reload':
|
|
274
|
+
// Hot reload from dev server
|
|
275
|
+
DevSettings.reload();
|
|
276
|
+
break;
|
|
277
|
+
case 'reload_bundle':
|
|
278
|
+
// Fetch new native bundle
|
|
279
|
+
const { bundleUrl, assetsUrl } = cmd.data;
|
|
280
|
+
loadNewBundle(bundleUrl, assetsUrl);
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
console.log('Unknown command:', cmd.command);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Check connection status
|
|
288
|
+
BlackBox.isCommandChannelConnected; // boolean
|
|
289
|
+
|
|
290
|
+
// Clean up
|
|
291
|
+
unsubscribe();
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Triggering Reload from P2PClient
|
|
295
|
+
|
|
296
|
+
The SDK can also trigger reload programmatically (e.g., from a "Reload" button in the Feedback modal):
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
import { P2PClient } from 'yaver-feedback-react-native';
|
|
300
|
+
|
|
301
|
+
const client = new P2PClient('http://192.168.1.10:18080', 'your-token');
|
|
302
|
+
|
|
303
|
+
// Hot reload (dev server restart)
|
|
304
|
+
await client.reloadApp('dev');
|
|
305
|
+
|
|
306
|
+
// Rebuild native bundle + push to all connected SDK devices
|
|
307
|
+
await client.reloadApp('bundle');
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### How It Works
|
|
311
|
+
|
|
312
|
+
1. `BlackBox.start()` connects to `/blackbox/command-stream` via SSE
|
|
313
|
+
2. The connection auto-reconnects on disconnect (5s backoff)
|
|
314
|
+
3. When the agent receives `POST /dev/reload` or `POST /dev/reload-app`, it broadcasts a command to all connected SDK sessions
|
|
315
|
+
4. The SDK invokes `onReload` / `onReloadBundle` callbacks, or falls back to `DevSettings.reload()` in dev mode
|
|
316
|
+
5. Events are still sent via batch POST to `/blackbox/events` (unchanged)
|
|
317
|
+
|
|
222
318
|
### Resilience
|
|
223
319
|
|
|
224
320
|
- Failed flushes re-add events to the buffer (capped at 2x maxBufferSize)
|
|
@@ -258,7 +354,7 @@ YaverFeedback.init({
|
|
|
258
354
|
### Manual discovery
|
|
259
355
|
|
|
260
356
|
```typescript
|
|
261
|
-
import { YaverDiscovery } from '
|
|
357
|
+
import { YaverDiscovery } from 'yaver-feedback-react-native';
|
|
262
358
|
|
|
263
359
|
// Full discovery (Convex → stored → LAN scan)
|
|
264
360
|
const result = await YaverDiscovery.discover({
|
|
@@ -281,7 +377,7 @@ await YaverDiscovery.clear();
|
|
|
281
377
|
A full-screen UI for discovering and connecting to a Yaver agent. Shows connection status, URL/token inputs, auto-discover button, and a Start/Stop testing toggle with recording timer.
|
|
282
378
|
|
|
283
379
|
```tsx
|
|
284
|
-
import { YaverConnectionScreen } from '
|
|
380
|
+
import { YaverConnectionScreen } from 'yaver-feedback-react-native';
|
|
285
381
|
|
|
286
382
|
function App() {
|
|
287
383
|
return (
|
|
@@ -445,6 +541,10 @@ YaverFeedback.init({
|
|
|
445
541
|
feedbackMode: 'batch', // 'live' | 'narrated' | 'batch' (default: 'batch')
|
|
446
542
|
agentCommentaryLevel: 0, // 0-10 (default: 0, only relevant in live mode)
|
|
447
543
|
maxCapturedErrors: 5, // Error ring buffer size (default: 5)
|
|
544
|
+
|
|
545
|
+
// Remote reload callbacks (from Yaver mobile app or another SDK device)
|
|
546
|
+
onReload: () => { ... }, // Called on hot reload command (default: DevSettings.reload())
|
|
547
|
+
onReloadBundle: (url, assets) => { ... },// Called on native bundle rebuild command
|
|
448
548
|
});
|
|
449
549
|
```
|
|
450
550
|
|
|
@@ -463,7 +563,7 @@ YaverFeedback.init({ authToken, trigger: 'shake' });
|
|
|
463
563
|
A small draggable "Y" button overlays the app. Tap to open the feedback modal.
|
|
464
564
|
|
|
465
565
|
```tsx
|
|
466
|
-
import { FloatingButton, FeedbackModal, YaverFeedback } from '
|
|
566
|
+
import { FloatingButton, FeedbackModal, YaverFeedback } from 'yaver-feedback-react-native';
|
|
467
567
|
|
|
468
568
|
function App() {
|
|
469
569
|
return (
|
|
@@ -481,7 +581,7 @@ function App() {
|
|
|
481
581
|
Trigger feedback collection programmatically from anywhere in your app.
|
|
482
582
|
|
|
483
583
|
```typescript
|
|
484
|
-
import { YaverFeedback } from '
|
|
584
|
+
import { YaverFeedback } from 'yaver-feedback-react-native';
|
|
485
585
|
|
|
486
586
|
// In a button handler, debug menu, etc.
|
|
487
587
|
YaverFeedback.startReport();
|
|
@@ -492,7 +592,7 @@ YaverFeedback.startReport();
|
|
|
492
592
|
For direct communication with the Yaver agent beyond feedback:
|
|
493
593
|
|
|
494
594
|
```typescript
|
|
495
|
-
import { P2PClient } from '
|
|
595
|
+
import { P2PClient } from 'yaver-feedback-react-native';
|
|
496
596
|
|
|
497
597
|
const client = new P2PClient('http://192.168.1.10:18080', 'your-token');
|
|
498
598
|
|
|
@@ -608,6 +708,8 @@ YaverFeedback.setEnabled(false);
|
|
|
608
708
|
| `wrapConsole()` | Intercept console.log/warn/error |
|
|
609
709
|
| `unwrapConsole()` | Restore original console methods |
|
|
610
710
|
| `wrapErrorHandler(next?)` | Pass-through error handler with real-time streaming |
|
|
711
|
+
| `onCommand(handler)` | Register handler for agent commands (reload, etc.). Returns unsubscribe fn |
|
|
712
|
+
| `isCommandChannelConnected` | Whether the SSE command channel is connected (getter) |
|
|
611
713
|
|
|
612
714
|
### P2PClient
|
|
613
715
|
|
|
@@ -622,6 +724,7 @@ YaverFeedback.setEnabled(false);
|
|
|
622
724
|
| `getArtifactUrl(buildId)` | Get download URL for a build artifact |
|
|
623
725
|
| `voiceStatus()` | Get voice capability info |
|
|
624
726
|
| `transcribeVoice(audioUri)` | Send audio for transcription |
|
|
727
|
+
| `reloadApp(mode)` | Trigger remote reload: `'dev'` (hot reload) or `'bundle'` (rebuild + push) |
|
|
625
728
|
| `startTestSession()` | Start autonomous test session |
|
|
626
729
|
| `stopTestSession()` | Stop test session |
|
|
627
730
|
| `getTestSession()` | Get test session status + fixes |
|
|
@@ -655,8 +758,10 @@ The SDK communicates with these agent HTTP endpoints:
|
|
|
655
758
|
| `/feedback` | POST | Upload feedback bundle (multipart) |
|
|
656
759
|
| `/feedback/stream` | POST | Stream live feedback events |
|
|
657
760
|
| `/blackbox/events` | POST | Batch stream Black Box events |
|
|
761
|
+
| `/blackbox/command-stream` | GET | SSE command channel (agent pushes reload, etc.) |
|
|
658
762
|
| `/blackbox/subscribe` | GET | SSE live log stream |
|
|
659
763
|
| `/blackbox/context` | GET | Get generated prompt context |
|
|
764
|
+
| `/dev/reload-app` | POST | Trigger remote reload of SDK-connected apps |
|
|
660
765
|
| `/builds` | GET/POST | List or start builds |
|
|
661
766
|
| `/voice/status` | GET | Voice capability info |
|
|
662
767
|
| `/voice/transcribe` | POST | Send audio for transcription |
|
|
@@ -667,23 +772,24 @@ The SDK communicates with these agent HTTP endpoints:
|
|
|
667
772
|
## Architecture
|
|
668
773
|
|
|
669
774
|
```
|
|
670
|
-
┌──────────────────┐ HTTP (Bearer auth) ┌──────────────────┐
|
|
671
|
-
│ Your App │────────────────────────────►│ Yaver Agent │
|
|
672
|
-
│ + Feedback SDK │ feedback, blackbox events │ (Go CLI) │
|
|
673
|
-
│ + BlackBox │ screenshots, voice, video │ on your machine │
|
|
674
|
-
│ │◄────────────────────────────│ │
|
|
675
|
-
│ │
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
│
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
│
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
775
|
+
┌──────────────────┐ HTTP (Bearer auth) ┌──────────────────┐ HTTP/SSE ┌──────────────────┐
|
|
776
|
+
│ Your App │────────────────────────────►│ Yaver Agent │◄────────────────── │ Yaver Mobile │
|
|
777
|
+
│ + Feedback SDK │ feedback, blackbox events │ (Go CLI) │ tasks, reload │ App (phone) │
|
|
778
|
+
│ + BlackBox │ screenshots, voice, video │ on your machine │ commands │ vibe coding │
|
|
779
|
+
│ │◄────────────────────────────│ │ │ │
|
|
780
|
+
│ │ reload commands (SSE), │ runs AI agent │ │ │
|
|
781
|
+
│ │ fixes, build status, voice │ │ │ │
|
|
782
|
+
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
|
783
|
+
│ │ │
|
|
784
|
+
│ Auth only │ Auth only │
|
|
785
|
+
▼ ▼ ▼
|
|
786
|
+
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
787
|
+
│ Convex Backend │
|
|
788
|
+
│ Token validation + device registry (no task data stored) │
|
|
789
|
+
└──────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
All feedback data flows P2P between your app and the agent. The Yaver mobile app can trigger remote reloads that reach your app via the agent's command channel. Convex handles only auth and device discovery.
|
|
687
793
|
|
|
688
794
|
## License
|
|
689
795
|
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
package io.yaver.feedback;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.SharedPreferences;
|
|
6
|
+
import android.os.Handler;
|
|
7
|
+
import android.os.Looper;
|
|
8
|
+
import android.util.Log;
|
|
9
|
+
|
|
10
|
+
import androidx.annotation.NonNull;
|
|
11
|
+
|
|
12
|
+
import com.facebook.react.bridge.Promise;
|
|
13
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
14
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
15
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
16
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
17
|
+
import com.facebook.react.bridge.WritableMap;
|
|
18
|
+
import com.facebook.react.bridge.Arguments;
|
|
19
|
+
import com.facebook.react.ReactApplication;
|
|
20
|
+
import com.facebook.react.ReactInstanceManager;
|
|
21
|
+
|
|
22
|
+
import java.io.File;
|
|
23
|
+
import java.io.FileOutputStream;
|
|
24
|
+
import java.io.InputStream;
|
|
25
|
+
import java.net.HttpURLConnection;
|
|
26
|
+
import java.net.URL;
|
|
27
|
+
import java.util.concurrent.Executors;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Hot reload native module for the Yaver Feedback SDK (Android).
|
|
31
|
+
*
|
|
32
|
+
* Downloads a Hermes bytecode bundle from the agent, saves it to the app's
|
|
33
|
+
* files directory, and recreates the React Native context to load the new bundle.
|
|
34
|
+
*
|
|
35
|
+
* Supports N reloads — each reload recreates the ReactContext with the updated bundle.
|
|
36
|
+
*/
|
|
37
|
+
public class YaverHotReloadModule extends ReactContextBaseJavaModule {
|
|
38
|
+
|
|
39
|
+
private static final String TAG = "YaverHotReload";
|
|
40
|
+
private static final String MODULE_NAME = "YaverHotReload";
|
|
41
|
+
private static final String BUNDLE_DIR = "yaver-hot-reload";
|
|
42
|
+
private static final String BUNDLE_FILE = "index.android.bundle";
|
|
43
|
+
private static final String PREFS_NAME = "yaver_hot_reload";
|
|
44
|
+
private static final String PREFS_KEY_BUNDLE = "bundle_path";
|
|
45
|
+
|
|
46
|
+
public YaverHotReloadModule(ReactApplicationContext context) {
|
|
47
|
+
super(context);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Override
|
|
51
|
+
@NonNull
|
|
52
|
+
public String getName() {
|
|
53
|
+
return MODULE_NAME;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Download a Hermes bundle from the agent and trigger a bridge reload.
|
|
58
|
+
*/
|
|
59
|
+
@ReactMethod
|
|
60
|
+
public void loadBundle(String urlString, ReadableMap headers, Promise promise) {
|
|
61
|
+
Executors.newSingleThreadExecutor().execute(() -> {
|
|
62
|
+
try {
|
|
63
|
+
URL url = new URL(urlString);
|
|
64
|
+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
65
|
+
conn.setConnectTimeout(60000);
|
|
66
|
+
conn.setReadTimeout(60000);
|
|
67
|
+
|
|
68
|
+
// Set auth headers
|
|
69
|
+
if (headers != null) {
|
|
70
|
+
if (headers.hasKey("Authorization")) {
|
|
71
|
+
conn.setRequestProperty("Authorization", headers.getString("Authorization"));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
int responseCode = conn.getResponseCode();
|
|
76
|
+
if (responseCode != 200) {
|
|
77
|
+
promise.reject("HTTP_ERROR", "Status " + responseCode);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
InputStream is = conn.getInputStream();
|
|
82
|
+
File dir = new File(getReactApplicationContext().getFilesDir(), BUNDLE_DIR);
|
|
83
|
+
if (!dir.exists()) dir.mkdirs();
|
|
84
|
+
File bundleFile = new File(dir, BUNDLE_FILE);
|
|
85
|
+
|
|
86
|
+
FileOutputStream fos = new FileOutputStream(bundleFile);
|
|
87
|
+
byte[] buffer = new byte[8192];
|
|
88
|
+
int bytesRead;
|
|
89
|
+
int totalBytes = 0;
|
|
90
|
+
while ((bytesRead = is.read(buffer)) != -1) {
|
|
91
|
+
fos.write(buffer, 0, bytesRead);
|
|
92
|
+
totalBytes += bytesRead;
|
|
93
|
+
}
|
|
94
|
+
fos.close();
|
|
95
|
+
is.close();
|
|
96
|
+
conn.disconnect();
|
|
97
|
+
|
|
98
|
+
Log.i(TAG, "saved " + totalBytes + " bytes to " + bundleFile.getAbsolutePath());
|
|
99
|
+
|
|
100
|
+
// Validate Hermes bytecode (magic bytes at offset 4)
|
|
101
|
+
if (totalBytes >= 12) {
|
|
102
|
+
java.io.RandomAccessFile raf = new java.io.RandomAccessFile(bundleFile, "r");
|
|
103
|
+
raf.seek(4);
|
|
104
|
+
int magic = Integer.reverseBytes(raf.readInt());
|
|
105
|
+
if (magic == 0x1F1903C1) {
|
|
106
|
+
int bcVersion = Integer.reverseBytes(raf.readInt());
|
|
107
|
+
Log.i(TAG, "Hermes bytecode BC" + bcVersion);
|
|
108
|
+
} else {
|
|
109
|
+
Log.w(TAG, "not Hermes bytecode (magic=0x" + Integer.toHexString(magic) + ")");
|
|
110
|
+
}
|
|
111
|
+
raf.close();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Save bundle path to SharedPreferences for next app launch
|
|
115
|
+
SharedPreferences prefs = getReactApplicationContext()
|
|
116
|
+
.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
117
|
+
prefs.edit().putString(PREFS_KEY_BUNDLE, bundleFile.getAbsolutePath()).apply();
|
|
118
|
+
|
|
119
|
+
WritableMap result = Arguments.createMap();
|
|
120
|
+
result.putBoolean("loaded", true);
|
|
121
|
+
result.putInt("size", totalBytes);
|
|
122
|
+
promise.resolve(result);
|
|
123
|
+
|
|
124
|
+
// Reload the bridge on the main thread
|
|
125
|
+
new Handler(Looper.getMainLooper()).post(() -> reloadBridge());
|
|
126
|
+
|
|
127
|
+
} catch (Exception e) {
|
|
128
|
+
Log.e(TAG, "download failed", e);
|
|
129
|
+
promise.reject("DOWNLOAD_FAILED", e.getMessage(), e);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@ReactMethod
|
|
135
|
+
public void hasBundle(Promise promise) {
|
|
136
|
+
File bundleFile = getSavedBundleFile(getReactApplicationContext());
|
|
137
|
+
promise.resolve(bundleFile != null && bundleFile.exists());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@ReactMethod
|
|
141
|
+
public void clearBundle(Promise promise) {
|
|
142
|
+
File dir = new File(getReactApplicationContext().getFilesDir(), BUNDLE_DIR);
|
|
143
|
+
if (dir.exists()) {
|
|
144
|
+
for (File f : dir.listFiles()) f.delete();
|
|
145
|
+
dir.delete();
|
|
146
|
+
}
|
|
147
|
+
SharedPreferences prefs = getReactApplicationContext()
|
|
148
|
+
.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
149
|
+
prefs.edit().remove(PREFS_KEY_BUNDLE).apply();
|
|
150
|
+
promise.resolve(true);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Recreate the React Native context with the new bundle.
|
|
155
|
+
*/
|
|
156
|
+
private void reloadBridge() {
|
|
157
|
+
Activity activity = getCurrentActivity();
|
|
158
|
+
if (activity == null) {
|
|
159
|
+
Log.e(TAG, "no current activity");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (activity.getApplication() instanceof ReactApplication) {
|
|
164
|
+
ReactApplication app = (ReactApplication) activity.getApplication();
|
|
165
|
+
ReactInstanceManager manager = app.getReactNativeHost().getReactInstanceManager();
|
|
166
|
+
Log.i(TAG, "recreating React context with new bundle");
|
|
167
|
+
manager.recreateReactContextInBackground();
|
|
168
|
+
} else {
|
|
169
|
+
Log.e(TAG, "Application does not implement ReactApplication");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// MARK: - Static helpers for Application/MainApplication
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Returns the hot-reloaded bundle file if it exists.
|
|
177
|
+
* Call from MainApplication.getJSBundleFile() to load the hot bundle on startup.
|
|
178
|
+
*/
|
|
179
|
+
public static File getSavedBundleFile(Context context) {
|
|
180
|
+
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
181
|
+
String path = prefs.getString(PREFS_KEY_BUNDLE, null);
|
|
182
|
+
if (path != null) {
|
|
183
|
+
File f = new File(path);
|
|
184
|
+
if (f.exists()) return f;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package io.yaver.feedback;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.ReactPackage;
|
|
6
|
+
import com.facebook.react.bridge.NativeModule;
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
9
|
+
|
|
10
|
+
import java.util.ArrayList;
|
|
11
|
+
import java.util.Collections;
|
|
12
|
+
import java.util.List;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* React Native package that registers the YaverHotReload native module.
|
|
16
|
+
* Auto-linked via the Expo config plugin.
|
|
17
|
+
*/
|
|
18
|
+
public class YaverHotReloadPackage implements ReactPackage {
|
|
19
|
+
|
|
20
|
+
@NonNull
|
|
21
|
+
@Override
|
|
22
|
+
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
|
|
23
|
+
List<NativeModule> modules = new ArrayList<>();
|
|
24
|
+
modules.add(new YaverHotReloadModule(reactContext));
|
|
25
|
+
return modules;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@NonNull
|
|
29
|
+
@Override
|
|
30
|
+
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
|
31
|
+
return Collections.emptyList();
|
|
32
|
+
}
|
|
33
|
+
}
|