rn-file-toolkit 1.0.7 → 1.0.9
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/FileToolkit.podspec +1 -0
- package/README.md +365 -24
- package/android/src/main/java/com/filetoolkit/FileToolkitModule.kt +273 -0
- package/ios/FileToolkit.mm +220 -0
- package/lib/commonjs/NativeFileToolkit.js.map +1 -1
- package/lib/commonjs/index.js +137 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeFileToolkit.js.map +1 -1
- package/lib/module/index.js +129 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/NativeFileToolkit.d.ts +6 -0
- package/lib/typescript/commonjs/src/NativeFileToolkit.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +67 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/NativeFileToolkit.d.ts +6 -0
- package/lib/typescript/module/src/NativeFileToolkit.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +67 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeFileToolkit.ts +6 -0
- package/src/index.tsx +183 -2
|
@@ -1327,6 +1327,279 @@ class FileToolkitModule(private val reactContext: ReactApplicationContext) :
|
|
|
1327
1327
|
zos.closeEntry()
|
|
1328
1328
|
}
|
|
1329
1329
|
|
|
1330
|
+
// ─── df (disk space) ──────────────────────────────────────────────────────
|
|
1331
|
+
|
|
1332
|
+
override fun df(promise: Promise) {
|
|
1333
|
+
try {
|
|
1334
|
+
val stat = android.os.StatFs(Environment.getDataDirectory().path)
|
|
1335
|
+
val freeBytes = stat.availableBytes
|
|
1336
|
+
val totalBytes = stat.totalBytes
|
|
1337
|
+
|
|
1338
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1339
|
+
putBoolean("success", true)
|
|
1340
|
+
putDouble("freeBytes", freeBytes.toDouble())
|
|
1341
|
+
putDouble("totalBytes", totalBytes.toDouble())
|
|
1342
|
+
})
|
|
1343
|
+
} catch (e: Throwable) {
|
|
1344
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1345
|
+
putBoolean("success", false)
|
|
1346
|
+
putString("error", e.message ?: "DF_ERROR")
|
|
1347
|
+
})
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// ─── appendFile ───────────────────────────────────────────────────────────
|
|
1352
|
+
|
|
1353
|
+
override fun appendFile(filePath: String, data: String, encoding: String, promise: Promise) {
|
|
1354
|
+
try {
|
|
1355
|
+
val file = File(filePath)
|
|
1356
|
+
if (!file.exists()) {
|
|
1357
|
+
// Create parent dirs and the file if it doesn't exist
|
|
1358
|
+
file.parentFile?.mkdirs()
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
if (encoding.equals("base64", ignoreCase = true)) {
|
|
1362
|
+
val bytes = android.util.Base64.decode(data, android.util.Base64.DEFAULT)
|
|
1363
|
+
FileOutputStream(file, true).use { fos ->
|
|
1364
|
+
fos.write(bytes)
|
|
1365
|
+
}
|
|
1366
|
+
} else {
|
|
1367
|
+
FileOutputStream(file, true).use { fos ->
|
|
1368
|
+
fos.write(data.toByteArray(Charsets.UTF_8))
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1373
|
+
putBoolean("success", true)
|
|
1374
|
+
})
|
|
1375
|
+
} catch (e: Throwable) {
|
|
1376
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1377
|
+
putBoolean("success", false)
|
|
1378
|
+
putString("error", e.message ?: "APPEND_FILE_ERROR")
|
|
1379
|
+
})
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// ─── hash ─────────────────────────────────────────────────────────────────
|
|
1384
|
+
|
|
1385
|
+
override fun hash(filePath: String, algorithm: String, promise: Promise) {
|
|
1386
|
+
try {
|
|
1387
|
+
val file = File(filePath)
|
|
1388
|
+
if (!file.exists() || file.isDirectory) {
|
|
1389
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1390
|
+
putBoolean("success", false)
|
|
1391
|
+
putString("error", "File not found: $filePath")
|
|
1392
|
+
})
|
|
1393
|
+
return
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
val hashValue = calculateChecksum(file, algorithm)
|
|
1397
|
+
|
|
1398
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1399
|
+
putBoolean("success", true)
|
|
1400
|
+
putString("hash", hashValue)
|
|
1401
|
+
})
|
|
1402
|
+
} catch (e: Throwable) {
|
|
1403
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1404
|
+
putBoolean("success", false)
|
|
1405
|
+
putString("error", e.message ?: "HASH_ERROR")
|
|
1406
|
+
})
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// ─── getCookies ───────────────────────────────────────────────────────────
|
|
1411
|
+
|
|
1412
|
+
override fun getCookies(domain: String, promise: Promise) {
|
|
1413
|
+
try {
|
|
1414
|
+
val cookieManager = android.webkit.CookieManager.getInstance()
|
|
1415
|
+
val cookieString = cookieManager.getCookie(domain)
|
|
1416
|
+
|
|
1417
|
+
val cookiesArray = Arguments.createArray()
|
|
1418
|
+
|
|
1419
|
+
if (!cookieString.isNullOrBlank()) {
|
|
1420
|
+
// CookieManager returns cookies as "name1=value1; name2=value2; ..."
|
|
1421
|
+
cookieString.split(";").forEach { pair ->
|
|
1422
|
+
val trimmed = pair.trim()
|
|
1423
|
+
val eqIndex = trimmed.indexOf("=")
|
|
1424
|
+
if (eqIndex > 0) {
|
|
1425
|
+
val name = trimmed.substring(0, eqIndex)
|
|
1426
|
+
val value = trimmed.substring(eqIndex + 1)
|
|
1427
|
+
cookiesArray.pushMap(Arguments.createMap().apply {
|
|
1428
|
+
putString("name", name)
|
|
1429
|
+
putString("value", value)
|
|
1430
|
+
putString("domain", domain)
|
|
1431
|
+
putString("path", "/")
|
|
1432
|
+
})
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1438
|
+
putBoolean("success", true)
|
|
1439
|
+
putArray("cookies", cookiesArray)
|
|
1440
|
+
})
|
|
1441
|
+
} catch (e: Throwable) {
|
|
1442
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1443
|
+
putBoolean("success", false)
|
|
1444
|
+
putString("error", e.message ?: "GET_COOKIES_ERROR")
|
|
1445
|
+
})
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// ─── clearCookies ─────────────────────────────────────────────────────────
|
|
1450
|
+
|
|
1451
|
+
override fun clearCookies(domain: String, promise: Promise) {
|
|
1452
|
+
try {
|
|
1453
|
+
val cookieManager = android.webkit.CookieManager.getInstance()
|
|
1454
|
+
|
|
1455
|
+
if (domain.isBlank()) {
|
|
1456
|
+
// Clear ALL cookies
|
|
1457
|
+
cookieManager.removeAllCookies { success ->
|
|
1458
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1459
|
+
putBoolean("success", success)
|
|
1460
|
+
})
|
|
1461
|
+
}
|
|
1462
|
+
} else {
|
|
1463
|
+
// Android CookieManager does not support per-domain clearing directly.
|
|
1464
|
+
// We read cookies for the domain and set each to expired.
|
|
1465
|
+
val cookieString = cookieManager.getCookie(domain)
|
|
1466
|
+
if (!cookieString.isNullOrBlank()) {
|
|
1467
|
+
cookieString.split(";").forEach { pair ->
|
|
1468
|
+
val trimmed = pair.trim()
|
|
1469
|
+
val eqIndex = trimmed.indexOf("=")
|
|
1470
|
+
if (eqIndex > 0) {
|
|
1471
|
+
val name = trimmed.substring(0, eqIndex)
|
|
1472
|
+
cookieManager.setCookie(domain, "$name=; Expires=Thu, 01 Jan 1970 00:00:00 GMT")
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
cookieManager.flush()
|
|
1476
|
+
}
|
|
1477
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1478
|
+
putBoolean("success", true)
|
|
1479
|
+
})
|
|
1480
|
+
}
|
|
1481
|
+
} catch (e: Throwable) {
|
|
1482
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1483
|
+
putBoolean("success", false)
|
|
1484
|
+
putString("error", e.message ?: "CLEAR_COOKIES_ERROR")
|
|
1485
|
+
})
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// ─── saveToMediaStore ─────────────────────────────────────────────────────
|
|
1490
|
+
|
|
1491
|
+
override fun saveToMediaStore(options: ReadableMap, promise: Promise) {
|
|
1492
|
+
val filePath = options.getString("filePath")
|
|
1493
|
+
if (filePath.isNullOrBlank()) {
|
|
1494
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1495
|
+
putBoolean("success", false)
|
|
1496
|
+
putString("error", "filePath is required")
|
|
1497
|
+
})
|
|
1498
|
+
return
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
val mediaType = if (options.hasKey("mediaType")) options.getString("mediaType") else "download"
|
|
1502
|
+
val album = if (options.hasKey("album")) options.getString("album") else null
|
|
1503
|
+
|
|
1504
|
+
thread {
|
|
1505
|
+
try {
|
|
1506
|
+
val file = File(filePath)
|
|
1507
|
+
if (!file.exists()) {
|
|
1508
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1509
|
+
putBoolean("success", false)
|
|
1510
|
+
putString("error", "File not found: $filePath")
|
|
1511
|
+
})
|
|
1512
|
+
return@thread
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
val mimeType = getMimeType(filePath)
|
|
1516
|
+
|
|
1517
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
1518
|
+
// Android 10+ — use MediaStore ContentResolver API
|
|
1519
|
+
val contentUri = when (mediaType) {
|
|
1520
|
+
"image" -> android.provider.MediaStore.Images.Media.getContentUri(android.provider.MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
|
1521
|
+
"video" -> android.provider.MediaStore.Video.Media.getContentUri(android.provider.MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
|
1522
|
+
"audio" -> android.provider.MediaStore.Audio.Media.getContentUri(android.provider.MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
|
1523
|
+
else -> android.provider.MediaStore.Downloads.getContentUri(android.provider.MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
val relativePath = when (mediaType) {
|
|
1527
|
+
"image" -> if (album != null) "${Environment.DIRECTORY_PICTURES}/$album" else Environment.DIRECTORY_PICTURES
|
|
1528
|
+
"video" -> if (album != null) "${Environment.DIRECTORY_MOVIES}/$album" else Environment.DIRECTORY_MOVIES
|
|
1529
|
+
"audio" -> if (album != null) "${Environment.DIRECTORY_MUSIC}/$album" else Environment.DIRECTORY_MUSIC
|
|
1530
|
+
else -> if (album != null) "${Environment.DIRECTORY_DOWNLOADS}/$album" else Environment.DIRECTORY_DOWNLOADS
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
val values = android.content.ContentValues().apply {
|
|
1534
|
+
put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, file.name)
|
|
1535
|
+
put(android.provider.MediaStore.MediaColumns.MIME_TYPE, mimeType)
|
|
1536
|
+
put(android.provider.MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
|
|
1537
|
+
put(android.provider.MediaStore.MediaColumns.IS_PENDING, 1)
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
val resolver = reactContext.contentResolver
|
|
1541
|
+
val uri = resolver.insert(contentUri, values)
|
|
1542
|
+
|
|
1543
|
+
if (uri == null) {
|
|
1544
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1545
|
+
putBoolean("success", false)
|
|
1546
|
+
putString("error", "Failed to create MediaStore entry")
|
|
1547
|
+
})
|
|
1548
|
+
return@thread
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
resolver.openOutputStream(uri)?.use { outputStream ->
|
|
1552
|
+
FileInputStream(file).use { inputStream ->
|
|
1553
|
+
val buffer = ByteArray(8192)
|
|
1554
|
+
var count: Int
|
|
1555
|
+
while (inputStream.read(buffer).also { count = it } != -1) {
|
|
1556
|
+
outputStream.write(buffer, 0, count)
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
values.clear()
|
|
1562
|
+
values.put(android.provider.MediaStore.MediaColumns.IS_PENDING, 0)
|
|
1563
|
+
resolver.update(uri, values, null, null)
|
|
1564
|
+
|
|
1565
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1566
|
+
putBoolean("success", true)
|
|
1567
|
+
putString("uri", uri.toString())
|
|
1568
|
+
})
|
|
1569
|
+
} else {
|
|
1570
|
+
// Android 9 and below — copy to public directory and media-scan
|
|
1571
|
+
val destDir = when (mediaType) {
|
|
1572
|
+
"image" -> Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
|
1573
|
+
"video" -> Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
|
|
1574
|
+
"audio" -> Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
|
|
1575
|
+
else -> Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
val albumDir = if (album != null) File(destDir, album) else destDir
|
|
1579
|
+
albumDir.mkdirs()
|
|
1580
|
+
val destFile = File(albumDir, file.name)
|
|
1581
|
+
file.copyTo(destFile, overwrite = true)
|
|
1582
|
+
|
|
1583
|
+
android.media.MediaScannerConnection.scanFile(
|
|
1584
|
+
reactContext,
|
|
1585
|
+
arrayOf(destFile.absolutePath),
|
|
1586
|
+
arrayOf(mimeType)
|
|
1587
|
+
) { _, scannedUri ->
|
|
1588
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1589
|
+
putBoolean("success", true)
|
|
1590
|
+
putString("uri", scannedUri?.toString() ?: destFile.absolutePath)
|
|
1591
|
+
})
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
} catch (e: Exception) {
|
|
1595
|
+
promise.resolve(Arguments.createMap().apply {
|
|
1596
|
+
putBoolean("success", false)
|
|
1597
|
+
putString("error", e.message ?: "MEDIA_STORE_ERROR")
|
|
1598
|
+
})
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1330
1603
|
companion object {
|
|
1331
1604
|
const val NAME = NativeFileToolkitSpec.NAME
|
|
1332
1605
|
}
|
package/ios/FileToolkit.mm
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import <React/RCTLog.h>
|
|
3
3
|
#import <CommonCrypto/CommonDigest.h>
|
|
4
4
|
#import <UIKit/UIKit.h>
|
|
5
|
+
#import <Photos/Photos.h>
|
|
5
6
|
#import "FileToolkit.h"
|
|
6
7
|
#include <zlib.h>
|
|
7
8
|
|
|
@@ -1574,4 +1575,223 @@ RCT_EXPORT_METHOD(zip:(NSString *)sourcePath
|
|
|
1574
1575
|
});
|
|
1575
1576
|
}
|
|
1576
1577
|
|
|
1578
|
+
// ─── df (disk space) ──────────────────────────────────────────────────────
|
|
1579
|
+
|
|
1580
|
+
- (void)df:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
1581
|
+
@try {
|
|
1582
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
1583
|
+
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
|
1584
|
+
NSError *error = nil;
|
|
1585
|
+
NSDictionary *attrs = [fm attributesOfFileSystemForPath:docPath error:&error];
|
|
1586
|
+
if (error || !attrs) {
|
|
1587
|
+
resolve(@{@"success": @NO, @"error": error.localizedDescription ?: @"DF_ERROR"});
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
NSNumber *freeBytes = attrs[NSFileSystemFreeSize];
|
|
1592
|
+
NSNumber *totalBytes = attrs[NSFileSystemSize];
|
|
1593
|
+
|
|
1594
|
+
resolve(@{
|
|
1595
|
+
@"success": @YES,
|
|
1596
|
+
@"freeBytes": freeBytes ?: @0,
|
|
1597
|
+
@"totalBytes": totalBytes ?: @0
|
|
1598
|
+
});
|
|
1599
|
+
} @catch (NSException *exception) {
|
|
1600
|
+
resolve(@{@"success": @NO, @"error": exception.reason ?: @"DF_ERROR"});
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// ─── appendFile ───────────────────────────────────────────────────────────
|
|
1605
|
+
|
|
1606
|
+
- (void)appendFile:(NSString *)filePath data:(NSString *)data encoding:(NSString *)encoding resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
1607
|
+
@try {
|
|
1608
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
1609
|
+
|
|
1610
|
+
// Create parent dirs if needed
|
|
1611
|
+
NSString *parent = [filePath stringByDeletingLastPathComponent];
|
|
1612
|
+
if (parent.length > 0) {
|
|
1613
|
+
[fm createDirectoryAtPath:parent withIntermediateDirectories:YES attributes:nil error:nil];
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// Create the file if it doesn't exist
|
|
1617
|
+
if (![fm fileExistsAtPath:filePath]) {
|
|
1618
|
+
[fm createFileAtPath:filePath contents:nil attributes:nil];
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
|
|
1622
|
+
if (!fileHandle) {
|
|
1623
|
+
resolve(@{@"success": @NO, @"error": @"Cannot open file for appending"});
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
[fileHandle seekToEndOfFile];
|
|
1628
|
+
|
|
1629
|
+
NSData *dataToWrite = nil;
|
|
1630
|
+
if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
|
|
1631
|
+
dataToWrite = [[NSData alloc] initWithBase64EncodedString:data options:NSDataBase64DecodingIgnoreUnknownCharacters];
|
|
1632
|
+
if (!dataToWrite) {
|
|
1633
|
+
[fileHandle closeFile];
|
|
1634
|
+
resolve(@{@"success": @NO, @"error": @"Invalid base64 string"});
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
} else {
|
|
1638
|
+
dataToWrite = [data dataUsingEncoding:NSUTF8StringEncoding];
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
[fileHandle writeData:dataToWrite];
|
|
1642
|
+
[fileHandle closeFile];
|
|
1643
|
+
|
|
1644
|
+
resolve(@{@"success": @YES});
|
|
1645
|
+
} @catch (NSException *exception) {
|
|
1646
|
+
resolve(@{@"success": @NO, @"error": exception.reason ?: @"APPEND_FILE_ERROR"});
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// ─── hash ─────────────────────────────────────────────────────────────────
|
|
1651
|
+
|
|
1652
|
+
- (void)hash:(NSString *)filePath algorithm:(NSString *)algorithm resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
1653
|
+
@try {
|
|
1654
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
1655
|
+
BOOL isDir = NO;
|
|
1656
|
+
if (![fm fileExistsAtPath:filePath isDirectory:&isDir] || isDir) {
|
|
1657
|
+
resolve(@{@"success": @NO, @"error": [NSString stringWithFormat:@"File not found: %@", filePath]});
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
NSString *hashValue = [self calculateChecksumForPath:filePath algorithm:[algorithm uppercaseString]];
|
|
1662
|
+
if (!hashValue) {
|
|
1663
|
+
resolve(@{@"success": @NO, @"error": @"Failed to compute hash"});
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
resolve(@{
|
|
1668
|
+
@"success": @YES,
|
|
1669
|
+
@"hash": hashValue
|
|
1670
|
+
});
|
|
1671
|
+
} @catch (NSException *exception) {
|
|
1672
|
+
resolve(@{@"success": @NO, @"error": exception.reason ?: @"HASH_ERROR"});
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// ─── getCookies ───────────────────────────────────────────────────────────
|
|
1677
|
+
|
|
1678
|
+
- (void)getCookies:(NSString *)domain resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
1679
|
+
@try {
|
|
1680
|
+
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
|
|
1681
|
+
NSArray<NSHTTPCookie *> *allCookies = [storage cookies];
|
|
1682
|
+
NSMutableArray *result = [NSMutableArray new];
|
|
1683
|
+
|
|
1684
|
+
for (NSHTTPCookie *cookie in allCookies) {
|
|
1685
|
+
// Match cookies whose domain ends with the requested domain
|
|
1686
|
+
if (domain.length == 0 || [cookie.domain hasSuffix:domain] || [domain hasSuffix:cookie.domain]) {
|
|
1687
|
+
NSMutableDictionary *cookieDict = [NSMutableDictionary dictionaryWithDictionary:@{
|
|
1688
|
+
@"name": cookie.name ?: @"",
|
|
1689
|
+
@"value": cookie.value ?: @"",
|
|
1690
|
+
@"domain": cookie.domain ?: @"",
|
|
1691
|
+
@"path": cookie.path ?: @"/"
|
|
1692
|
+
}];
|
|
1693
|
+
|
|
1694
|
+
if (cookie.expiresDate) {
|
|
1695
|
+
cookieDict[@"expiresDate"] = @([cookie.expiresDate timeIntervalSince1970] * 1000);
|
|
1696
|
+
}
|
|
1697
|
+
cookieDict[@"isSecure"] = @(cookie.isSecure);
|
|
1698
|
+
cookieDict[@"isHTTPOnly"] = @(cookie.isHTTPOnly);
|
|
1699
|
+
|
|
1700
|
+
[result addObject:cookieDict];
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
resolve(@{@"success": @YES, @"cookies": result});
|
|
1705
|
+
} @catch (NSException *exception) {
|
|
1706
|
+
resolve(@{@"success": @NO, @"error": exception.reason ?: @"GET_COOKIES_ERROR"});
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
// ─── clearCookies ─────────────────────────────────────────────────────────
|
|
1711
|
+
|
|
1712
|
+
- (void)clearCookies:(NSString *)domain resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
1713
|
+
@try {
|
|
1714
|
+
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
|
|
1715
|
+
|
|
1716
|
+
if (domain.length == 0) {
|
|
1717
|
+
// Clear ALL cookies
|
|
1718
|
+
NSArray<NSHTTPCookie *> *allCookies = [storage cookies];
|
|
1719
|
+
for (NSHTTPCookie *cookie in allCookies) {
|
|
1720
|
+
[storage deleteCookie:cookie];
|
|
1721
|
+
}
|
|
1722
|
+
} else {
|
|
1723
|
+
// Clear cookies matching the domain
|
|
1724
|
+
NSArray<NSHTTPCookie *> *allCookies = [storage cookies];
|
|
1725
|
+
for (NSHTTPCookie *cookie in allCookies) {
|
|
1726
|
+
if ([cookie.domain hasSuffix:domain] || [domain hasSuffix:cookie.domain]) {
|
|
1727
|
+
[storage deleteCookie:cookie];
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
resolve(@{@"success": @YES});
|
|
1733
|
+
} @catch (NSException *exception) {
|
|
1734
|
+
resolve(@{@"success": @NO, @"error": exception.reason ?: @"CLEAR_COOKIES_ERROR"});
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// ─── saveToMediaStore ─────────────────────────────────────────────────────
|
|
1739
|
+
|
|
1740
|
+
RCT_REMAP_METHOD(saveToMediaStore,
|
|
1741
|
+
mediaStoreOptions:(NSDictionary *)options
|
|
1742
|
+
mediaStoreResolver:(RCTPromiseResolveBlock)resolve
|
|
1743
|
+
mediaStoreRejecter:(RCTPromiseRejectBlock)reject)
|
|
1744
|
+
{
|
|
1745
|
+
NSString *filePath = options[@"filePath"];
|
|
1746
|
+
if (!filePath || filePath.length == 0) {
|
|
1747
|
+
resolve(@{@"success": @NO, @"error": @"filePath is required"});
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
|
1752
|
+
resolve(@{@"success": @NO, @"error": [NSString stringWithFormat:@"File not found: %@", filePath]});
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
NSString *mediaType = options[@"mediaType"] ?: @"download";
|
|
1757
|
+
|
|
1758
|
+
if ([mediaType isEqualToString:@"image"] || [mediaType isEqualToString:@"video"]) {
|
|
1759
|
+
// Use Photos framework for images and videos
|
|
1760
|
+
PHPhotoLibrary *photoLibrary = [PHPhotoLibrary sharedPhotoLibrary];
|
|
1761
|
+
|
|
1762
|
+
[photoLibrary performChanges:^{
|
|
1763
|
+
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
|
|
1764
|
+
if ([mediaType isEqualToString:@"image"]) {
|
|
1765
|
+
[PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:fileURL];
|
|
1766
|
+
} else {
|
|
1767
|
+
[PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:fileURL];
|
|
1768
|
+
}
|
|
1769
|
+
} completionHandler:^(BOOL success, NSError *error) {
|
|
1770
|
+
if (success) {
|
|
1771
|
+
resolve(@{@"success": @YES, @"uri": filePath});
|
|
1772
|
+
} else {
|
|
1773
|
+
resolve(@{@"success": @NO, @"error": error.localizedDescription ?: @"Failed to save to Photos"});
|
|
1774
|
+
}
|
|
1775
|
+
}];
|
|
1776
|
+
} else {
|
|
1777
|
+
// For audio/download types, copy to Documents directory (iOS has no shared media store for these)
|
|
1778
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
1779
|
+
NSString *fileName = [filePath lastPathComponent];
|
|
1780
|
+
NSURL *docsDir = [[NSFileManager defaultManager]
|
|
1781
|
+
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
|
|
1782
|
+
NSURL *destURL = [docsDir URLByAppendingPathComponent:fileName];
|
|
1783
|
+
|
|
1784
|
+
NSError *copyError = nil;
|
|
1785
|
+
[[NSFileManager defaultManager] removeItemAtURL:destURL error:nil];
|
|
1786
|
+
BOOL ok = [[NSFileManager defaultManager] copyItemAtPath:filePath toPath:destURL.path error:©Error];
|
|
1787
|
+
|
|
1788
|
+
if (ok) {
|
|
1789
|
+
resolve(@{@"success": @YES, @"uri": destURL.path});
|
|
1790
|
+
} else {
|
|
1791
|
+
resolve(@{@"success": @NO, @"error": copyError.localizedDescription ?: @"MEDIA_STORE_ERROR"});
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1577
1797
|
@end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeFileToolkit.ts"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAAqE,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeFileToolkit.ts"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAAqE,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAkCtDC,gCAAmB,CAACC,YAAY,CAAO,aAAa,CAAC","ignoreList":[]}
|