react-native-kookit 0.2.4 → 0.2.7

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.
@@ -292,10 +292,17 @@ class FtpClient: @unchecked Sendable {
292
292
  }
293
293
  dataConnection.cancel()
294
294
 
295
- let finalResponse = try await readResponse()
296
- print("FTP DEBUG: Final response: \(finalResponse)")
297
- guard finalResponse.hasPrefix("226") else {
298
- throw FtpError.commandFailed("LIST completion failed: \(finalResponse)")
295
+ // Check if the response already contained the completion message (226)
296
+ if response.contains("226") {
297
+ print("FTP DEBUG: Transfer completion already received in LIST response - skipping final response read")
298
+ } else {
299
+ print("FTP DEBUG: Reading final response...")
300
+ let finalResponse = try await readResponse()
301
+ let finalFirstLine = finalResponse.components(separatedBy: .newlines).first ?? finalResponse
302
+ print("FTP DEBUG: Final response: \(finalFirstLine)")
303
+ guard finalFirstLine.hasPrefix("226") else {
304
+ throw FtpError.commandFailed("LIST completion failed: \(finalFirstLine)")
305
+ }
299
306
  }
300
307
 
301
308
  let files = parseListingData(data)
@@ -803,7 +810,7 @@ public class ReactNativeKookitModule: Module {
803
810
  private var volumeObserver: NSKeyValueObservation?
804
811
  private var isVolumeKeyInterceptionEnabled = false
805
812
  private var previousVolume: Float = 0.0
806
- private var ftpClient: FtpClient?
813
+ private var ftpClients: [String: FtpClient] = [:] // Store multiple FTP clients by ID
807
814
 
808
815
  // Each module class must implement the definition function. The definition consists of components
809
816
  // that describes the module's functionality and behavior.
@@ -846,8 +853,31 @@ public class ReactNativeKookitModule: Module {
846
853
  self.disableVolumeKeyInterception()
847
854
  }
848
855
 
849
- // FTP Functions
850
- AsyncFunction("ftpConnect") { (config: [String: Any], promise: Promise) in
856
+ // New FTP Client API - Create new FTP client instance
857
+ AsyncFunction("createFtpClient") { (clientId: String, promise: Promise) in
858
+ if self.ftpClients[clientId] != nil {
859
+ promise.reject("FTP_CLIENT_EXISTS", "FTP client with ID '\(clientId)' already exists")
860
+ return
861
+ }
862
+
863
+ let ftpClient = FtpClient()
864
+ let progressDelegate = FtpProgressDelegateImpl(module: self, clientId: clientId)
865
+ ftpClient.setProgressDelegate(progressDelegate)
866
+ self.ftpClients[clientId] = ftpClient
867
+
868
+ promise.resolve([
869
+ "clientId": clientId,
870
+ "created": true
871
+ ])
872
+ }
873
+
874
+ // Connect FTP client
875
+ AsyncFunction("ftpClientConnect") { (clientId: String, config: [String: Any], promise: Promise) in
876
+ guard let ftpClient = self.ftpClients[clientId] else {
877
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
878
+ return
879
+ }
880
+
851
881
  Task {
852
882
  do {
853
883
  let ftpConfig = FtpConnectionConfig(
@@ -859,30 +889,60 @@ public class ReactNativeKookitModule: Module {
859
889
  timeout: config["timeout"] as? TimeInterval ?? 30.0
860
890
  )
861
891
 
862
- self.ftpClient = FtpClient()
863
- let progressDelegate = FtpProgressDelegateImpl(module: self)
864
- self.ftpClient?.setProgressDelegate(progressDelegate)
865
-
866
- try await self.ftpClient?.connect(config: ftpConfig)
867
- promise.resolve()
892
+ try await ftpClient.connect(config: ftpConfig)
893
+ promise.resolve([
894
+ "clientId": clientId,
895
+ "connected": true
896
+ ])
868
897
  } catch {
869
898
  promise.reject("FTP_CONNECT_ERROR", error.localizedDescription)
870
899
  }
871
900
  }
872
901
  }
873
902
 
874
- AsyncFunction("ftpDisconnect") { (promise: Promise) in
903
+ // Disconnect FTP client
904
+ AsyncFunction("ftpClientDisconnect") { (clientId: String, promise: Promise) in
905
+ guard let ftpClient = self.ftpClients[clientId] else {
906
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
907
+ return
908
+ }
909
+
875
910
  Task {
876
- await self.ftpClient?.disconnect()
877
- self.ftpClient = nil
878
- promise.resolve()
911
+ await ftpClient.disconnect()
912
+ promise.resolve([
913
+ "clientId": clientId,
914
+ "disconnected": true
915
+ ])
879
916
  }
880
917
  }
881
918
 
882
- AsyncFunction("ftpList") { (path: String?, promise: Promise) in
919
+ // Dispose FTP client
920
+ AsyncFunction("disposeFtpClient") { (clientId: String, promise: Promise) in
921
+ guard let ftpClient = self.ftpClients[clientId] else {
922
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
923
+ return
924
+ }
925
+
926
+ Task {
927
+ await ftpClient.disconnect()
928
+ self.ftpClients.removeValue(forKey: clientId)
929
+ promise.resolve([
930
+ "clientId": clientId,
931
+ "disposed": true
932
+ ])
933
+ }
934
+ }
935
+
936
+ // List files
937
+ AsyncFunction("ftpClientList") { (clientId: String, path: String?, promise: Promise) in
938
+ guard let ftpClient = self.ftpClients[clientId] else {
939
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
940
+ return
941
+ }
942
+
883
943
  Task {
884
944
  do {
885
- let files = try await self.ftpClient?.listFiles(path: path) ?? []
945
+ let files = try await ftpClient.listFiles(path: path)
886
946
  let result = files.map { file in
887
947
  [
888
948
  "name": file.name,
@@ -899,72 +959,163 @@ public class ReactNativeKookitModule: Module {
899
959
  }
900
960
  }
901
961
 
902
- AsyncFunction("ftpDownload") { (remotePath: String, localPath: String, promise: Promise) in
962
+ // Download file
963
+ AsyncFunction("ftpClientDownload") { (clientId: String, remotePath: String, localPath: String, promise: Promise) in
964
+ guard let ftpClient = self.ftpClients[clientId] else {
965
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
966
+ return
967
+ }
968
+
903
969
  Task {
904
970
  do {
905
- try await self.ftpClient?.downloadFile(remotePath: remotePath, localPath: localPath)
906
- promise.resolve()
971
+ try await ftpClient.downloadFile(remotePath: remotePath, localPath: localPath)
972
+ promise.resolve([
973
+ "clientId": clientId,
974
+ "remotePath": remotePath,
975
+ "localPath": localPath,
976
+ "downloaded": true
977
+ ])
907
978
  } catch {
908
979
  promise.reject("FTP_DOWNLOAD_ERROR", error.localizedDescription)
909
980
  }
910
981
  }
911
982
  }
912
983
 
913
- AsyncFunction("ftpUpload") { (localPath: String, remotePath: String, promise: Promise) in
984
+ // Upload file
985
+ AsyncFunction("ftpClientUpload") { (clientId: String, localPath: String, remotePath: String, promise: Promise) in
986
+ guard let ftpClient = self.ftpClients[clientId] else {
987
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
988
+ return
989
+ }
990
+
914
991
  Task {
915
992
  do {
916
- try await self.ftpClient?.uploadFile(localPath: localPath, remotePath: remotePath)
917
- promise.resolve()
993
+ try await ftpClient.uploadFile(localPath: localPath, remotePath: remotePath)
994
+ promise.resolve([
995
+ "clientId": clientId,
996
+ "localPath": localPath,
997
+ "remotePath": remotePath,
998
+ "uploaded": true
999
+ ])
918
1000
  } catch {
919
1001
  promise.reject("FTP_UPLOAD_ERROR", error.localizedDescription)
920
1002
  }
921
1003
  }
922
1004
  }
923
1005
 
924
- AsyncFunction("ftpDelete") { (remotePath: String, isDirectory: Bool?, promise: Promise) in
1006
+ // Delete file
1007
+ AsyncFunction("ftpClientDelete") { (clientId: String, remotePath: String, isDirectory: Bool?, promise: Promise) in
1008
+ guard let ftpClient = self.ftpClients[clientId] else {
1009
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
1010
+ return
1011
+ }
1012
+
925
1013
  Task {
926
1014
  do {
927
- try await self.ftpClient?.deleteFile(remotePath: remotePath, isDirectory: isDirectory ?? false)
928
- promise.resolve()
1015
+ try await ftpClient.deleteFile(remotePath: remotePath, isDirectory: isDirectory ?? false)
1016
+ promise.resolve([
1017
+ "clientId": clientId,
1018
+ "remotePath": remotePath,
1019
+ "deleted": true
1020
+ ])
929
1021
  } catch {
930
1022
  promise.reject("FTP_DELETE_ERROR", error.localizedDescription)
931
1023
  }
932
1024
  }
933
1025
  }
934
1026
 
935
- AsyncFunction("ftpCreateDirectory") { (remotePath: String, promise: Promise) in
1027
+ // Create directory
1028
+ AsyncFunction("ftpClientCreateDirectory") { (clientId: String, remotePath: String, promise: Promise) in
1029
+ guard let ftpClient = self.ftpClients[clientId] else {
1030
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
1031
+ return
1032
+ }
1033
+
936
1034
  Task {
937
1035
  do {
938
- try await self.ftpClient?.createDirectory(remotePath: remotePath)
939
- promise.resolve()
1036
+ try await ftpClient.createDirectory(remotePath: remotePath)
1037
+ promise.resolve([
1038
+ "clientId": clientId,
1039
+ "remotePath": remotePath,
1040
+ "created": true
1041
+ ])
940
1042
  } catch {
941
1043
  promise.reject("FTP_CREATE_DIR_ERROR", error.localizedDescription)
942
1044
  }
943
1045
  }
944
1046
  }
945
1047
 
946
- AsyncFunction("ftpChangeDirectory") { (remotePath: String, promise: Promise) in
1048
+ // Change directory
1049
+ AsyncFunction("ftpClientChangeDirectory") { (clientId: String, remotePath: String, promise: Promise) in
1050
+ guard let ftpClient = self.ftpClients[clientId] else {
1051
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
1052
+ return
1053
+ }
1054
+
947
1055
  Task {
948
1056
  do {
949
- try await self.ftpClient?.changeDirectory(remotePath: remotePath)
950
- promise.resolve()
1057
+ try await ftpClient.changeDirectory(remotePath: remotePath)
1058
+ promise.resolve([
1059
+ "clientId": clientId,
1060
+ "currentDirectory": remotePath,
1061
+ "changed": true
1062
+ ])
951
1063
  } catch {
952
1064
  promise.reject("FTP_CHANGE_DIR_ERROR", error.localizedDescription)
953
1065
  }
954
1066
  }
955
1067
  }
956
1068
 
957
- AsyncFunction("ftpGetCurrentDirectory") { (promise: Promise) in
1069
+ // Get current directory
1070
+ AsyncFunction("ftpClientGetCurrentDirectory") { (clientId: String, promise: Promise) in
1071
+ guard let ftpClient = self.ftpClients[clientId] else {
1072
+ promise.reject("FTP_CLIENT_NOT_FOUND", "FTP client with ID '\(clientId)' not found")
1073
+ return
1074
+ }
1075
+
958
1076
  Task {
959
1077
  do {
960
- let currentDir = try await self.ftpClient?.getCurrentDirectory() ?? "/"
961
- promise.resolve(currentDir)
1078
+ let currentDir = try await ftpClient.getCurrentDirectory()
1079
+ promise.resolve([
1080
+ "clientId": clientId,
1081
+ "currentDirectory": currentDir
1082
+ ])
962
1083
  } catch {
963
1084
  promise.reject("FTP_PWD_ERROR", error.localizedDescription)
964
1085
  }
965
1086
  }
966
1087
  }
967
1088
 
1089
+ // Get FTP client status
1090
+ AsyncFunction("getFtpClientStatus") { (clientId: String, promise: Promise) in
1091
+ guard let ftpClient = self.ftpClients[clientId] else {
1092
+ promise.resolve([
1093
+ "exists": false,
1094
+ "connected": false
1095
+ ])
1096
+ return
1097
+ }
1098
+
1099
+ promise.resolve([
1100
+ "exists": true,
1101
+ "connected": ftpClient.isConnected
1102
+ ])
1103
+ }
1104
+
1105
+ // List all FTP clients
1106
+ AsyncFunction("listFtpClients") { (promise: Promise) in
1107
+ let clientsInfo = self.ftpClients.mapValues { client in
1108
+ [
1109
+ "connected": client.isConnected
1110
+ ]
1111
+ }
1112
+
1113
+ promise.resolve([
1114
+ "clients": clientsInfo,
1115
+ "count": self.ftpClients.count
1116
+ ])
1117
+ }
1118
+
968
1119
  // Enables the module to be used as a native view. Definition components that are accepted as part of
969
1120
  // the view definition: Prop, Events.
970
1121
  View(ReactNativeKookitView.self) {
@@ -1063,14 +1214,17 @@ public class ReactNativeKookitModule: Module {
1063
1214
  // Helper class to handle FTP progress callbacks
1064
1215
  private class FtpProgressDelegateImpl: FtpProgressDelegate {
1065
1216
  weak var module: ReactNativeKookitModule?
1217
+ let clientId: String
1066
1218
 
1067
- init(module: ReactNativeKookitModule) {
1219
+ init(module: ReactNativeKookitModule, clientId: String) {
1068
1220
  self.module = module
1221
+ self.clientId = clientId
1069
1222
  }
1070
1223
 
1071
1224
  func onProgress(transferred: Int64, total: Int64) {
1072
1225
  let percentage = total > 0 ? Int((transferred * 100) / total) : 0
1073
1226
  module?.sendEvent("onFtpProgress", [
1227
+ "clientId": clientId,
1074
1228
  "transferred": transferred,
1075
1229
  "total": total,
1076
1230
  "percentage": percentage
@@ -1078,10 +1232,15 @@ private class FtpProgressDelegateImpl: FtpProgressDelegate {
1078
1232
  }
1079
1233
 
1080
1234
  func onComplete() {
1081
- module?.sendEvent("onFtpComplete")
1235
+ module?.sendEvent("onFtpComplete", [
1236
+ "clientId": clientId
1237
+ ])
1082
1238
  }
1083
1239
 
1084
1240
  func onError(error: String) {
1085
- module?.sendEvent("onFtpError", ["error": error])
1241
+ module?.sendEvent("onFtpError", [
1242
+ "clientId": clientId,
1243
+ "error": error
1244
+ ])
1086
1245
  }
1087
1246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-kookit",
3
- "version": "0.2.4",
3
+ "version": "0.2.7",
4
4
  "description": "React Native module for intercepting volume button presses on iOS and Android, with FTP client functionality",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",