import axios, { AxiosPromise, AxiosProgressEvent } from 'axios'; import HostingRoute from '../routes/HostingRoute'; import Config from '../config/Config'; import Requests from '../util/Requests'; import Response from '../util/Response'; export type HostingPlanKey = 'free' | 'launch' | 'growth' | 'scale' | 'studio'; export type HostingMode = 'static' | 'server'; export type HostingDatabaseEngine = 'postgresql' | 'mysql' | 'azure_sql' | 'cosmos_nosql' | 'redis'; export type HostingDatabasePlan = 'sandbox' | 'launch' | 'growth' | 'scale' | 'dedicated' | 'cache_sandbox' | 'cache_launch' | 'cache_growth' | 'cache_scale'; export type HostingServiceRole = 'web' | 'api' | 'game' | 'realtime' | 'simulation' | 'worker' | 'scheduled'; export type HostingServiceVisibility = 'public' | 'internal' | 'none'; export type HostingCapacityModel = 'singleton' | 'replicated' | 'serverless'; export type HostingDeliveryChannel = 'glitch_store' | 'glitch_hosted_subdomain' | 'glitch_hosted_custom_domain' | 'external'; export type HostingBillingProvider = 'direct' | 'microsoft_marketplace' | 'aws_marketplace'; export interface HostingAccount { id: string; community_id: string; plan: HostingPlanKey; pending_plan?: HostingPlanKey | null; status: string; billing_provider: HostingBillingProvider; microsoft_marketplace_subscription_id?: string | null; aws_marketplace_subscription_id?: string | null; included_bandwidth_bytes: number; used_bandwidth_bytes: number; remaining_bandwidth_bytes: number; bandwidth_overage_enabled: boolean; bandwidth_spend_limit_cents?: number | null; } export interface HostingRelease { id: string; hosting_site_id: string; game_build_id?: string | null; version: string; status: 'uploading' | 'processing' | 'ready' | 'failed' | 'active' | 'inactive'; source_type: 'upload' | 'cli' | 'game_build' | 'service_stack'; entry_point: string; size_bytes: number; checksum?: string | null; error_message?: string | null; promoted_at?: string | null; service_deployments?: HostingServiceDeployment[]; } export interface HostingDomain { id: string; hostname: string; type: 'generated' | 'custom' | 'managed'; status: 'pending_verification' | 'provisioning' | 'active' | 'failed' | 'expired'; verification_record_type?: string | null; verification_record_name?: string | null; verification_record_value?: string | null; certificate_status?: string | null; error_code?: string | null; incident_id?: string | null; last_error?: string | null; billing_status?: 'active' | 'past_due' | 'unpaid' | 'cancelled' | null; annual_price_cents?: number | null; auto_renew: boolean; } export interface HostingDatabase { id: string; hosting_site_id: string; name: string; engine: HostingDatabaseEngine; plan: HostingDatabasePlan; status: 'awaiting_payment' | 'requested' | 'provisioning' | 'ready' | 'resizing' | 'restoring' | 'failed' | 'deleting' | 'deleted'; deployment_model: 'shared' | 'dedicated' | 'serverless' | 'managed_cache'; azure_region: string; included_storage_bytes: number; current_storage_bytes: number; auto_grow_enabled: boolean; backup_retention_days: number; high_availability_enabled: boolean; endpoint?: string | null; port?: number | null; binding_name: string; billing_status: 'awaiting_payment' | 'active' | 'past_due' | 'unpaid' | 'cancelled' | 'expired' | 'failed'; billing_provider: HostingBillingProvider; billing_active: boolean; last_error?: string | null; } export interface HostingDatabaseCredentials { database_id: string; name: string; engine: HostingDatabaseEngine; binding_name: string; connection_string: string; connection: { driver?: string; host?: string; endpoint?: string; port?: number; database?: string; username?: string; password?: string; key?: string; tls: boolean; }; revealed_at: string; /** UI safety timer only; the database credential itself does not expire after this interval. */ hide_after_seconds: number; } export interface HostingSite { id: string; hosting_account_id: string; community_id: string; title_id: string; name: string; slug: string; community_slug: string; generated_hostname: string; url: string; mode: HostingMode; status: 'draft' | 'provisioning' | 'live' | 'limited' | 'failed' | 'disabled'; server_mode_enabled: boolean; azure_region?: string | null; active_release_id?: string | null; active_release?: HostingRelease | null; domains?: HostingDomain[]; databases?: HostingDatabase[]; services?: HostingService[]; runtime_routes?: Array<{ path_prefix: string; service: string; runtime_origin: string }>; } export interface HostingServiceVolume { name: string; mount_path: string; size_gb: number; access_mode?: 'ReadOnly' | 'ReadWrite'; } export interface HostingServiceDeployment { id: string; hosting_service_id: string; hosting_release_id: string; game_build_id?: string | null; version: string; status: 'queued' | 'deploying' | 'ready' | 'active' | 'inactive' | 'failed'; image?: string | null; public_url?: string | null; error_message?: string | null; ready_at?: string | null; promoted_at?: string | null; } export interface HostingService { id: string; hosting_site_id: string; name: string; slug: string; role: HostingServiceRole; runtime: 'node' | 'python' | 'rust' | 'container'; visibility: HostingServiceVisibility; status: 'draft' | 'deploying' | 'active' | 'disabled' | 'failed'; is_primary: boolean; target_port?: number | null; transport: 'http' | 'http2' | 'tcp'; health_check_path?: string | null; startup_check_path?: string | null; readiness_check_path?: string | null; liveness_check_path?: string | null; capacity_model: HostingCapacityModel; container_cpu: number; container_memory_mb: number; min_replicas: number; max_replicas: number; schedule_cron?: string | null; termination_grace_seconds: number; depends_on: string[]; public_paths: string[]; volumes: HostingServiceVolume[]; database_bindings: string[]; secret_names: string[]; active_deployment?: HostingServiceDeployment | null; deployments?: HostingServiceDeployment[]; } export interface HostingServiceDefinition { name?: string; slug: string; role?: HostingServiceRole; runtime?: 'node' | 'python' | 'rust' | 'container'; visibility?: HostingServiceVisibility; is_primary?: boolean; target_port?: number; transport?: 'http' | 'http2' | 'tcp'; health_check_path?: string; startup_check_path?: string; readiness_check_path?: string; liveness_check_path?: string; capacity_model?: HostingCapacityModel; container_cpu?: number; container_memory_mb?: number; min_replicas?: number; max_replicas?: number; schedule_cron?: string; termination_grace_seconds?: number; depends_on?: string[]; public_paths?: string[]; volumes?: HostingServiceVolume[]; environment?: Record; database_bindings?: string[]; configuration?: Record; command?: string[]; arguments?: string[]; game_build_id?: string; } export interface HostingServiceStackRequest { preset?: 'single_server' | 'stateful_game_server' | 'web_and_api' | 'authoritative_world' | 'large_realtime_world'; game_build_id?: string; builds?: Record; services?: HostingServiceDefinition[]; } export interface ApplyHostingServiceStackRequest extends HostingServiceStackRequest { version: string; test?: { service: string; command: string[] }; migration?: { service: string; command: string[] }; } export interface HostingServiceEstimate { billing_provider: HostingBillingProvider; estimated_monthly_floor_cents: number; estimated_monthly_floor_dollars: number; services: Array>; rates: { vcpu_hour_cents: number; memory_gib_hour_cents: number; requests_million_cents: number; persistent_storage_gib_month_cents: number; }; note: string; } export interface CreateHostingSiteRequest { name: string; slug: string; mode: HostingMode; azure_region?: string; } export interface CreateHostingReleaseRequest { version: string; source_type: 'upload' | 'cli' | 'game_build'; blob_path?: string; game_build_id?: string; /** Proven relative path in the finished artifact. Never guess index.html or package.json. */ entry_point: string; } export interface HostingCheckoutResult { database?: HostingDatabase; action?: string; checkout_url?: string | null; /** Stripe Embedded Checkout client secret for same-page card collection and 3DS. */ checkout_client_secret?: string | null; checkout_mode?: 'embedded' | 'hosted' | null; checkout_session_id?: string | null; billing_provider?: HostingBillingProvider; message?: string; } export interface HostingAiInstructionsRequest extends HostingServiceStackRequest { framework?: string; custom_domain?: string; databases?: Array>; version?: string; /** Candidate only; the generated instructions require proof from the finished artifact. */ entry_point?: string; } export interface CreateHostingDatabaseRequest { name: string; engine: HostingDatabaseEngine; plan: HostingDatabasePlan; azure_region: string; auto_grow_enabled?: boolean; high_availability_enabled?: boolean; } export interface HostingMarketplaceSubscription { id: string; marketplace_subscription_id: string; subscription_name?: string | null; offer_id: string; plan_id: string; plan_key: HostingPlanKey; status: 'PendingFulfillmentStart' | 'Subscribed' | 'Suspended' | 'Unsubscribed'; quantity: number; community_id?: string | null; is_test: boolean; is_free_trial: boolean; auto_renew: boolean; term_starts_at?: string | null; term_ends_at?: string | null; activated_at?: string | null; } export interface HostingAwsMarketplaceSubscription { id: string; customer_aws_account_id?: string | null; plan_dimension?: string | null; plan_key?: Exclude | null; status: 'PendingRegistration' | 'PendingEntitlement' | 'Subscribed' | 'Unsubscribed'; entitlement_value: number; entitlement_expires_at?: string | null; community_id?: string | null; hosting_account_id?: string | null; activated_at?: string | null; last_synced_at?: string | null; manage_url: string; } /** * Typed SDK for game website hosting, managed database add-ons, domains, usage, * deployment instructions, and hosted-play attribution. */ class Hosting { public static catalog(): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.catalog); } public static dashboard(title_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.dashboard, undefined, { title_id }); } public static channelAnalytics(title_id: string, params?: Record): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.channelAnalytics, undefined, { title_id }, params); } /** Start or apply a bandwidth-based Hosting plan, separate from Store distribution. */ public static billingCheckout(title_id: string, plan: HostingPlanKey): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.billingCheckout, { plan }, { title_id }); } /** Confirm a paid Stripe Checkout session before provisioning its add-on. */ public static confirmBillingCheckout(title_id: string, checkout_session_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.confirmBillingCheckout, { checkout_session_id }, { title_id }); } /** Resolve the one-hour purchase token passed to Glitch by Microsoft Marketplace. */ public static resolveMarketplacePurchase(token: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.resolveMarketplacePurchase, { token }); } /** Link a resolved Microsoft Marketplace subscription to a billable Glitch business account. */ public static activateMarketplaceSubscription(subscription_id: string, community_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.activateMarketplaceSubscription, { community_id }, { subscription_id }); } /** Retrieve safe Microsoft Marketplace entitlement and lifecycle status. */ public static marketplaceSubscription(subscription_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.marketplaceSubscription, undefined, { subscription_id }); } /** Claim the one-time Glitch code created after AWS ResolveCustomer succeeds. */ public static resolveAwsMarketplacePurchase(activation_code: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.resolveAwsMarketplacePurchase, { activation_code }); } /** Connect a paid AWS Marketplace contract to one Glitch business account. */ public static activateAwsMarketplaceSubscription(subscription_id: string, community_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.activateAwsMarketplaceSubscription, { community_id }, { subscription_id }); } /** Refresh and retrieve the safe AWS Marketplace entitlement state. */ public static awsMarketplaceSubscription(subscription_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.awsMarketplaceSubscription, undefined, { subscription_id }); } public static createSite(title_id: string, data: CreateHostingSiteRequest): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.createSite, data, { title_id }); } public static updateSite(title_id: string, site_id: string, data: Partial & Record): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.updateSite, data, { title_id, site_id }); } public static createUploadUrl(title_id: string, site_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.uploadUrl, {}, { title_id, site_id }); } /** Upload directly to the short-lived signed URL returned by createUploadUrl. */ public static uploadBuild(uploadUrl: string, file: Blob, requiredHeaders: Record = {}, onUploadProgress?: (event: AxiosProgressEvent) => void): AxiosPromise { return axios.put(uploadUrl, file, { headers: { 'x-ms-blob-type': 'BlockBlob', 'Content-Type': 'application/zip', ...requiredHeaders }, onUploadProgress, }); } public static releases(title_id: string, site_id: string, params?: Record): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.releases, undefined, { title_id, site_id }, params); } public static createRelease(title_id: string, site_id: string, data: CreateHostingReleaseRequest): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.createRelease, data, { title_id, site_id }); } public static promoteRelease(title_id: string, site_id: string, release_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.promoteRelease, {}, { title_id, site_id, release_id }); } public static connectDomain(title_id: string, site_id: string, hostname: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.connectDomain, { hostname }, { title_id, site_id }); } public static verifyDomain(title_id: string, site_id: string, domain_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.verifyDomain, {}, { title_id, site_id, domain_id }); } public static checkDomain(hostname: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.checkDomain, { hostname }); } public static purchaseDomain(title_id: string, site_id: string, data: Record): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.purchaseDomain, data, { title_id, site_id }); } public static aiInstructions(title_id: string, site_id: string, data: HostingAiInstructionsRequest = {}): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.aiInstructions, data, { title_id, site_id }); } public static services(title_id: string, site_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.services, undefined, { title_id, site_id }); } /** Calculate the always-on floor without creating resources or charges. */ public static estimateServices(title_id: string, site_id: string, data: HostingServiceStackRequest): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.estimateServices, data, { title_id, site_id }); } /** Queue an immutable multi-service release. Publishing remains a separate operation. */ public static applyServices(title_id: string, site_id: string, data: ApplyHostingServiceStackRequest): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.applyServices, data, { title_id, site_id }); } /** Store or rotate a secret. The API never returns the value. Interactive administrators only. */ public static putServiceSecret(title_id: string, site_id: string, service_id: string, name: string, value: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.putServiceSecret, { value }, { title_id, site_id, service_id, name }); } public static deleteServiceSecret(title_id: string, site_id: string, service_id: string, name: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.deleteServiceSecret, undefined, { title_id, site_id, service_id, name }); } public static resolve(hostname: string, gatewayToken?: string): AxiosPromise> { if (!gatewayToken) { return Requests.processRoute(HostingRoute.routes.resolve, undefined, undefined, { hostname }); } const base = (Config.getBaseUrl() || '').replace(/\/+$/, ''); const path = HostingRoute.routes.resolve.url.replace(/^\/+/, ''); return axios.get(`${base}/${path}`, { params: { hostname }, headers: { 'X-Glitch-Hosting-Gateway': gatewayToken }, }); } public static startPlaySession(data: Record): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.startPlaySession, data); } public static heartbeatPlaySession(session_id: string, sessionToken: string): AxiosPromise> { const path = HostingRoute.routes.heartbeatPlaySession.url.replace('{session_id}', session_id); const base = (Config.getBaseUrl() || '').replace(/\/+$/, ''); return axios.post(`${base}/${path.replace(/^\/+/, '')}`, {}, { headers: { Authorization: `Bearer ${sessionToken}` } }); } public static databases(title_id: string, site_id: string, params?: Record): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.databases, undefined, { title_id, site_id }, params); } public static createDatabase(title_id: string, site_id: string, data: CreateHostingDatabaseRequest): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.createDatabase, data, { title_id, site_id }); } public static database(title_id: string, site_id: string, database_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.database, undefined, { title_id, site_id, database_id }); } /** * Reveal credentials to a signed-in business billing administrator after an * exact database-name confirmation. Hosting and MCP tokens are rejected. */ public static databaseCredentials(title_id: string, site_id: string, database_id: string, confirmation: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.databaseCredentials, { confirmation }, { title_id, site_id, database_id }); } public static updateDatabase(title_id: string, site_id: string, database_id: string, data: Record): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.updateDatabase, data, { title_id, site_id, database_id }); } public static retryDatabase(title_id: string, site_id: string, database_id: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.retryDatabase, {}, { title_id, site_id, database_id }); } public static deleteDatabase(title_id: string, site_id: string, database_id: string, confirmation: string): AxiosPromise> { return Requests.processRoute(HostingRoute.routes.deleteDatabase, undefined, { title_id, site_id, database_id }, { confirmation }); } } export default Hosting;