"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/index.ts
var index_exports = {};
__export(index_exports, {
  WebSocketFetchHandler: () => WebSocketFetchHandler,
  eventStreamPayloadHandlerProvider: () => eventStreamPayloadHandlerProvider,
  getWebSocketPlugin: () => getWebSocketPlugin,
  resolveWebSocketConfig: () => resolveWebSocketConfig
});
module.exports = __toCommonJS(index_exports);

// src/EventStreamPayloadHandler.ts
var import_eventstream_codec = require("@smithy/eventstream-codec");

// src/get-event-signing-stream.ts
var import_util_hex_encoding = require("@smithy/util-hex-encoding");
var getEventSigningTransformStream = /* @__PURE__ */ __name((initialSignature, messageSigner, eventStreamCodec, systemClockOffsetProvider) => {
  let priorSignature = initialSignature;
  const transformer = {
    start() {
    },
    async transform(chunk, controller) {
      try {
        const now = new Date(Date.now() + await systemClockOffsetProvider());
        const dateHeader = {
          ":date": { type: "timestamp", value: now }
        };
        const signedMessage = await messageSigner.sign(
          {
            message: {
              body: chunk,
              headers: dateHeader
            },
            priorSignature
          },
          {
            signingDate: now
          }
        );
        priorSignature = signedMessage.signature;
        const serializedSigned = eventStreamCodec.encode({
          headers: {
            ...dateHeader,
            ":chunk-signature": {
              type: "binary",
              value: (0, import_util_hex_encoding.fromHex)(signedMessage.signature)
            }
          },
          body: chunk
        });
        controller.enqueue(serializedSigned);
      } catch (error) {
        controller.error(error);
      }
    }
  };
  return new TransformStream({ ...transformer });
}, "getEventSigningTransformStream");

// src/EventStreamPayloadHandler.ts
var EventStreamPayloadHandler = class {
  static {
    __name(this, "EventStreamPayloadHandler");
  }
  messageSigner;
  eventStreamCodec;
  systemClockOffsetProvider;
  constructor(options) {
    this.messageSigner = options.messageSigner;
    this.eventStreamCodec = new import_eventstream_codec.EventStreamCodec(options.utf8Encoder, options.utf8Decoder);
    this.systemClockOffsetProvider = async () => options.systemClockOffset ?? 0;
  }
  async handle(next, args, context = {}) {
    const request = args.request;
    const { body: payload, headers, query } = request;
    if (!(payload instanceof ReadableStream)) {
      throw new Error("Eventstream payload must be a ReadableStream.");
    }
    const placeHolderStream = new TransformStream();
    request.body = placeHolderStream.readable;
    let result;
    try {
      result = await next(args);
    } catch (e) {
      request.body.cancel();
      throw e;
    }
    const match = (headers["authorization"] || "").match(/Signature=([\w]+)$/);
    const priorSignature = (match || [])[1] || query && query["X-Amz-Signature"] || "";
    const signingStream = getEventSigningTransformStream(
      priorSignature,
      await this.messageSigner(),
      this.eventStreamCodec,
      this.systemClockOffsetProvider
    );
    const signedPayload = payload.pipeThrough(signingStream);
    signedPayload.pipeThrough(placeHolderStream);
    return result;
  }
};

// src/eventstream-payload-handler-provider.ts
var eventStreamPayloadHandlerProvider = /* @__PURE__ */ __name((options) => new EventStreamPayloadHandler(options), "eventStreamPayloadHandlerProvider");

// src/middleware-session-id.ts
var injectSessionIdMiddleware = /* @__PURE__ */ __name(() => (next) => async (args) => {
  const requestParams = {
    ...args.input
  };
  const response = await next(args);
  const output = response.output;
  if (requestParams.SessionId && output.SessionId == null) {
    output.SessionId = requestParams.SessionId;
  }
  return response;
}, "injectSessionIdMiddleware");
var injectSessionIdMiddlewareOptions = {
  step: "initialize",
  name: "injectSessionIdMiddleware",
  tags: ["WEBSOCKET", "EVENT_STREAM"],
  override: true
};

// src/middleware-websocket-endpoint.ts
var import_protocol_http = require("@smithy/protocol-http");
var websocketEndpointMiddleware = /* @__PURE__ */ __name((config, options) => (next) => (args) => {
  const { request } = args;
  if (import_protocol_http.HttpRequest.isInstance(request) && config.requestHandler.metadata?.handlerProtocol?.toLowerCase().includes("websocket")) {
    request.protocol = "wss:";
    request.method = "GET";
    request.path = `${request.path}-websocket`;
    const { headers } = request;
    delete headers["content-type"];
    delete headers["x-amz-content-sha256"];
    for (const name of Object.keys(headers)) {
      if (name.indexOf(options.headerPrefix) === 0) {
        const chunkedName = name.replace(options.headerPrefix, "");
        request.query[chunkedName] = headers[name];
      }
    }
    if (headers["x-amz-user-agent"]) {
      request.query["user-agent"] = headers["x-amz-user-agent"];
    }
    request.headers = { host: headers.host ?? request.hostname };
  }
  return next(args);
}, "websocketEndpointMiddleware");
var websocketEndpointMiddlewareOptions = {
  name: "websocketEndpointMiddleware",
  tags: ["WEBSOCKET", "EVENT_STREAM"],
  relation: "after",
  toMiddleware: "eventStreamHeaderMiddleware",
  override: true
};

// src/getWebSocketPlugin.ts
var getWebSocketPlugin = /* @__PURE__ */ __name((config, options) => ({
  applyToStack: /* @__PURE__ */ __name((clientStack) => {
    clientStack.addRelativeTo(websocketEndpointMiddleware(config, options), websocketEndpointMiddlewareOptions);
    clientStack.add(injectSessionIdMiddleware(), injectSessionIdMiddlewareOptions);
  }, "applyToStack")
}), "getWebSocketPlugin");

// src/WebsocketSignatureV4.ts


// src/utils.ts
var isWebSocketRequest = /* @__PURE__ */ __name((request) => request.protocol === "ws:" || request.protocol === "wss:", "isWebSocketRequest");

// src/WebsocketSignatureV4.ts
var WebsocketSignatureV4 = class {
  static {
    __name(this, "WebsocketSignatureV4");
  }
  signer;
  constructor(options) {
    this.signer = options.signer;
  }
  presign(originalRequest, options = {}) {
    return this.signer.presign(originalRequest, options);
  }
  async sign(toSign, options) {
    if (import_protocol_http.HttpRequest.isInstance(toSign) && isWebSocketRequest(toSign)) {
      const signedRequest = await this.signer.presign(
        { ...toSign, body: "" },
        {
          ...options,
          // presigned url must be expired within 1 min.
          expiresIn: 60,
          // Not to sign headers. Transcribe-streaming WebSocket
          // request omits headers except for required 'host' header. If we sign
          // the other headers, the signature could be mismatch.
          unsignableHeaders: new Set(Object.keys(toSign.headers).filter((header) => header !== "host"))
        }
      );
      return {
        ...signedRequest,
        body: toSign.body
      };
    } else {
      return this.signer.sign(toSign, options);
    }
  }
};

// src/websocket-configuration.ts
var resolveWebSocketConfig = /* @__PURE__ */ __name((input) => {
  const { signer } = input;
  return Object.assign(input, {
    signer: /* @__PURE__ */ __name(async (authScheme) => {
      const signerObj = await signer(authScheme);
      if (validateSigner(signerObj)) {
        return new WebsocketSignatureV4({ signer: signerObj });
      }
      throw new Error("Expected WebsocketSignatureV4 signer, please check the client constructor.");
    }, "signer")
  });
}, "resolveWebSocketConfig");
var validateSigner = /* @__PURE__ */ __name((signer) => !!signer, "validateSigner");

// src/websocket-fetch-handler.ts
var import_util_format_url = require("@aws-sdk/util-format-url");
var import_eventstream_serde_browser = require("@smithy/eventstream-serde-browser");
var import_fetch_http_handler = require("@smithy/fetch-http-handler");

var DEFAULT_WS_CONNECTION_TIMEOUT_MS = 2e3;
var WebSocketFetchHandler = class _WebSocketFetchHandler {
  static {
    __name(this, "WebSocketFetchHandler");
  }
  metadata = {
    handlerProtocol: "websocket/h1.1"
  };
  config;
  configPromise;
  httpHandler;
  sockets = {};
  /**
   * @returns the input if it is an HttpHandler of any class,
   * or instantiates a new instance of this handler.
   */
  static create(instanceOrOptions, httpHandler = new import_fetch_http_handler.FetchHttpHandler()) {
    if (typeof instanceOrOptions?.handle === "function") {
      return instanceOrOptions;
    }
    return new _WebSocketFetchHandler(
      instanceOrOptions,
      httpHandler
    );
  }
  constructor(options, httpHandler = new import_fetch_http_handler.FetchHttpHandler()) {
    this.httpHandler = httpHandler;
    if (typeof options === "function") {
      this.config = {};
      this.configPromise = options().then((opts) => this.config = opts ?? {});
    } else {
      this.config = options ?? {};
      this.configPromise = Promise.resolve(this.config);
    }
  }
  /**
   * Destroys the WebSocketHandler.
   * Closes all sockets from the socket pool.
   */
  destroy() {
    for (const [key, sockets] of Object.entries(this.sockets)) {
      for (const socket of sockets) {
        socket.close(1e3, `Socket closed through destroy() call`);
      }
      delete this.sockets[key];
    }
  }
  async handle(request) {
    if (!isWebSocketRequest(request)) {
      return this.httpHandler.handle(request);
    }
    const url = (0, import_util_format_url.formatUrl)(request);
    const socket = new WebSocket(url);
    if (!this.sockets[url]) {
      this.sockets[url] = [];
    }
    this.sockets[url].push(socket);
    socket.binaryType = "arraybuffer";
    this.config = await this.configPromise;
    const { connectionTimeout = DEFAULT_WS_CONNECTION_TIMEOUT_MS } = this.config;
    await this.waitForReady(socket, connectionTimeout);
    const { body } = request;
    const bodyStream = getIterator(body);
    const asyncIterable = this.connect(socket, bodyStream);
    const outputPayload = toReadableStream(asyncIterable);
    return {
      response: new import_protocol_http.HttpResponse({
        statusCode: 200,
        // indicates connection success
        body: outputPayload
      })
    };
  }
  updateHttpClientConfig(key, value) {
    this.configPromise = this.configPromise.then((config) => {
      config[key] = value;
      return config;
    });
  }
  httpHandlerConfigs() {
    return this.config ?? {};
  }
  /**
   * Removes all closing/closed sockets from the socket pool for URL.
   */
  removeNotUsableSockets(url) {
    this.sockets[url] = (this.sockets[url] ?? []).filter(
      (socket) => ![WebSocket.CLOSING, WebSocket.CLOSED].includes(socket.readyState)
    );
  }
  waitForReady(socket, connectionTimeout) {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        this.removeNotUsableSockets(socket.url);
        reject({
          $metadata: {
            httpStatusCode: 500
          }
        });
      }, connectionTimeout);
      socket.onopen = () => {
        clearTimeout(timeout);
        resolve();
      };
    });
  }
  connect(socket, data) {
    let streamError = void 0;
    let socketErrorOccurred = false;
    let reject = /* @__PURE__ */ __name(() => {
    }, "reject");
    let resolve = /* @__PURE__ */ __name(() => {
    }, "resolve");
    socket.onmessage = (event) => {
      resolve({
        done: false,
        value: new Uint8Array(event.data)
      });
    };
    socket.onerror = (error) => {
      socketErrorOccurred = true;
      socket.close();
      reject(error);
    };
    socket.onclose = () => {
      this.removeNotUsableSockets(socket.url);
      if (socketErrorOccurred) return;
      if (streamError) {
        reject(streamError);
      } else {
        resolve({
          done: true,
          value: void 0
          // unchecked because done=true.
        });
      }
    };
    const outputStream = {
      [Symbol.asyncIterator]: () => ({
        next: /* @__PURE__ */ __name(() => {
          return new Promise((_resolve, _reject) => {
            resolve = _resolve;
            reject = _reject;
          });
        }, "next")
      })
    };
    const send = /* @__PURE__ */ __name(async () => {
      try {
        for await (const inputChunk of data) {
          socket.send(inputChunk);
        }
      } catch (err) {
        streamError = err;
      } finally {
        socket.close(1e3);
      }
    }, "send");
    send();
    return outputStream;
  }
};
var getIterator = /* @__PURE__ */ __name((stream) => {
  if (stream[Symbol.asyncIterator]) {
    return stream;
  }
  if (isReadableStream(stream)) {
    return (0, import_eventstream_serde_browser.readableStreamtoIterable)(stream);
  }
  return {
    [Symbol.asyncIterator]: async function* () {
      yield stream;
    }
  };
}, "getIterator");
var toReadableStream = /* @__PURE__ */ __name((asyncIterable) => typeof ReadableStream === "function" ? (0, import_eventstream_serde_browser.iterableToReadableStream)(asyncIterable) : asyncIterable, "toReadableStream");
var isReadableStream = /* @__PURE__ */ __name((payload) => typeof ReadableStream === "function" && payload instanceof ReadableStream, "isReadableStream");
// Annotate the CommonJS export names for ESM import in node:

0 && (module.exports = {
  eventStreamPayloadHandlerProvider,
  getWebSocketPlugin,
  resolveWebSocketConfig,
  WebSocketFetchHandler
});

