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.
Files changed (93) hide show
  1. package/android/AGENTS.md +74 -0
  2. package/android/build.gradle +34 -0
  3. package/android/src/main/AndroidManifest.xml +4 -0
  4. package/android/src/main/java/com/bufferedblob/BufferedBlobModule.kt +274 -0
  5. package/android/src/main/java/com/bufferedblob/BufferedBlobPackage.kt +32 -0
  6. package/android/src/main/java/com/bufferedblob/HandleRegistry.kt +84 -0
  7. package/android/src/main/java/com/bufferedblob/StreamingBridge.kt +211 -0
  8. package/cpp/AGENTS.md +71 -0
  9. package/cpp/AndroidPlatformBridge.cpp +437 -0
  10. package/cpp/AndroidPlatformBridge.h +79 -0
  11. package/cpp/BufferedBlobStreamingHostObject.cpp +344 -0
  12. package/cpp/BufferedBlobStreamingHostObject.h +118 -0
  13. package/cpp/CMakeLists.txt +49 -0
  14. package/cpp/jni_onload.cpp +32 -0
  15. package/ios/AGENTS.md +76 -0
  16. package/ios/BufferedBlobModule.h +44 -0
  17. package/ios/BufferedBlobModule.m +433 -0
  18. package/ios/BufferedBlobModule.mm +192 -0
  19. package/ios/BufferedBlobStreamingBridge.h +21 -0
  20. package/ios/BufferedBlobStreamingBridge.mm +442 -0
  21. package/ios/HandleRegistry.h +29 -0
  22. package/ios/HandleRegistry.m +67 -0
  23. package/ios/HandleTypes.h +83 -0
  24. package/ios/HandleTypes.m +333 -0
  25. package/lib/module/AGENTS.md +70 -0
  26. package/lib/module/NativeBufferedBlob.js +5 -0
  27. package/lib/module/NativeBufferedBlob.js.map +1 -0
  28. package/lib/module/api/AGENTS.md +62 -0
  29. package/lib/module/api/download.js +40 -0
  30. package/lib/module/api/download.js.map +1 -0
  31. package/lib/module/api/fileOps.js +70 -0
  32. package/lib/module/api/fileOps.js.map +1 -0
  33. package/lib/module/api/hash.js +13 -0
  34. package/lib/module/api/hash.js.map +1 -0
  35. package/lib/module/api/readFile.js +23 -0
  36. package/lib/module/api/readFile.js.map +1 -0
  37. package/lib/module/api/writeFile.js +18 -0
  38. package/lib/module/api/writeFile.js.map +1 -0
  39. package/lib/module/errors.js +45 -0
  40. package/lib/module/errors.js.map +1 -0
  41. package/lib/module/index.js +25 -0
  42. package/lib/module/index.js.map +1 -0
  43. package/lib/module/module.js +19 -0
  44. package/lib/module/module.js.map +1 -0
  45. package/lib/module/package.json +1 -0
  46. package/lib/module/paths.js +32 -0
  47. package/lib/module/paths.js.map +1 -0
  48. package/lib/module/types.js +15 -0
  49. package/lib/module/types.js.map +1 -0
  50. package/lib/module/wrappers.js +107 -0
  51. package/lib/module/wrappers.js.map +1 -0
  52. package/lib/typescript/package.json +1 -0
  53. package/lib/typescript/src/NativeBufferedBlob.d.ts +37 -0
  54. package/lib/typescript/src/NativeBufferedBlob.d.ts.map +1 -0
  55. package/lib/typescript/src/api/download.d.ts +13 -0
  56. package/lib/typescript/src/api/download.d.ts.map +1 -0
  57. package/lib/typescript/src/api/fileOps.d.ts +9 -0
  58. package/lib/typescript/src/api/fileOps.d.ts.map +1 -0
  59. package/lib/typescript/src/api/hash.d.ts +3 -0
  60. package/lib/typescript/src/api/hash.d.ts.map +1 -0
  61. package/lib/typescript/src/api/readFile.d.ts +3 -0
  62. package/lib/typescript/src/api/readFile.d.ts.map +1 -0
  63. package/lib/typescript/src/api/writeFile.d.ts +3 -0
  64. package/lib/typescript/src/api/writeFile.d.ts.map +1 -0
  65. package/lib/typescript/src/errors.d.ts +25 -0
  66. package/lib/typescript/src/errors.d.ts.map +1 -0
  67. package/lib/typescript/src/index.d.ts +11 -0
  68. package/lib/typescript/src/index.d.ts.map +1 -0
  69. package/lib/typescript/src/module.d.ts +23 -0
  70. package/lib/typescript/src/module.d.ts.map +1 -0
  71. package/lib/typescript/src/paths.d.ts +11 -0
  72. package/lib/typescript/src/paths.d.ts.map +1 -0
  73. package/lib/typescript/src/types.d.ts +37 -0
  74. package/lib/typescript/src/types.d.ts.map +1 -0
  75. package/lib/typescript/src/wrappers.d.ts +14 -0
  76. package/lib/typescript/src/wrappers.d.ts.map +1 -0
  77. package/package.json +114 -0
  78. package/react-native-buffered-blob.podspec +37 -0
  79. package/react-native.config.js +10 -0
  80. package/src/AGENTS.md +70 -0
  81. package/src/NativeBufferedBlob.ts +54 -0
  82. package/src/api/AGENTS.md +62 -0
  83. package/src/api/download.ts +46 -0
  84. package/src/api/fileOps.ts +83 -0
  85. package/src/api/hash.ts +14 -0
  86. package/src/api/readFile.ts +37 -0
  87. package/src/api/writeFile.ts +24 -0
  88. package/src/errors.ts +50 -0
  89. package/src/index.ts +28 -0
  90. package/src/module.ts +48 -0
  91. package/src/paths.ts +35 -0
  92. package/src/types.ts +42 -0
  93. 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 -->