@nitra/zebra 7.1.0 → 7.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.
@@ -61,17 +61,17 @@ import ExternalAccessory
61
61
  public var errorDescription: String? {
62
62
  switch self {
63
63
  case .addressMissing:
64
- return "Адреса принтера не вказана. Передайте address у print()."
64
+ return "Printer address is not specified. Pass address to print()."
65
65
  case .zplEmpty:
66
- return "ZPL команда порожня."
66
+ return "ZPL command is empty."
67
67
  case .accessoryNotFound(let address):
68
- return "Пристрій не знайдено або не підключено: \(address)"
68
+ return "Device not found or not connected: \(address)"
69
69
  case .protocolMissing(let address):
70
- return "Для пристрою не знайдено жодного підтримуваного протоколу: \(address)"
70
+ return "No supported protocol found for device: \(address)"
71
71
  case .sessionFailed(let reason):
72
- return "Не вдалося відкрити сесію з принтером: \(reason)"
72
+ return "Failed to open session with printer: \(reason)"
73
73
  case .writeFailed(let reason):
74
- return "Помилка відправки даних: \(reason)"
74
+ return "Failed to send data: \(reason)"
75
75
  }
76
76
  }
77
77
  }
@@ -99,10 +99,17 @@ import ExternalAccessory
99
99
  super.init()
100
100
  }
101
101
 
102
- /// Запускає стрім - розміщує його в RunLoop та відкриває
102
+ /// Запускає стріми обидва (input і output) відкриваються й додаються в RunLoop, як у документації Apple; інакше сесія може не звільнятися коректно.
103
103
  func start() {
104
+ let runLoop = RunLoop.current
105
+ let mode: RunLoop.Mode = .default
106
+ if let input = session.inputStream {
107
+ input.delegate = self
108
+ input.schedule(in: runLoop, forMode: mode)
109
+ input.open()
110
+ }
104
111
  output.delegate = self
105
- output.schedule(in: .current, forMode: .default)
112
+ output.schedule(in: runLoop, forMode: mode)
106
113
  output.open()
107
114
  owner?.log("StreamWriter started, bytes=", data.count)
108
115
  startTime = Date()
@@ -119,14 +126,29 @@ import ExternalAccessory
119
126
  close(with: .success(PrintResult(address: address)))
120
127
  }
121
128
 
122
- /// Закриває стріми та викликає completion один раз
129
+ /// Закриває стріми та викликає completion один раз.
130
+ /// Порядок важливий для коректного звільнення EASession у iOS — інакше повторне відкриття сесії до того ж аксесуара може повертати nil.
123
131
  func close(with result: Result<PrintResult, ZebraError>) {
124
- RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.02))
125
- output.remove(from: .current, forMode: .default)
126
- output.close()
127
- if let input = session.inputStream { input.close() }
132
+ let runLoop = RunLoop.current
133
+ let mode: RunLoop.Mode = .default
134
+ // Даємо RunLoop обробити останні події перед закриттям
135
+ runLoop.run(mode: mode, before: Date().addingTimeInterval(0.02))
136
+
137
+ // 1) Вимикаємо делегата, щоб під час закриття не викликалися зворотні виклики
128
138
  output.delegate = nil
129
- Thread.sleep(forTimeInterval: 0.08)
139
+ // 2) Знімаємо output з RunLoop, потім закриваємо
140
+ output.remove(from: runLoop, forMode: mode)
141
+ output.close()
142
+
143
+ // 3) InputStream теж треба коректно закрити (зняти з RunLoop і делегата), інакше сесія не звільняється
144
+ if let input = session.inputStream {
145
+ input.delegate = nil
146
+ input.remove(from: runLoop, forMode: mode)
147
+ input.close()
148
+ }
149
+
150
+ // 4) Затримка, щоб iOS встиг звільнити EASession до наступного відкриття
151
+ Thread.sleep(forTimeInterval: 0.25)
130
152
  if let completion = completion {
131
153
  completion(result)
132
154
  }
@@ -135,6 +157,7 @@ import ExternalAccessory
135
157
  }
136
158
 
137
159
  func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
160
+ // Події input-потоку ігноруємо (потрібен лише для коректного життєвого циклу сесії)
138
161
  guard aStream === output else { return }
139
162
  if Date().timeIntervalSince(startTime) > timeout {
140
163
  owner?.log("StreamWriter timeout")
@@ -239,36 +262,27 @@ import ExternalAccessory
239
262
  }
240
263
 
241
264
  /// Друкує ZPL на принтері за вказаною адресою (без зчитування з налаштувань).
265
+ /// Відкриття/закриття EASession виконується на main run loop для коректного повторного друку.
242
266
  public func printZpl(address: String, zpl: String, completion: @escaping (Result<PrintResult, ZebraError>) -> Void) {
243
- printQueue.async {
244
- let trimmedAddress = address.trimmingCharacters(in: .whitespacesAndNewlines)
245
- if trimmedAddress.isEmpty {
246
- completion(.failure(.addressMissing))
247
- return
248
- }
249
-
250
- let trimmedZpl = zpl.trimmingCharacters(in: .whitespacesAndNewlines)
251
- if trimmedZpl.isEmpty {
252
- completion(.failure(.zplEmpty))
253
- return
254
- }
255
-
256
- // Додаємо CRLF в кінець для сумісності з прошивкою
257
- var payload = trimmedZpl
258
- if !payload.hasSuffix("\r\n") {
259
- payload += "\r\n"
260
- }
261
-
262
- // Debug: log exact ZPL content and hex bytes
263
- let visibleZpl = payload.replacingOccurrences(of: "\r", with: "<CR>").replacingOccurrences(of: "\n", with: "<LF>\n")
264
- self.log("ZPL payload (visible):\n\(visibleZpl)")
265
- let zplBytes = Array(payload.utf8)
266
- let hex = zplBytes.map { String(format: "%02X", $0) }.joined()
267
- self.log("ZPL payload (hex):", hex)
268
-
269
- self.log("printZpl called with address=\(trimmedAddress.isEmpty ? "<empty>" : trimmedAddress)", "zplLength=\(payload.utf8.count)")
267
+ let trimmedAddress = address.trimmingCharacters(in: .whitespacesAndNewlines)
268
+ if trimmedAddress.isEmpty {
269
+ completion(.failure(.addressMissing))
270
+ return
271
+ }
272
+ let trimmedZpl = zpl.trimmingCharacters(in: .whitespacesAndNewlines)
273
+ if trimmedZpl.isEmpty {
274
+ completion(.failure(.zplEmpty))
275
+ return
276
+ }
277
+ var payload = trimmedZpl
278
+ if !payload.hasSuffix("\r\n") {
279
+ payload += "\r\n"
280
+ }
281
+ let zplBytes = Array(payload.utf8)
282
+ log("printZpl called with address=\(trimmedAddress)", "zplLength=\(zplBytes.count)")
270
283
 
271
- // Отримуємо список підключених аксесуарів
284
+ DispatchQueue.main.async { [weak self] in
285
+ guard let self = self else { return }
272
286
  let accessories = EAAccessoryManager.shared().connectedAccessories
273
287
  self.log("EA connectedAccessories count=", accessories.count)
274
288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/zebra",
3
- "version": "7.1.0",
3
+ "version": "7.1.1",
4
4
  "description": "Zebra printer",
5
5
  "keywords": [
6
6
  "capacitor",