react-native-buffered-blob 1.0.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/android/AGENTS.md +74 -0
- package/android/build.gradle +34 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/bufferedblob/BufferedBlobModule.kt +274 -0
- package/android/src/main/java/com/bufferedblob/BufferedBlobPackage.kt +32 -0
- package/android/src/main/java/com/bufferedblob/HandleRegistry.kt +84 -0
- package/android/src/main/java/com/bufferedblob/StreamingBridge.kt +211 -0
- package/cpp/AGENTS.md +71 -0
- package/cpp/AndroidPlatformBridge.cpp +437 -0
- package/cpp/AndroidPlatformBridge.h +79 -0
- package/cpp/BufferedBlobStreamingHostObject.cpp +344 -0
- package/cpp/BufferedBlobStreamingHostObject.h +118 -0
- package/cpp/CMakeLists.txt +49 -0
- package/cpp/jni_onload.cpp +32 -0
- package/ios/AGENTS.md +76 -0
- package/ios/BufferedBlobModule.h +44 -0
- package/ios/BufferedBlobModule.m +433 -0
- package/ios/BufferedBlobModule.mm +192 -0
- package/ios/BufferedBlobStreamingBridge.h +21 -0
- package/ios/BufferedBlobStreamingBridge.mm +442 -0
- package/ios/HandleRegistry.h +29 -0
- package/ios/HandleRegistry.m +67 -0
- package/ios/HandleTypes.h +83 -0
- package/ios/HandleTypes.m +333 -0
- package/lib/module/AGENTS.md +70 -0
- package/lib/module/NativeBufferedBlob.js +5 -0
- package/lib/module/NativeBufferedBlob.js.map +1 -0
- package/lib/module/api/AGENTS.md +62 -0
- package/lib/module/api/download.js +40 -0
- package/lib/module/api/download.js.map +1 -0
- package/lib/module/api/fileOps.js +70 -0
- package/lib/module/api/fileOps.js.map +1 -0
- package/lib/module/api/hash.js +13 -0
- package/lib/module/api/hash.js.map +1 -0
- package/lib/module/api/readFile.js +23 -0
- package/lib/module/api/readFile.js.map +1 -0
- package/lib/module/api/writeFile.js +18 -0
- package/lib/module/api/writeFile.js.map +1 -0
- package/lib/module/errors.js +45 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +25 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/module.js +19 -0
- package/lib/module/module.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/paths.js +32 -0
- package/lib/module/paths.js.map +1 -0
- package/lib/module/types.js +15 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/wrappers.js +107 -0
- package/lib/module/wrappers.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeBufferedBlob.d.ts +37 -0
- package/lib/typescript/src/NativeBufferedBlob.d.ts.map +1 -0
- package/lib/typescript/src/api/download.d.ts +13 -0
- package/lib/typescript/src/api/download.d.ts.map +1 -0
- package/lib/typescript/src/api/fileOps.d.ts +9 -0
- package/lib/typescript/src/api/fileOps.d.ts.map +1 -0
- package/lib/typescript/src/api/hash.d.ts +3 -0
- package/lib/typescript/src/api/hash.d.ts.map +1 -0
- package/lib/typescript/src/api/readFile.d.ts +3 -0
- package/lib/typescript/src/api/readFile.d.ts.map +1 -0
- package/lib/typescript/src/api/writeFile.d.ts +3 -0
- package/lib/typescript/src/api/writeFile.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +25 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +11 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/module.d.ts +23 -0
- package/lib/typescript/src/module.d.ts.map +1 -0
- package/lib/typescript/src/paths.d.ts +11 -0
- package/lib/typescript/src/paths.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +37 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/wrappers.d.ts +14 -0
- package/lib/typescript/src/wrappers.d.ts.map +1 -0
- package/package.json +114 -0
- package/react-native-buffered-blob.podspec +37 -0
- package/react-native.config.js +10 -0
- package/src/AGENTS.md +70 -0
- package/src/NativeBufferedBlob.ts +54 -0
- package/src/api/AGENTS.md +62 -0
- package/src/api/download.ts +46 -0
- package/src/api/fileOps.ts +83 -0
- package/src/api/hash.ts +14 -0
- package/src/api/readFile.ts +37 -0
- package/src/api/writeFile.ts +24 -0
- package/src/errors.ts +50 -0
- package/src/index.ts +28 -0
- package/src/module.ts +48 -0
- package/src/paths.ts +35 -0
- package/src/types.ts +42 -0
- package/src/wrappers.ts +123 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
package com.bufferedblob
|
|
2
|
+
|
|
3
|
+
import okhttp3.OkHttpClient
|
|
4
|
+
import okhttp3.Request
|
|
5
|
+
import java.io.File
|
|
6
|
+
import java.io.FileOutputStream
|
|
7
|
+
import java.io.IOException
|
|
8
|
+
import java.util.concurrent.TimeUnit
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Static bridge methods called from C++ via JNI.
|
|
12
|
+
* All methods operate on handles from HandleRegistry.
|
|
13
|
+
*/
|
|
14
|
+
object StreamingBridge {
|
|
15
|
+
|
|
16
|
+
private val httpClient = OkHttpClient.Builder()
|
|
17
|
+
.connectTimeout(30, TimeUnit.SECONDS)
|
|
18
|
+
.readTimeout(60, TimeUnit.SECONDS)
|
|
19
|
+
.writeTimeout(60, TimeUnit.SECONDS)
|
|
20
|
+
.build()
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Read the next chunk from a ReaderHandle.
|
|
24
|
+
* Returns null when EOF is reached.
|
|
25
|
+
*/
|
|
26
|
+
@JvmStatic
|
|
27
|
+
fun readNextChunk(handleId: Int): ByteArray? {
|
|
28
|
+
val reader = HandleRegistry.get<ReaderHandle>(handleId)
|
|
29
|
+
?: throw RuntimeException("[READER_CLOSED] Reader handle not found: $handleId")
|
|
30
|
+
|
|
31
|
+
synchronized(reader) {
|
|
32
|
+
if (reader.isClosed) throw RuntimeException("[READER_CLOSED] Reader is closed")
|
|
33
|
+
if (reader.isEOF) return null
|
|
34
|
+
|
|
35
|
+
val buffer = ByteArray(reader.bufferSize)
|
|
36
|
+
val bytesRead = reader.stream.read(buffer)
|
|
37
|
+
|
|
38
|
+
if (bytesRead == -1) {
|
|
39
|
+
reader.isEOF = true
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
reader.bytesRead += bytesRead
|
|
44
|
+
|
|
45
|
+
return if (bytesRead == buffer.size) {
|
|
46
|
+
buffer
|
|
47
|
+
} else {
|
|
48
|
+
buffer.copyOf(bytesRead)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Write data to a WriterHandle.
|
|
55
|
+
* Returns the number of bytes written.
|
|
56
|
+
*/
|
|
57
|
+
@JvmStatic
|
|
58
|
+
fun write(handleId: Int, data: ByteArray): Int {
|
|
59
|
+
val writer = HandleRegistry.get<WriterHandle>(handleId)
|
|
60
|
+
?: throw RuntimeException("[WRITER_CLOSED] Writer handle not found: $handleId")
|
|
61
|
+
|
|
62
|
+
synchronized(writer) {
|
|
63
|
+
if (writer.isClosed) throw RuntimeException("[WRITER_CLOSED] Writer is closed")
|
|
64
|
+
|
|
65
|
+
writer.stream.write(data)
|
|
66
|
+
writer.bytesWritten += data.size
|
|
67
|
+
return data.size
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Flush a WriterHandle.
|
|
73
|
+
*/
|
|
74
|
+
@JvmStatic
|
|
75
|
+
fun flush(handleId: Int) {
|
|
76
|
+
val writer = HandleRegistry.get<WriterHandle>(handleId)
|
|
77
|
+
?: throw RuntimeException("[WRITER_CLOSED] Writer handle not found: $handleId")
|
|
78
|
+
|
|
79
|
+
synchronized(writer) {
|
|
80
|
+
if (writer.isClosed) throw RuntimeException("[WRITER_CLOSED] Writer is closed")
|
|
81
|
+
|
|
82
|
+
writer.stream.flush()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Close a handle (reader, writer, or downloader).
|
|
88
|
+
*/
|
|
89
|
+
@JvmStatic
|
|
90
|
+
fun close(handleId: Int) {
|
|
91
|
+
HandleRegistry.remove(handleId)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Start a download synchronously (blocking the calling thread).
|
|
96
|
+
* Updates handle.bytesDownloaded and handle.totalBytes during download
|
|
97
|
+
* for progress polling from the C++ layer.
|
|
98
|
+
*/
|
|
99
|
+
@JvmStatic
|
|
100
|
+
fun startDownload(handleId: Int) {
|
|
101
|
+
val handle = HandleRegistry.get<DownloaderHandle>(handleId)
|
|
102
|
+
?: throw RuntimeException("[DOWNLOAD_FAILED] Download handle not found: $handleId")
|
|
103
|
+
|
|
104
|
+
if (handle.isCancelled) {
|
|
105
|
+
throw RuntimeException("[DOWNLOAD_CANCELLED] Download was cancelled")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
val requestBuilder = Request.Builder().url(handle.url)
|
|
109
|
+
for ((key, value) in handle.headers) {
|
|
110
|
+
requestBuilder.addHeader(key, value)
|
|
111
|
+
}
|
|
112
|
+
val request = requestBuilder.build()
|
|
113
|
+
|
|
114
|
+
val call = httpClient.newCall(request)
|
|
115
|
+
handle.call = call
|
|
116
|
+
|
|
117
|
+
val response = try {
|
|
118
|
+
call.execute()
|
|
119
|
+
} catch (e: IOException) {
|
|
120
|
+
if (handle.isCancelled) {
|
|
121
|
+
throw RuntimeException("[DOWNLOAD_CANCELLED] Download was cancelled")
|
|
122
|
+
}
|
|
123
|
+
throw RuntimeException("[DOWNLOAD_FAILED] ${e.message}")
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
response.use { resp ->
|
|
127
|
+
if (!resp.isSuccessful) {
|
|
128
|
+
throw RuntimeException("[DOWNLOAD_FAILED] HTTP ${resp.code}")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
val body = resp.body
|
|
132
|
+
?: throw RuntimeException("[DOWNLOAD_FAILED] Empty response body")
|
|
133
|
+
|
|
134
|
+
val contentLength = body.contentLength()
|
|
135
|
+
handle.totalBytes = contentLength
|
|
136
|
+
|
|
137
|
+
val destFile = File(handle.destPath)
|
|
138
|
+
destFile.parentFile?.mkdirs()
|
|
139
|
+
|
|
140
|
+
FileOutputStream(destFile).use { fos ->
|
|
141
|
+
val buffer = ByteArray(8192)
|
|
142
|
+
var bytesRead: Int
|
|
143
|
+
|
|
144
|
+
body.byteStream().use { inputStream ->
|
|
145
|
+
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
|
146
|
+
if (handle.isCancelled) {
|
|
147
|
+
throw RuntimeException("[DOWNLOAD_CANCELLED] Download was cancelled")
|
|
148
|
+
}
|
|
149
|
+
fos.write(buffer, 0, bytesRead)
|
|
150
|
+
handle.bytesDownloaded += bytesRead
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Cancel a download.
|
|
159
|
+
*/
|
|
160
|
+
@JvmStatic
|
|
161
|
+
fun cancelDownload(handleId: Int) {
|
|
162
|
+
val handle = HandleRegistry.get<DownloaderHandle>(handleId) ?: return
|
|
163
|
+
handle.cancel()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- Reader info getters ---
|
|
167
|
+
|
|
168
|
+
@JvmStatic
|
|
169
|
+
fun getReaderFileSize(handleId: Int): Long {
|
|
170
|
+
val reader = HandleRegistry.get<ReaderHandle>(handleId) ?: return 0L
|
|
171
|
+
return reader.fileSize
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@JvmStatic
|
|
175
|
+
fun getReaderBytesRead(handleId: Int): Long {
|
|
176
|
+
val reader = HandleRegistry.get<ReaderHandle>(handleId) ?: return 0L
|
|
177
|
+
return reader.bytesRead
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@JvmStatic
|
|
181
|
+
fun getReaderIsEOF(handleId: Int): Boolean {
|
|
182
|
+
val reader = HandleRegistry.get<ReaderHandle>(handleId) ?: return true
|
|
183
|
+
return reader.isEOF
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --- Writer info getters ---
|
|
187
|
+
|
|
188
|
+
@JvmStatic
|
|
189
|
+
fun getWriterBytesWritten(handleId: Int): Long {
|
|
190
|
+
val writer = HandleRegistry.get<WriterHandle>(handleId) ?: return 0L
|
|
191
|
+
return writer.bytesWritten
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// --- Download progress getters (polled from C++ via JNI) ---
|
|
195
|
+
|
|
196
|
+
@JvmStatic
|
|
197
|
+
fun getDownloadBytesDownloaded(handleId: Int): Long {
|
|
198
|
+
return HandleRegistry.get<DownloaderHandle>(handleId)?.bytesDownloaded ?: 0L
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@JvmStatic
|
|
202
|
+
fun getDownloadTotalBytes(handleId: Int): Long {
|
|
203
|
+
return HandleRegistry.get<DownloaderHandle>(handleId)?.totalBytes ?: -1L
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
interface DownloadCallback {
|
|
207
|
+
fun onProgress(bytesDownloaded: Long, totalBytes: Long, progress: Double)
|
|
208
|
+
fun onSuccess()
|
|
209
|
+
fun onError(message: String)
|
|
210
|
+
}
|
|
211
|
+
}
|
package/cpp/AGENTS.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<!-- Parent: ../AGENTS.md -->
|
|
2
|
+
<!-- Generated: 2026-02-15 -->
|
|
3
|
+
|
|
4
|
+
# cpp/
|
|
5
|
+
|
|
6
|
+
JSI HostObject, platform bridge abstraction, and Android JNI loader.
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Implements the native streaming layer:
|
|
11
|
+
- **BufferedBlobStreamingHostObject**: JSI HostObject class exposing streaming methods (readNextChunk, write, flush, close, startDownload, etc.)
|
|
12
|
+
- **PlatformBridge**: Abstract interface that iOS and Android implement for actual streaming logic
|
|
13
|
+
- **OwnedMutableBuffer**: Zero-copy ArrayBuffer helper for JSI
|
|
14
|
+
- **jni_onload.cpp**: Android JNI entry point that installs the HostObject
|
|
15
|
+
- **AndroidPlatformBridge**: Android implementation of PlatformBridge using JNI callbacks to Kotlin
|
|
16
|
+
|
|
17
|
+
## Key Files
|
|
18
|
+
|
|
19
|
+
| File | Description |
|
|
20
|
+
|------|-------------|
|
|
21
|
+
| `BufferedBlobStreamingHostObject.h` | PlatformBridge interface (abstract), BufferedBlobStreamingHostObject class, OwnedMutableBuffer |
|
|
22
|
+
| `BufferedBlobStreamingHostObject.cpp` | JSI property handlers, Promise creation via callInvoker->invokeAsync, error handling |
|
|
23
|
+
| `AndroidPlatformBridge.h` | Android implementation header: inherits PlatformBridge, calls JNI static methods |
|
|
24
|
+
| `AndroidPlatformBridge.cpp` | JNI implementation: FindClass, GetStaticMethodID, uses std::thread::detach for async ops |
|
|
25
|
+
| `jni_onload.cpp` | Java_com_bufferedblob_BufferedBlobModule_nativeInstall: extracts JSI runtime, CallInvoker, creates bridge, calls install() |
|
|
26
|
+
| `CMakeLists.txt` | CMake build config: links ReactAndroid::jsi, react_nativemodule_core, turbomodulejsijni, fbjni |
|
|
27
|
+
|
|
28
|
+
## For AI Agents
|
|
29
|
+
|
|
30
|
+
### Working In This Directory
|
|
31
|
+
|
|
32
|
+
1. **Platform abstraction**: PlatformBridge is the abstraction layer. iOS and Android each implement it independently. Changes to PlatformBridge interface require updates to both platforms.
|
|
33
|
+
2. **JSI property handlers**: BufferedBlobStreamingHostObject::get() maps property names to methods. Methods are exposed as Properties or Functions in the HostObject.
|
|
34
|
+
3. **Promise pattern**: Use `react::createPromiseAsJSIValue(rt, asyncFn, callInvoker)` to return Promises to JavaScript. Callbacks invoke `resolve()` or `reject()` with JSI values.
|
|
35
|
+
4. **ArrayBuffer zero-copy**: OwnedMutableBuffer owns the data vector; returned as jsi::MutableBuffer to avoid copying.
|
|
36
|
+
5. **Android JNI**: nativeInstall() is called from Kotlin BufferedBlobModule.install(). Extracts JSI runtime pointer and CallInvoker, creates AndroidPlatformBridge, installs HostObject.
|
|
37
|
+
6. **Error handling**: PlatformBridge callbacks (onSuccess, onError) pass strings to the HostObject, which converts them to JSI errors.
|
|
38
|
+
|
|
39
|
+
### Testing Requirements
|
|
40
|
+
|
|
41
|
+
- **Compile without errors**: C++ code compiles with clang, including JSI headers
|
|
42
|
+
- **CMake linking**: Verify CMakeLists.txt links all required libraries (jsi, react_nativemodule_core, fbjni on Android)
|
|
43
|
+
- **Android linking**: Verify nativeInstall() JNI signature matches Kotlin method
|
|
44
|
+
- **Property access**: Verify JSI HostObject properties are accessible from JavaScript
|
|
45
|
+
- **Promise resolution**: Verify async callbacks resolve/reject Promises correctly
|
|
46
|
+
- **Zero-copy**: Verify ArrayBuffer does not copy data (requires profiling)
|
|
47
|
+
|
|
48
|
+
### Common Patterns
|
|
49
|
+
|
|
50
|
+
1. **Create Promise**: `react::createPromiseAsJSIValue(rt, [](jsi::Runtime& r, std::function<void(jsi::Value)> resolve, std::function<void(jsi::Runtime&, jsi::Error)> reject) { ... }, callInvoker_)`
|
|
51
|
+
2. **Get string property**: `rt.getPropertyAsFunction(rt, name).call(rt, args...)`
|
|
52
|
+
3. **Call PlatformBridge async**: `bridge_->readNextChunk(handleId, [=](std::vector<uint8_t> data) { ... }, ...)`
|
|
53
|
+
4. **Return Promises**: Store resolve/reject lambdas; call from platform bridge callbacks
|
|
54
|
+
5. **Convert to ArrayBuffer**: `rt.createArrayBuffer(std::make_shared<OwnedMutableBuffer>(data))`
|
|
55
|
+
|
|
56
|
+
## Dependencies
|
|
57
|
+
|
|
58
|
+
### Internal
|
|
59
|
+
- `BufferedBlobStreamingHostObject.h` — PlatformBridge interface
|
|
60
|
+
- Android: `AndroidPlatformBridge.h` — JNI to Kotlin static methods
|
|
61
|
+
|
|
62
|
+
### External
|
|
63
|
+
- **React Native** >= 0.76.0
|
|
64
|
+
- `<jsi/jsi.h>` — JSI runtime, HostObject, Value, MutableBuffer
|
|
65
|
+
- `<ReactCommon/CallInvoker.h>` — CallInvoker for Promise creation
|
|
66
|
+
- `<ReactCommon/TurboModuleManagerDelegate.h>` (Android) — Turbo Module integration
|
|
67
|
+
- **Android**
|
|
68
|
+
- `<fbjni/fbjni.h>` — Facebook JNI helpers (jni::make_local, etc.)
|
|
69
|
+
- `<android/log.h>` (optional) — Logging
|
|
70
|
+
|
|
71
|
+
<!-- MANUAL: Document JSI best practices, Promise handling guidelines, Android/iOS bridge API versioning -->
|