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}