react-native-kookit 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SMB_USAGE.md ADDED
@@ -0,0 +1,275 @@
1
+ # SMB Client 完整功能使用指南
2
+
3
+ React Native Kookit 现在提供了完整的 SMB (Server Message Block) 客户端功能,支持 Android 和 iOS 平台。
4
+
5
+ ## 特性
6
+
7
+ ✅ **OOP 面向对象设计** - 与 FTP 客户端 API 一致的设计模式
8
+ ✅ **多客户端管理** - 支持同时创建和管理多个 SMB 连接
9
+ ✅ **完整文件操作** - 连接、列目录、下载、上传、删除、创建目录
10
+ ✅ **进度事件** - 下载/上传进度回调和完成/错误事件
11
+ ✅ **跨平台支持** - Android (SMBJ) 和 iOS (SMBClient) 原生实现
12
+
13
+ ## 基本用法
14
+
15
+ ### 1. 创建和连接
16
+
17
+ ```typescript
18
+ import { SmbClient } from "react-native-kookit";
19
+
20
+ // 创建 SMB 客户端实例
21
+ const client = await SmbClient.create();
22
+
23
+ // 设置事件监听器(可选)
24
+ client.setEventHandlers({
25
+ onProgress: (progress) => {
26
+ console.log(
27
+ `Progress: ${progress.percentage}% (${progress.transferred}/${progress.total})`
28
+ );
29
+ },
30
+ onComplete: () => {
31
+ console.log("操作完成");
32
+ },
33
+ onError: (error) => {
34
+ console.error("SMB 错误:", error.message);
35
+ },
36
+ });
37
+
38
+ // 连接到 SMB 服务器
39
+ await client.connect({
40
+ host: "192.168.1.100",
41
+ port: 445, // 可选,默认 445
42
+ username: "myuser",
43
+ password: "mypassword",
44
+ domain: "WORKGROUP", // 可选,Windows 域
45
+ share: "Public", // 可选,默认共享
46
+ timeout: 10000, // 可选,超时时间(毫秒)
47
+ });
48
+ ```
49
+
50
+ ### 2. 文件和目录操作
51
+
52
+ ```typescript
53
+ // 列出文件和目录
54
+ const files = await client.list("Documents");
55
+ files.forEach((file) => {
56
+ console.log(
57
+ `${file.isDirectory ? "📁" : "📄"} ${file.name} (${file.size} bytes)`
58
+ );
59
+ });
60
+
61
+ // 下载文件
62
+ await client.download(
63
+ "Documents/report.pdf", // 远程路径
64
+ "/storage/emulated/0/Download/report.pdf" // 本地路径
65
+ );
66
+
67
+ // 上传文件
68
+ await client.upload(
69
+ "/storage/emulated/0/Pictures/photo.jpg", // 本地路径
70
+ "Photos/photo.jpg" // 远程路径
71
+ );
72
+
73
+ // 创建目录
74
+ await client.createDirectory("NewFolder");
75
+
76
+ // 删除文件
77
+ await client.delete("OldFile.txt");
78
+
79
+ // 删除目录
80
+ await client.delete("OldFolder", true);
81
+ ```
82
+
83
+ ### 3. 连接管理
84
+
85
+ ```typescript
86
+ // 检查连接状态
87
+ const isConnected = await client.isConnected();
88
+
89
+ // 获取客户端状态
90
+ const status = await client.getStatus();
91
+ console.log(
92
+ `Client ${status.clientId}: exists=${status.exists}, connected=${status.connected}`
93
+ );
94
+
95
+ // 断开连接
96
+ await client.disconnect();
97
+
98
+ // 释放资源
99
+ await client.dispose();
100
+ ```
101
+
102
+ ### 4. 多客户端管理
103
+
104
+ ```typescript
105
+ // 创建多个客户端
106
+ const client1 = await SmbClient.create("server1");
107
+ const client2 = await SmbClient.create("server2");
108
+
109
+ // 分别连接到不同服务器
110
+ await client1.connect({
111
+ host: "192.168.1.100",
112
+ username: "user1",
113
+ password: "pass1",
114
+ share: "Share1",
115
+ });
116
+ await client2.connect({
117
+ host: "192.168.1.101",
118
+ username: "user2",
119
+ password: "pass2",
120
+ share: "Share2",
121
+ });
122
+
123
+ // 列出所有客户端
124
+ const allClients = await SmbClient.listClients();
125
+ console.log(`管理中的客户端数量: ${allClients.count}`);
126
+
127
+ // 清理资源
128
+ await client1.dispose();
129
+ await client2.dispose();
130
+ ```
131
+
132
+ ## React Native 组件示例
133
+
134
+ ```tsx
135
+ import React, { useState, useEffect } from "react";
136
+ import { View, Text, Button, FlatList } from "react-native";
137
+ import { SmbClient, SmbFileInfo } from "react-native-kookit";
138
+
139
+ export function SmbBrowser() {
140
+ const [client, setClient] = useState<SmbClient | null>(null);
141
+ const [files, setFiles] = useState<SmbFileInfo[]>([]);
142
+ const [currentPath, setCurrentPath] = useState("");
143
+
144
+ const connect = async () => {
145
+ const smbClient = await SmbClient.create();
146
+
147
+ smbClient.setEventHandlers({
148
+ onProgress: (p) => console.log(`进度: ${p.percentage}%`),
149
+ onComplete: () => console.log("操作完成"),
150
+ onError: (e) => console.error("SMB 错误:", e.message),
151
+ });
152
+
153
+ await smbClient.connect({
154
+ host: "192.168.1.100",
155
+ username: "user",
156
+ password: "password",
157
+ share: "Public",
158
+ });
159
+
160
+ setClient(smbClient);
161
+ await listFiles("");
162
+ };
163
+
164
+ const listFiles = async (path: string) => {
165
+ if (!client) return;
166
+ const fileList = await client.list(path);
167
+ setFiles(fileList);
168
+ setCurrentPath(path);
169
+ };
170
+
171
+ const downloadFile = async (fileName: string) => {
172
+ if (!client) return;
173
+ const remotePath = currentPath ? `${currentPath}/${fileName}` : fileName;
174
+ const localPath = `/storage/emulated/0/Download/${fileName}`;
175
+ await client.download(remotePath, localPath);
176
+ alert(`文件已下载到: ${localPath}`);
177
+ };
178
+
179
+ useEffect(() => {
180
+ return () => {
181
+ client?.dispose();
182
+ };
183
+ }, [client]);
184
+
185
+ return (
186
+ <View style={{ flex: 1, padding: 16 }}>
187
+ <Text style={{ fontSize: 18, marginBottom: 16 }}>SMB 文件浏览器</Text>
188
+
189
+ {!client ? (
190
+ <Button title="连接 SMB 服务器" onPress={connect} />
191
+ ) : (
192
+ <>
193
+ <Text>当前路径: /{currentPath}</Text>
194
+ <Button title="返回上级" onPress={() => listFiles("")} />
195
+
196
+ <FlatList
197
+ data={files}
198
+ keyExtractor={(item, index) => index.toString()}
199
+ renderItem={({ item }) => (
200
+ <View
201
+ style={{
202
+ padding: 8,
203
+ borderBottomWidth: 1,
204
+ borderColor: "#eee",
205
+ }}
206
+ >
207
+ <Text
208
+ style={{ fontWeight: item.isDirectory ? "bold" : "normal" }}
209
+ >
210
+ {item.isDirectory ? "📁" : "📄"} {item.name}
211
+ </Text>
212
+ <Text style={{ fontSize: 12, color: "#666" }}>
213
+ 大小: {item.size} | 修改时间: {item.lastModified}
214
+ </Text>
215
+ {!item.isDirectory && (
216
+ <Button
217
+ title="下载"
218
+ onPress={() => downloadFile(item.name)}
219
+ />
220
+ )}
221
+ </View>
222
+ )}
223
+ />
224
+ </>
225
+ )}
226
+ </View>
227
+ );
228
+ }
229
+ ```
230
+
231
+ ## 平台特定说明
232
+
233
+ ### Android
234
+
235
+ - 基于 **SMBJ** 库实现
236
+ - 支持完整的 SMB2/3 协议
237
+ - 包含文件上传/下载进度回调
238
+ - 自动处理认证和会话管理
239
+
240
+ ### iOS
241
+
242
+ - 基于 **SMBClient** (kishikawakatsumi) 库实现
243
+ - 支持 SMB2 协议
244
+ - 完整的文件操作和进度事件
245
+ - 自动处理连接和共享管理
246
+
247
+ ## 错误处理
248
+
249
+ ```typescript
250
+ try {
251
+ await client.connect(config);
252
+ } catch (error) {
253
+ if (error.message.includes("authentication")) {
254
+ console.error("认证失败,请检查用户名和密码");
255
+ } else if (error.message.includes("timeout")) {
256
+ console.error("连接超时,请检查网络和主机地址");
257
+ } else {
258
+ console.error("连接错误:", error.message);
259
+ }
260
+ }
261
+ ```
262
+
263
+ ## 性能和最佳实践
264
+
265
+ 1. **连接管理**: 不用时及时调用 `disconnect()` 和 `dispose()`
266
+ 2. **批量操作**: 对于多个文件操作,复用同一个客户端连接
267
+ 3. **进度监听**: 对大文件操作使用进度事件提升用户体验
268
+ 4. **错误处理**: 始终包装 SMB 操作在 try-catch 块中
269
+ 5. **内存管理**: 在组件卸载时清理客户端资源
270
+
271
+ ## 许可和依赖
272
+
273
+ - **Android**: SMBJ (Apache License 2.0)
274
+ - **iOS**: SMBClient (MIT License)
275
+ - 该模块遵循 MIT 许可证
@@ -41,3 +41,10 @@ android {
41
41
  abortOnError false
42
42
  }
43
43
  }
44
+
45
+ dependencies {
46
+ // SMB client library
47
+ implementation 'com.hierynomus:smbj:0.13.0'
48
+ // Suppress SLF4J binding warnings
49
+ implementation 'org.slf4j:slf4j-nop:2.0.13'
50
+ }
@@ -18,6 +18,7 @@ class ReactNativeKookitModule : Module() {
18
18
  private var originalStreamVolume: Int = 0
19
19
  private var ftpClient: FtpClient? = null
20
20
  private val ftpClients = mutableMapOf<String, FtpClient>()
21
+ private val smbClients = mutableMapOf<String, SmbClient>()
21
22
  private val moduleScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
22
23
 
23
24
  // Each module class must implement the definition function. The definition consists of components
@@ -35,7 +36,11 @@ class ReactNativeKookitModule : Module() {
35
36
  )
36
37
 
37
38
  // Defines event names that the module can send to JavaScript.
38
- Events("onChange", "onVolumeButtonPressed", "onFtpProgress", "onFtpComplete", "onFtpError")
39
+ Events(
40
+ "onChange", "onVolumeButtonPressed",
41
+ "onFtpProgress", "onFtpComplete", "onFtpError",
42
+ "onSmbProgress", "onSmbComplete", "onSmbError"
43
+ )
39
44
 
40
45
  // Function to enable volume key interception
41
46
  Function("enableVolumeKeyInterception") {
@@ -470,6 +475,224 @@ class ReactNativeKookitModule : Module() {
470
475
  }
471
476
  }
472
477
 
478
+ // SMB Client API
479
+ AsyncFunction("createSmbClient") { clientId: String, promise: Promise ->
480
+ moduleScope.launch {
481
+ try {
482
+ if (smbClients.containsKey(clientId)) {
483
+ promise.reject("SMB_CLIENT_EXISTS", "SMB client with ID '$clientId' already exists", null)
484
+ return@launch
485
+ }
486
+ smbClients[clientId] = SmbClient()
487
+ promise.resolve(mapOf("clientId" to clientId))
488
+ } catch (e: Exception) {
489
+ promise.reject("SMB_CREATE_CLIENT_ERROR", e.message, e)
490
+ }
491
+ }
492
+ }
493
+
494
+ AsyncFunction("disposeSmbClient") { clientId: String, promise: Promise ->
495
+ moduleScope.launch {
496
+ try {
497
+ smbClients[clientId]?.disconnect()
498
+ smbClients.remove(clientId)
499
+ promise.resolve(null)
500
+ } catch (e: Exception) {
501
+ promise.reject("SMB_DISPOSE_CLIENT_ERROR", e.message, e)
502
+ }
503
+ }
504
+ }
505
+
506
+ AsyncFunction("getSmbClientStatus") { clientId: String, promise: Promise ->
507
+ moduleScope.launch {
508
+ try {
509
+ val client = smbClients[clientId]
510
+ val status = mapOf(
511
+ "exists" to (client != null),
512
+ "connected" to (client?.isConnected() ?: false)
513
+ )
514
+ promise.resolve(status)
515
+ } catch (e: Exception) {
516
+ promise.reject("SMB_CLIENT_STATUS_ERROR", e.message, e)
517
+ }
518
+ }
519
+ }
520
+
521
+ AsyncFunction("listSmbClients") { promise: Promise ->
522
+ moduleScope.launch {
523
+ try {
524
+ val clients = mutableMapOf<String, Map<String, Any>>()
525
+ smbClients.forEach { (id, client) ->
526
+ clients[id] = mapOf("connected" to client.isConnected())
527
+ }
528
+ val result = mapOf(
529
+ "clients" to clients,
530
+ "count" to smbClients.size
531
+ )
532
+ promise.resolve(result)
533
+ } catch (e: Exception) {
534
+ promise.reject("SMB_LIST_CLIENTS_ERROR", e.message, e)
535
+ }
536
+ }
537
+ }
538
+
539
+ AsyncFunction("smbClientConnect") { clientId: String, config: Map<String, Any>, promise: Promise ->
540
+ moduleScope.launch(Dispatchers.IO) {
541
+ try {
542
+ val client = smbClients[clientId]
543
+ ?: throw Exception("SMB client with ID '$clientId' not found")
544
+ val cfg = SmbConnectionConfig(
545
+ host = config["host"] as String,
546
+ port = (config["port"] as? Double)?.toInt() ?: 445,
547
+ username = config["username"] as String,
548
+ password = config["password"] as String,
549
+ domain = config["domain"] as? String,
550
+ share = config["share"] as? String,
551
+ timeoutMs = (config["timeout"] as? Double)?.toInt() ?: 10000
552
+ )
553
+ client.connect(cfg)
554
+ // If share not provided in connect, allow later openShare via list/upload APIs
555
+ withContext(Dispatchers.Main) { promise.resolve(null) }
556
+ } catch (e: Exception) {
557
+ withContext(Dispatchers.Main) { promise.reject("SMB_CLIENT_CONNECT_ERROR", e.message, e) }
558
+ }
559
+ }
560
+ }
561
+
562
+ AsyncFunction("smbClientDisconnect") { clientId: String, promise: Promise ->
563
+ moduleScope.launch {
564
+ try {
565
+ val client = smbClients[clientId]
566
+ ?: throw Exception("SMB client with ID '$clientId' not found")
567
+ client.disconnect()
568
+ promise.resolve(null)
569
+ } catch (e: Exception) {
570
+ promise.reject("SMB_CLIENT_DISCONNECT_ERROR", e.message, e)
571
+ }
572
+ }
573
+ }
574
+
575
+ AsyncFunction("smbClientConnectShare") { clientId: String, shareName: String, promise: Promise ->
576
+ moduleScope.launch(Dispatchers.IO) {
577
+ try {
578
+ val client = smbClients[clientId]
579
+ ?: throw Exception("SMB client with ID '$clientId' not found")
580
+ client.openShare(shareName)
581
+ withContext(Dispatchers.Main) {
582
+ promise.resolve(mapOf(
583
+ "clientId" to clientId,
584
+ "share" to shareName,
585
+ "connected" to true
586
+ ))
587
+ }
588
+ } catch (e: Exception) {
589
+ withContext(Dispatchers.Main) {
590
+ promise.reject("SMB_CONNECT_SHARE_ERROR", e.message, e)
591
+ }
592
+ }
593
+ }
594
+ }
595
+
596
+ AsyncFunction("smbClientList") { clientId: String, path: String?, promise: Promise ->
597
+ moduleScope.launch(Dispatchers.IO) {
598
+ try {
599
+ val client = smbClients[clientId]
600
+ ?: throw Exception("SMB client with ID '$clientId' not found")
601
+ val items = client.list(path)
602
+ val mapped = items.map {
603
+ mapOf(
604
+ "name" to it.name,
605
+ "isDirectory" to it.isDirectory,
606
+ "size" to it.size,
607
+ "lastModified" to it.lastModified,
608
+ "attributes" to it.attributes
609
+ )
610
+ }
611
+ withContext(Dispatchers.Main) { promise.resolve(mapped) }
612
+ } catch (e: Exception) {
613
+ withContext(Dispatchers.Main) { promise.reject("SMB_CLIENT_LIST_ERROR", e.message, e) }
614
+ }
615
+ }
616
+ }
617
+
618
+ AsyncFunction("smbClientDownload") { clientId: String, remotePath: String, localPath: String, promise: Promise ->
619
+ moduleScope.launch(Dispatchers.IO) {
620
+ try {
621
+ val client = smbClients[clientId]
622
+ ?: throw Exception("SMB client with ID '$clientId' not found")
623
+ client.download(remotePath, localPath) { transferred, total ->
624
+ val percentage = if (total > 0) ((transferred * 100) / total).toInt() else 0
625
+ sendEvent("onSmbProgress", mapOf(
626
+ "clientId" to clientId,
627
+ "transferred" to transferred,
628
+ "total" to total,
629
+ "percentage" to percentage
630
+ ))
631
+ }
632
+ sendEvent("onSmbComplete", mapOf("clientId" to clientId))
633
+ withContext(Dispatchers.Main) { promise.resolve(null) }
634
+ } catch (e: Exception) {
635
+ sendEvent("onSmbError", mapOf(
636
+ "clientId" to clientId,
637
+ "error" to (e.message ?: "SMB download error")
638
+ ))
639
+ withContext(Dispatchers.Main) { promise.reject("SMB_CLIENT_DOWNLOAD_ERROR", e.message, e) }
640
+ }
641
+ }
642
+ }
643
+
644
+ AsyncFunction("smbClientUpload") { clientId: String, localPath: String, remotePath: String, promise: Promise ->
645
+ moduleScope.launch(Dispatchers.IO) {
646
+ try {
647
+ val client = smbClients[clientId]
648
+ ?: throw Exception("SMB client with ID '$clientId' not found")
649
+ client.upload(localPath, remotePath) { transferred, total ->
650
+ val percentage = if (total > 0) ((transferred * 100) / total).toInt() else 0
651
+ sendEvent("onSmbProgress", mapOf(
652
+ "clientId" to clientId,
653
+ "transferred" to transferred,
654
+ "total" to total,
655
+ "percentage" to percentage
656
+ ))
657
+ }
658
+ sendEvent("onSmbComplete", mapOf("clientId" to clientId))
659
+ withContext(Dispatchers.Main) { promise.resolve(null) }
660
+ } catch (e: Exception) {
661
+ sendEvent("onSmbError", mapOf(
662
+ "clientId" to clientId,
663
+ "error" to (e.message ?: "SMB upload error")
664
+ ))
665
+ withContext(Dispatchers.Main) { promise.reject("SMB_CLIENT_UPLOAD_ERROR", e.message, e) }
666
+ }
667
+ }
668
+ }
669
+
670
+ AsyncFunction("smbClientDelete") { clientId: String, remotePath: String, isDirectory: Boolean?, promise: Promise ->
671
+ moduleScope.launch(Dispatchers.IO) {
672
+ try {
673
+ val client = smbClients[clientId]
674
+ ?: throw Exception("SMB client with ID '$clientId' not found")
675
+ client.delete(remotePath, isDirectory ?: false)
676
+ withContext(Dispatchers.Main) { promise.resolve(null) }
677
+ } catch (e: Exception) {
678
+ withContext(Dispatchers.Main) { promise.reject("SMB_CLIENT_DELETE_ERROR", e.message, e) }
679
+ }
680
+ }
681
+ }
682
+
683
+ AsyncFunction("smbClientCreateDirectory") { clientId: String, remotePath: String, promise: Promise ->
684
+ moduleScope.launch(Dispatchers.IO) {
685
+ try {
686
+ val client = smbClients[clientId]
687
+ ?: throw Exception("SMB client with ID '$clientId' not found")
688
+ client.mkdir(remotePath)
689
+ withContext(Dispatchers.Main) { promise.resolve(null) }
690
+ } catch (e: Exception) {
691
+ withContext(Dispatchers.Main) { promise.reject("SMB_CLIENT_CREATE_DIR_ERROR", e.message, e) }
692
+ }
693
+ }
694
+ }
695
+
473
696
  // Enables the module to be used as a native view. Definition components that are accepted as part of
474
697
  // the view definition: Prop, Events.
475
698
  View(ReactNativeKookitView::class) {