@narcisbodea/smstunnel-sdk 1.0.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.
@@ -0,0 +1,624 @@
1
+ // src/react/SmsTunnelPairing.tsx
2
+ import { useState as useState2 } from "react";
3
+
4
+ // src/react/useSmsTunnel.ts
5
+ import { useState, useEffect, useRef, useCallback } from "react";
6
+ function useSmsTunnel(options) {
7
+ const { apiBaseUrl, getAuthHeaders, routePrefix = "smstunnel", pollInterval = 3e3 } = options;
8
+ const [state, setState] = useState({
9
+ status: "loading",
10
+ serverUrl: "",
11
+ deviceName: "",
12
+ showQr: false,
13
+ qrData: "",
14
+ generating: false,
15
+ polling: false,
16
+ error: ""
17
+ });
18
+ const pollRef = useRef(null);
19
+ const pairingTokenRef = useRef("");
20
+ const baseUrl = apiBaseUrl.replace(/\/$/, "");
21
+ const authHeaders = useCallback(() => {
22
+ return getAuthHeaders ? getAuthHeaders() : {};
23
+ }, [getAuthHeaders]);
24
+ const fetchStatus = useCallback(async () => {
25
+ try {
26
+ const res = await fetch(`${baseUrl}/${routePrefix}/status`, {
27
+ headers: authHeaders()
28
+ });
29
+ const data = await res.json();
30
+ setState((prev) => ({
31
+ ...prev,
32
+ status: data.paired ? "paired" : "unpaired",
33
+ serverUrl: data.serverUrl || "",
34
+ deviceName: data.deviceName || ""
35
+ }));
36
+ } catch {
37
+ setState((prev) => ({ ...prev, status: "unpaired" }));
38
+ }
39
+ }, [baseUrl, routePrefix, authHeaders]);
40
+ useEffect(() => {
41
+ fetchStatus();
42
+ }, [fetchStatus]);
43
+ useEffect(() => {
44
+ if (!state.polling || !pairingTokenRef.current) return;
45
+ pollRef.current = setInterval(async () => {
46
+ try {
47
+ const res = await fetch(
48
+ `${baseUrl}/${routePrefix}/pairing-status/${pairingTokenRef.current}`
49
+ );
50
+ const data = await res.json();
51
+ if (data.status === "completed") {
52
+ setState((prev) => ({ ...prev, polling: false, showQr: false }));
53
+ if (pollRef.current) clearInterval(pollRef.current);
54
+ setTimeout(() => fetchStatus(), 2e3);
55
+ } else if (data.status === "expired") {
56
+ setState((prev) => ({
57
+ ...prev,
58
+ polling: false,
59
+ showQr: false,
60
+ error: "QR code expired"
61
+ }));
62
+ if (pollRef.current) clearInterval(pollRef.current);
63
+ }
64
+ } catch {
65
+ }
66
+ }, pollInterval);
67
+ return () => {
68
+ if (pollRef.current) clearInterval(pollRef.current);
69
+ };
70
+ }, [state.polling, baseUrl, routePrefix, pollInterval, fetchStatus]);
71
+ const generateQr = useCallback(async () => {
72
+ setState((prev) => ({ ...prev, generating: true, error: "" }));
73
+ try {
74
+ const res = await fetch(`${baseUrl}/${routePrefix}/create-token`, {
75
+ method: "POST",
76
+ headers: {
77
+ "Content-Type": "application/json",
78
+ ...authHeaders()
79
+ }
80
+ });
81
+ const data = await res.json();
82
+ if (!data.success) {
83
+ setState((prev) => ({
84
+ ...prev,
85
+ generating: false,
86
+ error: data.error || "Failed to create QR code"
87
+ }));
88
+ return;
89
+ }
90
+ pairingTokenRef.current = data.token;
91
+ setState((prev) => ({
92
+ ...prev,
93
+ qrData: data.qrData,
94
+ showQr: true,
95
+ polling: true,
96
+ generating: false
97
+ }));
98
+ } catch (err) {
99
+ setState((prev) => ({
100
+ ...prev,
101
+ generating: false,
102
+ error: `Error: ${err.message}`
103
+ }));
104
+ }
105
+ }, [baseUrl, routePrefix, authHeaders]);
106
+ const cancelPairing = useCallback(() => {
107
+ if (pollRef.current) clearInterval(pollRef.current);
108
+ pairingTokenRef.current = "";
109
+ setState((prev) => ({
110
+ ...prev,
111
+ showQr: false,
112
+ polling: false,
113
+ qrData: ""
114
+ }));
115
+ }, []);
116
+ const unpair = useCallback(async () => {
117
+ try {
118
+ await fetch(`${baseUrl}/${routePrefix}/unpair`, {
119
+ method: "POST",
120
+ headers: authHeaders()
121
+ });
122
+ setState((prev) => ({
123
+ ...prev,
124
+ status: "unpaired",
125
+ deviceName: ""
126
+ }));
127
+ } catch (err) {
128
+ setState((prev) => ({ ...prev, error: `Unpair failed: ${err.message}` }));
129
+ }
130
+ }, [baseUrl, routePrefix, authHeaders]);
131
+ const sendTestSms = useCallback(
132
+ async (to, message) => {
133
+ try {
134
+ const res = await fetch(`${baseUrl}/${routePrefix}/send`, {
135
+ method: "POST",
136
+ headers: {
137
+ "Content-Type": "application/json",
138
+ ...authHeaders()
139
+ },
140
+ body: JSON.stringify({ to, message })
141
+ });
142
+ return await res.json();
143
+ } catch (err) {
144
+ return { success: false, error: err.message };
145
+ }
146
+ },
147
+ [baseUrl, routePrefix, authHeaders]
148
+ );
149
+ const updateServerUrl = useCallback(
150
+ async (serverUrl) => {
151
+ try {
152
+ await fetch(`${baseUrl}/${routePrefix}/update-config`, {
153
+ method: "POST",
154
+ headers: {
155
+ "Content-Type": "application/json",
156
+ ...authHeaders()
157
+ },
158
+ body: JSON.stringify({ serverUrl })
159
+ });
160
+ setState((prev) => ({ ...prev, serverUrl }));
161
+ } catch (err) {
162
+ setState((prev) => ({ ...prev, error: `Update failed: ${err.message}` }));
163
+ }
164
+ },
165
+ [baseUrl, routePrefix, authHeaders]
166
+ );
167
+ const refetch = useCallback(() => fetchStatus(), [fetchStatus]);
168
+ return {
169
+ ...state,
170
+ generateQr,
171
+ cancelPairing,
172
+ unpair,
173
+ sendTestSms,
174
+ updateServerUrl,
175
+ refetch
176
+ };
177
+ }
178
+
179
+ // src/react/QrCodeCanvas.tsx
180
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
181
+ import qrcode from "qrcode-generator";
182
+ import { jsx } from "react/jsx-runtime";
183
+ function QrCodeCanvas({ value, size = 200 }) {
184
+ const canvasRef = useRef2(null);
185
+ useEffect2(() => {
186
+ if (!canvasRef.current || !value) return;
187
+ const canvas = canvasRef.current;
188
+ const ctx = canvas.getContext("2d");
189
+ if (!ctx) return;
190
+ const qr = qrcode(0, "M");
191
+ qr.addData(value);
192
+ qr.make();
193
+ const moduleCount = qr.getModuleCount();
194
+ const cellSize = size / moduleCount;
195
+ canvas.width = size;
196
+ canvas.height = size;
197
+ ctx.fillStyle = "#ffffff";
198
+ ctx.fillRect(0, 0, size, size);
199
+ ctx.fillStyle = "#000000";
200
+ for (let row = 0; row < moduleCount; row++) {
201
+ for (let col = 0; col < moduleCount; col++) {
202
+ if (qr.isDark(row, col)) {
203
+ ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
204
+ }
205
+ }
206
+ }
207
+ }, [value, size]);
208
+ return /* @__PURE__ */ jsx("canvas", { ref: canvasRef, width: size, height: size });
209
+ }
210
+
211
+ // src/shared/labels.ts
212
+ var EN_LABELS = {
213
+ title: "SMS Configuration (SMSTunnel)",
214
+ description: "Connect an Android phone with the SMSTunnel app to send SMS directly from the application.",
215
+ serverUrlLabel: "SMSTunnel Server",
216
+ serverUrlPlaceholder: "https://smstunnel.io",
217
+ saveButton: "Save",
218
+ pairedStatus: "Connected",
219
+ deviceLabel: "Device",
220
+ unpairButton: "Disconnect",
221
+ unpairConfirm: "Are you sure you want to disconnect the SMS device?",
222
+ notPairedStatus: "Not connected",
223
+ notPairedDescription: "Scan the QR code with the SMSTunnel app on your Android phone.",
224
+ connectButton: "Connect phone",
225
+ scanQrPrompt: "Scan this QR code with the SMSTunnel app on your phone:",
226
+ waitingForPairing: "Waiting for connection...",
227
+ cancelButton: "Cancel",
228
+ qrExpired: "QR code has expired. Generate a new one.",
229
+ testSmsTitle: "Send test SMS",
230
+ testPhonePlaceholder: "Phone number (e.g., +1234567890)",
231
+ testMessagePlaceholder: "Message",
232
+ sendTestButton: "Send",
233
+ smsSentSuccess: "SMS sent successfully!",
234
+ smsError: "Error"
235
+ };
236
+ var RO_LABELS = {
237
+ title: "Configurare SMS (SMSTunnel)",
238
+ description: "Conecteaza un telefon Android cu aplicatia SMSTunnel pentru a trimite SMS-uri direct din aplicatie.",
239
+ serverUrlLabel: "Server SMSTunnel",
240
+ serverUrlPlaceholder: "https://smstunnel.io",
241
+ saveButton: "Salveaza",
242
+ pairedStatus: "Conectat",
243
+ deviceLabel: "Dispozitiv",
244
+ unpairButton: "Deconecteaza",
245
+ unpairConfirm: "Sigur doresti sa deconectezi dispozitivul SMS?",
246
+ notPairedStatus: "Neconectat",
247
+ notPairedDescription: "Scaneaza codul QR cu aplicatia SMSTunnel de pe telefonul Android.",
248
+ connectButton: "Conecteaza telefon",
249
+ scanQrPrompt: "Scaneaza acest cod QR cu aplicatia SMSTunnel de pe telefon:",
250
+ waitingForPairing: "Se asteapta conectarea...",
251
+ cancelButton: "Anuleaza",
252
+ qrExpired: "Codul QR a expirat. Genereaza unul nou.",
253
+ testSmsTitle: "Trimite SMS de test",
254
+ testPhonePlaceholder: "Nr. telefon (ex: 0741234567)",
255
+ testMessagePlaceholder: "Mesaj",
256
+ sendTestButton: "Trimite",
257
+ smsSentSuccess: "SMS trimis cu succes!",
258
+ smsError: "Eroare"
259
+ };
260
+
261
+ // src/react/SmsTunnelPairing.tsx
262
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
263
+ var styles = {
264
+ container: {
265
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
266
+ fontSize: "14px",
267
+ color: "#1f2937"
268
+ },
269
+ card: {
270
+ border: "1px solid #e5e7eb",
271
+ borderRadius: "12px",
272
+ padding: "24px",
273
+ backgroundColor: "#ffffff"
274
+ },
275
+ title: {
276
+ fontSize: "18px",
277
+ fontWeight: 600,
278
+ color: "#111827",
279
+ marginBottom: "4px"
280
+ },
281
+ subtitle: {
282
+ fontSize: "13px",
283
+ color: "#6b7280",
284
+ marginBottom: "20px"
285
+ },
286
+ section: {
287
+ border: "1px solid #e5e7eb",
288
+ borderRadius: "8px",
289
+ padding: "16px",
290
+ marginBottom: "16px"
291
+ },
292
+ label: {
293
+ display: "block",
294
+ fontSize: "13px",
295
+ fontWeight: 500,
296
+ color: "#374151",
297
+ marginBottom: "6px"
298
+ },
299
+ inputRow: {
300
+ display: "flex",
301
+ gap: "8px"
302
+ },
303
+ input: {
304
+ flex: 1,
305
+ border: "1px solid #d1d5db",
306
+ borderRadius: "8px",
307
+ padding: "6px 12px",
308
+ fontSize: "13px",
309
+ outline: "none"
310
+ },
311
+ button: {
312
+ padding: "6px 16px",
313
+ borderRadius: "8px",
314
+ border: "none",
315
+ fontSize: "13px",
316
+ fontWeight: 500,
317
+ cursor: "pointer",
318
+ backgroundColor: "#4f46e5",
319
+ color: "#ffffff",
320
+ transition: "opacity 0.2s"
321
+ },
322
+ buttonSecondary: {
323
+ padding: "6px 16px",
324
+ borderRadius: "8px",
325
+ border: "1px solid #d1d5db",
326
+ fontSize: "13px",
327
+ fontWeight: 500,
328
+ cursor: "pointer",
329
+ backgroundColor: "#ffffff",
330
+ color: "#374151"
331
+ },
332
+ buttonDanger: {
333
+ padding: "6px",
334
+ borderRadius: "8px",
335
+ border: "none",
336
+ cursor: "pointer",
337
+ backgroundColor: "transparent",
338
+ color: "#9ca3af",
339
+ transition: "color 0.2s"
340
+ },
341
+ pairedBanner: {
342
+ display: "flex",
343
+ alignItems: "center",
344
+ gap: "12px",
345
+ border: "1px solid #bbf7d0",
346
+ backgroundColor: "#f0fdf4",
347
+ borderRadius: "8px",
348
+ padding: "16px",
349
+ marginBottom: "16px"
350
+ },
351
+ pairedIcon: {
352
+ width: "40px",
353
+ height: "40px",
354
+ borderRadius: "50%",
355
+ backgroundColor: "#dcfce7",
356
+ display: "flex",
357
+ alignItems: "center",
358
+ justifyContent: "center",
359
+ color: "#16a34a",
360
+ fontSize: "18px",
361
+ flexShrink: 0
362
+ },
363
+ unpairedBanner: {
364
+ display: "flex",
365
+ alignItems: "center",
366
+ gap: "12px",
367
+ border: "1px solid #fde68a",
368
+ backgroundColor: "#fefce8",
369
+ borderRadius: "8px",
370
+ padding: "16px",
371
+ marginBottom: "16px"
372
+ },
373
+ qrContainer: {
374
+ textAlign: "center",
375
+ padding: "16px"
376
+ },
377
+ qrFrame: {
378
+ display: "inline-block",
379
+ border: "2px dashed #a5b4fc",
380
+ borderRadius: "12px",
381
+ padding: "16px",
382
+ backgroundColor: "#ffffff",
383
+ marginBottom: "12px"
384
+ },
385
+ spinner: {
386
+ display: "inline-block",
387
+ width: "12px",
388
+ height: "12px",
389
+ border: "2px solid #d1d5db",
390
+ borderTopColor: "#4f46e5",
391
+ borderRadius: "50%",
392
+ animation: "smstunnel-spin 0.8s linear infinite",
393
+ marginRight: "8px",
394
+ verticalAlign: "middle"
395
+ },
396
+ error: {
397
+ color: "#dc2626",
398
+ fontSize: "13px",
399
+ marginBottom: "12px"
400
+ },
401
+ success: {
402
+ color: "#16a34a",
403
+ fontSize: "12px",
404
+ marginTop: "8px"
405
+ },
406
+ errorSmall: {
407
+ color: "#dc2626",
408
+ fontSize: "12px",
409
+ marginTop: "8px"
410
+ }
411
+ };
412
+ var stylesInjected = false;
413
+ function injectKeyframes() {
414
+ if (stylesInjected || typeof document === "undefined") return;
415
+ const style = document.createElement("style");
416
+ style.textContent = `@keyframes smstunnel-spin { to { transform: rotate(360deg); } }`;
417
+ document.head.appendChild(style);
418
+ stylesInjected = true;
419
+ }
420
+ function SmsTunnelPairing({
421
+ apiBaseUrl,
422
+ getAuthHeaders,
423
+ labels = EN_LABELS,
424
+ onPaired,
425
+ onUnpaired,
426
+ showTestSms = true,
427
+ showServerUrlInput = true,
428
+ qrSize = 220,
429
+ routePrefix = "smstunnel",
430
+ className
431
+ }) {
432
+ injectKeyframes();
433
+ const tunnel = useSmsTunnel({ apiBaseUrl, getAuthHeaders, routePrefix });
434
+ const [localServerUrl, setLocalServerUrl] = useState2("");
435
+ const [savingUrl, setSavingUrl] = useState2(false);
436
+ const [testPhone, setTestPhone] = useState2("");
437
+ const [testMsg, setTestMsg] = useState2("Test SMS");
438
+ const [testResult, setTestResult] = useState2(null);
439
+ const [sendingTest, setSendingTest] = useState2(false);
440
+ if (tunnel.serverUrl && !localServerUrl) {
441
+ setLocalServerUrl(tunnel.serverUrl);
442
+ }
443
+ const handleSaveUrl = async () => {
444
+ if (!localServerUrl.trim()) return;
445
+ setSavingUrl(true);
446
+ await tunnel.updateServerUrl(localServerUrl.trim().replace(/\/$/, ""));
447
+ setSavingUrl(false);
448
+ };
449
+ const handleUnpair = async () => {
450
+ if (!confirm(labels.unpairConfirm)) return;
451
+ await tunnel.unpair();
452
+ onUnpaired?.();
453
+ };
454
+ const handleGenerateQr = async () => {
455
+ await tunnel.generateQr();
456
+ };
457
+ const prevStatus = tunnel.status;
458
+ if (prevStatus === "paired" && tunnel.deviceName) {
459
+ }
460
+ const handleTestSms = async () => {
461
+ if (!testPhone.trim()) return;
462
+ setSendingTest(true);
463
+ setTestResult(null);
464
+ const result = await tunnel.sendTestSms(testPhone.trim(), testMsg);
465
+ setTestResult(result);
466
+ setSendingTest(false);
467
+ };
468
+ if (tunnel.status === "loading") {
469
+ return /* @__PURE__ */ jsx2("div", { style: styles.container, className, children: /* @__PURE__ */ jsx2("div", { style: styles.card, children: /* @__PURE__ */ jsx2("div", { style: { textAlign: "center", padding: "24px" }, children: /* @__PURE__ */ jsx2("span", { style: styles.spinner }) }) }) });
470
+ }
471
+ return /* @__PURE__ */ jsx2("div", { style: styles.container, className, children: /* @__PURE__ */ jsxs("div", { style: styles.card, children: [
472
+ /* @__PURE__ */ jsx2("div", { style: styles.title, children: labels.title }),
473
+ /* @__PURE__ */ jsx2("div", { style: styles.subtitle, children: labels.description }),
474
+ showServerUrlInput && /* @__PURE__ */ jsxs("div", { style: styles.section, children: [
475
+ /* @__PURE__ */ jsx2("label", { style: styles.label, children: labels.serverUrlLabel }),
476
+ /* @__PURE__ */ jsxs("div", { style: styles.inputRow, children: [
477
+ /* @__PURE__ */ jsx2(
478
+ "input",
479
+ {
480
+ value: localServerUrl,
481
+ onChange: (e) => setLocalServerUrl(e.target.value),
482
+ placeholder: labels.serverUrlPlaceholder,
483
+ style: styles.input
484
+ }
485
+ ),
486
+ /* @__PURE__ */ jsx2(
487
+ "button",
488
+ {
489
+ style: {
490
+ ...styles.button,
491
+ opacity: savingUrl || !localServerUrl.trim() ? 0.5 : 1
492
+ },
493
+ onClick: handleSaveUrl,
494
+ disabled: savingUrl || !localServerUrl.trim(),
495
+ children: labels.saveButton
496
+ }
497
+ )
498
+ ] })
499
+ ] }),
500
+ tunnel.error && /* @__PURE__ */ jsx2("div", { style: styles.error, children: tunnel.error }),
501
+ tunnel.status === "paired" ? /* @__PURE__ */ jsxs("div", { children: [
502
+ /* @__PURE__ */ jsxs("div", { style: styles.pairedBanner, children: [
503
+ /* @__PURE__ */ jsx2("div", { style: styles.pairedIcon, children: "\u2713" }),
504
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
505
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "14px", fontWeight: 600, color: "#166534" }, children: labels.pairedStatus }),
506
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: "12px", color: "#16a34a" }, children: [
507
+ labels.deviceLabel,
508
+ ": ",
509
+ tunnel.deviceName || "Android Phone"
510
+ ] })
511
+ ] }),
512
+ /* @__PURE__ */ jsx2(
513
+ "button",
514
+ {
515
+ onClick: handleUnpair,
516
+ style: styles.buttonDanger,
517
+ title: labels.unpairButton,
518
+ children: "\u2715"
519
+ }
520
+ )
521
+ ] }),
522
+ showTestSms && /* @__PURE__ */ jsxs("div", { style: styles.section, children: [
523
+ /* @__PURE__ */ jsx2(
524
+ "div",
525
+ {
526
+ style: {
527
+ fontSize: "13px",
528
+ fontWeight: 600,
529
+ color: "#374151",
530
+ marginBottom: "12px"
531
+ },
532
+ children: labels.testSmsTitle
533
+ }
534
+ ),
535
+ /* @__PURE__ */ jsxs("div", { style: styles.inputRow, children: [
536
+ /* @__PURE__ */ jsx2(
537
+ "input",
538
+ {
539
+ value: testPhone,
540
+ onChange: (e) => setTestPhone(e.target.value),
541
+ placeholder: labels.testPhonePlaceholder,
542
+ style: styles.input
543
+ }
544
+ ),
545
+ /* @__PURE__ */ jsx2(
546
+ "input",
547
+ {
548
+ value: testMsg,
549
+ onChange: (e) => setTestMsg(e.target.value),
550
+ placeholder: labels.testMessagePlaceholder,
551
+ style: styles.input
552
+ }
553
+ ),
554
+ /* @__PURE__ */ jsx2(
555
+ "button",
556
+ {
557
+ style: {
558
+ ...styles.button,
559
+ opacity: sendingTest || !testPhone.trim() ? 0.5 : 1
560
+ },
561
+ onClick: handleTestSms,
562
+ disabled: sendingTest || !testPhone.trim(),
563
+ children: sendingTest ? /* @__PURE__ */ jsx2("span", { style: styles.spinner }) : labels.sendTestButton
564
+ }
565
+ )
566
+ ] }),
567
+ testResult && /* @__PURE__ */ jsx2("div", { style: testResult.success ? styles.success : styles.errorSmall, children: testResult.success ? `${labels.smsSentSuccess} (ID: ${testResult.messageId})` : `${labels.smsError}: ${testResult.error}` })
568
+ ] })
569
+ ] }) : /* @__PURE__ */ jsxs("div", { children: [
570
+ /* @__PURE__ */ jsxs("div", { style: styles.unpairedBanner, children: [
571
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "24px" }, children: "\u{1F4F1}" }),
572
+ /* @__PURE__ */ jsxs("div", { children: [
573
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "14px", fontWeight: 600, color: "#854d0e" }, children: labels.notPairedStatus }),
574
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "12px", color: "#a16207" }, children: labels.notPairedDescription })
575
+ ] })
576
+ ] }),
577
+ !tunnel.showQr ? /* @__PURE__ */ jsxs(
578
+ "button",
579
+ {
580
+ style: {
581
+ ...styles.button,
582
+ opacity: tunnel.generating ? 0.5 : 1
583
+ },
584
+ onClick: handleGenerateQr,
585
+ disabled: tunnel.generating,
586
+ children: [
587
+ tunnel.generating && /* @__PURE__ */ jsx2("span", { style: styles.spinner }),
588
+ labels.connectButton
589
+ ]
590
+ }
591
+ ) : /* @__PURE__ */ jsxs("div", { style: styles.qrContainer, children: [
592
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "13px", color: "#4b5563", marginBottom: "12px" }, children: labels.scanQrPrompt }),
593
+ /* @__PURE__ */ jsx2("div", { style: styles.qrFrame, children: /* @__PURE__ */ jsx2(QrCodeCanvas, { value: tunnel.qrData, size: qrSize }) }),
594
+ /* @__PURE__ */ jsxs(
595
+ "div",
596
+ {
597
+ style: {
598
+ display: "flex",
599
+ alignItems: "center",
600
+ justifyContent: "center",
601
+ gap: "8px",
602
+ fontSize: "12px",
603
+ color: "#9ca3af",
604
+ marginBottom: "12px"
605
+ },
606
+ children: [
607
+ /* @__PURE__ */ jsx2("span", { style: styles.spinner }),
608
+ labels.waitingForPairing
609
+ ]
610
+ }
611
+ ),
612
+ /* @__PURE__ */ jsx2("button", { style: styles.buttonSecondary, onClick: tunnel.cancelPairing, children: labels.cancelButton })
613
+ ] })
614
+ ] })
615
+ ] }) });
616
+ }
617
+ export {
618
+ EN_LABELS,
619
+ QrCodeCanvas,
620
+ RO_LABELS,
621
+ SmsTunnelPairing,
622
+ useSmsTunnel
623
+ };
624
+ //# sourceMappingURL=index.mjs.map