Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
1import { verifyCustomDomain } from './dns-verify'; 2import { db } from './db'; 3 4interface VerificationStats { 5 totalChecked: number; 6 verified: number; 7 failed: number; 8 errors: number; 9} 10 11export class DNSVerificationWorker { 12 private interval: Timer | null = null; 13 private isRunning = false; 14 private lastRunTime: number | null = null; 15 private stats: VerificationStats = { 16 totalChecked: 0, 17 verified: 0, 18 failed: 0, 19 errors: 0, 20 }; 21 22 constructor( 23 private checkIntervalMs: number = 60 * 60 * 1000, // 1 hour default 24 private onLog?: (message: string, data?: any) => void 25 ) {} 26 27 private log(message: string, data?: any) { 28 if (this.onLog) { 29 this.onLog(message, data); 30 } 31 } 32 33 async start() { 34 if (this.isRunning) { 35 this.log('DNS verification worker already running'); 36 return; 37 } 38 39 this.isRunning = true; 40 this.log('Starting DNS verification worker', { 41 intervalMinutes: this.checkIntervalMs / 60000, 42 }); 43 44 // Run immediately on start 45 await this.verifyAllDomains(); 46 47 // Then run on interval 48 this.interval = setInterval(() => { 49 this.verifyAllDomains(); 50 }, this.checkIntervalMs); 51 } 52 53 stop() { 54 if (this.interval) { 55 clearInterval(this.interval); 56 this.interval = null; 57 } 58 this.isRunning = false; 59 this.log('DNS verification worker stopped'); 60 } 61 62 private async verifyAllDomains() { 63 this.log('Starting DNS verification check'); 64 const startTime = Date.now(); 65 66 const runStats: VerificationStats = { 67 totalChecked: 0, 68 verified: 0, 69 failed: 0, 70 errors: 0, 71 }; 72 73 try { 74 // Get all custom domains (both verified and pending) 75 const domains = await db<Array<{ 76 id: string; 77 domain: string; 78 did: string; 79 verified: boolean; 80 }>>` 81 SELECT id, domain, did, verified FROM custom_domains 82 `; 83 84 if (!domains || domains.length === 0) { 85 this.log('No custom domains to check'); 86 this.lastRunTime = Date.now(); 87 return; 88 } 89 90 const verifiedCount = domains.filter(d => d.verified).length; 91 const pendingCount = domains.filter(d => !d.verified).length; 92 this.log(`Checking ${domains.length} custom domains (${verifiedCount} verified, ${pendingCount} pending)`); 93 94 // Verify each domain 95 for (const row of domains) { 96 runStats.totalChecked++; 97 const { id, domain, did, verified: wasVerified } = row; 98 99 try { 100 // Extract hash from id (SHA256 of did:domain) 101 const expectedHash = id.substring(0, 16); 102 103 // Verify DNS records 104 const result = await verifyCustomDomain(domain, did, expectedHash); 105 106 if (result.verified) { 107 // Update verified status and last_verified_at timestamp 108 await db` 109 UPDATE custom_domains 110 SET verified = true, 111 last_verified_at = EXTRACT(EPOCH FROM NOW()) 112 WHERE id = ${id} 113 `; 114 runStats.verified++; 115 if (!wasVerified) { 116 this.log(`Domain newly verified: ${domain}`, { did }); 117 } else { 118 this.log(`Domain re-verified: ${domain}`, { did }); 119 } 120 } else { 121 // Mark domain as unverified or keep it pending 122 await db` 123 UPDATE custom_domains 124 SET verified = false, 125 last_verified_at = EXTRACT(EPOCH FROM NOW()) 126 WHERE id = ${id} 127 `; 128 runStats.failed++; 129 if (wasVerified) { 130 this.log(`Domain verification failed (was verified): ${domain}`, { 131 did, 132 error: result.error, 133 found: result.found, 134 }); 135 } else { 136 this.log(`Domain still pending: ${domain}`, { 137 did, 138 error: result.error, 139 found: result.found, 140 }); 141 } 142 } 143 } catch (error) { 144 runStats.errors++; 145 this.log(`Error verifying domain: ${domain}`, { 146 did, 147 error: error instanceof Error ? error.message : String(error), 148 }); 149 } 150 } 151 152 // Update cumulative stats 153 this.stats.totalChecked += runStats.totalChecked; 154 this.stats.verified += runStats.verified; 155 this.stats.failed += runStats.failed; 156 this.stats.errors += runStats.errors; 157 158 const duration = Date.now() - startTime; 159 this.lastRunTime = Date.now(); 160 161 this.log('DNS verification check completed', { 162 duration: `${duration}ms`, 163 ...runStats, 164 }); 165 } catch (error) { 166 this.log('Fatal error in DNS verification worker', { 167 error: error instanceof Error ? error.message : String(error), 168 }); 169 } 170 } 171 172 getHealth() { 173 return { 174 isRunning: this.isRunning, 175 lastRunTime: this.lastRunTime, 176 intervalMs: this.checkIntervalMs, 177 stats: this.stats, 178 healthy: this.isRunning && ( 179 this.lastRunTime === null || 180 Date.now() - this.lastRunTime < this.checkIntervalMs * 2 181 ), 182 }; 183 } 184 185 // Manual trigger for testing 186 async trigger() { 187 this.log('Manual DNS verification triggered'); 188 await this.verifyAllDomains(); 189 } 190}