@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 +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -2
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +4 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +40 -3
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +40 -3
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.d.mts +2 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +2 -0
- package/dist/server/index.mjs.map +1 -1
- package/dist/vue/index.d.mts +3 -0
- package/dist/vue/index.d.ts +3 -0
- package/dist/vue/index.js +10 -2
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/index.mjs +10 -2
- package/dist/vue/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/index.ts","../../src/server/smstunnel.module.ts","../../src/server/smstunnel.service.ts","../../src/server/smstunnel.constants.ts","../../src/server/smstunnel.controller.ts"],"sourcesContent":["export { SmsTunnelModule } from './smstunnel.module';\r\nexport { SmsTunnelService } from './smstunnel.service';\r\nexport { SmsTunnelController } from './smstunnel.controller';\r\nexport {\r\n SMSTUNNEL_OPTIONS,\r\n SMSTUNNEL_PUBLIC_PATHS,\r\n SMSTUNNEL_DEFAULT_PREFIX,\r\n getSmsTunnelPublicPaths,\r\n} from './smstunnel.constants';\r\nexport type {\r\n SmsTunnelConfig,\r\n SmsTunnelStorageAdapter,\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n SaasActivateResult,\r\n DeviceE2EStatus,\r\n DevicePublicKeyInfo,\r\n VerifyDeviceKeyResult,\r\n} from './smstunnel.interfaces';\r\n","import { DynamicModule, Module, Provider } from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SmsTunnelController } from './smstunnel.controller';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n} from './smstunnel.interfaces';\r\n\r\n@Module({})\r\nexport class SmsTunnelModule {\r\n static forRoot(options: SmsTunnelModuleOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useValue: options,\r\n };\r\n\r\n const controllers = [SmsTunnelController];\r\n if (!options.enableLegacyCallback) {\r\n // Legacy callback is always registered but won't match\r\n // if the consumer doesn't enable it - no-op\r\n }\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers,\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n\r\n static forRootAsync(asyncOptions: SmsTunnelModuleAsyncOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useFactory: asyncOptions.useFactory,\r\n inject: asyncOptions.inject || [],\r\n };\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers: [SmsTunnelController],\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n}\r\n","import { Inject, Injectable, Logger } from '@nestjs/common';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n PairWithApiKeyResult,\r\n ExchangeKeysResult,\r\n SaasActivateResult,\r\n} from './smstunnel.interfaces';\r\n\r\n@Injectable()\r\nexport class SmsTunnelService {\r\n private readonly logger = new Logger(SmsTunnelService.name);\r\n\r\n constructor(\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {}\r\n\r\n /**\r\n * Get current pairing status.\r\n */\r\n async getStatus(): Promise<SmsTunnelStatus> {\r\n const config = await this.options.storage.getConfig();\r\n return {\r\n paired: !!config.apiKey,\r\n serverUrl: config.serverUrl,\r\n deviceName: config.deviceName,\r\n e2eEnabled: !!config.devicePublicKey,\r\n deviceFingerprint: config.deviceFingerprint,\r\n };\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel server.\r\n * Uses Enterprise/SaaS flow when enterpriseApiKey is configured,\r\n * otherwise falls back to public-create flow.\r\n */\r\n async createPairingToken(): Promise<CreateTokenResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n if (this.options.enterpriseApiKey) {\r\n return this.createPairingTokenEnterprise(serverUrl);\r\n }\r\n\r\n return this.createPairingTokenPublic(serverUrl);\r\n }\r\n\r\n /**\r\n * Public flow: POST /api/v1/pairing/public-create\r\n */\r\n private async createPairingTokenPublic(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/public-create`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n source: 'api',\r\n displayName: this.options.displayName || 'App',\r\n displayUrl: this.options.displayUrl || this.options.callbackBaseUrl,\r\n context: { callbackUrl },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel create token failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n\r\n await this.options.storage.updateConfig({ siteToken: data.token });\r\n\r\n this.logger.log(`Pairing token created: ${data.token?.substring(0, 12)}...`);\r\n\r\n return {\r\n success: true,\r\n token: data.token,\r\n qrData: data.qrData,\r\n expiresAt: data.expiresAt,\r\n pollUrl: data.pollUrl || `${serverUrl}/api/v1/pairing/${data.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel create token error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Enterprise/SaaS flow: POST /api/v1/saas/activate\r\n * Creates sub-accounts under the Enterprise account.\r\n */\r\n private async createPairingTokenEnterprise(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/saas/activate`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': this.options.enterpriseApiKey!,\r\n },\r\n body: JSON.stringify({\r\n clientName: this.options.displayName || 'App',\r\n externalId: this.options.externalId || 'default',\r\n callbackUrl,\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel SaaS activate failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: SaasActivateResult = await res.json();\r\n\r\n if (!data.success || !data.activation) {\r\n return { success: false, error: data.error || 'SaaS activation failed' };\r\n }\r\n\r\n // Save the activation token as siteToken\r\n const configUpdate: Record<string, any> = { siteToken: data.activation.token };\r\n\r\n // If the server returned an API key, save it too\r\n if (data.apiKey?.key) {\r\n configUpdate.apiKey = data.apiKey.key;\r\n }\r\n\r\n await this.options.storage.updateConfig(configUpdate);\r\n\r\n this.logger.log(\r\n `SaaS pairing token created: ${data.activation.token?.substring(0, 12)}... (isNew: ${data.isNew})`,\r\n );\r\n\r\n return {\r\n success: true,\r\n token: data.activation.token,\r\n qrData: data.activation.qrData,\r\n expiresAt: data.activation.expiresAt,\r\n pollUrl: `${serverUrl}/api/v1/pairing/${data.activation.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel SaaS activate error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Check pairing status by polling SMSTunnel.\r\n */\r\n async getPairingStatus(token: string): Promise<PairingStatusResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { status: 'error', error: 'SMSTunnel server URL not configured' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/${token}`);\r\n if (!res.ok) {\r\n return { status: 'expired' };\r\n }\r\n return (await res.json()) as PairingStatusResult;\r\n } catch {\r\n return { status: 'error', error: 'Could not contact SMSTunnel' };\r\n }\r\n }\r\n\r\n /**\r\n * Handle callback from SMSTunnel after pairing completes.\r\n */\r\n async handlePairingCallback(body: PairingCallbackBody): Promise<boolean> {\r\n if (body.event !== 'pairing_completed') {\r\n this.logger.warn(`Unknown callback event: ${body.event}`);\r\n return false;\r\n }\r\n\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken && config.siteToken !== body.token) {\r\n this.logger.warn(\r\n `Callback token mismatch: expected ${config.siteToken?.substring(0, 8)}, got ${body.token?.substring(0, 8)}`,\r\n );\r\n return false;\r\n }\r\n\r\n const update: Record<string, any> = {\r\n apiKey: body.apiKey,\r\n deviceName: body.deviceName || 'Android Phone',\r\n };\r\n if (body.deviceId) {\r\n update.deviceId = body.deviceId;\r\n }\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(\r\n `SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`,\r\n );\r\n\r\n // Auto-exchange keys after pairing\r\n if (body.deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Handle legacy WordPress-compatible callback.\r\n */\r\n async handleLegacyCallback(\r\n siteToken: string,\r\n apiKey: string,\r\n deviceName?: string,\r\n ): Promise<boolean> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken !== siteToken) {\r\n this.logger.warn('Legacy callback: site_token mismatch');\r\n return false;\r\n }\r\n\r\n const update: Partial<{ apiKey: string; deviceName: string }> = { apiKey };\r\n if (deviceName) update.deviceName = deviceName;\r\n\r\n await this.options.storage.updateConfig(update);\r\n this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || 'unknown'}`);\r\n return true;\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Validates the key by calling /api/v1/devices, then saves it.\r\n */\r\n async pairWithApiKey(apiKey: string): Promise<PairWithApiKeyResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n if (res.status === 401 || res.status === 403) {\r\n return { success: false, error: 'API key invalid or expired.' };\r\n }\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n const devices = data?.devices || [];\r\n const device = devices[0];\r\n const deviceName = device?.name || 'Android Phone';\r\n const deviceId = device?.id || device?._id;\r\n\r\n const update: Record<string, any> = { apiKey, deviceName };\r\n if (deviceId) update.deviceId = deviceId;\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(`Paired via API key. Device: ${deviceName}`);\r\n\r\n // Auto-exchange keys\r\n if (deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return { success: true, deviceName };\r\n } catch (err: any) {\r\n this.logger.error(`Pair with API key error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send SMS via SMSTunnel API.\r\n */\r\n async sendSms(to: string, message: string): Promise<SendSmsResult> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (!config.apiKey) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel is not configured. Pair a device first.',\r\n };\r\n }\r\n\r\n if (!config.serverUrl) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel server URL is not configured.',\r\n };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/sms/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n\r\n const data: any = await res.json();\r\n\r\n if (data.success) {\r\n this.logger.log(`SMS sent via SMSTunnel: ${data.messageId} -> ${to}`);\r\n return { success: true, messageId: data.messageId };\r\n }\r\n\r\n return {\r\n success: false,\r\n error: data.error || data.message || 'SMSTunnel returned an error',\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMS send error: ${err.message}`);\r\n return { success: false, error: `Could not send SMS: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Unpair - clear saved API key and device info.\r\n */\r\n async unpair(): Promise<void> {\r\n await this.options.storage.clearConfig();\r\n this.logger.log('SMSTunnel unpaired');\r\n }\r\n\r\n /**\r\n * Update server URL in config.\r\n */\r\n async updateServerUrl(serverUrl: string): Promise<void> {\r\n await this.options.storage.updateConfig({\r\n serverUrl: serverUrl.replace(/\\/$/, ''),\r\n });\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send 2FA SMS via SMSTunnel API.\r\n */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send-2fa', { to, code, template });\r\n }\r\n\r\n /**\r\n * Send bulk SMS via SMSTunnel API.\r\n */\r\n async sendBulk(\r\n messages: Array<{ to: string; message: string }>,\r\n ): Promise<{ success: boolean; results?: any[]; error?: string }> {\r\n return this.apiPost('/api/v1/sms/send-bulk', { messages });\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n */\r\n async getSmsStatus(messageId: string): Promise<any> {\r\n return this.apiGet(`/api/v1/sms/status/${messageId}`);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n */\r\n async getReceivedSms(): Promise<any> {\r\n return this.apiGet('/api/v1/sms/received');\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n */\r\n async getDevices(): Promise<any> {\r\n return this.apiGet('/api/v1/devices');\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n */\r\n async getUsage(): Promise<any> {\r\n return this.apiGet('/api/v1/account/usage');\r\n }\r\n\r\n // ─── E2E Encryption ──────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel server and stores it locally.\r\n * Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.\r\n */\r\n async exchangeKeys(): Promise<ExchangeKeysResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n // Resolve deviceId if we don't have it\r\n let deviceId = config.deviceId;\r\n if (!deviceId) {\r\n const listRes = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n if (!listRes.ok) {\r\n return { success: false, error: `Could not list devices: ${listRes.status}` };\r\n }\r\n const listData: any = await listRes.json();\r\n const devices = listData?.devices || [];\r\n if (devices.length === 0) {\r\n return { success: false, error: 'No paired device found.' };\r\n }\r\n deviceId = devices[0].id || devices[0]._id;\r\n await this.options.storage.updateConfig({ deviceId });\r\n }\r\n\r\n // Fetch device detail (includes publicKey)\r\n const res = await fetch(`${serverUrl}/api/v1/devices/${deviceId}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n return { success: false, error: `Could not fetch device: ${res.status}` };\r\n }\r\n\r\n const device: any = await res.json();\r\n\r\n if (!device.publicKey) {\r\n return { success: false, error: 'Device has no public key. Enable encryption in SMSTunnel app on the phone.' };\r\n }\r\n\r\n await this.options.storage.updateConfig({\r\n deviceId,\r\n devicePublicKey: device.publicKey,\r\n deviceFingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name || config.deviceName,\r\n });\r\n\r\n this.logger.log(`E2E keys exchanged. Fingerprint: ${device.publicKeyFingerprint}`);\r\n return {\r\n success: true,\r\n fingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`Key exchange error: ${err.message}`);\r\n return { success: false, error: `Key exchange failed: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send encrypted SMS via SMSTunnel API.\r\n * The payload is encrypted client-side with the device's RSA public key.\r\n */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send', {\r\n encrypted: true,\r\n encryptedPayload,\r\n deviceId,\r\n is2FA,\r\n });\r\n }\r\n\r\n /**\r\n * List pairings for the authenticated user.\r\n */\r\n async getPairings(): Promise<any> {\r\n return this.apiGet('/api/v1/pairings');\r\n }\r\n\r\n // ─── Internal helpers ──────────────────────────────\r\n\r\n /** Make an authenticated GET request to SMSTunnel */\r\n private async apiGet(path: string): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n\r\n /** Make an authenticated POST request to SMSTunnel */\r\n private async apiPost(path: string, body: any): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n}\r\n","export const SMSTUNNEL_OPTIONS = 'SMSTUNNEL_OPTIONS';\r\n\r\nexport const SMSTUNNEL_DEFAULT_PREFIX = 'smstunnel';\r\n\r\n/**\r\n * Public paths that should be excluded from auth guards.\r\n * Use with your global guard to skip auth on callback/polling routes.\r\n *\r\n * Example (NestJS):\r\n * if (SMSTUNNEL_PUBLIC_PATHS.some(p => request.url.includes(p))) return true;\r\n */\r\nexport const SMSTUNNEL_PUBLIC_PATHS = [\r\n '/smstunnel/pairing-status/',\r\n '/smstunnel/callback',\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n];\r\n\r\n/**\r\n * Returns public paths with a custom prefix.\r\n */\r\nexport function getSmsTunnelPublicPaths(prefix: string): string[] {\r\n return [\r\n `/${prefix}/pairing-status/`,\r\n `/${prefix}/callback`,\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n ];\r\n}\r\n","import {\r\n Controller,\r\n Post,\r\n Get,\r\n Body,\r\n Param,\r\n Inject,\r\n Logger,\r\n} from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n} from './smstunnel.interfaces';\r\n\r\n/**\r\n * Decorator to mark routes as public (no auth).\r\n * If you have your own @Public() decorator, the module re-exports\r\n * SMSTUNNEL_PUBLIC_PATHS so you can exclude them in your guard.\r\n */\r\nfunction SmsTunnelPublic() {\r\n // We use Reflect metadata so consumers can detect public routes\r\n return (target: any, key: string, descriptor: PropertyDescriptor) => {\r\n Reflect.defineMetadata('isPublic', true, descriptor.value);\r\n return descriptor;\r\n };\r\n}\r\n\r\n@Controller()\r\nexport class SmsTunnelController {\r\n private readonly logger = new Logger(SmsTunnelController.name);\r\n private readonly prefix: string;\r\n\r\n constructor(\r\n private readonly smsTunnelService: SmsTunnelService,\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {\r\n this.prefix = 'smstunnel';\r\n }\r\n\r\n /**\r\n * Get current pairing status.\r\n * Route: GET /{prefix}/status\r\n */\r\n @Get('smstunnel/status')\r\n async getStatus() {\r\n return this.smsTunnelService.getStatus();\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel.\r\n * Route: POST /{prefix}/create-token\r\n */\r\n @Post('smstunnel/create-token')\r\n async createToken() {\r\n return this.smsTunnelService.createPairingToken();\r\n }\r\n\r\n /**\r\n * Proxy polling to SMSTunnel (avoids CORS issues).\r\n * This should be public (no auth) - just proxying SMSTunnel's endpoint.\r\n * Route: GET /{prefix}/pairing-status/:token\r\n */\r\n @SmsTunnelPublic()\r\n @Get('smstunnel/pairing-status/:token')\r\n async pairingStatus(@Param('token') token: string) {\r\n return this.smsTunnelService.getPairingStatus(token);\r\n }\r\n\r\n /**\r\n * Callback from SMSTunnel after pairing completes.\r\n * Route: POST /{prefix}/callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('smstunnel/callback')\r\n async pairingCallback(@Body() body: PairingCallbackBody) {\r\n this.logger.log(\r\n `SMSTunnel callback: event=${body.event}, token=${body.token?.substring(0, 8)}...`,\r\n );\r\n const success = await this.smsTunnelService.handlePairingCallback(body);\r\n return { success };\r\n }\r\n\r\n /**\r\n * WordPress-compatible legacy callback.\r\n * Route: POST /wp-json/smstunnel/v1/setup-callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('wp-json/smstunnel/v1/setup-callback')\r\n async setupCallbackWp(@Body() body: LegacyCallbackBody) {\r\n this.logger.log(`SMSTunnel legacy callback. Status: ${body.status || 'unknown'}`);\r\n const success = await this.smsTunnelService.handleLegacyCallback(\r\n body.site_token,\r\n body.api_key,\r\n body.device_name,\r\n );\r\n return { success };\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Route: POST /{prefix}/pair-with-key\r\n */\r\n @Post('smstunnel/pair-with-key')\r\n async pairWithKey(@Body() body: { apiKey: string }) {\r\n return this.smsTunnelService.pairWithApiKey(body.apiKey);\r\n }\r\n\r\n /**\r\n * Unpair the connected device.\r\n * Route: POST /{prefix}/unpair\r\n */\r\n @Post('smstunnel/unpair')\r\n async unpair() {\r\n await this.smsTunnelService.unpair();\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Send an SMS via SMSTunnel.\r\n * Route: POST /{prefix}/send\r\n */\r\n @Post('smstunnel/send')\r\n async sendSms(@Body() body: { to: string; message: string }) {\r\n return this.smsTunnelService.sendSms(body.to, body.message);\r\n }\r\n\r\n /**\r\n * Update server URL configuration.\r\n * Route: POST /{prefix}/update-config\r\n */\r\n @Post('smstunnel/update-config')\r\n async updateConfig(@Body() body: { serverUrl: string }) {\r\n await this.smsTunnelService.updateServerUrl(body.serverUrl);\r\n return { success: true };\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send a 2FA SMS.\r\n * Route: POST /{prefix}/send-2fa\r\n */\r\n @Post('smstunnel/send-2fa')\r\n async send2fa(@Body() body: { to: string; code: string; template?: string }) {\r\n return this.smsTunnelService.send2fa(body.to, body.code, body.template);\r\n }\r\n\r\n /**\r\n * Send bulk SMS.\r\n * Route: POST /{prefix}/send-bulk\r\n */\r\n @Post('smstunnel/send-bulk')\r\n async sendBulk(@Body() body: { messages: Array<{ to: string; message: string }> }) {\r\n return this.smsTunnelService.sendBulk(body.messages);\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n * Route: GET /{prefix}/sms-status/:messageId\r\n */\r\n @Get('smstunnel/sms-status/:messageId')\r\n async smsStatus(@Param('messageId') messageId: string) {\r\n return this.smsTunnelService.getSmsStatus(messageId);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n * Route: GET /{prefix}/received\r\n */\r\n @Get('smstunnel/received')\r\n async receivedSms() {\r\n return this.smsTunnelService.getReceivedSms();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel and stores it locally.\r\n * Route: POST /{prefix}/exchange-keys\r\n */\r\n @Post('smstunnel/exchange-keys')\r\n async exchangeKeys() {\r\n return this.smsTunnelService.exchangeKeys();\r\n }\r\n\r\n /**\r\n * Send encrypted SMS.\r\n * Route: POST /{prefix}/send-encrypted\r\n */\r\n @Post('smstunnel/send-encrypted')\r\n async sendEncrypted(\r\n @Body() body: { encryptedPayload: string; deviceId: string; is2FA?: boolean },\r\n ) {\r\n return this.smsTunnelService.sendEncryptedSms(\r\n body.encryptedPayload,\r\n body.deviceId,\r\n body.is2FA,\r\n );\r\n }\r\n\r\n /**\r\n * List pairings.\r\n * Route: GET /{prefix}/pairings\r\n */\r\n @Get('smstunnel/pairings')\r\n async pairings() {\r\n return this.smsTunnelService.getPairings();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n * Route: GET /{prefix}/devices\r\n */\r\n @Get('smstunnel/devices')\r\n async devices() {\r\n return this.smsTunnelService.getDevices();\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n * Route: GET /{prefix}/usage\r\n */\r\n @Get('smstunnel/usage')\r\n async usage() {\r\n return this.smsTunnelService.getUsage();\r\n }\r\n}\r\n"],"mappings":"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","names":["import_common","import_common"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/index.ts","../../src/server/smstunnel.module.ts","../../src/server/smstunnel.service.ts","../../src/server/smstunnel.constants.ts","../../src/server/smstunnel.controller.ts"],"sourcesContent":["export { SmsTunnelModule } from './smstunnel.module';\r\nexport { SmsTunnelService } from './smstunnel.service';\r\nexport { SmsTunnelController } from './smstunnel.controller';\r\nexport {\r\n SMSTUNNEL_OPTIONS,\r\n SMSTUNNEL_PUBLIC_PATHS,\r\n SMSTUNNEL_DEFAULT_PREFIX,\r\n getSmsTunnelPublicPaths,\r\n} from './smstunnel.constants';\r\nexport type {\r\n SmsTunnelConfig,\r\n SmsTunnelStorageAdapter,\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n SaasActivateResult,\r\n DeviceE2EStatus,\r\n DevicePublicKeyInfo,\r\n VerifyDeviceKeyResult,\r\n} from './smstunnel.interfaces';\r\n","import { DynamicModule, Module, Provider } from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SmsTunnelController } from './smstunnel.controller';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n} from './smstunnel.interfaces';\r\n\r\n@Module({})\r\nexport class SmsTunnelModule {\r\n static forRoot(options: SmsTunnelModuleOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useValue: options,\r\n };\r\n\r\n const controllers = [SmsTunnelController];\r\n if (!options.enableLegacyCallback) {\r\n // Legacy callback is always registered but won't match\r\n // if the consumer doesn't enable it - no-op\r\n }\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers,\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n\r\n static forRootAsync(asyncOptions: SmsTunnelModuleAsyncOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useFactory: asyncOptions.useFactory,\r\n inject: asyncOptions.inject || [],\r\n };\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers: [SmsTunnelController],\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n}\r\n","import { Inject, Injectable, Logger } from '@nestjs/common';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n PairWithApiKeyResult,\r\n ExchangeKeysResult,\r\n SaasActivateResult,\r\n} from './smstunnel.interfaces';\r\n\r\n@Injectable()\r\nexport class SmsTunnelService {\r\n private readonly logger = new Logger(SmsTunnelService.name);\r\n\r\n constructor(\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {}\r\n\r\n /**\r\n * Get current pairing status.\r\n */\r\n async getStatus(): Promise<SmsTunnelStatus> {\r\n const config = await this.options.storage.getConfig();\r\n return {\r\n paired: !!config.apiKey,\r\n serverUrl: config.serverUrl,\r\n deviceName: config.deviceName,\r\n e2eEnabled: !!config.devicePublicKey,\r\n deviceFingerprint: config.deviceFingerprint,\r\n };\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel server.\r\n * Uses Enterprise/SaaS flow when enterpriseApiKey is configured,\r\n * otherwise falls back to public-create flow.\r\n */\r\n async createPairingToken(): Promise<CreateTokenResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n if (this.options.enterpriseApiKey) {\r\n return this.createPairingTokenEnterprise(serverUrl);\r\n }\r\n\r\n return this.createPairingTokenPublic(serverUrl);\r\n }\r\n\r\n /**\r\n * Public flow: POST /api/v1/pairing/public-create\r\n */\r\n private async createPairingTokenPublic(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/public-create`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n source: 'api',\r\n displayName: this.options.displayName || 'App',\r\n displayUrl: this.options.displayUrl || this.options.callbackBaseUrl,\r\n context: { callbackUrl },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel create token failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n\r\n await this.options.storage.updateConfig({ siteToken: data.token });\r\n\r\n this.logger.log(`Pairing token created: ${data.token?.substring(0, 12)}...`);\r\n\r\n return {\r\n success: true,\r\n token: data.token,\r\n pairingCode: data.pairingCode,\r\n qrData: data.qrData,\r\n expiresAt: data.expiresAt,\r\n pollUrl: data.pollUrl || `${serverUrl}/api/v1/pairing/${data.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel create token error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Enterprise/SaaS flow: POST /api/v1/saas/activate\r\n * Creates sub-accounts under the Enterprise account.\r\n */\r\n private async createPairingTokenEnterprise(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/saas/activate`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': this.options.enterpriseApiKey!,\r\n },\r\n body: JSON.stringify({\r\n clientName: this.options.displayName || 'App',\r\n externalId: this.options.externalId || 'default',\r\n callbackUrl,\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel SaaS activate failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: SaasActivateResult = await res.json();\r\n\r\n if (!data.success || !data.activation) {\r\n return { success: false, error: data.error || 'SaaS activation failed' };\r\n }\r\n\r\n // Save the activation token as siteToken\r\n const configUpdate: Record<string, any> = { siteToken: data.activation.token };\r\n\r\n // If the server returned an API key, save it too\r\n if (data.apiKey?.key) {\r\n configUpdate.apiKey = data.apiKey.key;\r\n }\r\n\r\n await this.options.storage.updateConfig(configUpdate);\r\n\r\n this.logger.log(\r\n `SaaS pairing token created: ${data.activation.token?.substring(0, 12)}... (isNew: ${data.isNew})`,\r\n );\r\n\r\n return {\r\n success: true,\r\n token: data.activation.token,\r\n pairingCode: data.activation.pairingCode,\r\n qrData: data.activation.qrData,\r\n expiresAt: data.activation.expiresAt,\r\n pollUrl: `${serverUrl}/api/v1/pairing/${data.activation.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel SaaS activate error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Check pairing status by polling SMSTunnel.\r\n */\r\n async getPairingStatus(token: string): Promise<PairingStatusResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { status: 'error', error: 'SMSTunnel server URL not configured' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/${token}`);\r\n if (!res.ok) {\r\n return { status: 'expired' };\r\n }\r\n return (await res.json()) as PairingStatusResult;\r\n } catch {\r\n return { status: 'error', error: 'Could not contact SMSTunnel' };\r\n }\r\n }\r\n\r\n /**\r\n * Handle callback from SMSTunnel after pairing completes.\r\n */\r\n async handlePairingCallback(body: PairingCallbackBody): Promise<boolean> {\r\n if (body.event !== 'pairing_completed') {\r\n this.logger.warn(`Unknown callback event: ${body.event}`);\r\n return false;\r\n }\r\n\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken && config.siteToken !== body.token) {\r\n this.logger.warn(\r\n `Callback token mismatch: expected ${config.siteToken?.substring(0, 8)}, got ${body.token?.substring(0, 8)}`,\r\n );\r\n return false;\r\n }\r\n\r\n const update: Record<string, any> = {\r\n apiKey: body.apiKey,\r\n deviceName: body.deviceName || 'Android Phone',\r\n };\r\n if (body.deviceId) {\r\n update.deviceId = body.deviceId;\r\n }\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(\r\n `SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`,\r\n );\r\n\r\n // Auto-exchange keys after pairing\r\n if (body.deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Handle legacy WordPress-compatible callback.\r\n */\r\n async handleLegacyCallback(\r\n siteToken: string,\r\n apiKey: string,\r\n deviceName?: string,\r\n ): Promise<boolean> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken !== siteToken) {\r\n this.logger.warn('Legacy callback: site_token mismatch');\r\n return false;\r\n }\r\n\r\n const update: Partial<{ apiKey: string; deviceName: string }> = { apiKey };\r\n if (deviceName) update.deviceName = deviceName;\r\n\r\n await this.options.storage.updateConfig(update);\r\n this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || 'unknown'}`);\r\n return true;\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Validates the key by calling /api/v1/devices, then saves it.\r\n */\r\n async pairWithApiKey(apiKey: string): Promise<PairWithApiKeyResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n if (res.status === 401 || res.status === 403) {\r\n return { success: false, error: 'API key invalid or expired.' };\r\n }\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n const devices = data?.devices || [];\r\n const device = devices[0];\r\n const deviceName = device?.name || 'Android Phone';\r\n const deviceId = device?.id || device?._id;\r\n\r\n const update: Record<string, any> = { apiKey, deviceName };\r\n if (deviceId) update.deviceId = deviceId;\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(`Paired via API key. Device: ${deviceName}`);\r\n\r\n // Auto-exchange keys\r\n if (deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return { success: true, deviceName };\r\n } catch (err: any) {\r\n this.logger.error(`Pair with API key error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send SMS via SMSTunnel API.\r\n */\r\n async sendSms(to: string, message: string): Promise<SendSmsResult> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (!config.apiKey) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel is not configured. Pair a device first.',\r\n };\r\n }\r\n\r\n if (!config.serverUrl) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel server URL is not configured.',\r\n };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/sms/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n\r\n const data: any = await res.json();\r\n\r\n if (data.success) {\r\n this.logger.log(`SMS sent via SMSTunnel: ${data.messageId} -> ${to}`);\r\n return { success: true, messageId: data.messageId };\r\n }\r\n\r\n return {\r\n success: false,\r\n error: data.error || data.message || 'SMSTunnel returned an error',\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMS send error: ${err.message}`);\r\n return { success: false, error: `Could not send SMS: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Unpair - clear saved API key and device info.\r\n */\r\n async unpair(): Promise<void> {\r\n await this.options.storage.clearConfig();\r\n this.logger.log('SMSTunnel unpaired');\r\n }\r\n\r\n /**\r\n * Update server URL in config.\r\n */\r\n async updateServerUrl(serverUrl: string): Promise<void> {\r\n await this.options.storage.updateConfig({\r\n serverUrl: serverUrl.replace(/\\/$/, ''),\r\n });\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send 2FA SMS via SMSTunnel API.\r\n */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send-2fa', { to, code, template });\r\n }\r\n\r\n /**\r\n * Send bulk SMS via SMSTunnel API.\r\n */\r\n async sendBulk(\r\n messages: Array<{ to: string; message: string }>,\r\n ): Promise<{ success: boolean; results?: any[]; error?: string }> {\r\n return this.apiPost('/api/v1/sms/send-bulk', { messages });\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n */\r\n async getSmsStatus(messageId: string): Promise<any> {\r\n return this.apiGet(`/api/v1/sms/status/${messageId}`);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n */\r\n async getReceivedSms(): Promise<any> {\r\n return this.apiGet('/api/v1/sms/received');\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n */\r\n async getDevices(): Promise<any> {\r\n return this.apiGet('/api/v1/devices');\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n */\r\n async getUsage(): Promise<any> {\r\n return this.apiGet('/api/v1/account/usage');\r\n }\r\n\r\n // ─── E2E Encryption ──────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel server and stores it locally.\r\n * Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.\r\n */\r\n async exchangeKeys(): Promise<ExchangeKeysResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n // Resolve deviceId if we don't have it\r\n let deviceId = config.deviceId;\r\n if (!deviceId) {\r\n const listRes = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n if (!listRes.ok) {\r\n return { success: false, error: `Could not list devices: ${listRes.status}` };\r\n }\r\n const listData: any = await listRes.json();\r\n const devices = listData?.devices || [];\r\n if (devices.length === 0) {\r\n return { success: false, error: 'No paired device found.' };\r\n }\r\n deviceId = devices[0].id || devices[0]._id;\r\n await this.options.storage.updateConfig({ deviceId });\r\n }\r\n\r\n // Fetch device detail (includes publicKey)\r\n const res = await fetch(`${serverUrl}/api/v1/devices/${deviceId}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n return { success: false, error: `Could not fetch device: ${res.status}` };\r\n }\r\n\r\n const device: any = await res.json();\r\n\r\n if (!device.publicKey) {\r\n return { success: false, error: 'Device has no public key. Enable encryption in SMSTunnel app on the phone.' };\r\n }\r\n\r\n await this.options.storage.updateConfig({\r\n deviceId,\r\n devicePublicKey: device.publicKey,\r\n deviceFingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name || config.deviceName,\r\n });\r\n\r\n this.logger.log(`E2E keys exchanged. Fingerprint: ${device.publicKeyFingerprint}`);\r\n return {\r\n success: true,\r\n fingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`Key exchange error: ${err.message}`);\r\n return { success: false, error: `Key exchange failed: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send encrypted SMS via SMSTunnel API.\r\n * The payload is encrypted client-side with the device's RSA public key.\r\n */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send', {\r\n encrypted: true,\r\n encryptedPayload,\r\n deviceId,\r\n is2FA,\r\n });\r\n }\r\n\r\n /**\r\n * List pairings for the authenticated user.\r\n */\r\n async getPairings(): Promise<any> {\r\n return this.apiGet('/api/v1/pairings');\r\n }\r\n\r\n // ─── Internal helpers ──────────────────────────────\r\n\r\n /** Make an authenticated GET request to SMSTunnel */\r\n private async apiGet(path: string): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n\r\n /** Make an authenticated POST request to SMSTunnel */\r\n private async apiPost(path: string, body: any): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n}\r\n","export const SMSTUNNEL_OPTIONS = 'SMSTUNNEL_OPTIONS';\r\n\r\nexport const SMSTUNNEL_DEFAULT_PREFIX = 'smstunnel';\r\n\r\n/**\r\n * Public paths that should be excluded from auth guards.\r\n * Use with your global guard to skip auth on callback/polling routes.\r\n *\r\n * Example (NestJS):\r\n * if (SMSTUNNEL_PUBLIC_PATHS.some(p => request.url.includes(p))) return true;\r\n */\r\nexport const SMSTUNNEL_PUBLIC_PATHS = [\r\n '/smstunnel/pairing-status/',\r\n '/smstunnel/callback',\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n];\r\n\r\n/**\r\n * Returns public paths with a custom prefix.\r\n */\r\nexport function getSmsTunnelPublicPaths(prefix: string): string[] {\r\n return [\r\n `/${prefix}/pairing-status/`,\r\n `/${prefix}/callback`,\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n ];\r\n}\r\n","import {\r\n Controller,\r\n Post,\r\n Get,\r\n Body,\r\n Param,\r\n Inject,\r\n Logger,\r\n} from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n} from './smstunnel.interfaces';\r\n\r\n/**\r\n * Decorator to mark routes as public (no auth).\r\n * If you have your own @Public() decorator, the module re-exports\r\n * SMSTUNNEL_PUBLIC_PATHS so you can exclude them in your guard.\r\n */\r\nfunction SmsTunnelPublic() {\r\n // We use Reflect metadata so consumers can detect public routes\r\n return (target: any, key: string, descriptor: PropertyDescriptor) => {\r\n Reflect.defineMetadata('isPublic', true, descriptor.value);\r\n return descriptor;\r\n };\r\n}\r\n\r\n@Controller()\r\nexport class SmsTunnelController {\r\n private readonly logger = new Logger(SmsTunnelController.name);\r\n private readonly prefix: string;\r\n\r\n constructor(\r\n private readonly smsTunnelService: SmsTunnelService,\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {\r\n this.prefix = 'smstunnel';\r\n }\r\n\r\n /**\r\n * Get current pairing status.\r\n * Route: GET /{prefix}/status\r\n */\r\n @Get('smstunnel/status')\r\n async getStatus() {\r\n return this.smsTunnelService.getStatus();\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel.\r\n * Route: POST /{prefix}/create-token\r\n */\r\n @Post('smstunnel/create-token')\r\n async createToken() {\r\n return this.smsTunnelService.createPairingToken();\r\n }\r\n\r\n /**\r\n * Proxy polling to SMSTunnel (avoids CORS issues).\r\n * This should be public (no auth) - just proxying SMSTunnel's endpoint.\r\n * Route: GET /{prefix}/pairing-status/:token\r\n */\r\n @SmsTunnelPublic()\r\n @Get('smstunnel/pairing-status/:token')\r\n async pairingStatus(@Param('token') token: string) {\r\n return this.smsTunnelService.getPairingStatus(token);\r\n }\r\n\r\n /**\r\n * Callback from SMSTunnel after pairing completes.\r\n * Route: POST /{prefix}/callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('smstunnel/callback')\r\n async pairingCallback(@Body() body: PairingCallbackBody) {\r\n this.logger.log(\r\n `SMSTunnel callback: event=${body.event}, token=${body.token?.substring(0, 8)}...`,\r\n );\r\n const success = await this.smsTunnelService.handlePairingCallback(body);\r\n return { success };\r\n }\r\n\r\n /**\r\n * WordPress-compatible legacy callback.\r\n * Route: POST /wp-json/smstunnel/v1/setup-callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('wp-json/smstunnel/v1/setup-callback')\r\n async setupCallbackWp(@Body() body: LegacyCallbackBody) {\r\n this.logger.log(`SMSTunnel legacy callback. Status: ${body.status || 'unknown'}`);\r\n const success = await this.smsTunnelService.handleLegacyCallback(\r\n body.site_token,\r\n body.api_key,\r\n body.device_name,\r\n );\r\n return { success };\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Route: POST /{prefix}/pair-with-key\r\n */\r\n @Post('smstunnel/pair-with-key')\r\n async pairWithKey(@Body() body: { apiKey: string }) {\r\n return this.smsTunnelService.pairWithApiKey(body.apiKey);\r\n }\r\n\r\n /**\r\n * Unpair the connected device.\r\n * Route: POST /{prefix}/unpair\r\n */\r\n @Post('smstunnel/unpair')\r\n async unpair() {\r\n await this.smsTunnelService.unpair();\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Send an SMS via SMSTunnel.\r\n * Route: POST /{prefix}/send\r\n */\r\n @Post('smstunnel/send')\r\n async sendSms(@Body() body: { to: string; message: string }) {\r\n return this.smsTunnelService.sendSms(body.to, body.message);\r\n }\r\n\r\n /**\r\n * Update server URL configuration.\r\n * Route: POST /{prefix}/update-config\r\n */\r\n @Post('smstunnel/update-config')\r\n async updateConfig(@Body() body: { serverUrl: string }) {\r\n await this.smsTunnelService.updateServerUrl(body.serverUrl);\r\n return { success: true };\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send a 2FA SMS.\r\n * Route: POST /{prefix}/send-2fa\r\n */\r\n @Post('smstunnel/send-2fa')\r\n async send2fa(@Body() body: { to: string; code: string; template?: string }) {\r\n return this.smsTunnelService.send2fa(body.to, body.code, body.template);\r\n }\r\n\r\n /**\r\n * Send bulk SMS.\r\n * Route: POST /{prefix}/send-bulk\r\n */\r\n @Post('smstunnel/send-bulk')\r\n async sendBulk(@Body() body: { messages: Array<{ to: string; message: string }> }) {\r\n return this.smsTunnelService.sendBulk(body.messages);\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n * Route: GET /{prefix}/sms-status/:messageId\r\n */\r\n @Get('smstunnel/sms-status/:messageId')\r\n async smsStatus(@Param('messageId') messageId: string) {\r\n return this.smsTunnelService.getSmsStatus(messageId);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n * Route: GET /{prefix}/received\r\n */\r\n @Get('smstunnel/received')\r\n async receivedSms() {\r\n return this.smsTunnelService.getReceivedSms();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel and stores it locally.\r\n * Route: POST /{prefix}/exchange-keys\r\n */\r\n @Post('smstunnel/exchange-keys')\r\n async exchangeKeys() {\r\n return this.smsTunnelService.exchangeKeys();\r\n }\r\n\r\n /**\r\n * Send encrypted SMS.\r\n * Route: POST /{prefix}/send-encrypted\r\n */\r\n @Post('smstunnel/send-encrypted')\r\n async sendEncrypted(\r\n @Body() body: { encryptedPayload: string; deviceId: string; is2FA?: boolean },\r\n ) {\r\n return this.smsTunnelService.sendEncryptedSms(\r\n body.encryptedPayload,\r\n body.deviceId,\r\n body.is2FA,\r\n );\r\n }\r\n\r\n /**\r\n * List pairings.\r\n * Route: GET /{prefix}/pairings\r\n */\r\n @Get('smstunnel/pairings')\r\n async pairings() {\r\n return this.smsTunnelService.getPairings();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n * Route: GET /{prefix}/devices\r\n */\r\n @Get('smstunnel/devices')\r\n async devices() {\r\n return this.smsTunnelService.getDevices();\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n * Route: GET /{prefix}/usage\r\n */\r\n @Get('smstunnel/usage')\r\n async usage() {\r\n return this.smsTunnelService.getUsage();\r\n }\r\n}\r\n"],"mappings":"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","names":["import_common","import_common"]}
|
package/dist/server/index.mjs
CHANGED
|
@@ -95,6 +95,7 @@ var SmsTunnelService = class {
|
|
|
95
95
|
return {
|
|
96
96
|
success: true,
|
|
97
97
|
token: data.token,
|
|
98
|
+
pairingCode: data.pairingCode,
|
|
98
99
|
qrData: data.qrData,
|
|
99
100
|
expiresAt: data.expiresAt,
|
|
100
101
|
pollUrl: data.pollUrl || `${serverUrl}/api/v1/pairing/${data.token}`
|
|
@@ -144,6 +145,7 @@ var SmsTunnelService = class {
|
|
|
144
145
|
return {
|
|
145
146
|
success: true,
|
|
146
147
|
token: data.activation.token,
|
|
148
|
+
pairingCode: data.activation.pairingCode,
|
|
147
149
|
qrData: data.activation.qrData,
|
|
148
150
|
expiresAt: data.activation.expiresAt,
|
|
149
151
|
pollUrl: `${serverUrl}/api/v1/pairing/${data.activation.token}`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/smstunnel.module.ts","../../src/server/smstunnel.service.ts","../../src/server/smstunnel.constants.ts","../../src/server/smstunnel.controller.ts"],"sourcesContent":["import { DynamicModule, Module, Provider } from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SmsTunnelController } from './smstunnel.controller';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n} from './smstunnel.interfaces';\r\n\r\n@Module({})\r\nexport class SmsTunnelModule {\r\n static forRoot(options: SmsTunnelModuleOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useValue: options,\r\n };\r\n\r\n const controllers = [SmsTunnelController];\r\n if (!options.enableLegacyCallback) {\r\n // Legacy callback is always registered but won't match\r\n // if the consumer doesn't enable it - no-op\r\n }\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers,\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n\r\n static forRootAsync(asyncOptions: SmsTunnelModuleAsyncOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useFactory: asyncOptions.useFactory,\r\n inject: asyncOptions.inject || [],\r\n };\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers: [SmsTunnelController],\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n}\r\n","import { Inject, Injectable, Logger } from '@nestjs/common';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n PairWithApiKeyResult,\r\n ExchangeKeysResult,\r\n SaasActivateResult,\r\n} from './smstunnel.interfaces';\r\n\r\n@Injectable()\r\nexport class SmsTunnelService {\r\n private readonly logger = new Logger(SmsTunnelService.name);\r\n\r\n constructor(\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {}\r\n\r\n /**\r\n * Get current pairing status.\r\n */\r\n async getStatus(): Promise<SmsTunnelStatus> {\r\n const config = await this.options.storage.getConfig();\r\n return {\r\n paired: !!config.apiKey,\r\n serverUrl: config.serverUrl,\r\n deviceName: config.deviceName,\r\n e2eEnabled: !!config.devicePublicKey,\r\n deviceFingerprint: config.deviceFingerprint,\r\n };\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel server.\r\n * Uses Enterprise/SaaS flow when enterpriseApiKey is configured,\r\n * otherwise falls back to public-create flow.\r\n */\r\n async createPairingToken(): Promise<CreateTokenResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n if (this.options.enterpriseApiKey) {\r\n return this.createPairingTokenEnterprise(serverUrl);\r\n }\r\n\r\n return this.createPairingTokenPublic(serverUrl);\r\n }\r\n\r\n /**\r\n * Public flow: POST /api/v1/pairing/public-create\r\n */\r\n private async createPairingTokenPublic(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/public-create`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n source: 'api',\r\n displayName: this.options.displayName || 'App',\r\n displayUrl: this.options.displayUrl || this.options.callbackBaseUrl,\r\n context: { callbackUrl },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel create token failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n\r\n await this.options.storage.updateConfig({ siteToken: data.token });\r\n\r\n this.logger.log(`Pairing token created: ${data.token?.substring(0, 12)}...`);\r\n\r\n return {\r\n success: true,\r\n token: data.token,\r\n qrData: data.qrData,\r\n expiresAt: data.expiresAt,\r\n pollUrl: data.pollUrl || `${serverUrl}/api/v1/pairing/${data.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel create token error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Enterprise/SaaS flow: POST /api/v1/saas/activate\r\n * Creates sub-accounts under the Enterprise account.\r\n */\r\n private async createPairingTokenEnterprise(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/saas/activate`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': this.options.enterpriseApiKey!,\r\n },\r\n body: JSON.stringify({\r\n clientName: this.options.displayName || 'App',\r\n externalId: this.options.externalId || 'default',\r\n callbackUrl,\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel SaaS activate failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: SaasActivateResult = await res.json();\r\n\r\n if (!data.success || !data.activation) {\r\n return { success: false, error: data.error || 'SaaS activation failed' };\r\n }\r\n\r\n // Save the activation token as siteToken\r\n const configUpdate: Record<string, any> = { siteToken: data.activation.token };\r\n\r\n // If the server returned an API key, save it too\r\n if (data.apiKey?.key) {\r\n configUpdate.apiKey = data.apiKey.key;\r\n }\r\n\r\n await this.options.storage.updateConfig(configUpdate);\r\n\r\n this.logger.log(\r\n `SaaS pairing token created: ${data.activation.token?.substring(0, 12)}... (isNew: ${data.isNew})`,\r\n );\r\n\r\n return {\r\n success: true,\r\n token: data.activation.token,\r\n qrData: data.activation.qrData,\r\n expiresAt: data.activation.expiresAt,\r\n pollUrl: `${serverUrl}/api/v1/pairing/${data.activation.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel SaaS activate error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Check pairing status by polling SMSTunnel.\r\n */\r\n async getPairingStatus(token: string): Promise<PairingStatusResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { status: 'error', error: 'SMSTunnel server URL not configured' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/${token}`);\r\n if (!res.ok) {\r\n return { status: 'expired' };\r\n }\r\n return (await res.json()) as PairingStatusResult;\r\n } catch {\r\n return { status: 'error', error: 'Could not contact SMSTunnel' };\r\n }\r\n }\r\n\r\n /**\r\n * Handle callback from SMSTunnel after pairing completes.\r\n */\r\n async handlePairingCallback(body: PairingCallbackBody): Promise<boolean> {\r\n if (body.event !== 'pairing_completed') {\r\n this.logger.warn(`Unknown callback event: ${body.event}`);\r\n return false;\r\n }\r\n\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken && config.siteToken !== body.token) {\r\n this.logger.warn(\r\n `Callback token mismatch: expected ${config.siteToken?.substring(0, 8)}, got ${body.token?.substring(0, 8)}`,\r\n );\r\n return false;\r\n }\r\n\r\n const update: Record<string, any> = {\r\n apiKey: body.apiKey,\r\n deviceName: body.deviceName || 'Android Phone',\r\n };\r\n if (body.deviceId) {\r\n update.deviceId = body.deviceId;\r\n }\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(\r\n `SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`,\r\n );\r\n\r\n // Auto-exchange keys after pairing\r\n if (body.deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Handle legacy WordPress-compatible callback.\r\n */\r\n async handleLegacyCallback(\r\n siteToken: string,\r\n apiKey: string,\r\n deviceName?: string,\r\n ): Promise<boolean> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken !== siteToken) {\r\n this.logger.warn('Legacy callback: site_token mismatch');\r\n return false;\r\n }\r\n\r\n const update: Partial<{ apiKey: string; deviceName: string }> = { apiKey };\r\n if (deviceName) update.deviceName = deviceName;\r\n\r\n await this.options.storage.updateConfig(update);\r\n this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || 'unknown'}`);\r\n return true;\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Validates the key by calling /api/v1/devices, then saves it.\r\n */\r\n async pairWithApiKey(apiKey: string): Promise<PairWithApiKeyResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n if (res.status === 401 || res.status === 403) {\r\n return { success: false, error: 'API key invalid or expired.' };\r\n }\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n const devices = data?.devices || [];\r\n const device = devices[0];\r\n const deviceName = device?.name || 'Android Phone';\r\n const deviceId = device?.id || device?._id;\r\n\r\n const update: Record<string, any> = { apiKey, deviceName };\r\n if (deviceId) update.deviceId = deviceId;\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(`Paired via API key. Device: ${deviceName}`);\r\n\r\n // Auto-exchange keys\r\n if (deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return { success: true, deviceName };\r\n } catch (err: any) {\r\n this.logger.error(`Pair with API key error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send SMS via SMSTunnel API.\r\n */\r\n async sendSms(to: string, message: string): Promise<SendSmsResult> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (!config.apiKey) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel is not configured. Pair a device first.',\r\n };\r\n }\r\n\r\n if (!config.serverUrl) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel server URL is not configured.',\r\n };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/sms/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n\r\n const data: any = await res.json();\r\n\r\n if (data.success) {\r\n this.logger.log(`SMS sent via SMSTunnel: ${data.messageId} -> ${to}`);\r\n return { success: true, messageId: data.messageId };\r\n }\r\n\r\n return {\r\n success: false,\r\n error: data.error || data.message || 'SMSTunnel returned an error',\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMS send error: ${err.message}`);\r\n return { success: false, error: `Could not send SMS: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Unpair - clear saved API key and device info.\r\n */\r\n async unpair(): Promise<void> {\r\n await this.options.storage.clearConfig();\r\n this.logger.log('SMSTunnel unpaired');\r\n }\r\n\r\n /**\r\n * Update server URL in config.\r\n */\r\n async updateServerUrl(serverUrl: string): Promise<void> {\r\n await this.options.storage.updateConfig({\r\n serverUrl: serverUrl.replace(/\\/$/, ''),\r\n });\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send 2FA SMS via SMSTunnel API.\r\n */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send-2fa', { to, code, template });\r\n }\r\n\r\n /**\r\n * Send bulk SMS via SMSTunnel API.\r\n */\r\n async sendBulk(\r\n messages: Array<{ to: string; message: string }>,\r\n ): Promise<{ success: boolean; results?: any[]; error?: string }> {\r\n return this.apiPost('/api/v1/sms/send-bulk', { messages });\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n */\r\n async getSmsStatus(messageId: string): Promise<any> {\r\n return this.apiGet(`/api/v1/sms/status/${messageId}`);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n */\r\n async getReceivedSms(): Promise<any> {\r\n return this.apiGet('/api/v1/sms/received');\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n */\r\n async getDevices(): Promise<any> {\r\n return this.apiGet('/api/v1/devices');\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n */\r\n async getUsage(): Promise<any> {\r\n return this.apiGet('/api/v1/account/usage');\r\n }\r\n\r\n // ─── E2E Encryption ──────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel server and stores it locally.\r\n * Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.\r\n */\r\n async exchangeKeys(): Promise<ExchangeKeysResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n // Resolve deviceId if we don't have it\r\n let deviceId = config.deviceId;\r\n if (!deviceId) {\r\n const listRes = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n if (!listRes.ok) {\r\n return { success: false, error: `Could not list devices: ${listRes.status}` };\r\n }\r\n const listData: any = await listRes.json();\r\n const devices = listData?.devices || [];\r\n if (devices.length === 0) {\r\n return { success: false, error: 'No paired device found.' };\r\n }\r\n deviceId = devices[0].id || devices[0]._id;\r\n await this.options.storage.updateConfig({ deviceId });\r\n }\r\n\r\n // Fetch device detail (includes publicKey)\r\n const res = await fetch(`${serverUrl}/api/v1/devices/${deviceId}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n return { success: false, error: `Could not fetch device: ${res.status}` };\r\n }\r\n\r\n const device: any = await res.json();\r\n\r\n if (!device.publicKey) {\r\n return { success: false, error: 'Device has no public key. Enable encryption in SMSTunnel app on the phone.' };\r\n }\r\n\r\n await this.options.storage.updateConfig({\r\n deviceId,\r\n devicePublicKey: device.publicKey,\r\n deviceFingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name || config.deviceName,\r\n });\r\n\r\n this.logger.log(`E2E keys exchanged. Fingerprint: ${device.publicKeyFingerprint}`);\r\n return {\r\n success: true,\r\n fingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`Key exchange error: ${err.message}`);\r\n return { success: false, error: `Key exchange failed: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send encrypted SMS via SMSTunnel API.\r\n * The payload is encrypted client-side with the device's RSA public key.\r\n */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send', {\r\n encrypted: true,\r\n encryptedPayload,\r\n deviceId,\r\n is2FA,\r\n });\r\n }\r\n\r\n /**\r\n * List pairings for the authenticated user.\r\n */\r\n async getPairings(): Promise<any> {\r\n return this.apiGet('/api/v1/pairings');\r\n }\r\n\r\n // ─── Internal helpers ──────────────────────────────\r\n\r\n /** Make an authenticated GET request to SMSTunnel */\r\n private async apiGet(path: string): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n\r\n /** Make an authenticated POST request to SMSTunnel */\r\n private async apiPost(path: string, body: any): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n}\r\n","export const SMSTUNNEL_OPTIONS = 'SMSTUNNEL_OPTIONS';\r\n\r\nexport const SMSTUNNEL_DEFAULT_PREFIX = 'smstunnel';\r\n\r\n/**\r\n * Public paths that should be excluded from auth guards.\r\n * Use with your global guard to skip auth on callback/polling routes.\r\n *\r\n * Example (NestJS):\r\n * if (SMSTUNNEL_PUBLIC_PATHS.some(p => request.url.includes(p))) return true;\r\n */\r\nexport const SMSTUNNEL_PUBLIC_PATHS = [\r\n '/smstunnel/pairing-status/',\r\n '/smstunnel/callback',\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n];\r\n\r\n/**\r\n * Returns public paths with a custom prefix.\r\n */\r\nexport function getSmsTunnelPublicPaths(prefix: string): string[] {\r\n return [\r\n `/${prefix}/pairing-status/`,\r\n `/${prefix}/callback`,\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n ];\r\n}\r\n","import {\r\n Controller,\r\n Post,\r\n Get,\r\n Body,\r\n Param,\r\n Inject,\r\n Logger,\r\n} from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n} from './smstunnel.interfaces';\r\n\r\n/**\r\n * Decorator to mark routes as public (no auth).\r\n * If you have your own @Public() decorator, the module re-exports\r\n * SMSTUNNEL_PUBLIC_PATHS so you can exclude them in your guard.\r\n */\r\nfunction SmsTunnelPublic() {\r\n // We use Reflect metadata so consumers can detect public routes\r\n return (target: any, key: string, descriptor: PropertyDescriptor) => {\r\n Reflect.defineMetadata('isPublic', true, descriptor.value);\r\n return descriptor;\r\n };\r\n}\r\n\r\n@Controller()\r\nexport class SmsTunnelController {\r\n private readonly logger = new Logger(SmsTunnelController.name);\r\n private readonly prefix: string;\r\n\r\n constructor(\r\n private readonly smsTunnelService: SmsTunnelService,\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {\r\n this.prefix = 'smstunnel';\r\n }\r\n\r\n /**\r\n * Get current pairing status.\r\n * Route: GET /{prefix}/status\r\n */\r\n @Get('smstunnel/status')\r\n async getStatus() {\r\n return this.smsTunnelService.getStatus();\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel.\r\n * Route: POST /{prefix}/create-token\r\n */\r\n @Post('smstunnel/create-token')\r\n async createToken() {\r\n return this.smsTunnelService.createPairingToken();\r\n }\r\n\r\n /**\r\n * Proxy polling to SMSTunnel (avoids CORS issues).\r\n * This should be public (no auth) - just proxying SMSTunnel's endpoint.\r\n * Route: GET /{prefix}/pairing-status/:token\r\n */\r\n @SmsTunnelPublic()\r\n @Get('smstunnel/pairing-status/:token')\r\n async pairingStatus(@Param('token') token: string) {\r\n return this.smsTunnelService.getPairingStatus(token);\r\n }\r\n\r\n /**\r\n * Callback from SMSTunnel after pairing completes.\r\n * Route: POST /{prefix}/callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('smstunnel/callback')\r\n async pairingCallback(@Body() body: PairingCallbackBody) {\r\n this.logger.log(\r\n `SMSTunnel callback: event=${body.event}, token=${body.token?.substring(0, 8)}...`,\r\n );\r\n const success = await this.smsTunnelService.handlePairingCallback(body);\r\n return { success };\r\n }\r\n\r\n /**\r\n * WordPress-compatible legacy callback.\r\n * Route: POST /wp-json/smstunnel/v1/setup-callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('wp-json/smstunnel/v1/setup-callback')\r\n async setupCallbackWp(@Body() body: LegacyCallbackBody) {\r\n this.logger.log(`SMSTunnel legacy callback. Status: ${body.status || 'unknown'}`);\r\n const success = await this.smsTunnelService.handleLegacyCallback(\r\n body.site_token,\r\n body.api_key,\r\n body.device_name,\r\n );\r\n return { success };\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Route: POST /{prefix}/pair-with-key\r\n */\r\n @Post('smstunnel/pair-with-key')\r\n async pairWithKey(@Body() body: { apiKey: string }) {\r\n return this.smsTunnelService.pairWithApiKey(body.apiKey);\r\n }\r\n\r\n /**\r\n * Unpair the connected device.\r\n * Route: POST /{prefix}/unpair\r\n */\r\n @Post('smstunnel/unpair')\r\n async unpair() {\r\n await this.smsTunnelService.unpair();\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Send an SMS via SMSTunnel.\r\n * Route: POST /{prefix}/send\r\n */\r\n @Post('smstunnel/send')\r\n async sendSms(@Body() body: { to: string; message: string }) {\r\n return this.smsTunnelService.sendSms(body.to, body.message);\r\n }\r\n\r\n /**\r\n * Update server URL configuration.\r\n * Route: POST /{prefix}/update-config\r\n */\r\n @Post('smstunnel/update-config')\r\n async updateConfig(@Body() body: { serverUrl: string }) {\r\n await this.smsTunnelService.updateServerUrl(body.serverUrl);\r\n return { success: true };\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send a 2FA SMS.\r\n * Route: POST /{prefix}/send-2fa\r\n */\r\n @Post('smstunnel/send-2fa')\r\n async send2fa(@Body() body: { to: string; code: string; template?: string }) {\r\n return this.smsTunnelService.send2fa(body.to, body.code, body.template);\r\n }\r\n\r\n /**\r\n * Send bulk SMS.\r\n * Route: POST /{prefix}/send-bulk\r\n */\r\n @Post('smstunnel/send-bulk')\r\n async sendBulk(@Body() body: { messages: Array<{ to: string; message: string }> }) {\r\n return this.smsTunnelService.sendBulk(body.messages);\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n * Route: GET /{prefix}/sms-status/:messageId\r\n */\r\n @Get('smstunnel/sms-status/:messageId')\r\n async smsStatus(@Param('messageId') messageId: string) {\r\n return this.smsTunnelService.getSmsStatus(messageId);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n * Route: GET /{prefix}/received\r\n */\r\n @Get('smstunnel/received')\r\n async receivedSms() {\r\n return this.smsTunnelService.getReceivedSms();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel and stores it locally.\r\n * Route: POST /{prefix}/exchange-keys\r\n */\r\n @Post('smstunnel/exchange-keys')\r\n async exchangeKeys() {\r\n return this.smsTunnelService.exchangeKeys();\r\n }\r\n\r\n /**\r\n * Send encrypted SMS.\r\n * Route: POST /{prefix}/send-encrypted\r\n */\r\n @Post('smstunnel/send-encrypted')\r\n async sendEncrypted(\r\n @Body() body: { encryptedPayload: string; deviceId: string; is2FA?: boolean },\r\n ) {\r\n return this.smsTunnelService.sendEncryptedSms(\r\n body.encryptedPayload,\r\n body.deviceId,\r\n body.is2FA,\r\n );\r\n }\r\n\r\n /**\r\n * List pairings.\r\n * Route: GET /{prefix}/pairings\r\n */\r\n @Get('smstunnel/pairings')\r\n async pairings() {\r\n return this.smsTunnelService.getPairings();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n * Route: GET /{prefix}/devices\r\n */\r\n @Get('smstunnel/devices')\r\n async devices() {\r\n return this.smsTunnelService.getDevices();\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n * Route: GET /{prefix}/usage\r\n */\r\n @Get('smstunnel/usage')\r\n async usage() {\r\n return this.smsTunnelService.getUsage();\r\n }\r\n}\r\n"],"mappings":"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","names":["Inject","Logger","Logger","Inject"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/smstunnel.module.ts","../../src/server/smstunnel.service.ts","../../src/server/smstunnel.constants.ts","../../src/server/smstunnel.controller.ts"],"sourcesContent":["import { DynamicModule, Module, Provider } from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SmsTunnelController } from './smstunnel.controller';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n} from './smstunnel.interfaces';\r\n\r\n@Module({})\r\nexport class SmsTunnelModule {\r\n static forRoot(options: SmsTunnelModuleOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useValue: options,\r\n };\r\n\r\n const controllers = [SmsTunnelController];\r\n if (!options.enableLegacyCallback) {\r\n // Legacy callback is always registered but won't match\r\n // if the consumer doesn't enable it - no-op\r\n }\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers,\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n\r\n static forRootAsync(asyncOptions: SmsTunnelModuleAsyncOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useFactory: asyncOptions.useFactory,\r\n inject: asyncOptions.inject || [],\r\n };\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers: [SmsTunnelController],\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n}\r\n","import { Inject, Injectable, Logger } from '@nestjs/common';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n PairWithApiKeyResult,\r\n ExchangeKeysResult,\r\n SaasActivateResult,\r\n} from './smstunnel.interfaces';\r\n\r\n@Injectable()\r\nexport class SmsTunnelService {\r\n private readonly logger = new Logger(SmsTunnelService.name);\r\n\r\n constructor(\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {}\r\n\r\n /**\r\n * Get current pairing status.\r\n */\r\n async getStatus(): Promise<SmsTunnelStatus> {\r\n const config = await this.options.storage.getConfig();\r\n return {\r\n paired: !!config.apiKey,\r\n serverUrl: config.serverUrl,\r\n deviceName: config.deviceName,\r\n e2eEnabled: !!config.devicePublicKey,\r\n deviceFingerprint: config.deviceFingerprint,\r\n };\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel server.\r\n * Uses Enterprise/SaaS flow when enterpriseApiKey is configured,\r\n * otherwise falls back to public-create flow.\r\n */\r\n async createPairingToken(): Promise<CreateTokenResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n if (this.options.enterpriseApiKey) {\r\n return this.createPairingTokenEnterprise(serverUrl);\r\n }\r\n\r\n return this.createPairingTokenPublic(serverUrl);\r\n }\r\n\r\n /**\r\n * Public flow: POST /api/v1/pairing/public-create\r\n */\r\n private async createPairingTokenPublic(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/public-create`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n source: 'api',\r\n displayName: this.options.displayName || 'App',\r\n displayUrl: this.options.displayUrl || this.options.callbackBaseUrl,\r\n context: { callbackUrl },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel create token failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n\r\n await this.options.storage.updateConfig({ siteToken: data.token });\r\n\r\n this.logger.log(`Pairing token created: ${data.token?.substring(0, 12)}...`);\r\n\r\n return {\r\n success: true,\r\n token: data.token,\r\n pairingCode: data.pairingCode,\r\n qrData: data.qrData,\r\n expiresAt: data.expiresAt,\r\n pollUrl: data.pollUrl || `${serverUrl}/api/v1/pairing/${data.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel create token error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Enterprise/SaaS flow: POST /api/v1/saas/activate\r\n * Creates sub-accounts under the Enterprise account.\r\n */\r\n private async createPairingTokenEnterprise(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/saas/activate`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': this.options.enterpriseApiKey!,\r\n },\r\n body: JSON.stringify({\r\n clientName: this.options.displayName || 'App',\r\n externalId: this.options.externalId || 'default',\r\n callbackUrl,\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel SaaS activate failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: SaasActivateResult = await res.json();\r\n\r\n if (!data.success || !data.activation) {\r\n return { success: false, error: data.error || 'SaaS activation failed' };\r\n }\r\n\r\n // Save the activation token as siteToken\r\n const configUpdate: Record<string, any> = { siteToken: data.activation.token };\r\n\r\n // If the server returned an API key, save it too\r\n if (data.apiKey?.key) {\r\n configUpdate.apiKey = data.apiKey.key;\r\n }\r\n\r\n await this.options.storage.updateConfig(configUpdate);\r\n\r\n this.logger.log(\r\n `SaaS pairing token created: ${data.activation.token?.substring(0, 12)}... (isNew: ${data.isNew})`,\r\n );\r\n\r\n return {\r\n success: true,\r\n token: data.activation.token,\r\n pairingCode: data.activation.pairingCode,\r\n qrData: data.activation.qrData,\r\n expiresAt: data.activation.expiresAt,\r\n pollUrl: `${serverUrl}/api/v1/pairing/${data.activation.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel SaaS activate error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Check pairing status by polling SMSTunnel.\r\n */\r\n async getPairingStatus(token: string): Promise<PairingStatusResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { status: 'error', error: 'SMSTunnel server URL not configured' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/${token}`);\r\n if (!res.ok) {\r\n return { status: 'expired' };\r\n }\r\n return (await res.json()) as PairingStatusResult;\r\n } catch {\r\n return { status: 'error', error: 'Could not contact SMSTunnel' };\r\n }\r\n }\r\n\r\n /**\r\n * Handle callback from SMSTunnel after pairing completes.\r\n */\r\n async handlePairingCallback(body: PairingCallbackBody): Promise<boolean> {\r\n if (body.event !== 'pairing_completed') {\r\n this.logger.warn(`Unknown callback event: ${body.event}`);\r\n return false;\r\n }\r\n\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken && config.siteToken !== body.token) {\r\n this.logger.warn(\r\n `Callback token mismatch: expected ${config.siteToken?.substring(0, 8)}, got ${body.token?.substring(0, 8)}`,\r\n );\r\n return false;\r\n }\r\n\r\n const update: Record<string, any> = {\r\n apiKey: body.apiKey,\r\n deviceName: body.deviceName || 'Android Phone',\r\n };\r\n if (body.deviceId) {\r\n update.deviceId = body.deviceId;\r\n }\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(\r\n `SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`,\r\n );\r\n\r\n // Auto-exchange keys after pairing\r\n if (body.deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Handle legacy WordPress-compatible callback.\r\n */\r\n async handleLegacyCallback(\r\n siteToken: string,\r\n apiKey: string,\r\n deviceName?: string,\r\n ): Promise<boolean> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken !== siteToken) {\r\n this.logger.warn('Legacy callback: site_token mismatch');\r\n return false;\r\n }\r\n\r\n const update: Partial<{ apiKey: string; deviceName: string }> = { apiKey };\r\n if (deviceName) update.deviceName = deviceName;\r\n\r\n await this.options.storage.updateConfig(update);\r\n this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || 'unknown'}`);\r\n return true;\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Validates the key by calling /api/v1/devices, then saves it.\r\n */\r\n async pairWithApiKey(apiKey: string): Promise<PairWithApiKeyResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n if (res.status === 401 || res.status === 403) {\r\n return { success: false, error: 'API key invalid or expired.' };\r\n }\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n const devices = data?.devices || [];\r\n const device = devices[0];\r\n const deviceName = device?.name || 'Android Phone';\r\n const deviceId = device?.id || device?._id;\r\n\r\n const update: Record<string, any> = { apiKey, deviceName };\r\n if (deviceId) update.deviceId = deviceId;\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(`Paired via API key. Device: ${deviceName}`);\r\n\r\n // Auto-exchange keys\r\n if (deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return { success: true, deviceName };\r\n } catch (err: any) {\r\n this.logger.error(`Pair with API key error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send SMS via SMSTunnel API.\r\n */\r\n async sendSms(to: string, message: string): Promise<SendSmsResult> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (!config.apiKey) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel is not configured. Pair a device first.',\r\n };\r\n }\r\n\r\n if (!config.serverUrl) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel server URL is not configured.',\r\n };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/sms/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n\r\n const data: any = await res.json();\r\n\r\n if (data.success) {\r\n this.logger.log(`SMS sent via SMSTunnel: ${data.messageId} -> ${to}`);\r\n return { success: true, messageId: data.messageId };\r\n }\r\n\r\n return {\r\n success: false,\r\n error: data.error || data.message || 'SMSTunnel returned an error',\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMS send error: ${err.message}`);\r\n return { success: false, error: `Could not send SMS: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Unpair - clear saved API key and device info.\r\n */\r\n async unpair(): Promise<void> {\r\n await this.options.storage.clearConfig();\r\n this.logger.log('SMSTunnel unpaired');\r\n }\r\n\r\n /**\r\n * Update server URL in config.\r\n */\r\n async updateServerUrl(serverUrl: string): Promise<void> {\r\n await this.options.storage.updateConfig({\r\n serverUrl: serverUrl.replace(/\\/$/, ''),\r\n });\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send 2FA SMS via SMSTunnel API.\r\n */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send-2fa', { to, code, template });\r\n }\r\n\r\n /**\r\n * Send bulk SMS via SMSTunnel API.\r\n */\r\n async sendBulk(\r\n messages: Array<{ to: string; message: string }>,\r\n ): Promise<{ success: boolean; results?: any[]; error?: string }> {\r\n return this.apiPost('/api/v1/sms/send-bulk', { messages });\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n */\r\n async getSmsStatus(messageId: string): Promise<any> {\r\n return this.apiGet(`/api/v1/sms/status/${messageId}`);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n */\r\n async getReceivedSms(): Promise<any> {\r\n return this.apiGet('/api/v1/sms/received');\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n */\r\n async getDevices(): Promise<any> {\r\n return this.apiGet('/api/v1/devices');\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n */\r\n async getUsage(): Promise<any> {\r\n return this.apiGet('/api/v1/account/usage');\r\n }\r\n\r\n // ─── E2E Encryption ──────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel server and stores it locally.\r\n * Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.\r\n */\r\n async exchangeKeys(): Promise<ExchangeKeysResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n // Resolve deviceId if we don't have it\r\n let deviceId = config.deviceId;\r\n if (!deviceId) {\r\n const listRes = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n if (!listRes.ok) {\r\n return { success: false, error: `Could not list devices: ${listRes.status}` };\r\n }\r\n const listData: any = await listRes.json();\r\n const devices = listData?.devices || [];\r\n if (devices.length === 0) {\r\n return { success: false, error: 'No paired device found.' };\r\n }\r\n deviceId = devices[0].id || devices[0]._id;\r\n await this.options.storage.updateConfig({ deviceId });\r\n }\r\n\r\n // Fetch device detail (includes publicKey)\r\n const res = await fetch(`${serverUrl}/api/v1/devices/${deviceId}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n return { success: false, error: `Could not fetch device: ${res.status}` };\r\n }\r\n\r\n const device: any = await res.json();\r\n\r\n if (!device.publicKey) {\r\n return { success: false, error: 'Device has no public key. Enable encryption in SMSTunnel app on the phone.' };\r\n }\r\n\r\n await this.options.storage.updateConfig({\r\n deviceId,\r\n devicePublicKey: device.publicKey,\r\n deviceFingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name || config.deviceName,\r\n });\r\n\r\n this.logger.log(`E2E keys exchanged. Fingerprint: ${device.publicKeyFingerprint}`);\r\n return {\r\n success: true,\r\n fingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`Key exchange error: ${err.message}`);\r\n return { success: false, error: `Key exchange failed: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send encrypted SMS via SMSTunnel API.\r\n * The payload is encrypted client-side with the device's RSA public key.\r\n */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send', {\r\n encrypted: true,\r\n encryptedPayload,\r\n deviceId,\r\n is2FA,\r\n });\r\n }\r\n\r\n /**\r\n * List pairings for the authenticated user.\r\n */\r\n async getPairings(): Promise<any> {\r\n return this.apiGet('/api/v1/pairings');\r\n }\r\n\r\n // ─── Internal helpers ──────────────────────────────\r\n\r\n /** Make an authenticated GET request to SMSTunnel */\r\n private async apiGet(path: string): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n\r\n /** Make an authenticated POST request to SMSTunnel */\r\n private async apiPost(path: string, body: any): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n}\r\n","export const SMSTUNNEL_OPTIONS = 'SMSTUNNEL_OPTIONS';\r\n\r\nexport const SMSTUNNEL_DEFAULT_PREFIX = 'smstunnel';\r\n\r\n/**\r\n * Public paths that should be excluded from auth guards.\r\n * Use with your global guard to skip auth on callback/polling routes.\r\n *\r\n * Example (NestJS):\r\n * if (SMSTUNNEL_PUBLIC_PATHS.some(p => request.url.includes(p))) return true;\r\n */\r\nexport const SMSTUNNEL_PUBLIC_PATHS = [\r\n '/smstunnel/pairing-status/',\r\n '/smstunnel/callback',\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n];\r\n\r\n/**\r\n * Returns public paths with a custom prefix.\r\n */\r\nexport function getSmsTunnelPublicPaths(prefix: string): string[] {\r\n return [\r\n `/${prefix}/pairing-status/`,\r\n `/${prefix}/callback`,\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n ];\r\n}\r\n","import {\r\n Controller,\r\n Post,\r\n Get,\r\n Body,\r\n Param,\r\n Inject,\r\n Logger,\r\n} from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n} from './smstunnel.interfaces';\r\n\r\n/**\r\n * Decorator to mark routes as public (no auth).\r\n * If you have your own @Public() decorator, the module re-exports\r\n * SMSTUNNEL_PUBLIC_PATHS so you can exclude them in your guard.\r\n */\r\nfunction SmsTunnelPublic() {\r\n // We use Reflect metadata so consumers can detect public routes\r\n return (target: any, key: string, descriptor: PropertyDescriptor) => {\r\n Reflect.defineMetadata('isPublic', true, descriptor.value);\r\n return descriptor;\r\n };\r\n}\r\n\r\n@Controller()\r\nexport class SmsTunnelController {\r\n private readonly logger = new Logger(SmsTunnelController.name);\r\n private readonly prefix: string;\r\n\r\n constructor(\r\n private readonly smsTunnelService: SmsTunnelService,\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {\r\n this.prefix = 'smstunnel';\r\n }\r\n\r\n /**\r\n * Get current pairing status.\r\n * Route: GET /{prefix}/status\r\n */\r\n @Get('smstunnel/status')\r\n async getStatus() {\r\n return this.smsTunnelService.getStatus();\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel.\r\n * Route: POST /{prefix}/create-token\r\n */\r\n @Post('smstunnel/create-token')\r\n async createToken() {\r\n return this.smsTunnelService.createPairingToken();\r\n }\r\n\r\n /**\r\n * Proxy polling to SMSTunnel (avoids CORS issues).\r\n * This should be public (no auth) - just proxying SMSTunnel's endpoint.\r\n * Route: GET /{prefix}/pairing-status/:token\r\n */\r\n @SmsTunnelPublic()\r\n @Get('smstunnel/pairing-status/:token')\r\n async pairingStatus(@Param('token') token: string) {\r\n return this.smsTunnelService.getPairingStatus(token);\r\n }\r\n\r\n /**\r\n * Callback from SMSTunnel after pairing completes.\r\n * Route: POST /{prefix}/callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('smstunnel/callback')\r\n async pairingCallback(@Body() body: PairingCallbackBody) {\r\n this.logger.log(\r\n `SMSTunnel callback: event=${body.event}, token=${body.token?.substring(0, 8)}...`,\r\n );\r\n const success = await this.smsTunnelService.handlePairingCallback(body);\r\n return { success };\r\n }\r\n\r\n /**\r\n * WordPress-compatible legacy callback.\r\n * Route: POST /wp-json/smstunnel/v1/setup-callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('wp-json/smstunnel/v1/setup-callback')\r\n async setupCallbackWp(@Body() body: LegacyCallbackBody) {\r\n this.logger.log(`SMSTunnel legacy callback. Status: ${body.status || 'unknown'}`);\r\n const success = await this.smsTunnelService.handleLegacyCallback(\r\n body.site_token,\r\n body.api_key,\r\n body.device_name,\r\n );\r\n return { success };\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Route: POST /{prefix}/pair-with-key\r\n */\r\n @Post('smstunnel/pair-with-key')\r\n async pairWithKey(@Body() body: { apiKey: string }) {\r\n return this.smsTunnelService.pairWithApiKey(body.apiKey);\r\n }\r\n\r\n /**\r\n * Unpair the connected device.\r\n * Route: POST /{prefix}/unpair\r\n */\r\n @Post('smstunnel/unpair')\r\n async unpair() {\r\n await this.smsTunnelService.unpair();\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Send an SMS via SMSTunnel.\r\n * Route: POST /{prefix}/send\r\n */\r\n @Post('smstunnel/send')\r\n async sendSms(@Body() body: { to: string; message: string }) {\r\n return this.smsTunnelService.sendSms(body.to, body.message);\r\n }\r\n\r\n /**\r\n * Update server URL configuration.\r\n * Route: POST /{prefix}/update-config\r\n */\r\n @Post('smstunnel/update-config')\r\n async updateConfig(@Body() body: { serverUrl: string }) {\r\n await this.smsTunnelService.updateServerUrl(body.serverUrl);\r\n return { success: true };\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send a 2FA SMS.\r\n * Route: POST /{prefix}/send-2fa\r\n */\r\n @Post('smstunnel/send-2fa')\r\n async send2fa(@Body() body: { to: string; code: string; template?: string }) {\r\n return this.smsTunnelService.send2fa(body.to, body.code, body.template);\r\n }\r\n\r\n /**\r\n * Send bulk SMS.\r\n * Route: POST /{prefix}/send-bulk\r\n */\r\n @Post('smstunnel/send-bulk')\r\n async sendBulk(@Body() body: { messages: Array<{ to: string; message: string }> }) {\r\n return this.smsTunnelService.sendBulk(body.messages);\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n * Route: GET /{prefix}/sms-status/:messageId\r\n */\r\n @Get('smstunnel/sms-status/:messageId')\r\n async smsStatus(@Param('messageId') messageId: string) {\r\n return this.smsTunnelService.getSmsStatus(messageId);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n * Route: GET /{prefix}/received\r\n */\r\n @Get('smstunnel/received')\r\n async receivedSms() {\r\n return this.smsTunnelService.getReceivedSms();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel and stores it locally.\r\n * Route: POST /{prefix}/exchange-keys\r\n */\r\n @Post('smstunnel/exchange-keys')\r\n async exchangeKeys() {\r\n return this.smsTunnelService.exchangeKeys();\r\n }\r\n\r\n /**\r\n * Send encrypted SMS.\r\n * Route: POST /{prefix}/send-encrypted\r\n */\r\n @Post('smstunnel/send-encrypted')\r\n async sendEncrypted(\r\n @Body() body: { encryptedPayload: string; deviceId: string; is2FA?: boolean },\r\n ) {\r\n return this.smsTunnelService.sendEncryptedSms(\r\n body.encryptedPayload,\r\n body.deviceId,\r\n body.is2FA,\r\n );\r\n }\r\n\r\n /**\r\n * List pairings.\r\n * Route: GET /{prefix}/pairings\r\n */\r\n @Get('smstunnel/pairings')\r\n async pairings() {\r\n return this.smsTunnelService.getPairings();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n * Route: GET /{prefix}/devices\r\n */\r\n @Get('smstunnel/devices')\r\n async devices() {\r\n return this.smsTunnelService.getDevices();\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n * Route: GET /{prefix}/usage\r\n */\r\n @Get('smstunnel/usage')\r\n async usage() {\r\n return this.smsTunnelService.getUsage();\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAwB,cAAwB;;;ACAhD,SAAS,QAAQ,YAAY,cAAc;;;ACApC,IAAM,oBAAoB;AAE1B,IAAM,2BAA2B;AASjC,IAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF;AAKO,SAAS,wBAAwB,QAA0B;AAChE,SAAO;AAAA,IACL,IAAI,MAAM;AAAA,IACV,IAAI,MAAM;AAAA,IACV;AAAA,EACF;AACF;;;ADXO,IAAM,mBAAN,MAAuB;AAAA,EAG5B,YAEmB,SACjB;AADiB;AAJnB,SAAiB,SAAS,IAAI,OAAO,iBAAiB,IAAI;AAAA,EAKvD;AAAA;AAAA;AAAA;AAAA,EAKH,MAAM,YAAsC;AAC1C,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AACpD,WAAO;AAAA,MACL,QAAQ,CAAC,CAAC,OAAO;AAAA,MACjB,WAAW,OAAO;AAAA,MAClB,YAAY,OAAO;AAAA,MACnB,YAAY,CAAC,CAAC,OAAO;AAAA,MACrB,mBAAmB,OAAO;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,qBAAiD;AACrD,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AACpD,QAAI,CAAC,OAAO,WAAW;AACrB,aAAO,EAAE,SAAS,OAAO,OAAO,0CAA0C;AAAA,IAC5E;AAEA,UAAM,YAAY,OAAO,UAAU,QAAQ,OAAO,EAAE;AAEpD,QAAI,KAAK,QAAQ,kBAAkB;AACjC,aAAO,KAAK,6BAA6B,SAAS;AAAA,IACpD;AAEA,WAAO,KAAK,yBAAyB,SAAS;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,yBAAyB,WAA+C;AACpF,UAAM,SAAS;AACf,UAAM,cAAc,GAAG,KAAK,QAAQ,gBAAgB,QAAQ,OAAO,EAAE,CAAC,IAAI,MAAM;AAEhF,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,SAAS,iCAAiC;AAAA,QACnE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,QAAQ;AAAA,UACR,aAAa,KAAK,QAAQ,eAAe;AAAA,UACzC,YAAY,KAAK,QAAQ,cAAc,KAAK,QAAQ;AAAA,UACpD,SAAS,EAAE,YAAY;AAAA,QACzB,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,UAAU,MAAM,IAAI,KAAK;AAC/B,aAAK,OAAO,KAAK,kCAAkC,IAAI,MAAM,IAAI,OAAO,EAAE;AAC1E,eAAO,EAAE,SAAS,OAAO,OAAO,6BAA6B,IAAI,MAAM,GAAG;AAAA,MAC5E;AAEA,YAAM,OAAY,MAAM,IAAI,KAAK;AAEjC,YAAM,KAAK,QAAQ,QAAQ,aAAa,EAAE,WAAW,KAAK,MAAM,CAAC;AAEjE,WAAK,OAAO,IAAI,0BAA0B,KAAK,OAAO,UAAU,GAAG,EAAE,CAAC,KAAK;AAE3E,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,QAAQ,KAAK;AAAA,QACb,WAAW,KAAK;AAAA,QAChB,SAAS,KAAK,WAAW,GAAG,SAAS,mBAAmB,KAAK,KAAK;AAAA,MACpE;AAAA,IACF,SAAS,KAAU;AACjB,WAAK,OAAO,MAAM,iCAAiC,IAAI,OAAO,EAAE;AAChE,aAAO,EAAE,SAAS,OAAO,OAAO,mCAAmC,IAAI,OAAO,GAAG;AAAA,IACnF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,6BAA6B,WAA+C;AACxF,UAAM,SAAS;AACf,UAAM,cAAc,GAAG,KAAK,QAAQ,gBAAgB,QAAQ,OAAO,EAAE,CAAC,IAAI,MAAM;AAEhF,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,SAAS,yBAAyB;AAAA,QAC3D,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,KAAK,QAAQ;AAAA,QAC5B;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,YAAY,KAAK,QAAQ,eAAe;AAAA,UACxC,YAAY,KAAK,QAAQ,cAAc;AAAA,UACvC;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,UAAU,MAAM,IAAI,KAAK;AAC/B,aAAK,OAAO,KAAK,mCAAmC,IAAI,MAAM,IAAI,OAAO,EAAE;AAC3E,eAAO,EAAE,SAAS,OAAO,OAAO,6BAA6B,IAAI,MAAM,GAAG;AAAA,MAC5E;AAEA,YAAM,OAA2B,MAAM,IAAI,KAAK;AAEhD,UAAI,CAAC,KAAK,WAAW,CAAC,KAAK,YAAY;AACrC,eAAO,EAAE,SAAS,OAAO,OAAO,KAAK,SAAS,yBAAyB;AAAA,MACzE;AAGA,YAAM,eAAoC,EAAE,WAAW,KAAK,WAAW,MAAM;AAG7E,UAAI,KAAK,QAAQ,KAAK;AACpB,qBAAa,SAAS,KAAK,OAAO;AAAA,MACpC;AAEA,YAAM,KAAK,QAAQ,QAAQ,aAAa,YAAY;AAEpD,WAAK,OAAO;AAAA,QACV,+BAA+B,KAAK,WAAW,OAAO,UAAU,GAAG,EAAE,CAAC,eAAe,KAAK,KAAK;AAAA,MACjG;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,KAAK,WAAW;AAAA,QACvB,aAAa,KAAK,WAAW;AAAA,QAC7B,QAAQ,KAAK,WAAW;AAAA,QACxB,WAAW,KAAK,WAAW;AAAA,QAC3B,SAAS,GAAG,SAAS,mBAAmB,KAAK,WAAW,KAAK;AAAA,MAC/D;AAAA,IACF,SAAS,KAAU;AACjB,WAAK,OAAO,MAAM,kCAAkC,IAAI,OAAO,EAAE;AACjE,aAAO,EAAE,SAAS,OAAO,OAAO,mCAAmC,IAAI,OAAO,GAAG;AAAA,IACnF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,OAA6C;AAClE,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AACpD,QAAI,CAAC,OAAO,WAAW;AACrB,aAAO,EAAE,QAAQ,SAAS,OAAO,sCAAsC;AAAA,IACzE;AAEA,UAAM,YAAY,OAAO,UAAU,QAAQ,OAAO,EAAE;AAEpD,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,SAAS,mBAAmB,KAAK,EAAE;AAC9D,UAAI,CAAC,IAAI,IAAI;AACX,eAAO,EAAE,QAAQ,UAAU;AAAA,MAC7B;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB,QAAQ;AACN,aAAO,EAAE,QAAQ,SAAS,OAAO,8BAA8B;AAAA,IACjE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,sBAAsB,MAA6C;AACvE,QAAI,KAAK,UAAU,qBAAqB;AACtC,WAAK,OAAO,KAAK,2BAA2B,KAAK,KAAK,EAAE;AACxD,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AAEpD,QAAI,OAAO,aAAa,OAAO,cAAc,KAAK,OAAO;AACvD,WAAK,OAAO;AAAA,QACV,qCAAqC,OAAO,WAAW,UAAU,GAAG,CAAC,CAAC,SAAS,KAAK,OAAO,UAAU,GAAG,CAAC,CAAC;AAAA,MAC5G;AACA,aAAO;AAAA,IACT;AAEA,UAAM,SAA8B;AAAA,MAClC,QAAQ,KAAK;AAAA,MACb,YAAY,KAAK,cAAc;AAAA,IACjC;AACA,QAAI,KAAK,UAAU;AACjB,aAAO,WAAW,KAAK;AAAA,IACzB;AACA,UAAM,KAAK,QAAQ,QAAQ,aAAa,MAAM;AAE9C,SAAK,OAAO;AAAA,MACV,6BAA6B,KAAK,UAAU,cAAc,KAAK,QAAQ,UAAU,GAAG,EAAE,CAAC;AAAA,IACzF;AAGA,QAAI,KAAK,UAAU;AACjB,WAAK,aAAa,EAAE;AAAA,QAAM,CAAC,QACzB,KAAK,OAAO,KAAK,6BAA6B,IAAI,OAAO,EAAE;AAAA,MAC7D;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBACJ,WACA,QACA,YACkB;AAClB,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AAEpD,QAAI,OAAO,cAAc,WAAW;AAClC,WAAK,OAAO,KAAK,sCAAsC;AACvD,aAAO;AAAA,IACT;AAEA,UAAM,SAA0D,EAAE,OAAO;AACzE,QAAI,WAAY,QAAO,aAAa;AAEpC,UAAM,KAAK,QAAQ,QAAQ,aAAa,MAAM;AAC9C,SAAK,OAAO,IAAI,sCAAsC,cAAc,SAAS,EAAE;AAC/E,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAe,QAA+C;AAClE,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AACpD,QAAI,CAAC,OAAO,WAAW;AACrB,aAAO,EAAE,SAAS,OAAO,OAAO,0CAA0C;AAAA,IAC5E;AAEA,UAAM,YAAY,OAAO,UAAU,QAAQ,OAAO,EAAE;AAEpD,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,SAAS,mBAAmB;AAAA,QACrD,SAAS,EAAE,aAAa,OAAO;AAAA,MACjC,CAAC;AAED,UAAI,CAAC,IAAI,IAAI;AACX,YAAI,IAAI,WAAW,OAAO,IAAI,WAAW,KAAK;AAC5C,iBAAO,EAAE,SAAS,OAAO,OAAO,8BAA8B;AAAA,QAChE;AACA,eAAO,EAAE,SAAS,OAAO,OAAO,6BAA6B,IAAI,MAAM,GAAG;AAAA,MAC5E;AAEA,YAAM,OAAY,MAAM,IAAI,KAAK;AACjC,YAAM,UAAU,MAAM,WAAW,CAAC;AAClC,YAAM,SAAS,QAAQ,CAAC;AACxB,YAAM,aAAa,QAAQ,QAAQ;AACnC,YAAM,WAAW,QAAQ,MAAM,QAAQ;AAEvC,YAAM,SAA8B,EAAE,QAAQ,WAAW;AACzD,UAAI,SAAU,QAAO,WAAW;AAChC,YAAM,KAAK,QAAQ,QAAQ,aAAa,MAAM;AAE9C,WAAK,OAAO,IAAI,+BAA+B,UAAU,EAAE;AAG3D,UAAI,UAAU;AACZ,aAAK,aAAa,EAAE;AAAA,UAAM,CAAC,QACzB,KAAK,OAAO,KAAK,6BAA6B,IAAI,OAAO,EAAE;AAAA,QAC7D;AAAA,MACF;AAEA,aAAO,EAAE,SAAS,MAAM,WAAW;AAAA,IACrC,SAAS,KAAU;AACjB,WAAK,OAAO,MAAM,4BAA4B,IAAI,OAAO,EAAE;AAC3D,aAAO,EAAE,SAAS,OAAO,OAAO,mCAAmC,IAAI,OAAO,GAAG;AAAA,IACnF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,IAAY,SAAyC;AACjE,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AAEpD,QAAI,CAAC,OAAO,QAAQ;AAClB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI,CAAC,OAAO,WAAW;AACrB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,YAAY,OAAO,UAAU,QAAQ,OAAO,EAAE;AAEpD,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,SAAS,oBAAoB;AAAA,QACtD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,OAAO;AAAA,QACtB;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,IAAI,QAAQ,CAAC;AAAA,MACtC,CAAC;AAED,YAAM,OAAY,MAAM,IAAI,KAAK;AAEjC,UAAI,KAAK,SAAS;AAChB,aAAK,OAAO,IAAI,2BAA2B,KAAK,SAAS,OAAO,EAAE,EAAE;AACpE,eAAO,EAAE,SAAS,MAAM,WAAW,KAAK,UAAU;AAAA,MACpD;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,KAAK,SAAS,KAAK,WAAW;AAAA,MACvC;AAAA,IACF,SAAS,KAAU;AACjB,WAAK,OAAO,MAAM,mBAAmB,IAAI,OAAO,EAAE;AAClD,aAAO,EAAE,SAAS,OAAO,OAAO,uBAAuB,IAAI,OAAO,GAAG;AAAA,IACvE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAwB;AAC5B,UAAM,KAAK,QAAQ,QAAQ,YAAY;AACvC,SAAK,OAAO,IAAI,oBAAoB;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,WAAkC;AACtD,UAAM,KAAK,QAAQ,QAAQ,aAAa;AAAA,MACtC,WAAW,UAAU,QAAQ,OAAO,EAAE;AAAA,IACxC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ,IAAY,MAAc,UAA2C;AACjF,WAAO,KAAK,QAAQ,wBAAwB,EAAE,IAAI,MAAM,SAAS,CAAC;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SACJ,UACgE;AAChE,WAAO,KAAK,QAAQ,yBAAyB,EAAE,SAAS,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,WAAiC;AAClD,WAAO,KAAK,OAAO,sBAAsB,SAAS,EAAE;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAA+B;AACnC,WAAO,KAAK,OAAO,sBAAsB;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAA2B;AAC/B,WAAO,KAAK,OAAO,iBAAiB;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAyB;AAC7B,WAAO,KAAK,OAAO,uBAAuB;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,eAA4C;AAChD,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AACpD,QAAI,CAAC,OAAO,UAAU,CAAC,OAAO,WAAW;AACvC,aAAO,EAAE,SAAS,OAAO,OAAO,+BAA+B;AAAA,IACjE;AAEA,UAAM,YAAY,OAAO,UAAU,QAAQ,OAAO,EAAE;AAEpD,QAAI;AAEF,UAAI,WAAW,OAAO;AACtB,UAAI,CAAC,UAAU;AACb,cAAM,UAAU,MAAM,MAAM,GAAG,SAAS,mBAAmB;AAAA,UACzD,SAAS,EAAE,aAAa,OAAO,OAAO;AAAA,QACxC,CAAC;AACD,YAAI,CAAC,QAAQ,IAAI;AACf,iBAAO,EAAE,SAAS,OAAO,OAAO,2BAA2B,QAAQ,MAAM,GAAG;AAAA,QAC9E;AACA,cAAM,WAAgB,MAAM,QAAQ,KAAK;AACzC,cAAM,UAAU,UAAU,WAAW,CAAC;AACtC,YAAI,QAAQ,WAAW,GAAG;AACxB,iBAAO,EAAE,SAAS,OAAO,OAAO,0BAA0B;AAAA,QAC5D;AACA,mBAAW,QAAQ,CAAC,EAAE,MAAM,QAAQ,CAAC,EAAE;AACvC,cAAM,KAAK,QAAQ,QAAQ,aAAa,EAAE,SAAS,CAAC;AAAA,MACtD;AAGA,YAAM,MAAM,MAAM,MAAM,GAAG,SAAS,mBAAmB,QAAQ,IAAI;AAAA,QACjE,SAAS,EAAE,aAAa,OAAO,OAAO;AAAA,MACxC,CAAC;AAED,UAAI,CAAC,IAAI,IAAI;AACX,eAAO,EAAE,SAAS,OAAO,OAAO,2BAA2B,IAAI,MAAM,GAAG;AAAA,MAC1E;AAEA,YAAM,SAAc,MAAM,IAAI,KAAK;AAEnC,UAAI,CAAC,OAAO,WAAW;AACrB,eAAO,EAAE,SAAS,OAAO,OAAO,6EAA6E;AAAA,MAC/G;AAEA,YAAM,KAAK,QAAQ,QAAQ,aAAa;AAAA,QACtC;AAAA,QACA,iBAAiB,OAAO;AAAA,QACxB,mBAAmB,OAAO;AAAA,QAC1B,YAAY,OAAO,QAAQ,OAAO;AAAA,MACpC,CAAC;AAED,WAAK,OAAO,IAAI,oCAAoC,OAAO,oBAAoB,EAAE;AACjF,aAAO;AAAA,QACL,SAAS;AAAA,QACT,aAAa,OAAO;AAAA,QACpB,YAAY,OAAO;AAAA,MACrB;AAAA,IACF,SAAS,KAAU;AACjB,WAAK,OAAO,MAAM,uBAAuB,IAAI,OAAO,EAAE;AACtD,aAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,IAAI,OAAO,GAAG;AAAA,IACxE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBACJ,kBACA,UACA,QAAiB,OACO;AACxB,WAAO,KAAK,QAAQ,oBAAoB;AAAA,MACtC,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAA4B;AAChC,WAAO,KAAK,OAAO,kBAAkB;AAAA,EACvC;AAAA;AAAA;AAAA,EAKA,MAAc,OAAO,MAA4B;AAC/C,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AACpD,QAAI,CAAC,OAAO,UAAU,CAAC,OAAO,WAAW;AACvC,aAAO,EAAE,SAAS,OAAO,OAAO,+BAA+B;AAAA,IACjE;AACA,UAAM,YAAY,OAAO,UAAU,QAAQ,OAAO,EAAE;AACpD,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,IAAI,IAAI;AAAA,QAC7C,SAAS,EAAE,aAAa,OAAO,OAAO;AAAA,MACxC,CAAC;AACD,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB,SAAS,KAAU;AACjB,aAAO,EAAE,SAAS,OAAO,OAAO,IAAI,QAAQ;AAAA,IAC9C;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,QAAQ,MAAc,MAAyB;AAC3D,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,UAAU;AACpD,QAAI,CAAC,OAAO,UAAU,CAAC,OAAO,WAAW;AACvC,aAAO,EAAE,SAAS,OAAO,OAAO,+BAA+B;AAAA,IACjE;AACA,UAAM,YAAY,OAAO,UAAU,QAAQ,OAAO,EAAE;AACpD,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,IAAI,IAAI;AAAA,QAC7C,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,OAAO;AAAA,QACtB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AACD,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB,SAAS,KAAU;AACjB,aAAO,EAAE,SAAS,OAAO,OAAO,IAAI,QAAQ;AAAA,IAC9C;AAAA,EACF;AACF;AAnhBa,mBAAN;AAAA,EADN,WAAW;AAAA,EAKP,0BAAO,iBAAiB;AAAA,GAJhB;;;AEfb;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAAA;AAAA,EACA,UAAAC;AAAA,OACK;AAcP,SAAS,kBAAkB;AAEzB,SAAO,CAAC,QAAa,KAAa,eAAmC;AACnE,YAAQ,eAAe,YAAY,MAAM,WAAW,KAAK;AACzD,WAAO;AAAA,EACT;AACF;AAGO,IAAM,sBAAN,MAA0B;AAAA,EAI/B,YACmB,kBAEA,SACjB;AAHiB;AAEA;AANnB,SAAiB,SAAS,IAAIC,QAAO,oBAAoB,IAAI;AAQ3D,SAAK,SAAS;AAAA,EAChB;AAAA,EAOA,MAAM,YAAY;AAChB,WAAO,KAAK,iBAAiB,UAAU;AAAA,EACzC;AAAA,EAOA,MAAM,cAAc;AAClB,WAAO,KAAK,iBAAiB,mBAAmB;AAAA,EAClD;AAAA,EASA,MAAM,cAA8B,OAAe;AACjD,WAAO,KAAK,iBAAiB,iBAAiB,KAAK;AAAA,EACrD;AAAA,EAQA,MAAM,gBAAwB,MAA2B;AACvD,SAAK,OAAO;AAAA,MACV,6BAA6B,KAAK,KAAK,WAAW,KAAK,OAAO,UAAU,GAAG,CAAC,CAAC;AAAA,IAC/E;AACA,UAAM,UAAU,MAAM,KAAK,iBAAiB,sBAAsB,IAAI;AACtE,WAAO,EAAE,QAAQ;AAAA,EACnB;AAAA,EAQA,MAAM,gBAAwB,MAA0B;AACtD,SAAK,OAAO,IAAI,sCAAsC,KAAK,UAAU,SAAS,EAAE;AAChF,UAAM,UAAU,MAAM,KAAK,iBAAiB;AAAA,MAC1C,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AACA,WAAO,EAAE,QAAQ;AAAA,EACnB;AAAA,EAOA,MAAM,YAAoB,MAA0B;AAClD,WAAO,KAAK,iBAAiB,eAAe,KAAK,MAAM;AAAA,EACzD;AAAA,EAOA,MAAM,SAAS;AACb,UAAM,KAAK,iBAAiB,OAAO;AACnC,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA,EAOA,MAAM,QAAgB,MAAuC;AAC3D,WAAO,KAAK,iBAAiB,QAAQ,KAAK,IAAI,KAAK,OAAO;AAAA,EAC5D;AAAA,EAOA,MAAM,aAAqB,MAA6B;AACtD,UAAM,KAAK,iBAAiB,gBAAgB,KAAK,SAAS;AAC1D,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA,EASA,MAAM,QAAgB,MAAuD;AAC3E,WAAO,KAAK,iBAAiB,QAAQ,KAAK,IAAI,KAAK,MAAM,KAAK,QAAQ;AAAA,EACxE;AAAA,EAOA,MAAM,SAAiB,MAA4D;AACjF,WAAO,KAAK,iBAAiB,SAAS,KAAK,QAAQ;AAAA,EACrD;AAAA,EAOA,MAAM,UAA8B,WAAmB;AACrD,WAAO,KAAK,iBAAiB,aAAa,SAAS;AAAA,EACrD;AAAA,EAOA,MAAM,cAAc;AAClB,WAAO,KAAK,iBAAiB,eAAe;AAAA,EAC9C;AAAA,EAUA,MAAM,eAAe;AACnB,WAAO,KAAK,iBAAiB,aAAa;AAAA,EAC5C;AAAA,EAOA,MAAM,cACI,MACR;AACA,WAAO,KAAK,iBAAiB;AAAA,MAC3B,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AAAA,EACF;AAAA,EAOA,MAAM,WAAW;AACf,WAAO,KAAK,iBAAiB,YAAY;AAAA,EAC3C;AAAA,EASA,MAAM,UAAU;AACd,WAAO,KAAK,iBAAiB,WAAW;AAAA,EAC1C;AAAA,EAOA,MAAM,QAAQ;AACZ,WAAO,KAAK,iBAAiB,SAAS;AAAA,EACxC;AACF;AAzLQ;AAAA,EADL,IAAI,kBAAkB;AAAA,GAhBZ,oBAiBL;AASA;AAAA,EADL,KAAK,wBAAwB;AAAA,GAzBnB,oBA0BL;AAWA;AAAA,EAFL,gBAAgB;AAAA,EAChB,IAAI,iCAAiC;AAAA,EACjB,yBAAM,OAAO;AAAA,GArCvB,oBAqCL;AAUA;AAAA,EAFL,gBAAgB;AAAA,EAChB,KAAK,oBAAoB;AAAA,EACH,wBAAK;AAAA,GA/CjB,oBA+CL;AAcA;AAAA,EAFL,gBAAgB;AAAA,EAChB,KAAK,qCAAqC;AAAA,EACpB,wBAAK;AAAA,GA7DjB,oBA6DL;AAeA;AAAA,EADL,KAAK,yBAAyB;AAAA,EACZ,wBAAK;AAAA,GA5Eb,oBA4EL;AASA;AAAA,EADL,KAAK,kBAAkB;AAAA,GApFb,oBAqFL;AAUA;AAAA,EADL,KAAK,gBAAgB;AAAA,EACP,wBAAK;AAAA,GA/FT,oBA+FL;AASA;AAAA,EADL,KAAK,yBAAyB;AAAA,EACX,wBAAK;AAAA,GAxGd,oBAwGL;AAYA;AAAA,EADL,KAAK,oBAAoB;AAAA,EACX,wBAAK;AAAA,GApHT,oBAoHL;AASA;AAAA,EADL,KAAK,qBAAqB;AAAA,EACX,wBAAK;AAAA,GA7HV,oBA6HL;AASA;AAAA,EADL,IAAI,iCAAiC;AAAA,EACrB,yBAAM,WAAW;AAAA,GAtIvB,oBAsIL;AASA;AAAA,EADL,IAAI,oBAAoB;AAAA,GA9Id,oBA+IL;AAYA;AAAA,EADL,KAAK,yBAAyB;AAAA,GA1JpB,oBA2JL;AASA;AAAA,EADL,KAAK,0BAA0B;AAAA,EAE7B,wBAAK;AAAA,GArKG,oBAoKL;AAeA;AAAA,EADL,IAAI,oBAAoB;AAAA,GAlLd,oBAmLL;AAWA;AAAA,EADL,IAAI,mBAAmB;AAAA,GA7Lb,oBA8LL;AASA;AAAA,EADL,IAAI,iBAAiB;AAAA,GAtMX,oBAuML;AAvMK,sBAAN;AAAA,EADN,WAAW;AAAA,EAOP,mBAAAC,QAAO,iBAAiB;AAAA,GANhB;;;AHrBN,IAAM,kBAAN,MAAsB;AAAA,EAC3B,OAAO,QAAQ,SAAgD;AAC7D,UAAM,kBAA4B;AAAA,MAChC,SAAS;AAAA,MACT,UAAU;AAAA,IACZ;AAEA,UAAM,cAAc,CAAC,mBAAmB;AACxC,QAAI,CAAC,QAAQ,sBAAsB;AAAA,IAGnC;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,MACA,WAAW,CAAC,iBAAiB,gBAAgB;AAAA,MAC7C,SAAS,CAAC,gBAAgB;AAAA,MAC1B,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EAEA,OAAO,aAAa,cAA0D;AAC5E,UAAM,kBAA4B;AAAA,MAChC,SAAS;AAAA,MACT,YAAY,aAAa;AAAA,MACzB,QAAQ,aAAa,UAAU,CAAC;AAAA,IAClC;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,aAAa,CAAC,mBAAmB;AAAA,MACjC,WAAW,CAAC,iBAAiB,gBAAgB;AAAA,MAC7C,SAAS,CAAC,gBAAgB;AAAA,MAC1B,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AArCa,kBAAN;AAAA,EADN,OAAO,CAAC,CAAC;AAAA,GACG;","names":["Inject","Logger","Logger","Inject"]}
|
package/dist/vue/index.d.mts
CHANGED
|
@@ -24,6 +24,8 @@ interface SmsTunnelLabels {
|
|
|
24
24
|
sendTestButton: string;
|
|
25
25
|
smsSentSuccess: string;
|
|
26
26
|
smsError: string;
|
|
27
|
+
pairingCodeLabel: string;
|
|
28
|
+
pairingCodeCopied: string;
|
|
27
29
|
}
|
|
28
30
|
declare const EN_LABELS: SmsTunnelLabels;
|
|
29
31
|
declare const RO_LABELS: SmsTunnelLabels;
|
|
@@ -143,6 +145,7 @@ declare function useSmsTunnel(options: UseSmsTunnelOptions): {
|
|
|
143
145
|
deviceName: vue.Ref<string, string>;
|
|
144
146
|
showQr: vue.Ref<boolean, boolean>;
|
|
145
147
|
qrData: vue.Ref<string, string>;
|
|
148
|
+
pairingCode: vue.Ref<string, string>;
|
|
146
149
|
generating: vue.Ref<boolean, boolean>;
|
|
147
150
|
polling: vue.Ref<boolean, boolean>;
|
|
148
151
|
error: vue.Ref<string, string>;
|
package/dist/vue/index.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ interface SmsTunnelLabels {
|
|
|
24
24
|
sendTestButton: string;
|
|
25
25
|
smsSentSuccess: string;
|
|
26
26
|
smsError: string;
|
|
27
|
+
pairingCodeLabel: string;
|
|
28
|
+
pairingCodeCopied: string;
|
|
27
29
|
}
|
|
28
30
|
declare const EN_LABELS: SmsTunnelLabels;
|
|
29
31
|
declare const RO_LABELS: SmsTunnelLabels;
|
|
@@ -143,6 +145,7 @@ declare function useSmsTunnel(options: UseSmsTunnelOptions): {
|
|
|
143
145
|
deviceName: vue.Ref<string, string>;
|
|
144
146
|
showQr: vue.Ref<boolean, boolean>;
|
|
145
147
|
qrData: vue.Ref<string, string>;
|
|
148
|
+
pairingCode: vue.Ref<string, string>;
|
|
146
149
|
generating: vue.Ref<boolean, boolean>;
|
|
147
150
|
polling: vue.Ref<boolean, boolean>;
|
|
148
151
|
error: vue.Ref<string, string>;
|
package/dist/vue/index.js
CHANGED
|
@@ -57,6 +57,7 @@ function useSmsTunnel(options) {
|
|
|
57
57
|
const deviceName = (0, import_vue.ref)("");
|
|
58
58
|
const showQr = (0, import_vue.ref)(false);
|
|
59
59
|
const qrData = (0, import_vue.ref)("");
|
|
60
|
+
const pairingCode = (0, import_vue.ref)("");
|
|
60
61
|
const generating = (0, import_vue.ref)(false);
|
|
61
62
|
const polling = (0, import_vue.ref)(false);
|
|
62
63
|
const error = (0, import_vue.ref)("");
|
|
@@ -133,6 +134,7 @@ function useSmsTunnel(options) {
|
|
|
133
134
|
}
|
|
134
135
|
pairingToken = data.token;
|
|
135
136
|
qrData.value = data.qrData;
|
|
137
|
+
pairingCode.value = data.pairingCode || "";
|
|
136
138
|
showQr.value = true;
|
|
137
139
|
polling.value = true;
|
|
138
140
|
} catch (err) {
|
|
@@ -147,6 +149,7 @@ function useSmsTunnel(options) {
|
|
|
147
149
|
showQr.value = false;
|
|
148
150
|
polling.value = false;
|
|
149
151
|
qrData.value = "";
|
|
152
|
+
pairingCode.value = "";
|
|
150
153
|
}
|
|
151
154
|
async function unpair() {
|
|
152
155
|
try {
|
|
@@ -238,6 +241,7 @@ function useSmsTunnel(options) {
|
|
|
238
241
|
deviceName,
|
|
239
242
|
showQr,
|
|
240
243
|
qrData,
|
|
244
|
+
pairingCode,
|
|
241
245
|
generating,
|
|
242
246
|
polling,
|
|
243
247
|
error,
|
|
@@ -322,7 +326,9 @@ var EN_LABELS = {
|
|
|
322
326
|
testMessagePlaceholder: "Message",
|
|
323
327
|
sendTestButton: "Send",
|
|
324
328
|
smsSentSuccess: "SMS sent successfully!",
|
|
325
|
-
smsError: "Error"
|
|
329
|
+
smsError: "Error",
|
|
330
|
+
pairingCodeLabel: "Or enter this code in the app:",
|
|
331
|
+
pairingCodeCopied: "Code copied!"
|
|
326
332
|
};
|
|
327
333
|
var RO_LABELS = {
|
|
328
334
|
title: "Configurare SMS (SMSTunnel)",
|
|
@@ -346,7 +352,9 @@ var RO_LABELS = {
|
|
|
346
352
|
testMessagePlaceholder: "Mesaj",
|
|
347
353
|
sendTestButton: "Trimite",
|
|
348
354
|
smsSentSuccess: "SMS trimis cu succes!",
|
|
349
|
-
smsError: "Eroare"
|
|
355
|
+
smsError: "Eroare",
|
|
356
|
+
pairingCodeLabel: "Sau introdu acest cod in aplicatie:",
|
|
357
|
+
pairingCodeCopied: "Cod copiat!"
|
|
350
358
|
};
|
|
351
359
|
|
|
352
360
|
// src/vue/SmsTunnelPairing.ts
|