@nitra/zebra 7.0.0 → 7.1.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.
@@ -13,5 +13,6 @@ Pod::Spec.new do |s|
13
13
  s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
14
  s.ios.deployment_target = '15.0'
15
15
  s.dependency 'Capacitor'
16
+ s.frameworks = 'ExternalAccessory'
16
17
  s.swift_version = '5.1'
17
18
  end
package/Package.swift CHANGED
@@ -19,7 +19,10 @@ let package = Package(
19
19
  .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
20
  .product(name: "Cordova", package: "capacitor-swift-pm")
21
21
  ],
22
- path: "ios/Sources/ZebraPlugin"),
22
+ path: "ios/Sources/ZebraPlugin",
23
+ linkerSettings: [
24
+ .linkedFramework("ExternalAccessory")
25
+ ]),
23
26
  .testTarget(
24
27
  name: "ZebraPluginTests",
25
28
  dependencies: ["ZebraPlugin"],
package/README.md CHANGED
@@ -18,29 +18,62 @@ npx cap sync
18
18
 
19
19
  ## API
20
20
 
21
- ### `print(value: string): Promise<boolean>`
21
+ ### `print(zpl: string | PrintOptions): Promise<{ sent?: boolean }>`
22
22
 
23
23
  Відправляє ZPL-команду на принтер Zebra.
24
24
 
25
- | Платформа | Поведінка |
26
- | ----------- | --------------------------------------------------------------------------------------------------------------- |
27
- | **Web** | Запитує порт через Web Serial API, відкриває з'єднання (9600 baud) і відправляє рядок ZPL на вибраний пристрій. |
28
- | **iOS** | Нативна реалізація (через плагін). |
29
- | **Android** | Нативна реалізація (через плагін). |
25
+ | Платформа | Поведінка |
26
+ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | **Web** | Запитує порт через Web Serial API, відкриває з'єднання (9600 baud) і відправляє рядок ZPL на вибраний пристрій. |
28
+ | **iOS** | Друк по **Bluetooth (MFi)**. Принтер спочатку спарюють у Налаштування → Bluetooth; далі `getPairedDevices()` та `print({ zpl, address })`. |
29
+ | **Android** | Відправка ZPL по **Bluetooth Classic (SPP)**. Потрібно спочатку викликати `setPrinterAddress({ address: "MAC" })` або передати `address` (Bluetooth MAC) у виклику. |
30
30
 
31
- **Параметри:**
31
+ **Параметри (об'єкт для Android):**
32
32
 
33
- - `value` — рядок з ZPL-командами (наприклад, `^XA^FO50,50^A0N,28,28^FDHello^FS^XZ`).
33
+ - `zpl` — рядок ZPL-команд (наприклад, `^XA^FO50,50^A0N,28,28^FDHello^FS^XZ`).
34
+ - `address` — (Android, опційно) Bluetooth MAC-адреса принтера (наприклад `00:11:22:33:44:55`); якщо не передано, використовується збережена через `setPrinterAddress()`.
35
+ - `port` — ігнорується (для сумісності API).
34
36
 
35
37
  **Приклад:**
36
38
 
37
39
  ```javascript
38
- import { Zebra } from "@nitra/zebra";
40
+ import { Zebra } from '@nitra/zebra'
41
+
42
+ const zpl = '^XA^FO50,50^A0N,28,28^FDHello World^FS^XZ'
43
+ await Zebra.print(zpl)
44
+ ```
45
+
46
+ ### `setPrinterAddress(options: { address: string, port?: number }): Promise<{ address }>` (Android / iOS)
47
+
48
+ Зберігає Bluetooth MAC-адресу принтера Zebra для подальших викликів `print(zpl)` без передачі адреси.
49
+
50
+ **Параметри:**
51
+
52
+ - `address` — на Android: Bluetooth MAC (наприклад `00:11:22:33:44:55`); на iOS: серійний номер або ім'я з `getPairedDevices()`.
53
+ - `port` — ігнорується (для сумісності API).
54
+
55
+ **Приклад (Android):**
56
+
57
+ ```javascript
58
+ await Zebra.setPrinterAddress({ address: '00:11:22:33:44:55' })
59
+ await Zebra.print(zpl)
60
+ ```
61
+
62
+ **Приклад (iOS):** використовуйте `address` з `getPairedDevices()` (серійний номер або ім'я принтера).
63
+
64
+ ### `getPairedDevices(): Promise<{ devices: { address: string, name: string }[] }>` (Android / iOS)
39
65
 
40
- const zpl = "^XA^FO50,50^A0N,28,28^FDHello World^FS^XZ";
41
- await Zebra.print(zpl);
66
+ Повертає список пристроїв: на Android — спарені Bluetooth-пристрої; на iOS — підключені MFi-аксесуари (Bluetooth). Корисно для вибору принтера. На Android 12+ потрібен дозвіл **BLUETOOTH_CONNECT** (запитати до виклику).
67
+
68
+ **Приклад (Android):**
69
+
70
+ ```javascript
71
+ const { devices } = await Zebra.getPairedDevices()
72
+ // devices: [{ address: "00:11:22:33:44:55", name: "Zebra ZD420" }, ...]
42
73
  ```
43
74
 
75
+ **Приклад (iOS):** той самий API; `address` — серійний номер або ім'я MFi-пристрою.
76
+
44
77
  ## Веб (Web Serial API)
45
78
 
46
79
  На веб-платформі плагін використовує Web Serial API:
@@ -51,6 +84,36 @@ await Zebra.print(zpl);
51
84
 
52
85
  **Обмеження:** Web Serial API працює лише в підтримуваних браузерах (Chrome, Edge, Opera) і зазвичай лише через HTTPS або localhost.
53
86
 
87
+ ## Android (Bluetooth Classic)
88
+
89
+ На Android плагін відправляє ZPL на принтер Zebra по **Bluetooth Classic** (профіль SPP):
90
+
91
+ 1. Спаріть принтер у налаштуваннях Bluetooth пристрою.
92
+ 2. Викличте `getPairedDevices()` щоб отримати список пристроїв (опційно) або введіть MAC-адресу вручну.
93
+ 3. Викличте `setPrinterAddress({ address: "MAC_ПРИНТЕРА" })` один раз (наприклад, з налаштувань) або передайте `address` у виклику `print({ zpl, address })`.
94
+ 4. Викличте `print(zpl)` або `print({ zpl })` — ZPL буде відправлено на принтер по Bluetooth.
95
+
96
+ Потрібні дозволи **BLUETOOTH**, **BLUETOOTH_ADMIN** (до API 30), **BLUETOOTH_CONNECT** (API 31+). На Android 12+ дозвіл `BLUETOOTH_CONNECT` потрібно запитати під час виконання (наприклад перед `getPairedDevices()` або `print()`).
97
+
98
+ ## iOS (Bluetooth MFi)
99
+
100
+ На iOS друк працює лише по **Bluetooth** через **ExternalAccessory** (MFi). Підключення по Lightning/USB не підтримується Apple для Zebra в цьому режимі.
101
+
102
+ 1. **Спаріть принтер:** Налаштування → Bluetooth → увімкніть принтер і підключіть його.
103
+ 2. **У додатку:** викличте `getPairedDevices()` — повернуться підключені Zebra-пристрої (`address` та `name`).
104
+ 3. Викличте `setPrinterAddress({ address })` або передайте `address` у `print({ zpl, address })` (адреса — це `address` з `getPairedDevices()`: серійний номер або ім'я).
105
+ 4. `print({ zpl, address })` відправляє ZPL на принтер по Bluetooth.
106
+
107
+ **Обов'язково:** У проєкті додатку в `ios/App/App/Info.plist` мають бути протоколи Zebra в `UISupportedExternalAccessoryProtocols`, інакше iOS не покаже принтер:
108
+
109
+ ```xml
110
+ <key>UISupportedExternalAccessoryProtocols</key>
111
+ <array>
112
+ <string>com.zebra.rawport</string>
113
+ <string>com.zebra.print</string>
114
+ </array>
115
+ ```
116
+
54
117
  ## Структура пакету
55
118
 
56
119
  - `dist/` — зібраний JS-плагін (rolldown)
@@ -1,2 +1,5 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
3
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
4
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
2
5
  </manifest>
@@ -1,11 +1,164 @@
1
1
  package dev.nitra.zebra;
2
2
 
3
+ import android.Manifest;
4
+ import android.bluetooth.BluetoothAdapter;
5
+ import android.bluetooth.BluetoothDevice;
6
+ import android.bluetooth.BluetoothSocket;
7
+ import android.content.Context;
8
+ import android.content.SharedPreferences;
9
+ import android.content.pm.PackageManager;
10
+ import android.os.Build;
11
+
12
+ import androidx.core.app.ActivityCompat;
13
+
3
14
  import com.getcapacitor.Logger;
4
15
 
16
+ import java.io.IOException;
17
+ import java.io.OutputStream;
18
+ import java.nio.charset.StandardCharsets;
19
+ import java.util.ArrayList;
20
+ import java.util.List;
21
+ import java.util.Set;
22
+ import java.util.UUID;
23
+ import java.util.concurrent.ExecutorService;
24
+ import java.util.concurrent.Executors;
25
+
26
+ /**
27
+ * Реалізація друку ZPL на принтері Zebra через Bluetooth Classic (SPP).
28
+ */
5
29
  public class Zebra {
6
30
 
7
- public String echo(String value) {
8
- Logger.info("Echo", value);
9
- return value;
31
+ /** Стандартний UUID для профілю послідовного порту (SPP) */
32
+ private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
33
+
34
+ private static final String PREFS_NAME = "ZebraPrinter";
35
+ private static final String KEY_ADDRESS = "printer_address";
36
+
37
+ // Потік для виконання операцій друку у фоновому режимі
38
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
39
+
40
+ /**
41
+ * Зберігає адресу та порт принтера в налаштуваннях застосунку.
42
+ */
43
+ public void setPrinterAddress(Context context, String address, int port) {
44
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
45
+ String normalized = address != null ? address.trim() : "";
46
+ prefs.edit().putString(KEY_ADDRESS, normalized).apply();
47
+ Logger.info("Zebra", "Адресу принтера збережено: " + normalized);
48
+ }
49
+
50
+ /**
51
+ * Отримує список спарених Bluetooth-пристроїв.
52
+ */
53
+ public List<BluetoothDeviceInfo> getPairedDevices(Context context) {
54
+ List<BluetoothDeviceInfo> result = new ArrayList<>();
55
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
56
+
57
+ if (adapter == null || !adapter.isEnabled()) {
58
+ return result;
59
+ }
60
+
61
+ // Перевірка дозволів для Android 12+
62
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
63
+ ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
64
+ return result;
65
+ }
66
+
67
+ Set<BluetoothDevice> paired = adapter.getBondedDevices();
68
+ if (paired != null) {
69
+ for (BluetoothDevice device : paired) {
70
+ String name = device.getName();
71
+ String address = device.getAddress();
72
+ if (address != null && !address.isEmpty()) {
73
+ result.add(new BluetoothDeviceInfo(address, name != null ? name : ""));
74
+ }
75
+ }
76
+ }
77
+ return result;
78
+ }
79
+
80
+ /**
81
+ * Відправляє ZPL код на принтер.
82
+ *
83
+ * @param address MAC-адреса принтера.
84
+ * @param port Порт (зарезервовано для майбутнього використання).
85
+ * @param zpl Текст у форматі ZPL.
86
+ */
87
+ public void printZpl(Context context, String address, int port, String zpl, PrintCallback callback) {
88
+ final String addrTrimmed = address != null ? address.trim() : "";
89
+
90
+ if (addrTrimmed.isEmpty()) {
91
+ callback.onError("ADDRESS_MISSING", new IllegalArgumentException("Адреса принтера не вказана."));
92
+ return;
93
+ }
94
+
95
+ executor.execute(() -> {
96
+ BluetoothSocket socket = null;
97
+ OutputStream out = null;
98
+ try {
99
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
100
+
101
+ // Перевірка доступності Bluetooth
102
+ if (adapter == null || !adapter.isEnabled()) {
103
+ callback.onError("BLUETOOTH_DISABLED", new IllegalStateException("Bluetooth вимкнено або не підтримується."));
104
+ return;
105
+ }
106
+
107
+ BluetoothDevice device = adapter.getRemoteDevice(addrTrimmed);
108
+
109
+ // Спроба підключення через стандартний SPP UUID
110
+ try {
111
+ socket = device.createRfcommSocketToServiceRecord(SPP_UUID);
112
+ socket.connect();
113
+ } catch (IOException e) {
114
+ // Резервний метод підключення для деяких моделей принтерів
115
+ Logger.info("Zebra", "Помилка SPP, спроба через RFCOMM канал 1");
116
+ if (socket != null) socket.close();
117
+ socket = (BluetoothSocket) device.getClass()
118
+ .getMethod("createRfcommSocket", int.class)
119
+ .invoke(device, 1);
120
+ socket.connect();
121
+ }
122
+
123
+ out = socket.getOutputStream();
124
+ byte[] data = zpl.getBytes(StandardCharsets.UTF_8);
125
+ out.write(data);
126
+ out.flush();
127
+
128
+ Logger.info("Zebra", "ZPL успішно відправлено: " + data.length + " байт");
129
+ callback.onSuccess(addrTrimmed, data.length);
130
+
131
+ } catch (Exception e) {
132
+ Logger.error("Zebra", "Помилка друку: " + e.getMessage(), e);
133
+ callback.onError("PRINT_ERROR", e);
134
+ } finally {
135
+ // Закриття потоків та сокета
136
+ try {
137
+ if (out != null) out.close();
138
+ if (socket != null) socket.close();
139
+ } catch (IOException ignored) {}
140
+ }
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Модель даних для інформації про Bluetooth пристрій.
146
+ */
147
+ public static class BluetoothDeviceInfo {
148
+ public final String address;
149
+ public final String name;
150
+
151
+ public BluetoothDeviceInfo(String address, String name) {
152
+ this.address = address;
153
+ this.name = name;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Інтерфейс для зворотного зв'язку про результат операції друку.
159
+ */
160
+ public interface PrintCallback {
161
+ void onSuccess(String address, int bytesSent);
162
+ void onError(String code, Throwable error);
10
163
  }
11
164
  }
@@ -1,22 +1,158 @@
1
1
  package dev.nitra.zebra;
2
2
 
3
+ import android.Manifest;
3
4
  import com.getcapacitor.JSObject;
5
+ import com.getcapacitor.PermissionState;
4
6
  import com.getcapacitor.Plugin;
5
7
  import com.getcapacitor.PluginCall;
6
8
  import com.getcapacitor.PluginMethod;
7
9
  import com.getcapacitor.annotation.CapacitorPlugin;
10
+ import com.getcapacitor.annotation.Permission;
11
+ import com.getcapacitor.annotation.PermissionCallback;
8
12
 
9
- @CapacitorPlugin(name = "Zebra")
13
+ import org.json.JSONArray;
14
+ import org.json.JSONObject;
15
+
16
+ import java.util.List;
17
+
18
+ /**
19
+ * ZebraPlugin — Capacitor плагін для роботи з принтерами Zebra.
20
+ * Дозволяє шукати пристрої, встановлювати адресу та друкувати ZPL команди.
21
+ */
22
+ @CapacitorPlugin(
23
+ name = "Zebra",
24
+ permissions = {
25
+ @Permission(
26
+ strings = { Manifest.permission.BLUETOOTH_CONNECT },
27
+ alias = "bt_connect"
28
+ )
29
+ }
30
+ )
10
31
  public class ZebraPlugin extends Plugin {
11
32
 
12
- private Zebra implementation = new Zebra();
33
+ // Екземпляр класу з логікою реалізації взаємодії з принтером
34
+ private final Zebra implementation = new Zebra();
35
+
36
+ /**
37
+ * Встановлює адресу принтера (Bluetooth MAC-адресу).
38
+ */
39
+ @PluginMethod
40
+ public void setPrinterAddress(PluginCall call) {
41
+ String address = call.getString("address");
42
+ Integer port = call.getInt("port", 0);
43
+
44
+ // Перевірка наявності адреси
45
+ if (address == null || address.trim().isEmpty()) {
46
+ call.reject("Параметр 'address' обов'язковий (Bluetooth MAC-адреса принтера).");
47
+ return;
48
+ }
49
+
50
+ // Збереження адреси в реалізації
51
+ implementation.setPrinterAddress(getContext(), address.trim(), port != null ? port : 0);
52
+
53
+ JSObject ret = new JSObject();
54
+ ret.put("address", address.trim());
55
+ call.resolve(ret);
56
+ }
13
57
 
58
+ /**
59
+ * Отримує список підключених (paired) Bluetooth пристроїв.
60
+ * Спочатку перевіряє дозволи на роботу з Bluetooth.
61
+ */
14
62
  @PluginMethod
15
- public void echo(PluginCall call) {
16
- String value = call.getString("value");
63
+ public void getPairedDevices(PluginCall call) {
64
+ if (getPermissionState("bt_connect") != PermissionState.GRANTED) {
65
+ // Запит дозволу, якщо він не наданий
66
+ requestPermissionForAlias("bt_connect", call, "getPairedDevicesCallback");
67
+ } else {
68
+ loadPairedDevices(call);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Callback, який викликається після відповіді користувача на запит дозволу.
74
+ */
75
+ @PermissionCallback
76
+ private void getPairedDevicesCallback(PluginCall call) {
77
+ if (getPermissionState("bt_connect") != PermissionState.GRANTED) {
78
+ call.reject("Дозвіл необхідний для доступу до Bluetooth пристроїв.");
79
+ return;
80
+ }
81
+ loadPairedDevices(call);
82
+ }
17
83
 
84
+ /**
85
+ * Внутрішній метод для завантаження списку пристроїв та відправки результату в JS.
86
+ */
87
+ void loadPairedDevices(PluginCall call) {
88
+ List<Zebra.BluetoothDeviceInfo> devices = implementation.getPairedDevices(getContext());
89
+ JSONArray arr = new JSONArray();
90
+ for (Zebra.BluetoothDeviceInfo d : devices) {
91
+ JSONObject o = new JSONObject();
92
+ try {
93
+ o.put("address", d.address);
94
+ o.put("name", d.name);
95
+ } catch (Exception ignored) {
96
+ }
97
+ arr.put(o);
98
+ }
18
99
  JSObject ret = new JSObject();
19
- ret.put("value", implementation.echo(value));
100
+ ret.put("devices", arr);
20
101
  call.resolve(ret);
21
102
  }
103
+
104
+ /**
105
+ * Метод для друку ZPL коду.
106
+ * Підтримує параметри: { zpl, address?, port? }
107
+ */
108
+ @PluginMethod
109
+ public void print(PluginCall call) {
110
+ String zpl = call.getString("zpl");
111
+ String address = call.getString("address");
112
+ Integer port = call.getInt("port", 0);
113
+
114
+ // Виклик методу друку в основній реалізації
115
+ implementation.printZpl(
116
+ getContext(),
117
+ address,
118
+ port != null ? port : 0,
119
+ zpl != null ? zpl : "",
120
+ new Zebra.PrintCallback() {
121
+ @Override
122
+ public void onSuccess(String address, int bytesSent) {
123
+ // Повернення успішного результату в JS на головному потоці
124
+ runOnMainThread(() -> {
125
+ JSObject ret = new JSObject();
126
+ ret.put("success", true);
127
+ ret.put("address", address);
128
+ ret.put("bytesSent", bytesSent);
129
+ call.resolve(ret);
130
+ });
131
+ }
132
+
133
+ @Override
134
+ public void onError(String code, Throwable error) {
135
+ // Повернення помилки в JS
136
+ runOnMainThread(() -> {
137
+ JSObject ret = new JSObject();
138
+ ret.put("success", false);
139
+ ret.put("code", code);
140
+ ret.put("message", error.getMessage());
141
+ call.reject(error.getMessage(), code, new Exception(error), ret);
142
+ });
143
+ }
144
+ }
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Допоміжний метод для виконання коду на UI-потоці.
150
+ */
151
+ private void runOnMainThread(Runnable runnable) {
152
+ if (getActivity() != null) {
153
+ getActivity().runOnUiThread(runnable);
154
+ } else {
155
+ runnable.run();
156
+ }
157
+ }
22
158
  }
@@ -0,0 +1,35 @@
1
+ //#region src/index.d.ts
2
+ type PairedDevice = {
3
+ address: string;
4
+ name: string;
5
+ };
6
+ type PrintResult = {
7
+ success?: boolean;
8
+ devices?: PairedDevice[];
9
+ message?: string;
10
+ };
11
+ interface ZebraPlugin {
12
+ print(options: {
13
+ zpl: string;
14
+ address: string;
15
+ }): Promise<PrintResult>;
16
+ setPrinterAddress(options: {
17
+ address: string;
18
+ port?: number;
19
+ }): Promise<{
20
+ address: string;
21
+ }>;
22
+ getPairedDevices(): Promise<{
23
+ devices: PairedDevice[];
24
+ }>;
25
+ }
26
+ interface Zebra {
27
+ /** Приймає лише zpl як рядок; address береться з Preferences на нативних платформах. */
28
+ print(zpl: string): Promise<PrintResult>;
29
+ getPairedDevices(): Promise<{
30
+ devices: PairedDevice[];
31
+ }>;
32
+ }
33
+ declare const Zebra: Zebra;
34
+ //#endregion
35
+ export { PairedDevice, PrintResult, Zebra, ZebraPlugin };