react-native-kookit 0.2.3 → 0.2.6
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_FTP_UPDATE.md +161 -0
- package/ANDROID_PARAMETER_FIX.md +0 -0
- package/API_UNIFICATION.md +180 -0
- package/FTP_CLIENT_API.md +301 -0
- package/FTP_EXAMPLE_IMPLEMENTATION.md +0 -0
- package/FTP_FILE_LIST_ENHANCEMENT.md +186 -0
- package/FTP_FILE_OPERATIONS.md +0 -0
- package/android/src/main/java/expo/modules/kookit/FtpClient.kt +2 -0
- package/android/src/main/java/expo/modules/kookit/ReactNativeKookitModule.kt +252 -0
- package/build/FtpClient.d.ts +97 -0
- package/build/FtpClient.d.ts.map +1 -0
- package/build/FtpClient.js +199 -0
- package/build/FtpClient.js.map +1 -0
- package/build/ReactNativeKookit.types.d.ts +13 -5
- package/build/ReactNativeKookit.types.d.ts.map +1 -1
- package/build/ReactNativeKookit.types.js.map +1 -1
- package/build/ReactNativeKookitModule.d.ts +54 -12
- package/build/ReactNativeKookitModule.d.ts.map +1 -1
- package/build/ReactNativeKookitModule.js.map +1 -1
- package/build/index.d.ts +4 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -3
- package/build/index.js.map +1 -1
- package/ios/ReactNativeKookitModule.swift +252 -44
- package/package.json +1 -1
|
@@ -311,32 +311,53 @@ class FtpClient: @unchecked Sendable {
|
|
|
311
311
|
func downloadFile(remotePath: String, localPath: String) async throws {
|
|
312
312
|
guard isConnected else { throw FtpError.notConnected }
|
|
313
313
|
|
|
314
|
+
print("FTP DEBUG: Starting download - Remote: \(remotePath), Local: \(localPath)")
|
|
315
|
+
|
|
314
316
|
let dataConnection = try await enterPassiveMode()
|
|
317
|
+
print("FTP DEBUG: Passive mode established for download")
|
|
315
318
|
|
|
316
319
|
let response = try await sendCommand("RETR \(remotePath)")
|
|
317
|
-
|
|
320
|
+
print("FTP DEBUG: RETR command response: \(response)")
|
|
321
|
+
|
|
322
|
+
// Extract the first line for status check
|
|
323
|
+
let firstLine = response.components(separatedBy: .newlines).first ?? response
|
|
324
|
+
guard firstLine.hasPrefix("150") || firstLine.hasPrefix("125") else {
|
|
318
325
|
dataConnection.cancel()
|
|
319
|
-
throw FtpError.commandFailed("Download failed: \(
|
|
326
|
+
throw FtpError.commandFailed("Download failed: \(firstLine)")
|
|
320
327
|
}
|
|
321
328
|
|
|
322
329
|
let localURL = URL(fileURLWithPath: localPath)
|
|
323
330
|
|
|
324
331
|
// Create parent directories if needed
|
|
332
|
+
print("FTP DEBUG: Creating parent directories for: \(localURL.deletingLastPathComponent().path)")
|
|
325
333
|
try FileManager.default.createDirectory(at: localURL.deletingLastPathComponent(),
|
|
326
334
|
withIntermediateDirectories: true,
|
|
327
335
|
attributes: nil)
|
|
328
336
|
|
|
337
|
+
print("FTP DEBUG: Starting data reception...")
|
|
329
338
|
let data = try await receiveData(from: dataConnection, reportProgress: true)
|
|
339
|
+
print("FTP DEBUG: Data reception completed, received \(data.count) bytes")
|
|
330
340
|
dataConnection.cancel()
|
|
331
341
|
|
|
342
|
+
print("FTP DEBUG: Writing data to file...")
|
|
332
343
|
try data.write(to: localURL)
|
|
344
|
+
print("FTP DEBUG: File written successfully")
|
|
333
345
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
346
|
+
// Check if the response already contained the completion message (226)
|
|
347
|
+
if response.contains("226") {
|
|
348
|
+
print("FTP DEBUG: Transfer completion already received in RETR response - skipping final response read")
|
|
349
|
+
} else {
|
|
350
|
+
print("FTP DEBUG: Reading final response...")
|
|
351
|
+
let finalResponse = try await readResponse()
|
|
352
|
+
let finalFirstLine = finalResponse.components(separatedBy: .newlines).first ?? finalResponse
|
|
353
|
+
print("FTP DEBUG: Final download response: \(finalFirstLine)")
|
|
354
|
+
guard finalFirstLine.hasPrefix("226") else {
|
|
355
|
+
try? FileManager.default.removeItem(at: localURL)
|
|
356
|
+
throw FtpError.commandFailed("Download completion failed: \(finalFirstLine)")
|
|
357
|
+
}
|
|
338
358
|
}
|
|
339
359
|
|
|
360
|
+
print("FTP DEBUG: Download completed successfully")
|
|
340
361
|
progressDelegate?.onComplete()
|
|
341
362
|
}
|
|
342
363
|
|
|
@@ -454,6 +475,7 @@ class FtpClient: @unchecked Sendable {
|
|
|
454
475
|
throw FtpError.notConnected
|
|
455
476
|
}
|
|
456
477
|
|
|
478
|
+
print("FTP DEBUG: Sending command: \(command)")
|
|
457
479
|
let commandData = "\(command)\r\n".data(using: .utf8)!
|
|
458
480
|
|
|
459
481
|
return try await withCheckedThrowingContinuation { continuation in
|
|
@@ -461,13 +483,24 @@ class FtpClient: @unchecked Sendable {
|
|
|
461
483
|
|
|
462
484
|
connection.send(content: commandData, completion: .contentProcessed { error in
|
|
463
485
|
if let error = error {
|
|
486
|
+
print("FTP DEBUG: Send command error: \(error)")
|
|
464
487
|
connectionBox.resume(throwing: error)
|
|
465
488
|
} else {
|
|
489
|
+
print("FTP DEBUG: Command sent successfully, waiting for response...")
|
|
466
490
|
Task {
|
|
467
491
|
do {
|
|
468
492
|
let response = try await self.readResponse()
|
|
469
|
-
|
|
493
|
+
print("FTP DEBUG: Command response received: \(response)")
|
|
494
|
+
|
|
495
|
+
// Store the full response for multi-line responses
|
|
496
|
+
if response.contains("\n") {
|
|
497
|
+
print("FTP DEBUG: Multi-line response detected")
|
|
498
|
+
connectionBox.resume(returning: response)
|
|
499
|
+
} else {
|
|
500
|
+
connectionBox.resume(returning: response)
|
|
501
|
+
}
|
|
470
502
|
} catch {
|
|
503
|
+
print("FTP DEBUG: Error reading response: \(error)")
|
|
471
504
|
connectionBox.resume(throwing: error)
|
|
472
505
|
}
|
|
473
506
|
}
|
|
@@ -481,35 +514,47 @@ class FtpClient: @unchecked Sendable {
|
|
|
481
514
|
throw FtpError.notConnected
|
|
482
515
|
}
|
|
483
516
|
|
|
517
|
+
print("FTP DEBUG: Reading response...")
|
|
484
518
|
return try await withCheckedThrowingContinuation { continuation in
|
|
485
519
|
let connectionBox = ConnectionBox<String>(continuation: continuation)
|
|
486
520
|
|
|
487
521
|
connection.receive(minimumIncompleteLength: 1, maximumLength: 4096) { data, _, isComplete, error in
|
|
488
522
|
if let error = error {
|
|
523
|
+
print("FTP DEBUG: Read response error: \(error)")
|
|
489
524
|
connectionBox.resume(throwing: error)
|
|
490
525
|
} else if let data = data {
|
|
526
|
+
print("FTP DEBUG: Received response data: \(data.count) bytes")
|
|
491
527
|
// Try multiple encodings for FTP responses
|
|
492
528
|
var response: String?
|
|
493
529
|
|
|
494
530
|
// Try UTF-8 first
|
|
495
531
|
if let utf8Response = String(data: data, encoding: .utf8) {
|
|
496
532
|
response = utf8Response
|
|
533
|
+
print("FTP DEBUG: Response decoded with UTF-8")
|
|
497
534
|
}
|
|
498
535
|
// Try GBK for Chinese FTP servers
|
|
499
536
|
else if let gbkResponse = String(data: data, encoding: String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue)))) {
|
|
500
537
|
response = gbkResponse
|
|
538
|
+
print("FTP DEBUG: Response decoded with GBK")
|
|
501
539
|
}
|
|
502
540
|
// Try ASCII as fallback
|
|
503
541
|
else if let asciiResponse = String(data: data, encoding: .ascii) {
|
|
504
542
|
response = asciiResponse
|
|
543
|
+
print("FTP DEBUG: Response decoded with ASCII")
|
|
505
544
|
}
|
|
506
545
|
|
|
507
546
|
if let validResponse = response {
|
|
508
|
-
|
|
547
|
+
let trimmedResponse = validResponse.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
548
|
+
print("FTP DEBUG: Response content: '\(trimmedResponse)'")
|
|
549
|
+
|
|
550
|
+
// Return the full response for multi-line processing
|
|
551
|
+
connectionBox.resume(returning: trimmedResponse)
|
|
509
552
|
} else {
|
|
553
|
+
print("FTP DEBUG: Failed to decode response")
|
|
510
554
|
connectionBox.resume(throwing: FtpError.invalidResponse)
|
|
511
555
|
}
|
|
512
556
|
} else {
|
|
557
|
+
print("FTP DEBUG: No data received")
|
|
513
558
|
connectionBox.resume(throwing: FtpError.invalidResponse)
|
|
514
559
|
}
|
|
515
560
|
}
|
|
@@ -517,7 +562,9 @@ class FtpClient: @unchecked Sendable {
|
|
|
517
562
|
}
|
|
518
563
|
|
|
519
564
|
private func enterPassiveMode() async throws -> NWConnection {
|
|
565
|
+
print("FTP DEBUG: Entering passive mode...")
|
|
520
566
|
let response = try await sendCommand("PASV")
|
|
567
|
+
print("FTP DEBUG: PASV response: \(response)")
|
|
521
568
|
guard response.hasPrefix("227") else {
|
|
522
569
|
throw FtpError.commandFailed("Failed to enter passive mode: \(response)")
|
|
523
570
|
}
|
|
@@ -541,6 +588,8 @@ class FtpClient: @unchecked Sendable {
|
|
|
541
588
|
let host = "\(h1).\(h2).\(h3).\(h4)"
|
|
542
589
|
let port = p1 * 256 + p2
|
|
543
590
|
|
|
591
|
+
print("FTP DEBUG: Parsed passive mode - Host: \(host), Port: \(port)")
|
|
592
|
+
|
|
544
593
|
let dataHost = NWEndpoint.Host(host)
|
|
545
594
|
let dataPort = NWEndpoint.Port(integerLiteral: UInt16(port))
|
|
546
595
|
let dataEndpoint = NWEndpoint.hostPort(host: dataHost, port: dataPort)
|
|
@@ -551,15 +600,18 @@ class FtpClient: @unchecked Sendable {
|
|
|
551
600
|
let connectionBox = NWConnectionBox(connection: dataConnection, continuation: continuation)
|
|
552
601
|
|
|
553
602
|
dataConnection.stateUpdateHandler = { state in
|
|
603
|
+
print("FTP DEBUG: Data connection state: \(state)")
|
|
554
604
|
connectionBox.handleStateUpdate(state)
|
|
555
605
|
}
|
|
556
606
|
|
|
607
|
+
print("FTP DEBUG: Starting data connection...")
|
|
557
608
|
dataConnection.start(queue: .global())
|
|
558
609
|
}
|
|
559
610
|
}
|
|
560
611
|
|
|
561
612
|
private func receiveData(from connection: NWConnection, reportProgress: Bool = false) async throws -> Data {
|
|
562
613
|
var receivedData = Data()
|
|
614
|
+
print("FTP DEBUG: Starting data reception, reportProgress: \(reportProgress)")
|
|
563
615
|
|
|
564
616
|
return try await withCheckedThrowingContinuation { continuation in
|
|
565
617
|
let connectionBox = ConnectionBox<Data>(continuation: continuation)
|
|
@@ -567,12 +619,14 @@ class FtpClient: @unchecked Sendable {
|
|
|
567
619
|
func receiveMore() {
|
|
568
620
|
connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { data, _, isComplete, error in
|
|
569
621
|
if let error = error {
|
|
622
|
+
print("FTP DEBUG: receiveData error: \(error)")
|
|
570
623
|
connectionBox.resume(throwing: error)
|
|
571
624
|
return
|
|
572
625
|
}
|
|
573
626
|
|
|
574
627
|
if let data = data {
|
|
575
628
|
receivedData.append(data)
|
|
629
|
+
print("FTP DEBUG: Received chunk of \(data.count) bytes, total: \(receivedData.count) bytes")
|
|
576
630
|
if reportProgress {
|
|
577
631
|
DispatchQueue.main.async {
|
|
578
632
|
self.progressDelegate?.onProgress(transferred: Int64(receivedData.count), total: -1)
|
|
@@ -581,8 +635,10 @@ class FtpClient: @unchecked Sendable {
|
|
|
581
635
|
}
|
|
582
636
|
|
|
583
637
|
if isComplete {
|
|
638
|
+
print("FTP DEBUG: Data reception complete, total bytes: \(receivedData.count)")
|
|
584
639
|
connectionBox.resume(returning: receivedData)
|
|
585
640
|
} else {
|
|
641
|
+
print("FTP DEBUG: Receiving more data...")
|
|
586
642
|
receiveMore()
|
|
587
643
|
}
|
|
588
644
|
}
|
|
@@ -747,7 +803,7 @@ public class ReactNativeKookitModule: Module {
|
|
|
747
803
|
private var volumeObserver: NSKeyValueObservation?
|
|
748
804
|
private var isVolumeKeyInterceptionEnabled = false
|
|
749
805
|
private var previousVolume: Float = 0.0
|
|
750
|
-
private var
|
|
806
|
+
private var ftpClients: [String: FtpClient] = [:] // Store multiple FTP clients by ID
|
|
751
807
|
|
|
752
808
|
// Each module class must implement the definition function. The definition consists of components
|
|
753
809
|
// that describes the module's functionality and behavior.
|
|
@@ -790,8 +846,31 @@ public class ReactNativeKookitModule: Module {
|
|
|
790
846
|
self.disableVolumeKeyInterception()
|
|
791
847
|
}
|
|
792
848
|
|
|
793
|
-
// FTP
|
|
794
|
-
AsyncFunction("
|
|
849
|
+
// New FTP Client API - Create new FTP client instance
|
|
850
|
+
AsyncFunction("createFtpClient") { (clientId: String, promise: Promise) in
|
|
851
|
+
if self.ftpClients[clientId] != nil {
|
|
852
|
+
promise.reject("FTP_CLIENT_EXISTS", "FTP client with ID '\(clientId)' already exists")
|
|
853
|
+
return
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
let ftpClient = FtpClient()
|
|
857
|
+
let progressDelegate = FtpProgressDelegateImpl(module: self, clientId: clientId)
|
|
858
|
+
ftpClient.setProgressDelegate(progressDelegate)
|
|
859
|
+
self.ftpClients[clientId] = ftpClient
|
|
860
|
+
|
|
861
|
+
promise.resolve([
|
|
862
|
+
"clientId": clientId,
|
|
863
|
+
"created": true
|
|
864
|
+
])
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Connect FTP client
|
|
868
|
+
AsyncFunction("ftpClientConnect") { (clientId: String, config: [String: Any], promise: Promise) in
|
|
869
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
870
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
871
|
+
return
|
|
872
|
+
}
|
|
873
|
+
|
|
795
874
|
Task {
|
|
796
875
|
do {
|
|
797
876
|
let ftpConfig = FtpConnectionConfig(
|
|
@@ -803,30 +882,60 @@ public class ReactNativeKookitModule: Module {
|
|
|
803
882
|
timeout: config["timeout"] as? TimeInterval ?? 30.0
|
|
804
883
|
)
|
|
805
884
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
promise.resolve()
|
|
885
|
+
try await ftpClient.connect(config: ftpConfig)
|
|
886
|
+
promise.resolve([
|
|
887
|
+
"clientId": clientId,
|
|
888
|
+
"connected": true
|
|
889
|
+
])
|
|
812
890
|
} catch {
|
|
813
891
|
promise.reject("FTP_CONNECT_ERROR", error.localizedDescription)
|
|
814
892
|
}
|
|
815
893
|
}
|
|
816
894
|
}
|
|
817
895
|
|
|
818
|
-
|
|
896
|
+
// Disconnect FTP client
|
|
897
|
+
AsyncFunction("ftpClientDisconnect") { (clientId: String, promise: Promise) in
|
|
898
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
899
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
900
|
+
return
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
Task {
|
|
904
|
+
await ftpClient.disconnect()
|
|
905
|
+
promise.resolve([
|
|
906
|
+
"clientId": clientId,
|
|
907
|
+
"disconnected": true
|
|
908
|
+
])
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Dispose FTP client
|
|
913
|
+
AsyncFunction("disposeFtpClient") { (clientId: String, promise: Promise) in
|
|
914
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
915
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
916
|
+
return
|
|
917
|
+
}
|
|
918
|
+
|
|
819
919
|
Task {
|
|
820
|
-
await
|
|
821
|
-
self.
|
|
822
|
-
promise.resolve(
|
|
920
|
+
await ftpClient.disconnect()
|
|
921
|
+
self.ftpClients.removeValue(forKey: clientId)
|
|
922
|
+
promise.resolve([
|
|
923
|
+
"clientId": clientId,
|
|
924
|
+
"disposed": true
|
|
925
|
+
])
|
|
823
926
|
}
|
|
824
927
|
}
|
|
825
928
|
|
|
826
|
-
|
|
929
|
+
// List files
|
|
930
|
+
AsyncFunction("ftpClientList") { (clientId: String, path: String?, promise: Promise) in
|
|
931
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
932
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
933
|
+
return
|
|
934
|
+
}
|
|
935
|
+
|
|
827
936
|
Task {
|
|
828
937
|
do {
|
|
829
|
-
let files = try await
|
|
938
|
+
let files = try await ftpClient.listFiles(path: path)
|
|
830
939
|
let result = files.map { file in
|
|
831
940
|
[
|
|
832
941
|
"name": file.name,
|
|
@@ -843,72 +952,163 @@ public class ReactNativeKookitModule: Module {
|
|
|
843
952
|
}
|
|
844
953
|
}
|
|
845
954
|
|
|
846
|
-
|
|
955
|
+
// Download file
|
|
956
|
+
AsyncFunction("ftpClientDownload") { (clientId: String, remotePath: String, localPath: String, promise: Promise) in
|
|
957
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
958
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
959
|
+
return
|
|
960
|
+
}
|
|
961
|
+
|
|
847
962
|
Task {
|
|
848
963
|
do {
|
|
849
|
-
try await
|
|
850
|
-
promise.resolve(
|
|
964
|
+
try await ftpClient.downloadFile(remotePath: remotePath, localPath: localPath)
|
|
965
|
+
promise.resolve([
|
|
966
|
+
"clientId": clientId,
|
|
967
|
+
"remotePath": remotePath,
|
|
968
|
+
"localPath": localPath,
|
|
969
|
+
"downloaded": true
|
|
970
|
+
])
|
|
851
971
|
} catch {
|
|
852
972
|
promise.reject("FTP_DOWNLOAD_ERROR", error.localizedDescription)
|
|
853
973
|
}
|
|
854
974
|
}
|
|
855
975
|
}
|
|
856
976
|
|
|
857
|
-
|
|
977
|
+
// Upload file
|
|
978
|
+
AsyncFunction("ftpClientUpload") { (clientId: String, localPath: String, remotePath: String, promise: Promise) in
|
|
979
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
980
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
981
|
+
return
|
|
982
|
+
}
|
|
983
|
+
|
|
858
984
|
Task {
|
|
859
985
|
do {
|
|
860
|
-
try await
|
|
861
|
-
promise.resolve(
|
|
986
|
+
try await ftpClient.uploadFile(localPath: localPath, remotePath: remotePath)
|
|
987
|
+
promise.resolve([
|
|
988
|
+
"clientId": clientId,
|
|
989
|
+
"localPath": localPath,
|
|
990
|
+
"remotePath": remotePath,
|
|
991
|
+
"uploaded": true
|
|
992
|
+
])
|
|
862
993
|
} catch {
|
|
863
994
|
promise.reject("FTP_UPLOAD_ERROR", error.localizedDescription)
|
|
864
995
|
}
|
|
865
996
|
}
|
|
866
997
|
}
|
|
867
998
|
|
|
868
|
-
|
|
999
|
+
// Delete file
|
|
1000
|
+
AsyncFunction("ftpClientDelete") { (clientId: String, remotePath: String, isDirectory: Bool?, promise: Promise) in
|
|
1001
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
1002
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
1003
|
+
return
|
|
1004
|
+
}
|
|
1005
|
+
|
|
869
1006
|
Task {
|
|
870
1007
|
do {
|
|
871
|
-
try await
|
|
872
|
-
promise.resolve(
|
|
1008
|
+
try await ftpClient.deleteFile(remotePath: remotePath, isDirectory: isDirectory ?? false)
|
|
1009
|
+
promise.resolve([
|
|
1010
|
+
"clientId": clientId,
|
|
1011
|
+
"remotePath": remotePath,
|
|
1012
|
+
"deleted": true
|
|
1013
|
+
])
|
|
873
1014
|
} catch {
|
|
874
1015
|
promise.reject("FTP_DELETE_ERROR", error.localizedDescription)
|
|
875
1016
|
}
|
|
876
1017
|
}
|
|
877
1018
|
}
|
|
878
1019
|
|
|
879
|
-
|
|
1020
|
+
// Create directory
|
|
1021
|
+
AsyncFunction("ftpClientCreateDirectory") { (clientId: String, remotePath: String, promise: Promise) in
|
|
1022
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
1023
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
1024
|
+
return
|
|
1025
|
+
}
|
|
1026
|
+
|
|
880
1027
|
Task {
|
|
881
1028
|
do {
|
|
882
|
-
try await
|
|
883
|
-
promise.resolve(
|
|
1029
|
+
try await ftpClient.createDirectory(remotePath: remotePath)
|
|
1030
|
+
promise.resolve([
|
|
1031
|
+
"clientId": clientId,
|
|
1032
|
+
"remotePath": remotePath,
|
|
1033
|
+
"created": true
|
|
1034
|
+
])
|
|
884
1035
|
} catch {
|
|
885
1036
|
promise.reject("FTP_CREATE_DIR_ERROR", error.localizedDescription)
|
|
886
1037
|
}
|
|
887
1038
|
}
|
|
888
1039
|
}
|
|
889
1040
|
|
|
890
|
-
|
|
1041
|
+
// Change directory
|
|
1042
|
+
AsyncFunction("ftpClientChangeDirectory") { (clientId: String, remotePath: String, promise: Promise) in
|
|
1043
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
1044
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
1045
|
+
return
|
|
1046
|
+
}
|
|
1047
|
+
|
|
891
1048
|
Task {
|
|
892
1049
|
do {
|
|
893
|
-
try await
|
|
894
|
-
promise.resolve(
|
|
1050
|
+
try await ftpClient.changeDirectory(remotePath: remotePath)
|
|
1051
|
+
promise.resolve([
|
|
1052
|
+
"clientId": clientId,
|
|
1053
|
+
"currentDirectory": remotePath,
|
|
1054
|
+
"changed": true
|
|
1055
|
+
])
|
|
895
1056
|
} catch {
|
|
896
1057
|
promise.reject("FTP_CHANGE_DIR_ERROR", error.localizedDescription)
|
|
897
1058
|
}
|
|
898
1059
|
}
|
|
899
1060
|
}
|
|
900
1061
|
|
|
901
|
-
|
|
1062
|
+
// Get current directory
|
|
1063
|
+
AsyncFunction("ftpClientGetCurrentDirectory") { (clientId: String, promise: Promise) in
|
|
1064
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
1065
|
+
promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
|
|
1066
|
+
return
|
|
1067
|
+
}
|
|
1068
|
+
|
|
902
1069
|
Task {
|
|
903
1070
|
do {
|
|
904
|
-
let currentDir = try await
|
|
905
|
-
promise.resolve(
|
|
1071
|
+
let currentDir = try await ftpClient.getCurrentDirectory()
|
|
1072
|
+
promise.resolve([
|
|
1073
|
+
"clientId": clientId,
|
|
1074
|
+
"currentDirectory": currentDir
|
|
1075
|
+
])
|
|
906
1076
|
} catch {
|
|
907
1077
|
promise.reject("FTP_PWD_ERROR", error.localizedDescription)
|
|
908
1078
|
}
|
|
909
1079
|
}
|
|
910
1080
|
}
|
|
911
1081
|
|
|
1082
|
+
// Get FTP client status
|
|
1083
|
+
AsyncFunction("getFtpClientStatus") { (clientId: String, promise: Promise) in
|
|
1084
|
+
guard let ftpClient = self.ftpClients[clientId] else {
|
|
1085
|
+
promise.resolve([
|
|
1086
|
+
"exists": false,
|
|
1087
|
+
"connected": false
|
|
1088
|
+
])
|
|
1089
|
+
return
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
promise.resolve([
|
|
1093
|
+
"exists": true,
|
|
1094
|
+
"connected": ftpClient.isConnected
|
|
1095
|
+
])
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// List all FTP clients
|
|
1099
|
+
AsyncFunction("listFtpClients") { (promise: Promise) in
|
|
1100
|
+
let clientsInfo = self.ftpClients.mapValues { client in
|
|
1101
|
+
[
|
|
1102
|
+
"connected": client.isConnected
|
|
1103
|
+
]
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
promise.resolve([
|
|
1107
|
+
"clients": clientsInfo,
|
|
1108
|
+
"count": self.ftpClients.count
|
|
1109
|
+
])
|
|
1110
|
+
}
|
|
1111
|
+
|
|
912
1112
|
// Enables the module to be used as a native view. Definition components that are accepted as part of
|
|
913
1113
|
// the view definition: Prop, Events.
|
|
914
1114
|
View(ReactNativeKookitView.self) {
|
|
@@ -1007,14 +1207,17 @@ public class ReactNativeKookitModule: Module {
|
|
|
1007
1207
|
// Helper class to handle FTP progress callbacks
|
|
1008
1208
|
private class FtpProgressDelegateImpl: FtpProgressDelegate {
|
|
1009
1209
|
weak var module: ReactNativeKookitModule?
|
|
1210
|
+
let clientId: String
|
|
1010
1211
|
|
|
1011
|
-
init(module: ReactNativeKookitModule) {
|
|
1212
|
+
init(module: ReactNativeKookitModule, clientId: String) {
|
|
1012
1213
|
self.module = module
|
|
1214
|
+
self.clientId = clientId
|
|
1013
1215
|
}
|
|
1014
1216
|
|
|
1015
1217
|
func onProgress(transferred: Int64, total: Int64) {
|
|
1016
1218
|
let percentage = total > 0 ? Int((transferred * 100) / total) : 0
|
|
1017
1219
|
module?.sendEvent("onFtpProgress", [
|
|
1220
|
+
"clientId": clientId,
|
|
1018
1221
|
"transferred": transferred,
|
|
1019
1222
|
"total": total,
|
|
1020
1223
|
"percentage": percentage
|
|
@@ -1022,10 +1225,15 @@ private class FtpProgressDelegateImpl: FtpProgressDelegate {
|
|
|
1022
1225
|
}
|
|
1023
1226
|
|
|
1024
1227
|
func onComplete() {
|
|
1025
|
-
module?.sendEvent("onFtpComplete"
|
|
1228
|
+
module?.sendEvent("onFtpComplete", [
|
|
1229
|
+
"clientId": clientId
|
|
1230
|
+
])
|
|
1026
1231
|
}
|
|
1027
1232
|
|
|
1028
1233
|
func onError(error: String) {
|
|
1029
|
-
module?.sendEvent("onFtpError", [
|
|
1234
|
+
module?.sendEvent("onFtpError", [
|
|
1235
|
+
"clientId": clientId,
|
|
1236
|
+
"error": error
|
|
1237
|
+
])
|
|
1030
1238
|
}
|
|
1031
1239
|
}
|
package/package.json
CHANGED