boot-nemonic-printer 0.1.0

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.
Files changed (84) hide show
  1. package/.eslintrc.js +5 -0
  2. package/README.md +209 -0
  3. package/ai/example-ui-card-refactor.md +348 -0
  4. package/android/build.gradle +22 -0
  5. package/android/libs/NemonicSdk.aar +0 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/net/eventboot/nemonicprinter/BootNemonicPrinterModule.kt +509 -0
  8. package/build/BootNemonicPrinter.types.d.ts +44 -0
  9. package/build/BootNemonicPrinter.types.d.ts.map +1 -0
  10. package/build/BootNemonicPrinter.types.js +2 -0
  11. package/build/BootNemonicPrinter.types.js.map +1 -0
  12. package/build/BootNemonicPrinterModule.d.ts +36 -0
  13. package/build/BootNemonicPrinterModule.d.ts.map +1 -0
  14. package/build/BootNemonicPrinterModule.js +3 -0
  15. package/build/BootNemonicPrinterModule.js.map +1 -0
  16. package/build/constants/NBatteryStatus.d.ts +8 -0
  17. package/build/constants/NBatteryStatus.d.ts.map +1 -0
  18. package/build/constants/NBatteryStatus.js +7 -0
  19. package/build/constants/NBatteryStatus.js.map +1 -0
  20. package/build/constants/NCartridgeType.d.ts +35 -0
  21. package/build/constants/NCartridgeType.d.ts.map +1 -0
  22. package/build/constants/NCartridgeType.js +183 -0
  23. package/build/constants/NCartridgeType.js.map +1 -0
  24. package/build/constants/NConnectState.d.ts +8 -0
  25. package/build/constants/NConnectState.d.ts.map +1 -0
  26. package/build/constants/NConnectState.js +7 -0
  27. package/build/constants/NConnectState.js.map +1 -0
  28. package/build/constants/NPowerSaveMode.d.ts +6 -0
  29. package/build/constants/NPowerSaveMode.d.ts.map +1 -0
  30. package/build/constants/NPowerSaveMode.js +5 -0
  31. package/build/constants/NPowerSaveMode.js.map +1 -0
  32. package/build/constants/NPrintImageLength.d.ts +12 -0
  33. package/build/constants/NPrintImageLength.d.ts.map +1 -0
  34. package/build/constants/NPrintImageLength.js +12 -0
  35. package/build/constants/NPrintImageLength.js.map +1 -0
  36. package/build/constants/NPrintQuality.d.ts +7 -0
  37. package/build/constants/NPrintQuality.d.ts.map +1 -0
  38. package/build/constants/NPrintQuality.js +6 -0
  39. package/build/constants/NPrintQuality.js.map +1 -0
  40. package/build/constants/NPrinterStatus.d.ts +9 -0
  41. package/build/constants/NPrinterStatus.d.ts.map +1 -0
  42. package/build/constants/NPrinterStatus.js +8 -0
  43. package/build/constants/NPrinterStatus.js.map +1 -0
  44. package/build/constants/NPrinterType.d.ts +9 -0
  45. package/build/constants/NPrinterType.d.ts.map +1 -0
  46. package/build/constants/NPrinterType.js +8 -0
  47. package/build/constants/NPrinterType.js.map +1 -0
  48. package/build/constants/NResult.d.ts +51 -0
  49. package/build/constants/NResult.d.ts.map +1 -0
  50. package/build/constants/NResult.js +50 -0
  51. package/build/constants/NResult.js.map +1 -0
  52. package/build/index.d.ts +13 -0
  53. package/build/index.d.ts.map +1 -0
  54. package/build/index.js +13 -0
  55. package/build/index.js.map +1 -0
  56. package/expo-module.config.json +9 -0
  57. package/ios/BootNemonicPrinter.podspec +31 -0
  58. package/ios/BootNemonicPrinterModule.swift +257 -0
  59. package/ios/Frameworks/NemonicSdk.framework/Headers/NemonicSdk-Swift.h +797 -0
  60. package/ios/Frameworks/NemonicSdk.framework/Headers/NemonicSdk.h +18 -0
  61. package/ios/Frameworks/NemonicSdk.framework/Info.plist +0 -0
  62. package/ios/Frameworks/NemonicSdk.framework/Modules/NemonicSdk.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo +0 -0
  63. package/ios/Frameworks/NemonicSdk.framework/Modules/NemonicSdk.swiftmodule/arm64-apple-ios.abi.json +12244 -0
  64. package/ios/Frameworks/NemonicSdk.framework/Modules/NemonicSdk.swiftmodule/arm64-apple-ios.private.swiftinterface +356 -0
  65. package/ios/Frameworks/NemonicSdk.framework/Modules/NemonicSdk.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  66. package/ios/Frameworks/NemonicSdk.framework/Modules/NemonicSdk.swiftmodule/arm64-apple-ios.swiftinterface +356 -0
  67. package/ios/Frameworks/NemonicSdk.framework/Modules/NemonicSdk.swiftmodule/arm64-apple-ios.swiftmodule +0 -0
  68. package/ios/Frameworks/NemonicSdk.framework/Modules/module.modulemap +11 -0
  69. package/ios/Frameworks/NemonicSdk.framework/NemonicSdk +0 -0
  70. package/ios/Frameworks/NemonicSdk.framework/_CodeSignature/CodeResources +237 -0
  71. package/package.json +44 -0
  72. package/src/BootNemonicPrinter.types.ts +50 -0
  73. package/src/BootNemonicPrinterModule.ts +48 -0
  74. package/src/constants/NBatteryStatus.ts +8 -0
  75. package/src/constants/NCartridgeType.ts +181 -0
  76. package/src/constants/NConnectState.ts +8 -0
  77. package/src/constants/NPowerSaveMode.ts +6 -0
  78. package/src/constants/NPrintImageLength.ts +11 -0
  79. package/src/constants/NPrintQuality.ts +7 -0
  80. package/src/constants/NPrinterStatus.ts +9 -0
  81. package/src/constants/NPrinterType.ts +9 -0
  82. package/src/constants/NResult.ts +51 -0
  83. package/src/index.ts +13 -0
  84. package/tsconfig.json +9 -0
@@ -0,0 +1,509 @@
1
+ package net.eventboot.nemonicprinter
2
+
3
+ import android.app.PendingIntent
4
+ import android.content.BroadcastReceiver
5
+ import android.content.Context
6
+ import android.content.Intent
7
+ import android.content.IntentFilter
8
+ import android.graphics.Bitmap
9
+ import android.graphics.BitmapFactory
10
+ import android.hardware.usb.UsbDevice
11
+ import android.hardware.usb.UsbManager
12
+ import android.os.Build
13
+ import android.util.Log
14
+ import androidx.core.content.ContextCompat
15
+ import com.mangoslab.nemonicsdk.INPrinterControllerCallback
16
+ import com.mangoslab.nemonicsdk.INPrinterScanControllerCallback
17
+ import com.mangoslab.nemonicsdk.NPrintInfo
18
+ import com.mangoslab.nemonicsdk.NPrinter
19
+ import com.mangoslab.nemonicsdk.NPrinterController
20
+ import com.mangoslab.nemonicsdk.NPrinterScanController
21
+ import com.mangoslab.nemonicsdk.constants.NPrintQuality
22
+ import com.mangoslab.nemonicsdk.constants.NPrinterType
23
+ import com.mangoslab.nemonicsdk.constants.NResult
24
+ import com.mangoslab.nemonicsdk.constants.NSDKInfo
25
+ import expo.modules.kotlin.Promise
26
+ import expo.modules.kotlin.modules.Module
27
+ import expo.modules.kotlin.modules.ModuleDefinition
28
+ import java.net.URL
29
+
30
+ private const val ACTION_USB_PERMISSION = "net.eventboot.nemonicprinter.USB_PERMISSION"
31
+ private const val MAX_IMAGE_SIZE = 196_603
32
+ private const val EXCEED_MAX_SIZE = -900
33
+ private const val PRINT_TAG = "NemonicPrintTimer"
34
+ private const val USB_TAG = "NemonicUsbEnum"
35
+
36
+ class BootNemonicPrinterModule
37
+ : Module(),
38
+ INPrinterControllerCallback,
39
+ INPrinterScanControllerCallback {
40
+
41
+ private val ctx: Context get() = appContext.reactContext
42
+ ?: throw IllegalStateException("React context is null")
43
+
44
+ private val printerController: NPrinterController by lazy {
45
+ NPrinterController(ctx, this)
46
+ }
47
+ private val scanController: NPrinterScanController by lazy {
48
+ NPrinterScanController(ctx, this)
49
+ }
50
+
51
+ private var connectedPrinter: NPrinter? = null
52
+ private var pendingUsbPermissionPromise: Promise? = null
53
+ private var pendingUsbReceiver: BroadcastReceiver? = null
54
+ @Volatile private var printStartedAt: Long = 0L
55
+ @Volatile private var printSeq: Int = 0
56
+
57
+ override fun definition() = ModuleDefinition {
58
+ Name("BootNemonicPrinter")
59
+
60
+ Constant("MAX_IMAGE_SIZE") { MAX_IMAGE_SIZE }
61
+ Constant("SDK_VERSION") { NSDKInfo.BUNDLE_NAME }
62
+
63
+ Events("onDeviceFound", "onDisconnected", "onPrintProgress", "onPrintComplete", "onNativeLog")
64
+
65
+ // === BLE 스캔
66
+ AsyncFunction("startScan") { promise: Promise ->
67
+ try {
68
+ promise.resolve(scanController.startScan())
69
+ } catch (e: Throwable) {
70
+ emitNativeLog(USB_TAG, "E", "startScan threw: ${e.javaClass.simpleName}: ${e.message}")
71
+ promise.resolve(NResult.BLUETOOTH_UNKNOWN)
72
+ }
73
+ }
74
+ Function("stopScan") {
75
+ try {
76
+ scanController.stopScan()
77
+ } catch (e: Throwable) {
78
+ // BT가 꺼져있거나 BluetoothLeScanner 미초기화 시 NPE 발생할 수 있음.
79
+ // JS 흐름을 끊지 않도록 swallow + 이벤트로 알림.
80
+ emitNativeLog(USB_TAG, "W", "stopScan ignored: ${e.javaClass.simpleName}: ${e.message}")
81
+ }
82
+ }
83
+
84
+ // === USB 목록 / 권한
85
+ AsyncFunction("getUsbPrinterList") { promise: Promise ->
86
+ val list = enumerateUsbPrinters()
87
+ promise.resolve(list.map { it.toMap() })
88
+ }
89
+
90
+ // Nemonic SDK 의 printerController.requestUsbPermission() 은 내부적으로
91
+ // implicit Intent + FLAG_MUTABLE 로 PendingIntent 를 만들어서, Android 14+
92
+ // (targetSdk 34+) 정책 (IllegalArgumentException) 에 걸려 throw 함.
93
+ // → SDK 우회: Android UsbManager.requestPermission() 을 직접 호출하고
94
+ // PendingIntent 는 우리가 setPackage(...) + FLAG_IMMUTABLE 로 안전하게 생성.
95
+ AsyncFunction("requestUsbPermission") { params: Map<String, Any?>, promise: Promise ->
96
+ emitNativeLog(USB_TAG, "I", "▶ requestUsbPermission entered (type=${params["type"]}, name=${params["name"]})")
97
+ val usbManager = ctx.getSystemService(Context.USB_SERVICE) as? UsbManager
98
+ if (usbManager == null) {
99
+ emitNativeLog(USB_TAG, "E", " UsbManager null, resolve(false)")
100
+ promise.resolve(false)
101
+ return@AsyncFunction
102
+ }
103
+ val device = findUsbDevice(params)
104
+ if (device == null) {
105
+ emitNativeLog(USB_TAG, "W", " findUsbDevice → null, resolve(false)")
106
+ promise.resolve(false)
107
+ return@AsyncFunction
108
+ }
109
+ emitNativeLog(USB_TAG, "I", " findUsbDevice → ${device.deviceName}")
110
+
111
+ if (usbManager.hasPermission(device)) {
112
+ emitNativeLog(USB_TAG, "I", " → already granted (UsbManager.hasPermission), resolve(true) immediately")
113
+ promise.resolve(true)
114
+ return@AsyncFunction
115
+ }
116
+
117
+ pendingUsbPermissionPromise = promise
118
+ val receiver = object : BroadcastReceiver() {
119
+ override fun onReceive(c: Context, intent: Intent) {
120
+ emitNativeLog(USB_TAG, "I", " ✉ onReceive fired (action=${intent.action})")
121
+ if (intent.action != ACTION_USB_PERMISSION) return
122
+ try { ctx.unregisterReceiver(this) } catch (_: Exception) {}
123
+ pendingUsbReceiver = null
124
+
125
+ // 진단: intent 안의 모든 extras 덤프 (Android 14+ 에서 EXTRA_PERMISSION_GRANTED 가
126
+ // 잘못 false 로 오는 케이스가 있음 — 실제 상태는 hasPermission() 으로 재확인)
127
+ val extras = intent.extras
128
+ val extrasStr = extras?.keySet()?.joinToString(", ") { k -> "$k=${extras.get(k)}" } ?: "(none)"
129
+ emitNativeLog(USB_TAG, "I", " · extras: $extrasStr")
130
+ val extraDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
131
+ intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
132
+ else
133
+ @Suppress("DEPRECATION") intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) as? UsbDevice
134
+ emitNativeLog(USB_TAG, "I", " · extraDevice=${extraDevice?.deviceName} requestedDevice=${device.deviceName}")
135
+
136
+ val extraGranted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
137
+ val actualGranted = extraGranted || usbManager.hasPermission(device)
138
+ emitNativeLog(
139
+ USB_TAG, "I",
140
+ " ✓ broadcast extraGranted=$extraGranted hasPermission=${usbManager.hasPermission(device)} → resolve($actualGranted)"
141
+ )
142
+ pendingUsbPermissionPromise?.resolve(actualGranted)
143
+ pendingUsbPermissionPromise = null
144
+ }
145
+ }
146
+ pendingUsbReceiver = receiver
147
+ val rcvFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
148
+ ContextCompat.RECEIVER_NOT_EXPORTED else 0
149
+ ContextCompat.registerReceiver(ctx, receiver, IntentFilter(ACTION_USB_PERMISSION), rcvFlags)
150
+ emitNativeLog(USB_TAG, "I", " receiver registered (flag=${if (rcvFlags == 0) "0" else "NOT_EXPORTED"}, action=$ACTION_USB_PERMISSION)")
151
+
152
+ // 명시적 Intent (setPackage) + FLAG_IMMUTABLE — Android 14+ 보안 정책 준수.
153
+ val piIntent = Intent(ACTION_USB_PERMISSION).setPackage(ctx.packageName)
154
+ val piFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
155
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
156
+ else
157
+ PendingIntent.FLAG_UPDATE_CURRENT
158
+ val pendingIntent = PendingIntent.getBroadcast(ctx, 0, piIntent, piFlags)
159
+
160
+ emitNativeLog(USB_TAG, "I", " ▶ usbManager.requestPermission() calling …")
161
+ try {
162
+ usbManager.requestPermission(device, pendingIntent)
163
+ } catch (e: Throwable) {
164
+ emitNativeLog(USB_TAG, "E", " ✗ usbManager.requestPermission threw: ${e.javaClass.simpleName}: ${e.message}")
165
+ try { ctx.unregisterReceiver(receiver) } catch (_: Exception) {}
166
+ pendingUsbReceiver = null
167
+ pendingUsbPermissionPromise = null
168
+ promise.resolve(false)
169
+ return@AsyncFunction
170
+ }
171
+ emitNativeLog(USB_TAG, "I", " ◀ usbManager.requestPermission() returned; waiting for system dialog + broadcast …")
172
+ }
173
+
174
+ // === 연결 라이프사이클
175
+ AsyncFunction("connect") { params: Map<String, Any?>, promise: Promise ->
176
+ val typeInt = (params["type"] as? Number)?.toInt() ?: NPrinterType.NONE.value
177
+ val printerType = NPrinterType.valueOf(typeInt)
178
+ val transport = params["transport"] as? String ?: "bluetooth"
179
+
180
+ val printer: NPrinter? = if (transport == "usb") {
181
+ findUsbPrinter(params)
182
+ } else {
183
+ val name = params["name"] as? String ?: ""
184
+ val mac = params["macAddress"] as? String ?: ""
185
+ NPrinter(printerType, name, mac)
186
+ }
187
+
188
+ if (printer == null) {
189
+ promise.resolve(NResult.NO_SELECTED_PRINTER)
190
+ return@AsyncFunction
191
+ }
192
+
193
+ val result = printerController.connect(printer)
194
+ if (result == NResult.OK) {
195
+ connectedPrinter = printer
196
+ }
197
+ promise.resolve(result)
198
+ }
199
+
200
+ Function("disconnect") {
201
+ printerController.disconnect()
202
+ connectedPrinter = null
203
+ }
204
+
205
+ AsyncFunction("getConnectState") { promise: Promise ->
206
+ promise.resolve(printerController.connectState)
207
+ }
208
+
209
+ Function("cancel") {
210
+ printerController.cancel()
211
+ }
212
+
213
+ // === 인쇄
214
+ Function("setPrintTimeout") { enableAuto: Boolean, manualTime: Int ->
215
+ printerController.setPrintTimeout(enableAuto, manualTime)
216
+ }
217
+
218
+ AsyncFunction("print") { params: Map<String, Any?>, promise: Promise ->
219
+ @Suppress("UNCHECKED_CAST")
220
+ val images = (params["images"] as? List<List<Int>>) ?: emptyList()
221
+ @Suppress("UNCHECKED_CAST")
222
+ val imageUrls = (params["imageUrls"] as? List<String>) ?: emptyList()
223
+
224
+ if (images.any { it.size > MAX_IMAGE_SIZE }) {
225
+ promise.resolve(EXCEED_MAX_SIZE)
226
+ return@AsyncFunction
227
+ }
228
+
229
+ val printer = connectedPrinter
230
+ if (printer == null) {
231
+ promise.resolve(NResult.NOT_CONNECTED)
232
+ return@AsyncFunction
233
+ }
234
+
235
+ val seq = ++printSeq
236
+ val t0 = System.currentTimeMillis()
237
+ Log.i(PRINT_TAG, "#$seq T+0ms ▶ print() entered (images=${images.size}, urls=${imageUrls.size})")
238
+
239
+ val bitmaps = decodeImages(images, imageUrls)
240
+ val tDecodedAt = System.currentTimeMillis()
241
+ Log.i(
242
+ PRINT_TAG,
243
+ "#$seq T+${tDecodedAt - t0}ms images decoded (count=${bitmaps.size}, " +
244
+ "size=${bitmaps.firstOrNull()?.let { "${it.width}x${it.height}" } ?: "-"})"
245
+ )
246
+
247
+ val quality = NPrintQuality.valueOf((params["quality"] as? Number)?.toInt() ?: 0)
248
+ val checkStatus = params["isCheckPrinterStatus"] as? Boolean ?: true
249
+ val checkCart = params["isCheckCartridgeType"] as? Boolean ?: true
250
+ val checkPower = params["isCheckPower"] as? Boolean ?: true
251
+ val dither = params["enableDither"] as? Boolean ?: true
252
+ val cut = params["isLastPageCut"] as? Boolean ?: true
253
+ val copiesN = (params["copies"] as? Number)?.toInt() ?: 1
254
+
255
+ val info = NPrintInfo(printer, bitmaps)
256
+ info.setPrintQuality(quality)
257
+ info.setCopies(copiesN)
258
+ info.setEnableLastPageCut(cut)
259
+ info.setEnableDither(dither)
260
+ info.setEnableCheckPrinterStatus(checkStatus)
261
+ info.setEnableCheckCartridgeType(checkCart)
262
+ info.setEnableCheckPower(checkPower)
263
+
264
+ val tBeforeSdk = System.currentTimeMillis()
265
+ Log.i(
266
+ PRINT_TAG,
267
+ "#$seq T+${tBeforeSdk - t0}ms ▶ printerController.print() called " +
268
+ "(quality=${quality.value}, copies=$copiesN, dither=$dither, cut=$cut, " +
269
+ "checkStatus=$checkStatus, checkCart=$checkCart, checkPower=$checkPower, " +
270
+ "transport=${if (printer.isUsb) "USB" else "BLE"})"
271
+ )
272
+ printStartedAt = tBeforeSdk
273
+
274
+ val result = printerController.print(info)
275
+
276
+ val tAfterSdk = System.currentTimeMillis()
277
+ Log.i(
278
+ PRINT_TAG,
279
+ "#$seq T+${tAfterSdk - t0}ms ◀ printerController.print() returned (result=$result, " +
280
+ "sdkCall=${tAfterSdk - tBeforeSdk}ms)"
281
+ )
282
+ promise.resolve(result)
283
+ }
284
+
285
+ // === 템플릿
286
+ AsyncFunction("setTemplate") { image: List<Int>, withPrint: Boolean, enableDither: Boolean, promise: Promise ->
287
+ if (image.size > MAX_IMAGE_SIZE) {
288
+ promise.resolve(EXCEED_MAX_SIZE)
289
+ return@AsyncFunction
290
+ }
291
+ val bytes = ByteArray(image.size) { image[it].toByte() }
292
+ val bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
293
+ if (bmp == null) {
294
+ promise.resolve(NResult.INVALID_PARAMETER)
295
+ return@AsyncFunction
296
+ }
297
+ promise.resolve(printerController.setTemplate(bmp, withPrint, enableDither))
298
+ }
299
+
300
+ AsyncFunction("setTemplateWithUrl") { imageUrl: String, withPrint: Boolean, enableDither: Boolean, promise: Promise ->
301
+ val bmp = try {
302
+ URL(imageUrl).openStream().use { BitmapFactory.decodeStream(it) }
303
+ } catch (_: Exception) {
304
+ null
305
+ }
306
+ if (bmp == null) {
307
+ promise.resolve(NResult.INVALID_PARAMETER)
308
+ return@AsyncFunction
309
+ }
310
+ promise.resolve(printerController.setTemplate(bmp, withPrint, enableDither))
311
+ }
312
+
313
+ AsyncFunction("clearTemplate") { promise: Promise ->
314
+ promise.resolve(printerController.clearTemplate())
315
+ }
316
+
317
+ // === 상태
318
+ AsyncFunction("getPrinterStatus") { promise: Promise ->
319
+ promise.resolve(printerController.printerStatus)
320
+ }
321
+ AsyncFunction("getCartridgeType") { promise: Promise ->
322
+ promise.resolve(printerController.cartridgeType)
323
+ }
324
+ AsyncFunction("getPrinterName") { promise: Promise ->
325
+ val r = printerController.printerName
326
+ promise.resolve(mapOf("result" to r.result, "value" to (r.value ?: "")))
327
+ }
328
+ AsyncFunction("getBatteryLevel") { promise: Promise ->
329
+ promise.resolve(printerController.batteryLevel)
330
+ }
331
+ AsyncFunction("getBatteryStatus") { promise: Promise ->
332
+ promise.resolve(printerController.batteryStatus)
333
+ }
334
+
335
+ // === BLE 딜레이 (Android 전용)
336
+ AsyncFunction("getBleConnectDelayOffset") { promise: Promise ->
337
+ promise.resolve(printerController.bleConnectDelayOffset)
338
+ }
339
+ Function("setBleConnectDelayOffset") { msec: Int ->
340
+ printerController.setBleConnectDelayOffset(msec.toLong())
341
+ }
342
+ AsyncFunction("getBleSendRetryDelayOffset") { promise: Promise ->
343
+ promise.resolve(printerController.bleSendRetryDelayOffset)
344
+ }
345
+ Function("setBleSendRetryDelayOffset") { msec: Int ->
346
+ printerController.setBleSendRetryDelayOffset(msec)
347
+ }
348
+
349
+ // === iOS connect delay 호환 stub
350
+ AsyncFunction("getDefaultConnectDelay") { promise: Promise -> promise.resolve(0) }
351
+ AsyncFunction("getConnectDelay") { promise: Promise -> promise.resolve(0) }
352
+ Function("setConnectDelay") { _: Int -> /* no-op */ }
353
+
354
+ OnDestroy {
355
+ pendingUsbReceiver?.let {
356
+ try { ctx.unregisterReceiver(it) } catch (_: Exception) {}
357
+ }
358
+ pendingUsbReceiver = null
359
+ pendingUsbPermissionPromise = null
360
+ }
361
+ }
362
+
363
+ // === INPrinterControllerCallback
364
+ override fun disconnected() {
365
+ sendEvent("onDisconnected", emptyMap<String, Any>())
366
+ }
367
+ override fun printProgress(index: Int, total: Int, result: Int) {
368
+ val elapsed = if (printStartedAt > 0) System.currentTimeMillis() - printStartedAt else -1
369
+ Log.i(PRINT_TAG, "#$printSeq T+${elapsed}ms · printProgress index=$index/$total result=$result")
370
+ sendEvent("onPrintProgress", mapOf("index" to index, "total" to total, "result" to result))
371
+ }
372
+ override fun printComplete(result: Int) {
373
+ val elapsed = if (printStartedAt > 0) System.currentTimeMillis() - printStartedAt else -1
374
+ Log.i(PRINT_TAG, "#$printSeq T+${elapsed}ms ✓ printComplete result=$result")
375
+ sendEvent("onPrintComplete", mapOf("result" to result))
376
+ }
377
+
378
+ // === INPrinterScanControllerCallback
379
+ override fun deviceFound(printer: NPrinter) {
380
+ sendEvent("onDeviceFound", printer.toMap())
381
+ }
382
+
383
+ private fun NPrinter.toMap(): Map<String, Any?> = mapOf(
384
+ "name" to (name ?: ""),
385
+ "macAddress" to (macAddress ?: ""),
386
+ "type" to (type?.value ?: NPrinterType.NONE.value),
387
+ "typeName" to (type?.toString() ?: ""),
388
+ "transport" to if (isUsb) "usb" else "bluetooth",
389
+ )
390
+
391
+ // logcat 출력 + JS 측 onNativeLog 이벤트 동시 emit.
392
+ private fun emitNativeLog(tag: String, level: String, message: String) {
393
+ when (level) {
394
+ "E" -> Log.e(tag, message)
395
+ "W" -> Log.w(tag, message)
396
+ "I" -> Log.i(tag, message)
397
+ else -> Log.d(tag, message)
398
+ }
399
+ try {
400
+ sendEvent(
401
+ "onNativeLog",
402
+ mapOf("tag" to tag, "level" to level, "message" to message)
403
+ )
404
+ } catch (_: Throwable) {
405
+ // 모듈이 아직 listener에 연결되지 않은 시점이면 무시
406
+ }
407
+ }
408
+
409
+ // SDK의 printerController.usbPrinterList는 내부 lazy 캐시라 USB 재연결을 즉시 반영 못함.
410
+ // (BLE startScan 같은 트리거가 일어나야 갱신됨.) Android UsbManager에서 매번 직접
411
+ // enumerate해서 NPrinter를 새로 구성해 반환 → 항상 최신.
412
+ private fun enumerateUsbPrinters(): List<NPrinter> {
413
+ val usbManager = ctx.getSystemService(Context.USB_SERVICE) as? UsbManager
414
+ if (usbManager == null) {
415
+ emitNativeLog(USB_TAG, "W", "UsbManager service is null — cannot enumerate")
416
+ return emptyList()
417
+ }
418
+ val allDevices = usbManager.deviceList.values.toList()
419
+ emitNativeLog(USB_TAG, "I", "─── enumerateUsbPrinters(): UsbManager.deviceList.size=${allDevices.size}")
420
+ if (allDevices.isEmpty()) {
421
+ emitNativeLog(USB_TAG, "W", "UsbManager reports NO devices — Android kernel doesn't see any USB device")
422
+ }
423
+ val matched = mutableListOf<NPrinter>()
424
+ allDevices.forEachIndexed { idx, device ->
425
+ val vid = device.vendorId
426
+ val pid = device.productId
427
+ val validResult: Pair<Boolean, String?> = try {
428
+ Pair(NPrinter.isVaildUsbPrinter(device), null)
429
+ } catch (e: Throwable) {
430
+ Pair(false, "${e.javaClass.simpleName}: ${e.message}")
431
+ }
432
+ val valid = validResult.first
433
+ val validErr = validResult.second
434
+ if (validErr != null) {
435
+ emitNativeLog(USB_TAG, "E", "isVaildUsbPrinter threw for ${device.deviceName}: $validErr")
436
+ }
437
+ val typeStr = if (valid) {
438
+ try {
439
+ NPrinter.getPrinterType(device).toString()
440
+ } catch (e: Throwable) {
441
+ emitNativeLog(USB_TAG, "E", "getPrinterType threw for ${device.deviceName}: ${e.javaClass.simpleName}: ${e.message}")
442
+ "?"
443
+ }
444
+ } else "-"
445
+ emitNativeLog(
446
+ USB_TAG, "I",
447
+ " [#$idx] name=${device.deviceName} VID=$vid(0x${vid.toString(16)}) " +
448
+ "PID=$pid(0x${pid.toString(16)}) class=${device.deviceClass} " +
449
+ "isVaildUsbPrinter=$valid type=$typeStr"
450
+ )
451
+ if (valid) {
452
+ try {
453
+ val printer = NPrinter(NPrinter.getPrinterType(device), device)
454
+ // USB는 SDK가 name을 비워두므로 device path로 채워서 UI에서 식별 가능하게 함
455
+ if (printer.name.isNullOrEmpty()) {
456
+ printer.setName(device.deviceName)
457
+ }
458
+ matched.add(printer)
459
+ } catch (e: Throwable) {
460
+ emitNativeLog(USB_TAG, "E", "NPrinter ctor threw for ${device.deviceName}: ${e.javaClass.simpleName}: ${e.message}")
461
+ }
462
+ }
463
+ }
464
+ emitNativeLog(USB_TAG, "I", "─── matched Nemonic printers=${matched.size}")
465
+ return matched
466
+ }
467
+
468
+ private fun findUsbPrinter(params: Map<String, Any?>): NPrinter? {
469
+ val list = enumerateUsbPrinters()
470
+ val typeInt = (params["type"] as? Number)?.toInt() ?: return list.firstOrNull()
471
+ val target = NPrinterType.valueOf(typeInt)
472
+ return list.firstOrNull { it.type == target } ?: list.firstOrNull()
473
+ }
474
+
475
+ // requestUsbPermission 전용: raw UsbDevice 가 필요 (UsbManager.requestPermission API).
476
+ private fun findUsbDevice(params: Map<String, Any?>): UsbDevice? {
477
+ val usbManager = ctx.getSystemService(Context.USB_SERVICE) as? UsbManager ?: return null
478
+ val all = usbManager.deviceList.values.toList()
479
+ val nemonic = all.filter {
480
+ try { NPrinter.isVaildUsbPrinter(it) } catch (_: Throwable) { false }
481
+ }
482
+ val typeInt = (params["type"] as? Number)?.toInt() ?: return nemonic.firstOrNull()
483
+ val target = NPrinterType.valueOf(typeInt)
484
+ return nemonic.firstOrNull {
485
+ try { NPrinter.getPrinterType(it) == target } catch (_: Throwable) { false }
486
+ } ?: nemonic.firstOrNull()
487
+ }
488
+
489
+ private fun decodeImages(images: List<List<Int>>, urls: List<String>): List<Bitmap> {
490
+ val result = mutableListOf<Bitmap>()
491
+ if (images.isNotEmpty()) {
492
+ for (img in images) {
493
+ val bytes = ByteArray(img.size) { img[it].toByte() }
494
+ val bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: continue
495
+ result.add(bmp)
496
+ }
497
+ } else {
498
+ for (url in urls) {
499
+ val bmp = try {
500
+ URL(url).openStream().use { BitmapFactory.decodeStream(it) }
501
+ } catch (_: Exception) {
502
+ null
503
+ }
504
+ if (bmp != null) result.add(bmp)
505
+ }
506
+ }
507
+ return result
508
+ }
509
+ }
@@ -0,0 +1,44 @@
1
+ export type Transport = 'bluetooth' | 'usb';
2
+ export type NPrinter = {
3
+ name: string;
4
+ macAddress: string;
5
+ type: number;
6
+ typeName?: string;
7
+ transport?: Transport;
8
+ };
9
+ export type PrintOptions = {
10
+ images?: number[][];
11
+ imageUrls?: string[];
12
+ quality?: number;
13
+ copies?: number;
14
+ isLastPageCut?: boolean;
15
+ enableDither?: boolean;
16
+ isCheckPrinterStatus?: boolean;
17
+ isCheckCartridgeType?: boolean;
18
+ isCheckPower?: boolean;
19
+ };
20
+ export type PrinterNameResult = {
21
+ result: number;
22
+ value: string;
23
+ };
24
+ export type PrintProgressEvent = {
25
+ index: number;
26
+ total: number;
27
+ result: number;
28
+ };
29
+ export type PrintCompleteEvent = {
30
+ result: number;
31
+ };
32
+ export type NativeLogEvent = {
33
+ tag: string;
34
+ level: string;
35
+ message: string;
36
+ };
37
+ export type BootNemonicPrinterModuleEvents = {
38
+ onDeviceFound: (printer: NPrinter) => void;
39
+ onDisconnected: () => void;
40
+ onPrintProgress: (event: PrintProgressEvent) => void;
41
+ onPrintComplete: (event: PrintCompleteEvent) => void;
42
+ onNativeLog: (event: NativeLogEvent) => void;
43
+ };
44
+ //# sourceMappingURL=BootNemonicPrinter.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BootNemonicPrinter.types.d.ts","sourceRoot":"","sources":["../src/BootNemonicPrinter.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,KAAK,CAAC;AAE5C,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,aAAa,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC3C,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACrD,eAAe,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACrD,WAAW,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CAC9C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=BootNemonicPrinter.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BootNemonicPrinter.types.js","sourceRoot":"","sources":["../src/BootNemonicPrinter.types.ts"],"names":[],"mappings":"","sourcesContent":["export type Transport = 'bluetooth' | 'usb';\n\nexport type NPrinter = {\n name: string;\n macAddress: string;\n type: number;\n typeName?: string;\n transport?: Transport;\n};\n\nexport type PrintOptions = {\n images?: number[][];\n imageUrls?: string[];\n quality?: number;\n copies?: number;\n isLastPageCut?: boolean;\n enableDither?: boolean;\n isCheckPrinterStatus?: boolean;\n isCheckCartridgeType?: boolean;\n isCheckPower?: boolean;\n};\n\nexport type PrinterNameResult = {\n result: number;\n value: string;\n};\n\nexport type PrintProgressEvent = {\n index: number;\n total: number;\n result: number;\n};\n\nexport type PrintCompleteEvent = {\n result: number;\n};\n\nexport type NativeLogEvent = {\n tag: string;\n level: string;\n message: string;\n};\n\nexport type BootNemonicPrinterModuleEvents = {\n onDeviceFound: (printer: NPrinter) => void;\n onDisconnected: () => void;\n onPrintProgress: (event: PrintProgressEvent) => void;\n onPrintComplete: (event: PrintCompleteEvent) => void;\n onNativeLog: (event: NativeLogEvent) => void;\n};\n"]}
@@ -0,0 +1,36 @@
1
+ import { NativeModule } from 'expo';
2
+ import { BootNemonicPrinterModuleEvents, NPrinter, PrintOptions, PrinterNameResult } from './BootNemonicPrinter.types';
3
+ declare class BootNemonicPrinterModule extends NativeModule<BootNemonicPrinterModuleEvents> {
4
+ readonly MAX_IMAGE_SIZE: number;
5
+ readonly SDK_VERSION: string;
6
+ startScan(): Promise<number>;
7
+ stopScan(): void;
8
+ getUsbPrinterList(): Promise<NPrinter[]>;
9
+ requestUsbPermission(printer: NPrinter): Promise<boolean>;
10
+ connect(printer: NPrinter & {
11
+ queueLabel?: string;
12
+ }): Promise<number>;
13
+ disconnect(): void;
14
+ getConnectState(): Promise<number>;
15
+ cancel(): void;
16
+ setPrintTimeout(enableAuto: boolean, manualTime: number): void;
17
+ print(options: PrintOptions): Promise<number>;
18
+ setTemplate(image: number[], withPrint: boolean, enableDither: boolean): Promise<number>;
19
+ setTemplateWithUrl(imageUrl: string, withPrint: boolean, enableDither: boolean): Promise<number>;
20
+ clearTemplate(): Promise<number>;
21
+ getPrinterStatus(): Promise<number>;
22
+ getCartridgeType(): Promise<number>;
23
+ getPrinterName(): Promise<PrinterNameResult>;
24
+ getBatteryLevel(): Promise<number>;
25
+ getBatteryStatus(): Promise<number>;
26
+ getBleConnectDelayOffset(): Promise<number>;
27
+ setBleConnectDelayOffset(msec: number): void;
28
+ getBleSendRetryDelayOffset(): Promise<number>;
29
+ setBleSendRetryDelayOffset(msec: number): void;
30
+ getDefaultConnectDelay(): Promise<number>;
31
+ getConnectDelay(): Promise<number>;
32
+ setConnectDelay(msec: number): void;
33
+ }
34
+ declare const _default: BootNemonicPrinterModule;
35
+ export default _default;
36
+ //# sourceMappingURL=BootNemonicPrinterModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BootNemonicPrinterModule.d.ts","sourceRoot":"","sources":["../src/BootNemonicPrinterModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EACL,8BAA8B,EAC9B,QAAQ,EACR,YAAY,EACZ,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,OAAO,wBAAyB,SAAQ,YAAY,CAAC,8BAA8B,CAAC;IACzF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAC5B,QAAQ,IAAI,IAAI;IAEhB,iBAAiB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IACxC,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAEzD,OAAO,CAAC,OAAO,EAAE,QAAQ,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IACrE,UAAU,IAAI,IAAI;IAClB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAClC,MAAM,IAAI,IAAI;IAEd,eAAe,CAAC,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAC9D,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAE7C,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IACxF,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAChG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAEhC,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IACnC,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IACnC,cAAc,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAC5C,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAClC,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAEnC,wBAAwB,IAAI,OAAO,CAAC,MAAM,CAAC;IAC3C,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAC5C,0BAA0B,IAAI,OAAO,CAAC,MAAM,CAAC;IAC7C,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAE9C,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;IACzC,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAClC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CACpC;;AAED,wBAAmF"}
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo';
2
+ export default requireNativeModule('BootNemonicPrinter');
3
+ //# sourceMappingURL=BootNemonicPrinterModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BootNemonicPrinterModule.js","sourceRoot":"","sources":["../src/BootNemonicPrinterModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AA+CzD,eAAe,mBAAmB,CAA2B,oBAAoB,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\nimport {\n BootNemonicPrinterModuleEvents,\n NPrinter,\n PrintOptions,\n PrinterNameResult,\n} from './BootNemonicPrinter.types';\n\ndeclare class BootNemonicPrinterModule extends NativeModule<BootNemonicPrinterModuleEvents> {\n readonly MAX_IMAGE_SIZE: number;\n readonly SDK_VERSION: string;\n\n startScan(): Promise<number>;\n stopScan(): void;\n\n getUsbPrinterList(): Promise<NPrinter[]>;\n requestUsbPermission(printer: NPrinter): Promise<boolean>;\n\n connect(printer: NPrinter & { queueLabel?: string }): Promise<number>;\n disconnect(): void;\n getConnectState(): Promise<number>;\n cancel(): void;\n\n setPrintTimeout(enableAuto: boolean, manualTime: number): void;\n print(options: PrintOptions): Promise<number>;\n\n setTemplate(image: number[], withPrint: boolean, enableDither: boolean): Promise<number>;\n setTemplateWithUrl(imageUrl: string, withPrint: boolean, enableDither: boolean): Promise<number>;\n clearTemplate(): Promise<number>;\n\n getPrinterStatus(): Promise<number>;\n getCartridgeType(): Promise<number>;\n getPrinterName(): Promise<PrinterNameResult>;\n getBatteryLevel(): Promise<number>;\n getBatteryStatus(): Promise<number>;\n\n getBleConnectDelayOffset(): Promise<number>;\n setBleConnectDelayOffset(msec: number): void;\n getBleSendRetryDelayOffset(): Promise<number>;\n setBleSendRetryDelayOffset(msec: number): void;\n\n getDefaultConnectDelay(): Promise<number>;\n getConnectDelay(): Promise<number>;\n setConnectDelay(msec: number): void;\n}\n\nexport default requireNativeModule<BootNemonicPrinterModule>('BootNemonicPrinter');\n"]}
@@ -0,0 +1,8 @@
1
+ export declare const NBatteryStatus: {
2
+ readonly NO_CHARGING: 0;
3
+ readonly LOW_NO_CHARGING: 1;
4
+ readonly CHARGING: 2;
5
+ readonly LOW_CHARGING: 3;
6
+ };
7
+ export type NBatteryStatusCode = (typeof NBatteryStatus)[keyof typeof NBatteryStatus];
8
+ //# sourceMappingURL=NBatteryStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NBatteryStatus.d.ts","sourceRoot":"","sources":["../../src/constants/NBatteryStatus.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc;;;;;CAKjB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ export const NBatteryStatus = {
2
+ NO_CHARGING: 0,
3
+ LOW_NO_CHARGING: 1,
4
+ CHARGING: 2,
5
+ LOW_CHARGING: 3,
6
+ };
7
+ //# sourceMappingURL=NBatteryStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NBatteryStatus.js","sourceRoot":"","sources":["../../src/constants/NBatteryStatus.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,WAAW,EAAE,CAAC;IACd,eAAe,EAAE,CAAC;IAClB,QAAQ,EAAE,CAAC;IACX,YAAY,EAAE,CAAC;CACP,CAAC","sourcesContent":["export const NBatteryStatus = {\n NO_CHARGING: 0,\n LOW_NO_CHARGING: 1,\n CHARGING: 2,\n LOW_CHARGING: 3,\n} as const;\n\nexport type NBatteryStatusCode = (typeof NBatteryStatus)[keyof typeof NBatteryStatus];\n"]}