/** * Watch Tower Feature Module * * Monitors RGB invoices via the Watch Tower service. * Provides addToWatchTower and FCM token configuration. * * @module features/watch-tower/WatchTowerModule */ import axios, { AxiosError } from 'axios'; import type { AxiosInstance } from 'axios'; import type { IFeatureModule } from '../../types/IFeatureModule'; import type { Wallet } from '../../core/Wallet'; import type { Logger } from '../../utils/logger'; import type { WatchTowerConfig } from './types'; import type { AddToWatchTowerParams } from './types/WatchTowerRequest'; import type { AddToWatchTowerResponse } from './types/WatchTowerResponse'; import { getWatchTowerServiceUrl } from './types/WatchTowerConfig'; import { Feature } from '../../types/Feature'; export class WatchTowerModule implements IFeatureModule { readonly name = Feature.WATCH_TOWER; readonly config: WatchTowerConfig; private client: AxiosInstance | null = null; private fcmToken: string | undefined; private logger?: Logger; private ready = false; constructor(config: WatchTowerConfig) { this.config = { ...config }; } async initialize(_wallet?: Wallet, logger?: Logger): Promise { // Wallet parameter ignored - Watch Tower doesn't need RGB operations // Store logger (create child logger with WatchTower prefix) this.logger = logger?.child('WatchTower'); if (!this.config.apiKey) { throw new Error('Watch Tower requires apiKey in config'); } this.client = axios.create({ baseURL: getWatchTowerServiceUrl(this.config.environment), headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.config.apiKey}`, }, }); this.ready = true; this.logger?.debug('Initialized successfully'); } async cleanup(): Promise { this.client = null; this.fcmToken = undefined; this.ready = false; } isReady(): boolean { return this.ready && this.client !== null; } getStatus(): { enabled: boolean; ready: boolean; [key: string]: unknown } { return { enabled: this.config.enabled, ready: this.isReady(), }; } /** * Set FCM token for push notifications (optional). */ setFcmToken(token: string | null | undefined): void { this.fcmToken = token ?? undefined; } /** * Register an RGB invoice with the Watch Tower service. * * @param params - Invoice string plus optional FCM token, email, or web push subscription * @returns Successful response body (`status`, `message`, `data`). Errors throw. */ async addToWatchTower({ invoice, fcmToken, email, webPushNotification, }: AddToWatchTowerParams): Promise { if (!this.isReady() || !this.client) { throw new Error( 'Orbis1 SDK not initialized. Call initOrbis1({ apiKey }) first.' ); } this.logger?.debug('Adding invoice to watch tower', { invoicePrefix: invoice.slice(0, 20) + '...', }); const payload: AddToWatchTowerParams = { invoice, fcmToken: fcmToken ?? this.fcmToken, ...(email && { email }), ...(webPushNotification && { webPushNotification }), }; try { const response = await this.client.post( '/addToWatchTower', payload ); this.logger?.debug('Invoice added successfully'); return response.data; } catch (error) { this.logger?.error('Failed to add invoice to watch tower', error); if (axios.isAxiosError(error)) { const axiosError = error as AxiosError<{ message?: string; error?: string; }>; const data = axiosError.response?.data; let message = axiosError.message; if (data) { if (typeof data === 'string') message = data; else if (data.message) message = data.message; else if (data.error) message = data.error; } const err = new Error(message) as Error & { status?: number; data?: unknown; }; err.status = axiosError.response?.status; err.data = data; throw err; } throw error; } } }