@ruhiverse/thermal-printer-plugin 1.0.0 → 1.0.2
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.
|
@@ -0,0 +1,17 @@
|
|
|
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 = 'RuhiverseThermalPrinterPlugin'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['homepage']
|
|
11
|
+
s.author = package['author']['name']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '13.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.1'
|
|
17
|
+
end
|
|
@@ -10,6 +10,76 @@ import CoreBluetooth
|
|
|
10
10
|
@objc(ThermalPrinterPlugin)
|
|
11
11
|
public class ThermalPrinterPlugin: CAPPlugin {
|
|
12
12
|
|
|
13
|
+
/// Protocol identifiers for supported MFi ESC/POS printers.
|
|
14
|
+
/// Update this list with the protocol strings for your specific printer model.
|
|
15
|
+
private let supportedPrinterProtocols = [
|
|
16
|
+
"com.epson.escpos",
|
|
17
|
+
"com.starmicronics.starprnt",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
/// Find a connected External Accessory that matches the given (optional) name
|
|
21
|
+
/// and supports one of the known printer protocols.
|
|
22
|
+
private func findPrinterAccessory(preferredName: String?) -> (EAAccessory, String)? {
|
|
23
|
+
let manager = EAAccessoryManager.shared()
|
|
24
|
+
let accessories = manager.connectedAccessories
|
|
25
|
+
|
|
26
|
+
// Filter accessories that support at least one of the known printer protocols
|
|
27
|
+
let printerAccessories: [(EAAccessory, String)] = accessories.compactMap { accessory in
|
|
28
|
+
if let protocolString = accessory.protocolStrings.first(where: { supportedPrinterProtocols.contains($0) }) {
|
|
29
|
+
return (accessory, protocolString)
|
|
30
|
+
}
|
|
31
|
+
return nil
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
guard !printerAccessories.isEmpty else {
|
|
35
|
+
return nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// If a preferredName is provided, try to match it against the accessory name
|
|
39
|
+
if let name = preferredName, !name.isEmpty {
|
|
40
|
+
if let match = printerAccessories.first(where: { (accessory, _) in
|
|
41
|
+
accessory.name.localizedCaseInsensitiveContains(name)
|
|
42
|
+
}) {
|
|
43
|
+
return match
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Fallback: just return the first compatible printer
|
|
48
|
+
return printerAccessories.first
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Send raw ESC/POS (or plain text) data to the given accessory/protocol.
|
|
52
|
+
private func sendPrintData(
|
|
53
|
+
_ data: Data,
|
|
54
|
+
to accessory: EAAccessory,
|
|
55
|
+
using protocolString: String
|
|
56
|
+
) throws {
|
|
57
|
+
guard let session = EASession(accessory: accessory, forProtocol: protocolString),
|
|
58
|
+
let outputStream = session.outputStream else {
|
|
59
|
+
throw NSError(domain: "ThermalPrinterPlugin", code: 1, userInfo: [
|
|
60
|
+
NSLocalizedDescriptionKey: "Unable to open session/output stream for printer.",
|
|
61
|
+
])
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
outputStream.open()
|
|
65
|
+
defer {
|
|
66
|
+
outputStream.close()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let bytesWritten = data.withUnsafeBytes { buffer -> Int in
|
|
70
|
+
guard let pointer = buffer.bindMemory(to: UInt8.self).baseAddress else {
|
|
71
|
+
return -1
|
|
72
|
+
}
|
|
73
|
+
return outputStream.write(pointer, maxLength: data.count)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if bytesWritten <= 0 {
|
|
77
|
+
throw NSError(domain: "ThermalPrinterPlugin", code: 2, userInfo: [
|
|
78
|
+
NSLocalizedDescriptionKey: "Failed to send data to printer.",
|
|
79
|
+
])
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
13
83
|
@objc func printByUsb(_ call: CAPPluginCall) {
|
|
14
84
|
guard let textToPrint = call.getString("textToPrint") else {
|
|
15
85
|
call.reject("textToPrint is required")
|
|
@@ -21,19 +91,42 @@ public class ThermalPrinterPlugin: CAPPlugin {
|
|
|
21
91
|
// your Info.plist with supported protocols for your printer
|
|
22
92
|
|
|
23
93
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
24
|
-
// For iOS, USB
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
94
|
+
// For many iOS thermal printers, USB/MFi printers are exposed
|
|
95
|
+
// via the ExternalAccessory framework using the same protocol
|
|
96
|
+
// strings as Bluetooth / network models.
|
|
97
|
+
let preferredName = call.getString("name")
|
|
28
98
|
|
|
29
|
-
|
|
30
|
-
|
|
99
|
+
guard let (accessory, protocolString) = self.findPrinterAccessory(preferredName: preferredName) else {
|
|
100
|
+
DispatchQueue.main.async {
|
|
101
|
+
call.reject("No compatible printer accessory found. Make sure your printer is connected and UISupportedExternalAccessoryProtocols in Info.plist contains its protocol.")
|
|
102
|
+
}
|
|
103
|
+
return
|
|
104
|
+
}
|
|
31
105
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
106
|
+
// Convert text to basic ESC/POS-compatible data (UTF-8 + line feed).
|
|
107
|
+
// NOTE: This is a minimal implementation and does not yet parse
|
|
108
|
+
// formatting tags like [C], <b>, etc. Those will print as plain text.
|
|
109
|
+
guard let data = (textToPrint + "\n\n").data(using: .utf8) else {
|
|
110
|
+
DispatchQueue.main.async {
|
|
111
|
+
call.reject("Failed to encode text as UTF-8.")
|
|
112
|
+
}
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
do {
|
|
117
|
+
try self.sendPrintData(data, to: accessory, using: protocolString)
|
|
118
|
+
DispatchQueue.main.async {
|
|
119
|
+
call.resolve([
|
|
120
|
+
"success": true,
|
|
121
|
+
"message": "USB print data sent to printer.",
|
|
122
|
+
"printerName": accessory.name,
|
|
123
|
+
"protocol": protocolString,
|
|
124
|
+
])
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
DispatchQueue.main.async {
|
|
128
|
+
call.reject("USB print failed: \(error.localizedDescription)")
|
|
129
|
+
}
|
|
37
130
|
}
|
|
38
131
|
}
|
|
39
132
|
}
|
|
@@ -49,32 +142,87 @@ public class ThermalPrinterPlugin: CAPPlugin {
|
|
|
49
142
|
// Bluetooth permissions in Info.plist
|
|
50
143
|
|
|
51
144
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
145
|
+
// On many MFi / ExternalAccessory-based thermal printers, the
|
|
146
|
+
// Bluetooth connection is abstracted behind the ExternalAccessory
|
|
147
|
+
// protocols (e.g., com.epson.escpos, com.starmicronics.starprnt).
|
|
148
|
+
// We reuse the same ExternalAccessory-based implementation here.
|
|
149
|
+
|
|
150
|
+
let preferredName = call.getString("name")
|
|
151
|
+
|
|
152
|
+
guard let (accessory, protocolString) = self.findPrinterAccessory(preferredName: preferredName) else {
|
|
153
|
+
DispatchQueue.main.async {
|
|
154
|
+
call.reject("No compatible Bluetooth printer found. Make sure it is paired, connected, and its protocol is listed in UISupportedExternalAccessoryProtocols.")
|
|
155
|
+
}
|
|
156
|
+
return
|
|
157
|
+
}
|
|
56
158
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
159
|
+
// Minimal text → ESC/POS data conversion.
|
|
160
|
+
// Formatting tags like [C] or <b> are not parsed yet and will
|
|
161
|
+
// be sent as plain text.
|
|
162
|
+
guard let data = (textToPrint + "\n\n").data(using: .utf8) else {
|
|
163
|
+
DispatchQueue.main.async {
|
|
164
|
+
call.reject("Failed to encode text as UTF-8.")
|
|
165
|
+
}
|
|
166
|
+
return
|
|
167
|
+
}
|
|
59
168
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
169
|
+
do {
|
|
170
|
+
try self.sendPrintData(data, to: accessory, using: protocolString)
|
|
171
|
+
DispatchQueue.main.async {
|
|
172
|
+
call.resolve([
|
|
173
|
+
"success": true,
|
|
174
|
+
"message": "Bluetooth print data sent to printer.",
|
|
175
|
+
"printerName": accessory.name,
|
|
176
|
+
"protocol": protocolString,
|
|
177
|
+
])
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
DispatchQueue.main.async {
|
|
181
|
+
call.reject("Bluetooth print failed: \(error.localizedDescription)")
|
|
182
|
+
}
|
|
65
183
|
}
|
|
66
184
|
}
|
|
67
185
|
}
|
|
68
186
|
|
|
69
187
|
@objc func listBluetoothPrinters(_ call: CAPPluginCall) {
|
|
188
|
+
let manager = EAAccessoryManager.shared()
|
|
189
|
+
let accessories = manager.connectedAccessories
|
|
190
|
+
|
|
191
|
+
let printers: [[String: String]] = accessories.compactMap { accessory in
|
|
192
|
+
guard accessory.protocolStrings.contains(where: { supportedPrinterProtocols.contains($0) }) else {
|
|
193
|
+
return nil
|
|
194
|
+
}
|
|
195
|
+
return [
|
|
196
|
+
"name": accessory.name,
|
|
197
|
+
// iOS doesn't expose a MAC address for accessories; we
|
|
198
|
+
// return the serial number or a generated identifier.
|
|
199
|
+
"address": accessory.serialNumber.isEmpty ? "\(accessory.connectionID)" : accessory.serialNumber,
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
|
|
70
203
|
call.resolve([
|
|
71
|
-
"printers":
|
|
204
|
+
"printers": printers,
|
|
72
205
|
])
|
|
73
206
|
}
|
|
74
207
|
|
|
75
208
|
@objc func listUsbPrinters(_ call: CAPPluginCall) {
|
|
209
|
+
// On iOS, many thermal printers expose both USB and Bluetooth via the
|
|
210
|
+
// ExternalAccessory framework. We reuse the same discovery logic here.
|
|
211
|
+
let manager = EAAccessoryManager.shared()
|
|
212
|
+
let accessories = manager.connectedAccessories
|
|
213
|
+
|
|
214
|
+
let printers: [[String: String]] = accessories.compactMap { accessory in
|
|
215
|
+
guard accessory.protocolStrings.contains(where: { supportedPrinterProtocols.contains($0) }) else {
|
|
216
|
+
return nil
|
|
217
|
+
}
|
|
218
|
+
return [
|
|
219
|
+
"name": accessory.name,
|
|
220
|
+
"address": accessory.serialNumber.isEmpty ? "\(accessory.connectionID)" : accessory.serialNumber,
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
|
|
76
224
|
call.resolve([
|
|
77
|
-
"printers":
|
|
225
|
+
"printers": printers,
|
|
78
226
|
])
|
|
79
227
|
}
|
|
80
228
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ruhiverse/thermal-printer-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Capacitor plugin for thermal printing via USB and Bluetooth",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"android/build.gradle",
|
|
12
12
|
"dist/",
|
|
13
13
|
"ios/Plugin/",
|
|
14
|
-
"ThermalPrinterPlugin.podspec"
|
|
14
|
+
"ThermalPrinterPlugin.podspec",
|
|
15
|
+
"RuhiverseThermalPrinterPlugin.podspec"
|
|
15
16
|
],
|
|
16
17
|
"author": {
|
|
17
18
|
"name": "Paresh Gami",
|