import { b as private_env } from './shared-server-DaWdgxVh.js';
import { execFileSync, execFile } from 'child_process';
import jwt from 'jsonwebtoken';
import { promisify } from 'util';
import { t as toErrorMessage } from './error-DDXB3duW.js';

const APNS_MAX_BYTES = 3900;
const ELLIPSIS = "…";
function fitApnsPayload(body, maxBytes = APNS_MAX_BYTES) {
  if (payloadBytes(body) <= maxBytes) {
    return body;
  }
  const aps = body.aps;
  const alert = aps?.alert;
  if (!alert) {
    return body;
  }
  for (const field of ["body", "subtitle"]) {
    if (payloadBytes(body) <= maxBytes) {
      break;
    }
    const value = alert[field];
    if (typeof value !== "string" || value.length === 0) {
      continue;
    }
    let text = value;
    while (payloadBytes(body) > maxBytes && text.length > 0) {
      const cut = Math.max(1, Math.ceil(text.length * 0.12));
      text = text.slice(0, text.length - cut);
      alert[field] = text.length > 0 ? text + ELLIPSIS : void 0;
    }
  }
  return body;
}
function payloadBytes(body) {
  return Buffer.byteLength(JSON.stringify(body), "utf8");
}
const execFileAsync = promisify(execFile);
const APNS_HOST_PROD = "api.push.apple.com";
const APNS_HOST_SANDBOX = "api.sandbox.push.apple.com";
const JWT_REFRESH_INTERVAL_MS = 30 * 60 * 1e3;
const REQUEST_TIMEOUT_SECONDS = 15;
class LibraryAPNsService {
  bundleId;
  cachedJwt = null;
  cachedJwtAt = 0;
  configured = false;
  host = "";
  keyId;
  privateKey;
  teamId;
  constructor() {
    this.keyId = private_env.APNS_KEY_ID;
    this.teamId = private_env.APNS_TEAM_ID;
    this.bundleId = private_env.APNS_BUNDLE_ID;
    this.privateKey = private_env.APNS_KEY;
    if (!this.keyId || !this.teamId || !this.bundleId || !this.privateKey) {
      console.error(
        "[apns] Missing required configuration (APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID, or APNS_KEY)"
      );
      this.configured = false;
      return;
    }
    const production = private_env.APNS_PRODUCTION === "true";
    this.host = production ? APNS_HOST_PROD : APNS_HOST_SANDBOX;
    try {
      execFileSync("curl", ["--version"], { stdio: "ignore" });
    } catch (error) {
      console.error(
        "[apns] curl binary not found — install curl to enable APNs delivery:",
        toErrorMessage(error)
      );
      this.configured = false;
      return;
    }
    try {
      this.getJwt();
      this.configured = true;
      console.log(
        `[apns] Provider initialized (${production ? "production" : "sandbox"} mode, curl transport)`
      );
    } catch (error) {
      console.error("[apns] Failed to initialize:", toErrorMessage(error));
      this.configured = false;
    }
  }
  isConfigured() {
    return this.configured;
  }
  async sendNotification(deviceToken, payload) {
    if (!this.configured) {
      throw new Error("APNs service not configured properly");
    }
    if (!deviceToken || !payload) {
      throw new Error("Device token and payload are required");
    }
    const aps = {
      alert: {
        body: payload.body ?? payload.message ?? "",
        title: payload.title,
        ...payload.subtitle ? { subtitle: payload.subtitle } : {}
      },
      badge: payload.badge ?? 1,
      sound: payload.sound ?? "default"
    };
    if (payload.category) {
      aps.category = payload.category;
    }
    const body = { aps };
    if (payload.data) {
      const { aps: _ignoredAps, ...customData } = payload.data;
      Object.assign(body, customData);
    }
    return this.deliver(deviceToken, body, "alert", "10");
  }
  /**
   * Send a SILENT (content-available) background push to wake a backgrounded app without
   * showing an alert — used to wake the phone-resident agent loop so it can run a burst.
   * Uses apns-push-type:background + apns-priority:5 (required by APNs for silent pushes).
   * iOS throttles these (a few per hour); delivery is best-effort.
   */
  async sendSilentNotification(deviceToken, data) {
    if (!this.configured) {
      throw new Error("APNs service not configured properly");
    }
    if (!deviceToken) {
      throw new Error("Device token is required");
    }
    const body = { aps: { "content-available": 1 } };
    if (data) {
      const { aps: _ignoredAps, ...customData } = data;
      Object.assign(body, customData);
    }
    return this.deliver(deviceToken, body, "background", "5");
  }
  shutdown() {
  }
  /** Shared curl/HTTP-2 delivery. `pushType` is 'alert' | 'background', `priority` '10' | '5'. */
  async deliver(deviceToken, body, pushType, priority) {
    const bodyJson = JSON.stringify(fitApnsPayload(body));
    const jwtToken = this.getJwt();
    const url = `https://${this.host}/3/device/${deviceToken}`;
    const args = [
      "-sS",
      "--http2",
      "--max-time",
      String(REQUEST_TIMEOUT_SECONDS),
      "-X",
      "POST",
      "-H",
      "content-type: application/json",
      "-H",
      `apns-topic: ${this.bundleId}`,
      "-H",
      `authorization: bearer ${jwtToken}`,
      "-H",
      `apns-push-type: ${pushType}`,
      "-H",
      `apns-priority: ${priority}`,
      "-d",
      bodyJson,
      "-w",
      "\n__SHOOTER_HTTP_STATUS__:%{http_code}\n",
      url
    ];
    try {
      const { stdout } = await execFileAsync("curl", args, {
        maxBuffer: 1024 * 1024,
        timeout: (REQUEST_TIMEOUT_SECONDS + 5) * 1e3
      });
      const statusMatch = /__SHOOTER_HTTP_STATUS__:(\d+)/.exec(stdout);
      const status = statusMatch ? parseInt(statusMatch[1], 10) : 0;
      const bodyText = stdout.replace(/\n?__SHOOTER_HTTP_STATUS__:\d+\n?$/, "").trim();
      if (status === 200) {
        return { failed: 0, sent: 1, success: true };
      }
      let reason = bodyText;
      try {
        const parsed = JSON.parse(bodyText);
        if (parsed && typeof parsed === "object" && "reason" in parsed && typeof parsed.reason === "string") {
          reason = parsed.reason;
        }
      } catch {
      }
      console.error(`[apns] Delivery failed (status=${status}): ${reason}`);
      return { error: reason, failed: 1, sent: 0, success: false };
    } catch (err) {
      const msg = toErrorMessage(err).replace(/bearer\s+[A-Za-z0-9._-]+/gi, "bearer [REDACTED]").replace(/device\/[A-Fa-f0-9]+/g, "device/[REDACTED]");
      console.error(`[apns] curl transport error: ${msg}`);
      return { error: msg, failed: 1, sent: 0, success: false };
    }
  }
  getJwt() {
    const now = Date.now();
    if (this.cachedJwt && now - this.cachedJwtAt < JWT_REFRESH_INTERVAL_MS) {
      return this.cachedJwt;
    }
    if (!this.privateKey || !this.keyId || !this.teamId) {
      throw new Error("APNs credentials missing");
    }
    const token = jwt.sign({ iat: Math.floor(now / 1e3), iss: this.teamId }, this.privateKey, {
      algorithm: "ES256",
      header: { alg: "ES256", kid: this.keyId }
    });
    this.cachedJwt = token;
    this.cachedJwtAt = now;
    return token;
  }
}

export { LibraryAPNsService as L };
//# sourceMappingURL=library-apns-D8RPINlv.js.map
