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.
- package/android/src/main/java/expo/modules/kookit/FtpClient.kt +56 -11
- package/android/src/main/java/expo/modules/kookit/ReactNativeKookitModule.kt +17 -0
- package/build/FtpClient.d.ts +5 -0
- package/build/FtpClient.d.ts.map +1 -1
- package/build/FtpClient.js +15 -0
- package/build/FtpClient.js.map +1 -1
- package/ios/ReactNativeKookitModule.swift +67 -36
- package/package.json +1 -1
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ?:
|
|
369
|
-
connect(InetSocketAddress(host, port), config?.timeout ?:
|
|
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 {
|
package/build/FtpClient.d.ts
CHANGED
|
@@ -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
|
*/
|
package/build/FtpClient.d.ts.map
CHANGED
|
@@ -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"}
|
package/build/FtpClient.js
CHANGED
|
@@ -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
|
*/
|
package/build/FtpClient.js.map
CHANGED
|
@@ -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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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