react-native-mesmart 3.0.2 → 3.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.
@@ -59,6 +59,7 @@ android {
59
59
  defaultConfig {
60
60
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
61
61
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
62
+ consumerProguardFiles "consumer-rules.pro"
62
63
  }
63
64
 
64
65
  buildTypes {
@@ -0,0 +1,2 @@
1
+ # Interface trocada com o dex do SDK Gertec Neo (assets/gertec-neo): nao pode ser renomeada nem ter metodos removidos.
2
+ -keep class com.gertec.backend.** { *; }
@@ -0,0 +1 @@
1
+ 2.0.10-b12dc1dd8256
@@ -1,8 +1,15 @@
1
1
  package com.gertec
2
2
 
3
+ import android.util.Log
3
4
  import br.com.gertec.gedi.GEDI
4
5
  import com.facebook.react.bridge.Promise
5
6
  import com.facebook.react.bridge.ReactApplicationContext
7
+ import java.util.concurrent.ExecutionException
8
+ import java.util.concurrent.Executors
9
+ import java.util.concurrent.TimeUnit
10
+ import java.util.concurrent.TimeoutException
11
+ import com.gertec.backend.GertecBackend
12
+ import com.gertec.backend.GertecCallback
6
13
  import com.gertec.utils.MifareGPOS780
7
14
  import com.gertec.utils.PrinterGPOS780
8
15
  import com.mesmart.Utils
@@ -12,42 +19,124 @@ class GertecImpl(
12
19
  ) {
13
20
  private val isSupported = Utils.isGPOS780()
14
21
 
22
+ // GPOS780S usa o SDK Gertec Neo (libgedi 2.x), carregado a parte; os demais GPOS780 seguem na libgedi 1.16.
23
+ private val useNeo = Utils.isGPOS780S()
24
+
15
25
  @Volatile private var mifare: MifareGPOS780? = null
16
26
  @Volatile private var printer: PrinterGPOS780? = null
27
+ private var neo: GertecBackend? = null
28
+ private var neoError: Throwable? = null
29
+
30
+ // Falha no init da GEDI (ex.: libgedi.so incompativel com o Android do aparelho) nao pode derrubar o modulo:
31
+ // fica guardada e volta como erro legivel em cada chamada.
32
+ private var initError: Throwable? = null
17
33
 
18
34
  init {
19
- if (isSupported) {
20
- GEDI.init(reactContext.applicationContext)
35
+ if (isSupported && !useNeo) {
36
+ try {
37
+ GEDI.init(reactContext.applicationContext)
38
+ } catch (t: Throwable) {
39
+ initError = t
40
+ Log.e(TAG, "GEDI.init failed: ${t.javaClass.name}: ${t.message} (${Utils.deviceContext()})", t)
41
+ }
42
+ }
43
+ }
44
+
45
+ private fun checkSupported() {
46
+ if (!isSupported) throw Exception("Unsupported device (Supported models GPOS780, GPOS780S). ${Utils.deviceContext()}")
47
+ initError?.let {
48
+ throw Exception("GEDI init failed: ${it.javaClass.name}: ${it.message} (${Utils.deviceContext()})", it)
49
+ }
50
+ }
51
+
52
+ // GEDI.init() sobe a instancia numa thread e engole falhas; a lib de RFID le GEDI.getInstance() sem argumento
53
+ // e leva NPE se ainda for null. getInstance(context) cria de forma sincrona e deixa a causa real subir,
54
+ // com timeout p/ nao travar a fila de modulos nativos caso o init fique pendurado (visto na GPOS780S).
55
+ private fun ensureGedi() {
56
+ if (GEDI.getInstance() != null) return
57
+ val executor = Executors.newSingleThreadExecutor()
58
+ try {
59
+ executor.submit<Any?> { GEDI.getInstance(reactContext.applicationContext) }.get(GEDI_INIT_TIMEOUT_S, TimeUnit.SECONDS)
60
+ } catch (e: TimeoutException) {
61
+ throw Exception("GEDI init timeout (${GEDI_INIT_TIMEOUT_S}s) (${Utils.deviceContext()})", e)
62
+ } catch (e: ExecutionException) {
63
+ val cause = e.cause ?: e
64
+ throw Exception("GEDI init failed: ${cause.javaClass.name}: ${cause.message} (${Utils.deviceContext()})", cause)
65
+ } finally {
66
+ executor.shutdownNow()
67
+ }
68
+ if (GEDI.getInstance() == null) {
69
+ throw Exception("GEDI instance is null after init (${Utils.deviceContext()})")
21
70
  }
22
71
  }
23
72
 
24
73
  @Synchronized
25
74
  private fun ensureMifare(): MifareGPOS780 {
26
- if (!isSupported) throw Exception("Unsupported device (Supported models GPOS780, GPOS780S).")
75
+ checkSupported()
76
+ ensureGedi()
27
77
  if (mifare == null) mifare = MifareGPOS780()
28
78
  return mifare!!
29
79
  }
30
80
 
31
81
  @Synchronized
32
82
  private fun ensurePrinter(): PrinterGPOS780 {
33
- if (!isSupported) throw Exception("Unsupported device (Supported models GPOS780, GPOS780S).")
83
+ checkSupported()
34
84
  if (printer == null) printer = PrinterGPOS780(reactContext)
35
85
  return printer!!
36
86
  }
37
87
 
88
+ // Carga preguicosa: so a GPOS780S paga o custo de extrair o dex/.so da Neo, e a falha vira erro legivel por chamada.
89
+ @Synchronized
90
+ private fun ensureNeo(): GertecBackend {
91
+ neo?.let { return it }
92
+ neoError?.let { throw neoLoadException(it) }
93
+ return try {
94
+ NeoSdkLoader.load(reactContext.applicationContext).also { neo = it }
95
+ } catch (t: Throwable) {
96
+ neoError = t
97
+ Log.e(TAG, "Gertec Neo SDK load failed: ${t.javaClass.name}: ${t.message} (${Utils.deviceContext()})", t)
98
+ throw neoLoadException(t)
99
+ }
100
+ }
101
+
102
+ private fun neoLoadException(t: Throwable) =
103
+ Exception("Gertec Neo SDK load failed: ${t.javaClass.name}: ${t.message} (${Utils.deviceContext()})", t)
104
+
105
+ private fun neoCallback(promise: Promise, transform: (Any?) -> Any? = { it }) = object : GertecCallback {
106
+ override fun resolve(value: Any?) {
107
+ try {
108
+ promise.resolve(transform(value))
109
+ } catch (t: Throwable) {
110
+ promise.reject("GERTEC_RFID_ERROR", t.message, t)
111
+ }
112
+ }
113
+
114
+ override fun reject(code: String, message: String?, cause: Throwable?) {
115
+ if (cause != null) promise.reject(code, message, cause) else promise.reject(code, message)
116
+ }
117
+ }
118
+
38
119
  fun scanRfid(promise: Promise) {
39
120
  try {
121
+ if (useNeo) {
122
+ ensureNeo().scanRfid(neoCallback(promise) { Utils.resolveRFID(Utils.byteArrayToHexString(it as ByteArray)) })
123
+ return
124
+ }
40
125
  ensureMifare().scanRfid(promise)
41
126
  } catch (t: Throwable) {
42
- promise.reject(t)
127
+ promise.reject("GERTEC_RFID_ERROR", t.message, t)
43
128
  }
44
129
  }
45
130
 
46
131
  fun cancelScan(promise: Promise) {
47
132
  try {
133
+ if (useNeo) {
134
+ ensureNeo().cancelScan(neoCallback(promise))
135
+ return
136
+ }
48
137
  ensureMifare().cancelScan(promise)
49
138
  } catch (t: Throwable) {
50
- promise.reject(t)
139
+ promise.reject("GERTEC_RFID_ERROR", t.message, t)
51
140
  }
52
141
  }
53
142
 
@@ -55,13 +144,34 @@ class GertecImpl(
55
144
  lines: Int,
56
145
  promise: Promise
57
146
  ) {
58
- ensurePrinter().scrollPaper(lines, promise)
147
+ try {
148
+ if (useNeo) {
149
+ ensureNeo().scrollPrinterPaper(lines, neoCallback(promise))
150
+ return
151
+ }
152
+ ensurePrinter().scrollPaper(lines, promise)
153
+ } catch (t: Throwable) {
154
+ promise.reject("GERTEC_PRINTER_ERROR", t.message, t)
155
+ }
59
156
  }
60
157
 
61
158
  fun printHtml(
62
159
  html: String,
63
160
  promise: Promise
64
161
  ) {
65
- ensurePrinter().printHtml(html, promise)
162
+ try {
163
+ if (useNeo) {
164
+ ensureNeo().printHtml(html, neoCallback(promise))
165
+ return
166
+ }
167
+ ensurePrinter().printHtml(html, promise)
168
+ } catch (t: Throwable) {
169
+ promise.reject("GERTEC_PRINTER_ERROR", t.message, t)
170
+ }
171
+ }
172
+
173
+ private companion object {
174
+ const val TAG = "MeSmartGertec"
175
+ const val GEDI_INIT_TIMEOUT_S = 5L
66
176
  }
67
177
  }
@@ -18,7 +18,7 @@ class GertecModule(
18
18
  fun scanRfid(promise: Promise) {
19
19
  try {
20
20
  gertec.scanRfid(promise)
21
- } catch (e: Exception) {
21
+ } catch (e: Throwable) {
22
22
  e.printStackTrace()
23
23
  promise.reject(e)
24
24
  }
@@ -28,7 +28,7 @@ class GertecModule(
28
28
  fun cancelScan(promise: Promise) {
29
29
  try {
30
30
  gertec.cancelScan(promise)
31
- } catch (e: Exception) {
31
+ } catch (e: Throwable) {
32
32
  e.printStackTrace()
33
33
  promise.reject(e)
34
34
  }
@@ -41,7 +41,7 @@ class GertecModule(
41
41
  ) {
42
42
  try {
43
43
  gertec.scrollPrinterPaper(lines, promise)
44
- } catch (e: Exception) {
44
+ } catch (e: Throwable) {
45
45
  e.printStackTrace()
46
46
  promise.reject(e)
47
47
  }
@@ -54,7 +54,7 @@ class GertecModule(
54
54
  ) {
55
55
  try {
56
56
  gertec.printHtml(html, promise)
57
- } catch (e: Exception) {
57
+ } catch (e: Throwable) {
58
58
  e.printStackTrace()
59
59
  promise.reject(e)
60
60
  }
@@ -0,0 +1,94 @@
1
+ package com.gertec
2
+
3
+ import android.content.Context
4
+ import android.os.Process
5
+ import com.gertec.backend.GertecBackend
6
+ import dalvik.system.BaseDexClassLoader
7
+ import dalvik.system.DexClassLoader
8
+ import java.io.File
9
+
10
+ /**
11
+ * Carrega o SDK Gertec Neo (libgedi 2.x) num classloader proprio, so na GPOS780S.
12
+ *
13
+ * O app usa a libgedi 1.16 nos demais POS e as duas tem as mesmas classes e o mesmo libgedi.so (JNI por nome,
14
+ * entao nao da p/ renomear pacote), por isso a Neo vai num dex a parte (assets/gertec-neo, ver gertec-neo/build.sh)
15
+ * e o libgedi.so dela fica fora de jniLibs, extraido p/ uma pasta que so esse classloader enxerga.
16
+ */
17
+ internal object NeoSdkLoader {
18
+ private const val ASSET_DIR = "gertec-neo"
19
+ private const val ADAPTER_CLASS = "com.gertec.neo.NeoGertecBackend"
20
+
21
+ @Synchronized
22
+ fun load(context: Context): GertecBackend {
23
+ val abi = if (Process.is64Bit()) "arm64-v8a" else "armeabi-v7a"
24
+ val assets = context.assets
25
+ val version = assets.open("$ASSET_DIR/VERSION").bufferedReader().use { it.readText().trim() }
26
+
27
+ val root = File(context.codeCacheDir, "$ASSET_DIR/$version-$abi")
28
+ val jar = File(root, "neo-bridge.jar")
29
+ val libDir = File(root, "lib")
30
+ val so = File(libDir, "libgedi.so")
31
+
32
+ if (!jar.exists() || !so.exists()) {
33
+ File(context.codeCacheDir, ASSET_DIR).deleteRecursively()
34
+ libDir.mkdirs()
35
+ copyAsset(context, "$ASSET_DIR/neo-bridge.jar", jar)
36
+ copyAsset(context, "$ASSET_DIR/$abi/libgedi.so", so)
37
+ }
38
+ // Android 14+ recusa dex gravavel em DexClassLoader.
39
+ jar.setReadOnly()
40
+
41
+ val parent = GertecBackend::class.java.classLoader
42
+ ?: throw IllegalStateException("Sem classloader do app para o SDK Gertec Neo")
43
+ val loader = ChildFirstDexClassLoader(jar.absolutePath, libDir.absolutePath, parent)
44
+ val adapter = loader.loadClass(ADAPTER_CLASS)
45
+ return adapter.getConstructor(Context::class.java).newInstance(context.applicationContext) as GertecBackend
46
+ }
47
+
48
+ private fun copyAsset(context: Context, assetPath: String, dest: File) {
49
+ context.assets.open(assetPath).use { input ->
50
+ dest.outputStream().use { output -> input.copyTo(output) }
51
+ }
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Classloader "filho primeiro": as classes do dex da Neo (br.com.gertec.*, gedi.*, ...) tem o mesmo nome das da 1.16
57
+ * que estao no app, entao procura no dex antes de perguntar ao pai. Plataforma, Kotlin, React Native e a interface
58
+ * com.gertec.backend continuam vindo do pai, para os tipos trocados entre os dois lados serem os mesmos.
59
+ */
60
+ internal class ChildFirstDexClassLoader(
61
+ dexPath: String,
62
+ libPath: String,
63
+ parent: ClassLoader
64
+ ) : DexClassLoader(dexPath, null, libPath, parent) {
65
+
66
+ override fun loadClass(name: String, resolve: Boolean): Class<*> {
67
+ synchronized(this) {
68
+ findLoadedClass(name)?.let { return it }
69
+ if (!isShared(name)) {
70
+ try {
71
+ val clazz = findClass(name)
72
+ if (resolve) resolveClass(clazz)
73
+ return clazz
74
+ } catch (_: ClassNotFoundException) {
75
+ // nao esta no dex da Neo: cai no pai
76
+ }
77
+ }
78
+ return super.loadClass(name, resolve)
79
+ }
80
+ }
81
+
82
+ // O libgedi.so da Neo tem que vir daqui (nao da 1.16 do app); o resto (ex.: opencv) pode vir do pai.
83
+ override fun findLibrary(name: String): String? =
84
+ super.findLibrary(name) ?: (parent as? BaseDexClassLoader)?.findLibrary(name)
85
+
86
+ private fun isShared(name: String): Boolean = SHARED_PREFIXES.any { name.startsWith(it) }
87
+
88
+ private companion object {
89
+ val SHARED_PREFIXES = arrayOf(
90
+ "java.", "javax.", "android.", "androidx.", "dalvik.", "libcore.",
91
+ "kotlin.", "kotlinx.", "com.facebook.", "com.gertec.backend."
92
+ )
93
+ }
94
+ }
@@ -0,0 +1,20 @@
1
+ package com.gertec.backend;
2
+
3
+ /**
4
+ * Contrato entre o modulo e o SDK Gertec "Neo" (libgedi 2.x), que roda num classloader isolado.
5
+ * A interface e o callback sao carregados pelo classloader do app (pai) e so usam tipos da plataforma,
6
+ * assim o adaptador carregado do dex da Neo consegue implementar sem depender de classes do React Native.
7
+ */
8
+ public interface GertecBackend {
9
+ /** Resolve com o UID do cartao (byte[]). */
10
+ void scanRfid(GertecCallback callback);
11
+
12
+ /** Resolve com Boolean.TRUE. */
13
+ void cancelScan(GertecCallback callback);
14
+
15
+ /** Resolve com Boolean.TRUE. */
16
+ void scrollPrinterPaper(int lines, GertecCallback callback);
17
+
18
+ /** Resolve com Boolean.TRUE. */
19
+ void printHtml(String html, GertecCallback callback);
20
+ }
@@ -0,0 +1,7 @@
1
+ package com.gertec.backend;
2
+
3
+ public interface GertecCallback {
4
+ void resolve(Object value);
5
+
6
+ void reject(String code, String message, Throwable cause);
7
+ }
@@ -1,5 +1,6 @@
1
1
  package com.gertec.utils
2
2
 
3
+ import android.util.Log
3
4
  import br.com.gertec.easylayer.contactless.MifareClassic
4
5
  import com.facebook.react.bridge.Promise
5
6
  import com.mesmart.Utils
@@ -33,7 +34,10 @@ class MifareGPOS780 {
33
34
  } catch (e: Exception) {
34
35
  val msg = e.message?.lowercase().orEmpty()
35
36
  val isTimeoutLike = msg.contains("timeout") || msg.contains("polling")
36
- if (!isTimeoutLike) throw e
37
+ if (!isTimeoutLike) {
38
+ Log.e(TAG, "RFID connect failed: ${e.javaClass.name}: ${e.message}")
39
+ throw e
40
+ }
37
41
  } finally {
38
42
  try { mifare.close() } catch (_: Exception) {}
39
43
  }
@@ -46,9 +50,11 @@ class MifareGPOS780 {
46
50
  )
47
51
  else -> promise.reject("Timeout to read RFID card!")
48
52
  }
49
- } catch (e: Exception) {
50
- e.printStackTrace()
51
- promise.reject(e)
53
+ } catch (t: Throwable) {
54
+ // Throwable: falha da lib nativa e Error e, sem isso, a thread morre e a promise nunca resolve.
55
+ val message = "RFID scan threw ${t.javaClass.name}: ${t.message} (${Utils.deviceContext()})"
56
+ Log.e(TAG, message, t)
57
+ promise.reject("GERTEC_RFID_ERROR", message, t)
52
58
  } finally {
53
59
  try { mifare.close() } catch (_: Exception) {}
54
60
  mifare = MifareClassic()
@@ -70,4 +76,8 @@ class MifareGPOS780 {
70
76
  promise.reject(e)
71
77
  }
72
78
  }
79
+
80
+ private companion object {
81
+ const val TAG = "MeSmartGertec"
82
+ }
73
83
  }
@@ -1,9 +1,11 @@
1
1
  package com.gertec.utils
2
2
 
3
+ import android.util.Log
3
4
  import br.com.gertec.easylayer.printer.Printer
4
5
  import br.com.gertec.easylayer.printer.PrinterError
5
6
  import com.facebook.react.bridge.Promise
6
7
  import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.mesmart.Utils
7
9
 
8
10
  class PrinterGPOS780(
9
11
  private val reactContext: ReactApplicationContext
@@ -12,27 +14,48 @@ class PrinterGPOS780(
12
14
  lines: Int,
13
15
  promise: Promise
14
16
  ) {
15
- Printer.getInstance(reactContext.applicationContext, object: Printer.Listener {
16
- override fun onPrinterError(printerError: PrinterError?) {
17
- promise.reject("Cause: ${printerError?.cause}\\nError code: ${printerError?.code}\\nRequest ID: ${printerError?.requestId}\"")
18
- }
19
- override fun onPrinterSuccessful(var1: Int) {
20
- promise.resolve(true)
21
- }
22
- }).scrollPaper(lines)
17
+ try {
18
+ Printer.getInstance(reactContext.applicationContext, listener(promise, "scrollPaper"))
19
+ .scrollPaper(lines)
20
+ } catch (t: Throwable) {
21
+ reject(promise, "scrollPaper", t)
22
+ }
23
23
  }
24
24
 
25
25
  fun printHtml(
26
26
  html: String,
27
27
  promise: Promise
28
28
  ) {
29
- Printer.getInstance(reactContext.applicationContext, object: Printer.Listener {
30
- override fun onPrinterError(printerError: PrinterError?) {
31
- promise.reject("Cause: ${printerError?.cause}\\nError code: ${printerError?.code}\\nRequest ID: ${printerError?.requestId}\"")
32
- }
33
- override fun onPrinterSuccessful(var1: Int) {
34
- promise.resolve(true)
35
- }
36
- }).printHtml(reactContext.applicationContext, html)
29
+ try {
30
+ Printer.getInstance(reactContext.applicationContext, listener(promise, "printHtml"))
31
+ .printHtml(reactContext.applicationContext, html)
32
+ } catch (t: Throwable) {
33
+ reject(promise, "printHtml", t)
34
+ }
35
+ }
36
+
37
+ private fun listener(promise: Promise, operation: String) = object : Printer.Listener {
38
+ override fun onPrinterError(printerError: PrinterError?) {
39
+ val message = "Printer $operation failed. Cause: ${printerError?.cause}, " +
40
+ "error code: ${printerError?.code}, request ID: ${printerError?.requestId} " +
41
+ "(${Utils.deviceContext()})"
42
+ Log.e(TAG, message)
43
+ promise.reject("GERTEC_PRINTER_ERROR", message)
44
+ }
45
+
46
+ override fun onPrinterSuccessful(var1: Int) {
47
+ promise.resolve(true)
48
+ }
49
+ }
50
+
51
+ // Throwable (e nao so Exception): falha da lib nativa (UnsatisfiedLinkError, NoClassDefFoundError) e Error.
52
+ private fun reject(promise: Promise, operation: String, t: Throwable) {
53
+ val message = "Printer $operation threw ${t.javaClass.name}: ${t.message} (${Utils.deviceContext()})"
54
+ Log.e(TAG, message, t)
55
+ promise.reject("GERTEC_PRINTER_ERROR", message, t)
56
+ }
57
+
58
+ private companion object {
59
+ const val TAG = "MeSmartGertec"
37
60
  }
38
61
  }
@@ -18,11 +18,21 @@ object Utils {
18
18
  return Build.MODEL.equals("A910", true)
19
19
  }
20
20
 
21
- /** Cobre GPOS780 e GPOS780S (mesma familia Gertec, mesmo SDK). */
21
+ /** Cobre GPOS780 e GPOS780S (mesma familia Gertec). */
22
22
  fun isGPOS780(): Boolean {
23
23
  return Build.MODEL.startsWith("GPOS780", true)
24
24
  }
25
25
 
26
+ /** GPOS780S: nao inicializa o GEDI da libgedi 1.16, usa o SDK Neo (ver com.gertec.NeoSdkLoader). */
27
+ fun isGPOS780S(): Boolean {
28
+ return Build.MODEL.startsWith("GPOS780S", true)
29
+ }
30
+
31
+ /** Contexto do aparelho p/ mensagens de erro do Gertec (modelo + Android), p/ debugar em campo. */
32
+ fun deviceContext(): String {
33
+ return "model=${Build.MODEL}, android=${Build.VERSION.RELEASE} (sdk ${Build.VERSION.SDK_INT})"
34
+ }
35
+
26
36
  fun isT8(): Boolean {
27
37
  return Build.MODEL.equals("T8", true)
28
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mesmart",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Native methods for integrating with Sunmi P2-B/P2-A11 POS, Pax A910/A920, Pagbank's Plugpag payment SDK, Stone's payment SDK, and Paystore's payment SDK.",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -27,7 +27,7 @@
27
27
  "lint": "eslint \"**/*.{js,ts,tsx}\"",
28
28
  "clean": "del-cli android/build example/android/build example/android/app/build lib",
29
29
  "prepare": "bob build",
30
- "release": "release-it"
30
+ "release": "bash scripts/release.sh"
31
31
  },
32
32
  "keywords": [
33
33
  "react-native",