react-native-kookit 0.3.2 → 0.3.3

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.
@@ -14,7 +14,7 @@ data class FtpConnectionConfig(
14
14
  val username: String,
15
15
  val password: String,
16
16
  val passive: Boolean = true,
17
- val timeout: Int = 30000
17
+ val timeout: Int = 0
18
18
  )
19
19
 
20
20
  data class FtpFileInfo(
@@ -39,7 +39,33 @@ class FtpClient {
39
39
  private var config: FtpConnectionConfig? = null
40
40
  private var useUtf8 = false
41
41
 
42
- fun isConnected(): Boolean = isConnected
42
+ fun isConnected(): Boolean = isConnected && isControlSocketAlive()
43
+
44
+ private fun isControlSocketAlive(): Boolean {
45
+ return try {
46
+ controlSocket?.let { socket ->
47
+ !socket.isClosed && socket.isConnected && !socket.isInputShutdown && !socket.isOutputShutdown
48
+ } ?: false
49
+ } catch (e: Exception) {
50
+ false
51
+ }
52
+ }
53
+
54
+ @Throws(Exception::class)
55
+ suspend fun verifyConnection(): Boolean = withContext(Dispatchers.IO) {
56
+ if (!isConnected) return@withContext false
57
+
58
+ try {
59
+ // Try to send NOOP command to verify connection
60
+ sendCommand("NOOP")
61
+ val response = readResponse()
62
+ response.startsWith("200") || response.startsWith("250")
63
+ } catch (e: Exception) {
64
+ // Connection is dead, mark as disconnected
65
+ isConnected = false
66
+ false
67
+ }
68
+ }
43
69
 
44
70
  @Throws(Exception::class)
45
71
  suspend fun connect(config: FtpConnectionConfig) = withContext(Dispatchers.IO) {
@@ -128,7 +154,7 @@ class FtpClient {
128
154
 
129
155
  @Throws(Exception::class)
130
156
  suspend fun listFiles(path: String? = null): List<FtpFileInfo> = withContext(Dispatchers.IO) {
131
- if (!isConnected) throw Exception("Not connected to FTP server")
157
+ ensureConnected()
132
158
 
133
159
  val dataSocket = enterPassiveMode()
134
160
 
@@ -171,7 +197,7 @@ class FtpClient {
171
197
 
172
198
  @Throws(Exception::class)
173
199
  suspend fun downloadFile(remotePath: String, localPath: String, listener: FtpProgressListener? = null) = withContext(Dispatchers.IO) {
174
- if (!isConnected) throw Exception("Not connected to FTP server")
200
+ ensureConnected()
175
201
 
176
202
  val dataSocket = enterPassiveMode()
177
203
  val localFile = File(localPath)
@@ -223,7 +249,7 @@ class FtpClient {
223
249
 
224
250
  @Throws(Exception::class)
225
251
  suspend fun uploadFile(localPath: String, remotePath: String, listener: FtpProgressListener? = null) = withContext(Dispatchers.IO) {
226
- if (!isConnected) throw Exception("Not connected to FTP server")
252
+ ensureConnected()
227
253
 
228
254
  val localFile = File(localPath)
229
255
  if (!localFile.exists()) {
@@ -276,7 +302,7 @@ class FtpClient {
276
302
 
277
303
  @Throws(Exception::class)
278
304
  suspend fun deleteFile(remotePath: String, isDirectory: Boolean = false) = withContext(Dispatchers.IO) {
279
- if (!isConnected) throw Exception("Not connected to FTP server")
305
+ ensureConnected()
280
306
 
281
307
  val command = if (isDirectory) "RMD $remotePath" else "DELE $remotePath"
282
308
  sendCommand(command)
@@ -288,7 +314,7 @@ class FtpClient {
288
314
 
289
315
  @Throws(Exception::class)
290
316
  suspend fun createDirectory(remotePath: String) = withContext(Dispatchers.IO) {
291
- if (!isConnected) throw Exception("Not connected to FTP server")
317
+ ensureConnected()
292
318
 
293
319
  sendCommand("MKD $remotePath")
294
320
  val response = readResponse()
@@ -299,7 +325,7 @@ class FtpClient {
299
325
 
300
326
  @Throws(Exception::class)
301
327
  suspend fun changeDirectory(remotePath: String) = withContext(Dispatchers.IO) {
302
- if (!isConnected) throw Exception("Not connected to FTP server")
328
+ ensureConnected()
303
329
 
304
330
  sendCommand("CWD $remotePath")
305
331
  val response = readResponse()
@@ -310,7 +336,7 @@ class FtpClient {
310
336
 
311
337
  @Throws(Exception::class)
312
338
  suspend fun getCurrentDirectory(): String = withContext(Dispatchers.IO) {
313
- if (!isConnected) throw Exception("Not connected to FTP server")
339
+ ensureConnected()
314
340
 
315
341
  sendCommand("PWD")
316
342
  val response = readResponse()
@@ -347,6 +373,25 @@ class FtpClient {
347
373
  }
348
374
 
349
375
  @Throws(Exception::class)
376
+ private suspend fun ensureConnected() {
377
+ if (!isConnected) {
378
+ throw Exception("Not connected to FTP server")
379
+ }
380
+
381
+ // Verify the connection is still alive
382
+ if (!verifyConnection()) {
383
+ // Try to reconnect
384
+ config?.let { cfg ->
385
+ try {
386
+ disconnect() // Clean up the dead connection
387
+ connect(cfg) // Reconnect
388
+ } catch (e: Exception) {
389
+ throw Exception("Connection lost and reconnection failed: ${e.message}")
390
+ }
391
+ } ?: throw Exception("Connection lost and no config available for reconnection")
392
+ }
393
+ }
394
+
350
395
  private fun enterPassiveMode(): Socket {
351
396
  sendCommand("PASV")
352
397
  val response = readResponse()
@@ -365,8 +410,8 @@ class FtpClient {
365
410
  val port = matcher.group(5)!!.toInt() * 256 + matcher.group(6)!!.toInt()
366
411
 
367
412
  return Socket().apply {
368
- soTimeout = config?.timeout ?: 30000
369
- connect(InetSocketAddress(host, port), config?.timeout ?: 30000)
413
+ soTimeout = config?.timeout ?: 0
414
+ connect(InetSocketAddress(host, port), config?.timeout ?: 0)
370
415
  }
371
416
  }
372
417
 
@@ -136,6 +136,23 @@ class ReactNativeKookitModule : Module() {
136
136
  }
137
137
  }
138
138
 
139
+ AsyncFunction("verifyFtpConnection") { clientId: String, promise: Promise ->
140
+ moduleScope.launch {
141
+ try {
142
+ val client = ftpClients[clientId]
143
+ if (client == null) {
144
+ promise.resolve(false)
145
+ return@launch
146
+ }
147
+
148
+ val isConnected = client.verifyConnection()
149
+ promise.resolve(isConnected)
150
+ } catch (e: Exception) {
151
+ promise.reject("FTP_VERIFY_CONNECTION_ERROR", e.message, e)
152
+ }
153
+ }
154
+ }
155
+
139
156
  AsyncFunction("listFtpClients") { promise: Promise ->
140
157
  moduleScope.launch {
141
158
  try {
@@ -76,6 +76,11 @@ export declare class FtpClient {
76
76
  * Check if the client is connected
77
77
  */
78
78
  isConnected(): Promise<boolean>;
79
+ /**
80
+ * Verify the actual FTP connection by testing communication with server
81
+ * This will attempt to send a NOOP command to verify the connection is alive
82
+ */
83
+ verifyConnection(): Promise<boolean>;
79
84
  /**
80
85
  * Dispose the FTP client and clean up resources
81
86
  */
@@ -1 +1 @@
1
- {"version":3,"file":"FtpClient.d.ts","sourceRoot":"","sources":["../src/FtpClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,WAAW,EAIZ,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,KAAK,IAAI,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,kBAAkB,CAAqC;gBAEnD,QAAQ,CAAC,EAAE,MAAM;IAO7B,OAAO,CAAC,mBAAmB;IAoC3B;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAIxD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IASzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC;;OAEG;IACG,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAQjD;;OAEG;IACG,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpE;;OAEG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlE;;OAEG;IACG,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,OAAe,GAC3B,OAAO,CAAC,IAAI,CAAC;IAYhB;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxD;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxD;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAW5C;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IAUF;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAarC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B;;OAEG;WACU,WAAW,IAAI,OAAO,CAAC;QAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,SAAS,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAChD,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IAIF;;OAEG;WACU,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAK3D"}
1
+ {"version":3,"file":"FtpClient.d.ts","sourceRoot":"","sources":["../src/FtpClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,WAAW,EAIZ,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,KAAK,IAAI,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,kBAAkB,CAAqC;gBAEnD,QAAQ,CAAC,EAAE,MAAM;IAO7B,OAAO,CAAC,mBAAmB;IAoC3B;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAIxD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IASzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC;;OAEG;IACG,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAQjD;;OAEG;IACG,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpE;;OAEG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlE;;OAEG;IACG,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,OAAe,GAC3B,OAAO,CAAC,IAAI,CAAC;IAYhB;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxD;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxD;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAW5C;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IAUF;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAarC;;;OAGG;IACG,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAY1C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B;;OAEG;WACU,WAAW,IAAI,OAAO,CAAC;QAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,SAAS,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAChD,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IAIF;;OAEG;WACU,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAK3D"}
@@ -163,6 +163,21 @@ export class FtpClient {
163
163
  return false;
164
164
  }
165
165
  }
166
+ /**
167
+ * Verify the actual FTP connection by testing communication with server
168
+ * This will attempt to send a NOOP command to verify the connection is alive
169
+ */
170
+ async verifyConnection() {
171
+ if (this.isDisposed) {
172
+ return false;
173
+ }
174
+ try {
175
+ return await ReactNativeKookitModule.verifyFtpConnection(this.clientId);
176
+ }
177
+ catch {
178
+ return false;
179
+ }
180
+ }
166
181
  /**
167
182
  * Dispose the FTP client and clean up resources
168
183
  */
@@ -1 +1 @@
1
- {"version":3,"file":"FtpClient.js","sourceRoot":"","sources":["../src/FtpClient.ts"],"names":[],"mappings":"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","sourcesContent":["import ReactNativeKookitModule from \"./ReactNativeKookitModule\";\nimport type {\n FtpConnectionConfig,\n FtpFileInfo,\n FtpProgressEvent,\n FtpCompleteEvent,\n FtpErrorEvent,\n} from \"./ReactNativeKookit.types\";\n\nexport interface FtpClientEventHandlers {\n onProgress?: (progress: {\n transferred: number;\n total: number;\n percentage: number;\n }) => void;\n onComplete?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport class FtpClient {\n private clientId: string;\n private isDisposed: boolean = false;\n private eventHandlers: FtpClientEventHandlers = {};\n private eventSubscriptions: Array<{ remove: () => void }> = [];\n\n constructor(clientId?: string) {\n this.clientId =\n clientId ||\n `ftp_client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n this.setupEventListeners();\n }\n\n private setupEventListeners() {\n // Listen to native events and filter by clientId\n const progressSub = ReactNativeKookitModule.addListener(\n \"onFtpProgress\",\n (event: FtpProgressEvent) => {\n if (event.clientId === this.clientId && this.eventHandlers.onProgress) {\n this.eventHandlers.onProgress({\n transferred: event.transferred,\n total: event.total,\n percentage: event.percentage,\n });\n }\n }\n );\n\n const completeSub = ReactNativeKookitModule.addListener(\n \"onFtpComplete\",\n (event: FtpCompleteEvent) => {\n if (event.clientId === this.clientId && this.eventHandlers.onComplete) {\n this.eventHandlers.onComplete();\n }\n }\n );\n\n const errorSub = ReactNativeKookitModule.addListener(\n \"onFtpError\",\n (event: FtpErrorEvent) => {\n if (event.clientId === this.clientId && this.eventHandlers.onError) {\n this.eventHandlers.onError(new Error(event.error));\n }\n }\n );\n\n this.eventSubscriptions = [progressSub, completeSub, errorSub];\n }\n\n /**\n * Set event handlers\n */\n setEventHandlers(handlers: FtpClientEventHandlers): void {\n this.eventHandlers = { ...handlers };\n }\n\n /**\n * Initialize the FTP client instance\n */\n async initialize(): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.createFtpClient(this.clientId);\n }\n\n /**\n * Connect to the FTP server\n */\n async connect(config: FtpConnectionConfig): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n console.log(\"Connecting with config:\", this.clientId, config);\n\n await ReactNativeKookitModule.ftpClientConnect(this.clientId, config);\n }\n\n /**\n * Disconnect from the FTP server\n */\n async disconnect(): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientDisconnect(this.clientId);\n }\n\n /**\n * List files in the specified directory\n */\n async list(path?: string): Promise<FtpFileInfo[]> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n return await ReactNativeKookitModule.ftpClientList(this.clientId, path);\n }\n\n /**\n * Download a file from the remote server\n */\n async download(remotePath: string, localPath: string): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientDownload(\n this.clientId,\n remotePath,\n localPath\n );\n }\n\n /**\n * Upload a file to the remote server\n */\n async upload(localPath: string, remotePath: string): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientUpload(\n this.clientId,\n localPath,\n remotePath\n );\n }\n\n /**\n * Delete a file or directory on the remote server\n */\n async delete(\n remotePath: string,\n isDirectory: boolean = false\n ): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientDelete(\n this.clientId,\n remotePath,\n isDirectory\n );\n }\n\n /**\n * Create a directory on the remote server\n */\n async createDirectory(remotePath: string): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientCreateDirectory(\n this.clientId,\n remotePath\n );\n }\n\n /**\n * Change the current working directory on the remote server\n */\n async changeDirectory(remotePath: string): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientChangeDirectory(\n this.clientId,\n remotePath\n );\n }\n\n /**\n * Get the current working directory on the remote server\n */\n async getCurrentDirectory(): Promise<string> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n const result = await ReactNativeKookitModule.ftpClientGetCurrentDirectory(\n this.clientId\n );\n return result;\n }\n\n /**\n * Get client status information\n * @returns Client status including existence and connection state\n */\n async getStatus(): Promise<{\n clientId: string;\n exists: boolean;\n connected: boolean;\n }> {\n const status = await ReactNativeKookitModule.getFtpClientStatus(\n this.clientId\n );\n return {\n clientId: this.clientId,\n ...status,\n };\n }\n\n /**\n * Get the client ID\n */\n getClientId(): string {\n return this.clientId;\n }\n\n /**\n * Check if the client is connected\n */\n async isConnected(): Promise<boolean> {\n if (this.isDisposed) {\n return false;\n }\n\n try {\n const status = await this.getStatus();\n return status.exists && status.connected;\n } catch {\n return false;\n }\n }\n\n /**\n * Dispose the FTP client and clean up resources\n */\n async dispose(): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n try {\n await ReactNativeKookitModule.disposeFtpClient(this.clientId);\n } catch (error) {\n // Ignore errors during disposal\n }\n\n // Remove event listeners\n this.eventSubscriptions.forEach((sub) => sub.remove());\n this.eventSubscriptions = [];\n\n this.isDisposed = true;\n }\n\n /**\n * Static method to list all FTP clients\n */\n static async listClients(): Promise<{\n clients: Record<string, { connected: boolean }>;\n count: number;\n }> {\n return ReactNativeKookitModule.listFtpClients();\n }\n\n /**\n * Static method to create and initialize a new FTP client\n */\n static async create(clientId?: string): Promise<FtpClient> {\n const client = new FtpClient(clientId);\n await client.initialize();\n return client;\n }\n}\n"]}
1
+ {"version":3,"file":"FtpClient.js","sourceRoot":"","sources":["../src/FtpClient.ts"],"names":[],"mappings":"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","sourcesContent":["import ReactNativeKookitModule from \"./ReactNativeKookitModule\";\nimport type {\n FtpConnectionConfig,\n FtpFileInfo,\n FtpProgressEvent,\n FtpCompleteEvent,\n FtpErrorEvent,\n} from \"./ReactNativeKookit.types\";\n\nexport interface FtpClientEventHandlers {\n onProgress?: (progress: {\n transferred: number;\n total: number;\n percentage: number;\n }) => void;\n onComplete?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport class FtpClient {\n private clientId: string;\n private isDisposed: boolean = false;\n private eventHandlers: FtpClientEventHandlers = {};\n private eventSubscriptions: Array<{ remove: () => void }> = [];\n\n constructor(clientId?: string) {\n this.clientId =\n clientId ||\n `ftp_client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n this.setupEventListeners();\n }\n\n private setupEventListeners() {\n // Listen to native events and filter by clientId\n const progressSub = ReactNativeKookitModule.addListener(\n \"onFtpProgress\",\n (event: FtpProgressEvent) => {\n if (event.clientId === this.clientId && this.eventHandlers.onProgress) {\n this.eventHandlers.onProgress({\n transferred: event.transferred,\n total: event.total,\n percentage: event.percentage,\n });\n }\n }\n );\n\n const completeSub = ReactNativeKookitModule.addListener(\n \"onFtpComplete\",\n (event: FtpCompleteEvent) => {\n if (event.clientId === this.clientId && this.eventHandlers.onComplete) {\n this.eventHandlers.onComplete();\n }\n }\n );\n\n const errorSub = ReactNativeKookitModule.addListener(\n \"onFtpError\",\n (event: FtpErrorEvent) => {\n if (event.clientId === this.clientId && this.eventHandlers.onError) {\n this.eventHandlers.onError(new Error(event.error));\n }\n }\n );\n\n this.eventSubscriptions = [progressSub, completeSub, errorSub];\n }\n\n /**\n * Set event handlers\n */\n setEventHandlers(handlers: FtpClientEventHandlers): void {\n this.eventHandlers = { ...handlers };\n }\n\n /**\n * Initialize the FTP client instance\n */\n async initialize(): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.createFtpClient(this.clientId);\n }\n\n /**\n * Connect to the FTP server\n */\n async connect(config: FtpConnectionConfig): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n console.log(\"Connecting with config:\", this.clientId, config);\n\n await ReactNativeKookitModule.ftpClientConnect(this.clientId, config);\n }\n\n /**\n * Disconnect from the FTP server\n */\n async disconnect(): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientDisconnect(this.clientId);\n }\n\n /**\n * List files in the specified directory\n */\n async list(path?: string): Promise<FtpFileInfo[]> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n return await ReactNativeKookitModule.ftpClientList(this.clientId, path);\n }\n\n /**\n * Download a file from the remote server\n */\n async download(remotePath: string, localPath: string): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientDownload(\n this.clientId,\n remotePath,\n localPath\n );\n }\n\n /**\n * Upload a file to the remote server\n */\n async upload(localPath: string, remotePath: string): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientUpload(\n this.clientId,\n localPath,\n remotePath\n );\n }\n\n /**\n * Delete a file or directory on the remote server\n */\n async delete(\n remotePath: string,\n isDirectory: boolean = false\n ): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientDelete(\n this.clientId,\n remotePath,\n isDirectory\n );\n }\n\n /**\n * Create a directory on the remote server\n */\n async createDirectory(remotePath: string): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientCreateDirectory(\n this.clientId,\n remotePath\n );\n }\n\n /**\n * Change the current working directory on the remote server\n */\n async changeDirectory(remotePath: string): Promise<void> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n await ReactNativeKookitModule.ftpClientChangeDirectory(\n this.clientId,\n remotePath\n );\n }\n\n /**\n * Get the current working directory on the remote server\n */\n async getCurrentDirectory(): Promise<string> {\n if (this.isDisposed) {\n throw new Error(\"FTP client has been disposed\");\n }\n\n const result = await ReactNativeKookitModule.ftpClientGetCurrentDirectory(\n this.clientId\n );\n return result;\n }\n\n /**\n * Get client status information\n * @returns Client status including existence and connection state\n */\n async getStatus(): Promise<{\n clientId: string;\n exists: boolean;\n connected: boolean;\n }> {\n const status = await ReactNativeKookitModule.getFtpClientStatus(\n this.clientId\n );\n return {\n clientId: this.clientId,\n ...status,\n };\n }\n\n /**\n * Get the client ID\n */\n getClientId(): string {\n return this.clientId;\n }\n\n /**\n * Check if the client is connected\n */\n async isConnected(): Promise<boolean> {\n if (this.isDisposed) {\n return false;\n }\n\n try {\n const status = await this.getStatus();\n return status.exists && status.connected;\n } catch {\n return false;\n }\n }\n\n /**\n * Verify the actual FTP connection by testing communication with server\n * This will attempt to send a NOOP command to verify the connection is alive\n */\n async verifyConnection(): Promise<boolean> {\n if (this.isDisposed) {\n return false;\n }\n\n try {\n return await ReactNativeKookitModule.verifyFtpConnection(this.clientId);\n } catch {\n return false;\n }\n }\n\n /**\n * Dispose the FTP client and clean up resources\n */\n async dispose(): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n try {\n await ReactNativeKookitModule.disposeFtpClient(this.clientId);\n } catch (error) {\n // Ignore errors during disposal\n }\n\n // Remove event listeners\n this.eventSubscriptions.forEach((sub) => sub.remove());\n this.eventSubscriptions = [];\n\n this.isDisposed = true;\n }\n\n /**\n * Static method to list all FTP clients\n */\n static async listClients(): Promise<{\n clients: Record<string, { connected: boolean }>;\n count: number;\n }> {\n return ReactNativeKookitModule.listFtpClients();\n }\n\n /**\n * Static method to create and initialize a new FTP client\n */\n static async create(clientId?: string): Promise<FtpClient> {\n const client = new FtpClient(clientId);\n await client.initialize();\n return client;\n }\n}\n"]}
@@ -165,36 +165,36 @@ private class FtpConnectionBox: @unchecked Sendable {
165
165
  do {
166
166
  try await self.ftpClient.performAuthentication()
167
167
  self.ftpClient.isConnected = true
168
- self.queue.sync {
169
- if !self.hasResumed {
170
- self.hasResumed = true
171
- self.continuation.resume()
172
- }
173
- }
168
+ // Resume on our serial queue to synchronize state, avoid sync deadlocks
169
+ self.queue.async {
170
+ if !self.hasResumed {
171
+ self.hasResumed = true
172
+ self.continuation.resume()
173
+ }
174
+ }
174
175
  } catch {
175
- self.queue.sync {
176
- if !self.hasResumed {
177
- self.hasResumed = true
178
- self.continuation.resume(throwing: error)
179
- }
180
- }
176
+ // Resume on our serial queue to synchronize state, avoid sync deadlocks
177
+ self.queue.async {
178
+ if !self.hasResumed {
179
+ self.hasResumed = true
180
+ self.continuation.resume(throwing: error)
181
+ }
182
+ }
181
183
  }
182
184
  }
183
185
  case .failed(let error):
184
186
  self.timeoutTask?.cancel()
185
- self.queue.sync {
186
- if !self.hasResumed {
187
- self.hasResumed = true
188
- self.continuation.resume(throwing: error)
189
- }
187
+ // We're already on self.queue due to the outer async; update inline
188
+ if !self.hasResumed {
189
+ self.hasResumed = true
190
+ self.continuation.resume(throwing: error)
190
191
  }
191
192
  case .cancelled:
192
193
  self.timeoutTask?.cancel()
193
- self.queue.sync {
194
- if !self.hasResumed {
195
- self.hasResumed = true
196
- self.continuation.resume(throwing: FtpError.connectionCancelled)
197
- }
194
+ // We're already on self.queue due to the outer async; update inline
195
+ if !self.hasResumed {
196
+ self.hasResumed = true
197
+ self.continuation.resume(throwing: FtpError.connectionCancelled)
198
198
  }
199
199
  default:
200
200
  break
@@ -203,12 +203,13 @@ private class FtpConnectionBox: @unchecked Sendable {
203
203
  }
204
204
 
205
205
  func handleTimeout() {
206
- queue.sync {
207
- if !hasResumed {
208
- hasResumed = true
209
- continuation.resume(throwing: FtpError.connectionTimeout)
210
- }
211
- }
206
+ // Schedule asynchronously to avoid any potential self-queue sync deadlocks
207
+ queue.async {
208
+ if !self.hasResumed {
209
+ self.hasResumed = true
210
+ self.continuation.resume(throwing: FtpError.connectionTimeout)
211
+ }
212
+ }
212
213
  }
213
214
  }
214
215
 
@@ -263,8 +264,38 @@ class FtpClient: @unchecked Sendable {
263
264
  isConnected = false
264
265
  }
265
266
 
267
+ private func ensureConnected() async throws {
268
+ if !isConnected {
269
+ throw FtpError.notConnected
270
+ }
271
+
272
+ // Verify connection is alive by sending a NOOP command
273
+ do {
274
+ let response = try await sendCommand("NOOP")
275
+ if !response.hasPrefix("200") && !response.hasPrefix("250") {
276
+ // Connection might be dead, try to reconnect
277
+ try await reconnect()
278
+ }
279
+ } catch {
280
+ // If NOOP fails, try to reconnect
281
+ try await reconnect()
282
+ }
283
+ }
284
+
285
+ private func reconnect() async throws {
286
+ guard let config = config else {
287
+ throw FtpError.notConnected
288
+ }
289
+
290
+ // Clean up dead connection
291
+ await disconnect()
292
+
293
+ // Reconnect with the same config
294
+ try await connect(config: config)
295
+ }
296
+
266
297
  func listFiles(path: String? = nil) async throws -> [FtpFileInfo] {
267
- guard isConnected else { throw FtpError.notConnected }
298
+ try await ensureConnected()
268
299
 
269
300
  // Set ASCII mode for directory listings
270
301
  print("FTP DEBUG: Setting ASCII mode for directory listing")
@@ -317,7 +348,7 @@ class FtpClient: @unchecked Sendable {
317
348
  }
318
349
 
319
350
  func downloadFile(remotePath: String, localPath: String) async throws {
320
- guard isConnected else { throw FtpError.notConnected }
351
+ try await ensureConnected()
321
352
 
322
353
  print("FTP DEBUG: Starting download - Remote: \(remotePath), Local: \(localPath)")
323
354
 
@@ -370,7 +401,7 @@ class FtpClient: @unchecked Sendable {
370
401
  }
371
402
 
372
403
  func uploadFile(localPath: String, remotePath: String) async throws {
373
- guard isConnected else { throw FtpError.notConnected }
404
+ try await ensureConnected()
374
405
 
375
406
  let localURL = URL(fileURLWithPath: localPath)
376
407
  guard FileManager.default.fileExists(atPath: localPath) else {
@@ -398,7 +429,7 @@ class FtpClient: @unchecked Sendable {
398
429
  }
399
430
 
400
431
  func deleteFile(remotePath: String, isDirectory: Bool = false) async throws {
401
- guard isConnected else { throw FtpError.notConnected }
432
+ try await ensureConnected()
402
433
 
403
434
  let command = isDirectory ? "RMD \(remotePath)" : "DELE \(remotePath)"
404
435
  let response = try await sendCommand(command)
@@ -409,7 +440,7 @@ class FtpClient: @unchecked Sendable {
409
440
  }
410
441
 
411
442
  func createDirectory(remotePath: String) async throws {
412
- guard isConnected else { throw FtpError.notConnected }
443
+ try await ensureConnected()
413
444
 
414
445
  let response = try await sendCommand("MKD \(remotePath)")
415
446
  guard response.hasPrefix("257") else {
@@ -418,7 +449,7 @@ class FtpClient: @unchecked Sendable {
418
449
  }
419
450
 
420
451
  func changeDirectory(remotePath: String) async throws {
421
- guard isConnected else { throw FtpError.notConnected }
452
+ try await ensureConnected()
422
453
 
423
454
  let response = try await sendCommand("CWD \(remotePath)")
424
455
  guard response.hasPrefix("250") else {
@@ -427,7 +458,7 @@ class FtpClient: @unchecked Sendable {
427
458
  }
428
459
 
429
460
  func getCurrentDirectory() async throws -> String {
430
- guard isConnected else { throw FtpError.notConnected }
461
+ try await ensureConnected()
431
462
 
432
463
  let response = try await sendCommand("PWD")
433
464
  guard response.hasPrefix("257") else {
@@ -1677,4 +1708,4 @@ private class SmbProgressDelegateImpl: SmbProgressDelegate {
1677
1708
  "error": error
1678
1709
  ])
1679
1710
  }
1680
- }
1711
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-kookit",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
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",