/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Behavior } from "#behavior/Behavior.js";
import { EndpointInitializer } from "#endpoint/properties/EndpointInitializer.js";
import { ServerEndpointInitializer } from "#node/server/ServerEndpointInitializer.js";
import {
  AppAddress,
  Crypto,
  CurveType,
  DatafileRoot,
  Diagnostic,
  ImplementationError,
  InternalError,
  Key,
  KeyType,
  Logger,
  X509,
  X520,
  X962
} from "@matter/general";
import { DatatypeModel, FieldElement } from "@matter/model";
const logger = Logger.get("RemoteServer");
class RemoteServer extends Behavior {
  static early = true;
  static interfaceType;
  async initialize() {
    if (this.state.key === void 0) {
      this.state.key = await this.#createKey();
    }
    if (this.state.certificate === void 0) {
      this.state.certificate = await this.#createCertificate(this.state.key);
    }
    if (!(this.env.get(EndpointInitializer) instanceof ServerEndpointInitializer)) {
      throw new ImplementationError("Remote server is not installed in a ServerNode");
    }
    this.reactTo(this.endpoint.lifecycle.online, this.#start);
    this.reactTo(this.endpoint.lifecycle.offline, this.#onOffline);
    this.reactTo(this.endpoint.lifecycle.destroying, this.#stop);
    if (this.state.allowOfflineUse) {
      await this.#start();
    }
  }
  [Symbol.asyncDispose]() {
    return this.#stop();
  }
  async #start() {
    if (!this.state.enabled || this.internal.interface) {
      return;
    }
    const interfaceType = this.constructor.interfaceType;
    if (typeof interfaceType !== "function") {
      throw new InternalError(`Remote server ${this.constructor.name} does not define a remote interface`);
    }
    let addressStr = this.state.address.replace(/\{node\}/g, this.endpoint.id);
    const address = new AppAddress(addressStr);
    const { transport } = address;
    if (transport.kind === "unix" && !transport.path.startsWith("/")) {
      const root = this.env.has(DatafileRoot) ? this.env.get(DatafileRoot) : void 0;
      if (root) {
        const absolutePath = `${root.path}/${transport.path}`;
        address.hostname = encodeURIComponent(absolutePath);
        this.internal.lock = await root.lock();
      }
    }
    const { key, certificate } = this.state;
    const intf = await interfaceType.create({
      node: this.endpoint,
      address,
      key,
      certificate
    });
    this.internal.interface = intf;
    logger.info("Listening on", Diagnostic.strong(intf.address));
  }
  async #stop() {
    const { interface: intf, lock } = this.internal;
    if (!intf) {
      return;
    }
    this.internal.interface = void 0;
    await intf.close();
    if (lock) {
      this.internal.lock = void 0;
      await lock.close();
    }
  }
  async #onOffline() {
    if (!this.state.allowOfflineUse) {
      await this.#stop();
    }
  }
  static schema = new DatatypeModel(
    {
      name: "ApiState",
      type: "struct"
    },
    FieldElement({ name: "address", type: "string" }),
    FieldElement({ name: "enabled", type: "bool" }),
    FieldElement({ name: "allowOfflineUse", type: "bool" }),
    FieldElement({ name: "certificate", type: "octstr" }),
    FieldElement({ name: "key", type: "octstr" })
  );
  async #createKey() {
    const key = await this.env.get(Crypto).createKeyPair();
    if (key.pem === void 0) {
      throw new InternalError("PEM form of new private key is missing");
    }
    return key.pem;
  }
  async #createCertificate(pkcs8) {
    const crypto = this.env.get(Crypto);
    const key = Key({ pem: pkcs8, kty: KeyType.EC, crv: CurveType.p256 });
    const notBefore = /* @__PURE__ */ new Date();
    const notAfter = /* @__PURE__ */ new Date();
    notAfter.setFullYear(notAfter.getFullYear() + 10);
    const unsigned = {
      serialNumber: crypto.randomBytes(20),
      subject: { commonName: X520.CommonName("localhost") },
      validity: {
        notBefore,
        notAfter
      },
      issuer: { commonName: X520.CommonName("matter.js") },
      extensions: {
        basicConstraints: {
          isCa: true
        }
      },
      signatureAlgorithm: X962.EcdsaWithSHA256,
      publicKey: X962.PublicKeyEcPrime256v1(key.publicBits)
    };
    const certificate = await X509.sign(this.env.get(Crypto), key, unsigned);
    return X509.certificateToPem(certificate);
  }
}
((RemoteServer2) => {
  class Internal {
    interface;
    lock;
  }
  RemoteServer2.Internal = Internal;
  class State {
    /**
     * The public address at which the service endpoint is accessible.
     *
     * The address is a URL.  See subclasses for supported protocols.  An "s" suffix indicates standard TLS support.
     * The "+unix" suffix indicates that the hostname is a URL encoded path to a UNIX socket.  The socket path may
     * be absolute or relative to the node's storage root.
     *
     * The path portion of the URL generally acts as a namespace prefix for the relevant protocol implementation.
     * Matter.js replaces the special token `{node}` in the URL with the {@link ServerNode.id}.  This allows for
     * multiple nodes to participate in a protocol in separate namespaces.
     */
    address = "";
    /**
     * Set to false to disable this service.
     */
    enabled = true;
    /**
     * By default the HTTP endpoint is available as soon as the {@link Node} initializes.
     *
     * If you set this to false, the HTTP endpoint is only available when the {@link Node}'s Matter networking is
     * also online.
     */
    allowOfflineUse = true;
    /**
     * Certificate for encrypted endpoints in PEM format.
     */
    certificate;
    /**
     * Private key for encrypted endpoints in PEM format.
     */
    key;
  }
  RemoteServer2.State = State;
})(RemoteServer || (RemoteServer = {}));
export {
  RemoteServer
};
//# sourceMappingURL=RemoteServer.js.map
