@unif/react-native-hms-scan 0.6.2 → 2.0.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 (156) hide show
  1. package/README.md +3 -2
  2. package/android/src/main/AndroidManifest.xml +0 -9
  3. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanImage.kt +135 -0
  4. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanModule.kt +163 -147
  5. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanResultMapper.kt +17 -9
  6. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanView.kt +57 -19
  7. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanViewManager.kt +1 -1
  8. package/ios/HmsScanModule.mm +13 -37
  9. package/ios/HmsScanResultMapper.h +4 -3
  10. package/ios/HmsScanResultMapper.mm +31 -19
  11. package/ios/HmsScanView.h +1 -1
  12. package/ios/HmsScanView.mm +140 -25
  13. package/lib/module/HmsScanView/HmsScanView.js +66 -0
  14. package/lib/module/HmsScanView/HmsScanView.js.map +1 -0
  15. package/lib/module/HmsScanView/HmsScanView.web.js +26 -0
  16. package/lib/module/HmsScanView/HmsScanView.web.js.map +1 -0
  17. package/lib/module/HmsScanView/types.js +4 -0
  18. package/lib/module/HmsScanView/types.js.map +1 -0
  19. package/lib/module/HmsScanViewNativeComponent.ts +7 -5
  20. package/lib/module/NativeHmsScan.js +1 -1
  21. package/lib/module/NativeHmsScan.js.map +1 -1
  22. package/lib/module/ScanError.js +12 -0
  23. package/lib/module/ScanError.js.map +1 -0
  24. package/lib/module/Scanner/DeniedOverlay.js +1 -1
  25. package/lib/module/Scanner/DeniedOverlay.js.map +1 -1
  26. package/lib/module/Scanner/ResultFail.js +3 -2
  27. package/lib/module/Scanner/ResultFail.js.map +1 -1
  28. package/lib/module/Scanner/ResultFocus/ResultFocus.js +56 -0
  29. package/lib/module/Scanner/ResultFocus/ResultFocus.js.map +1 -0
  30. package/lib/module/Scanner/ResultFocus/styles.js +39 -0
  31. package/lib/module/Scanner/ResultFocus/styles.js.map +1 -0
  32. package/lib/module/Scanner/ResultFocus/types.js +4 -0
  33. package/lib/module/Scanner/ResultFocus/types.js.map +1 -0
  34. package/lib/module/Scanner/Scanner.js +11 -310
  35. package/lib/module/Scanner/Scanner.js.map +1 -1
  36. package/lib/module/Scanner/Scanner.web.js +29 -0
  37. package/lib/module/Scanner/Scanner.web.js.map +1 -0
  38. package/lib/module/Scanner/ScannerInner.js +310 -0
  39. package/lib/module/Scanner/ScannerInner.js.map +1 -0
  40. package/lib/module/Scanner/styles.js +11 -0
  41. package/lib/module/Scanner/styles.js.map +1 -0
  42. package/lib/module/Scanner/types.js +4 -0
  43. package/lib/module/Scanner/types.js.map +1 -0
  44. package/lib/module/constants.js +20 -0
  45. package/lib/module/constants.js.map +1 -0
  46. package/lib/module/decodeImage.js +29 -24
  47. package/lib/module/decodeImage.js.map +1 -1
  48. package/lib/module/decodeImage.web.js +7 -0
  49. package/lib/module/decodeImage.web.js.map +1 -0
  50. package/lib/module/errors.js +24 -0
  51. package/lib/module/errors.js.map +1 -0
  52. package/lib/module/format.js +53 -47
  53. package/lib/module/format.js.map +1 -1
  54. package/lib/module/index.js +5 -19
  55. package/lib/module/index.js.map +1 -1
  56. package/lib/module/index.web.js +8 -0
  57. package/lib/module/index.web.js.map +1 -0
  58. package/lib/module/mock.js +2 -23
  59. package/lib/module/mock.js.map +1 -1
  60. package/lib/module/permissions.js +24 -20
  61. package/lib/module/permissions.js.map +1 -1
  62. package/lib/module/permissions.web.js +10 -0
  63. package/lib/module/permissions.web.js.map +1 -0
  64. package/lib/module/types.js +1 -45
  65. package/lib/module/types.js.map +1 -1
  66. package/lib/typescript/src/HmsScanView/HmsScanView.d.ts +3 -0
  67. package/lib/typescript/src/HmsScanView/HmsScanView.d.ts.map +1 -0
  68. package/lib/typescript/src/HmsScanView/HmsScanView.web.d.ts +3 -0
  69. package/lib/typescript/src/HmsScanView/HmsScanView.web.d.ts.map +1 -0
  70. package/lib/typescript/src/HmsScanView/types.d.ts +12 -0
  71. package/lib/typescript/src/HmsScanView/types.d.ts.map +1 -0
  72. package/lib/typescript/src/HmsScanViewNativeComponent.d.ts +6 -4
  73. package/lib/typescript/src/HmsScanViewNativeComponent.d.ts.map +1 -1
  74. package/lib/typescript/src/NativeHmsScan.d.ts +3 -5
  75. package/lib/typescript/src/NativeHmsScan.d.ts.map +1 -1
  76. package/lib/typescript/src/ScanError.d.ts +7 -0
  77. package/lib/typescript/src/ScanError.d.ts.map +1 -0
  78. package/lib/typescript/src/Scanner/DeniedOverlay.d.ts.map +1 -1
  79. package/lib/typescript/src/Scanner/ResultFail.d.ts +2 -1
  80. package/lib/typescript/src/Scanner/ResultFail.d.ts.map +1 -1
  81. package/lib/typescript/src/Scanner/ResultFocus/ResultFocus.d.ts +4 -0
  82. package/lib/typescript/src/Scanner/ResultFocus/ResultFocus.d.ts.map +1 -0
  83. package/lib/typescript/src/Scanner/ResultFocus/styles.d.ts +36 -0
  84. package/lib/typescript/src/Scanner/ResultFocus/styles.d.ts.map +1 -0
  85. package/lib/typescript/src/Scanner/ResultFocus/types.d.ts +8 -0
  86. package/lib/typescript/src/Scanner/ResultFocus/types.d.ts.map +1 -0
  87. package/lib/typescript/src/Scanner/Scanner.d.ts +2 -45
  88. package/lib/typescript/src/Scanner/Scanner.d.ts.map +1 -1
  89. package/lib/typescript/src/Scanner/Scanner.web.d.ts +3 -0
  90. package/lib/typescript/src/Scanner/Scanner.web.d.ts.map +1 -0
  91. package/lib/typescript/src/Scanner/ScannerInner.d.ts +3 -0
  92. package/lib/typescript/src/Scanner/ScannerInner.d.ts.map +1 -0
  93. package/lib/typescript/src/Scanner/styles.d.ts +7 -0
  94. package/lib/typescript/src/Scanner/styles.d.ts.map +1 -0
  95. package/lib/typescript/src/Scanner/types.d.ts +20 -0
  96. package/lib/typescript/src/Scanner/types.d.ts.map +1 -0
  97. package/lib/typescript/src/constants.d.ts +8 -0
  98. package/lib/typescript/src/constants.d.ts.map +1 -0
  99. package/lib/typescript/src/decodeImage.d.ts +2 -12
  100. package/lib/typescript/src/decodeImage.d.ts.map +1 -1
  101. package/lib/typescript/src/decodeImage.web.d.ts +3 -0
  102. package/lib/typescript/src/decodeImage.web.d.ts.map +1 -0
  103. package/lib/typescript/src/errors.d.ts +6 -0
  104. package/lib/typescript/src/errors.d.ts.map +1 -0
  105. package/lib/typescript/src/format.d.ts +15 -12
  106. package/lib/typescript/src/format.d.ts.map +1 -1
  107. package/lib/typescript/src/index.d.ts +6 -7
  108. package/lib/typescript/src/index.d.ts.map +1 -1
  109. package/lib/typescript/src/index.web.d.ts +9 -0
  110. package/lib/typescript/src/index.web.d.ts.map +1 -0
  111. package/lib/typescript/src/mock.d.ts +9 -8
  112. package/lib/typescript/src/mock.d.ts.map +1 -1
  113. package/lib/typescript/src/permissions.d.ts +4 -8
  114. package/lib/typescript/src/permissions.d.ts.map +1 -1
  115. package/lib/typescript/src/permissions.web.d.ts +4 -0
  116. package/lib/typescript/src/permissions.web.d.ts.map +1 -0
  117. package/lib/typescript/src/types.d.ts +31 -56
  118. package/lib/typescript/src/types.d.ts.map +1 -1
  119. package/package.json +16 -9
  120. package/src/HmsScanView/HmsScanView.tsx +67 -0
  121. package/src/HmsScanView/HmsScanView.web.tsx +22 -0
  122. package/src/HmsScanView/types.ts +17 -0
  123. package/src/HmsScanViewNativeComponent.ts +7 -5
  124. package/src/NativeHmsScan.ts +3 -5
  125. package/src/ScanError.ts +13 -0
  126. package/src/Scanner/DeniedOverlay.tsx +4 -2
  127. package/src/Scanner/ResultFail.tsx +23 -5
  128. package/src/Scanner/ResultFocus/ResultFocus.tsx +43 -0
  129. package/src/Scanner/ResultFocus/styles.ts +25 -0
  130. package/src/Scanner/ResultFocus/types.ts +8 -0
  131. package/src/Scanner/Scanner.tsx +14 -432
  132. package/src/Scanner/Scanner.web.tsx +26 -0
  133. package/src/Scanner/ScannerInner.tsx +391 -0
  134. package/src/Scanner/styles.ts +6 -0
  135. package/src/Scanner/types.ts +36 -0
  136. package/src/constants.ts +55 -0
  137. package/src/decodeImage.ts +35 -29
  138. package/src/decodeImage.web.ts +8 -0
  139. package/src/errors.ts +26 -0
  140. package/src/format.ts +77 -76
  141. package/src/index.tsx +17 -32
  142. package/src/index.web.tsx +22 -0
  143. package/src/mock.ts +21 -29
  144. package/src/permissions.ts +33 -20
  145. package/src/permissions.web.ts +9 -0
  146. package/src/types.ts +38 -126
  147. package/lib/module/HmsScanView.js +0 -43
  148. package/lib/module/HmsScanView.js.map +0 -1
  149. package/lib/module/Scanner/ResultFocus.js +0 -171
  150. package/lib/module/Scanner/ResultFocus.js.map +0 -1
  151. package/lib/typescript/src/HmsScanView.d.ts +0 -33
  152. package/lib/typescript/src/HmsScanView.d.ts.map +0 -1
  153. package/lib/typescript/src/Scanner/ResultFocus.d.ts +0 -14
  154. package/lib/typescript/src/Scanner/ResultFocus.d.ts.map +0 -1
  155. package/src/HmsScanView.tsx +0 -87
  156. package/src/Scanner/ResultFocus.tsx +0 -122
@@ -2,6 +2,7 @@ package com.unif.reactnativehmsscan
2
2
 
3
3
  import android.app.Activity
4
4
  import android.content.Context
5
+ import android.content.pm.PackageManager
5
6
  import android.os.Bundle
6
7
  import android.widget.FrameLayout
7
8
  import com.facebook.react.bridge.Arguments
@@ -42,14 +43,12 @@ class HmsScanView(
42
43
  private var paused: Boolean = false
43
44
  private var torch: Boolean = false
44
45
  private var actualTorchOn: Boolean = false
45
- private var torchAvailable: Boolean = false
46
+ private var lowLight: Boolean? = null
46
47
 
47
48
  // True while this view is between onAttachedToWindow and onDetachedFromWindow.
48
49
  private var attached: Boolean = false
49
-
50
- init {
51
- themedReactContext.addLifecycleEventListener(this)
52
- }
50
+ private var listening: Boolean = false
51
+ private var lastTorchState: Triple<Boolean, Boolean, Boolean?>? = null
53
52
 
54
53
  // ── Props (called from the ViewManager) ───────────────────────────────────
55
54
 
@@ -81,6 +80,10 @@ class HmsScanView(
81
80
  override fun onAttachedToWindow() {
82
81
  super.onAttachedToWindow()
83
82
  attached = true
83
+ if (!listening) {
84
+ listening = true
85
+ themedReactContext.addLifecycleEventListener(this)
86
+ }
84
87
  if (remoteView == null) {
85
88
  buildRemoteView()
86
89
  }
@@ -91,6 +94,7 @@ class HmsScanView(
91
94
  super.onDetachedFromWindow()
92
95
  attached = false
93
96
  teardownRemoteView()
97
+ removeLifecycleListener()
94
98
  }
95
99
 
96
100
  // ── Host (Activity) lifecycle ─────────────────────────────────────────────
@@ -109,7 +113,14 @@ class HmsScanView(
109
113
 
110
114
  override fun onHostDestroy() {
111
115
  teardownRemoteView()
112
- themedReactContext.removeLifecycleEventListener(this)
116
+ removeLifecycleListener()
117
+ }
118
+
119
+ private fun removeLifecycleListener() {
120
+ if (listening) {
121
+ listening = false
122
+ themedReactContext.removeLifecycleEventListener(this)
123
+ }
113
124
  }
114
125
 
115
126
  // ── RemoteView build / teardown ───────────────────────────────────────────
@@ -142,9 +153,15 @@ class HmsScanView(
142
153
 
143
154
  val view = builder.build()
144
155
 
145
- view.setOnResultCallback(OnResultCallback { result -> onScanResult(result) })
156
+ view.setOnResultCallback(
157
+ OnResultCallback { result ->
158
+ if (remoteView === view && !paused) onScanResult(result)
159
+ },
160
+ )
146
161
  view.setOnLightVisibleCallback(
147
- OnLightVisibleCallBack { visible -> onTorchVisible(visible) },
162
+ OnLightVisibleCallBack { visible ->
163
+ if (remoteView === view) onTorchVisible(visible)
164
+ },
148
165
  )
149
166
 
150
167
  // onCreate must run after build() and before addView (per HMS docs/demo).
@@ -172,6 +189,8 @@ class HmsScanView(
172
189
 
173
190
  private fun teardownRemoteView() {
174
191
  val view = remoteView ?: return
192
+ // Invalidate callbacks before the SDK starts its asynchronous teardown.
193
+ remoteView = null
175
194
  try {
176
195
  view.onPause()
177
196
  view.onStop()
@@ -180,8 +199,9 @@ class HmsScanView(
180
199
  // Defensive: never let teardown crash the host.
181
200
  }
182
201
  removeView(view)
183
- remoteView = null
184
202
  actualTorchOn = false
203
+ lowLight = null
204
+ lastTorchState = null
185
205
  }
186
206
 
187
207
  /** Rebuild the RemoteView in place to honour a changed build-time prop. */
@@ -207,15 +227,17 @@ class HmsScanView(
207
227
 
208
228
  private fun applyTorch() {
209
229
  val view = remoteView ?: return
230
+ var commandFailed = false
210
231
  try {
211
232
  // switchLight() toggles; only flip when the current state differs from target.
212
233
  if (view.lightStatus != torch) {
213
- view.switchLight()
234
+ commandFailed = !view.switchLight()
214
235
  }
215
236
  } catch (_: Throwable) {
216
- // Ignore: device may have no flash.
237
+ commandFailed = true
217
238
  }
218
- emitTorchStatus(view)
239
+ // A rejected command must acknowledge even an unchanged state so JS can retry.
240
+ emitTorchState(view, force = commandFailed)
219
241
  }
220
242
 
221
243
  /** Resolve the hosting Activity required by RemoteView.Builder.setContext(). */
@@ -226,8 +248,14 @@ class HmsScanView(
226
248
  private fun onScanResult(result: Array<HmsScan?>?) {
227
249
  // Drop empty callbacks (continuous mode can fire with nothing useful).
228
250
  if (result == null || result.isEmpty()) return
229
- val json = HmsScanResultMapper.toJson(result)
230
- // toJson skips value-less hits; avoid emitting an empty "[]" event.
251
+ val json =
252
+ try {
253
+ HmsScanResultMapper.toJson(result)
254
+ } catch (error: InvalidScanResponse) {
255
+ emitError("E_INVALID_RESPONSE", error.message ?: "Invalid scan result")
256
+ return
257
+ }
258
+ // A batch containing only null vendor entries has no result to emit.
231
259
  if (json == "[]") return
232
260
  emitEvent(
233
261
  "topScanResult",
@@ -238,12 +266,15 @@ class HmsScanView(
238
266
  }
239
267
 
240
268
  private fun onTorchVisible(visible: Boolean) {
241
- torchAvailable = visible
269
+ lowLight = visible
242
270
  val view = remoteView ?: return
243
- emitTorchStatus(view)
271
+ emitTorchState(view)
244
272
  }
245
273
 
246
- private fun emitTorchStatus(view: RemoteView) {
274
+ private fun emitTorchState(
275
+ view: RemoteView,
276
+ force: Boolean = false,
277
+ ) {
247
278
  val on =
248
279
  try {
249
280
  view.lightStatus
@@ -251,10 +282,17 @@ class HmsScanView(
251
282
  actualTorchOn
252
283
  }
253
284
  actualTorchOn = on
285
+ val available = context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)
286
+ val state = Triple(on, available, lowLight)
287
+ if (!force && lastTorchState == state) return
288
+ lastTorchState = state
254
289
  emitEvent(
255
- "topTorchStatus",
290
+ "topTorchState",
256
291
  Arguments.createMap().apply {
257
- putBoolean("available", torchAvailable)
292
+ putBoolean("available", available)
293
+ putBoolean("hasAvailable", true)
294
+ putBoolean("lowLight", lowLight ?: false)
295
+ putBoolean("hasLowLight", lowLight != null)
258
296
  putBoolean("on", on)
259
297
  },
260
298
  )
@@ -66,7 +66,7 @@ class HmsScanViewManager :
66
66
  mapOf(
67
67
  "topScanResult" to mapOf("registrationName" to "onScanResult"),
68
68
  "topScanError" to mapOf("registrationName" to "onScanError"),
69
- "topTorchStatus" to mapOf("registrationName" to "onTorchStatus"),
69
+ "topTorchState" to mapOf("registrationName" to "onTorchState"),
70
70
  )
71
71
 
72
72
  companion object {
@@ -11,18 +11,18 @@
11
11
 
12
12
  #import <ScanKitFrameWork/ScanKitFrameWork.h>
13
13
 
14
- // Error codes mirror HmsScanErrorCode in src/types.ts.
14
+ // Native codes are normalized to ScanFailure at the public capability boundary.
15
15
  static NSString *const kErrImageLoadFailed = @"E_IMAGE_LOAD_FAILED";
16
16
  static NSString *const kErrDecodeFailed = @"E_DECODE_FAILED";
17
17
 
18
18
  @implementation HmsScanModule
19
19
 
20
- // Export under the JS-visible name "HmsScan" (TurboModuleRegistry.getEnforcing<Spec>('HmsScan')).
20
+ // Export under the JS-visible TurboModule name "HmsScan".
21
21
  RCT_EXPORT_MODULE(HmsScan)
22
22
 
23
23
  #pragma mark - Permission status mapping
24
24
 
25
- // AVAuthorizationStatus -> our CameraPermissionStatus string.
25
+ // AVAuthorizationStatus -> our ScanCameraPermission string.
26
26
  // authorized -> granted; notDetermined -> undetermined; denied/restricted -> blocked.
27
27
  + (NSString *)stringForAuthorizationStatus:(AVAuthorizationStatus)status {
28
28
  switch (status) {
@@ -34,7 +34,7 @@ RCT_EXPORT_MODULE(HmsScan)
34
34
  case AVAuthorizationStatusRestricted:
35
35
  return @"blocked";
36
36
  default:
37
- return @"undetermined";
37
+ return @"unknown";
38
38
  }
39
39
  }
40
40
 
@@ -66,45 +66,18 @@ RCT_EXPORT_MODULE(HmsScan)
66
66
 
67
67
  #pragma mark - Image loading
68
68
 
69
- // Load a UIImage from a local URI: file:// , an absolute/relative file path,
70
- // or a data: URI. Remote URLs and Photos (ph:// / assets-library://) are not
71
- // supported here -> returns nil so the caller rejects E_IMAGE_LOAD_FAILED.
69
+ // Read only the local file prepared by the caller.
72
70
  + (nullable UIImage *)imageForURI:(NSString *)uri {
73
71
  if (uri.length == 0) {
74
72
  return nil;
75
73
  }
76
74
 
77
75
  NSURL *url = [NSURL URLWithString:uri];
78
- NSString *scheme = url.scheme.lowercaseString;
79
-
80
- // data: URI (base64 or otherwise) -> decode bytes directly.
81
- if ([scheme isEqualToString:@"data"]) {
82
- NSData *data = [NSData dataWithContentsOfURL:url];
83
- return data ? [UIImage imageWithData:data] : nil;
84
- }
85
-
86
- // file:// URI -> use its filesystem path.
87
- if ([scheme isEqualToString:@"file"]) {
76
+ if (url.isFileURL) {
88
77
  NSString *path = url.path;
89
78
  return path ? [UIImage imageWithContentsOfFile:path] : nil;
90
79
  }
91
80
 
92
- // No scheme (or a Windows-like drive letter is irrelevant on iOS): treat the
93
- // whole string as a filesystem path. Also strip a leading file path that may
94
- // have arrived percent-encoded.
95
- if (scheme == nil || scheme.length == 0) {
96
- UIImage *image = [UIImage imageWithContentsOfFile:uri];
97
- if (image == nil) {
98
- NSString *decoded = [uri stringByRemovingPercentEncoding];
99
- if (decoded != nil && ![decoded isEqualToString:uri]) {
100
- image = [UIImage imageWithContentsOfFile:decoded];
101
- }
102
- }
103
- return image;
104
- }
105
-
106
- // ph:// , assets-library:// , http(s):// , content:// (Android-only) etc. are
107
- // intentionally unsupported on iOS in this bridge.
108
81
  return nil;
109
82
  }
110
83
 
@@ -114,11 +87,13 @@ RCT_EXPORT_MODULE(HmsScan)
114
87
  formatsCsv:(NSString *)formatsCsv
115
88
  resolve:(RCTPromiseResolveBlock)resolve
116
89
  reject:(RCTPromiseRejectBlock)reject {
90
+ if ([HmsScanResultMapper unsupportedFormatInCsv:formatsCsv] != nil) {
91
+ reject(@"E_UNSUPPORTED_FORMAT", @"iOS ScanKit 不支持请求的码制", nil);
92
+ return;
93
+ }
117
94
  UIImage *image = [HmsScanModule imageForURI:uri];
118
95
  if (image == nil) {
119
- reject(kErrImageLoadFailed,
120
- [NSString stringWithFormat:@"无法从 URI 加载图片(仅支持 file:// / 绝对路径 / data:):%@", uri ?: @"(nil)"],
121
- nil);
96
+ reject(kErrImageLoadFailed, [NSString stringWithFormat:@"无法读取本地图片文件:%@", uri ?: @"(nil)"], nil);
122
97
  return;
123
98
  }
124
99
 
@@ -133,7 +108,8 @@ RCT_EXPORT_MODULE(HmsScan)
133
108
  // JS parseResultsJson handles the empty case.
134
109
  resolve(json);
135
110
  } @catch (NSException *exception) {
136
- reject(kErrDecodeFailed, exception.reason ?: @"HUAWEI Scan Kit 解码图片时发生异常", nil);
111
+ reject([exception.name isEqualToString:@"HmsInvalidResponse"] ? @"E_INVALID_RESPONSE" : kErrDecodeFailed,
112
+ exception.reason ?: @"HUAWEI Scan Kit 解码图片时发生异常", nil);
137
113
  }
138
114
  }
139
115
 
@@ -18,8 +18,9 @@
18
18
  NS_ASSUME_NONNULL_BEGIN
19
19
 
20
20
  @interface HmsScanResultMapper : NSObject
21
+ + (nullable NSString *)unsupportedFormatInCsv:(NSString *)csv;
21
22
 
22
- /// Parse a comma-separated list of our BarcodeFormat strings into the
23
+ /// Parse a comma-separated list of our ScanFormat strings into the
23
24
  /// `HmsScanOptions.scanFormatType` bitmask (HMSScanFormatTypeCode OR-ed).
24
25
  /// An empty/whitespace-only string means "all formats".
25
26
  + (unsigned int)scanFormatTypeFromCsv:(nullable NSString *)csv;
@@ -30,11 +31,11 @@ NS_ASSUME_NONNULL_BEGIN
30
31
  + (nullable NSDictionary *)scanResultFromHuaweiDict:(nullable NSDictionary *)dict;
31
32
 
32
33
  /// Map an array of HUAWEI result dictionaries into an array of ScanResult
33
- /// dictionaries (dropping entries without a usable value).
34
+ /// dictionaries. Invalid required fields raise HmsInvalidResponse.
34
35
  + (NSArray<NSDictionary *> *)scanResultsFromHuaweiArray:(nullable NSArray *)array;
35
36
 
36
37
  /// Serialize an array of ScanResult dictionaries to a JSON string.
37
- /// Never returns nil: on failure it returns "[]".
38
+ /// Invalid input or serialization failures raise HmsInvalidResponse.
38
39
  + (NSString *)jsonStringFromScanResults:(nullable NSArray<NSDictionary *> *)results;
39
40
 
40
41
  /// Convenience: HUAWEI multi-decode array -> ScanResult[] JSON string.
@@ -6,6 +6,7 @@
6
6
  #import "HmsScanResultMapper.h"
7
7
 
8
8
  #import <ScanKitFrameWork/ScanKitFrameWork.h>
9
+ #include <cmath>
9
10
 
10
11
  // NOTE on naming collisions: HMSScanFormatTypeCode (in HmsScanFormat.h) declares
11
12
  // *global, unscoped* enum constants named QR_CODE, EAN_13, DATA_MATRIX, etc.
@@ -17,7 +18,7 @@
17
18
 
18
19
  #pragma mark - CSV -> scanFormatType bitmask
19
20
 
20
- // Maps one of our BarcodeFormat tokens to the matching HMSScanFormatTypeCode bit.
21
+ // Maps one of our ScanFormat tokens to the matching HMSScanFormatTypeCode bit.
21
22
  // Returns 0 for tokens HUAWEI iOS does not support (e.g. MULTI_FUNCTIONAL) or
22
23
  // UNKNOWN, so they contribute nothing to the OR-ed mask.
23
24
  + (unsigned int)bitForFormatToken:(NSString *)token {
@@ -53,6 +54,16 @@
53
54
  return bit ? (unsigned int)bit.unsignedIntValue : 0u;
54
55
  }
55
56
 
57
+ + (nullable NSString *)unsupportedFormatInCsv:(NSString *)csv {
58
+ if (csv.length == 0)
59
+ return nil;
60
+ for (NSString *token in [csv componentsSeparatedByString:@","]) {
61
+ if ([self bitForFormatToken:token] == 0u)
62
+ return token;
63
+ }
64
+ return nil;
65
+ }
66
+
56
67
  + (unsigned int)scanFormatTypeFromCsv:(NSString *)csv {
57
68
  NSString *trimmed = [csv stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
58
69
  if (trimmed.length == 0) {
@@ -63,12 +74,10 @@
63
74
  for (NSString *token in [trimmed componentsSeparatedByString:@","]) {
64
75
  mask |= [self bitForFormatToken:token];
65
76
  }
66
- // If nothing resolved (e.g. only MULTI_FUNCTIONAL/UNKNOWN was requested),
67
- // fall back to ALL so we still attempt a decode rather than scanning nothing.
68
- return mask == 0u ? (unsigned int)ALL : mask;
77
+ return mask; // Unsupported tokens are rejected by the capability boundary.
69
78
  }
70
79
 
71
- #pragma mark - formatValue -> our BarcodeFormat string
80
+ #pragma mark - formatValue -> our ScanFormat string
72
81
 
73
82
  // HUAWEI's `formatValue` is documented as the HMSScanFormatTypeCode integer.
74
83
  // We tolerate it being delivered as an NSNumber (bit value) OR an NSString
@@ -207,9 +216,6 @@
207
216
  if ([v isKindOfClass:[NSNumber class]]) {
208
217
  return (NSNumber *)v;
209
218
  }
210
- if ([v isKindOfClass:[NSString class]]) {
211
- return @([(NSString *)v doubleValue]);
212
- }
213
219
  }
214
220
  return nil;
215
221
  }
@@ -228,7 +234,7 @@
228
234
  NSDictionary *p = (NSDictionary *)element;
229
235
  NSNumber *x = [self numberFromPointDict:p keys:@[ @"posX", @"x", @"X" ]];
230
236
  NSNumber *y = [self numberFromPointDict:p keys:@[ @"posY", @"y", @"Y" ]];
231
- if (x != nil && y != nil) {
237
+ if (x != nil && y != nil && std::isfinite(x.doubleValue) && std::isfinite(y.doubleValue)) {
232
238
  [points addObject:@{@"x" : x, @"y" : y}];
233
239
  }
234
240
  }
@@ -241,7 +247,7 @@
241
247
  // Primary key is `text`; tolerate a couple of alternates defensively.
242
248
  for (NSString *key in @[ @"text", @"originalValue", @"showText", @"value" ]) {
243
249
  id v = dict[key];
244
- if ([v isKindOfClass:[NSString class]] && ((NSString *)v).length > 0) {
250
+ if ([v isKindOfClass:[NSString class]] && v != nil) {
245
251
  return (NSString *)v;
246
252
  }
247
253
  }
@@ -255,7 +261,7 @@
255
261
 
256
262
  NSString *value = [self valueFromHuaweiDict:dict];
257
263
  if (value == nil) {
258
- return nil; // no usable decoded text -> drop
264
+ return nil;
259
265
  }
260
266
 
261
267
  NSMutableDictionary *out = [NSMutableDictionary dictionary];
@@ -277,12 +283,16 @@
277
283
 
278
284
  + (NSArray<NSDictionary *> *)scanResultsFromHuaweiArray:(NSArray *)array {
279
285
  NSMutableArray<NSDictionary *> *results = [NSMutableArray array];
286
+ if (array != nil && ![array isKindOfClass:[NSArray class]]) {
287
+ [NSException raise:@"HmsInvalidResponse" format:@"ScanKit returned a non-array result"];
288
+ }
280
289
  if ([array isKindOfClass:[NSArray class]]) {
281
290
  for (id element in array) {
282
291
  NSDictionary *mapped = [self scanResultFromHuaweiDict:element];
283
- if (mapped != nil) {
284
- [results addObject:mapped];
292
+ if (mapped == nil) {
293
+ [NSException raise:@"HmsInvalidResponse" format:@"ScanKit returned an invalid result"];
285
294
  }
295
+ [results addObject:mapped];
286
296
  }
287
297
  }
288
298
  return [results copy];
@@ -291,17 +301,19 @@
291
301
  #pragma mark - JSON
292
302
 
293
303
  + (NSString *)jsonStringFromScanResults:(NSArray<NSDictionary *> *)results {
294
- NSArray *safe = [results isKindOfClass:[NSArray class]] ? results : @[];
295
- if (![NSJSONSerialization isValidJSONObject:safe]) {
296
- return @"[]";
304
+ if (![results isKindOfClass:[NSArray class]] || ![NSJSONSerialization isValidJSONObject:results]) {
305
+ [NSException raise:@"HmsInvalidResponse" format:@"ScanKit result cannot be serialized"];
297
306
  }
298
307
  NSError *error = nil;
299
- NSData *data = [NSJSONSerialization dataWithJSONObject:safe options:0 error:&error];
308
+ NSData *data = [NSJSONSerialization dataWithJSONObject:results options:0 error:&error];
300
309
  if (data == nil || error != nil) {
301
- return @"[]";
310
+ [NSException raise:@"HmsInvalidResponse" format:@"ScanKit result cannot be serialized"];
302
311
  }
303
312
  NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
304
- return json ?: @"[]";
313
+ if (json == nil) {
314
+ [NSException raise:@"HmsInvalidResponse" format:@"ScanKit result cannot be encoded as UTF-8"];
315
+ }
316
+ return json;
305
317
  }
306
318
 
307
319
  + (NSString *)jsonStringFromHuaweiArray:(NSArray *)array {
package/ios/HmsScanView.h CHANGED
@@ -5,7 +5,7 @@
5
5
  // Fabric component registered under the name "HmsScanView". Hosts HUAWEI Scan
6
6
  // Kit's HmsCustomScanViewController as an embedded child view controller and
7
7
  // bridges its delegate callbacks to the codegen events
8
- // (onScanResult / onScanError / onTorchStatus).
8
+ // (onScanResult / onScanError / onTorchState).
9
9
  //
10
10
 
11
11
  #import <React/RCTViewComponentView.h>