@narcisbodea/smstunnel-sdk 1.1.9 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -27,6 +27,7 @@ interface SendSmsResult {
27
27
  interface CreateTokenResult {
28
28
  success: boolean;
29
29
  token?: string;
30
+ pairingCode?: string;
30
31
  qrData?: string;
31
32
  expiresAt?: string;
32
33
  pollUrl?: string;
@@ -69,6 +70,8 @@ interface SmsTunnelLabels {
69
70
  sendTestButton: string;
70
71
  smsSentSuccess: string;
71
72
  smsError: string;
73
+ pairingCodeLabel: string;
74
+ pairingCodeCopied: string;
72
75
  }
73
76
  declare const EN_LABELS: SmsTunnelLabels;
74
77
  declare const RO_LABELS: SmsTunnelLabels;
@@ -95,6 +98,7 @@ interface SmsTunnelStatusResponse {
95
98
  interface CreateTokenResponse {
96
99
  success: boolean;
97
100
  token?: string;
101
+ pairingCode?: string;
98
102
  qrData?: string;
99
103
  expiresAt?: string;
100
104
  pollUrl?: string;
package/dist/index.d.ts CHANGED
@@ -27,6 +27,7 @@ interface SendSmsResult {
27
27
  interface CreateTokenResult {
28
28
  success: boolean;
29
29
  token?: string;
30
+ pairingCode?: string;
30
31
  qrData?: string;
31
32
  expiresAt?: string;
32
33
  pollUrl?: string;
@@ -69,6 +70,8 @@ interface SmsTunnelLabels {
69
70
  sendTestButton: string;
70
71
  smsSentSuccess: string;
71
72
  smsError: string;
73
+ pairingCodeLabel: string;
74
+ pairingCodeCopied: string;
72
75
  }
73
76
  declare const EN_LABELS: SmsTunnelLabels;
74
77
  declare const RO_LABELS: SmsTunnelLabels;
@@ -95,6 +98,7 @@ interface SmsTunnelStatusResponse {
95
98
  interface CreateTokenResponse {
96
99
  success: boolean;
97
100
  token?: string;
101
+ pairingCode?: string;
98
102
  qrData?: string;
99
103
  expiresAt?: string;
100
104
  pollUrl?: string;
package/dist/index.js CHANGED
@@ -49,7 +49,9 @@ var EN_LABELS = {
49
49
  testMessagePlaceholder: "Message",
50
50
  sendTestButton: "Send",
51
51
  smsSentSuccess: "SMS sent successfully!",
52
- smsError: "Error"
52
+ smsError: "Error",
53
+ pairingCodeLabel: "Or enter this code in the app:",
54
+ pairingCodeCopied: "Code copied!"
53
55
  };
54
56
  var RO_LABELS = {
55
57
  title: "Configurare SMS (SMSTunnel)",
@@ -73,7 +75,9 @@ var RO_LABELS = {
73
75
  testMessagePlaceholder: "Mesaj",
74
76
  sendTestButton: "Trimite",
75
77
  smsSentSuccess: "SMS trimis cu succes!",
76
- smsError: "Eroare"
78
+ smsError: "Eroare",
79
+ pairingCodeLabel: "Sau introdu acest cod in aplicatie:",
80
+ pairingCodeCopied: "Cod copiat!"
77
81
  };
78
82
 
79
83
  // src/client.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/shared/labels.ts","../src/client.ts"],"sourcesContent":["// Common types shared between server and all frontends\r\nexport type {\r\n SmsTunnelConfig,\r\n SmsTunnelStorageAdapter,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n} from './server/smstunnel.interfaces';\r\n\r\n// Shared labels (i18n)\r\nexport type { SmsTunnelLabels } from './shared/labels';\r\nexport { EN_LABELS, RO_LABELS } from './shared/labels';\r\n\r\n// Framework-agnostic client (Angular, Svelte, Astro, Solid, Vanilla JS)\r\nexport { SmsTunnelClient } from './client';\r\nexport type {\r\n SmsTunnelClientOptions,\r\n SmsTunnelStatusResponse,\r\n CreateTokenResponse,\r\n PairingStatusResponse,\r\n SendSmsResponse,\r\n SmsStatusResponse,\r\n SendBulkSmsRequest,\r\n SendBulkSmsResponse,\r\n Send2faRequest,\r\n ReceivedSmsResponse,\r\n DevicesResponse,\r\n AccountUsageResponse,\r\n PairingInfo,\r\n PairingEventCallback,\r\n DeviceE2EStatusResponse,\r\n DevicePublicKeyResponse,\r\n VerifyDeviceKeyResponse,\r\n} from './client';\r\n","export interface SmsTunnelLabels {\r\n title: string;\r\n description: string;\r\n serverUrlLabel: string;\r\n serverUrlPlaceholder: string;\r\n saveButton: string;\r\n pairedStatus: string;\r\n deviceLabel: string;\r\n unpairButton: string;\r\n unpairConfirm: string;\r\n notPairedStatus: string;\r\n notPairedDescription: string;\r\n connectButton: string;\r\n scanQrPrompt: string;\r\n waitingForPairing: string;\r\n cancelButton: string;\r\n qrExpired: string;\r\n testSmsTitle: string;\r\n testPhonePlaceholder: string;\r\n testMessagePlaceholder: string;\r\n sendTestButton: string;\r\n smsSentSuccess: string;\r\n smsError: string;\r\n}\r\n\r\nexport const EN_LABELS: SmsTunnelLabels = {\r\n title: 'SMS Configuration (SMSTunnel)',\r\n description: 'Connect an Android phone with the SMSTunnel app to send SMS directly from the application.',\r\n serverUrlLabel: 'SMSTunnel Server',\r\n serverUrlPlaceholder: 'https://smstunnel.io',\r\n saveButton: 'Save',\r\n pairedStatus: 'Connected',\r\n deviceLabel: 'Device',\r\n unpairButton: 'Disconnect',\r\n unpairConfirm: 'Are you sure you want to disconnect the SMS device?',\r\n notPairedStatus: 'Not connected',\r\n notPairedDescription: 'Scan the QR code with the SMSTunnel app on your Android phone.',\r\n connectButton: 'Connect phone',\r\n scanQrPrompt: 'Scan this QR code with the SMSTunnel app on your phone:',\r\n waitingForPairing: 'Waiting for connection...',\r\n cancelButton: 'Cancel',\r\n qrExpired: 'QR code has expired. Generate a new one.',\r\n testSmsTitle: 'Send test SMS',\r\n testPhonePlaceholder: 'Phone number (e.g., +1234567890)',\r\n testMessagePlaceholder: 'Message',\r\n sendTestButton: 'Send',\r\n smsSentSuccess: 'SMS sent successfully!',\r\n smsError: 'Error',\r\n};\r\n\r\nexport const RO_LABELS: SmsTunnelLabels = {\r\n title: 'Configurare SMS (SMSTunnel)',\r\n description: 'Conecteaza un telefon Android cu aplicatia SMSTunnel pentru a trimite SMS-uri direct din aplicatie.',\r\n serverUrlLabel: 'Server SMSTunnel',\r\n serverUrlPlaceholder: 'https://smstunnel.io',\r\n saveButton: 'Salveaza',\r\n pairedStatus: 'Conectat',\r\n deviceLabel: 'Dispozitiv',\r\n unpairButton: 'Deconecteaza',\r\n unpairConfirm: 'Sigur doresti sa deconectezi dispozitivul SMS?',\r\n notPairedStatus: 'Neconectat',\r\n notPairedDescription: 'Scaneaza codul QR cu aplicatia SMSTunnel de pe telefonul Android.',\r\n connectButton: 'Conecteaza telefon',\r\n scanQrPrompt: 'Scaneaza acest cod QR cu aplicatia SMSTunnel de pe telefon:',\r\n waitingForPairing: 'Se asteapta conectarea...',\r\n cancelButton: 'Anuleaza',\r\n qrExpired: 'Codul QR a expirat. Genereaza unul nou.',\r\n testSmsTitle: 'Trimite SMS de test',\r\n testPhonePlaceholder: 'Nr. telefon (ex: 0741234567)',\r\n testMessagePlaceholder: 'Mesaj',\r\n sendTestButton: 'Trimite',\r\n smsSentSuccess: 'SMS trimis cu succes!',\r\n smsError: 'Eroare',\r\n};\r\n","/**\r\n * Framework-agnostic SMSTunnel client.\r\n *\r\n * Works with Angular, Svelte, Astro, Solid, Vanilla JS, or any framework.\r\n * For React use `smstunnel-sdk/react`, for Vue use `smstunnel-sdk/vue`.\r\n */\r\nexport interface SmsTunnelClientOptions {\r\n apiBaseUrl: string;\r\n /** Return the current JWT/auth token. SDK adds Authorization: Bearer header automatically. */\r\n getToken?: () => string | null;\r\n /** Full control over auth headers. Takes precedence over getToken. */\r\n getAuthHeaders?: () => Record<string, string>;\r\n pollInterval?: number;\r\n}\r\n\r\n// --- Status ---\r\n\r\nexport interface SmsTunnelStatusResponse {\r\n paired: boolean;\r\n serverUrl?: string;\r\n deviceName?: string;\r\n}\r\n\r\n// --- Pairing ---\r\n\r\nexport interface CreateTokenResponse {\r\n success: boolean;\r\n token?: string;\r\n qrData?: string;\r\n expiresAt?: string;\r\n pollUrl?: string;\r\n error?: string;\r\n}\r\n\r\nexport interface PairingStatusResponse {\r\n status: 'pending' | 'completed' | 'expired' | 'error';\r\n source?: string;\r\n displayName?: string;\r\n pairedDeviceId?: string;\r\n pairingId?: string;\r\n error?: string;\r\n}\r\n\r\n// --- SMS ---\r\n\r\nexport interface SendSmsResponse {\r\n success: boolean;\r\n messageId?: string;\r\n queued?: boolean;\r\n data?: {\r\n messageId: string;\r\n branded: boolean;\r\n queued: boolean;\r\n remaining: number;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface SmsStatusResponse {\r\n success: boolean;\r\n data?: {\r\n messageId: string;\r\n status: 'sent' | 'pending' | 'failed' | 'delivered';\r\n recipient: string;\r\n sentAt?: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface SendBulkSmsRequest {\r\n messages: Array<{ to: string; message: string }>;\r\n}\r\n\r\nexport interface SendBulkSmsResponse {\r\n success: boolean;\r\n results?: Array<{ to: string; messageId?: string; error?: string }>;\r\n error?: string;\r\n}\r\n\r\nexport interface Send2faRequest {\r\n to: string;\r\n code: string;\r\n template?: string;\r\n}\r\n\r\nexport interface ReceivedSmsResponse {\r\n success: boolean;\r\n data?: Array<{\r\n from: string;\r\n message: string;\r\n receivedAt: string;\r\n }>;\r\n error?: string;\r\n}\r\n\r\n// --- Devices ---\r\n\r\nexport interface DevicesResponse {\r\n success: boolean;\r\n data?: Array<{\r\n id: string;\r\n clientId: string;\r\n name: string;\r\n brand?: string;\r\n model?: string;\r\n connected?: boolean;\r\n }>;\r\n error?: string;\r\n}\r\n\r\n// --- Account ---\r\n\r\nexport interface AccountUsageResponse {\r\n success: boolean;\r\n data?: {\r\n messagesSent: number;\r\n messagesReceived: number;\r\n messagesSentToday: number;\r\n messagesSentMonth: number;\r\n dailyLimit?: number;\r\n monthlyLimit?: number;\r\n };\r\n error?: string;\r\n}\r\n\r\n// --- E2E Encryption ---\r\n\r\nexport interface DeviceE2EStatusResponse {\r\n success: boolean;\r\n data?: {\r\n encryptionEnabled: boolean;\r\n hasPublicKey: boolean;\r\n publicKeyFingerprint?: string;\r\n keyCreatedAt?: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface DevicePublicKeyResponse {\r\n success: boolean;\r\n data?: {\r\n deviceId: string;\r\n deviceName: string;\r\n publicKey: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface VerifyDeviceKeyResponse {\r\n valid: boolean;\r\n currentFingerprint?: string;\r\n needsRePairing: boolean;\r\n}\r\n\r\n// --- Pairings list ---\r\n\r\nexport interface PairingInfo {\r\n _id: string;\r\n deviceId: string;\r\n type: string;\r\n name: string;\r\n status: 'active' | 'paused' | 'revoked';\r\n messagesSent: number;\r\n createdAt: string;\r\n source: string;\r\n}\r\n\r\n// --- Callback ---\r\n\r\nexport type PairingEventCallback = (event: 'completed' | 'expired' | 'error', data?: any) => void;\r\n\r\nexport class SmsTunnelClient {\r\n private readonly baseUrl: string;\r\n private readonly prefix = 'smstunnel';\r\n private readonly pollInterval: number;\r\n private readonly getAuthHeaders: () => Record<string, string>;\r\n private pollTimer: ReturnType<typeof setInterval> | null = null;\r\n\r\n constructor(options: SmsTunnelClientOptions) {\r\n this.baseUrl = options.apiBaseUrl.replace(/\\/$/, '');\r\n this.pollInterval = options.pollInterval || 3000;\r\n\r\n if (options.getAuthHeaders) {\r\n this.getAuthHeaders = options.getAuthHeaders;\r\n } else if (options.getToken) {\r\n const getToken = options.getToken;\r\n this.getAuthHeaders = (): Record<string, string> => {\r\n const token = getToken();\r\n if (token) return { Authorization: `Bearer ${token}` };\r\n return {};\r\n };\r\n } else {\r\n this.getAuthHeaders = () => ({});\r\n }\r\n }\r\n\r\n // ─── Pairing ───────────────────────────────────────\r\n\r\n /** Get current pairing status */\r\n async getStatus(): Promise<SmsTunnelStatusResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/status`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Create a pairing token (generates QR data) */\r\n async createToken(): Promise<CreateTokenResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/create-token`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Check pairing status for a token (no auth needed) */\r\n async getPairingStatus(token: string): Promise<PairingStatusResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/pairing-status/${token}`,\r\n );\r\n return res.json();\r\n }\r\n\r\n /**\r\n * Start polling for pairing completion.\r\n * Returns a cleanup function to stop polling.\r\n */\r\n startPolling(token: string, callback: PairingEventCallback): () => void {\r\n this.stopPolling();\r\n\r\n this.pollTimer = setInterval(async () => {\r\n try {\r\n const data = await this.getPairingStatus(token);\r\n if (data.status === 'completed') {\r\n this.stopPolling();\r\n callback('completed', data);\r\n } else if (data.status === 'expired') {\r\n this.stopPolling();\r\n callback('expired', data);\r\n }\r\n } catch (err) {\r\n // ignore polling errors\r\n }\r\n }, this.pollInterval);\r\n\r\n return () => this.stopPolling();\r\n }\r\n\r\n /** Stop polling */\r\n stopPolling(): void {\r\n if (this.pollTimer) {\r\n clearInterval(this.pollTimer);\r\n this.pollTimer = null;\r\n }\r\n }\r\n\r\n /** Unpair the connected device */\r\n async unpair(): Promise<{ success: boolean }> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/unpair`, {\r\n method: 'POST',\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** List active pairings (proxied through backend) */\r\n async getPairings(): Promise<PairingInfo[]> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/pairings`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── SMS ───────────────────────────────────────────\r\n\r\n /** Send a single SMS */\r\n async sendSms(to: string, message: string): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send a 2FA SMS */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-2fa`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ to, code, template }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send bulk SMS */\r\n async sendBulk(messages: Array<{ to: string; message: string }>): Promise<SendBulkSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-bulk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ messages }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get SMS delivery status */\r\n async getSmsStatus(messageId: string): Promise<SmsStatusResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/sms-status/${messageId}`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get received SMS (inbox) */\r\n async getReceivedSms(): Promise<ReceivedSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/received`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /** List paired devices */\r\n async getDevices(): Promise<DevicesResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/devices`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get account usage stats */\r\n async getUsage(): Promise<AccountUsageResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/usage`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /** Get E2E encryption status for a device */\r\n async getDeviceE2EStatus(deviceId: string): Promise<DeviceE2EStatusResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/e2e-status/${deviceId}`,\r\n { headers: this.getAuthHeaders() },\r\n );\r\n return res.json();\r\n }\r\n\r\n /** Get device public key for E2E encryption */\r\n async getDevicePublicKey(deviceId: string): Promise<DevicePublicKeyResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/public-key/${deviceId}`,\r\n { headers: this.getAuthHeaders() },\r\n );\r\n return res.json();\r\n }\r\n\r\n /** Verify that a stored fingerprint matches the current device key */\r\n async verifyDeviceKey(\r\n deviceId: string,\r\n fingerprint: string,\r\n ): Promise<VerifyDeviceKeyResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/verify-key`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ deviceId, fingerprint }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send an encrypted SMS (payload already encrypted with device public key) */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-encrypted`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ encryptedPayload, deviceId, is2FA }),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── Config ────────────────────────────────────────\r\n\r\n /** Update server URL configuration */\r\n async updateServerUrl(serverUrl: string): Promise<{ success: boolean }> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/update-config`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ serverUrl }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Destroy - cleanup polling */\r\n destroy(): void {\r\n this.stopPolling();\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/shared/labels.ts","../src/client.ts"],"sourcesContent":["// Common types shared between server and all frontends\r\nexport type {\r\n SmsTunnelConfig,\r\n SmsTunnelStorageAdapter,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n} from './server/smstunnel.interfaces';\r\n\r\n// Shared labels (i18n)\r\nexport type { SmsTunnelLabels } from './shared/labels';\r\nexport { EN_LABELS, RO_LABELS } from './shared/labels';\r\n\r\n// Framework-agnostic client (Angular, Svelte, Astro, Solid, Vanilla JS)\r\nexport { SmsTunnelClient } from './client';\r\nexport type {\r\n SmsTunnelClientOptions,\r\n SmsTunnelStatusResponse,\r\n CreateTokenResponse,\r\n PairingStatusResponse,\r\n SendSmsResponse,\r\n SmsStatusResponse,\r\n SendBulkSmsRequest,\r\n SendBulkSmsResponse,\r\n Send2faRequest,\r\n ReceivedSmsResponse,\r\n DevicesResponse,\r\n AccountUsageResponse,\r\n PairingInfo,\r\n PairingEventCallback,\r\n DeviceE2EStatusResponse,\r\n DevicePublicKeyResponse,\r\n VerifyDeviceKeyResponse,\r\n} from './client';\r\n","export interface SmsTunnelLabels {\r\n title: string;\r\n description: string;\r\n serverUrlLabel: string;\r\n serverUrlPlaceholder: string;\r\n saveButton: string;\r\n pairedStatus: string;\r\n deviceLabel: string;\r\n unpairButton: string;\r\n unpairConfirm: string;\r\n notPairedStatus: string;\r\n notPairedDescription: string;\r\n connectButton: string;\r\n scanQrPrompt: string;\r\n waitingForPairing: string;\r\n cancelButton: string;\r\n qrExpired: string;\r\n testSmsTitle: string;\r\n testPhonePlaceholder: string;\r\n testMessagePlaceholder: string;\r\n sendTestButton: string;\r\n smsSentSuccess: string;\r\n smsError: string;\r\n pairingCodeLabel: string;\r\n pairingCodeCopied: string;\r\n}\r\n\r\nexport const EN_LABELS: SmsTunnelLabels = {\r\n title: 'SMS Configuration (SMSTunnel)',\r\n description: 'Connect an Android phone with the SMSTunnel app to send SMS directly from the application.',\r\n serverUrlLabel: 'SMSTunnel Server',\r\n serverUrlPlaceholder: 'https://smstunnel.io',\r\n saveButton: 'Save',\r\n pairedStatus: 'Connected',\r\n deviceLabel: 'Device',\r\n unpairButton: 'Disconnect',\r\n unpairConfirm: 'Are you sure you want to disconnect the SMS device?',\r\n notPairedStatus: 'Not connected',\r\n notPairedDescription: 'Scan the QR code with the SMSTunnel app on your Android phone.',\r\n connectButton: 'Connect phone',\r\n scanQrPrompt: 'Scan this QR code with the SMSTunnel app on your phone:',\r\n waitingForPairing: 'Waiting for connection...',\r\n cancelButton: 'Cancel',\r\n qrExpired: 'QR code has expired. Generate a new one.',\r\n testSmsTitle: 'Send test SMS',\r\n testPhonePlaceholder: 'Phone number (e.g., +1234567890)',\r\n testMessagePlaceholder: 'Message',\r\n sendTestButton: 'Send',\r\n smsSentSuccess: 'SMS sent successfully!',\r\n smsError: 'Error',\r\n pairingCodeLabel: 'Or enter this code in the app:',\r\n pairingCodeCopied: 'Code copied!',\r\n};\r\n\r\nexport const RO_LABELS: SmsTunnelLabels = {\r\n title: 'Configurare SMS (SMSTunnel)',\r\n description: 'Conecteaza un telefon Android cu aplicatia SMSTunnel pentru a trimite SMS-uri direct din aplicatie.',\r\n serverUrlLabel: 'Server SMSTunnel',\r\n serverUrlPlaceholder: 'https://smstunnel.io',\r\n saveButton: 'Salveaza',\r\n pairedStatus: 'Conectat',\r\n deviceLabel: 'Dispozitiv',\r\n unpairButton: 'Deconecteaza',\r\n unpairConfirm: 'Sigur doresti sa deconectezi dispozitivul SMS?',\r\n notPairedStatus: 'Neconectat',\r\n notPairedDescription: 'Scaneaza codul QR cu aplicatia SMSTunnel de pe telefonul Android.',\r\n connectButton: 'Conecteaza telefon',\r\n scanQrPrompt: 'Scaneaza acest cod QR cu aplicatia SMSTunnel de pe telefon:',\r\n waitingForPairing: 'Se asteapta conectarea...',\r\n cancelButton: 'Anuleaza',\r\n qrExpired: 'Codul QR a expirat. Genereaza unul nou.',\r\n testSmsTitle: 'Trimite SMS de test',\r\n testPhonePlaceholder: 'Nr. telefon (ex: 0741234567)',\r\n testMessagePlaceholder: 'Mesaj',\r\n sendTestButton: 'Trimite',\r\n smsSentSuccess: 'SMS trimis cu succes!',\r\n smsError: 'Eroare',\r\n pairingCodeLabel: 'Sau introdu acest cod in aplicatie:',\r\n pairingCodeCopied: 'Cod copiat!',\r\n};\r\n","/**\r\n * Framework-agnostic SMSTunnel client.\r\n *\r\n * Works with Angular, Svelte, Astro, Solid, Vanilla JS, or any framework.\r\n * For React use `smstunnel-sdk/react`, for Vue use `smstunnel-sdk/vue`.\r\n */\r\nexport interface SmsTunnelClientOptions {\r\n apiBaseUrl: string;\r\n /** Return the current JWT/auth token. SDK adds Authorization: Bearer header automatically. */\r\n getToken?: () => string | null;\r\n /** Full control over auth headers. Takes precedence over getToken. */\r\n getAuthHeaders?: () => Record<string, string>;\r\n pollInterval?: number;\r\n}\r\n\r\n// --- Status ---\r\n\r\nexport interface SmsTunnelStatusResponse {\r\n paired: boolean;\r\n serverUrl?: string;\r\n deviceName?: string;\r\n}\r\n\r\n// --- Pairing ---\r\n\r\nexport interface CreateTokenResponse {\r\n success: boolean;\r\n token?: string;\r\n pairingCode?: string;\r\n qrData?: string;\r\n expiresAt?: string;\r\n pollUrl?: string;\r\n error?: string;\r\n}\r\n\r\nexport interface PairingStatusResponse {\r\n status: 'pending' | 'completed' | 'expired' | 'error';\r\n source?: string;\r\n displayName?: string;\r\n pairedDeviceId?: string;\r\n pairingId?: string;\r\n error?: string;\r\n}\r\n\r\n// --- SMS ---\r\n\r\nexport interface SendSmsResponse {\r\n success: boolean;\r\n messageId?: string;\r\n queued?: boolean;\r\n data?: {\r\n messageId: string;\r\n branded: boolean;\r\n queued: boolean;\r\n remaining: number;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface SmsStatusResponse {\r\n success: boolean;\r\n data?: {\r\n messageId: string;\r\n status: 'sent' | 'pending' | 'failed' | 'delivered';\r\n recipient: string;\r\n sentAt?: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface SendBulkSmsRequest {\r\n messages: Array<{ to: string; message: string }>;\r\n}\r\n\r\nexport interface SendBulkSmsResponse {\r\n success: boolean;\r\n results?: Array<{ to: string; messageId?: string; error?: string }>;\r\n error?: string;\r\n}\r\n\r\nexport interface Send2faRequest {\r\n to: string;\r\n code: string;\r\n template?: string;\r\n}\r\n\r\nexport interface ReceivedSmsResponse {\r\n success: boolean;\r\n data?: Array<{\r\n from: string;\r\n message: string;\r\n receivedAt: string;\r\n }>;\r\n error?: string;\r\n}\r\n\r\n// --- Devices ---\r\n\r\nexport interface DevicesResponse {\r\n success: boolean;\r\n data?: Array<{\r\n id: string;\r\n clientId: string;\r\n name: string;\r\n brand?: string;\r\n model?: string;\r\n connected?: boolean;\r\n }>;\r\n error?: string;\r\n}\r\n\r\n// --- Account ---\r\n\r\nexport interface AccountUsageResponse {\r\n success: boolean;\r\n data?: {\r\n messagesSent: number;\r\n messagesReceived: number;\r\n messagesSentToday: number;\r\n messagesSentMonth: number;\r\n dailyLimit?: number;\r\n monthlyLimit?: number;\r\n };\r\n error?: string;\r\n}\r\n\r\n// --- E2E Encryption ---\r\n\r\nexport interface DeviceE2EStatusResponse {\r\n success: boolean;\r\n data?: {\r\n encryptionEnabled: boolean;\r\n hasPublicKey: boolean;\r\n publicKeyFingerprint?: string;\r\n keyCreatedAt?: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface DevicePublicKeyResponse {\r\n success: boolean;\r\n data?: {\r\n deviceId: string;\r\n deviceName: string;\r\n publicKey: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface VerifyDeviceKeyResponse {\r\n valid: boolean;\r\n currentFingerprint?: string;\r\n needsRePairing: boolean;\r\n}\r\n\r\n// --- Pairings list ---\r\n\r\nexport interface PairingInfo {\r\n _id: string;\r\n deviceId: string;\r\n type: string;\r\n name: string;\r\n status: 'active' | 'paused' | 'revoked';\r\n messagesSent: number;\r\n createdAt: string;\r\n source: string;\r\n}\r\n\r\n// --- Callback ---\r\n\r\nexport type PairingEventCallback = (event: 'completed' | 'expired' | 'error', data?: any) => void;\r\n\r\nexport class SmsTunnelClient {\r\n private readonly baseUrl: string;\r\n private readonly prefix = 'smstunnel';\r\n private readonly pollInterval: number;\r\n private readonly getAuthHeaders: () => Record<string, string>;\r\n private pollTimer: ReturnType<typeof setInterval> | null = null;\r\n\r\n constructor(options: SmsTunnelClientOptions) {\r\n this.baseUrl = options.apiBaseUrl.replace(/\\/$/, '');\r\n this.pollInterval = options.pollInterval || 3000;\r\n\r\n if (options.getAuthHeaders) {\r\n this.getAuthHeaders = options.getAuthHeaders;\r\n } else if (options.getToken) {\r\n const getToken = options.getToken;\r\n this.getAuthHeaders = (): Record<string, string> => {\r\n const token = getToken();\r\n if (token) return { Authorization: `Bearer ${token}` };\r\n return {};\r\n };\r\n } else {\r\n this.getAuthHeaders = () => ({});\r\n }\r\n }\r\n\r\n // ─── Pairing ───────────────────────────────────────\r\n\r\n /** Get current pairing status */\r\n async getStatus(): Promise<SmsTunnelStatusResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/status`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Create a pairing token (generates QR data) */\r\n async createToken(): Promise<CreateTokenResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/create-token`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Check pairing status for a token (no auth needed) */\r\n async getPairingStatus(token: string): Promise<PairingStatusResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/pairing-status/${token}`,\r\n );\r\n return res.json();\r\n }\r\n\r\n /**\r\n * Start polling for pairing completion.\r\n * Returns a cleanup function to stop polling.\r\n */\r\n startPolling(token: string, callback: PairingEventCallback): () => void {\r\n this.stopPolling();\r\n\r\n this.pollTimer = setInterval(async () => {\r\n try {\r\n const data = await this.getPairingStatus(token);\r\n if (data.status === 'completed') {\r\n this.stopPolling();\r\n callback('completed', data);\r\n } else if (data.status === 'expired') {\r\n this.stopPolling();\r\n callback('expired', data);\r\n }\r\n } catch (err) {\r\n // ignore polling errors\r\n }\r\n }, this.pollInterval);\r\n\r\n return () => this.stopPolling();\r\n }\r\n\r\n /** Stop polling */\r\n stopPolling(): void {\r\n if (this.pollTimer) {\r\n clearInterval(this.pollTimer);\r\n this.pollTimer = null;\r\n }\r\n }\r\n\r\n /** Unpair the connected device */\r\n async unpair(): Promise<{ success: boolean }> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/unpair`, {\r\n method: 'POST',\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** List active pairings (proxied through backend) */\r\n async getPairings(): Promise<PairingInfo[]> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/pairings`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── SMS ───────────────────────────────────────────\r\n\r\n /** Send a single SMS */\r\n async sendSms(to: string, message: string): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send a 2FA SMS */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-2fa`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ to, code, template }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send bulk SMS */\r\n async sendBulk(messages: Array<{ to: string; message: string }>): Promise<SendBulkSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-bulk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ messages }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get SMS delivery status */\r\n async getSmsStatus(messageId: string): Promise<SmsStatusResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/sms-status/${messageId}`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get received SMS (inbox) */\r\n async getReceivedSms(): Promise<ReceivedSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/received`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /** List paired devices */\r\n async getDevices(): Promise<DevicesResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/devices`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get account usage stats */\r\n async getUsage(): Promise<AccountUsageResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/usage`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /** Get E2E encryption status for a device */\r\n async getDeviceE2EStatus(deviceId: string): Promise<DeviceE2EStatusResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/e2e-status/${deviceId}`,\r\n { headers: this.getAuthHeaders() },\r\n );\r\n return res.json();\r\n }\r\n\r\n /** Get device public key for E2E encryption */\r\n async getDevicePublicKey(deviceId: string): Promise<DevicePublicKeyResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/public-key/${deviceId}`,\r\n { headers: this.getAuthHeaders() },\r\n );\r\n return res.json();\r\n }\r\n\r\n /** Verify that a stored fingerprint matches the current device key */\r\n async verifyDeviceKey(\r\n deviceId: string,\r\n fingerprint: string,\r\n ): Promise<VerifyDeviceKeyResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/verify-key`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ deviceId, fingerprint }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send an encrypted SMS (payload already encrypted with device public key) */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-encrypted`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ encryptedPayload, deviceId, is2FA }),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── Config ────────────────────────────────────────\r\n\r\n /** Update server URL configuration */\r\n async updateServerUrl(serverUrl: string): Promise<{ success: boolean }> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/update-config`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ serverUrl }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Destroy - cleanup polling */\r\n destroy(): void {\r\n this.stopPolling();\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -21,7 +21,9 @@ var EN_LABELS = {
21
21
  testMessagePlaceholder: "Message",
22
22
  sendTestButton: "Send",
23
23
  smsSentSuccess: "SMS sent successfully!",
24
- smsError: "Error"
24
+ smsError: "Error",
25
+ pairingCodeLabel: "Or enter this code in the app:",
26
+ pairingCodeCopied: "Code copied!"
25
27
  };
26
28
  var RO_LABELS = {
27
29
  title: "Configurare SMS (SMSTunnel)",
@@ -45,7 +47,9 @@ var RO_LABELS = {
45
47
  testMessagePlaceholder: "Mesaj",
46
48
  sendTestButton: "Trimite",
47
49
  smsSentSuccess: "SMS trimis cu succes!",
48
- smsError: "Eroare"
50
+ smsError: "Eroare",
51
+ pairingCodeLabel: "Sau introdu acest cod in aplicatie:",
52
+ pairingCodeCopied: "Cod copiat!"
49
53
  };
50
54
 
51
55
  // src/client.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/labels.ts","../src/client.ts"],"sourcesContent":["export interface SmsTunnelLabels {\r\n title: string;\r\n description: string;\r\n serverUrlLabel: string;\r\n serverUrlPlaceholder: string;\r\n saveButton: string;\r\n pairedStatus: string;\r\n deviceLabel: string;\r\n unpairButton: string;\r\n unpairConfirm: string;\r\n notPairedStatus: string;\r\n notPairedDescription: string;\r\n connectButton: string;\r\n scanQrPrompt: string;\r\n waitingForPairing: string;\r\n cancelButton: string;\r\n qrExpired: string;\r\n testSmsTitle: string;\r\n testPhonePlaceholder: string;\r\n testMessagePlaceholder: string;\r\n sendTestButton: string;\r\n smsSentSuccess: string;\r\n smsError: string;\r\n}\r\n\r\nexport const EN_LABELS: SmsTunnelLabels = {\r\n title: 'SMS Configuration (SMSTunnel)',\r\n description: 'Connect an Android phone with the SMSTunnel app to send SMS directly from the application.',\r\n serverUrlLabel: 'SMSTunnel Server',\r\n serverUrlPlaceholder: 'https://smstunnel.io',\r\n saveButton: 'Save',\r\n pairedStatus: 'Connected',\r\n deviceLabel: 'Device',\r\n unpairButton: 'Disconnect',\r\n unpairConfirm: 'Are you sure you want to disconnect the SMS device?',\r\n notPairedStatus: 'Not connected',\r\n notPairedDescription: 'Scan the QR code with the SMSTunnel app on your Android phone.',\r\n connectButton: 'Connect phone',\r\n scanQrPrompt: 'Scan this QR code with the SMSTunnel app on your phone:',\r\n waitingForPairing: 'Waiting for connection...',\r\n cancelButton: 'Cancel',\r\n qrExpired: 'QR code has expired. Generate a new one.',\r\n testSmsTitle: 'Send test SMS',\r\n testPhonePlaceholder: 'Phone number (e.g., +1234567890)',\r\n testMessagePlaceholder: 'Message',\r\n sendTestButton: 'Send',\r\n smsSentSuccess: 'SMS sent successfully!',\r\n smsError: 'Error',\r\n};\r\n\r\nexport const RO_LABELS: SmsTunnelLabels = {\r\n title: 'Configurare SMS (SMSTunnel)',\r\n description: 'Conecteaza un telefon Android cu aplicatia SMSTunnel pentru a trimite SMS-uri direct din aplicatie.',\r\n serverUrlLabel: 'Server SMSTunnel',\r\n serverUrlPlaceholder: 'https://smstunnel.io',\r\n saveButton: 'Salveaza',\r\n pairedStatus: 'Conectat',\r\n deviceLabel: 'Dispozitiv',\r\n unpairButton: 'Deconecteaza',\r\n unpairConfirm: 'Sigur doresti sa deconectezi dispozitivul SMS?',\r\n notPairedStatus: 'Neconectat',\r\n notPairedDescription: 'Scaneaza codul QR cu aplicatia SMSTunnel de pe telefonul Android.',\r\n connectButton: 'Conecteaza telefon',\r\n scanQrPrompt: 'Scaneaza acest cod QR cu aplicatia SMSTunnel de pe telefon:',\r\n waitingForPairing: 'Se asteapta conectarea...',\r\n cancelButton: 'Anuleaza',\r\n qrExpired: 'Codul QR a expirat. Genereaza unul nou.',\r\n testSmsTitle: 'Trimite SMS de test',\r\n testPhonePlaceholder: 'Nr. telefon (ex: 0741234567)',\r\n testMessagePlaceholder: 'Mesaj',\r\n sendTestButton: 'Trimite',\r\n smsSentSuccess: 'SMS trimis cu succes!',\r\n smsError: 'Eroare',\r\n};\r\n","/**\r\n * Framework-agnostic SMSTunnel client.\r\n *\r\n * Works with Angular, Svelte, Astro, Solid, Vanilla JS, or any framework.\r\n * For React use `smstunnel-sdk/react`, for Vue use `smstunnel-sdk/vue`.\r\n */\r\nexport interface SmsTunnelClientOptions {\r\n apiBaseUrl: string;\r\n /** Return the current JWT/auth token. SDK adds Authorization: Bearer header automatically. */\r\n getToken?: () => string | null;\r\n /** Full control over auth headers. Takes precedence over getToken. */\r\n getAuthHeaders?: () => Record<string, string>;\r\n pollInterval?: number;\r\n}\r\n\r\n// --- Status ---\r\n\r\nexport interface SmsTunnelStatusResponse {\r\n paired: boolean;\r\n serverUrl?: string;\r\n deviceName?: string;\r\n}\r\n\r\n// --- Pairing ---\r\n\r\nexport interface CreateTokenResponse {\r\n success: boolean;\r\n token?: string;\r\n qrData?: string;\r\n expiresAt?: string;\r\n pollUrl?: string;\r\n error?: string;\r\n}\r\n\r\nexport interface PairingStatusResponse {\r\n status: 'pending' | 'completed' | 'expired' | 'error';\r\n source?: string;\r\n displayName?: string;\r\n pairedDeviceId?: string;\r\n pairingId?: string;\r\n error?: string;\r\n}\r\n\r\n// --- SMS ---\r\n\r\nexport interface SendSmsResponse {\r\n success: boolean;\r\n messageId?: string;\r\n queued?: boolean;\r\n data?: {\r\n messageId: string;\r\n branded: boolean;\r\n queued: boolean;\r\n remaining: number;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface SmsStatusResponse {\r\n success: boolean;\r\n data?: {\r\n messageId: string;\r\n status: 'sent' | 'pending' | 'failed' | 'delivered';\r\n recipient: string;\r\n sentAt?: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface SendBulkSmsRequest {\r\n messages: Array<{ to: string; message: string }>;\r\n}\r\n\r\nexport interface SendBulkSmsResponse {\r\n success: boolean;\r\n results?: Array<{ to: string; messageId?: string; error?: string }>;\r\n error?: string;\r\n}\r\n\r\nexport interface Send2faRequest {\r\n to: string;\r\n code: string;\r\n template?: string;\r\n}\r\n\r\nexport interface ReceivedSmsResponse {\r\n success: boolean;\r\n data?: Array<{\r\n from: string;\r\n message: string;\r\n receivedAt: string;\r\n }>;\r\n error?: string;\r\n}\r\n\r\n// --- Devices ---\r\n\r\nexport interface DevicesResponse {\r\n success: boolean;\r\n data?: Array<{\r\n id: string;\r\n clientId: string;\r\n name: string;\r\n brand?: string;\r\n model?: string;\r\n connected?: boolean;\r\n }>;\r\n error?: string;\r\n}\r\n\r\n// --- Account ---\r\n\r\nexport interface AccountUsageResponse {\r\n success: boolean;\r\n data?: {\r\n messagesSent: number;\r\n messagesReceived: number;\r\n messagesSentToday: number;\r\n messagesSentMonth: number;\r\n dailyLimit?: number;\r\n monthlyLimit?: number;\r\n };\r\n error?: string;\r\n}\r\n\r\n// --- E2E Encryption ---\r\n\r\nexport interface DeviceE2EStatusResponse {\r\n success: boolean;\r\n data?: {\r\n encryptionEnabled: boolean;\r\n hasPublicKey: boolean;\r\n publicKeyFingerprint?: string;\r\n keyCreatedAt?: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface DevicePublicKeyResponse {\r\n success: boolean;\r\n data?: {\r\n deviceId: string;\r\n deviceName: string;\r\n publicKey: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface VerifyDeviceKeyResponse {\r\n valid: boolean;\r\n currentFingerprint?: string;\r\n needsRePairing: boolean;\r\n}\r\n\r\n// --- Pairings list ---\r\n\r\nexport interface PairingInfo {\r\n _id: string;\r\n deviceId: string;\r\n type: string;\r\n name: string;\r\n status: 'active' | 'paused' | 'revoked';\r\n messagesSent: number;\r\n createdAt: string;\r\n source: string;\r\n}\r\n\r\n// --- Callback ---\r\n\r\nexport type PairingEventCallback = (event: 'completed' | 'expired' | 'error', data?: any) => void;\r\n\r\nexport class SmsTunnelClient {\r\n private readonly baseUrl: string;\r\n private readonly prefix = 'smstunnel';\r\n private readonly pollInterval: number;\r\n private readonly getAuthHeaders: () => Record<string, string>;\r\n private pollTimer: ReturnType<typeof setInterval> | null = null;\r\n\r\n constructor(options: SmsTunnelClientOptions) {\r\n this.baseUrl = options.apiBaseUrl.replace(/\\/$/, '');\r\n this.pollInterval = options.pollInterval || 3000;\r\n\r\n if (options.getAuthHeaders) {\r\n this.getAuthHeaders = options.getAuthHeaders;\r\n } else if (options.getToken) {\r\n const getToken = options.getToken;\r\n this.getAuthHeaders = (): Record<string, string> => {\r\n const token = getToken();\r\n if (token) return { Authorization: `Bearer ${token}` };\r\n return {};\r\n };\r\n } else {\r\n this.getAuthHeaders = () => ({});\r\n }\r\n }\r\n\r\n // ─── Pairing ───────────────────────────────────────\r\n\r\n /** Get current pairing status */\r\n async getStatus(): Promise<SmsTunnelStatusResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/status`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Create a pairing token (generates QR data) */\r\n async createToken(): Promise<CreateTokenResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/create-token`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Check pairing status for a token (no auth needed) */\r\n async getPairingStatus(token: string): Promise<PairingStatusResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/pairing-status/${token}`,\r\n );\r\n return res.json();\r\n }\r\n\r\n /**\r\n * Start polling for pairing completion.\r\n * Returns a cleanup function to stop polling.\r\n */\r\n startPolling(token: string, callback: PairingEventCallback): () => void {\r\n this.stopPolling();\r\n\r\n this.pollTimer = setInterval(async () => {\r\n try {\r\n const data = await this.getPairingStatus(token);\r\n if (data.status === 'completed') {\r\n this.stopPolling();\r\n callback('completed', data);\r\n } else if (data.status === 'expired') {\r\n this.stopPolling();\r\n callback('expired', data);\r\n }\r\n } catch (err) {\r\n // ignore polling errors\r\n }\r\n }, this.pollInterval);\r\n\r\n return () => this.stopPolling();\r\n }\r\n\r\n /** Stop polling */\r\n stopPolling(): void {\r\n if (this.pollTimer) {\r\n clearInterval(this.pollTimer);\r\n this.pollTimer = null;\r\n }\r\n }\r\n\r\n /** Unpair the connected device */\r\n async unpair(): Promise<{ success: boolean }> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/unpair`, {\r\n method: 'POST',\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** List active pairings (proxied through backend) */\r\n async getPairings(): Promise<PairingInfo[]> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/pairings`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── SMS ───────────────────────────────────────────\r\n\r\n /** Send a single SMS */\r\n async sendSms(to: string, message: string): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send a 2FA SMS */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-2fa`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ to, code, template }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send bulk SMS */\r\n async sendBulk(messages: Array<{ to: string; message: string }>): Promise<SendBulkSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-bulk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ messages }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get SMS delivery status */\r\n async getSmsStatus(messageId: string): Promise<SmsStatusResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/sms-status/${messageId}`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get received SMS (inbox) */\r\n async getReceivedSms(): Promise<ReceivedSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/received`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /** List paired devices */\r\n async getDevices(): Promise<DevicesResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/devices`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get account usage stats */\r\n async getUsage(): Promise<AccountUsageResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/usage`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /** Get E2E encryption status for a device */\r\n async getDeviceE2EStatus(deviceId: string): Promise<DeviceE2EStatusResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/e2e-status/${deviceId}`,\r\n { headers: this.getAuthHeaders() },\r\n );\r\n return res.json();\r\n }\r\n\r\n /** Get device public key for E2E encryption */\r\n async getDevicePublicKey(deviceId: string): Promise<DevicePublicKeyResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/public-key/${deviceId}`,\r\n { headers: this.getAuthHeaders() },\r\n );\r\n return res.json();\r\n }\r\n\r\n /** Verify that a stored fingerprint matches the current device key */\r\n async verifyDeviceKey(\r\n deviceId: string,\r\n fingerprint: string,\r\n ): Promise<VerifyDeviceKeyResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/verify-key`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ deviceId, fingerprint }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send an encrypted SMS (payload already encrypted with device public key) */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-encrypted`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ encryptedPayload, deviceId, is2FA }),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── Config ────────────────────────────────────────\r\n\r\n /** Update server URL configuration */\r\n async updateServerUrl(serverUrl: string): Promise<{ success: boolean }> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/update-config`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ serverUrl }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Destroy - cleanup polling */\r\n destroy(): void {\r\n this.stopPolling();\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/shared/labels.ts","../src/client.ts"],"sourcesContent":["export interface SmsTunnelLabels {\r\n title: string;\r\n description: string;\r\n serverUrlLabel: string;\r\n serverUrlPlaceholder: string;\r\n saveButton: string;\r\n pairedStatus: string;\r\n deviceLabel: string;\r\n unpairButton: string;\r\n unpairConfirm: string;\r\n notPairedStatus: string;\r\n notPairedDescription: string;\r\n connectButton: string;\r\n scanQrPrompt: string;\r\n waitingForPairing: string;\r\n cancelButton: string;\r\n qrExpired: string;\r\n testSmsTitle: string;\r\n testPhonePlaceholder: string;\r\n testMessagePlaceholder: string;\r\n sendTestButton: string;\r\n smsSentSuccess: string;\r\n smsError: string;\r\n pairingCodeLabel: string;\r\n pairingCodeCopied: string;\r\n}\r\n\r\nexport const EN_LABELS: SmsTunnelLabels = {\r\n title: 'SMS Configuration (SMSTunnel)',\r\n description: 'Connect an Android phone with the SMSTunnel app to send SMS directly from the application.',\r\n serverUrlLabel: 'SMSTunnel Server',\r\n serverUrlPlaceholder: 'https://smstunnel.io',\r\n saveButton: 'Save',\r\n pairedStatus: 'Connected',\r\n deviceLabel: 'Device',\r\n unpairButton: 'Disconnect',\r\n unpairConfirm: 'Are you sure you want to disconnect the SMS device?',\r\n notPairedStatus: 'Not connected',\r\n notPairedDescription: 'Scan the QR code with the SMSTunnel app on your Android phone.',\r\n connectButton: 'Connect phone',\r\n scanQrPrompt: 'Scan this QR code with the SMSTunnel app on your phone:',\r\n waitingForPairing: 'Waiting for connection...',\r\n cancelButton: 'Cancel',\r\n qrExpired: 'QR code has expired. Generate a new one.',\r\n testSmsTitle: 'Send test SMS',\r\n testPhonePlaceholder: 'Phone number (e.g., +1234567890)',\r\n testMessagePlaceholder: 'Message',\r\n sendTestButton: 'Send',\r\n smsSentSuccess: 'SMS sent successfully!',\r\n smsError: 'Error',\r\n pairingCodeLabel: 'Or enter this code in the app:',\r\n pairingCodeCopied: 'Code copied!',\r\n};\r\n\r\nexport const RO_LABELS: SmsTunnelLabels = {\r\n title: 'Configurare SMS (SMSTunnel)',\r\n description: 'Conecteaza un telefon Android cu aplicatia SMSTunnel pentru a trimite SMS-uri direct din aplicatie.',\r\n serverUrlLabel: 'Server SMSTunnel',\r\n serverUrlPlaceholder: 'https://smstunnel.io',\r\n saveButton: 'Salveaza',\r\n pairedStatus: 'Conectat',\r\n deviceLabel: 'Dispozitiv',\r\n unpairButton: 'Deconecteaza',\r\n unpairConfirm: 'Sigur doresti sa deconectezi dispozitivul SMS?',\r\n notPairedStatus: 'Neconectat',\r\n notPairedDescription: 'Scaneaza codul QR cu aplicatia SMSTunnel de pe telefonul Android.',\r\n connectButton: 'Conecteaza telefon',\r\n scanQrPrompt: 'Scaneaza acest cod QR cu aplicatia SMSTunnel de pe telefon:',\r\n waitingForPairing: 'Se asteapta conectarea...',\r\n cancelButton: 'Anuleaza',\r\n qrExpired: 'Codul QR a expirat. Genereaza unul nou.',\r\n testSmsTitle: 'Trimite SMS de test',\r\n testPhonePlaceholder: 'Nr. telefon (ex: 0741234567)',\r\n testMessagePlaceholder: 'Mesaj',\r\n sendTestButton: 'Trimite',\r\n smsSentSuccess: 'SMS trimis cu succes!',\r\n smsError: 'Eroare',\r\n pairingCodeLabel: 'Sau introdu acest cod in aplicatie:',\r\n pairingCodeCopied: 'Cod copiat!',\r\n};\r\n","/**\r\n * Framework-agnostic SMSTunnel client.\r\n *\r\n * Works with Angular, Svelte, Astro, Solid, Vanilla JS, or any framework.\r\n * For React use `smstunnel-sdk/react`, for Vue use `smstunnel-sdk/vue`.\r\n */\r\nexport interface SmsTunnelClientOptions {\r\n apiBaseUrl: string;\r\n /** Return the current JWT/auth token. SDK adds Authorization: Bearer header automatically. */\r\n getToken?: () => string | null;\r\n /** Full control over auth headers. Takes precedence over getToken. */\r\n getAuthHeaders?: () => Record<string, string>;\r\n pollInterval?: number;\r\n}\r\n\r\n// --- Status ---\r\n\r\nexport interface SmsTunnelStatusResponse {\r\n paired: boolean;\r\n serverUrl?: string;\r\n deviceName?: string;\r\n}\r\n\r\n// --- Pairing ---\r\n\r\nexport interface CreateTokenResponse {\r\n success: boolean;\r\n token?: string;\r\n pairingCode?: string;\r\n qrData?: string;\r\n expiresAt?: string;\r\n pollUrl?: string;\r\n error?: string;\r\n}\r\n\r\nexport interface PairingStatusResponse {\r\n status: 'pending' | 'completed' | 'expired' | 'error';\r\n source?: string;\r\n displayName?: string;\r\n pairedDeviceId?: string;\r\n pairingId?: string;\r\n error?: string;\r\n}\r\n\r\n// --- SMS ---\r\n\r\nexport interface SendSmsResponse {\r\n success: boolean;\r\n messageId?: string;\r\n queued?: boolean;\r\n data?: {\r\n messageId: string;\r\n branded: boolean;\r\n queued: boolean;\r\n remaining: number;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface SmsStatusResponse {\r\n success: boolean;\r\n data?: {\r\n messageId: string;\r\n status: 'sent' | 'pending' | 'failed' | 'delivered';\r\n recipient: string;\r\n sentAt?: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface SendBulkSmsRequest {\r\n messages: Array<{ to: string; message: string }>;\r\n}\r\n\r\nexport interface SendBulkSmsResponse {\r\n success: boolean;\r\n results?: Array<{ to: string; messageId?: string; error?: string }>;\r\n error?: string;\r\n}\r\n\r\nexport interface Send2faRequest {\r\n to: string;\r\n code: string;\r\n template?: string;\r\n}\r\n\r\nexport interface ReceivedSmsResponse {\r\n success: boolean;\r\n data?: Array<{\r\n from: string;\r\n message: string;\r\n receivedAt: string;\r\n }>;\r\n error?: string;\r\n}\r\n\r\n// --- Devices ---\r\n\r\nexport interface DevicesResponse {\r\n success: boolean;\r\n data?: Array<{\r\n id: string;\r\n clientId: string;\r\n name: string;\r\n brand?: string;\r\n model?: string;\r\n connected?: boolean;\r\n }>;\r\n error?: string;\r\n}\r\n\r\n// --- Account ---\r\n\r\nexport interface AccountUsageResponse {\r\n success: boolean;\r\n data?: {\r\n messagesSent: number;\r\n messagesReceived: number;\r\n messagesSentToday: number;\r\n messagesSentMonth: number;\r\n dailyLimit?: number;\r\n monthlyLimit?: number;\r\n };\r\n error?: string;\r\n}\r\n\r\n// --- E2E Encryption ---\r\n\r\nexport interface DeviceE2EStatusResponse {\r\n success: boolean;\r\n data?: {\r\n encryptionEnabled: boolean;\r\n hasPublicKey: boolean;\r\n publicKeyFingerprint?: string;\r\n keyCreatedAt?: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface DevicePublicKeyResponse {\r\n success: boolean;\r\n data?: {\r\n deviceId: string;\r\n deviceName: string;\r\n publicKey: string;\r\n };\r\n error?: string;\r\n}\r\n\r\nexport interface VerifyDeviceKeyResponse {\r\n valid: boolean;\r\n currentFingerprint?: string;\r\n needsRePairing: boolean;\r\n}\r\n\r\n// --- Pairings list ---\r\n\r\nexport interface PairingInfo {\r\n _id: string;\r\n deviceId: string;\r\n type: string;\r\n name: string;\r\n status: 'active' | 'paused' | 'revoked';\r\n messagesSent: number;\r\n createdAt: string;\r\n source: string;\r\n}\r\n\r\n// --- Callback ---\r\n\r\nexport type PairingEventCallback = (event: 'completed' | 'expired' | 'error', data?: any) => void;\r\n\r\nexport class SmsTunnelClient {\r\n private readonly baseUrl: string;\r\n private readonly prefix = 'smstunnel';\r\n private readonly pollInterval: number;\r\n private readonly getAuthHeaders: () => Record<string, string>;\r\n private pollTimer: ReturnType<typeof setInterval> | null = null;\r\n\r\n constructor(options: SmsTunnelClientOptions) {\r\n this.baseUrl = options.apiBaseUrl.replace(/\\/$/, '');\r\n this.pollInterval = options.pollInterval || 3000;\r\n\r\n if (options.getAuthHeaders) {\r\n this.getAuthHeaders = options.getAuthHeaders;\r\n } else if (options.getToken) {\r\n const getToken = options.getToken;\r\n this.getAuthHeaders = (): Record<string, string> => {\r\n const token = getToken();\r\n if (token) return { Authorization: `Bearer ${token}` };\r\n return {};\r\n };\r\n } else {\r\n this.getAuthHeaders = () => ({});\r\n }\r\n }\r\n\r\n // ─── Pairing ───────────────────────────────────────\r\n\r\n /** Get current pairing status */\r\n async getStatus(): Promise<SmsTunnelStatusResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/status`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Create a pairing token (generates QR data) */\r\n async createToken(): Promise<CreateTokenResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/create-token`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Check pairing status for a token (no auth needed) */\r\n async getPairingStatus(token: string): Promise<PairingStatusResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/pairing-status/${token}`,\r\n );\r\n return res.json();\r\n }\r\n\r\n /**\r\n * Start polling for pairing completion.\r\n * Returns a cleanup function to stop polling.\r\n */\r\n startPolling(token: string, callback: PairingEventCallback): () => void {\r\n this.stopPolling();\r\n\r\n this.pollTimer = setInterval(async () => {\r\n try {\r\n const data = await this.getPairingStatus(token);\r\n if (data.status === 'completed') {\r\n this.stopPolling();\r\n callback('completed', data);\r\n } else if (data.status === 'expired') {\r\n this.stopPolling();\r\n callback('expired', data);\r\n }\r\n } catch (err) {\r\n // ignore polling errors\r\n }\r\n }, this.pollInterval);\r\n\r\n return () => this.stopPolling();\r\n }\r\n\r\n /** Stop polling */\r\n stopPolling(): void {\r\n if (this.pollTimer) {\r\n clearInterval(this.pollTimer);\r\n this.pollTimer = null;\r\n }\r\n }\r\n\r\n /** Unpair the connected device */\r\n async unpair(): Promise<{ success: boolean }> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/unpair`, {\r\n method: 'POST',\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** List active pairings (proxied through backend) */\r\n async getPairings(): Promise<PairingInfo[]> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/pairings`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── SMS ───────────────────────────────────────────\r\n\r\n /** Send a single SMS */\r\n async sendSms(to: string, message: string): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send a 2FA SMS */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-2fa`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ to, code, template }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send bulk SMS */\r\n async sendBulk(messages: Array<{ to: string; message: string }>): Promise<SendBulkSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-bulk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ messages }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get SMS delivery status */\r\n async getSmsStatus(messageId: string): Promise<SmsStatusResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/sms-status/${messageId}`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get received SMS (inbox) */\r\n async getReceivedSms(): Promise<ReceivedSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/received`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /** List paired devices */\r\n async getDevices(): Promise<DevicesResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/devices`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Get account usage stats */\r\n async getUsage(): Promise<AccountUsageResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/usage`, {\r\n headers: this.getAuthHeaders(),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /** Get E2E encryption status for a device */\r\n async getDeviceE2EStatus(deviceId: string): Promise<DeviceE2EStatusResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/e2e-status/${deviceId}`,\r\n { headers: this.getAuthHeaders() },\r\n );\r\n return res.json();\r\n }\r\n\r\n /** Get device public key for E2E encryption */\r\n async getDevicePublicKey(deviceId: string): Promise<DevicePublicKeyResponse> {\r\n const res = await fetch(\r\n `${this.baseUrl}/${this.prefix}/public-key/${deviceId}`,\r\n { headers: this.getAuthHeaders() },\r\n );\r\n return res.json();\r\n }\r\n\r\n /** Verify that a stored fingerprint matches the current device key */\r\n async verifyDeviceKey(\r\n deviceId: string,\r\n fingerprint: string,\r\n ): Promise<VerifyDeviceKeyResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/verify-key`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ deviceId, fingerprint }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Send an encrypted SMS (payload already encrypted with device public key) */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResponse> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/send-encrypted`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ encryptedPayload, deviceId, is2FA }),\r\n });\r\n return res.json();\r\n }\r\n\r\n // ─── Config ────────────────────────────────────────\r\n\r\n /** Update server URL configuration */\r\n async updateServerUrl(serverUrl: string): Promise<{ success: boolean }> {\r\n const res = await fetch(`${this.baseUrl}/${this.prefix}/update-config`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...this.getAuthHeaders(),\r\n },\r\n body: JSON.stringify({ serverUrl }),\r\n });\r\n return res.json();\r\n }\r\n\r\n /** Destroy - cleanup polling */\r\n destroy(): void {\r\n this.stopPolling();\r\n }\r\n}\r\n"],"mappings":";AA2BO,IAAM,YAA6B;AAAA,EACxC,OAAO;AAAA,EACP,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,sBAAsB;AAAA,EACtB,eAAe;AAAA,EACf,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,mBAAmB;AACrB;AAEO,IAAM,YAA6B;AAAA,EACxC,OAAO;AAAA,EACP,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,sBAAsB;AAAA,EACtB,eAAe;AAAA,EACf,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,mBAAmB;AACrB;;;AC6FO,IAAM,kBAAN,MAAsB;AAAA,EAO3B,YAAY,SAAiC;AAL7C,SAAiB,SAAS;AAG1B,SAAQ,YAAmD;AAGzD,SAAK,UAAU,QAAQ,WAAW,QAAQ,OAAO,EAAE;AACnD,SAAK,eAAe,QAAQ,gBAAgB;AAE5C,QAAI,QAAQ,gBAAgB;AAC1B,WAAK,iBAAiB,QAAQ;AAAA,IAChC,WAAW,QAAQ,UAAU;AAC3B,YAAM,WAAW,QAAQ;AACzB,WAAK,iBAAiB,MAA8B;AAClD,cAAM,QAAQ,SAAS;AACvB,YAAI,MAAO,QAAO,EAAE,eAAe,UAAU,KAAK,GAAG;AACrD,eAAO,CAAC;AAAA,MACV;AAAA,IACF,OAAO;AACL,WAAK,iBAAiB,OAAO,CAAC;AAAA,IAChC;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,MAAM,YAA8C;AAClD,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,WAAW;AAAA,MAC/D,SAAS,KAAK,eAAe;AAAA,IAC/B,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,cAA4C;AAChD,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,iBAAiB;AAAA,MACrE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,KAAK,eAAe;AAAA,MACzB;AAAA,IACF,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,iBAAiB,OAA+C;AACpE,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,mBAAmB,KAAK;AAAA,IACxD;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,OAAe,UAA4C;AACtE,SAAK,YAAY;AAEjB,SAAK,YAAY,YAAY,YAAY;AACvC,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,iBAAiB,KAAK;AAC9C,YAAI,KAAK,WAAW,aAAa;AAC/B,eAAK,YAAY;AACjB,mBAAS,aAAa,IAAI;AAAA,QAC5B,WAAW,KAAK,WAAW,WAAW;AACpC,eAAK,YAAY;AACjB,mBAAS,WAAW,IAAI;AAAA,QAC1B;AAAA,MACF,SAAS,KAAK;AAAA,MAEd;AAAA,IACF,GAAG,KAAK,YAAY;AAEpB,WAAO,MAAM,KAAK,YAAY;AAAA,EAChC;AAAA;AAAA,EAGA,cAAoB;AAClB,QAAI,KAAK,WAAW;AAClB,oBAAc,KAAK,SAAS;AAC5B,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,SAAwC;AAC5C,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,WAAW;AAAA,MAC/D,QAAQ;AAAA,MACR,SAAS,KAAK,eAAe;AAAA,IAC/B,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,cAAsC;AAC1C,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,aAAa;AAAA,MACjE,SAAS,KAAK,eAAe;AAAA,IAC/B,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,IAAY,SAA2C;AACnE,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,SAAS;AAAA,MAC7D,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,KAAK,eAAe;AAAA,MACzB;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,IAAI,QAAQ,CAAC;AAAA,IACtC,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,QAAQ,IAAY,MAAc,UAA6C;AACnF,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,aAAa;AAAA,MACjE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,KAAK,eAAe;AAAA,MACzB;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,IAAI,MAAM,SAAS,CAAC;AAAA,IAC7C,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,SAAS,UAAgF;AAC7F,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,cAAc;AAAA,MAClE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,KAAK,eAAe;AAAA,MACzB;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,SAAS,CAAC;AAAA,IACnC,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,aAAa,WAA+C;AAChE,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,eAAe,SAAS,IAAI;AAAA,MAChF,SAAS,KAAK,eAAe;AAAA,IAC/B,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,iBAA+C;AACnD,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,aAAa;AAAA,MACjE,SAAS,KAAK,eAAe;AAAA,IAC/B,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA;AAAA,EAKA,MAAM,aAAuC;AAC3C,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,YAAY;AAAA,MAChE,SAAS,KAAK,eAAe;AAAA,IAC/B,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,WAA0C;AAC9C,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,UAAU;AAAA,MAC9D,SAAS,KAAK,eAAe;AAAA,IAC/B,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAmB,UAAoD;AAC3E,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,eAAe,QAAQ;AAAA,MACrD,EAAE,SAAS,KAAK,eAAe,EAAE;AAAA,IACnC;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,mBAAmB,UAAoD;AAC3E,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,eAAe,QAAQ;AAAA,MACrD,EAAE,SAAS,KAAK,eAAe,EAAE;AAAA,IACnC;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,gBACJ,UACA,aACkC;AAClC,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,eAAe;AAAA,MACnE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,KAAK,eAAe;AAAA,MACzB;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,UAAU,YAAY,CAAC;AAAA,IAChD,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,iBACJ,kBACA,UACA,QAAiB,OACS;AAC1B,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,mBAAmB;AAAA,MACvE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,KAAK,eAAe;AAAA,MACzB;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,kBAAkB,UAAU,MAAM,CAAC;AAAA,IAC5D,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,WAAkD;AACtE,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,kBAAkB;AAAA,MACtE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,KAAK,eAAe;AAAA,MACzB;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC;AAAA,IACpC,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,UAAgB;AACd,SAAK,YAAY;AAAA,EACnB;AACF;","names":[]}
@@ -23,6 +23,8 @@ interface SmsTunnelLabels {
23
23
  sendTestButton: string;
24
24
  smsSentSuccess: string;
25
25
  smsError: string;
26
+ pairingCodeLabel: string;
27
+ pairingCodeCopied: string;
26
28
  }
27
29
  declare const EN_LABELS: SmsTunnelLabels;
28
30
  declare const RO_LABELS: SmsTunnelLabels;
@@ -57,6 +59,7 @@ interface SmsTunnelState {
57
59
  deviceName: string;
58
60
  showQr: boolean;
59
61
  qrData: string;
62
+ pairingCode: string;
60
63
  generating: boolean;
61
64
  polling: boolean;
62
65
  error: string;
@@ -93,6 +96,7 @@ declare function useSmsTunnel(options: UseSmsTunnelOptions): {
93
96
  deviceName: string;
94
97
  showQr: boolean;
95
98
  qrData: string;
99
+ pairingCode: string;
96
100
  generating: boolean;
97
101
  polling: boolean;
98
102
  error: string;
@@ -23,6 +23,8 @@ interface SmsTunnelLabels {
23
23
  sendTestButton: string;
24
24
  smsSentSuccess: string;
25
25
  smsError: string;
26
+ pairingCodeLabel: string;
27
+ pairingCodeCopied: string;
26
28
  }
27
29
  declare const EN_LABELS: SmsTunnelLabels;
28
30
  declare const RO_LABELS: SmsTunnelLabels;
@@ -57,6 +59,7 @@ interface SmsTunnelState {
57
59
  deviceName: string;
58
60
  showQr: boolean;
59
61
  qrData: string;
62
+ pairingCode: string;
60
63
  generating: boolean;
61
64
  polling: boolean;
62
65
  error: string;
@@ -93,6 +96,7 @@ declare function useSmsTunnel(options: UseSmsTunnelOptions): {
93
96
  deviceName: string;
94
97
  showQr: boolean;
95
98
  qrData: string;
99
+ pairingCode: string;
96
100
  generating: boolean;
97
101
  polling: boolean;
98
102
  error: string;
@@ -52,6 +52,7 @@ function useSmsTunnel(options) {
52
52
  deviceName: "",
53
53
  showQr: false,
54
54
  qrData: "",
55
+ pairingCode: "",
55
56
  generating: false,
56
57
  polling: false,
57
58
  error: ""
@@ -137,6 +138,7 @@ function useSmsTunnel(options) {
137
138
  setState((prev) => ({
138
139
  ...prev,
139
140
  qrData: data.qrData,
141
+ pairingCode: data.pairingCode || "",
140
142
  showQr: true,
141
143
  polling: true,
142
144
  generating: false
@@ -156,7 +158,8 @@ function useSmsTunnel(options) {
156
158
  ...prev,
157
159
  showQr: false,
158
160
  polling: false,
159
- qrData: ""
161
+ qrData: "",
162
+ pairingCode: ""
160
163
  }));
161
164
  }, []);
162
165
  const unpair = (0, import_react.useCallback)(async () => {
@@ -340,7 +343,9 @@ var EN_LABELS = {
340
343
  testMessagePlaceholder: "Message",
341
344
  sendTestButton: "Send",
342
345
  smsSentSuccess: "SMS sent successfully!",
343
- smsError: "Error"
346
+ smsError: "Error",
347
+ pairingCodeLabel: "Or enter this code in the app:",
348
+ pairingCodeCopied: "Code copied!"
344
349
  };
345
350
  var RO_LABELS = {
346
351
  title: "Configurare SMS (SMSTunnel)",
@@ -364,7 +369,9 @@ var RO_LABELS = {
364
369
  testMessagePlaceholder: "Mesaj",
365
370
  sendTestButton: "Trimite",
366
371
  smsSentSuccess: "SMS trimis cu succes!",
367
- smsError: "Eroare"
372
+ smsError: "Eroare",
373
+ pairingCodeLabel: "Sau introdu acest cod in aplicatie:",
374
+ pairingCodeCopied: "Cod copiat!"
368
375
  };
369
376
 
370
377
  // src/react/SmsTunnelPairing.tsx
@@ -700,6 +707,36 @@ function SmsTunnelPairing({
700
707
  ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.qrContainer, children: [
701
708
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "13px", color: "#4b5563", marginBottom: "12px" }, children: labels.scanQrPrompt }),
702
709
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: styles.qrFrame, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(QrCodeCanvas, { value: tunnel.qrData, size: qrSize }) }),
710
+ tunnel.pairingCode && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
711
+ marginTop: "12px",
712
+ marginBottom: "12px",
713
+ padding: "12px",
714
+ backgroundColor: "#f9fafb",
715
+ borderRadius: "8px",
716
+ border: "1px solid #e5e7eb",
717
+ textAlign: "center"
718
+ }, children: [
719
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "12px", color: "#6b7280", marginBottom: "6px" }, children: labels.pairingCodeLabel }),
720
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
721
+ "div",
722
+ {
723
+ style: {
724
+ fontSize: "28px",
725
+ fontWeight: 700,
726
+ fontFamily: "monospace",
727
+ letterSpacing: "0.3em",
728
+ color: "#111827",
729
+ cursor: "pointer",
730
+ userSelect: "all"
731
+ },
732
+ onClick: () => {
733
+ navigator.clipboard?.writeText(tunnel.pairingCode);
734
+ },
735
+ title: "Click to copy",
736
+ children: tunnel.pairingCode
737
+ }
738
+ )
739
+ ] }),
703
740
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
704
741
  "div",
705
742
  {