import {
  BufferSizer,
  CLOSE_BEACON_PATH,
  DatadogLogSink,
  assert,
  downstreamSchema,
  getConfig,
  nullableVersionSchema,
  parse,
  valita_exports
} from "./chunk-4ZRNZFBK.js";
import {
  Ee,
  Hi,
  Ir,
  Nl,
  ROOM_ID_REGEX,
  Ye,
  ct,
  isValidRoomID,
  jt,
  lo,
  ut,
  version,
  zs
} from "./chunk-XXWJQHVF.js";
import "./chunk-F5QR3K72.js";

// ../reflect-client/src/client/reflect.ts
import { LogContext } from "@rocicorp/logger";
import { resolver as resolver4 } from "@rocicorp/resolver";

// ../shared/src/document-visible.ts
import { resolver } from "@rocicorp/resolver";
function getDocumentVisibilityWatcher(doc, hiddenIntervalMS, signal) {
  return doc ? new DocumentVisibilityWatcherImpl(doc, hiddenIntervalMS, signal) : new DocumentVisibilityWatcherNoDoc();
}
var DocumentVisibilityWatcherImpl = class {
  #doc;
  #hiddenIntervalMS;
  #timeoutID = 0;
  // This trails doc.visibilityState by hiddenIntervalMS when being hidden. This
  // is because we want to wait for the tab to be hidden for a while before
  // considering as hidden.
  visibilityState;
  #promises = /* @__PURE__ */ new Set();
  constructor(doc, hiddenIntervalMS, signal) {
    this.#doc = doc;
    this.#hiddenIntervalMS = hiddenIntervalMS;
    this.visibilityState = doc.visibilityState;
    this.#doc.addEventListener("visibilitychange", this.#onVisibilityChange, {
      signal
    });
  }
  #onVisibilityChange = () => {
    if (this.#doc.visibilityState === "visible") {
      clearTimeout(this.#timeoutID);
      this.#setVisibilityState("visible");
    } else {
      this.#timeoutID = setTimeout(() => {
        this.#setVisibilityState("hidden");
      }, this.#hiddenIntervalMS);
    }
  };
  #setVisibilityState(visibilityState) {
    if (visibilityState === this.visibilityState) {
      return;
    }
    this.visibilityState = visibilityState;
    for (const entry of this.#promises) {
      const { resolve, state } = entry;
      if (state === visibilityState) {
        resolve();
        this.#promises.delete(entry);
      }
    }
  }
  waitForVisible() {
    return this.#waitFor("visible");
  }
  waitForHidden() {
    return this.#waitFor("hidden");
  }
  #waitFor(state) {
    if (this.visibilityState === state) {
      return Promise.resolve();
    }
    const { promise, resolve } = resolver();
    this.#promises.add({ resolve, state });
    return promise;
  }
};
var resolvedPromise = Promise.resolve();
var promiseThatNeverResolves = new Promise(() => void 0);
var DocumentVisibilityWatcherNoDoc = class {
  visibilityState = "visible";
  waitForVisible() {
    return resolvedPromise;
  }
  waitForHidden() {
    return promiseThatNeverResolves;
  }
};

// ../shared/src/get-document.ts
function getDocument() {
  return typeof document !== "undefined" ? document : void 0;
}

// ../shared/src/get-window.ts
function getWindow() {
  return typeof window !== "undefined" ? window : void 0;
}

// ../shared/src/sleep.ts
import { resolver as resolver2 } from "@rocicorp/resolver";

// ../shared/src/abort-error.ts
var AbortError = class extends Error {
  name = "AbortError";
};

// ../shared/src/sleep.ts
var promiseVoid = Promise.resolve();
var promiseNever = new Promise(() => void 0);
function sleep(ms, signal) {
  if (ms === 0) {
    return promiseVoid;
  }
  return new Promise((resolve, reject) => {
    const id = setTimeout(() => {
      resolve();
    }, ms);
    if (signal) {
      signal.addEventListener("abort", () => {
        clearTimeout(id);
        reject(new AbortError("Aborted"));
      });
    }
  });
}
function sleepWithAbort(ms, signal) {
  if (ms === 0) {
    return [promiseVoid, promiseNever];
  }
  const { promise: abortedPromise, resolve: abortedResolve } = resolver2();
  const sleepPromise = new Promise((resolve) => {
    const handleAbort = () => {
      clearTimeout(id);
      abortedResolve();
    };
    const id = setTimeout(() => {
      resolve();
      signal.removeEventListener("abort", handleAbort);
    }, ms);
    signal.addEventListener("abort", handleAbort, { once: true });
  });
  return [sleepPromise, abortedPromise];
}

// ../reflect-client/src/util/nanoid.ts
function nanoid(size = 21) {
  return crypto.getRandomValues(new Uint8Array(size)).reduce((id, byte) => {
    byte &= 63;
    if (byte < 36) {
      id += byte.toString(36);
    } else if (byte < 62) {
      id += (byte - 26).toString(36).toUpperCase();
    } else if (byte > 62) {
      id += "-";
    } else {
      id += "_";
    }
    return id;
  }, "");
}

// ../reflect-client/src/util/socket.ts
function send(ws, data) {
  ws.send(JSON.stringify(data));
}

// ../reflect-client/src/client/close-beacon.ts
var CloseBeaconManager = class {
  #lc;
  #server;
  #reflect;
  #auth;
  #lastMutationID;
  #signal;
  #sent = false;
  constructor(reflect, lc, server, auth, lastMutationID, signal, window2) {
    this.#reflect = reflect;
    this.#lc = lc;
    this.#server = server;
    this.#auth = auth;
    this.#lastMutationID = lastMutationID;
    this.#signal = signal;
    this.#initForPageHide(window2);
  }
  #initForPageHide(window2) {
    if (getConfig("closeBeacon")) {
      window2?.addEventListener(
        "pagehide",
        (e) => {
          if (e.persisted) {
            return;
          }
          this.send("Pagehide");
        },
        { signal: this.#signal }
      );
    }
  }
  send(reason) {
    if (!getConfig("closeBeacon")) {
      return;
    }
    const lc = this.#lc.withContext("close-beacon", reason);
    if (this.#sent) {
      lc.debug?.("Not sending close beacon because already sent");
      return;
    }
    this.#sent = true;
    if (this.#server === null) {
      this.#lc.debug?.(
        `Not sending close beacon for ${reason} because server is null`
      );
      return;
    }
    const lastMutationID = this.#lastMutationID();
    const auth = this.#auth();
    const url = new URL(CLOSE_BEACON_PATH, this.#server);
    const params = {
      roomID: this.#reflect.roomID,
      userID: this.#reflect.userID,
      clientID: this.#reflect.clientID
    };
    url.search = new URLSearchParams(params).toString();
    const body = {
      lastMutationID
    };
    lc.debug?.("Sending close beacon", params, body);
    const headers = {
      "content-type": "application/json"
    };
    if (auth) {
      headers["authorization"] = `Bearer ${auth}`;
    }
    fetch(url, {
      method: "POST",
      headers,
      body: JSON.stringify(body),
      keepalive: true
    }).catch((e) => {
      lc.info?.("Failed to send close beacon", e);
    });
  }
};

// ../reflect-client/src/client/connect-checks.ts
import { resolver as resolver3 } from "@rocicorp/resolver";

// ../reflect-client/src/client/http-string.ts
function toWSString(url) {
  return "ws" + url.slice(4);
}
function toHTTPString(url) {
  return "http" + url.slice(2);
}
function assertHTTPString(url) {
  assert(/^https?:\/\//.test(url));
}
function assertWSString(url) {
  assert(/^wss?:\/\//.test(url));
}

// ../reflect-client/src/client/connect-checks.ts
async function checkConnectivity(reason, server, lc, signal, enableAnalytics) {
  const id = nanoid();
  lc = lc.withContext("connectCheckID", id).withContext("checkReason", reason);
  lc.info?.("Starting connectivity checks", {
    navigatorOnline: navigator.onLine
  });
  const socketOrigin = toWSString(server);
  const cfChecks = {
    cfGet: (_) => checkCfGet(id, server, signal),
    cfWebSocket: (l) => checkCfSocket(id, socketOrigin, false, l, signal),
    cfWebSocketWSecWebSocketProtocolHeader: (l) => checkCfSocket(id, socketOrigin, true, l, signal)
  };
  const renderChecks = {
    renderGet: (_) => checkRenderGet(id, signal),
    renderWebSocket: (l) => checkRenderSocket(id, false, l, signal),
    renderWebSocketWSecWebSocketProtocolHeader: (l) => checkRenderSocket(id, true, l, signal)
  };
  const checks = enableAnalytics ? {
    ...cfChecks,
    ...renderChecks
  } : cfChecks;
  const resultPs = [];
  for (const [checkName, check] of Object.entries(checks)) {
    resultPs.push(
      (async () => {
        const checkLc = lc.withContext("checkName", checkName);
        checkLc.info?.("Starting check");
        let result;
        try {
          result = await check(checkLc);
        } catch (e) {
          const detail = `Error: ${e instanceof Error ? `{name: ${e.name}, message: ${e.message}}` : e}`;
          result = { success: false, detail };
        }
        checkLc.info?.(checkName, result);
        return result;
      })()
    );
  }
  const results = await Promise.all(resultPs);
  if (signal.aborted) {
    return;
  }
  lc.info?.(
    "Connectivity checks summary\n",
    ...Object.keys(checks).map(
      (checkName, i) => `${checkName}=${results[i].success}
`
    ),
    {
      navigatorOnline: navigator.onLine
    }
  );
  lc.info?.(
    "Connectivity checks detail\n",
    ...Object.keys(checks).flatMap(
      (checkName, i) => `${checkName}=${results[i].detail}
`
    ),
    {
      navigatorOnline: navigator.onLine
    }
  );
}
function checkRenderGet(id, signal) {
  return checkGet(
    id,
    "https://networkcheck.reflect-server.net/canary/get",
    signal
  );
}
function checkCfGet(id, server, signal) {
  const cfGetCheckBaseURL = new URL(server);
  cfGetCheckBaseURL.pathname = "/api/canary/v0/get";
  const url = cfGetCheckBaseURL.toString();
  assertHTTPString(url);
  return checkGet(id, url, signal);
}
function checkGet(id, baseURL, signal) {
  const getCheckURL = new URL(baseURL);
  getCheckURL.searchParams.set("id", id);
  return Promise.race([
    timeout(signal),
    (async () => {
      const response = await fetch(getCheckURL, { signal });
      return {
        success: response.status === 200,
        detail: `Got response ${response.status} "${await response.text()}"`
      };
    })()
  ]);
}
function checkRenderSocket(id, wSecWebSocketProtocolHeader, lc, signal) {
  return checkSocket(
    id,
    "wss://networkcheck.reflect-server.net/canary/websocket",
    wSecWebSocketProtocolHeader,
    lc,
    signal
  );
}
function checkCfSocket(id, socketOrigin, wSecWebSocketProtocolHeader, lc, signal) {
  const cfSocketCheckBaseURL = new URL(socketOrigin);
  cfSocketCheckBaseURL.pathname = "/api/canary/v0/websocket";
  const url = cfSocketCheckBaseURL.toString();
  assertWSString(url);
  return checkSocket(id, url, wSecWebSocketProtocolHeader, lc, signal);
}
async function checkSocket(id, socketBaseURL, wSecWebSocketProtocolHeader, lc, signal) {
  const socketCheckURL = new URL(socketBaseURL);
  socketCheckURL.searchParams.set("id", id);
  socketCheckURL.searchParams.set(
    "wSecWebSocketProtocolHeader",
    wSecWebSocketProtocolHeader ? "true" : "false"
  );
  const cfWebSocket = wSecWebSocketProtocolHeader ? new WebSocket(socketCheckURL, "check-" + id) : new WebSocket(socketCheckURL);
  const { promise, resolve } = resolver3();
  const onMessage = (e) => {
    lc.info?.("Received message", e.data);
    resolve({
      success: true,
      detail: `Connected and received message "${e.data}"`
    });
  };
  const onOpen = () => {
    lc.info?.("Open event");
  };
  const onClose = (e) => {
    const { code, reason, wasClean } = e;
    const closeInfo = {
      code,
      reason,
      wasClean
    };
    lc.info?.("Received close", closeInfo);
    resolve({
      success: false,
      detail: `Closed before connected ${JSON.stringify(closeInfo)}.`
    });
  };
  try {
    cfWebSocket.addEventListener("message", onMessage);
    cfWebSocket.addEventListener("open", onOpen);
    cfWebSocket.addEventListener("close", onClose);
    return await Promise.race([timeout(signal), promise]);
  } finally {
    cfWebSocket.removeEventListener("message", onMessage);
    cfWebSocket.removeEventListener("open", onOpen);
    cfWebSocket.removeEventListener("close", onClose);
    cfWebSocket.close();
  }
}
var TIMEOUT_MS = 1e4;
async function timeout(signal) {
  await sleep(TIMEOUT_MS, signal);
  return { success: false, detail: "Timed out." };
}

// ../reflect-client/src/client/enable-analytics.ts
var IPV4_ADDRESS_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
var IPV6_ADDRESS_HOSTNAME_REGEX = /^\[[a-fA-F0-9:]*:[a-fA-F0-9:]*\]$/;
var IP_ADDRESS_HOSTNAME_REGEX = new RegExp(
  `(${IPV4_ADDRESS_REGEX.source}|${IPV6_ADDRESS_HOSTNAME_REGEX.source})`
);
function shouldEnableAnalytics(server, enableAnalytics = true) {
  if (!enableAnalytics) {
    return false;
  }
  const serverURL = server === null ? null : new URL(server);
  const socketHostname = serverURL?.hostname;
  return server !== null && socketHostname !== void 0 && socketHostname !== "localhost" && !IP_ADDRESS_HOSTNAME_REGEX.test(socketHostname);
}

// ../reflect-client/src/client/log-options.ts
import {
  TeeLogSink,
  consoleLogSink
} from "@rocicorp/logger";
var LevelFilterLogSink = class {
  #wrappedLogSink;
  #level;
  constructor(wrappedLogSink, level) {
    this.#wrappedLogSink = wrappedLogSink;
    this.#level = level;
  }
  log(level, context, ...args) {
    if (this.#level === "error" && level !== "error") {
      return;
    }
    if (this.#level === "info" && level === "debug") {
      return;
    }
    this.#wrappedLogSink.log(level, context, ...args);
  }
  async flush() {
    await consoleLogSink.flush?.();
  }
};
var DATADOG_LOG_LEVEL = "info";
var REFLECT_SAAS_DOMAIN = ".reflect-server.net";
function createLogOptions(options, createDatadogLogSink = (options2) => new DatadogLogSink(options2)) {
  const { consoleLogLevel, server, enableAnalytics } = options;
  if (!enableAnalytics || server === null) {
    return {
      logLevel: consoleLogLevel,
      logSink: consoleLogSink
    };
  }
  const serverURL = new URL(server);
  const socketHostname = serverURL.hostname;
  const datadogServiceLabel = socketHostname.endsWith(REFLECT_SAAS_DOMAIN) ? socketHostname.substring(0, socketHostname.length - REFLECT_SAAS_DOMAIN.length).toLowerCase() : socketHostname;
  const baseURL = new URL("/api/logs/v0/log", server);
  const logLevel = consoleLogLevel === "debug" ? "debug" : "info";
  const logSink = new TeeLogSink([
    new LevelFilterLogSink(consoleLogSink, consoleLogLevel),
    new LevelFilterLogSink(
      createDatadogLogSink({
        service: datadogServiceLabel,
        host: location.host,
        version,
        baseURL
      }),
      DATADOG_LOG_LEVEL
    )
  ]);
  return {
    logLevel,
    logSink
  };
}

// ../reflect-client/src/client/metrics.ts
var DID_NOT_CONNECT_VALUE = 100 * 1e3;
var REPORT_INTERVAL_MS = 5e3;
function getLastConnectErrorValue(reason) {
  if ("server" in reason) {
    return `server_${camelToSnake(reason.server)}`;
  }
  return `client_${camelToSnake(reason.client)}`;
}
function camelToSnake(s) {
  return s.split(/\.?(?=[A-Z])/).join("_").toLowerCase();
}
var MetricManager = class {
  #reportIntervalMs;
  #host;
  #reporter;
  #lc;
  #timerID;
  constructor(opts) {
    this.#reportIntervalMs = opts.reportIntervalMs;
    this.#host = opts.host;
    this.#reporter = opts.reporter;
    this.#lc = opts.lc;
    this.tags.push(`source:${opts.source}`);
    this.timeToConnectMs.set(DID_NOT_CONNECT_VALUE);
    this.#setNotConnectedReason("init");
    this.#timerID = setInterval(() => {
      void this.flush();
    }, this.#reportIntervalMs);
  }
  #metrics = [];
  // timeToConnectMs measures the time from the call to connect() to receiving
  // the 'connected' ws message. We record the DID_NOT_CONNECT_VALUE if the previous
  // connection attempt failed for any reason.
  //
  // We set the gauge using #connectStart as follows:
  // - #connectStart is undefined if we are disconnected or connected; it is
  //   defined only in the Connecting state, as a number representing the timestamp
  //   at which we started connecting.
  // - #connectStart is set to the current time when connect() is called.
  // - When we receive the 'connected' message we record the time to connect and
  //   set #connectStart to undefined.
  // - If disconnect() is called with a defined #connectStart then we record
  //   DID_NOT_CONNECT_VALUE and set #connectStart to undefined.
  //
  // TODO It's clear after playing with the connection code we should encapsulate
  // the ConnectionState along with its state transitions and possibly behavior.
  // In that world the metric gauge(s) and bookkeeping like #connectStart would
  // be encapsulated with the ConnectionState. This will probably happen as part
  // of https://github.com/rocicorp/reflect-server/issues/255.
  timeToConnectMs = this.#register(
    new Gauge("time_to_connect_ms" /* TimeToConnectMs */)
  );
  // lastConnectError records the last error that occurred when connecting,
  // if any. It is cleared when connecting successfully or when reported, so this
  // state only gets reported if there was a failure during the reporting period and
  // we are still not connected.
  lastConnectError = this.#register(
    new State(
      "last_connect_error" /* LastConnectError */,
      true
      // clearOnFlush
    )
  );
  // notConnected records the reason why the client is not currently connected.
  // It is cleared when the client successfully connects.
  #notConnected = this.#register(new State("not_connected" /* NotConnected */));
  // The time from the call to connect() to receiving the 'connected' ws message
  // for the current connection.  Cleared when the client is not connected.
  // TODO: Not actually currently cleared on disconnect untill there is a
  // connect error, or client reports disconnected and waiting for visible.
  // Should have a value iff _notConnected has no value.
  #timeToConnectMsV2 = this.#register(
    new Gauge("time_to_connect_ms_v2" /* TimeToConnectMsV2 */)
  );
  // lastConnectErrorV2 records the last error that occurred when connecting,
  // if any. It is cleared when the client successfully connects or
  // stops trying to connect due to being hidden.
  // Should have a value iff notConnected state is NotConnectedReason.Error.
  #lastConnectErrorV2 = this.#register(
    new State("last_connect_error_v2" /* LastConnectErrorV2 */)
  );
  // The total time it took to connect across retries for the current
  // connection.  Cleared when the client is not connected.
  // TODO: Not actually currently cleared on disconnect until there is a
  // connect error, or client reports disconnected and waiting for visible.
  // See Reflect.#totalToConnectStart for details of how this total is computed.
  // Should have a value iff _notConnected has no value.
  #totalTimeToConnectMs = this.#register(
    new Gauge("total_time_to_connect_ms" /* TotalTimeToConnectMs */)
  );
  #setNotConnectedReason(reason) {
    this.#notConnected.set(reason);
  }
  setConnected(timeToConnectMs, totalTimeToConnectMs) {
    this.#notConnected.clear();
    this.#lastConnectErrorV2.clear();
    this.#timeToConnectMsV2.set(timeToConnectMs);
    this.#totalTimeToConnectMs.set(totalTimeToConnectMs);
  }
  setDisconnectedWaitingForVisible() {
    this.#timeToConnectMsV2.clear();
    this.#totalTimeToConnectMs.clear();
    this.#lastConnectErrorV2.clear();
    let notConnectedReason;
    switch (this.#notConnected.get()) {
      case "init":
        notConnectedReason = "hidden_was_init";
        break;
      case "error":
        notConnectedReason = "hidden_was_error";
        break;
      default:
        notConnectedReason = "hidden";
        break;
    }
    this.#setNotConnectedReason(notConnectedReason);
  }
  setConnectError(reason) {
    this.#timeToConnectMsV2.clear();
    this.#totalTimeToConnectMs.clear();
    this.#setNotConnectedReason("error");
    this.#lastConnectErrorV2.set(getLastConnectErrorValue(reason));
  }
  /**
   * Tags to include in all metrics.
   */
  tags = [];
  // Flushes all metrics to an array of time series (plural), one Series
  // per metric.
  async flush() {
    const lc = this.#lc;
    if (this.#timerID === null) {
      lc.error?.("MetricManager.flush() called but already stopped");
      return;
    }
    const allSeries = [];
    for (const metric of this.#metrics) {
      const series = metric.flush();
      if (series !== void 0) {
        allSeries.push({
          ...series,
          host: this.#host,
          tags: this.tags
        });
      }
    }
    if (allSeries.length === 0) {
      lc?.debug?.("No metrics to report");
      return;
    }
    try {
      await this.#reporter(allSeries);
    } catch (e) {
      lc?.error?.(`Error reporting metrics: ${e}`);
    }
  }
  stop() {
    if (this.#timerID === null) {
      this.#lc.error?.("MetricManager.stop() called but already stopped");
      return;
    }
    clearInterval(this.#timerID);
    this.#timerID = null;
  }
  #register(metric) {
    this.#metrics.push(metric);
    return metric;
  }
};
function makePoint(ts, value) {
  return [ts, [value]];
}
var Gauge = class {
  #name;
  #value = void 0;
  constructor(name) {
    this.#name = name;
  }
  set(value) {
    this.#value = value;
  }
  get() {
    return this.#value;
  }
  clear() {
    this.#value = void 0;
  }
  flush() {
    if (this.#value === void 0) {
      return void 0;
    }
    const points = [makePoint(t(), this.#value)];
    return { metric: this.#name, points };
  }
};
function t() {
  return Math.round(Date.now() / 1e3);
}
var State = class {
  #prefix;
  #clearOnFlush;
  #current = void 0;
  constructor(prefix, clearOnFlush = false) {
    this.#prefix = prefix;
    this.#clearOnFlush = clearOnFlush;
  }
  set(state) {
    this.#current = state;
  }
  get() {
    return this.#current;
  }
  clear() {
    this.#current = void 0;
  }
  flush() {
    if (this.#current === void 0) {
      return void 0;
    }
    const gauge = new Gauge([this.#prefix, this.#current].join("_"));
    gauge.set(1);
    const series = gauge.flush();
    if (this.#clearOnFlush) {
      this.clear();
    }
    return series;
  }
};

// ../reflect-client/src/client/poke-handler.ts
import { Lock } from "@rocicorp/lock";

// ../reflect-client/src/client/merge-pokes.ts
function mergePokes(toMerge) {
  if (toMerge.length === 0) {
    return void 0;
  }
  const mergedPatch = [];
  const mergedPresence = [];
  const mergedLastMutationIDChanges = {};
  for (const poke of toMerge) {
    mergedPatch.push(...poke.patch);
    mergedPresence.push(...poke.presence ?? []);
    for (const [clientID, lastMutationID] of Object.entries(
      poke.lastMutationIDChanges
    )) {
      mergedLastMutationIDChanges[clientID] = lastMutationID;
    }
  }
  return {
    baseCookie: toMerge[0].baseCookie,
    cookie: toMerge[toMerge.length - 1].cookie,
    lastMutationIDChanges: mergedLastMutationIDChanges,
    patch: mergedPatch,
    presence: mergedPresence,
    timestamp: toMerge[0].timestamp
  };
}

// ../reflect-client/src/client/poke-handler.ts
var BUFFER_SIZER_OPTIONS = {
  initialBufferSizeMs: 50,
  maxBufferSizeMs: 1e3,
  minBufferSizeMs: -1e3,
  adjustBufferSizeIntervalMs: 5e3
};
var FRAME_INTERVAL_TOLERANCE_MS = 18;
var RESET_PLAYBACK_OFFSET_THRESHOLD_MS = 1e3;
var MAX_RECENT_POKE_LATENCIES_SIZE = 10;
var PokeHandler = class {
  #replicachePoke;
  #presenceManager;
  #onOutOfOrderPoke;
  #clientID;
  #lc;
  #pokeBuffer = [];
  #bufferSizer;
  #maxRecentPokeLatenciesSize;
  #pokePlaybackLoopRunning = false;
  #lastRafPerfTimestamp = 0;
  #playbackOffsetMs = void 0;
  // Serializes calls to this._replicachePoke otherwise we can cause out of
  // order poke errors.
  #pokeLock = new Lock();
  #timedPokeCount = 0;
  #missedTimedPokeCount = 0;
  #timedFrameCount = 0;
  #missedTimedFrameCount = 0;
  #timedPokeLatencyTotal = 0;
  #recentPokeLatencies = [];
  constructor(replicachePoke, presenceManager, onOutOfOrderPoke, clientID, lc, bufferSizer = new BufferSizer(BUFFER_SIZER_OPTIONS), maxRecentPokeLatenciesSize = MAX_RECENT_POKE_LATENCIES_SIZE) {
    this.#replicachePoke = replicachePoke;
    this.#presenceManager = presenceManager;
    this.#onOutOfOrderPoke = onOutOfOrderPoke;
    this.#clientID = clientID;
    this.#lc = lc.withContext("PokeHandler");
    this.#bufferSizer = bufferSizer;
    this.#maxRecentPokeLatenciesSize = maxRecentPokeLatenciesSize;
  }
  handlePoke(pokeBody) {
    const lc = this.#lc.withContext("requestID", pokeBody.requestID);
    lc.debug?.("Applying poke", pokeBody);
    if (pokeBody.debugServerBufferMs) {
      lc.debug?.("server buffer ms", pokeBody.debugServerBufferMs);
    }
    const now = Date.now();
    const thisClientID = this.#clientID;
    let lastMutationIDChangeForSelf;
    let bufferNeededMs = void 0;
    for (const poke of pokeBody.pokes) {
      const { timestamp } = poke;
      if (timestamp !== void 0) {
        const timestampOffset = now - timestamp;
        if (this.#playbackOffsetMs === void 0 || Math.abs(timestampOffset - this.#playbackOffsetMs) > RESET_PLAYBACK_OFFSET_THRESHOLD_MS) {
          this.#bufferSizer.reset();
          this.#playbackOffsetMs = timestampOffset;
          lc.debug?.("new playback offset", timestampOffset);
        }
        bufferNeededMs = now - (timestamp + this.#playbackOffsetMs) + FRAME_INTERVAL_TOLERANCE_MS;
        break;
      }
    }
    for (const poke of pokeBody.pokes) {
      if (poke.lastMutationIDChanges[thisClientID] !== void 0) {
        lastMutationIDChangeForSelf = poke.lastMutationIDChanges[thisClientID];
      }
      let normalizedTimestamp = void 0;
      if (poke.timestamp !== void 0) {
        assert(this.#playbackOffsetMs !== void 0);
        normalizedTimestamp = poke.timestamp + this.#playbackOffsetMs;
      }
      const pendingPoke = {
        ...poke,
        serverBufferMs: pokeBody.debugServerBufferMs,
        playbackOffsetMs: this.#playbackOffsetMs,
        receivedTimestamp: now,
        normalizedTimestamp,
        bufferNeededMs
      };
      this.#pokeBuffer.push(pendingPoke);
    }
    if (this.#pokeBuffer.length > 0 && !this.#pokePlaybackLoopRunning) {
      this.#startPlaybackLoop(lc);
    }
    return lastMutationIDChangeForSelf;
  }
  #startPlaybackLoop(lc) {
    lc.debug?.("starting playback loop");
    this.#pokePlaybackLoopRunning = true;
    requestAnimationFrame(this.#rafCallback);
  }
  #rafCallback = async () => {
    const rafLC = this.#lc.withContext("rafAt", Math.floor(performance.now()));
    if (this.#pokeBuffer.length === 0) {
      rafLC.debug?.("stopping playback loop");
      this.#pokePlaybackLoopRunning = false;
      return;
    }
    requestAnimationFrame(this.#rafCallback);
    const start = performance.now();
    rafLC.debug?.(
      "raf fired, processing pokes.  Since last raf",
      start - this.#lastRafPerfTimestamp
    );
    this.#lastRafPerfTimestamp = start;
    await this.#processPokesForFrame(rafLC);
    rafLC.debug?.("processing pokes took", performance.now() - start);
  };
  async #processPokesForFrame(lc) {
    await this.#pokeLock.withLock(async () => {
      const now = Date.now();
      lc.debug?.("got poke lock at", now);
      const toMerge = [];
      const thisClientID = this.#clientID;
      let maxBufferNeededMs = Number.MIN_SAFE_INTEGER;
      let timedPokeCount = 0;
      let missedTimedPokeCount = 0;
      while (this.#pokeBuffer.length) {
        const headPoke = this.#pokeBuffer[0];
        const { normalizedTimestamp, lastMutationIDChanges } = headPoke;
        const lastMutationIDChangesClientIDs = Object.keys(
          lastMutationIDChanges
        );
        const isThisClientsMutation = lastMutationIDChangesClientIDs.length === 1 && lastMutationIDChangesClientIDs[0] === thisClientID;
        if (!isThisClientsMutation && normalizedTimestamp !== void 0) {
          const pokePlaybackTarget = normalizedTimestamp + this.#bufferSizer.bufferSizeMs;
          const pokePlaybackOffset = now - pokePlaybackTarget;
          if (pokePlaybackOffset < 0) {
            break;
          }
          const { bufferNeededMs } = headPoke;
          if (bufferNeededMs !== void 0) {
            maxBufferNeededMs = Math.max(maxBufferNeededMs, bufferNeededMs);
          }
          if (headPoke.debugOriginTimestamp !== void 0) {
            const serverReceivedLatency = (headPoke.debugServerReceivedTimestamp ?? 0) - headPoke.debugOriginTimestamp;
            const serverSentLatency = (headPoke.debugServerSentTimestamp ?? 0) - headPoke.debugOriginTimestamp;
            const clientReceivedLatency = headPoke.receivedTimestamp - headPoke.debugOriginTimestamp;
            const playbackLatency = now - headPoke.debugOriginTimestamp;
            this.#timedPokeLatencyTotal += playbackLatency;
            this.#recentPokeLatencies.unshift(playbackLatency);
            if (this.#recentPokeLatencies.length > this.#maxRecentPokeLatenciesSize) {
              this.#recentPokeLatencies.length = this.#maxRecentPokeLatenciesSize;
            }
            lc.debug?.(
              "poke latency breakdown:",
              "\nserver received:",
              serverReceivedLatency,
              "(+",
              serverReceivedLatency,
              ")",
              "\nserver sent (server buffer",
              headPoke.serverBufferMs,
              "):",
              serverSentLatency,
              "(+",
              serverSentLatency - serverReceivedLatency,
              ")",
              "\nclient received:",
              clientReceivedLatency,
              "(+",
              clientReceivedLatency - serverSentLatency,
              ")",
              "\nplayback (offset",
              headPoke.playbackOffsetMs,
              ", buffer",
              this.#bufferSizer.bufferSizeMs,
              "):",
              playbackLatency,
              "(+",
              playbackLatency - clientReceivedLatency,
              ")"
            );
          }
          timedPokeCount++;
          this.#timedPokeCount++;
          if (pokePlaybackOffset > FRAME_INTERVAL_TOLERANCE_MS) {
            lc.debug?.(
              "poke",
              this.#timedPokeCount,
              "playback missed by",
              pokePlaybackOffset - FRAME_INTERVAL_TOLERANCE_MS
            );
            this.#missedTimedPokeCount++;
            missedTimedPokeCount++;
          }
        }
        const poke = this.#pokeBuffer.shift();
        assert(poke);
        toMerge.push(poke);
      }
      if (timedPokeCount > 0) {
        this.#timedFrameCount++;
        assert(maxBufferNeededMs !== Number.MIN_SAFE_INTEGER);
        this.#bufferSizer.recordMissable(
          now,
          missedTimedPokeCount > 0,
          maxBufferNeededMs,
          lc
        );
        if (missedTimedPokeCount > 0) {
          this.#missedTimedFrameCount++;
          lc.debug?.(
            "frame",
            this.#timedFrameCount,
            "contains",
            missedTimedPokeCount,
            "missed pokes"
          );
        }
      }
      lc.debug?.(
        "merging",
        toMerge.length,
        "remaining buffer length",
        this.#pokeBuffer.length
      );
      const merged = mergePokes(toMerge);
      if (merged === void 0) {
        lc.debug?.("frame is empty");
        return;
      }
      try {
        const start = performance.now();
        const { lastMutationIDChanges, baseCookie, patch, cookie } = merged;
        const poke = {
          baseCookie,
          pullResponse: {
            lastMutationIDChanges,
            patch,
            cookie
          }
        };
        lc.debug?.("poking replicache");
        await this.#replicachePoke(poke);
        await this.#presenceManager.updatePresence(merged.presence ?? []);
        lc.debug?.("poking replicache took", performance.now() - start);
      } catch (e) {
        if (String(e).indexOf("unexpected base cookie for poke") > -1) {
          await this.#onOutOfOrderPoke();
        }
      }
      lc.debug?.(
        "playback stats (misses / total = percent missed):",
        "\npokes:",
        this.#missedTimedPokeCount,
        "/",
        this.#timedPokeCount,
        "=",
        this.#missedTimedPokeCount / this.#timedPokeCount,
        "\nframes:",
        this.#missedTimedFrameCount,
        "/",
        this.#timedFrameCount,
        "=",
        this.#missedTimedFrameCount / this.#timedFrameCount,
        "\navg poke latency:",
        this.#timedPokeLatencyTotal / this.#timedPokeCount,
        "\nrecent poke latencies:",
        this.#recentPokeLatencies
      );
    });
  }
  handleDisconnect() {
    this.#lc.debug?.("clearing buffer and playback offset due to disconnect");
    this.#pokeBuffer.length = 0;
    this.#playbackOffsetMs = void 0;
    this.#bufferSizer.reset();
  }
};

// ../reflect-client/src/client/presence-manager.ts
var PresenceManager = class {
  #clientID;
  #lc;
  #subscriptions = /* @__PURE__ */ new Set();
  #pendingInitial = /* @__PURE__ */ new Set();
  #initialRunsScheduled = false;
  #presentClientIDs;
  constructor(clientID, lc) {
    this.#clientID = clientID;
    this.#presentClientIDs = [clientID];
    this.#lc = lc;
  }
  updatePresence(patch) {
    if (patch.length === 0) {
      return;
    }
    const prior = this.#presentClientIDs;
    const updated = new Set(this.#presentClientIDs);
    for (const op of patch) {
      switch (op.op) {
        case "clear":
          updated.clear();
          break;
        case "put":
          updated.add(op.key);
          break;
        case "del":
          updated.delete(op.key);
          break;
      }
    }
    updated.add(this.#clientID);
    if (!setEqual(prior, updated)) {
      this.#presentClientIDs = Array.from(updated);
      for (const sub of this.#subscriptions) {
        callSubscriptionCallback(sub, this.#presentClientIDs, this.#lc);
        this.#pendingInitial.delete(sub);
      }
    }
  }
  addSubscription(callback) {
    const subscription = {
      callback
    };
    this.#subscriptions.add(subscription);
    this.#scheduleInitialSubscriptionRun(subscription);
    return () => {
      this.#subscriptions.delete(subscription);
    };
  }
  #scheduleInitialSubscriptionRun(subscription) {
    this.#pendingInitial.add(subscription);
    if (!this.#initialRunsScheduled) {
      this.#initialRunsScheduled = true;
      queueMicrotask(() => {
        const lc = this.#lc;
        this.#initialRunsScheduled = false;
        for (const sub of this.#pendingInitial) {
          if (this.#subscriptions.has(sub)) {
            callSubscriptionCallback(sub, this.#presentClientIDs, lc);
            this.#pendingInitial.delete(sub);
          }
        }
      });
    }
  }
  clearSubscriptions() {
    this.#subscriptions.clear();
  }
  handleDisconnect() {
    this.#updateToOnlySelfPresent();
  }
  #updateToOnlySelfPresent() {
    this.updatePresence([{ op: "clear" }]);
  }
};
function callSubscriptionCallback(sub, presentClientIDs, lc) {
  try {
    sub.callback(presentClientIDs);
  } catch (e) {
    lc.error?.("Error in presence subscription callback:", e);
    queueMicrotask(() => {
      throw new Error("Error in presence subscription callback.", { cause: e });
    });
  }
}
function setEqual(a, b) {
  if (a.length !== b.size) {
    return false;
  }
  for (const el of a) {
    if (!b.has(el)) {
      return false;
    }
  }
  return true;
}

// ../reflect-client/src/client/reload-error-handler.ts
var RELOAD_REASON_STORAGE_KEY = "_reflectReloadReason";
function reloadWithReason(lc, reload, reason) {
  if (typeof localStorage === "undefined") {
    lc.error?.("Reflect reloaded the page.", reason);
  } else {
    localStorage[RELOAD_REASON_STORAGE_KEY] = reason;
  }
  reload();
}
function reportReloadReason(lc) {
  if (typeof localStorage === "undefined") {
    return;
  }
  const reason = localStorage[RELOAD_REASON_STORAGE_KEY];
  if (reason) {
    delete localStorage[RELOAD_REASON_STORAGE_KEY];
    lc.error?.("Reflect reloaded the page.", reason);
  }
}

// ../reflect-client/src/client/server-error.ts
var ServerError = class extends Error {
  kind;
  name = "ServerError";
  constructor(kind, message) {
    super(kind + ": " + message);
    this.kind = kind;
  }
};
function isServerError(ex) {
  return ex instanceof ServerError;
}
function isAuthError(ex) {
  return isServerError(ex) && isAuthErrorKind(ex.kind);
}
function isAuthErrorKind(kind) {
  return kind === "AuthInvalidated" || kind === "Unauthorized";
}

// ../reflect-client/src/client/server-option.ts
function validateServerParam(paramName, server, expectedProtocol) {
  const forExample = () => ` For example: "${expectedProtocol}s://myapp-myteam.reflect.net/".`;
  if (!server.startsWith(`${expectedProtocol}://`) && !server.startsWith(`${expectedProtocol}s://`)) {
    throw new Error(
      `ReflectOptions.${paramName} must use the "${expectedProtocol}" or "${expectedProtocol}s" scheme.`
    );
  }
  let url;
  try {
    url = new URL(server);
  } catch {
    throw new Error(
      `ReflectOptions.${paramName} must be a valid URL.${forExample()}`
    );
  }
  const urlString = url.toString();
  if (url.pathname !== "/") {
    throw new Error(
      `ReflectOptions.${paramName} must not contain a path component (other than "/").${forExample()}`
    );
  }
  for (const [property, invalidEndsWith] of [
    ["search", "?"],
    ["hash", "#"]
  ]) {
    if (url[property] || urlString.endsWith(invalidEndsWith)) {
      throw new Error(
        `ReflectOptions.${paramName} must not contain a ${property} component.${forExample()}`
      );
    }
  }
  return urlString;
}
function getServer(server, socketOrigin) {
  if (server) {
    return validateServerParam("server", server, "http");
  }
  if (socketOrigin) {
    const validatedSocketOrigin = validateServerParam(
      "socketOrigin",
      socketOrigin,
      "ws"
    );
    return toHTTPString(validatedSocketOrigin);
  }
  return null;
}

// ../reflect-client/src/client/reflect.ts
var onSetConnectionStateSymbol = Symbol();
var exposedToTestingSymbol = Symbol();
var createLogOptionsSymbol = Symbol();
var RUN_LOOP_INTERVAL_MS = 5e3;
var PING_INTERVAL_MS = 5e3;
var PING_TIMEOUT_MS = 5e3;
var PULL_TIMEOUT_MS = 5e3;
var DEFAULT_DISCONNECT_HIDDEN_DELAY_MS = 5e3;
var CONNECT_TIMEOUT_MS = 1e4;
var CHECK_CONNECTIVITY_ON_ERROR_FREQUENCY = 6;
var NULL_LAST_MUTATION_ID_SENT = { clientID: "", id: -1 };
var REFLECT_VERSION = 1;
function serverAheadReloadReason(kind) {
  return `Server reported that client is ahead of server (${kind}). This probably happened because the server is in development mode and restarted. Currently when this happens, the dev server loses its state and on reconnect sees the client as ahead. If you see this in other cases, it may be a bug in Reflect.`;
}
function convertOnUpdateNeededReason(reason) {
  return { type: reason.type };
}
var Reflect = class {
  version = version;
  #rep;
  #server;
  userID;
  roomID;
  #lc;
  #logOptions;
  #enableAnalytics;
  #pokeHandler;
  #presenceManager;
  #lastMutationIDSent = NULL_LAST_MUTATION_ID_SENT;
  #onPong = () => void 0;
  #online = false;
  /**
   * `onOnlineChange` is called when the Reflect instance's online status
   * changes.
   */
  onOnlineChange = null;
  #onUpdateNeeded;
  #jurisdiction;
  #baseCookie = null;
  // Total number of sockets successfully connected by this client
  #connectedCount = 0;
  // Number of messages received over currently connected socket.  Reset
  // on disconnect.
  #messageCount = 0;
  #connectedAt = 0;
  // Reset on successful connection.
  #connectErrorCount = 0;
  #abortPingTimeout = () => {
  };
  #internalAPI;
  #closeBeaconManager;
  /**
   * `onUpdateNeeded` is called when a code update is needed.
   *
   * A code update can be needed because:
   * - the server no longer supports the protocol version of the current code,
   * - a new Reflect client has created a new client group, because its code
   *   has different mutators, indexes, schema version and/or format version
   *   from this Reflect client. This is likely due to the new client having
   *   newer code. A code update is needed to be able to locally sync with this
   *   new Reflect client (i.e. to sync while offline, the clients can can
   *   still sync with each other via the server).
   *
   * The default behavior is to reload the page (using `location.reload()`). Set
   * this to `null` or provide your own function to prevent the page from
   * reloading automatically. You may want to provide your own function to
   * display a toast to inform the end user there is a new version of your app
   * available and prompting them to refresh.
   */
  get onUpdateNeeded() {
    return this.#onUpdateNeeded;
  }
  set onUpdateNeeded(callback) {
    this.#onUpdateNeeded = callback;
    this.#rep.onUpdateNeeded = callback && ((reason) => {
      callback(convertOnUpdateNeededReason(reason));
    });
  }
  #connectResolver = resolver4();
  #baseCookieResolver = null;
  #pendingPullsByRequestID = /* @__PURE__ */ new Map();
  #lastMutationIDReceived = 0;
  #socket = void 0;
  #socketResolver = resolver4();
  #connectionStateChangeResolver = resolver4();
  /**
   * This resolver is only used for rejections. It is awaited in the connected
   * state (including when waiting for a pong). It is rejected when we get an
   * invalid message or an 'error' message.
   */
  #rejectMessageError = void 0;
  #closeAbortController = new AbortController();
  #visibilityWatcher;
  // We use an accessor pair to allow the subclass to override the setter.
  #connectionState = 0 /* Disconnected */;
  #setConnectionState(state) {
    if (state === this.#connectionState) {
      return;
    }
    this.#connectionState = state;
    this.#connectionStateChangeResolver.resolve(state);
    this.#connectionStateChangeResolver = resolver4();
    if (false) {
      forTesting(this)[onSetConnectionStateSymbol](state);
    }
  }
  #connectStart = void 0;
  // Set on connect attempt if currently undefined.
  // Reset to undefined when
  // 1. client stops trying to connect because it is hidden
  // 2. client encounters a connect error and canary request indicates
  //    the client is offline
  // 2. client successfully connects
  #totalToConnectStart = void 0;
  #options;
  #metrics;
  // Store as field to allow test subclass to override. Web API doesn't allow
  // overwriting location fields for security reasons.
  #reload = () => location.reload();
  /**
   * Constructs a new Reflect client.
   */
  constructor(options) {
    const {
      userID,
      roomID,
      onOnlineChange,
      jurisdiction,
      hiddenTabDisconnectDelay = DEFAULT_DISCONNECT_HIDDEN_DELAY_MS
    } = options;
    if (!userID) {
      throw new Error("ReflectOptions.userID must not be empty.");
    }
    if (!isValidRoomID(roomID)) {
      throw new Error(
        `ReflectOptions.roomID must match ${ROOM_ID_REGEX.toString()}.`
      );
    }
    const server = getServer(options.server, options.socketOrigin);
    this.#enableAnalytics = shouldEnableAnalytics(
      server,
      options.enableAnalytics
    );
    if (jurisdiction !== void 0 && jurisdiction !== "eu") {
      throw new Error('ReflectOptions.jurisdiction must be "eu" if present.');
    }
    if (hiddenTabDisconnectDelay < 0) {
      throw new Error(
        "ReflectOptions.hiddenTabDisconnectDelay must not be negative."
      );
    }
    this.onOnlineChange = onOnlineChange;
    this.#options = options;
    this.#logOptions = this.#createLogOptions({
      consoleLogLevel: options.logLevel ?? "error",
      server,
      enableAnalytics: this.#enableAnalytics
    });
    const logOptions = this.#logOptions;
    const replicacheOptions = {
      schemaVersion: options.schemaVersion,
      logLevel: logOptions.logLevel,
      logSinks: [logOptions.logSink],
      mutators: options.mutators,
      name: `reflect-${userID}-${roomID}`,
      pusher: (req, reqID) => this.#pusher(req, reqID),
      puller: (req, reqID) => this.#puller(req, reqID),
      // TODO: Do we need these?
      pushDelay: 0,
      requestOptions: {
        maxDelayMs: 0,
        minDelayMs: 0
      },
      licenseKey: "reflect-client-static-key",
      experimentalCreateKVStore: getCreateKVStore(options)
    };
    let internalAPI;
    const replicacheInternalOptions = {
      enableLicensing: false,
      exposeInternalAPI: (api) => {
        internalAPI = api;
      }
    };
    this.#rep = new Ir({
      ...replicacheOptions,
      ...replicacheInternalOptions
    });
    this.#internalAPI = internalAPI;
    this.#rep.getAuth = this.#getAuthToken;
    this.#onUpdateNeeded = this.#rep.onUpdateNeeded;
    this.#server = server;
    this.roomID = roomID;
    this.userID = userID;
    this.#jurisdiction = jurisdiction;
    this.#lc = new LogContext(
      logOptions.logLevel,
      { roomID, clientID: this.#rep.clientID },
      logOptions.logSink
    );
    reportReloadReason(this.#lc);
    this.#metrics = new MetricManager({
      reportIntervalMs: REPORT_INTERVAL_MS,
      host: location.host,
      source: "client",
      reporter: this.#enableAnalytics ? (allSeries) => this.#reportMetrics(allSeries) : () => Promise.resolve(),
      lc: this.#lc
    });
    this.#metrics.tags.push(`version:${this.version}`);
    this.#presenceManager = new PresenceManager(this.#rep.clientID, this.#lc);
    this.#pokeHandler = new PokeHandler(
      (pokeDD31) => this.#rep.poke(pokeDD31),
      this.#presenceManager,
      () => this.#onOutOfOrderPoke(),
      this.#rep.clientID,
      this.#lc
    );
    this.#visibilityWatcher = getDocumentVisibilityWatcher(
      getDocument(),
      hiddenTabDisconnectDelay,
      this.#closeAbortController.signal
    );
    this.#closeBeaconManager = new CloseBeaconManager(
      this,
      this.#lc,
      server,
      () => this.#rep.auth,
      () => this.#internalAPI.lastMutationID(),
      this.#closeAbortController.signal,
      getWindow()
    );
    void this.#runLoop();
    if (false) {
      forTesting(this)[exposedToTestingSymbol] = {
        puller: this.#puller,
        pusher: this.#pusher,
        setReload: (r) => {
          this.#reload = r;
        },
        logOptions: this.#logOptions,
        connectStart: () => this.#connectStart,
        socketResolver: () => this.#socketResolver,
        connectionState: () => this.#connectionState
      };
    }
  }
  #createLogOptions(options) {
    if (false) {
      return forTesting(this)[createLogOptionsSymbol](options);
    }
    return createLogOptions(options);
  }
  /**
   * The name of the IndexedDB database in which the data of this
   * instance of Reflect is stored.
   */
  get idbName() {
    return this.#rep.idbName;
  }
  /**
   * The schema version of the data understood by this application.
   * See [[ReflectOptions.schemaVersion]].
   */
  get schemaVersion() {
    return this.#rep.schemaVersion;
  }
  /**
   * The client ID for this instance of Reflect. Each instance
   * gets a unique client ID.
   */
  get clientID() {
    return this.#rep.clientID;
  }
  get clientGroupID() {
    return this.#rep.clientGroupID;
  }
  /**
   * The registered mutators (see [[ReflectOptions.mutators]]).
   */
  get mutate() {
    return this.#rep.mutate;
  }
  /**
   * Whether this Reflect instance has been closed. Once a Reflect instance has
   * been closed it no longer syncs and you can no longer read or write data out
   * of it. After it has been closed it is pretty much useless and should not be
   * used any more.
   */
  get closed() {
    return this.#rep.closed;
  }
  /**
   * Closes this Reflect instance.
   *
   * When closed all subscriptions end and no more read or writes are allowed.
   */
  close() {
    const lc = this.#lc.withContext("close");
    if (this.#connectionState !== 0 /* Disconnected */) {
      this.#disconnect(lc, {
        client: "ReflectClosed"
      });
    }
    this.#closeBeaconManager.send("ReflectClosed");
    lc.debug?.("Aborting closeAbortController due to close()");
    this.#closeAbortController.abort();
    this.#metrics.stop();
    this.#presenceManager.clearSubscriptions();
    return this.#rep.close();
  }
  /**
   * Subscribe to changes to Reflect data. Every time the underlying data
   * changes `body` is called and if the result of `body` changes compared to
   * last time `onData` is called. The function is also called once the first
   * time the subscription is added.
   *
   * This returns a function that can be used to cancel the subscription.
   *
   * If an error occurs in the `body` the `onError` function is called if
   * present. Otherwise, the error is thrown.
   */
  subscribe(body, options) {
    return this.#rep.subscribe(body, options);
  }
  /**
   * Transactionally read Reflect data.
   */
  query(body) {
    return this.#rep.query(body);
  }
  experimentalWatch(callback, options) {
    return this.#rep.experimentalWatch(callback, options);
  }
  /**
   * Subscribe to the set of present client ids.
   *
   * When offline this set will contain just this
   * Reflect instances clientID.
   *
   * To cancel the subscription call the returned function.
   */
  subscribeToPresence(callback) {
    return this.#presenceManager.addSubscription(callback);
  }
  #onMessage = (e) => {
    const lc = this.#lc;
    lc.debug?.("received message", e.data);
    if (this.closed) {
      lc.debug?.("ignoring message because already closed");
      return;
    }
    const rejectInvalidMessage = (e2) => this.#rejectMessageError?.reject(
      new Error(
        `Invalid message received from server: ${e2 instanceof Error ? e2.message + ". " : ""}${data}`
      )
    );
    let downMessage;
    const { data } = e;
    try {
      downMessage = parse(JSON.parse(data), downstreamSchema);
    } catch (e2) {
      rejectInvalidMessage(e2);
      return;
    }
    this.#messageCount++;
    switch (downMessage[0]) {
      case "connected":
        return this.#handleConnectedMessage(lc, downMessage);
      case "error":
        return this.#handleErrorMessage(lc, downMessage);
      case "pong":
        return this.#onPong();
      case "poke":
        return this.#handlePoke(lc, downMessage);
      case "pull":
        return this.#handlePullResponse(lc, downMessage);
      default:
        rejectInvalidMessage();
    }
  };
  #onOpen = (e) => {
    const l = addWebSocketIDFromSocketToLogContext(
      e.target,
      this.#lc
    );
    if (this.#connectStart === void 0) {
      l.error?.(
        "Got open event but connect start time is undefined. This should not happen."
      );
    } else {
      const now = Date.now();
      const timeToOpenMs = now - this.#connectStart;
      l.info?.("Got socket open event", {
        navigatorOnline: navigator.onLine,
        timeToOpenMs
      });
    }
  };
  #onClose = (e) => {
    const l = addWebSocketIDFromSocketToLogContext(
      e.target,
      this.#lc
    );
    const { code, reason, wasClean } = e;
    l.info?.("Got socket close event", { code, reason, wasClean });
    const closeKind = wasClean ? "CleanClose" : "AbruptClose";
    this.#connectResolver.reject(new CloseError(closeKind));
    this.#disconnect(l, { client: closeKind });
  };
  // An error on the connection is fatal for the connection.
  async #handleErrorMessage(lc, downMessage) {
    const [, kind, message] = downMessage;
    if (kind === "VersionNotSupported") {
      this.onUpdateNeeded?.({ type: kind });
    }
    if (kind === "InvalidConnectionRequestLastMutationID" || kind === "InvalidConnectionRequestBaseCookie") {
      await Nl(this.#rep.idbName);
      reloadWithReason(lc, this.#reload, serverAheadReloadReason(kind));
    }
    const error = new ServerError(kind, message);
    lc.info?.(`${kind}: ${message}}`);
    this.#rejectMessageError?.reject(error);
    lc.debug?.("Rejecting connect resolver due to error", error);
    this.#connectResolver.reject(error);
    this.#disconnect(lc, { server: kind });
  }
  #handleConnectedMessage(lc, connectedMessage) {
    const now = Date.now();
    const [, connectBody] = connectedMessage;
    lc = addWebSocketIDToLogContext(connectBody.wsid, lc);
    if (this.#connectedCount === 0) {
      this.#checkConnectivity("firstConnect");
    } else if (this.#connectErrorCount > 0) {
      this.#checkConnectivity("connectAfterError");
    }
    this.#connectedCount++;
    this.#connectedAt = now;
    this.#metrics.lastConnectError.clear();
    const proceedingConnectErrorCount = this.#connectErrorCount;
    this.#connectErrorCount = 0;
    let timeToConnectMs = void 0;
    let connectMsgLatencyMs = void 0;
    if (this.#connectStart === void 0) {
      lc.error?.(
        "Got connected message but connect start time is undefined. This should not happen."
      );
    } else {
      timeToConnectMs = now - this.#connectStart;
      this.#metrics.timeToConnectMs.set(timeToConnectMs);
      connectMsgLatencyMs = connectBody.timestamp !== void 0 ? now - connectBody.timestamp : void 0;
      this.#connectStart = void 0;
    }
    let totalTimeToConnectMs = void 0;
    if (this.#totalToConnectStart === void 0) {
      lc.error?.(
        "Got connected message but total to connect start time is undefined. This should not happen."
      );
    } else {
      totalTimeToConnectMs = now - this.#totalToConnectStart;
      this.#totalToConnectStart = void 0;
    }
    this.#metrics.setConnected(timeToConnectMs ?? 0, totalTimeToConnectMs ?? 0);
    lc.info?.("Connected", {
      navigatorOnline: navigator.onLine,
      timeToConnectMs,
      totalTimeToConnectMs,
      connectMsgLatencyMs,
      connectedCount: this.#connectedCount,
      proceedingConnectErrorCount
    });
    this.#lastMutationIDSent = NULL_LAST_MUTATION_ID_SENT;
    lc.debug?.("Resolving connect resolver");
    this.#setConnectionState(2 /* Connected */);
    this.#connectResolver.resolve();
  }
  /**
   * Starts a new connection. This will create the WebSocket that does the HTTP
   * request to the server.
   *
   * {@link #connect} will throw an assertion error if the
   * {@link #connectionState} is not {@link ConnectionState.Disconnected}.
   * Callers MUST check the connection state before calling this method and log
   * an error as needed.
   *
   * The function will resolve once the socket is connected. If you need to know
   * when a connection has been established, as in we have received the
   * {@link ConnectedMessage}, you should await the {@link #connectResolver}
   * promise. The {@link #connectResolver} promise rejects if an error message
   * is received before the connected message is received or if the connection
   * attempt times out.
   */
  async #connect(l) {
    assert(this.#server);
    assert(this.#connectionState === 0 /* Disconnected */);
    const wsid = nanoid();
    l = addWebSocketIDToLogContext(wsid, l);
    l.info?.("Connecting...", { navigatorOnline: navigator.onLine });
    this.#setConnectionState(1 /* Connecting */);
    assert(this.#connectStart === void 0);
    const now = Date.now();
    this.#connectStart = now;
    if (this.#totalToConnectStart === void 0) {
      this.#totalToConnectStart = now;
    }
    const baseCookie = await this.#getBaseCookie();
    if (this.closed) {
      return;
    }
    this.#baseCookie = baseCookie;
    const timeoutID = setTimeout(() => {
      l.debug?.("Rejecting connect resolver due to timeout");
      this.#connectResolver.reject(new TimedOutError("Connect"));
      this.#disconnect(l, {
        client: "ConnectTimeout"
      });
    }, CONNECT_TIMEOUT_MS);
    this.#closeAbortController.signal.addEventListener("abort", () => {
      clearTimeout(timeoutID);
    });
    const ws = createSocket(
      toWSString(this.#server),
      baseCookie,
      this.clientID,
      await this.clientGroupID,
      this.roomID,
      this.userID,
      this.#rep.auth,
      this.#jurisdiction,
      this.#lastMutationIDReceived,
      wsid,
      this.#options.logLevel === "debug",
      l
    );
    if (this.closed) {
      return;
    }
    ws.addEventListener("message", this.#onMessage);
    ws.addEventListener("open", this.#onOpen);
    ws.addEventListener("close", this.#onClose);
    this.#socket = ws;
    this.#socketResolver.resolve(ws);
    try {
      l.debug?.("Waiting for connection to be acknowledged");
      await this.#connectResolver.promise;
    } finally {
      clearTimeout(timeoutID);
    }
  }
  #disconnect(l, reason) {
    if (this.#connectionState === 1 /* Connecting */) {
      this.#connectErrorCount++;
    }
    l.info?.("disconnecting", {
      navigatorOnline: navigator.onLine,
      reason,
      connectStart: this.#connectStart,
      totalToConnectStart: this.#totalToConnectStart,
      connectedAt: this.#connectedAt,
      connectionDuration: this.#connectedAt ? Date.now() - this.#connectedAt : 0,
      messageCount: this.#messageCount,
      connectionState: this.#connectionState,
      connectErrorCount: this.#connectErrorCount
    });
    switch (this.#connectionState) {
      case 2 /* Connected */: {
        if (this.#connectStart !== void 0) {
          l.error?.(
            "disconnect() called while connected but connect start time is defined. This should not happen."
          );
        }
        break;
      }
      case 1 /* Connecting */: {
        this.#metrics.lastConnectError.set(getLastConnectErrorValue(reason));
        this.#metrics.timeToConnectMs.set(DID_NOT_CONNECT_VALUE);
        this.#metrics.setConnectError(reason);
        if (this.#connectErrorCount % CHECK_CONNECTIVITY_ON_ERROR_FREQUENCY === 1) {
          this.#checkConnectivity(
            `connectErrorCount=${this.#connectErrorCount}`
          );
        }
        if (this.#connectStart === void 0) {
          l.error?.(
            "disconnect() called while connecting but connect start time is undefined. This should not happen."
          );
        }
        break;
      }
      case 0 /* Disconnected */:
        l.error?.("disconnect() called while disconnected");
        break;
    }
    this.#socketResolver = resolver4();
    l.debug?.("Creating new connect resolver");
    this.#connectResolver = resolver4();
    this.#setConnectionState(0 /* Disconnected */);
    this.#messageCount = 0;
    this.#connectStart = void 0;
    this.#connectedAt = 0;
    this.#socket?.removeEventListener("message", this.#onMessage);
    this.#socket?.removeEventListener("open", this.#onOpen);
    this.#socket?.removeEventListener("close", this.#onClose);
    this.#socket?.close();
    this.#socket = void 0;
    this.#lastMutationIDSent = NULL_LAST_MUTATION_ID_SENT;
    this.#pokeHandler.handleDisconnect();
    this.#presenceManager.handleDisconnect();
  }
  async #handlePoke(_lc, pokeMessage) {
    this.#abortPingTimeout();
    const pokeBody = pokeMessage[1];
    const lastMutationIDChangeForSelf = await this.#pokeHandler.handlePoke(pokeBody);
    if (lastMutationIDChangeForSelf !== void 0) {
      this.#lastMutationIDReceived = lastMutationIDChangeForSelf;
    }
  }
  #onOutOfOrderPoke() {
    const lc = this.#lc;
    lc.info?.("out of order poke, disconnecting");
    if (this.#connectionState !== 0 /* Disconnected */) {
      this.#disconnect(lc, {
        client: "UnexpectedBaseCookie"
      });
    }
  }
  #handlePullResponse(lc, pullResponseMessage) {
    this.#abortPingTimeout();
    const body = pullResponseMessage[1];
    lc = lc.withContext("requestID", body.requestID);
    lc.debug?.("Handling pull response", body);
    const resolver5 = this.#pendingPullsByRequestID.get(body.requestID);
    if (!resolver5) {
      lc.debug?.("No resolver found");
      return;
    }
    resolver5.resolve(pullResponseMessage[1]);
  }
  async #pusher(req, requestID) {
    await this.#connectResolver.promise;
    const lc = this.#lc.withContext("requestID", requestID);
    lc.debug?.(`pushing ${req.mutations.length} mutations`);
    if (req.pushVersion === 0) {
      return {
        httpRequestInfo: {
          errorMessage: "",
          httpStatusCode: 200
        }
      };
    }
    const socket = this.#socket;
    assert(socket);
    const isMutationRecoveryPush = req.clientGroupID !== await this.clientGroupID;
    const start = isMutationRecoveryPush ? 0 : req.mutations.findIndex(
      (m) => m.clientID === this.#lastMutationIDSent.clientID && m.id === this.#lastMutationIDSent.id
    ) + 1;
    lc.debug?.(
      isMutationRecoveryPush ? "pushing for recovery" : "pushing",
      req.mutations.length - start,
      "mutations of",
      req.mutations.length,
      "mutations."
    );
    const now = Date.now();
    for (let i = start; i < req.mutations.length; i++) {
      const m = req.mutations[i];
      const msg = [
        "push",
        {
          timestamp: now,
          clientGroupID: req.clientGroupID,
          mutations: [
            {
              timestamp: now - Math.round(performance.now() - m.timestamp),
              id: m.id,
              clientID: m.clientID,
              name: m.name,
              args: m.args
            }
          ],
          pushVersion: req.pushVersion,
          schemaVersion: req.schemaVersion,
          requestID
        }
      ];
      send(socket, msg);
      if (!isMutationRecoveryPush) {
        this.#lastMutationIDSent = { clientID: m.clientID, id: m.id };
      }
    }
    return {
      httpRequestInfo: {
        errorMessage: "",
        httpStatusCode: 200
      }
    };
  }
  #getAuthToken = () => {
    const { auth } = this.#options;
    return typeof auth === "function" ? auth() : auth;
  };
  async #updateAuthToken(lc) {
    const auth = await this.#getAuthToken();
    if (auth) {
      lc.debug?.("Got auth token");
      this.#rep.auth = auth;
    }
  }
  async #runLoop() {
    this.#lc.info?.(`Starting Reflect version: ${this.version}`);
    if (this.#server === null) {
      this.#lc.info?.("No socket origin provided, not starting connect loop.");
      return;
    }
    let runLoopCounter = 0;
    const bareLogContext = this.#lc;
    const getLogContext = () => {
      let lc = bareLogContext;
      if (this.#socket) {
        lc = addWebSocketIDFromSocketToLogContext(this.#socket, lc);
      }
      return lc.withContext("runLoopCounter", runLoopCounter);
    };
    await this.#updateAuthToken(bareLogContext);
    let needsReauth = false;
    let gotError = false;
    while (!this.closed) {
      runLoopCounter++;
      let lc = getLogContext();
      try {
        switch (this.#connectionState) {
          case 0 /* Disconnected */: {
            if (this.#visibilityWatcher.visibilityState === "hidden") {
              this.#metrics.setDisconnectedWaitingForVisible();
              this.#totalToConnectStart = void 0;
            }
            await this.#visibilityWatcher.waitForVisible();
            if (needsReauth) {
              await this.#updateAuthToken(lc);
            }
            await this.#connect(lc);
            if (this.closed) {
              break;
            }
            assert(this.#socket);
            lc = getLogContext();
            lc.debug?.("Connected successfully");
            gotError = false;
            needsReauth = false;
            this.#setOnline(true);
            break;
          }
          case 1 /* Connecting */:
            lc.error?.("unreachable");
            gotError = true;
            break;
          case 2 /* Connected */: {
            const controller = new AbortController();
            this.#abortPingTimeout = () => controller.abort();
            const [pingTimeoutPromise, pingTimeoutAborted] = sleepWithAbort(
              PING_INTERVAL_MS,
              controller.signal
            );
            this.#rejectMessageError = resolver4();
            let RaceCases;
            ((RaceCases2) => {
              RaceCases2[RaceCases2["Ping"] = 0] = "Ping";
              RaceCases2[RaceCases2["Hidden"] = 2] = "Hidden";
            })(RaceCases || (RaceCases = {}));
            const raceResult = await promiseRace([
              pingTimeoutPromise,
              pingTimeoutAborted,
              this.#visibilityWatcher.waitForHidden(),
              this.#connectionStateChangeResolver.promise,
              this.#rejectMessageError.promise
            ]);
            if (this.closed) {
              this.#rejectMessageError = void 0;
              break;
            }
            switch (raceResult) {
              case 0 /* Ping */: {
                const pingResult = await this.#ping(
                  lc,
                  this.#rejectMessageError.promise
                );
                if (pingResult === 0 /* TimedOut */) {
                  gotError = true;
                }
                break;
              }
              case 2 /* Hidden */:
                this.#disconnect(lc, {
                  client: "Hidden"
                });
                this.#setOnline(false);
                break;
            }
            this.#rejectMessageError = void 0;
          }
        }
      } catch (ex) {
        if (this.#connectionState !== 2 /* Connected */) {
          lc.error?.("Failed to connect", ex, {
            lmid: this.#lastMutationIDReceived,
            baseCookie: this.#baseCookie
          });
        }
        lc.debug?.(
          "Got an exception in the run loop",
          "state:",
          this.#connectionState,
          "exception:",
          ex
        );
        if (isAuthError(ex)) {
          if (!needsReauth) {
            needsReauth = true;
            continue;
          }
          needsReauth = true;
        }
        if (isServerError(ex) || ex instanceof TimedOutError || ex instanceof CloseError) {
          gotError = true;
        }
      }
      if (gotError) {
        this.#setOnline(false);
        let cfGetCheckSucceeded = false;
        const cfGetCheckURL = new URL(this.#server);
        cfGetCheckURL.pathname = "/api/canary/v0/get";
        cfGetCheckURL.searchParams.set("id", nanoid());
        const cfGetCheckController = new AbortController();
        fetch(cfGetCheckURL, { signal: cfGetCheckController.signal }).then((_) => {
          cfGetCheckSucceeded = true;
        }).catch((_) => {
          cfGetCheckSucceeded = false;
        });
        lc.debug?.(
          "Sleeping",
          RUN_LOOP_INTERVAL_MS,
          "ms before reconnecting due to error, state:",
          this.#connectionState
        );
        await sleep(RUN_LOOP_INTERVAL_MS);
        cfGetCheckController.abort();
        if (!cfGetCheckSucceeded) {
          lc.info?.(
            "Canary request failed, resetting total time to connect start time."
          );
          this.#totalToConnectStart = void 0;
        }
      }
    }
  }
  async #puller(req, requestID) {
    const lc = this.#lc.withContext("requestID", requestID);
    lc.debug?.("Pull", req);
    if (req.pullVersion === 0) {
      return {
        httpRequestInfo: {
          errorMessage: "",
          httpStatusCode: 200
        }
      };
    }
    if (req.clientGroupID === await this.clientGroupID) {
      const cookie2 = parse(req.cookie, nullableVersionSchema);
      const resolver5 = this.#baseCookieResolver;
      this.#baseCookieResolver = null;
      resolver5?.resolve(cookie2);
      return {
        httpRequestInfo: {
          errorMessage: "",
          httpStatusCode: 200
        }
      };
    }
    await this.#connectResolver.promise;
    const socket = this.#socket;
    assert(socket);
    lc.debug?.("Pull is for mutation recovery");
    const cookie = parse(req.cookie, nullableVersionSchema);
    const pullRequestMessage = [
      "pull",
      {
        clientGroupID: req.clientGroupID,
        cookie,
        requestID
      }
    ];
    send(socket, pullRequestMessage);
    const pullResponseResolver = resolver4();
    this.#pendingPullsByRequestID.set(requestID, pullResponseResolver);
    try {
      let RaceCases;
      ((RaceCases2) => {
        RaceCases2[RaceCases2["Timeout"] = 0] = "Timeout";
        RaceCases2[RaceCases2["Response"] = 1] = "Response";
      })(RaceCases || (RaceCases = {}));
      const raceResult = await promiseRace([
        sleep(PULL_TIMEOUT_MS),
        pullResponseResolver.promise
      ]);
      switch (raceResult) {
        case 0 /* Timeout */:
          lc.debug?.("Mutation recovery pull timed out");
          throw new Error("Pull timed out");
        case 1 /* Response */: {
          lc.debug?.("Returning mutation recovery pull response");
          const response = await pullResponseResolver.promise;
          return {
            response: {
              cookie: response.cookie,
              lastMutationIDChanges: response.lastMutationIDChanges,
              patch: []
            },
            httpRequestInfo: {
              errorMessage: "",
              httpStatusCode: 200
            }
          };
        }
        default:
          assert(false, "unreachable");
      }
    } finally {
      pullResponseResolver.reject("timed out");
      this.#pendingPullsByRequestID.delete(requestID);
    }
  }
  #setOnline(online) {
    if (this.#online === online) {
      return;
    }
    this.#online = online;
    this.onOnlineChange?.(online);
  }
  /**
   * A rough heuristic for whether the client is currently online and
   * authenticated.
   */
  get online() {
    return this.#online;
  }
  /**
   * Starts a a ping and waits for a pong.
   *
   * If it takes too long to get a pong we disconnect and this returns
   * {@code PingResult.TimedOut}.
   */
  async #ping(l, messageErrorRejectionPromise) {
    l.debug?.("pinging");
    const { promise, resolve } = resolver4();
    this.#onPong = resolve;
    const pingMessage = ["ping", {}];
    const t0 = performance.now();
    assert(this.#socket);
    send(this.#socket, pingMessage);
    const connected = await promiseRace([
      promise,
      sleep(PING_TIMEOUT_MS),
      messageErrorRejectionPromise
    ]) === 0;
    const delta = performance.now() - t0;
    if (!connected) {
      l.info?.("ping failed in", delta, "ms - disconnecting");
      this.#disconnect(l, {
        client: "PingTimeout"
      });
      return 0 /* TimedOut */;
    }
    l.debug?.("ping succeeded in", delta, "ms");
    return 1 /* Success */;
  }
  // Sends a set of metrics to the server. Throws unless the server
  // returns 200.
  async #reportMetrics(allSeries) {
    if (this.#server === null) {
      this.#lc.info?.("Skipping metrics report, socketOrigin is null");
      return;
    }
    const body = JSON.stringify({ series: allSeries });
    const url = new URL("/api/metrics/v0/report", this.#server);
    url.searchParams.set("clientID", this.clientID);
    url.searchParams.set("clientGroupID", await this.clientGroupID);
    url.searchParams.set("roomID", this.roomID);
    url.searchParams.set("userID", this.userID);
    url.searchParams.set("requestID", nanoid());
    const res = await fetch(url.toString(), {
      method: "POST",
      body,
      keepalive: true
    });
    if (!res.ok) {
      const maybeBody = await res.text();
      throw new Error(
        `unexpected response: ${res.status} ${res.statusText} body: ${maybeBody}`
      );
    }
  }
  #checkConnectivity(reason) {
    void this.#checkConnectivityAsync(reason);
  }
  async #checkConnectivityAsync(reason) {
    assert(this.#server);
    if (this.closed) {
      return;
    }
    try {
      await checkConnectivity(
        reason,
        this.#server,
        this.#lc,
        this.#closeAbortController.signal,
        this.#enableAnalytics
      );
    } catch (e) {
      this.#lc.info?.("Error checking connectivity for", reason, e);
    }
  }
  // Total hack to get base cookie, see #puller for how the promise is resolved.
  #getBaseCookie() {
    this.#baseCookieResolver ??= resolver4();
    void this.#rep.pull();
    return this.#baseCookieResolver.promise;
  }
};
function createSocket(socketOrigin, baseCookie, clientID, clientGroupID, roomID, userID, auth, jurisdiction, lmid, wsid, debugPerf, lc) {
  const url = new URL(socketOrigin);
  url.pathname = `/api/sync/v${REFLECT_VERSION}/connect`;
  const { searchParams } = url;
  searchParams.set("clientID", clientID);
  searchParams.set("clientGroupID", clientGroupID);
  searchParams.set("roomID", roomID);
  searchParams.set("userID", userID);
  if (jurisdiction !== void 0) {
    searchParams.set("jurisdiction", jurisdiction);
  }
  searchParams.set("baseCookie", baseCookie === null ? "" : String(baseCookie));
  searchParams.set("ts", String(performance.now()));
  searchParams.set("lmid", String(lmid));
  searchParams.set("wsid", wsid);
  if (debugPerf) {
    searchParams.set("debugPerf", true.toString());
  }
  lc.info?.("Connecting to", url.toString());
  return new WebSocket(
    url,
    auth === "" || auth === void 0 ? void 0 : encodeURIComponent(auth)
  );
}
function addWebSocketIDFromSocketToLogContext({ url }, lc) {
  const wsid = new URL(url).searchParams.get("wsid") ?? nanoid();
  return addWebSocketIDToLogContext(wsid, lc);
}
function addWebSocketIDToLogContext(wsid, lc) {
  return lc.withContext("wsid", wsid);
}
function promiseRace(ps) {
  return Promise.race(ps.map((p, i) => p.then(() => i)));
}
var TimedOutError = class extends Error {
  constructor(m) {
    super(`${m} timed out`);
  }
};
var CloseError = class extends Error {
};
function createMemStore(name) {
  return new ct(name);
}
function getCreateKVStore(options) {
  switch (options.kvStore) {
    case "idb":
      return void 0;
    case "mem":
    case void 0:
      return createMemStore;
    default:
      return options.kvStore;
  }
}
export {
  ut as IDBNotFoundError,
  Ee as PullError,
  Reflect,
  jt as TransactionClosedError,
  zs as dropAllDatabases,
  Nl as dropDatabase,
  lo as getDefaultPuller,
  Ye as isScanIndexOptions,
  Hi as makeIDBName
};
//# sourceMappingURL=client.js.map
