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