- Implement SynorStorage class for decentralized storage operations including upload, download, pinning, and CAR file management. - Create supporting types and models for storage operations such as UploadOptions, Pin, and StorageConfig. - Implement SynorWallet class for wallet operations including wallet creation, address generation, transaction signing, and balance queries. - Create supporting types and models for wallet operations such as Wallet, Address, and Transaction. - Introduce error handling for both storage and wallet operations.
396 lines
10 KiB
TypeScript
396 lines
10 KiB
TypeScript
/**
|
|
* 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<HostingConfig>;
|
|
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<DomainAvailability> {
|
|
return this.request<DomainAvailability>(`/domains/check/${encodeURIComponent(name)}`);
|
|
}
|
|
|
|
/**
|
|
* Register a new domain
|
|
*/
|
|
async registerDomain(name: string, options?: RegisterDomainOptions): Promise<Domain> {
|
|
return this.request<Domain>('/domains', {
|
|
method: 'POST',
|
|
body: { name, ...options },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get domain information
|
|
*/
|
|
async getDomain(name: string): Promise<Domain> {
|
|
return this.request<Domain>(`/domains/${encodeURIComponent(name)}`);
|
|
}
|
|
|
|
/**
|
|
* List all domains
|
|
*/
|
|
async listDomains(): Promise<Domain[]> {
|
|
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<Domain> {
|
|
return this.request<Domain>(`/domains/${encodeURIComponent(name)}/record`, {
|
|
method: 'PUT',
|
|
body: record,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Resolve a domain to its record
|
|
*/
|
|
async resolveDomain(name: string): Promise<DomainRecord> {
|
|
return this.request<DomainRecord>(`/domains/${encodeURIComponent(name)}/resolve`);
|
|
}
|
|
|
|
/**
|
|
* Renew a domain
|
|
*/
|
|
async renewDomain(name: string, years: number = 1): Promise<Domain> {
|
|
return this.request<Domain>(`/domains/${encodeURIComponent(name)}/renew`, {
|
|
method: 'POST',
|
|
body: { years },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Transfer domain ownership
|
|
*/
|
|
async transferDomain(name: string, newOwner: string): Promise<Domain> {
|
|
return this.request<Domain>(`/domains/${encodeURIComponent(name)}/transfer`, {
|
|
method: 'POST',
|
|
body: { new_owner: newOwner },
|
|
});
|
|
}
|
|
|
|
// ==================== DNS Operations ====================
|
|
|
|
/**
|
|
* Get DNS zone for a domain
|
|
*/
|
|
async getDnsZone(domain: string): Promise<DnsZone> {
|
|
return this.request<DnsZone>(`/dns/${encodeURIComponent(domain)}`);
|
|
}
|
|
|
|
/**
|
|
* Set DNS records for a domain
|
|
*/
|
|
async setDnsRecords(domain: string, records: DnsRecord[]): Promise<DnsZone> {
|
|
return this.request<DnsZone>(`/dns/${encodeURIComponent(domain)}`, {
|
|
method: 'PUT',
|
|
body: { records },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Add a DNS record
|
|
*/
|
|
async addDnsRecord(domain: string, record: DnsRecord): Promise<DnsZone> {
|
|
return this.request<DnsZone>(`/dns/${encodeURIComponent(domain)}/records`, {
|
|
method: 'POST',
|
|
body: record,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete a DNS record
|
|
*/
|
|
async deleteDnsRecord(domain: string, recordType: string, name: string): Promise<DnsZone> {
|
|
return this.request<DnsZone>(
|
|
`/dns/${encodeURIComponent(domain)}/records/${recordType}/${encodeURIComponent(name)}`,
|
|
{ method: 'DELETE' }
|
|
);
|
|
}
|
|
|
|
// ==================== Deployment Operations ====================
|
|
|
|
/**
|
|
* Deploy a site from CID
|
|
*/
|
|
async deploy(cid: string, options?: DeployOptions): Promise<Deployment> {
|
|
return this.request<Deployment>('/deployments', {
|
|
method: 'POST',
|
|
body: { cid, ...options },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get deployment by ID
|
|
*/
|
|
async getDeployment(id: string): Promise<Deployment> {
|
|
return this.request<Deployment>(`/deployments/${encodeURIComponent(id)}`);
|
|
}
|
|
|
|
/**
|
|
* List deployments
|
|
*/
|
|
async listDeployments(domain?: string): Promise<Deployment[]> {
|
|
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<Deployment> {
|
|
return this.request<Deployment>(`/deployments/${encodeURIComponent(deploymentId)}/rollback`, {
|
|
method: 'POST',
|
|
body: { domain },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete a deployment
|
|
*/
|
|
async deleteDeployment(id: string): Promise<void> {
|
|
await this.request(`/deployments/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
}
|
|
|
|
/**
|
|
* Get deployment stats
|
|
*/
|
|
async getDeploymentStats(id: string, period: string = '24h'): Promise<DeploymentStats> {
|
|
return this.request<DeploymentStats>(
|
|
`/deployments/${encodeURIComponent(id)}/stats?period=${period}`
|
|
);
|
|
}
|
|
|
|
// ==================== SSL Operations ====================
|
|
|
|
/**
|
|
* Provision SSL certificate
|
|
*/
|
|
async provisionSsl(domain: string, options?: ProvisionSslOptions): Promise<Certificate> {
|
|
return this.request<Certificate>(`/ssl/${encodeURIComponent(domain)}`, {
|
|
method: 'POST',
|
|
body: options || {},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get certificate status
|
|
*/
|
|
async getCertificate(domain: string): Promise<Certificate> {
|
|
return this.request<Certificate>(`/ssl/${encodeURIComponent(domain)}`);
|
|
}
|
|
|
|
/**
|
|
* Renew SSL certificate
|
|
*/
|
|
async renewCertificate(domain: string): Promise<Certificate> {
|
|
return this.request<Certificate>(`/ssl/${encodeURIComponent(domain)}/renew`, {
|
|
method: 'POST',
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete/revoke SSL certificate
|
|
*/
|
|
async deleteCertificate(domain: string): Promise<void> {
|
|
await this.request(`/ssl/${encodeURIComponent(domain)}`, { method: 'DELETE' });
|
|
}
|
|
|
|
// ==================== Site Configuration ====================
|
|
|
|
/**
|
|
* Get site configuration
|
|
*/
|
|
async getSiteConfig(domain: string): Promise<SiteConfig> {
|
|
return this.request<SiteConfig>(`/sites/${encodeURIComponent(domain)}/config`);
|
|
}
|
|
|
|
/**
|
|
* Update site configuration
|
|
*/
|
|
async updateSiteConfig(domain: string, config: Partial<SiteConfig>): Promise<SiteConfig> {
|
|
return this.request<SiteConfig>(`/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<AnalyticsData> {
|
|
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<AnalyticsData>(`/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<boolean> {
|
|
try {
|
|
const response = await this.request<{ status: string }>('/health');
|
|
return response.status === 'healthy';
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal request method
|
|
*/
|
|
private async request<T>(
|
|
path: string,
|
|
options: { method?: string; body?: unknown } = {}
|
|
): Promise<T> {
|
|
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');
|
|
}
|
|
}
|