@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,155 @@
1
+ import * as vue from 'vue';
2
+ import { PropType } from 'vue';
3
+
4
+ interface SmsTunnelLabels {
5
+ title: string;
6
+ description: string;
7
+ serverUrlLabel: string;
8
+ serverUrlPlaceholder: string;
9
+ saveButton: string;
10
+ pairedStatus: string;
11
+ deviceLabel: string;
12
+ unpairButton: string;
13
+ unpairConfirm: string;
14
+ notPairedStatus: string;
15
+ notPairedDescription: string;
16
+ connectButton: string;
17
+ scanQrPrompt: string;
18
+ waitingForPairing: string;
19
+ cancelButton: string;
20
+ qrExpired: string;
21
+ testSmsTitle: string;
22
+ testPhonePlaceholder: string;
23
+ testMessagePlaceholder: string;
24
+ sendTestButton: string;
25
+ smsSentSuccess: string;
26
+ smsError: string;
27
+ }
28
+ declare const EN_LABELS: SmsTunnelLabels;
29
+ declare const RO_LABELS: SmsTunnelLabels;
30
+
31
+ declare const SmsTunnelPairing: vue.DefineComponent<vue.ExtractPropTypes<{
32
+ apiBaseUrl: {
33
+ type: StringConstructor;
34
+ required: true;
35
+ };
36
+ getAuthHeaders: {
37
+ type: PropType<() => Record<string, string>>;
38
+ default: undefined;
39
+ };
40
+ labels: {
41
+ type: PropType<SmsTunnelLabels>;
42
+ default: () => SmsTunnelLabels;
43
+ };
44
+ showTestSms: {
45
+ type: BooleanConstructor;
46
+ default: boolean;
47
+ };
48
+ showServerUrlInput: {
49
+ type: BooleanConstructor;
50
+ default: boolean;
51
+ };
52
+ qrSize: {
53
+ type: NumberConstructor;
54
+ default: number;
55
+ };
56
+ routePrefix: {
57
+ type: StringConstructor;
58
+ default: string;
59
+ };
60
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
61
+ [key: string]: any;
62
+ }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, ("unpaired" | "paired")[], "unpaired" | "paired", vue.PublicProps, Readonly<vue.ExtractPropTypes<{
63
+ apiBaseUrl: {
64
+ type: StringConstructor;
65
+ required: true;
66
+ };
67
+ getAuthHeaders: {
68
+ type: PropType<() => Record<string, string>>;
69
+ default: undefined;
70
+ };
71
+ labels: {
72
+ type: PropType<SmsTunnelLabels>;
73
+ default: () => SmsTunnelLabels;
74
+ };
75
+ showTestSms: {
76
+ type: BooleanConstructor;
77
+ default: boolean;
78
+ };
79
+ showServerUrlInput: {
80
+ type: BooleanConstructor;
81
+ default: boolean;
82
+ };
83
+ qrSize: {
84
+ type: NumberConstructor;
85
+ default: number;
86
+ };
87
+ routePrefix: {
88
+ type: StringConstructor;
89
+ default: string;
90
+ };
91
+ }>> & Readonly<{
92
+ onUnpaired?: ((...args: any[]) => any) | undefined;
93
+ onPaired?: ((...args: any[]) => any) | undefined;
94
+ }>, {
95
+ getAuthHeaders: () => Record<string, string>;
96
+ routePrefix: string;
97
+ labels: SmsTunnelLabels;
98
+ showTestSms: boolean;
99
+ showServerUrlInput: boolean;
100
+ qrSize: number;
101
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
102
+
103
+ declare const QrCodeCanvas: vue.DefineComponent<vue.ExtractPropTypes<{
104
+ value: {
105
+ type: StringConstructor;
106
+ required: true;
107
+ };
108
+ size: {
109
+ type: NumberConstructor;
110
+ default: number;
111
+ };
112
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
113
+ [key: string]: any;
114
+ }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
115
+ value: {
116
+ type: StringConstructor;
117
+ required: true;
118
+ };
119
+ size: {
120
+ type: NumberConstructor;
121
+ default: number;
122
+ };
123
+ }>> & Readonly<{}>, {
124
+ size: number;
125
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
126
+
127
+ interface UseSmsTunnelOptions {
128
+ apiBaseUrl: string;
129
+ getAuthHeaders?: () => Record<string, string>;
130
+ routePrefix?: string;
131
+ pollInterval?: number;
132
+ }
133
+ interface SendSmsResult {
134
+ success: boolean;
135
+ messageId?: string;
136
+ error?: string;
137
+ }
138
+ declare function useSmsTunnel(options: UseSmsTunnelOptions): {
139
+ status: vue.Ref<"loading" | "unpaired" | "paired", "loading" | "unpaired" | "paired">;
140
+ serverUrl: vue.Ref<string, string>;
141
+ deviceName: vue.Ref<string, string>;
142
+ showQr: vue.Ref<boolean, boolean>;
143
+ qrData: vue.Ref<string, string>;
144
+ generating: vue.Ref<boolean, boolean>;
145
+ polling: vue.Ref<boolean, boolean>;
146
+ error: vue.Ref<string, string>;
147
+ generateQr: () => Promise<void>;
148
+ cancelPairing: () => void;
149
+ unpair: () => Promise<void>;
150
+ sendTestSms: (to: string, message: string) => Promise<SendSmsResult>;
151
+ updateServerUrl: (url: string) => Promise<void>;
152
+ refetch: () => Promise<void>;
153
+ };
154
+
155
+ export { EN_LABELS, QrCodeCanvas, RO_LABELS, type SendSmsResult, type SmsTunnelLabels, SmsTunnelPairing, type UseSmsTunnelOptions, useSmsTunnel };
@@ -0,0 +1,525 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/vue/index.ts
31
+ var vue_exports = {};
32
+ __export(vue_exports, {
33
+ EN_LABELS: () => EN_LABELS,
34
+ QrCodeCanvas: () => QrCodeCanvas,
35
+ RO_LABELS: () => RO_LABELS,
36
+ SmsTunnelPairing: () => SmsTunnelPairing,
37
+ useSmsTunnel: () => useSmsTunnel
38
+ });
39
+ module.exports = __toCommonJS(vue_exports);
40
+
41
+ // src/vue/SmsTunnelPairing.ts
42
+ var import_vue3 = require("vue");
43
+
44
+ // src/vue/useSmsTunnel.ts
45
+ var import_vue = require("vue");
46
+ function useSmsTunnel(options) {
47
+ const {
48
+ apiBaseUrl,
49
+ getAuthHeaders,
50
+ routePrefix = "smstunnel",
51
+ pollInterval = 3e3
52
+ } = options;
53
+ const baseUrl = apiBaseUrl.replace(/\/$/, "");
54
+ const status = (0, import_vue.ref)("loading");
55
+ const serverUrl = (0, import_vue.ref)("");
56
+ const deviceName = (0, import_vue.ref)("");
57
+ const showQr = (0, import_vue.ref)(false);
58
+ const qrData = (0, import_vue.ref)("");
59
+ const generating = (0, import_vue.ref)(false);
60
+ const polling = (0, import_vue.ref)(false);
61
+ const error = (0, import_vue.ref)("");
62
+ let pairingToken = "";
63
+ let pollTimer = null;
64
+ function authHeaders() {
65
+ return getAuthHeaders ? getAuthHeaders() : {};
66
+ }
67
+ async function fetchStatus() {
68
+ try {
69
+ const res = await fetch(`${baseUrl}/${routePrefix}/status`, {
70
+ headers: authHeaders()
71
+ });
72
+ const data = await res.json();
73
+ status.value = data.paired ? "paired" : "unpaired";
74
+ serverUrl.value = data.serverUrl || "";
75
+ deviceName.value = data.deviceName || "";
76
+ } catch {
77
+ status.value = "unpaired";
78
+ }
79
+ }
80
+ function startPolling() {
81
+ stopPolling();
82
+ pollTimer = setInterval(async () => {
83
+ if (!pairingToken) return;
84
+ try {
85
+ const res = await fetch(
86
+ `${baseUrl}/${routePrefix}/pairing-status/${pairingToken}`
87
+ );
88
+ const data = await res.json();
89
+ if (data.status === "completed") {
90
+ polling.value = false;
91
+ showQr.value = false;
92
+ stopPolling();
93
+ setTimeout(() => fetchStatus(), 2e3);
94
+ } else if (data.status === "expired") {
95
+ polling.value = false;
96
+ showQr.value = false;
97
+ error.value = "QR code expired";
98
+ stopPolling();
99
+ }
100
+ } catch {
101
+ }
102
+ }, pollInterval);
103
+ }
104
+ function stopPolling() {
105
+ if (pollTimer) {
106
+ clearInterval(pollTimer);
107
+ pollTimer = null;
108
+ }
109
+ }
110
+ (0, import_vue.watch)(polling, (val) => {
111
+ if (val) startPolling();
112
+ else stopPolling();
113
+ });
114
+ async function generateQr() {
115
+ generating.value = true;
116
+ error.value = "";
117
+ try {
118
+ const res = await fetch(`${baseUrl}/${routePrefix}/create-token`, {
119
+ method: "POST",
120
+ headers: { "Content-Type": "application/json", ...authHeaders() }
121
+ });
122
+ const data = await res.json();
123
+ if (!data.success) {
124
+ error.value = data.error || "Failed to create QR code";
125
+ generating.value = false;
126
+ return;
127
+ }
128
+ pairingToken = data.token;
129
+ qrData.value = data.qrData;
130
+ showQr.value = true;
131
+ polling.value = true;
132
+ } catch (err) {
133
+ error.value = `Error: ${err.message}`;
134
+ } finally {
135
+ generating.value = false;
136
+ }
137
+ }
138
+ function cancelPairing() {
139
+ stopPolling();
140
+ pairingToken = "";
141
+ showQr.value = false;
142
+ polling.value = false;
143
+ qrData.value = "";
144
+ }
145
+ async function unpair() {
146
+ try {
147
+ await fetch(`${baseUrl}/${routePrefix}/unpair`, {
148
+ method: "POST",
149
+ headers: authHeaders()
150
+ });
151
+ status.value = "unpaired";
152
+ deviceName.value = "";
153
+ } catch (err) {
154
+ error.value = `Unpair failed: ${err.message}`;
155
+ }
156
+ }
157
+ async function sendTestSms(to, message) {
158
+ try {
159
+ const res = await fetch(`${baseUrl}/${routePrefix}/send`, {
160
+ method: "POST",
161
+ headers: { "Content-Type": "application/json", ...authHeaders() },
162
+ body: JSON.stringify({ to, message })
163
+ });
164
+ return await res.json();
165
+ } catch (err) {
166
+ return { success: false, error: err.message };
167
+ }
168
+ }
169
+ async function updateServerUrl(url) {
170
+ try {
171
+ await fetch(`${baseUrl}/${routePrefix}/update-config`, {
172
+ method: "POST",
173
+ headers: { "Content-Type": "application/json", ...authHeaders() },
174
+ body: JSON.stringify({ serverUrl: url })
175
+ });
176
+ serverUrl.value = url;
177
+ } catch (err) {
178
+ error.value = `Update failed: ${err.message}`;
179
+ }
180
+ }
181
+ (0, import_vue.onMounted)(() => fetchStatus());
182
+ (0, import_vue.onUnmounted)(() => stopPolling());
183
+ return {
184
+ status,
185
+ serverUrl,
186
+ deviceName,
187
+ showQr,
188
+ qrData,
189
+ generating,
190
+ polling,
191
+ error,
192
+ generateQr,
193
+ cancelPairing,
194
+ unpair,
195
+ sendTestSms,
196
+ updateServerUrl,
197
+ refetch: fetchStatus
198
+ };
199
+ }
200
+
201
+ // src/vue/QrCodeCanvas.ts
202
+ var import_vue2 = require("vue");
203
+ var import_qrcode_generator = __toESM(require("qrcode-generator"));
204
+ var QrCodeCanvas = (0, import_vue2.defineComponent)({
205
+ name: "QrCodeCanvas",
206
+ props: {
207
+ value: { type: String, required: true },
208
+ size: { type: Number, default: 200 }
209
+ },
210
+ setup(props) {
211
+ const canvasRef = (0, import_vue2.ref)(null);
212
+ function render() {
213
+ if (!canvasRef.value || !props.value) return;
214
+ const canvas = canvasRef.value;
215
+ const ctx = canvas.getContext("2d");
216
+ if (!ctx) return;
217
+ const qr = (0, import_qrcode_generator.default)(0, "M");
218
+ qr.addData(props.value);
219
+ qr.make();
220
+ const moduleCount = qr.getModuleCount();
221
+ const cellSize = props.size / moduleCount;
222
+ canvas.width = props.size;
223
+ canvas.height = props.size;
224
+ ctx.fillStyle = "#ffffff";
225
+ ctx.fillRect(0, 0, props.size, props.size);
226
+ ctx.fillStyle = "#000000";
227
+ for (let row = 0; row < moduleCount; row++) {
228
+ for (let col = 0; col < moduleCount; col++) {
229
+ if (qr.isDark(row, col)) {
230
+ ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
231
+ }
232
+ }
233
+ }
234
+ }
235
+ (0, import_vue2.onMounted)(render);
236
+ (0, import_vue2.watch)(() => [props.value, props.size], render);
237
+ return () => (0, import_vue2.h)("canvas", {
238
+ ref: canvasRef,
239
+ width: props.size,
240
+ height: props.size
241
+ });
242
+ }
243
+ });
244
+
245
+ // src/shared/labels.ts
246
+ var EN_LABELS = {
247
+ title: "SMS Configuration (SMSTunnel)",
248
+ description: "Connect an Android phone with the SMSTunnel app to send SMS directly from the application.",
249
+ serverUrlLabel: "SMSTunnel Server",
250
+ serverUrlPlaceholder: "https://smstunnel.io",
251
+ saveButton: "Save",
252
+ pairedStatus: "Connected",
253
+ deviceLabel: "Device",
254
+ unpairButton: "Disconnect",
255
+ unpairConfirm: "Are you sure you want to disconnect the SMS device?",
256
+ notPairedStatus: "Not connected",
257
+ notPairedDescription: "Scan the QR code with the SMSTunnel app on your Android phone.",
258
+ connectButton: "Connect phone",
259
+ scanQrPrompt: "Scan this QR code with the SMSTunnel app on your phone:",
260
+ waitingForPairing: "Waiting for connection...",
261
+ cancelButton: "Cancel",
262
+ qrExpired: "QR code has expired. Generate a new one.",
263
+ testSmsTitle: "Send test SMS",
264
+ testPhonePlaceholder: "Phone number (e.g., +1234567890)",
265
+ testMessagePlaceholder: "Message",
266
+ sendTestButton: "Send",
267
+ smsSentSuccess: "SMS sent successfully!",
268
+ smsError: "Error"
269
+ };
270
+ var RO_LABELS = {
271
+ title: "Configurare SMS (SMSTunnel)",
272
+ description: "Conecteaza un telefon Android cu aplicatia SMSTunnel pentru a trimite SMS-uri direct din aplicatie.",
273
+ serverUrlLabel: "Server SMSTunnel",
274
+ serverUrlPlaceholder: "https://smstunnel.io",
275
+ saveButton: "Salveaza",
276
+ pairedStatus: "Conectat",
277
+ deviceLabel: "Dispozitiv",
278
+ unpairButton: "Deconecteaza",
279
+ unpairConfirm: "Sigur doresti sa deconectezi dispozitivul SMS?",
280
+ notPairedStatus: "Neconectat",
281
+ notPairedDescription: "Scaneaza codul QR cu aplicatia SMSTunnel de pe telefonul Android.",
282
+ connectButton: "Conecteaza telefon",
283
+ scanQrPrompt: "Scaneaza acest cod QR cu aplicatia SMSTunnel de pe telefon:",
284
+ waitingForPairing: "Se asteapta conectarea...",
285
+ cancelButton: "Anuleaza",
286
+ qrExpired: "Codul QR a expirat. Genereaza unul nou.",
287
+ testSmsTitle: "Trimite SMS de test",
288
+ testPhonePlaceholder: "Nr. telefon (ex: 0741234567)",
289
+ testMessagePlaceholder: "Mesaj",
290
+ sendTestButton: "Trimite",
291
+ smsSentSuccess: "SMS trimis cu succes!",
292
+ smsError: "Eroare"
293
+ };
294
+
295
+ // src/vue/SmsTunnelPairing.ts
296
+ var s = {
297
+ container: { fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: "14px", color: "#1f2937" },
298
+ card: { border: "1px solid #e5e7eb", borderRadius: "12px", padding: "24px", backgroundColor: "#ffffff" },
299
+ title: { fontSize: "18px", fontWeight: "600", color: "#111827", marginBottom: "4px" },
300
+ subtitle: { fontSize: "13px", color: "#6b7280", marginBottom: "20px" },
301
+ section: { border: "1px solid #e5e7eb", borderRadius: "8px", padding: "16px", marginBottom: "16px" },
302
+ label: { display: "block", fontSize: "13px", fontWeight: "500", color: "#374151", marginBottom: "6px" },
303
+ inputRow: { display: "flex", gap: "8px" },
304
+ input: { flex: "1", border: "1px solid #d1d5db", borderRadius: "8px", padding: "6px 12px", fontSize: "13px", outline: "none" },
305
+ button: { padding: "6px 16px", borderRadius: "8px", border: "none", fontSize: "13px", fontWeight: "500", cursor: "pointer", backgroundColor: "#4f46e5", color: "#ffffff" },
306
+ buttonSecondary: { padding: "6px 16px", borderRadius: "8px", border: "1px solid #d1d5db", fontSize: "13px", fontWeight: "500", cursor: "pointer", backgroundColor: "#ffffff", color: "#374151" },
307
+ pairedBanner: { display: "flex", alignItems: "center", gap: "12px", border: "1px solid #bbf7d0", backgroundColor: "#f0fdf4", borderRadius: "8px", padding: "16px", marginBottom: "16px" },
308
+ pairedIcon: { width: "40px", height: "40px", borderRadius: "50%", backgroundColor: "#dcfce7", display: "flex", alignItems: "center", justifyContent: "center", color: "#16a34a", fontSize: "18px", flexShrink: "0" },
309
+ unpairedBanner: { display: "flex", alignItems: "center", gap: "12px", border: "1px solid #fde68a", backgroundColor: "#fefce8", borderRadius: "8px", padding: "16px", marginBottom: "16px" },
310
+ qrContainer: { textAlign: "center", padding: "16px" },
311
+ qrFrame: { display: "inline-block", border: "2px dashed #a5b4fc", borderRadius: "12px", padding: "16px", backgroundColor: "#ffffff", marginBottom: "12px" },
312
+ spinner: { display: "inline-block", width: "12px", height: "12px", border: "2px solid #d1d5db", borderTopColor: "#4f46e5", borderRadius: "50%", animation: "smstunnel-spin 0.8s linear infinite", marginRight: "8px", verticalAlign: "middle" },
313
+ error: { color: "#dc2626", fontSize: "13px", marginBottom: "12px" },
314
+ success: { color: "#16a34a", fontSize: "12px", marginTop: "8px" },
315
+ errorSmall: { color: "#dc2626", fontSize: "12px", marginTop: "8px" }
316
+ };
317
+ var stylesInjected = false;
318
+ function injectKeyframes() {
319
+ if (stylesInjected || typeof document === "undefined") return;
320
+ const style = document.createElement("style");
321
+ style.textContent = "@keyframes smstunnel-spin { to { transform: rotate(360deg); } }";
322
+ document.head.appendChild(style);
323
+ stylesInjected = true;
324
+ }
325
+ var SmsTunnelPairing = (0, import_vue3.defineComponent)({
326
+ name: "SmsTunnelPairing",
327
+ props: {
328
+ apiBaseUrl: { type: String, required: true },
329
+ getAuthHeaders: { type: Function, default: void 0 },
330
+ labels: { type: Object, default: () => EN_LABELS },
331
+ showTestSms: { type: Boolean, default: true },
332
+ showServerUrlInput: { type: Boolean, default: true },
333
+ qrSize: { type: Number, default: 220 },
334
+ routePrefix: { type: String, default: "smstunnel" }
335
+ },
336
+ emits: ["paired", "unpaired"],
337
+ setup(props, { emit }) {
338
+ injectKeyframes();
339
+ const tunnel = useSmsTunnel({
340
+ apiBaseUrl: props.apiBaseUrl,
341
+ getAuthHeaders: props.getAuthHeaders,
342
+ routePrefix: props.routePrefix
343
+ });
344
+ const localServerUrl = (0, import_vue3.ref)("");
345
+ const savingUrl = (0, import_vue3.ref)(false);
346
+ const testPhone = (0, import_vue3.ref)("");
347
+ const testMsg = (0, import_vue3.ref)("Test SMS");
348
+ const testResult = (0, import_vue3.ref)(null);
349
+ const sendingTest = (0, import_vue3.ref)(false);
350
+ (0, import_vue3.watch)(tunnel.serverUrl, (val) => {
351
+ if (val && !localServerUrl.value) localServerUrl.value = val;
352
+ }, { immediate: true });
353
+ (0, import_vue3.watch)(tunnel.status, (val, old) => {
354
+ if (val === "paired" && old !== "paired") {
355
+ emit("paired", { deviceName: tunnel.deviceName.value });
356
+ }
357
+ });
358
+ async function saveUrl() {
359
+ if (!localServerUrl.value.trim()) return;
360
+ savingUrl.value = true;
361
+ await tunnel.updateServerUrl(localServerUrl.value.trim().replace(/\/$/, ""));
362
+ savingUrl.value = false;
363
+ }
364
+ async function handleUnpair() {
365
+ if (!confirm(props.labels.unpairConfirm)) return;
366
+ await tunnel.unpair();
367
+ emit("unpaired");
368
+ }
369
+ async function handleTestSms() {
370
+ if (!testPhone.value.trim()) return;
371
+ sendingTest.value = true;
372
+ testResult.value = null;
373
+ testResult.value = await tunnel.sendTestSms(testPhone.value.trim(), testMsg.value);
374
+ sendingTest.value = false;
375
+ }
376
+ return () => {
377
+ const L = props.labels;
378
+ if (tunnel.status.value === "loading") {
379
+ return (0, import_vue3.h)("div", { style: s.container }, [
380
+ (0, import_vue3.h)("div", { style: s.card }, [
381
+ (0, import_vue3.h)("div", { style: { textAlign: "center", padding: "24px" } }, [
382
+ (0, import_vue3.h)("span", { style: s.spinner })
383
+ ])
384
+ ])
385
+ ]);
386
+ }
387
+ const children = [];
388
+ children.push((0, import_vue3.h)("div", { style: s.title }, L.title));
389
+ children.push((0, import_vue3.h)("div", { style: s.subtitle }, L.description));
390
+ if (props.showServerUrlInput) {
391
+ children.push(
392
+ (0, import_vue3.h)("div", { style: s.section }, [
393
+ (0, import_vue3.h)("label", { style: s.label }, L.serverUrlLabel),
394
+ (0, import_vue3.h)("div", { style: s.inputRow }, [
395
+ (0, import_vue3.h)("input", {
396
+ value: localServerUrl.value,
397
+ onInput: (e) => {
398
+ localServerUrl.value = e.target.value;
399
+ },
400
+ placeholder: L.serverUrlPlaceholder,
401
+ style: s.input
402
+ }),
403
+ (0, import_vue3.h)("button", {
404
+ style: { ...s.button, opacity: savingUrl.value || !localServerUrl.value.trim() ? "0.5" : "1" },
405
+ onClick: saveUrl,
406
+ disabled: savingUrl.value || !localServerUrl.value.trim()
407
+ }, L.saveButton)
408
+ ])
409
+ ])
410
+ );
411
+ }
412
+ if (tunnel.error.value) {
413
+ children.push((0, import_vue3.h)("div", { style: s.error }, tunnel.error.value));
414
+ }
415
+ if (tunnel.status.value === "paired") {
416
+ children.push(
417
+ (0, import_vue3.h)("div", { style: s.pairedBanner }, [
418
+ (0, import_vue3.h)("div", { style: s.pairedIcon }, "\u2713"),
419
+ (0, import_vue3.h)("div", { style: { flex: "1" } }, [
420
+ (0, import_vue3.h)("div", { style: { fontSize: "14px", fontWeight: "600", color: "#166534" } }, L.pairedStatus),
421
+ (0, import_vue3.h)(
422
+ "div",
423
+ { style: { fontSize: "12px", color: "#16a34a" } },
424
+ `${L.deviceLabel}: ${tunnel.deviceName.value || "Android Phone"}`
425
+ )
426
+ ]),
427
+ (0, import_vue3.h)("button", {
428
+ onClick: handleUnpair,
429
+ style: { padding: "6px", borderRadius: "8px", border: "none", cursor: "pointer", backgroundColor: "transparent", color: "#9ca3af" },
430
+ title: L.unpairButton
431
+ }, "\u2715")
432
+ ])
433
+ );
434
+ if (props.showTestSms) {
435
+ const testChildren = [
436
+ (0, import_vue3.h)("div", { style: { fontSize: "13px", fontWeight: "600", color: "#374151", marginBottom: "12px" } }, L.testSmsTitle),
437
+ (0, import_vue3.h)("div", { style: s.inputRow }, [
438
+ (0, import_vue3.h)("input", {
439
+ value: testPhone.value,
440
+ onInput: (e) => {
441
+ testPhone.value = e.target.value;
442
+ },
443
+ placeholder: L.testPhonePlaceholder,
444
+ style: s.input
445
+ }),
446
+ (0, import_vue3.h)("input", {
447
+ value: testMsg.value,
448
+ onInput: (e) => {
449
+ testMsg.value = e.target.value;
450
+ },
451
+ placeholder: L.testMessagePlaceholder,
452
+ style: s.input
453
+ }),
454
+ (0, import_vue3.h)("button", {
455
+ style: { ...s.button, opacity: sendingTest.value || !testPhone.value.trim() ? "0.5" : "1" },
456
+ onClick: handleTestSms,
457
+ disabled: sendingTest.value || !testPhone.value.trim()
458
+ }, sendingTest.value ? (0, import_vue3.h)("span", { style: s.spinner }) : L.sendTestButton)
459
+ ])
460
+ ];
461
+ if (testResult.value) {
462
+ testChildren.push(
463
+ (0, import_vue3.h)(
464
+ "div",
465
+ { style: testResult.value.success ? s.success : s.errorSmall },
466
+ testResult.value.success ? `${L.smsSentSuccess} (ID: ${testResult.value.messageId})` : `${L.smsError}: ${testResult.value.error}`
467
+ )
468
+ );
469
+ }
470
+ children.push((0, import_vue3.h)("div", { style: s.section }, testChildren));
471
+ }
472
+ } else {
473
+ children.push(
474
+ (0, import_vue3.h)("div", { style: s.unpairedBanner }, [
475
+ (0, import_vue3.h)("div", { style: { fontSize: "24px" } }, "\u{1F4F1}"),
476
+ (0, import_vue3.h)("div", {}, [
477
+ (0, import_vue3.h)("div", { style: { fontSize: "14px", fontWeight: "600", color: "#854d0e" } }, L.notPairedStatus),
478
+ (0, import_vue3.h)("div", { style: { fontSize: "12px", color: "#a16207" } }, L.notPairedDescription)
479
+ ])
480
+ ])
481
+ );
482
+ if (!tunnel.showQr.value) {
483
+ children.push(
484
+ (0, import_vue3.h)("button", {
485
+ style: { ...s.button, opacity: tunnel.generating.value ? "0.5" : "1" },
486
+ onClick: tunnel.generateQr,
487
+ disabled: tunnel.generating.value
488
+ }, [
489
+ tunnel.generating.value ? (0, import_vue3.h)("span", { style: s.spinner }) : null,
490
+ L.connectButton
491
+ ])
492
+ );
493
+ } else {
494
+ children.push(
495
+ (0, import_vue3.h)("div", { style: s.qrContainer }, [
496
+ (0, import_vue3.h)("div", { style: { fontSize: "13px", color: "#4b5563", marginBottom: "12px" } }, L.scanQrPrompt),
497
+ (0, import_vue3.h)("div", { style: s.qrFrame }, [
498
+ (0, import_vue3.h)(QrCodeCanvas, { value: tunnel.qrData.value, size: props.qrSize })
499
+ ]),
500
+ (0, import_vue3.h)("div", {
501
+ style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "8px", fontSize: "12px", color: "#9ca3af", marginBottom: "12px" }
502
+ }, [
503
+ (0, import_vue3.h)("span", { style: s.spinner }),
504
+ L.waitingForPairing
505
+ ]),
506
+ (0, import_vue3.h)("button", { style: s.buttonSecondary, onClick: tunnel.cancelPairing }, L.cancelButton)
507
+ ])
508
+ );
509
+ }
510
+ }
511
+ return (0, import_vue3.h)("div", { style: s.container }, [
512
+ (0, import_vue3.h)("div", { style: s.card }, children)
513
+ ]);
514
+ };
515
+ }
516
+ });
517
+ // Annotate the CommonJS export names for ESM import in node:
518
+ 0 && (module.exports = {
519
+ EN_LABELS,
520
+ QrCodeCanvas,
521
+ RO_LABELS,
522
+ SmsTunnelPairing,
523
+ useSmsTunnel
524
+ });
525
+ //# sourceMappingURL=index.js.map