@nitra/zebra 8.1.0 → 8.2.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.
@@ -23,9 +23,6 @@ import ExternalAccessory
23
23
  /// Невелика затримка між черговими завданнями, щоб дати прошивці час стабілізуватися
24
24
  private let cooldownInterval: TimeInterval = 1.0
25
25
 
26
- /// Перемикач debug-логування
27
- private static let debugLogging = true
28
-
29
26
  /// Один раз реєструємося для нотифікацій EA — потрібно для коректного доступу до аксесуарів при холодному запуску (без Xcode).
30
27
  private static var didRegisterForAccessoryNotifications = false
31
28
  private static let registerLock = NSLock()
@@ -35,13 +32,7 @@ import ExternalAccessory
35
32
  guard !Zebra.didRegisterForAccessoryNotifications else { return }
36
33
  EAAccessoryManager.shared().registerForLocalNotifications()
37
34
  Zebra.didRegisterForAccessoryNotifications = true
38
- log("EAAccessoryManager.registerForLocalNotifications() called")
39
- }
40
-
41
- /// Функція логування з префіксом [Zebra]
42
- private func log(_ items: Any...) {
43
- guard Zebra.debugLogging else { return }
44
- print("[Zebra]", items.map { String(describing: $0) }.joined(separator: " "))
35
+ logDebug("EAAccessoryManager.registerForLocalNotifications() called")
45
36
  }
46
37
 
47
38
  /// Представляє інформацію про підключений аксесуар, яку використовує плагін
@@ -103,12 +94,10 @@ import ExternalAccessory
103
94
  private let timeout: TimeInterval
104
95
  private var totalWritten = 0
105
96
  private var completion: ((Result<PrintResult, ZebraError>) -> Void)?
106
- private weak var owner: Zebra?
107
97
  private var startTime = Date()
108
98
  private let session: EASession
109
99
 
110
- init(owner: Zebra, session: EASession, output: OutputStream, data: [UInt8], address: String, timeout: TimeInterval = 5.0, completion: @escaping (Result<PrintResult, ZebraError>) -> Void) {
111
- self.owner = owner
100
+ init(owner _: Zebra, session: EASession, output: OutputStream, data: [UInt8], address: String, timeout: TimeInterval = 5.0, completion: @escaping (Result<PrintResult, ZebraError>) -> Void) {
112
101
  self.session = session
113
102
  self.output = output
114
103
  self.data = data
@@ -130,7 +119,7 @@ import ExternalAccessory
130
119
  output.delegate = self
131
120
  output.schedule(in: runLoop, forMode: mode)
132
121
  output.open()
133
- owner?.log("StreamWriter started, bytes=", data.count)
122
+ logDebug("StreamWriter started, bytes= \(self.data.count)")
134
123
  startTime = Date()
135
124
  }
136
125
 
@@ -141,7 +130,7 @@ import ExternalAccessory
141
130
  while Date() < deadline {
142
131
  RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.1))
143
132
  }
144
- owner?.log("Flush done, closing stream")
133
+ logDebug("Flush done, closing stream")
145
134
  close(with: .success(PrintResult(address: address)))
146
135
  }
147
136
 
@@ -172,26 +161,26 @@ import ExternalAccessory
172
161
  completion(result)
173
162
  }
174
163
  completion = nil
175
- owner?.log("StreamWriter finished with", result)
164
+ logDebug("StreamWriter finished with \(String(describing: result))")
176
165
  }
177
166
 
178
167
  func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
179
168
  // Події input-потоку ігноруємо (потрібен лише для коректного життєвого циклу сесії)
180
169
  guard aStream === output else { return }
181
170
  if Date().timeIntervalSince(startTime) > timeout {
182
- owner?.log("StreamWriter timeout")
171
+ logDebug("StreamWriter timeout")
183
172
  close(with: .failure(.writeFailed("Таймаут запису")))
184
173
  return
185
174
  }
186
175
  switch eventCode {
187
176
  case .openCompleted:
188
177
  // Потік відкрито
189
- owner?.log("Stream open completed")
178
+ logDebug("Stream open completed")
190
179
  case .hasSpaceAvailable:
191
180
  // Є простір для запису, пишемо дані порціями
192
181
  if totalWritten >= data.count {
193
182
  // Усі байти записані — даємо час на флаш буфера перед закриттям (iOS часто не встигає відправити на пристрій)
194
- owner?.log("All bytes written, flushing before close...")
183
+ logDebug("All bytes written, flushing before close...")
195
184
  flushThenClose()
196
185
  return
197
186
  }
@@ -200,7 +189,7 @@ import ExternalAccessory
200
189
  let wrote = output.write(Array(data[totalWritten..<(totalWritten + chunkSize)]), maxLength: chunkSize)
201
190
  if wrote <= 0 {
202
191
  let reason = output.streamError?.localizedDescription ?? "Невідома помилка потоку"
203
- owner?.log("StreamWriter write failed:", reason)
192
+ logDebug("StreamWriter write failed: \(reason)")
204
193
  close(with: .failure(.writeFailed(reason)))
205
194
  return
206
195
  }
@@ -208,11 +197,11 @@ import ExternalAccessory
208
197
  case .errorOccurred:
209
198
  // Сталася помилка потоку
210
199
  let reason = output.streamError?.localizedDescription ?? "Невідома помилка потоку"
211
- owner?.log("Stream error:", reason)
200
+ logDebug("Stream error: \(reason)")
212
201
  close(with: .failure(.writeFailed(reason)))
213
202
  case .endEncountered:
214
203
  // Потік закінчився, перевіряємо чи всі дані записані
215
- owner?.log("Stream end encountered, bytes=", totalWritten)
204
+ logDebug("Stream end encountered, bytes= \(self.totalWritten)")
216
205
  if totalWritten >= data.count {
217
206
  close(with: .success(PrintResult(address: address)))
218
207
  } else {
@@ -239,7 +228,18 @@ import ExternalAccessory
239
228
  /// Повертає список підключених EA аксесуарів у вигляді DeviceInfo, вибираючи серійний номер або ім'я як адресу
240
229
  public func getConnectedAccessories() -> [DeviceInfo] {
241
230
  ensureRegisteredForAccessoryNotifications()
242
- let accessories = EAAccessoryManager.shared().connectedAccessories
231
+ // Якщо аксесуарів немає, чекаємо 0.6 с і повторюємо спробу до 3 разів (щоб дати системі час підхопити нове підключення)
232
+ let maxAttempts = 3
233
+ let retryDelay: TimeInterval = 0.6
234
+ var accessories = EAAccessoryManager.shared().connectedAccessories
235
+ for attempt in 1...maxAttempts {
236
+ if !accessories.isEmpty { break }
237
+ if attempt < maxAttempts {
238
+ logDebug("connectedAccessories attempt: \(attempt)")
239
+ Thread.sleep(forTimeInterval: retryDelay)
240
+ accessories = EAAccessoryManager.shared().connectedAccessories
241
+ }
242
+ }
243
243
  return accessories.compactMap { accessory in
244
244
  let address = accessory.serialNumber.isEmpty ? accessory.name : accessory.serialNumber
245
245
  if address.isEmpty {
@@ -252,14 +252,14 @@ import ExternalAccessory
252
252
  /// Виводить діагностичну інформацію щодо підключених аксесуарів та протоколів у Info.plist
253
253
  public func diagnoseEnvironment() {
254
254
  let accessories = EAAccessoryManager.shared().connectedAccessories
255
- log("Connected accessories count:", accessories.count)
255
+ logDebug("Connected accessories count: \(accessories.count)")
256
256
  for a in accessories {
257
- log("Accessory:", "name=\(a.name)", "serial=\(a.serialNumber)", "protocols=\(a.protocolStrings)")
257
+ logDebug("Accessory: name=\(a.name) serial=\(a.serialNumber) protocols=\(a.protocolStrings)")
258
258
  }
259
259
  if let supported = Bundle.main.object(forInfoDictionaryKey: "UISupportedExternalAccessoryProtocols") as? [String] {
260
- log("Info.plist UISupportedExternalAccessoryProtocols:", supported)
260
+ logDebug("Info.plist UISupportedExternalAccessoryProtocols: \(supported)")
261
261
  } else {
262
- log("Info.plist UISupportedExternalAccessoryProtocols: MISSING or not an array")
262
+ logDebug("Info.plist UISupportedExternalAccessoryProtocols: MISSING or not an array")
263
263
  }
264
264
  }
265
265
 
@@ -270,10 +270,10 @@ import ExternalAccessory
270
270
  for proto in protocolsToTry {
271
271
  for attempt in 1...retries {
272
272
  if let session = EASession(accessory: accessory, forProtocol: proto) {
273
- self.log("EASession open success on attempt", attempt, "protocol:", proto)
273
+ logDebug("EASession open success on attempt \(attempt) protocol: \(proto)")
274
274
  return (session, proto)
275
275
  } else {
276
- self.log("EASession open failed attempt", attempt, "protocol:", proto)
276
+ logDebug("EASession open failed attempt \(attempt) protocol: \(proto)")
277
277
  Thread.sleep(forTimeInterval: delay)
278
278
  }
279
279
  }
@@ -299,18 +299,25 @@ import ExternalAccessory
299
299
  payload += "\r\n"
300
300
  }
301
301
  let zplBytes = Array(payload.utf8)
302
- log("printZpl called with address=\(trimmedAddress)", "zplLength=\(zplBytes.count)")
302
+ logDebug("printZpl called with address=\(trimmedAddress) zplLength=\(zplBytes.count)")
303
303
 
304
304
  DispatchQueue.main.async { [weak self] in
305
305
  guard let self = self else { return }
306
306
  self.ensureRegisteredForAccessoryNotifications()
307
307
  let accessories = EAAccessoryManager.shared().connectedAccessories
308
- self.log("EA connectedAccessories count=", accessories.count)
308
+ logDebug("EA connectedAccessories count= \(accessories.count)")
309
309
 
310
310
  // Шукаємо аксесуар, що відповідає адресі (serialNumber або name)
311
311
  let availableList = self.getConnectedAccessories()
312
+
313
+ if availableList.isEmpty {
314
+ logDebug("No accessories found")
315
+ completion(.failure(.accessoryNotFound(requestedAddress: trimmedAddress, availableAccessories: availableList)))
316
+ return
317
+ }
318
+
312
319
  guard let accessory = accessories.first(where: { self.accessoryMatches($0, address: trimmedAddress) }) else {
313
- self.log("No accessory matched address:", trimmedAddress)
320
+ logDebug("No accessory matched address: \(trimmedAddress)")
314
321
  // У failure передаємо не лише адресу, а й список доступних аксесуарів (назви та адреси) для показу в помилці
315
322
  completion(.failure(.accessoryNotFound(requestedAddress: trimmedAddress, availableAccessories: availableList)))
316
323
  return
@@ -318,30 +325,30 @@ import ExternalAccessory
318
325
 
319
326
  // Обираємо протокол для з’єднання
320
327
  let protocolString = self.preferredProtocol(for: accessory)
321
- self.log("Chosen accessory:", accessory.name, "serial=\(accessory.serialNumber)")
322
- self.log("Selected protocol:", protocolString)
328
+ logDebug("Chosen accessory: \(accessory.name) serial=\(accessory.serialNumber)")
329
+ logDebug("Selected protocol: \(protocolString)")
323
330
  if let supported = Bundle.main.object(forInfoDictionaryKey: "UISupportedExternalAccessoryProtocols") as? [String] {
324
331
  if !supported.contains(protocolString) {
325
- self.log("WARNING: Selected protocol not present in Info.plist UISupportedExternalAccessoryProtocols")
332
+ logDebug("WARNING: Selected protocol not present in Info.plist UISupportedExternalAccessoryProtocols")
326
333
  }
327
334
  } else {
328
- self.log("WARNING: UISupportedExternalAccessoryProtocols missing in Info.plist")
335
+ logDebug("WARNING: UISupportedExternalAccessoryProtocols missing in Info.plist")
329
336
  }
330
337
 
331
- self.log("Attempting to open EASession with retries and fallback protocols...")
338
+ logDebug("Attempting to open EASession with retries and fallback protocols...")
332
339
  Thread.sleep(forTimeInterval: 0.1)
333
340
  let fallback = accessory.protocolStrings
334
341
  // Відкриваємо сесію, пробуємо ретраї і fallback протоколи
335
342
  guard let result = self.openSessionWithRetries(accessory: accessory, preferredProtocol: protocolString, fallbackProtocols: fallback) else {
336
- self.log("EASession failed after retries and fallback")
343
+ logDebug("EASession failed after retries and fallback")
337
344
  completion(.failure(.sessionFailed("EASession повернув nil")))
338
345
  return
339
346
  }
340
347
  let session = result.session
341
348
  let protocolUsed = result.protocolUsed
342
- self.log("Using protocol for session:", protocolUsed)
349
+ logDebug("Using protocol for session: \(protocolUsed)")
343
350
  guard let outputStream = session.outputStream else {
344
- self.log("Session opened but outputStream is nil")
351
+ logDebug("Session opened but outputStream is nil")
345
352
  completion(.failure(.sessionFailed("Відсутній outputStream")))
346
353
  return
347
354
  }
@@ -0,0 +1,11 @@
1
+ import os
2
+
3
+ /// Спільна функція дебаг-логування для модуля Zebra.
4
+ /// У Release не виконується й не обчислює повідомлення (zero cost).
5
+ #if DEBUG
6
+ func logDebug(_ message: @autoclosure @escaping () -> String) {
7
+ Logger(subsystem: "com.nitra.zebra", category: "Zebra").debug("\(message(), privacy: .public)")
8
+ }
9
+ #else
10
+ func logDebug(_ message: @autoclosure @escaping () -> String) {}
11
+ #endif
@@ -36,9 +36,8 @@ public class ZebraPlugin: CAPPlugin, CAPBridgedPlugin {
36
36
  return
37
37
  }
38
38
  Task {
39
- // Зберігаємо адресу в UserDefaults
40
39
  await implementation.setPrinterAddress(address)
41
- // Повертаємо успішний результат
40
+ logDebug("setPrinterAddress: \(address)")
42
41
  await MainActor.run {
43
42
  call.resolve(["address": address])
44
43
  }
@@ -49,13 +48,10 @@ public class ZebraPlugin: CAPPlugin, CAPBridgedPlugin {
49
48
  /// - Parameter call: Виклик без параметрів
50
49
  @objc func getPairedDevices(_ call: CAPPluginCall) {
51
50
  Task {
52
- // Отримуємо список підключених аксесуарів
53
51
  let devices = await implementation.getConnectedAccessories()
54
-
55
- // Перетворюємо масив DeviceInfo в масив словників
52
+ logDebug("getPairedDevices: \(devices.count) device(s)")
56
53
  let list = devices.map { ["address": $0.address, "name": $0.name] }
57
54
  await MainActor.run {
58
- // Повертаємо результат
59
55
  call.resolve(["devices": list])
60
56
  }
61
57
  }
@@ -71,13 +67,15 @@ public class ZebraPlugin: CAPPlugin, CAPBridgedPlugin {
71
67
  return
72
68
  }
73
69
  Task {
70
+ logDebug("print address=\(address) zplLength=\(zpl.utf8.count)")
74
71
  do {
75
72
  _ = try await implementation.printZpl(address: address, zpl: zpl)
73
+ logDebug("print success")
76
74
  await MainActor.run {
77
- // Успішний друк - повертаємо лише success
78
75
  call.resolve(["success": true])
79
76
  }
80
77
  } catch let error as Zebra.ZebraError {
78
+ logDebug("print failed: \(error.localizedDescription)")
81
79
  // Помилка друку - визначаємо код помилки
82
80
  await MainActor.run {
83
81
  let code: String
@@ -110,8 +108,8 @@ public class ZebraPlugin: CAPPlugin, CAPBridgedPlugin {
110
108
  call.reject(error.localizedDescription, code, error)
111
109
  }
112
110
  } catch {
111
+ logDebug("print failed (unknown): \(error.localizedDescription)")
113
112
  await MainActor.run {
114
- // Повертаємо помилку з UNKNOWN
115
113
  call.reject(error.localizedDescription, "UNKNOWN", error)
116
114
  }
117
115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/zebra",
3
- "version": "8.1.0",
3
+ "version": "8.2.0",
4
4
  "description": "Zebra printer",
5
5
  "keywords": [
6
6
  "capacitor",