···11-import { verifyCustomDomain } from '../../../src/lib/dns-verify';
22-import { db } from '../../../src/lib/db';
11+import { verifyCustomDomain } from './dns-verify';
22+import { db } from './db';
3344interface VerificationStats {
55 totalChecked: number;
+3-19
hosting-service/src/lib/utils.ts
···44import { writeFile, readFile } from 'fs/promises';
55import { safeFetchJson, safeFetchBlob } from './safe-fetch';
66import { CID } from 'multiformats/cid';
77-import { createHash } from 'crypto';
8798const CACHE_DIR = './cache/sites';
109const CACHE_TTL = 14 * 24 * 60 * 60 * 1000; // 14 days cache TTL
···1615 rkey: string;
1716}
18171919-// Type guards for different blob reference formats
2018interface IpldLink {
2119 $link: string;
2220}
···6361 let doc;
64626563 if (did.startsWith('did:plc:')) {
6666- // Resolve did:plc from plc.directory
6764 doc = await safeFetchJson(`https://plc.directory/${encodeURIComponent(did)}`);
6865 } else if (did.startsWith('did:web:')) {
6969- // Resolve did:web from the domain
7066 const didUrl = didWebToHttps(did);
7167 doc = await safeFetchJson(didUrl);
7268 } else {
···8581}
86828783function didWebToHttps(did: string): string {
8888- // did:web:example.com -> https://example.com/.well-known/did.json
8989- // did:web:example.com:path:to:did -> https://example.com/path/to/did/did.json
9090-9184 const didParts = did.split(':');
9285 if (didParts.length < 3 || didParts[0] !== 'did' || didParts[1] !== 'web') {
9386 throw new Error('Invalid did:web format');
···9790 const pathParts = didParts.slice(3);
98919992 if (pathParts.length === 0) {
100100- // No path, use .well-known
10193 return `https://${domain}/.well-known/did.json`;
10294 } else {
103103- // Has path
10495 const path = pathParts.join('/');
10596 return `https://${domain}/${path}/did.json`;
10697 }
···114105 const url = `${pdsEndpoint}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=place.wisp.fs&rkey=${encodeURIComponent(rkey)}`;
115106 const data = await safeFetchJson(url);
116107117117- // Return both the record and its CID for verification
118108 return {
119109 record: data.value as WispFsRecord,
120110 cid: data.cid || ''
···126116}
127117128118export function extractBlobCid(blobRef: unknown): string | null {
129129- // Check if it's a direct IPLD link
130119 if (isIpldLink(blobRef)) {
131120 return blobRef.$link;
132121 }
133122134134- // Check if it's a typed blob ref with a ref property
135123 if (isTypedBlobRef(blobRef)) {
136124 const ref = blobRef.ref;
137125138138- // Check if ref is a CID object
139139- if (CID.isCID(ref)) {
140140- return ref.toString();
126126+ const cid = CID.asCID(ref);
127127+ if (cid) {
128128+ return cid.toString();
141129 }
142130143143- // Check if ref is an IPLD link object
144131 if (isIpldLink(ref)) {
145132 return ref.$link;
146133 }
147134 }
148135149149- // Check if it's an untyped blob ref with a cid string
150136 if (isUntypedBlobRef(blobRef)) {
151137 return blobRef.cid;
152138 }
···157143export async function downloadAndCacheSite(did: string, rkey: string, record: WispFsRecord, pdsEndpoint: string, recordCid: string): Promise<void> {
158144 console.log('Caching site', did, rkey);
159145160160- // Validate record structure
161146 if (!record.root) {
162147 console.error('Record missing root directory:', JSON.stringify(record, null, 2));
163148 throw new Error('Invalid record structure: missing root directory');
···170155171156 await cacheFiles(did, rkey, record.root.entries, pdsEndpoint, '');
172157173173- // Save cache metadata with CID for verification
174158 await saveCacheMetadata(did, rkey, recordCid);
175159}
176160