/** * Synor Hosting SDK Client * Decentralized web hosting with domain management, DNS, and deployments. */ import type { HostingConfig, Domain, DomainRecord, RegisterDomainOptions, DomainAvailability, DnsRecord, DnsZone, Deployment, DeployOptions, DeploymentStats, Certificate, ProvisionSslOptions, SiteConfig, AnalyticsData, AnalyticsOptions, } from './types'; import { HostingError } from './types'; const DEFAULT_CONFIG = { endpoint: 'https://hosting.synor.io/v1', network: 'mainnet' as const, timeout: 60000, retries: 3, debug: false, }; /** * Synor Hosting SDK Client * * Provides domain registration, DNS management, site deployment, and SSL provisioning. * * @example * ```typescript * const hosting = new SynorHosting({ apiKey: 'your-api-key' }); * * // Register a domain * const domain = await hosting.registerDomain('mysite.synor'); * * // Deploy a site from IPFS CID * const deployment = await hosting.deploy('Qm...', { domain: 'mysite.synor' }); * * // Provision SSL * const cert = await hosting.provisionSsl('mysite.synor'); * ``` */ export class SynorHosting { private config: Required; private closed = false; constructor(config: HostingConfig) { this.config = { ...DEFAULT_CONFIG, ...config, }; if (!this.config.apiKey) { throw new HostingError('API key is required'); } } // ==================== Domain Operations ==================== /** * Check domain availability */ async checkAvailability(name: string): Promise { return this.request(`/domains/check/${encodeURIComponent(name)}`); } /** * Register a new domain */ async registerDomain(name: string, options?: RegisterDomainOptions): Promise { return this.request('/domains', { method: 'POST', body: { name, ...options }, }); } /** * Get domain information */ async getDomain(name: string): Promise { return this.request(`/domains/${encodeURIComponent(name)}`); } /** * List all domains */ async listDomains(): Promise { const response = await this.request<{ domains: Domain[] }>('/domains'); return response.domains; } /** * Update domain record (for IPFS/IPNS resolution) */ async updateDomainRecord(name: string, record: DomainRecord): Promise { return this.request(`/domains/${encodeURIComponent(name)}/record`, { method: 'PUT', body: record, }); } /** * Resolve a domain to its record */ async resolveDomain(name: string): Promise { return this.request(`/domains/${encodeURIComponent(name)}/resolve`); } /** * Renew a domain */ async renewDomain(name: string, years: number = 1): Promise { return this.request(`/domains/${encodeURIComponent(name)}/renew`, { method: 'POST', body: { years }, }); } /** * Transfer domain ownership */ async transferDomain(name: string, newOwner: string): Promise { return this.request(`/domains/${encodeURIComponent(name)}/transfer`, { method: 'POST', body: { new_owner: newOwner }, }); } // ==================== DNS Operations ==================== /** * Get DNS zone for a domain */ async getDnsZone(domain: string): Promise { return this.request(`/dns/${encodeURIComponent(domain)}`); } /** * Set DNS records for a domain */ async setDnsRecords(domain: string, records: DnsRecord[]): Promise { return this.request(`/dns/${encodeURIComponent(domain)}`, { method: 'PUT', body: { records }, }); } /** * Add a DNS record */ async addDnsRecord(domain: string, record: DnsRecord): Promise { return this.request(`/dns/${encodeURIComponent(domain)}/records`, { method: 'POST', body: record, }); } /** * Delete a DNS record */ async deleteDnsRecord(domain: string, recordType: string, name: string): Promise { return this.request( `/dns/${encodeURIComponent(domain)}/records/${recordType}/${encodeURIComponent(name)}`, { method: 'DELETE' } ); } // ==================== Deployment Operations ==================== /** * Deploy a site from CID */ async deploy(cid: string, options?: DeployOptions): Promise { return this.request('/deployments', { method: 'POST', body: { cid, ...options }, }); } /** * Get deployment by ID */ async getDeployment(id: string): Promise { return this.request(`/deployments/${encodeURIComponent(id)}`); } /** * List deployments */ async listDeployments(domain?: string): Promise { const query = domain ? `?domain=${encodeURIComponent(domain)}` : ''; const response = await this.request<{ deployments: Deployment[] }>(`/deployments${query}`); return response.deployments; } /** * Rollback to a previous deployment */ async rollback(domain: string, deploymentId: string): Promise { return this.request(`/deployments/${encodeURIComponent(deploymentId)}/rollback`, { method: 'POST', body: { domain }, }); } /** * Delete a deployment */ async deleteDeployment(id: string): Promise { await this.request(`/deployments/${encodeURIComponent(id)}`, { method: 'DELETE' }); } /** * Get deployment stats */ async getDeploymentStats(id: string, period: string = '24h'): Promise { return this.request( `/deployments/${encodeURIComponent(id)}/stats?period=${period}` ); } // ==================== SSL Operations ==================== /** * Provision SSL certificate */ async provisionSsl(domain: string, options?: ProvisionSslOptions): Promise { return this.request(`/ssl/${encodeURIComponent(domain)}`, { method: 'POST', body: options || {}, }); } /** * Get certificate status */ async getCertificate(domain: string): Promise { return this.request(`/ssl/${encodeURIComponent(domain)}`); } /** * Renew SSL certificate */ async renewCertificate(domain: string): Promise { return this.request(`/ssl/${encodeURIComponent(domain)}/renew`, { method: 'POST', }); } /** * Delete/revoke SSL certificate */ async deleteCertificate(domain: string): Promise { await this.request(`/ssl/${encodeURIComponent(domain)}`, { method: 'DELETE' }); } // ==================== Site Configuration ==================== /** * Get site configuration */ async getSiteConfig(domain: string): Promise { return this.request(`/sites/${encodeURIComponent(domain)}/config`); } /** * Update site configuration */ async updateSiteConfig(domain: string, config: Partial): Promise { return this.request(`/sites/${encodeURIComponent(domain)}/config`, { method: 'PATCH', body: config, }); } /** * Purge CDN cache */ async purgeCache(domain: string, paths?: string[]): Promise<{ purged: number }> { return this.request<{ purged: number }>(`/sites/${encodeURIComponent(domain)}/cache`, { method: 'DELETE', body: paths ? { paths } : undefined, }); } // ==================== Analytics ==================== /** * Get site analytics */ async getAnalytics(domain: string, options?: AnalyticsOptions): Promise { const params = new URLSearchParams(); if (options?.period) params.set('period', options.period); if (options?.startDate) params.set('start', options.startDate); if (options?.endDate) params.set('end', options.endDate); const query = params.toString() ? `?${params}` : ''; return this.request(`/sites/${encodeURIComponent(domain)}/analytics${query}`); } // ==================== Lifecycle ==================== /** * Close the client */ close(): void { this.closed = true; } /** * Check if the client is closed */ isClosed(): boolean { return this.closed; } /** * Health check */ async healthCheck(): Promise { try { const response = await this.request<{ status: string }>('/health'); return response.status === 'healthy'; } catch { return false; } } /** * Internal request method */ private async request( path: string, options: { method?: string; body?: unknown } = {} ): Promise { if (this.closed) { throw new HostingError('Client has been closed'); } const { method = 'GET', body } = options; let lastError: Error | null = null; for (let attempt = 0; attempt < this.config.retries; attempt++) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); const response = await fetch(`${this.config.endpoint}${path}`, { method, headers: { 'Authorization': `Bearer ${this.config.apiKey}`, 'Content-Type': 'application/json', 'X-SDK-Version': 'js/0.1.0', }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new HostingError( errorData.message || errorData.error || `HTTP ${response.status}`, errorData.code, response.status ); } const text = await response.text(); return text ? JSON.parse(text) : ({} as T); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (this.config.debug) { console.error(`Attempt ${attempt + 1} failed:`, lastError.message); } if (attempt < this.config.retries - 1) { await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); } } } throw lastError || new HostingError('Unknown error after retries'); } }