@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.
@@ -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"]}
@@ -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":"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","names":["Inject","Logger","Logger","Inject"]}
@@ -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>;
@@ -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