import "./chunk-DGUM43GV.js";

// src/platform/auth.ts
var PlatformAuthError = class extends Error {
  constructor(message, status, body) {
    super(message);
    this.status = status;
    this.body = body;
    this.name = "PlatformAuthError";
  }
  status;
  body;
};
var PlatformAuthClient = class {
  baseUrl;
  appId;
  fetchImpl;
  constructor(options) {
    if (!options.baseUrl) throw new Error("PlatformAuthClient: baseUrl is required");
    if (!options.appId) throw new Error("PlatformAuthClient: appId is required");
    this.baseUrl = options.baseUrl.replace(/\/+$/, "");
    this.appId = options.appId;
    this.fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
  }
  /**
   * Build the URL the user is redirected to in order to start SSO.
   * The platform redirects back to one of `appId`'s registered
   * `redirectUris` with `?code=...&app=...&state=...`.
   */
  authorizeUrl(options) {
    if (!options.state) {
      throw new Error("PlatformAuthClient.authorizeUrl: state is required for CSRF");
    }
    const url = new URL("/cross-site/authorize", this.baseUrl);
    url.searchParams.set("app", this.appId);
    url.searchParams.set("state", options.state);
    if (options.redirectUri) url.searchParams.set("redirect", options.redirectUri);
    if (options.prompt) url.searchParams.set("prompt", options.prompt);
    if (options.email) url.searchParams.set("email", options.email);
    return url.toString();
  }
  /**
   * Exchange a single-use auth code (delivered to the consumer's
   * callback by the platform) for an API key + the user's identity.
   * Codes are single-use and expire ~5 minutes after issue.
   */
  async exchange(code) {
    if (!code) throw new Error("PlatformAuthClient.exchange: code is required");
    const res = await this.fetchImpl(`${this.baseUrl}/cross-site/exchange`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ code, app: this.appId })
    });
    const body = await res.json().catch(() => null);
    if (!res.ok) {
      const message = body && typeof body === "object" && "error" in body && typeof body.error === "string" ? body.error : `Platform exchange failed (${res.status})`;
      throw new PlatformAuthError(message, res.status, body);
    }
    const result = body;
    if (!result.apiKey || !result.user?.id) {
      throw new PlatformAuthError(
        "Platform exchange response is missing apiKey or user",
        res.status,
        body
      );
    }
    return result;
  }
};

// src/platform/integrations.ts
var PlatformHubError = class extends Error {
  constructor(message, status, code, body) {
    super(message);
    this.status = status;
    this.code = code;
    this.body = body;
    this.name = "PlatformHubError";
  }
  status;
  code;
  body;
};
var PlatformHubClient = class {
  baseUrl;
  bearer;
  fetchImpl;
  constructor(options) {
    if (!options.baseUrl) throw new Error("PlatformHubClient: baseUrl is required");
    if (!options.bearer) throw new Error("PlatformHubClient: bearer is required");
    this.baseUrl = options.baseUrl.replace(/\/+$/, "");
    this.bearer = options.bearer;
    this.fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
  }
  /** GET /v1/hub/providers — the connectable provider catalog. */
  catalog() {
    return this.request("GET", "/v1/hub/providers");
  }
  /** GET /v1/hub/connections — the calling user's live connections. */
  async listConnections() {
    const data = await this.request(
      "GET",
      "/v1/hub/connections"
    );
    return data.connections;
  }
  /** DELETE /v1/hub/connections/:connectionId — revoke + disable a connection. */
  revokeConnection(connectionId) {
    return this.request("DELETE", `/v1/hub/connections/${encodeURIComponent(connectionId)}`);
  }
  /**
   * POST /v1/hub/connections/:provider/start — begin OAuth/grant. The provider
   * is taken from the URL; the body carries `returnUrl` (+ `cli`). The platform's
   * two start branches name the URL field differently (github → `authorizationUrl`,
   * substrate → `redirectUrl`); this normalizes to `authorizationUrl`.
   */
  async startAuth(input) {
    const body = { returnUrl: input.returnUrl };
    if (input.cli !== void 0) body.cli = input.cli;
    const data = await this.request("POST", `/v1/hub/connections/${encodeURIComponent(input.providerId)}/start`, body);
    const authorizationUrl = data.authorizationUrl ?? data.redirectUrl;
    if (!authorizationUrl) {
      throw new PlatformHubError(
        "Platform hub start response missing an authorization URL",
        502,
        "HUB_INVALID_START_RESPONSE",
        data
      );
    }
    return { authorizationUrl, state: data.state, expiresAt: data.expiresAt, scopes: data.scopes };
  }
  /**
   * Last-known health for every connection. The platform has no global
   * healthcheck listing — health rides on each connection row — so this derives
   * the list from `listConnections()` (one request, no extra round-trips).
   */
  async listHealthchecks() {
    const connections = await this.listConnections();
    return connections.map((c) => ({
      connectionId: c.id,
      providerId: c.providerId,
      status: c.health,
      checkedAt: c.updatedAt
    }));
  }
  /**
   * POST /v1/hub/connections/:connectionId/health — trigger a fresh health
   * probe for one connection and return its updated state.
   */
  checkConnectionHealth(connectionId) {
    return this.request("POST", `/v1/hub/connections/${encodeURIComponent(connectionId)}/health`);
  }
  /**
   * Trigger a fresh health probe across all of the user's connections. The
   * platform exposes health per-connection only, so this fans out over
   * `listConnections()`. `scheduled` is the number of probes dispatched.
   */
  async runHealthchecks() {
    const connections = await this.listConnections();
    await Promise.allSettled(connections.map((c) => this.checkConnectionHealth(c.id)));
    return { scheduled: connections.length };
  }
  /** GET /v1/hub/status — principal + aggregate connection counts. */
  status() {
    return this.request("GET", "/v1/hub/status");
  }
  /**
   * POST /v1/hub/tokens — mint a short-lived, action-scoped capability token a
   * sandbox can use to invoke one hub action on the user's behalf without
   * seeing the underlying provider credential.
   */
  mintToken(input) {
    return this.request("POST", "/v1/hub/tokens", input);
  }
  /** POST /v1/hub/exec — execute a hub action and return its result. */
  async exec(input) {
    const data = await this.request("POST", "/v1/hub/exec", input);
    return data.result;
  }
  async request(method, path, body) {
    const headers = {
      authorization: `Bearer ${this.bearer}`,
      accept: "application/json"
    };
    if (body !== void 0) headers["content-type"] = "application/json";
    const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
      method,
      headers,
      body: body !== void 0 ? JSON.stringify(body) : void 0
    });
    const text = await res.text();
    let parsed = null;
    if (text) {
      try {
        parsed = JSON.parse(text);
      } catch {
      }
    }
    if (!res.ok || parsed && parsed.success === false) {
      const code = parsed?.error && typeof parsed.error === "object" ? parsed.error.code : void 0;
      const message = parsed?.error && typeof parsed.error === "object" && parsed.error.message || (typeof parsed?.error === "string" ? parsed.error : `Platform hub error (${res.status})`);
      throw new PlatformHubError(message, res.status, code, parsed ?? text);
    }
    if (!parsed) {
      throw new PlatformHubError(
        `Platform hub returned non-JSON success (${res.status})`,
        res.status,
        void 0,
        text
      );
    }
    if (parsed.data === void 0) {
      throw new PlatformHubError(
        "Platform hub envelope missing `data`",
        res.status,
        void 0,
        parsed
      );
    }
    return parsed.data;
  }
};
export {
  PlatformAuthClient,
  PlatformAuthError,
  PlatformHubClient,
  PlatformHubError
};
//# sourceMappingURL=platform.js.map