synor/sdk/swift/Sources/Synor/Hosting/SynorHosting.swift
Gulshan Yadav a874faef13 feat: complete Phase 3 SDKs for Swift, C, C++, C#, and Ruby
Implements Database, Hosting, and Bridge SDKs for remaining languages:

Swift SDKs:
- SynorDatabase with KV, Document, Vector, TimeSeries stores
- SynorHosting with domain, DNS, deployment, SSL operations
- SynorBridge with lock-mint and burn-unlock cross-chain flows

C SDKs:
- database.h/c - multi-model database client
- hosting.h/c - hosting and domain management
- bridge.h/c - cross-chain asset transfers

C++ SDKs:
- database.hpp - modern C++17 with std::future async
- hosting.hpp - domain and deployment operations
- bridge.hpp - cross-chain bridge with wait operations

C# SDKs:
- SynorDatabase.cs - async/await with inner store classes
- SynorHosting.cs - domain management and analytics
- SynorBridge.cs - cross-chain with BridgeException handling

Ruby SDKs:
- synor_database - Struct-based types with Faraday HTTP
- synor_hosting - domain, DNS, SSL, analytics
- synor_bridge - lock-mint/burn-unlock with retry logic

Phase 3 complete: Database/Hosting/Bridge now available in all 12 languages.
2026-01-27 02:23:07 +05:30

402 lines
14 KiB
Swift

import Foundation
/// Synor Hosting SDK client for Swift.
///
/// Provides decentralized web hosting with domain management, DNS, deployments, SSL, and analytics.
///
/// Example:
/// ```swift
/// let hosting = SynorHosting(config: HostingConfig(apiKey: "your-api-key"))
///
/// // Check domain availability
/// let availability = try await hosting.checkAvailability(name: "mydomain.synor")
/// if availability.available {
/// // Register domain
/// let domain = try await hosting.registerDomain(name: "mydomain.synor")
/// print("Registered: \(domain.name)")
/// }
///
/// // Deploy content
/// let deployment = try await hosting.deploy(cid: "Qm...", options: DeployOptions(domain: "mydomain.synor"))
/// print("Deployed to: \(deployment.url)")
///
/// // Provision SSL
/// let cert = try await hosting.provisionSsl(domain: "mydomain.synor")
/// print("SSL status: \(cert.status)")
/// ```
public class SynorHosting {
private let config: HostingConfig
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private var closed = false
public init(config: HostingConfig) {
self.config = config
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = config.timeout
sessionConfig.timeoutIntervalForResource = config.timeout * 2
self.session = URLSession(configuration: sessionConfig)
self.decoder = JSONDecoder()
self.encoder = JSONEncoder()
}
// MARK: - Domain Operations
/// Check domain availability.
public func checkAvailability(name: String) async throws -> DomainAvailability {
return try await get(path: "/domains/check/\(name.urlEncoded)")
}
/// Register a new domain.
public func registerDomain(name: String, options: RegisterDomainOptions? = nil) async throws -> Domain {
var body: [String: Any] = ["name": name]
if let options = options {
if let years = options.years { body["years"] = years }
if let autoRenew = options.autoRenew { body["autoRenew"] = autoRenew }
}
return try await post(path: "/domains", body: body)
}
/// Get domain details.
public func getDomain(name: String) async throws -> Domain {
return try await get(path: "/domains/\(name.urlEncoded)")
}
/// List all domains.
public func listDomains() async throws -> [Domain] {
let response: DomainsResponse = try await get(path: "/domains")
return response.domains ?? []
}
/// Update domain record.
public func updateDomainRecord(name: String, record: DomainRecord) async throws -> Domain {
return try await put(path: "/domains/\(name.urlEncoded)/record", body: record)
}
/// Resolve domain to its record.
public func resolveDomain(name: String) async throws -> DomainRecord {
return try await get(path: "/domains/\(name.urlEncoded)/resolve")
}
/// Renew a domain.
public func renewDomain(name: String, years: Int) async throws -> Domain {
return try await post(path: "/domains/\(name.urlEncoded)/renew", body: ["years": years])
}
// MARK: - DNS Operations
/// Get DNS zone for a domain.
public func getDnsZone(domain: String) async throws -> DnsZone {
return try await get(path: "/dns/\(domain.urlEncoded)")
}
/// Set DNS records for a domain.
public func setDnsRecords(domain: String, records: [DnsRecord]) async throws -> DnsZone {
return try await put(path: "/dns/\(domain.urlEncoded)", body: ["records": records])
}
/// Add a DNS record.
public func addDnsRecord(domain: String, record: DnsRecord) async throws -> DnsZone {
return try await post(path: "/dns/\(domain.urlEncoded)/records", body: record)
}
/// Delete a DNS record.
public func deleteDnsRecord(domain: String, recordType: String, name: String) async throws -> DnsZone {
return try await delete(path: "/dns/\(domain.urlEncoded)/records/\(recordType)/\(name.urlEncoded)")
}
// MARK: - Deployment Operations
/// Deploy content to a domain.
public func deploy(cid: String, options: DeployOptions? = nil) async throws -> Deployment {
var body: [String: Any] = ["cid": cid]
if let options = options {
if let domain = options.domain { body["domain"] = domain }
if let subdomain = options.subdomain { body["subdomain"] = subdomain }
if let spa = options.spa { body["spa"] = spa }
if let cleanUrls = options.cleanUrls { body["cleanUrls"] = cleanUrls }
if let trailingSlash = options.trailingSlash { body["trailingSlash"] = trailingSlash }
}
return try await post(path: "/deployments", body: body)
}
/// Get deployment details.
public func getDeployment(id: String) async throws -> Deployment {
return try await get(path: "/deployments/\(id.urlEncoded)")
}
/// List deployments.
public func listDeployments(domain: String? = nil) async throws -> [Deployment] {
var path = "/deployments"
if let domain = domain {
path += "?domain=\(domain.urlEncoded)"
}
let response: DeploymentsResponse = try await get(path: path)
return response.deployments ?? []
}
/// Rollback to a previous deployment.
public func rollback(domain: String, deploymentId: String) async throws -> Deployment {
return try await post(
path: "/deployments/\(deploymentId.urlEncoded)/rollback",
body: ["domain": domain]
)
}
/// Delete a deployment.
public func deleteDeployment(id: String) async throws {
let _: EmptyHostingResponse = try await deleteRequest(path: "/deployments/\(id.urlEncoded)")
}
// MARK: - SSL Operations
/// Provision SSL certificate for a domain.
public func provisionSsl(domain: String, options: ProvisionSslOptions? = nil) async throws -> Certificate {
var body: [String: Any] = [:]
if let options = options {
if let includeWww = options.includeWww { body["includeWww"] = includeWww }
if let autoRenew = options.autoRenew { body["autoRenew"] = autoRenew }
}
return try await post(path: "/ssl/\(domain.urlEncoded)", body: body.isEmpty ? nil : body)
}
/// Get certificate details.
public func getCertificate(domain: String) async throws -> Certificate {
return try await get(path: "/ssl/\(domain.urlEncoded)")
}
/// Renew SSL certificate.
public func renewCertificate(domain: String) async throws -> Certificate {
return try await post(path: "/ssl/\(domain.urlEncoded)/renew", body: nil as [String: Any]?)
}
/// Delete SSL certificate.
public func deleteCertificate(domain: String) async throws {
let _: EmptyHostingResponse = try await deleteRequest(path: "/ssl/\(domain.urlEncoded)")
}
// MARK: - Site Configuration
/// Get site configuration.
public func getSiteConfig(domain: String) async throws -> SiteConfig {
return try await get(path: "/sites/\(domain.urlEncoded)/config")
}
/// Update site configuration.
public func updateSiteConfig(domain: String, config: [String: Any]) async throws -> SiteConfig {
return try await patch(path: "/sites/\(domain.urlEncoded)/config", body: config)
}
/// Purge cache.
public func purgeCache(domain: String, paths: [String]? = nil) async throws -> Int64 {
let body: [String: Any]? = paths.map { ["paths": $0] }
let response: PurgeResponse = try await deleteRequest(
path: "/sites/\(domain.urlEncoded)/cache",
body: body
)
return response.purged
}
// MARK: - Analytics
/// Get analytics data for a domain.
public func getAnalytics(domain: String, options: AnalyticsOptions? = nil) async throws -> AnalyticsData {
var params: [String] = []
if let period = options?.period { params.append("period=\(period.urlEncoded)") }
if let start = options?.start { params.append("start=\(start.urlEncoded)") }
if let end = options?.end { params.append("end=\(end.urlEncoded)") }
var path = "/sites/\(domain.urlEncoded)/analytics"
if !params.isEmpty {
path += "?\(params.joined(separator: "&"))"
}
return try await get(path: path)
}
// MARK: - Lifecycle
/// Close the client.
public func close() {
closed = true
session.invalidateAndCancel()
}
/// Check if the client is closed.
public var isClosed: Bool { closed }
/// Perform a health check.
public func healthCheck() async -> Bool {
do {
let response: HostingHealthResponse = try await get(path: "/health")
return response.status == "healthy"
} catch {
return false
}
}
// MARK: - Private HTTP Methods
private func get<T: Decodable>(path: String) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "GET", path: path, body: nil as [String: Any]?)
}
}
private func post<T: Decodable>(path: String, body: [String: Any]?) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "POST", path: path, body: body)
}
}
private func post<T: Decodable, R: Encodable>(path: String, body: R) async throws -> T {
return try await executeWithRetry {
try await self.performEncodableRequest(method: "POST", path: path, body: body)
}
}
private func put<T: Decodable>(path: String, body: [String: Any]) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "PUT", path: path, body: body)
}
}
private func put<T: Decodable, R: Encodable>(path: String, body: R) async throws -> T {
return try await executeWithRetry {
try await self.performEncodableRequest(method: "PUT", path: path, body: body)
}
}
private func patch<T: Decodable>(path: String, body: [String: Any]) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "PATCH", path: path, body: body)
}
}
private func delete<T: Decodable>(path: String) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "DELETE", path: path, body: nil as [String: Any]?)
}
}
private func deleteRequest<T: Decodable>(path: String, body: [String: Any]? = nil) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "DELETE", path: path, body: body)
}
}
private func performRequest<T: Decodable>(
method: String,
path: String,
body: [String: Any]?
) async throws -> T {
if closed { throw HostingError.clientClosed }
guard let url = URL(string: "\(config.endpoint)\(path)") else {
throw HostingError.invalidResponse
}
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
if let body = body {
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body)
} catch {
throw HostingError.encodingError(error)
}
}
return try await executeRequest(request)
}
private func performEncodableRequest<T: Decodable, R: Encodable>(
method: String,
path: String,
body: R
) async throws -> T {
if closed { throw HostingError.clientClosed }
guard let url = URL(string: "\(config.endpoint)\(path)") else {
throw HostingError.invalidResponse
}
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
do {
request.httpBody = try encoder.encode(body)
} catch {
throw HostingError.encodingError(error)
}
return try await executeRequest(request)
}
private func executeRequest<T: Decodable>(_ request: URLRequest) async throws -> T {
let (data, response): (Data, URLResponse)
do {
(data, response) = try await session.data(for: request)
} catch {
throw HostingError.networkError(error)
}
guard let httpResponse = response as? HTTPURLResponse else {
throw HostingError.invalidResponse
}
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
if data.isEmpty && T.self == EmptyHostingResponse.self {
return EmptyHostingResponse() as! T
}
do {
return try decoder.decode(T.self, from: data)
} catch {
throw HostingError.decodingError(error)
}
}
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
let message = errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)"
let code = errorInfo?["code"] as? String
throw HostingError.httpError(statusCode: httpResponse.statusCode, message: message, code: code)
}
private func executeWithRetry<T>(_ operation: () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await operation()
} catch {
lastError = error
if config.debug {
print("Attempt \(attempt + 1) failed: \(error)")
}
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * (1 << attempt)))
}
}
}
throw lastError ?? HostingError.invalidResponse
}
}
// MARK: - Private Helpers
private struct EmptyHostingResponse: Decodable {}
private extension String {
var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
}
}