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,433 @@
1
+ #import "BufferedBlobModule.h"
2
+ #import "HandleRegistry.h"
3
+ #import "HandleTypes.h"
4
+ #import <CommonCrypto/CommonDigest.h>
5
+
6
+ @implementation BufferedBlobModule
7
+
8
+ + (NSString *)moduleName {
9
+ return @"BufferedBlob";
10
+ }
11
+
12
+ + (BOOL)requiresMainQueueSetup {
13
+ return NO;
14
+ }
15
+
16
+ - (NSDictionary *)constantsToExport {
17
+ NSArray<NSString *> *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
18
+ NSString *docDir = docPaths.firstObject ?: @"";
19
+
20
+ NSArray<NSString *> *cachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
21
+ NSString *cacheDir = cachePaths.firstObject ?: @"";
22
+
23
+ NSString *tempDir = NSTemporaryDirectory();
24
+
25
+ NSString *downloadDir = docDir;
26
+ if (@available(iOS 16.0, *)) {
27
+ NSArray<NSString *> *dlPaths = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, YES);
28
+ if (dlPaths.firstObject) {
29
+ downloadDir = dlPaths.firstObject;
30
+ }
31
+ }
32
+
33
+ return @{
34
+ @"documentDir": docDir,
35
+ @"cacheDir": cacheDir,
36
+ @"tempDir": tempDir,
37
+ @"downloadDir": downloadDir,
38
+ };
39
+ }
40
+
41
+ // ──────────────────────────────────────────────────────────────────────
42
+ #pragma mark - Handle Factories
43
+ // ──────────────────────────────────────────────────────────────────────
44
+
45
+ static const NSInteger kMinBufferSize = 4096;
46
+ static const NSInteger kMaxBufferSize = 4194304; // 4MB
47
+
48
+ - (NSNumber *)openRead:(NSString *)path bufferSize:(double)bufferSize {
49
+ NSInteger size = (NSInteger)bufferSize;
50
+ if (size < kMinBufferSize || size > kMaxBufferSize) {
51
+ NSLog(@"[BufferedBlob] openRead: buffer size out of range (%ld). Must be %ld-%ld.",
52
+ (long)size, (long)kMinBufferSize, (long)kMaxBufferSize);
53
+ return @(-1);
54
+ }
55
+
56
+ NSError *error = nil;
57
+ ReaderHandleIOS *reader = [[ReaderHandleIOS alloc] initWithPath:path
58
+ bufferSize:size
59
+ error:&error];
60
+ if (!reader) {
61
+ return @(-1);
62
+ }
63
+
64
+ NSInteger handleId = [[HandleRegistry shared] registerObject:reader];
65
+ return @(handleId);
66
+ }
67
+
68
+ - (NSNumber *)openWrite:(NSString *)path append:(BOOL)append {
69
+ NSFileManager *fm = [NSFileManager defaultManager];
70
+ NSString *parentDir = [path stringByDeletingLastPathComponent];
71
+ if (![fm fileExistsAtPath:parentDir]) {
72
+ [fm createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:nil];
73
+ }
74
+
75
+ NSError *error = nil;
76
+ WriterHandleIOS *writer = [[WriterHandleIOS alloc] initWithPath:path
77
+ append:append
78
+ error:&error];
79
+ if (!writer) {
80
+ return @(-1);
81
+ }
82
+
83
+ NSInteger handleId = [[HandleRegistry shared] registerObject:writer];
84
+ return @(handleId);
85
+ }
86
+
87
+ - (NSNumber *)createDownload:(NSString *)url destPath:(NSString *)destPath headers:(NSDictionary *)headers {
88
+ NSFileManager *fm = [NSFileManager defaultManager];
89
+ NSString *parentDir = [destPath stringByDeletingLastPathComponent];
90
+ if (![fm fileExistsAtPath:parentDir]) {
91
+ [fm createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:nil];
92
+ }
93
+
94
+ NSMutableDictionary<NSString *, NSString *> *headerMap = [NSMutableDictionary new];
95
+ [headers enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
96
+ if ([key isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) {
97
+ headerMap[key] = value;
98
+ }
99
+ }];
100
+
101
+ DownloaderHandleIOS *handle = [[DownloaderHandleIOS alloc] initWithURL:url
102
+ destPath:destPath
103
+ headers:headerMap];
104
+ NSInteger handleId = [[HandleRegistry shared] registerObject:handle];
105
+ return @(handleId);
106
+ }
107
+
108
+ - (void)closeHandle:(double)handleId {
109
+ [[HandleRegistry shared] removeObjectForId:(NSInteger)handleId];
110
+ }
111
+
112
+ // ──────────────────────────────────────────────────────────────────────
113
+ #pragma mark - FS Operations
114
+ // ──────────────────────────────────────────────────────────────────────
115
+
116
+ - (void)exists:(NSString *)path
117
+ resolve:(RCTPromiseResolveBlock)resolve
118
+ reject:(RCTPromiseRejectBlock)reject {
119
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
120
+ BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path];
121
+ resolve(@(exists));
122
+ });
123
+ }
124
+
125
+ - (void)stat:(NSString *)path
126
+ resolve:(RCTPromiseResolveBlock)resolve
127
+ reject:(RCTPromiseRejectBlock)reject {
128
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
129
+ NSFileManager *fm = [NSFileManager defaultManager];
130
+ NSError *error = nil;
131
+ NSDictionary *attrs = [fm attributesOfItemAtPath:path error:&error];
132
+ if (!attrs) {
133
+ reject(@"ERR_FS",
134
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] File does not exist: %@", path],
135
+ error);
136
+ return;
137
+ }
138
+
139
+ int64_t size = [attrs[NSFileSize] longLongValue];
140
+ NSDate *modDate = attrs[NSFileModificationDate] ?: [NSDate date];
141
+ NSString *fileType = attrs[NSFileType];
142
+
143
+ NSString *type;
144
+ if ([fileType isEqualToString:NSFileTypeDirectory]) {
145
+ type = @"directory";
146
+ } else if ([fileType isEqualToString:NSFileTypeRegular]) {
147
+ type = @"file";
148
+ } else {
149
+ type = @"unknown";
150
+ }
151
+
152
+ NSString *name = [path lastPathComponent];
153
+ resolve(@{
154
+ @"path": path,
155
+ @"name": name,
156
+ @"size": @(size),
157
+ @"type": type,
158
+ @"lastModified": @(modDate.timeIntervalSince1970 * 1000),
159
+ });
160
+ });
161
+ }
162
+
163
+ - (void)unlink:(NSString *)path
164
+ resolve:(RCTPromiseResolveBlock)resolve
165
+ reject:(RCTPromiseRejectBlock)reject {
166
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
167
+ NSFileManager *fm = [NSFileManager defaultManager];
168
+ NSError *error = nil;
169
+ if (![fm removeItemAtPath:path error:&error]) {
170
+ // Map "file not found" NSCocoa errors to the same ERR_FS format
171
+ if (error.code == NSFileNoSuchFileError || error.code == NSFileReadNoSuchFileError) {
172
+ reject(@"ERR_FS",
173
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] File does not exist: %@", path],
174
+ error);
175
+ } else {
176
+ reject(@"ERR_FS", error.localizedDescription, error);
177
+ }
178
+ return;
179
+ }
180
+ resolve(nil);
181
+ });
182
+ }
183
+
184
+ - (void)mkdir:(NSString *)path
185
+ resolve:(RCTPromiseResolveBlock)resolve
186
+ reject:(RCTPromiseRejectBlock)reject {
187
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
188
+ NSFileManager *fm = [NSFileManager defaultManager];
189
+ BOOL isDir = NO;
190
+ if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
191
+ if (isDir) {
192
+ resolve(nil);
193
+ return;
194
+ }
195
+ reject(@"ERR_FS",
196
+ [NSString stringWithFormat:@"[INVALID_ARGUMENT] Path exists and is not a directory: %@", path],
197
+ nil);
198
+ return;
199
+ }
200
+
201
+ NSError *error = nil;
202
+ if (![fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error]) {
203
+ reject(@"ERR_FS", error.localizedDescription, error);
204
+ return;
205
+ }
206
+ resolve(nil);
207
+ });
208
+ }
209
+
210
+ - (void)ls:(NSString *)path
211
+ resolve:(RCTPromiseResolveBlock)resolve
212
+ reject:(RCTPromiseRejectBlock)reject {
213
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
214
+ NSFileManager *fm = [NSFileManager defaultManager];
215
+ BOOL isDir = NO;
216
+ if (![fm fileExistsAtPath:path isDirectory:&isDir]) {
217
+ reject(@"ERR_FS",
218
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] Directory does not exist: %@", path],
219
+ nil);
220
+ return;
221
+ }
222
+ if (!isDir) {
223
+ reject(@"ERR_FS",
224
+ [NSString stringWithFormat:@"[NOT_A_DIRECTORY] Path is not a directory: %@", path],
225
+ nil);
226
+ return;
227
+ }
228
+
229
+ NSError *error = nil;
230
+ NSArray<NSString *> *contents = [fm contentsOfDirectoryAtPath:path error:&error];
231
+ if (!contents) {
232
+ reject(@"ERR_FS", error.localizedDescription, error);
233
+ return;
234
+ }
235
+
236
+ NSMutableArray *results = [NSMutableArray new];
237
+ for (NSString *item in contents) {
238
+ NSString *fullPath = [path stringByAppendingPathComponent:item];
239
+ NSDictionary *attrs = [fm attributesOfItemAtPath:fullPath error:nil];
240
+ if (!attrs) continue;
241
+
242
+ int64_t size = [attrs[NSFileSize] longLongValue];
243
+ NSDate *modDate = attrs[NSFileModificationDate] ?: [NSDate date];
244
+ NSString *fileType = attrs[NSFileType];
245
+
246
+ NSString *type;
247
+ if ([fileType isEqualToString:NSFileTypeDirectory]) {
248
+ type = @"directory";
249
+ } else if ([fileType isEqualToString:NSFileTypeRegular]) {
250
+ type = @"file";
251
+ } else {
252
+ type = @"unknown";
253
+ }
254
+
255
+ [results addObject:@{
256
+ @"path": fullPath,
257
+ @"name": item,
258
+ @"size": @(size),
259
+ @"type": type,
260
+ @"lastModified": @(modDate.timeIntervalSince1970 * 1000),
261
+ }];
262
+ }
263
+ resolve(results);
264
+ });
265
+ }
266
+
267
+ - (void)cp:(NSString *)srcPath
268
+ destPath:(NSString *)destPath
269
+ resolve:(RCTPromiseResolveBlock)resolve
270
+ reject:(RCTPromiseRejectBlock)reject {
271
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
272
+ NSFileManager *fm = [NSFileManager defaultManager];
273
+ if (![fm fileExistsAtPath:srcPath]) {
274
+ reject(@"ERR_FS",
275
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] Source does not exist: %@", srcPath],
276
+ nil);
277
+ return;
278
+ }
279
+
280
+ NSString *parentDir = [destPath stringByDeletingLastPathComponent];
281
+ if (![fm fileExistsAtPath:parentDir]) {
282
+ [fm createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:nil];
283
+ }
284
+
285
+ if ([fm fileExistsAtPath:destPath]) {
286
+ [fm removeItemAtPath:destPath error:nil];
287
+ }
288
+
289
+ NSError *error = nil;
290
+ if (![fm copyItemAtPath:srcPath toPath:destPath error:&error]) {
291
+ reject(@"ERR_FS", error.localizedDescription, error);
292
+ return;
293
+ }
294
+ resolve(nil);
295
+ });
296
+ }
297
+
298
+ - (void)mv:(NSString *)srcPath
299
+ destPath:(NSString *)destPath
300
+ resolve:(RCTPromiseResolveBlock)resolve
301
+ reject:(RCTPromiseRejectBlock)reject {
302
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
303
+ NSFileManager *fm = [NSFileManager defaultManager];
304
+ if (![fm fileExistsAtPath:srcPath]) {
305
+ reject(@"ERR_FS",
306
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] Source does not exist: %@", srcPath],
307
+ nil);
308
+ return;
309
+ }
310
+
311
+ NSString *parentDir = [destPath stringByDeletingLastPathComponent];
312
+ if (![fm fileExistsAtPath:parentDir]) {
313
+ [fm createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:nil];
314
+ }
315
+
316
+ if ([fm fileExistsAtPath:destPath]) {
317
+ [fm removeItemAtPath:destPath error:nil];
318
+ }
319
+
320
+ NSError *error = nil;
321
+ if (![fm moveItemAtPath:srcPath toPath:destPath error:&error]) {
322
+ reject(@"ERR_FS", error.localizedDescription, error);
323
+ return;
324
+ }
325
+ resolve(nil);
326
+ });
327
+ }
328
+
329
+ - (void)hashFile:(NSString *)path
330
+ algorithm:(NSString *)algorithm
331
+ resolve:(RCTPromiseResolveBlock)resolve
332
+ reject:(RCTPromiseRejectBlock)reject {
333
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
334
+ NSFileManager *fm = [NSFileManager defaultManager];
335
+ BOOL isDir = NO;
336
+ if ([fm fileExistsAtPath:path isDirectory:&isDir] && isDir) {
337
+ reject(@"ERR_FS",
338
+ [NSString stringWithFormat:@"[INVALID_ARGUMENT] Cannot hash a directory: %@", path],
339
+ nil);
340
+ return;
341
+ }
342
+
343
+ NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:path];
344
+ if (!inputStream) {
345
+ reject(@"ERR_FS",
346
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] File does not exist: %@", path],
347
+ nil);
348
+ return;
349
+ }
350
+ [inputStream open];
351
+ if (inputStream.streamStatus == NSStreamStatusError) {
352
+ [inputStream close];
353
+ reject(@"ERR_FS",
354
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] File does not exist: %@", path],
355
+ nil);
356
+ return;
357
+ }
358
+
359
+ const NSUInteger kBufferSize = 8192;
360
+ uint8_t *buffer = malloc(kBufferSize);
361
+ if (!buffer) {
362
+ [inputStream close];
363
+ reject(@"ERR_FS", @"[READ_ERROR] Failed to allocate buffer", nil);
364
+ return;
365
+ }
366
+
367
+ if ([algorithm isEqualToString:@"sha256"]) {
368
+ CC_SHA256_CTX ctx;
369
+ CC_SHA256_Init(&ctx);
370
+
371
+ while ([inputStream hasBytesAvailable]) {
372
+ NSInteger bytesRead = [inputStream read:buffer maxLength:kBufferSize];
373
+ if (bytesRead < 0) {
374
+ free(buffer);
375
+ [inputStream close];
376
+ reject(@"ERR_FS", @"[READ_ERROR] Error reading file", nil);
377
+ return;
378
+ }
379
+ if (bytesRead == 0) break;
380
+ CC_SHA256_Update(&ctx, buffer, (CC_LONG)bytesRead);
381
+ }
382
+
383
+ unsigned char digest[CC_SHA256_DIGEST_LENGTH];
384
+ CC_SHA256_Final(digest, &ctx);
385
+
386
+ free(buffer);
387
+ [inputStream close];
388
+
389
+ NSMutableString *hex = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
390
+ for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
391
+ [hex appendFormat:@"%02x", digest[i]];
392
+ }
393
+ resolve(hex);
394
+
395
+ } else if ([algorithm isEqualToString:@"md5"]) {
396
+ CC_MD5_CTX ctx;
397
+ CC_MD5_Init(&ctx);
398
+
399
+ while ([inputStream hasBytesAvailable]) {
400
+ NSInteger bytesRead = [inputStream read:buffer maxLength:kBufferSize];
401
+ if (bytesRead < 0) {
402
+ free(buffer);
403
+ [inputStream close];
404
+ reject(@"ERR_FS", @"[READ_ERROR] Error reading file", nil);
405
+ return;
406
+ }
407
+ if (bytesRead == 0) break;
408
+ CC_MD5_Update(&ctx, buffer, (CC_LONG)bytesRead);
409
+ }
410
+
411
+ unsigned char digest[CC_MD5_DIGEST_LENGTH];
412
+ CC_MD5_Final(digest, &ctx);
413
+
414
+ free(buffer);
415
+ [inputStream close];
416
+
417
+ NSMutableString *hex = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
418
+ for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
419
+ [hex appendFormat:@"%02x", digest[i]];
420
+ }
421
+ resolve(hex);
422
+
423
+ } else {
424
+ free(buffer);
425
+ [inputStream close];
426
+ reject(@"ERR_FS",
427
+ [NSString stringWithFormat:@"[INVALID_ARGUMENT] Unknown algorithm: %@", algorithm],
428
+ nil);
429
+ }
430
+ });
431
+ }
432
+
433
+ @end
@@ -0,0 +1,192 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTBridge+Private.h>
3
+ #import <ReactCommon/RCTTurboModule.h>
4
+ #import <ReactCommon/CallInvoker.h>
5
+ #import <jsi/jsi.h>
6
+ #import "BufferedBlobStreamingBridge.h"
7
+ #import "BufferedBlobModule.h"
8
+ #import "HandleRegistry.h"
9
+
10
+ #ifdef RCT_NEW_ARCH_ENABLED
11
+ #import <BufferedBlobSpec/BufferedBlobSpec.h>
12
+ #endif
13
+
14
+ @interface BufferedBlobModuleBridge : NSObject <
15
+ #ifdef RCT_NEW_ARCH_ENABLED
16
+ NativeBufferedBlobSpec
17
+ #else
18
+ RCTBridgeModule
19
+ #endif
20
+ >
21
+ @property (nonatomic, strong) BufferedBlobModule *module;
22
+ - (void)setRuntimePointer:(facebook::jsi::Runtime *)runtime;
23
+ @end
24
+
25
+ #ifdef RCT_NEW_ARCH_ENABLED
26
+ namespace facebook::react {
27
+
28
+ /**
29
+ * Subclass the codegen JSI wrapper to intercept install() and capture
30
+ * the jsi::Runtime reference, which is only available inside JSI host functions.
31
+ */
32
+ class BufferedBlobSpecJSI : public NativeBufferedBlobSpecJSI {
33
+ public:
34
+ BufferedBlobSpecJSI(const ObjCTurboModule::InitParams &params)
35
+ : NativeBufferedBlobSpecJSI(params) {
36
+ // Override install to capture runtime before delegating to ObjC
37
+ methodMap_["install"] = MethodMetadata {0,
38
+ [](jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t count) -> jsi::Value {
39
+ auto &self = static_cast<BufferedBlobSpecJSI &>(turboModule);
40
+ auto *module = static_cast<BufferedBlobModuleBridge *>(self.instance_);
41
+ [module setRuntimePointer:&rt];
42
+ return static_cast<ObjCTurboModule &>(turboModule).invokeObjCMethod(
43
+ rt, BooleanKind, "install", @selector(install), args, count);
44
+ }
45
+ };
46
+ }
47
+ };
48
+
49
+ } // namespace facebook::react
50
+ #endif
51
+
52
+ @implementation BufferedBlobModuleBridge {
53
+ facebook::jsi::Runtime *_runtime;
54
+ std::shared_ptr<facebook::react::CallInvoker> _callInvoker;
55
+ }
56
+
57
+ RCT_EXPORT_MODULE(BufferedBlob)
58
+
59
+ + (BOOL)requiresMainQueueSetup {
60
+ return NO;
61
+ }
62
+
63
+ - (instancetype)init {
64
+ self = [super init];
65
+ if (self) {
66
+ _module = [[BufferedBlobModule alloc] init];
67
+ _runtime = nullptr;
68
+ }
69
+ return self;
70
+ }
71
+
72
+ - (void)setRuntimePointer:(facebook::jsi::Runtime *)runtime {
73
+ _runtime = runtime;
74
+ }
75
+
76
+ - (NSDictionary *)constantsToExport {
77
+ return [_module constantsToExport];
78
+ }
79
+
80
+ - (NSDictionary *)getConstants {
81
+ return [self constantsToExport];
82
+ }
83
+
84
+ // --- install(): Wire JSI HostObject ---
85
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
86
+ @try {
87
+ // New arch path: runtime and callInvoker are captured by
88
+ // getTurboModule: (callInvoker) and the JSI install() override (runtime).
89
+ if (_runtime && _callInvoker) {
90
+ installBufferedBlobStreaming(*_runtime, _callInvoker);
91
+ return @(YES);
92
+ }
93
+
94
+ // Old arch / bridge fallback
95
+ RCTBridge *bridge = self.bridge;
96
+ if (!bridge) {
97
+ bridge = [RCTBridge currentBridge];
98
+ }
99
+ if (!bridge) {
100
+ return @(NO);
101
+ }
102
+
103
+ RCTCxxBridge *target = (RCTCxxBridge *)(bridge.batchedBridge ?: bridge);
104
+
105
+ void *runtimePtr = target.runtime;
106
+ if (!runtimePtr) {
107
+ return @(NO);
108
+ }
109
+ facebook::jsi::Runtime *runtime = (facebook::jsi::Runtime *)runtimePtr;
110
+
111
+ auto callInvoker = [bridge jsCallInvoker];
112
+ if (!callInvoker) {
113
+ return @(NO);
114
+ }
115
+
116
+ _runtime = runtime;
117
+ _callInvoker = callInvoker;
118
+
119
+ installBufferedBlobStreaming(*_runtime, _callInvoker);
120
+ return @(YES);
121
+ } @catch (NSException *exception) {
122
+ NSLog(@"[BufferedBlob] install() failed: %@", exception.reason);
123
+ return @(NO);
124
+ }
125
+ }
126
+
127
+ #ifdef RCT_NEW_ARCH_ENABLED
128
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
129
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
130
+ _callInvoker = params.jsInvoker;
131
+ return std::make_shared<facebook::react::BufferedBlobSpecJSI>(params);
132
+ }
133
+ #endif
134
+
135
+ // --- Handle Factories ---
136
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(openRead:(NSString *)path bufferSize:(double)bufferSize) {
137
+ return [_module openRead:path bufferSize:bufferSize];
138
+ }
139
+
140
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(openWrite:(NSString *)path append:(BOOL)append) {
141
+ return [_module openWrite:path append:append];
142
+ }
143
+
144
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(createDownload:(NSString *)url destPath:(NSString *)destPath headers:(NSDictionary *)headers) {
145
+ return [_module createDownload:url destPath:destPath headers:headers];
146
+ }
147
+
148
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(closeHandle:(double)handleId) {
149
+ [_module closeHandle:handleId];
150
+ return nil;
151
+ }
152
+
153
+ // --- FS Operations ---
154
+ RCT_EXPORT_METHOD(exists:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
155
+ [_module exists:path resolve:resolve reject:reject];
156
+ }
157
+
158
+ RCT_EXPORT_METHOD(stat:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
159
+ [_module stat:path resolve:resolve reject:reject];
160
+ }
161
+
162
+ RCT_EXPORT_METHOD(unlink:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
163
+ [_module unlink:path resolve:resolve reject:reject];
164
+ }
165
+
166
+ RCT_EXPORT_METHOD(mkdir:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
167
+ [_module mkdir:path resolve:resolve reject:reject];
168
+ }
169
+
170
+ RCT_EXPORT_METHOD(ls:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
171
+ [_module ls:path resolve:resolve reject:reject];
172
+ }
173
+
174
+ RCT_EXPORT_METHOD(cp:(NSString *)srcPath destPath:(NSString *)destPath resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
175
+ [_module cp:srcPath destPath:destPath resolve:resolve reject:reject];
176
+ }
177
+
178
+ RCT_EXPORT_METHOD(mv:(NSString *)srcPath destPath:(NSString *)destPath resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
179
+ [_module mv:srcPath destPath:destPath resolve:resolve reject:reject];
180
+ }
181
+
182
+ RCT_EXPORT_METHOD(hashFile:(NSString *)path algorithm:(NSString *)algorithm resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
183
+ [_module hashFile:path algorithm:algorithm resolve:resolve reject:reject];
184
+ }
185
+
186
+ - (void)invalidate {
187
+ [[HandleRegistry shared] clear];
188
+ _runtime = nullptr;
189
+ _callInvoker = nullptr;
190
+ }
191
+
192
+ @end
@@ -0,0 +1,21 @@
1
+ #pragma once
2
+
3
+ #import <React/RCTBridgeModule.h>
4
+ #import <ReactCommon/CallInvoker.h>
5
+ #import <jsi/jsi.h>
6
+
7
+ #ifdef __cplusplus
8
+ extern "C" {
9
+ #endif
10
+
11
+ /**
12
+ * Install the BufferedBlobStreaming JSI HostObject on the given runtime.
13
+ * Called from BufferedBlobModule.mm during install().
14
+ */
15
+ void installBufferedBlobStreaming(
16
+ facebook::jsi::Runtime& runtime,
17
+ std::shared_ptr<facebook::react::CallInvoker> callInvoker);
18
+
19
+ #ifdef __cplusplus
20
+ }
21
+ #endif