raxon-barcode-scanner 0.1.0 → 0.1.1

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.
@@ -1,7 +1,7 @@
1
1
  import { registerWebModule, NativeModule } from 'expo';
2
2
  class RaxonBarcodeScannerModule extends NativeModule {
3
3
  startListening(_options) {
4
- console.warn('raxon-barcode-scanner is only supported on Android.');
4
+ console.warn('raxon-barcode-scanner is only supported on iOS and Android.');
5
5
  }
6
6
  stopListening() {
7
7
  // no-op
@@ -1 +1 @@
1
- {"version":3,"file":"RaxonBarcodeScannerModule.web.js","sourceRoot":"","sources":["../src/RaxonBarcodeScannerModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAOvD,MAAM,yBAA0B,SAAQ,YAA6C;IACnF,cAAc,CAAC,QAAgC;QAC7C,OAAO,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;IACtE,CAAC;IAED,aAAa;QACX,QAAQ;IACV,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,yBAAyB,EAAE,qBAAqB,CAAC,CAAC","sourcesContent":["import { registerWebModule, NativeModule } from 'expo';\n\nimport {\n BarcodeScannerOptions,\n RaxonBarcodeScannerModuleEvents,\n} from './RaxonBarcodeScanner.types';\n\nclass RaxonBarcodeScannerModule extends NativeModule<RaxonBarcodeScannerModuleEvents> {\n startListening(_options?: BarcodeScannerOptions): void {\n console.warn('raxon-barcode-scanner is only supported on Android.');\n }\n\n stopListening(): void {\n // no-op\n }\n}\n\nexport default registerWebModule(RaxonBarcodeScannerModule, 'RaxonBarcodeScanner');\n"]}
1
+ {"version":3,"file":"RaxonBarcodeScannerModule.web.js","sourceRoot":"","sources":["../src/RaxonBarcodeScannerModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAOvD,MAAM,yBAA0B,SAAQ,YAA6C;IACnF,cAAc,CAAC,QAAgC;QAC7C,OAAO,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IAC9E,CAAC;IAED,aAAa;QACX,QAAQ;IACV,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,yBAAyB,EAAE,qBAAqB,CAAC,CAAC","sourcesContent":["import { registerWebModule, NativeModule } from 'expo';\n\nimport {\n BarcodeScannerOptions,\n RaxonBarcodeScannerModuleEvents,\n} from './RaxonBarcodeScanner.types';\n\nclass RaxonBarcodeScannerModule extends NativeModule<RaxonBarcodeScannerModuleEvents> {\n startListening(_options?: BarcodeScannerOptions): void {\n console.warn('raxon-barcode-scanner is only supported on iOS and Android.');\n }\n\n stopListening(): void {\n // no-op\n }\n}\n\nexport default registerWebModule(RaxonBarcodeScannerModule, 'RaxonBarcodeScanner');\n"]}
@@ -1,5 +1,8 @@
1
1
  {
2
- "platforms": ["android"],
2
+ "platforms": ["ios", "android"],
3
+ "ios": {
4
+ "modules": ["RaxonBarcodeScannerModule"]
5
+ },
3
6
  "android": {
4
7
  "modules": ["expo.modules.raxonbarcodescanner.RaxonBarcodeScannerModule"]
5
8
  }
@@ -0,0 +1 @@
1
+ #import <ExpoModulesCore/ExpoModulesCore.h>
@@ -0,0 +1,21 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'RaxonBarcodeScanner'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platform = :ios, '13.4'
14
+ s.swift_version = '5.4'
15
+ s.source = { git: 'https://github.com/raxonltd/raxon-barcode-scanner.git' }
16
+ s.static_framework = true
17
+
18
+ s.dependency 'ExpoModulesCore'
19
+
20
+ s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
21
+ end
@@ -0,0 +1,326 @@
1
+ import ExpoModulesCore
2
+ import UIKit
3
+
4
+ // MARK: - RaxonBarcodeScannerModule
5
+
6
+ /// iOS için Bluetooth ve HID barkod okuyucu desteği sağlayan Expo modülü.
7
+ /// Harici klavye olarak davranan Bluetooth barkod okuyucuların tuş vuruşlarını yakalar.
8
+ public final class RaxonBarcodeScannerModule: Module {
9
+ private var isListening = false
10
+ private var captureKeyboard = true
11
+ private var keyboardHandler: ExternalKeyboardHandler?
12
+
13
+ public required init(appContext: AppContext) {
14
+ super.init(appContext: appContext)
15
+ }
16
+
17
+ public func definition() -> ModuleDefinition {
18
+ Name("RaxonBarcodeScanner")
19
+
20
+ Events("onBarcodeScanned")
21
+
22
+ Function("startListening") { (options: [String: Any]?) in
23
+ self.startListening(options: options)
24
+ }
25
+
26
+ Function("stopListening") {
27
+ self.stopListening()
28
+ }
29
+
30
+ OnDestroy {
31
+ self.stopListening()
32
+ }
33
+ }
34
+
35
+ private func startListening(options: [String: Any]?) {
36
+ guard !isListening else {
37
+ stopListening()
38
+ }
39
+
40
+ captureKeyboard = options?["captureKeyboard"] as? Bool ?? true
41
+
42
+ if captureKeyboard {
43
+ setupKeyboardCapture()
44
+ }
45
+
46
+ isListening = true
47
+ }
48
+
49
+ private func stopListening() {
50
+ teardownKeyboardCapture()
51
+ isListening = false
52
+ }
53
+
54
+ private func setupKeyboardCapture() {
55
+ guard let viewController = appContext?.currentViewController else {
56
+ return
57
+ }
58
+
59
+ if #available(iOS 13.4, *) {
60
+ keyboardHandler = ExternalKeyboardHandler(
61
+ viewController: viewController,
62
+ onBarcodeScanned: { [weak self] code in
63
+ self?.sendEvent("onBarcodeScanned", ["code": code])
64
+ }
65
+ )
66
+ keyboardHandler?.startListening()
67
+ }
68
+ }
69
+
70
+ private func teardownKeyboardCapture() {
71
+ keyboardHandler?.stopListening()
72
+ keyboardHandler = nil
73
+ }
74
+ }
75
+
76
+ // MARK: - ExternalKeyboardHandler
77
+
78
+ /// iOS 13.4+ için harici klavye (Bluetooth barkod okuyucu) girişini yakalayan sınıf.
79
+ /// pressesBegan/pressesEnded API'sini kullanarak fiziksel klavye tuşlarını yakalar.
80
+ @available(iOS 13.4, *)
81
+ private final class ExternalKeyboardHandler {
82
+ private weak var viewController: UIViewController?
83
+ private let onBarcodeScanned: (String) -> Void
84
+ private var keyBuffer: String = ""
85
+ private var lastKeyTime: TimeInterval = 0
86
+ private let bufferTimeout: TimeInterval = 1.0
87
+
88
+ // Terminatör tuş kodları
89
+ private let terminatorKeyCodes: Set<Int> = [
90
+ 36, // Return/Enter
91
+ 76, // Numpad Enter
92
+ 48, // Tab
93
+ 52, // Numpad Equal (bazı okuyucularda)
94
+ 67, // Numpad Asterisk (bazı okuyucularda)
95
+ ]
96
+
97
+ init(viewController: UIViewController, onBarcodeScanned: @escaping (String) -> Void) {
98
+ self.viewController = viewController
99
+ self.onBarcodeScanned = onBarcodeScanned
100
+ }
101
+
102
+ func startListening() {
103
+ // pressesBegan/pressesEnded metodlarını sarmalamak için swizzling kullanacağız
104
+ swizzlePressesMethods()
105
+
106
+ // Ayrıca UIResponder chain'den gelen tuş olaylarını da dinle
107
+ setupKeyCommandHandling()
108
+ }
109
+
110
+ func stopListening() {
111
+ unswizzlePressesMethods()
112
+ keyBuffer = ""
113
+ }
114
+
115
+ private func setupKeyCommandHandling() {
116
+ // UIKeyCommand kullanarak bazı özel tuşları yakalayabiliriz
117
+ // Ancak tüm tuşları yakalamak için pressesBegan/pressesEnded daha iyi
118
+ }
119
+
120
+ // MARK: - Tuş İşleme
121
+
122
+ func handlePress(_ press: UIPress) -> Bool {
123
+ guard let key = press.key else { return false }
124
+
125
+ let keyCode = key.keyCode.rawValue
126
+ let now = Date().timeIntervalSince1970
127
+
128
+ // Zaman aşımı kontrolü - tuşlar arası 1 saniyeden fazla varsa tamponu sıfırla
129
+ if now - lastKeyTime > bufferTimeout {
130
+ keyBuffer = ""
131
+ }
132
+ lastKeyTime = now
133
+
134
+ // Terminatör tuş kontrolü
135
+ if isTerminator(keyCode) {
136
+ if !keyBuffer.isEmpty {
137
+ let code = keyBuffer
138
+ keyBuffer = ""
139
+ onBarcodeScanned(code)
140
+ return true // Tuşu yut
141
+ }
142
+ // Tampon boşken terminatörü normal davranışa bırak
143
+ return false
144
+ }
145
+
146
+ // Karakter tuşu mu?
147
+ if let char = characterFromKey(key) {
148
+ keyBuffer.append(char)
149
+ return true // Tuşu yut (UI'ya ulaşmasını engelle)
150
+ }
151
+
152
+ return false
153
+ }
154
+
155
+ private func isTerminator(_ keyCode: Int) -> Bool {
156
+ return terminatorKeyCodes.contains(keyCode)
157
+ }
158
+
159
+ private func characterFromKey(_ key: UIKey) -> Character? {
160
+ // Karakter önceliği:
161
+ // 1. modifiersiz karakter (temel ASCII)
162
+ // 2. modifiers varsa ve shift varsa büyük harf
163
+
164
+ var char: Character?
165
+
166
+ // UIKey.characters ile dene
167
+ if let chars = key.characters, !chars.isEmpty {
168
+ char = chars.first
169
+ }
170
+
171
+ // UIKey.charactersIgnoringModifiers ile dene (Shift/Ctrl/Alt olmadan)
172
+ if char == nil, let chars = key.charactersIgnoringModifiers, !chars.isEmpty {
173
+ char = chars.first
174
+ }
175
+
176
+ // Eğer hala nil ise, keyCode'dan çevir
177
+ if char == nil {
178
+ char = characterFromKeyCode(key.keyCode.rawValue)
179
+ }
180
+
181
+ return char
182
+ }
183
+
184
+ private func characterFromKeyCode(_ keyCode: Int) -> Character? {
185
+ // iOS fiziksel klavye key kodlarından karakter dönüşümü
186
+ // Bluetooth barkod okuyucular genellikle standart USB HID key kodları kullanır
187
+ let mapping: [Int: Character] = [
188
+ // Rakamlar (ana klavye)
189
+ 23: "0", 22: "1", 26: "2", 20: "3", 25: "4",
190
+ 29: "5", 27: "6", 24: "7", 28: "8", 21: "9",
191
+
192
+ // Numpad rakamlar
193
+ 98: "0", 89: "1", 90: "2", 91: "3", 92: "4",
194
+ 93: "5", 94: "6", 95: "7", 96: "8", 97: "9",
195
+
196
+ // Harfler (ana klavye)
197
+ 12: "q", 13: "w", 14: "e", 15: "r", 17: "t",
198
+ 16: "y", 32: "u", 34: "i", 31: "o", 35: "p",
199
+ 0: "a", 1: "s", 2: "d", 3: "f", 5: "g",
200
+ 4: "h", 38: "k", 37: "j", 40: "l", 6: "z",
201
+ 7: "x", 8: "c", 9: "v", 11: "b", 45: "n",
202
+ 46: "m",
203
+
204
+ // Özel karakterler
205
+ 51: "\u{0008}", // Backspace (silme)
206
+ 117: "\u{007F}", // Delete
207
+ 42: ",", 43: "-", 44: ".", 47: "/",
208
+ 39: "'", 33: "[", 30: "]", 41: ";", 50: "`",
209
+ 115: "\u{001B}", // Escape
210
+
211
+ // Boşluk
212
+ 49: " ",
213
+ ]
214
+
215
+ return mapping[keyCode]
216
+ }
217
+
218
+ // MARK: - Method Swizzling
219
+
220
+ private func swizzlePressesMethods() {
221
+ guard let viewController = viewController else { return }
222
+
223
+ let originalPressesBeganSelector = #selector(UIViewController.pressesBegan(_:with:))
224
+ let swizzledPressesBeganSelector = #selector(UIViewController.raxon_pressesBegan(_:with:))
225
+
226
+ let originalPressesEndedSelector = #selector(UIViewController.pressesEnded(_:with:))
227
+ let swizzledPressesEndedSelector = #selector(UIViewController.raxon_pressesEnded(_:with:))
228
+
229
+ swizzleMethod(
230
+ for: UIViewController.self,
231
+ originalSelector: originalPressesBeganSelector,
232
+ swizzledSelector: swizzledPressesBeganSelector
233
+ )
234
+
235
+ swizzleMethod(
236
+ for: UIViewController.self,
237
+ originalSelector: originalPressesEndedSelector,
238
+ swizzledSelector: swizzledPressesEndedSelector
239
+ )
240
+
241
+ // Bu view controller'a weak referans ile handler'a erişim sağla
242
+ objc_setAssociatedObject(
243
+ viewController,
244
+ &AssociatedKeys.keyboardHandler,
245
+ self,
246
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
247
+ )
248
+ }
249
+
250
+ private func unswizzlePressesMethods() {
251
+ // Method swizzling geri alınamaz ama handler'ı temizleyebiliriz
252
+ if let viewController = viewController {
253
+ objc_setAssociatedObject(
254
+ viewController,
255
+ &AssociatedKeys.keyboardHandler,
256
+ nil,
257
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
258
+ )
259
+ }
260
+ }
261
+
262
+ private func swizzleMethod(
263
+ for classType: AnyClass,
264
+ originalSelector: Selector,
265
+ swizzledSelector: Selector
266
+ ) {
267
+ guard let originalMethod = class_getInstanceMethod(classType, originalSelector),
268
+ let swizzledMethod = class_getInstanceMethod(classType, swizzledSelector) else {
269
+ return
270
+ }
271
+
272
+ let didAddMethod = class_addMethod(
273
+ classType,
274
+ originalSelector,
275
+ method_getImplementation(swizzledMethod),
276
+ method_getTypeEncoding(swizzledMethod)
277
+ )
278
+
279
+ if didAddMethod {
280
+ class_replaceMethod(
281
+ classType,
282
+ swizzledSelector,
283
+ method_getImplementation(originalMethod),
284
+ method_getTypeEncoding(originalMethod)
285
+ )
286
+ } else {
287
+ method_exchangeImplementations(originalMethod, swizzledMethod)
288
+ }
289
+ }
290
+ }
291
+
292
+ // MARK: - UIViewController Extension
293
+
294
+ private var AssociatedKeys = (
295
+ keyboardHandler: "raxon_keyboardHandler"
296
+ )
297
+
298
+ @available(iOS 13.4, *)
299
+ extension UIViewController {
300
+ @objc dynamic func raxon_pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
301
+ // Orijinal implementasyonu çağır (swizzling yüzünden bu aslında orijinal)
302
+ raxon_pressesBegan(presses, with: event)
303
+
304
+ // Handler varsa tuşları işle
305
+ if let handler = objc_getAssociatedObject(self, &AssociatedKeys.keyboardHandler)
306
+ as? ExternalKeyboardHandler {
307
+ for press in presses {
308
+ _ = handler.handlePress(press)
309
+ }
310
+ }
311
+ }
312
+
313
+ @objc dynamic func raxon_pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
314
+ // Orijinal implementasyonu çağır
315
+ raxon_pressesEnded(presses, with: event)
316
+ }
317
+ }
318
+
319
+ // MARK: - UIKey Extensions
320
+
321
+ @available(iOS 13.4, *)
322
+ extension UIKey {
323
+ var keyCode: UIKeyboardHIDUsage {
324
+ return self.keyCode
325
+ }
326
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raxon-barcode-scanner",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Zebra and enterprise barcode scanner hook for React Native Expo apps",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -37,6 +37,11 @@
37
37
  "files": [
38
38
  "android/build.gradle",
39
39
  "android/src",
40
+ "ios/**/*.h",
41
+ "ios/**/*.m",
42
+ "ios/**/*.mm",
43
+ "ios/**/*.swift",
44
+ "ios/*.podspec",
40
45
  "build",
41
46
  "expo-module.config.json",
42
47
  "src"
@@ -7,7 +7,7 @@ import {
7
7
 
8
8
  class RaxonBarcodeScannerModule extends NativeModule<RaxonBarcodeScannerModuleEvents> {
9
9
  startListening(_options?: BarcodeScannerOptions): void {
10
- console.warn('raxon-barcode-scanner is only supported on Android.');
10
+ console.warn('raxon-barcode-scanner is only supported on iOS and Android.');
11
11
  }
12
12
 
13
13
  stopListening(): void {