{"version":3,"sources":["../src/providers/360dialog/index.ts"],"names":["noopLogger","HttpClient","RateLimiter","mapOutboundToCloudApi","ProviderError","response","getResponseBodyStream","parseCloudApiWebhook","verifyHmacSha256","UnsupportedFeatureError"],"mappings":"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","file":"chunk-KXKBQB3J.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// 360Dialog Provider — thin BSP wrapper over Cloud API\n//\n// Same payload format, same webhook format.\n// Only differences: base URL, auth header, and no webhook challenge.\n// ---------------------------------------------------------------------------\n\nimport type { WhatsAppProviderAdapter, ProviderFeature } from '../../types/provider.js'\nimport type { SendResult } from '../../types/common.js'\nimport type { OutboundMessage } from '../../types/messages.js'\nimport type { MediaUpload, MediaUploadResult, MediaUrlResult, MediaDownloadResult } from '../../types/media.js'\nimport type { WebhookEvent } from '../../types/webhooks.js'\nimport type { Dialog360Config, ClientOptions } from '../../types/config.js'\nimport type { CloudApiSendResponse, CloudApiMediaUploadResponse, CloudApiMediaUrlResponse } from '../cloud-api/types.js'\nimport { HttpClient } from '../../core/http.js'\nimport { RateLimiter } from '../../core/rate-limiter.js'\nimport { noopLogger, type Logger } from '../../core/logger.js'\nimport { UnsupportedFeatureError, ProviderError } from '../../core/errors.js'\nimport { mapOutboundToCloudApi } from '../cloud-api/mapper.js'\nimport { parseCloudApiWebhook } from '../cloud-api/webhook-parser.js'\nimport { getResponseBodyStream } from '../cloud-api/index.js'\nimport { verifyHmacSha256 } from '../../utils/crypto.js'\n\nconst DEFAULT_BASE_URL = 'https://waba-v2.360dialog.io'\n\n/** 360Dialog supports same features as Cloud API, minus webhook challenge */\nconst SUPPORTED_FEATURES = new Set<ProviderFeature>([\n  'interactive.button',\n  'interactive.list',\n  'media.upload',\n  'media.download',\n  'media.delete',\n  'reaction',\n  'read_receipts',\n  'sticker',\n  'location',\n  'contacts',\n  'webhook.signature_verification',\n])\n\nexport class Dialog360Provider implements WhatsAppProviderAdapter {\n  readonly name = '360dialog' as const\n\n  private readonly config: Dialog360Config\n  private readonly options: ClientOptions\n  private readonly http: HttpClient\n  private readonly logger: Logger\n\n  constructor(config: Dialog360Config, options: ClientOptions = {}) {\n    this.config = config\n    this.options = options\n    this.logger = options.logger ?? noopLogger\n\n    this.http = new HttpClient({\n      baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n      defaultHeaders: {\n        'D360-API-KEY': config.apiKey,\n      },\n      timeout: options.timeout ?? 30_000,\n      provider: '360dialog',\n      logger: this.logger,\n      rateLimiter: new RateLimiter(options.rateLimit),\n      retry: options.retry ?? {},\n      hooks: options.hooks,\n    })\n  }\n\n  // -- Messaging (reuses Cloud API mapper) --------------------------------\n\n  async sendMessage(message: OutboundMessage): Promise<SendResult> {\n    const payload = mapOutboundToCloudApi(message)\n\n    const response = await this.http.request<CloudApiSendResponse>({\n      method: 'POST',\n      path: '/messages',\n      body: payload,\n    })\n\n    const messageId = response.data.messages[0]?.id ?? ''\n\n    if (!messageId) {\n      throw new ProviderError({\n        message: 'Provider returned no message ID — the send may not have succeeded',\n        provider: this.name,\n        statusCode: response.status,\n        raw: response.data,\n      })\n    }\n\n    return {\n      messageId,\n      provider: this.name,\n      ...(this.options.includeRawResponse ? { raw: response.data } : {}),\n    }\n  }\n\n  async markAsRead(messageId: string): Promise<void> {\n    await this.http.request({\n      method: 'POST',\n      path: '/messages',\n      body: {\n        messaging_product: 'whatsapp',\n        status: 'read',\n        message_id: messageId,\n      },\n    })\n  }\n\n  // -- Media --------------------------------------------------------------\n\n  async uploadMedia(params: MediaUpload): Promise<MediaUploadResult> {\n    const formData = new FormData()\n    formData.set('messaging_product', 'whatsapp')\n    formData.set('type', params.mimeType)\n\n    if (params.file instanceof Blob) {\n      formData.set('file', params.file, params.filename ?? 'file')\n    } else if (params.file instanceof ReadableStream) {\n      // The ReadableStream branch still buffers the whole stream into memory via\n      // `new Response(stream).blob()` — unavoidable with fetch FormData. Callers\n      // with very large media should prefer URL-based sends where supported.\n      const response = new Response(params.file)\n      const blob = await response.blob()\n      formData.set('file', blob, params.filename ?? 'file')\n    } else {\n      // Uint8Array — pass the view directly. Blob already copies the bytes, so an\n      // extra `.slice()` here would just double peak memory for no benefit. The\n      // cast is type-only (Uint8Array<ArrayBufferLike> → BlobPart); it copies nothing.\n      const blob = new Blob([params.file as BlobPart], { type: params.mimeType })\n      formData.set('file', blob, params.filename ?? 'file')\n    }\n\n    const response = await this.http.uploadRequest<CloudApiMediaUploadResponse>(\n      '/media',\n      formData,\n    )\n\n    return { id: response.data.id }\n  }\n\n  async getMediaUrl(mediaId: string): Promise<MediaUrlResult> {\n    const response = await this.http.request<CloudApiMediaUrlResponse>({\n      method: 'GET',\n      path: `/${mediaId}`,\n    })\n\n    return {\n      url: response.data.url,\n      mimeType: response.data.mime_type,\n      sha256: response.data.sha256,\n      fileSize: response.data.file_size ? parseInt(response.data.file_size, 10) : undefined,\n    }\n  }\n\n  async downloadMedia(mediaIdOrUrl: string): Promise<MediaDownloadResult> {\n    let downloadUrl: string\n    let expectedMimeType: string | undefined\n\n    if (mediaIdOrUrl.startsWith('http://') || mediaIdOrUrl.startsWith('https://')) {\n      downloadUrl = mediaIdOrUrl\n    } else {\n      const mediaInfo = await this.getMediaUrl(mediaIdOrUrl)\n      downloadUrl = mediaInfo.url\n      expectedMimeType = mediaInfo.mimeType\n    }\n\n    const response = await this.http.rawRequest({\n      method: 'GET',\n      path: downloadUrl,\n    })\n\n    const stream = getResponseBodyStream(response, this.name)\n\n    const mimeType =\n      response.headers.get('content-type') ??\n      expectedMimeType ??\n      'application/octet-stream'\n\n    const contentLength = response.headers.get('content-length')\n\n    return {\n      stream,\n      mimeType,\n      contentLength: contentLength ? parseInt(contentLength, 10) : undefined,\n    }\n  }\n\n  async deleteMedia(mediaId: string): Promise<void> {\n    await this.http.request({\n      method: 'DELETE',\n      path: `/${mediaId}`,\n    })\n  }\n\n  // -- Webhooks (reuses Cloud API parser) ---------------------------------\n\n  parseWebhook(body: unknown): WebhookEvent[] {\n    return parseCloudApiWebhook(body, '360dialog', { includeRaw: this.options.includeRawWebhook ?? false })\n  }\n\n  async verifyWebhookSignature(body: string, signature: string): Promise<boolean> {\n    if (!this.config.webhookSecret) {\n      this.logger.warn('verifyWebhookSignature called but no webhookSecret configured')\n      return false\n    }\n    return verifyHmacSha256(body, signature, this.config.webhookSecret)\n  }\n\n  // -- Capabilities -------------------------------------------------------\n\n  supports(feature: ProviderFeature): boolean {\n    return SUPPORTED_FEATURES.has(feature)\n  }\n\n  /** Release the rate limiter's pending timer and queued waiters. */\n  destroy(): void {\n    this.http.destroy()\n  }\n\n  protected assertSupported(feature: ProviderFeature): void {\n    if (!this.supports(feature)) {\n      throw new UnsupportedFeatureError({\n        message: `Feature \"${feature}\" is not supported by the ${this.name} provider`,\n        provider: this.name,\n      })\n    }\n  }\n}\n"]}