@noya-app/noya-multiplayer-react 0.1.89 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4844,12 +4844,12 @@ var TOKEN_PRIMITIVE = Symbol("primitive");
4844
4844
  var TOKEN_ARGSEP = Symbol("argsep");
4845
4845
 
4846
4846
  // ../../node_modules/uuid/dist/esm-node/rng.js
4847
- import crypto from "crypto";
4847
+ import crypto2 from "crypto";
4848
4848
  var rnds8Pool = new Uint8Array(256);
4849
4849
  var poolPtr = rnds8Pool.length;
4850
4850
  function rng() {
4851
4851
  if (poolPtr > rnds8Pool.length - 16) {
4852
- crypto.randomFillSync(rnds8Pool);
4852
+ crypto2.randomFillSync(rnds8Pool);
4853
4853
  poolPtr = 0;
4854
4854
  }
4855
4855
  return rnds8Pool.slice(poolPtr, poolPtr += 16);
@@ -7760,6 +7760,16 @@ function parseAppDataParameter(options) {
7760
7760
  return null;
7761
7761
  }
7762
7762
  }
7763
+ function parseInspectorParameter(options) {
7764
+ const window2 = options?.window ?? globalThis;
7765
+ const location = window2.location;
7766
+ if (!location) return null;
7767
+ const params = new URLSearchParams((location.hash || "").replace(/^#/, ""));
7768
+ const inspector = params.get("inspector");
7769
+ if (inspector === "true") return true;
7770
+ if (inspector === "false") return false;
7771
+ return null;
7772
+ }
7763
7773
  function applyClientIdentityFromAppData(appData) {
7764
7774
  if (appData.clientId !== void 0) {
7765
7775
  setClientId(appData.clientId);
@@ -7774,6 +7784,8 @@ function applyClientIdentityFromAppData(appData) {
7774
7784
  function getAppData(initialState, schema, options) {
7775
7785
  const resolvedInitialState = initialState instanceof Function ? initialState() : initialState;
7776
7786
  let appData = parseAppDataParameter(options) ?? createDefaultAppData(resolvedInitialState);
7787
+ const inspector = parseInspectorParameter(options);
7788
+ if (inspector !== null) appData.inspector = inspector;
7777
7789
  if (options?.overrideExistingState || appData.initialState === null) {
7778
7790
  appData.initialState = resolvedInitialState;
7779
7791
  }
@@ -8561,9 +8573,37 @@ function useResourceUrl(idOrStableId, options) {
8561
8573
 
8562
8574
  // src/WebSocketConnection.ts
8563
8575
  import {
8576
+ MAX_CLIENT_MESSAGE_BATCH_SIZE,
8577
+ MAX_REALTIME_INPUT_BATCHES_IN_FLIGHT,
8564
8578
  ReconnectingWebSocket,
8565
8579
  UserActivityDetector
8566
8580
  } from "@noya-app/state-manager";
8581
+ function simulationSessionIdentity(state) {
8582
+ if (!state || typeof state !== "object" || Array.isArray(state)) return;
8583
+ const game = Reflect.get(state, "$game");
8584
+ if (!game || typeof game !== "object" || Array.isArray(game)) return;
8585
+ const startedAt = Reflect.get(game, "startedAt");
8586
+ if (startedAt === null || typeof startedAt === "string" || typeof startedAt === "number" && Number.isFinite(startedAt)) {
8587
+ return `startedAt:${typeof startedAt}:${String(startedAt)}`;
8588
+ }
8589
+ }
8590
+ function simulationStateSequence(state) {
8591
+ if (!state || typeof state !== "object" || Array.isArray(state)) return;
8592
+ const simulation = Reflect.get(state, "simulation");
8593
+ const simulationTick = simulation && typeof simulation === "object" ? Reflect.get(simulation, "tick") : void 0;
8594
+ if (Number.isSafeInteger(simulationTick) && simulationTick >= 0) {
8595
+ return simulationTick;
8596
+ }
8597
+ const game = Reflect.get(state, "$game");
8598
+ const gameTick = game && typeof game === "object" ? Reflect.get(game, "simulationTick") : void 0;
8599
+ return Number.isSafeInteger(gameTick) && gameTick >= 0 ? gameTick : void 0;
8600
+ }
8601
+ function sameSimulationSession(left, right) {
8602
+ return left.sessionIdentity === void 0 || right.sessionIdentity === void 0 || left.sessionIdentity === right.sessionIdentity;
8603
+ }
8604
+ function createRealtimeInputSessionId() {
8605
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
8606
+ }
8567
8607
  var WebSocketConnection = class {
8568
8608
  constructor(url, options = {}) {
8569
8609
  this.url = url;
@@ -8578,12 +8618,40 @@ var WebSocketConnection = class {
8578
8618
  }
8579
8619
  ws;
8580
8620
  closedForever = false;
8621
+ heartbeatIntervalHandle;
8622
+ silentTimeoutHandle;
8623
+ simulationClockPulseIntervalHandle;
8624
+ simulationClockPulseSocket;
8625
+ simulationClockDirectFailed = false;
8626
+ heartbeatSequence = 0;
8627
+ pendingSimulationFrames = /* @__PURE__ */ new Map();
8628
+ frameFlushHandle;
8629
+ frameFlushScheduler;
8630
+ droppedSimulationFrames = 0;
8631
+ pendingFrameReceipts = /* @__PURE__ */ new Map();
8632
+ deliveredSimulationFrameCursors = /* @__PURE__ */ new Map();
8633
+ queuedRealtimeInputs = [];
8634
+ /**
8635
+ * A batch stays here until the server explicitly confirms ingestion.
8636
+ * Reconnects resend these exact batches, so a frame that was already in
8637
+ * flight can never create a hole in an input stream.
8638
+ */
8639
+ unconfirmedRealtimeBatches = [];
8640
+ lastSentRealtimeSequences = /* @__PURE__ */ new Map();
8641
+ realtimeInputSessionId = createRealtimeInputSessionId();
8642
+ nextRealtimeInputBatchSequence = 0;
8643
+ realtimeInputWatchdogHandle;
8644
+ realtimeBatchesSent = 0;
8645
+ realtimeInputsSent = 0;
8581
8646
  async connect() {
8582
8647
  if (this.closedForever) return;
8583
8648
  this.ws.connect(this.url.toString());
8584
8649
  }
8585
8650
  handleOpen = () => {
8586
8651
  if (this.closedForever) return;
8652
+ this.startHeartbeat();
8653
+ this.replayUnconfirmedRealtimeBatches();
8654
+ this.armRealtimeInputWatchdog();
8587
8655
  if (this.options.debug) {
8588
8656
  console.info("ws connected");
8589
8657
  }
@@ -8594,16 +8662,124 @@ var WebSocketConnection = class {
8594
8662
  };
8595
8663
  handleMessage = (event) => {
8596
8664
  if (this.closedForever) return;
8665
+ this.armSilentTimeout();
8597
8666
  if (this.options.debug) {
8598
8667
  console.info("ws receiving message ", event.data);
8599
8668
  }
8600
8669
  const parsed = JSON.parse(event.data);
8670
+ if (parsed.type === "simulationClockConfig") {
8671
+ this.applySimulationClockConfig(parsed);
8672
+ return;
8673
+ }
8674
+ if (parsed.type === "simulationFrame") {
8675
+ const cursor = {
8676
+ sequence: parsed.frame.sequence,
8677
+ sessionIdentity: simulationSessionIdentity(parsed.frame.state)
8678
+ };
8679
+ const receipt = {
8680
+ type: "simulationFrameReceipt",
8681
+ scriptId: parsed.frame.scriptId,
8682
+ sequence: parsed.frame.sequence
8683
+ };
8684
+ const pendingReceipt = this.pendingFrameReceipts.get(
8685
+ parsed.frame.scriptId
8686
+ );
8687
+ if (!pendingReceipt || receipt.sequence > pendingReceipt.sequence) {
8688
+ this.pendingFrameReceipts.set(parsed.frame.scriptId, receipt);
8689
+ }
8690
+ const delivered = this.deliveredSimulationFrameCursors.get(
8691
+ parsed.frame.scriptId
8692
+ );
8693
+ const pending = this.pendingSimulationFrames.get(parsed.frame.scriptId);
8694
+ const pendingCursor = pending ? {
8695
+ sequence: pending.frame.sequence,
8696
+ sessionIdentity: simulationSessionIdentity(pending.frame.state)
8697
+ } : void 0;
8698
+ if (delivered && sameSimulationSession(delivered, cursor) && cursor.sequence <= delivered.sequence || pendingCursor && sameSimulationSession(pendingCursor, cursor) && cursor.sequence <= pendingCursor.sequence) {
8699
+ this.droppedSimulationFrames += 1;
8700
+ this.scheduleSimulationFrameFlush();
8701
+ return;
8702
+ }
8703
+ if (pending) this.droppedSimulationFrames += 1;
8704
+ this.pendingSimulationFrames.set(parsed.frame.scriptId, parsed);
8705
+ this.scheduleSimulationFrameFlush();
8706
+ return;
8707
+ }
8708
+ if (parsed.type === "object" && this.shouldSuppressNonMonotonicObject(parsed.object)) {
8709
+ return;
8710
+ }
8711
+ if (parsed.type === "realtimeInputReceipt") {
8712
+ this.confirmRealtimeInputBatches(parsed.sessionId, parsed.sequence);
8713
+ return;
8714
+ }
8715
+ this.flushSimulationFrames();
8716
+ this.emitReceivedMessage(parsed);
8717
+ };
8718
+ emitReceivedMessage(message) {
8601
8719
  this.options.onConnectionEvent?.({
8602
8720
  type: "receive",
8603
- message: parsed
8721
+ message
8604
8722
  });
8605
- };
8723
+ }
8724
+ scheduleSimulationFrameFlush() {
8725
+ if (this.frameFlushHandle !== void 0) return;
8726
+ const flush = () => {
8727
+ this.frameFlushHandle = void 0;
8728
+ this.frameFlushScheduler = void 0;
8729
+ this.flushSimulationFrames();
8730
+ };
8731
+ if (typeof requestAnimationFrame === "function") {
8732
+ this.frameFlushScheduler = "animationFrame";
8733
+ this.frameFlushHandle = requestAnimationFrame(flush);
8734
+ } else {
8735
+ this.frameFlushScheduler = "timeout";
8736
+ this.frameFlushHandle = setTimeout(flush, 0);
8737
+ }
8738
+ }
8739
+ flushSimulationFrames() {
8740
+ this.cancelSimulationFrameFlush();
8741
+ this.flushPendingFrameReceipts();
8742
+ const frames = Array.from(this.pendingSimulationFrames.values());
8743
+ this.pendingSimulationFrames.clear();
8744
+ for (const frame of frames) {
8745
+ this.deliveredSimulationFrameCursors.set(frame.frame.scriptId, {
8746
+ sequence: frame.frame.sequence,
8747
+ sessionIdentity: simulationSessionIdentity(frame.frame.state)
8748
+ });
8749
+ this.emitReceivedMessage(frame);
8750
+ }
8751
+ }
8752
+ shouldSuppressNonMonotonicObject(state) {
8753
+ const sequence = simulationStateSequence(state);
8754
+ if (sequence === void 0) return false;
8755
+ const cursor = {
8756
+ sequence,
8757
+ sessionIdentity: simulationSessionIdentity(state)
8758
+ };
8759
+ const previous = Array.from(this.deliveredSimulationFrameCursors.values());
8760
+ const comparable = previous.filter(
8761
+ (entry) => sameSimulationSession(entry, cursor)
8762
+ );
8763
+ if (cursor.sessionIdentity !== void 0 && previous.some((entry) => entry.sessionIdentity !== void 0) && comparable.length === 0) {
8764
+ this.deliveredSimulationFrameCursors.clear();
8765
+ return false;
8766
+ }
8767
+ return comparable.some((entry) => entry.sequence > cursor.sequence);
8768
+ }
8769
+ cancelSimulationFrameFlush() {
8770
+ if (this.frameFlushHandle === void 0) return;
8771
+ if (this.frameFlushScheduler === "animationFrame") {
8772
+ cancelAnimationFrame(this.frameFlushHandle);
8773
+ } else {
8774
+ clearTimeout(this.frameFlushHandle);
8775
+ }
8776
+ this.frameFlushHandle = void 0;
8777
+ this.frameFlushScheduler = void 0;
8778
+ }
8606
8779
  handleClose = () => {
8780
+ this.stopHeartbeat();
8781
+ this.stopSimulationClockPulses();
8782
+ this.stopRealtimeInputWatchdog();
8607
8783
  if (this.closedForever) return;
8608
8784
  if (this.options.debug) {
8609
8785
  console.info("ws disconnected");
@@ -8613,8 +8789,140 @@ var WebSocketConnection = class {
8613
8789
  state: "CLOSED"
8614
8790
  });
8615
8791
  };
8792
+ startHeartbeat() {
8793
+ this.stopHeartbeat();
8794
+ this.armSilentTimeout();
8795
+ const intervalMs = this.options.heartbeatIntervalMs ?? 15e3;
8796
+ if (intervalMs <= 0) return;
8797
+ this.heartbeatIntervalHandle = setInterval(() => {
8798
+ if (this.closedForever || this.ws.state !== "OPEN") return;
8799
+ this.send({
8800
+ type: "ping",
8801
+ id: `heartbeat-${Date.now()}-${++this.heartbeatSequence}`,
8802
+ clientSentAt: Date.now()
8803
+ });
8804
+ }, intervalMs);
8805
+ }
8806
+ armSilentTimeout() {
8807
+ if (this.silentTimeoutHandle !== void 0) {
8808
+ clearTimeout(this.silentTimeoutHandle);
8809
+ this.silentTimeoutHandle = void 0;
8810
+ }
8811
+ const timeoutMs = this.options.silentTimeoutMs ?? 45e3;
8812
+ if (timeoutMs <= 0 || this.closedForever || this.ws.state !== "OPEN")
8813
+ return;
8814
+ this.silentTimeoutHandle = setTimeout(() => {
8815
+ this.silentTimeoutHandle = void 0;
8816
+ if (this.closedForever || this.ws.state !== "OPEN") return;
8817
+ this.stopSimulationClockPulses();
8818
+ this.stopHeartbeat();
8819
+ this.ws.reconnect();
8820
+ }, timeoutMs);
8821
+ }
8822
+ stopHeartbeat() {
8823
+ if (this.heartbeatIntervalHandle !== void 0) {
8824
+ clearInterval(this.heartbeatIntervalHandle);
8825
+ this.heartbeatIntervalHandle = void 0;
8826
+ }
8827
+ if (this.silentTimeoutHandle !== void 0) {
8828
+ clearTimeout(this.silentTimeoutHandle);
8829
+ this.silentTimeoutHandle = void 0;
8830
+ }
8831
+ this.cancelSimulationFrameFlush();
8832
+ this.pendingSimulationFrames.clear();
8833
+ this.pendingFrameReceipts.clear();
8834
+ }
8835
+ applySimulationClockConfig(config) {
8836
+ this.stopSimulationClockPulses();
8837
+ if (config.enabled !== true || !Number.isFinite(config.intervalMs) || config.intervalMs <= 0 || this.closedForever || this.ws.state !== "OPEN") {
8838
+ return;
8839
+ }
8840
+ const intervalMs = Math.max(1, Math.floor(config.intervalMs));
8841
+ if (config.pulsePath !== void 0) {
8842
+ this.openSimulationClockPulseSocket(config.pulsePath);
8843
+ }
8844
+ this.simulationClockPulseIntervalHandle = setInterval(() => {
8845
+ if (this.closedForever || this.ws.state !== "OPEN") return;
8846
+ const pulse = JSON.stringify({ type: "simulationClockPulse" });
8847
+ if (this.simulationClockPulseSocket?.state === "OPEN") {
8848
+ this.simulationClockPulseSocket.send(pulse);
8849
+ return;
8850
+ }
8851
+ if (this.simulationClockPulseSocket && !this.simulationClockDirectFailed) {
8852
+ return;
8853
+ }
8854
+ this.sendTransportMessage({ type: "simulationClockPulse" });
8855
+ }, intervalMs);
8856
+ }
8857
+ openSimulationClockPulseSocket(pulsePath) {
8858
+ if (!pulsePath.startsWith("/") || pulsePath.includes("?") || pulsePath.includes("#")) {
8859
+ this.simulationClockDirectFailed = true;
8860
+ return;
8861
+ }
8862
+ const pulseUrl = new URL(this.url);
8863
+ pulseUrl.pathname = pulsePath;
8864
+ pulseUrl.hash = "";
8865
+ const socket = new ReconnectingWebSocket({
8866
+ onopen: () => {
8867
+ if (this.simulationClockPulseSocket !== socket) return;
8868
+ this.simulationClockDirectFailed = false;
8869
+ },
8870
+ onclose: () => {
8871
+ if (this.simulationClockPulseSocket !== socket) return;
8872
+ this.simulationClockDirectFailed = true;
8873
+ },
8874
+ onerror: () => {
8875
+ if (this.simulationClockPulseSocket !== socket) return;
8876
+ this.simulationClockDirectFailed = true;
8877
+ }
8878
+ });
8879
+ this.simulationClockPulseSocket = socket;
8880
+ this.simulationClockDirectFailed = false;
8881
+ try {
8882
+ socket.connect(pulseUrl.toString());
8883
+ } catch {
8884
+ this.simulationClockPulseSocket = void 0;
8885
+ this.simulationClockDirectFailed = true;
8886
+ socket.shutdown();
8887
+ }
8888
+ }
8889
+ stopSimulationClockPulses() {
8890
+ if (this.simulationClockPulseIntervalHandle !== void 0) {
8891
+ clearInterval(this.simulationClockPulseIntervalHandle);
8892
+ this.simulationClockPulseIntervalHandle = void 0;
8893
+ }
8894
+ const socket = this.simulationClockPulseSocket;
8895
+ this.simulationClockPulseSocket = void 0;
8896
+ this.simulationClockDirectFailed = false;
8897
+ socket?.shutdown();
8898
+ }
8899
+ getSimulationFrameDiagnostics() {
8900
+ return {
8901
+ pending: this.pendingSimulationFrames.size,
8902
+ dropped: this.droppedSimulationFrames,
8903
+ pendingReceipts: this.pendingFrameReceipts.size,
8904
+ queuedRealtimeInputs: this.queuedRealtimeInputs.length,
8905
+ replayRealtimeInputs: this.unconfirmedRealtimeBatches.reduce(
8906
+ (count, batch) => count + batch.messages.length,
8907
+ 0
8908
+ ),
8909
+ unconfirmedRealtimeBatches: this.unconfirmedRealtimeBatches.length,
8910
+ realtimeBatchesSent: this.realtimeBatchesSent,
8911
+ realtimeInputsSent: this.realtimeInputsSent,
8912
+ realtimeWatchdogArmed: this.realtimeInputWatchdogHandle !== void 0,
8913
+ simulationClockPulsesActive: this.simulationClockPulseIntervalHandle !== void 0
8914
+ };
8915
+ }
8616
8916
  send(message) {
8617
8917
  if (this.closedForever) return;
8918
+ if (message.type === "enqueueInput" && message.transport === "realtime" && this.getRealtimeInputSequence(message)) {
8919
+ this.queuedRealtimeInputs.push(message);
8920
+ this.armRealtimeInputWatchdog();
8921
+ return;
8922
+ }
8923
+ this.sendTransportMessage(message);
8924
+ }
8925
+ sendTransportMessage(message) {
8618
8926
  if (this.options.debug) {
8619
8927
  console.info("ws sending message", message);
8620
8928
  }
@@ -8624,9 +8932,124 @@ var WebSocketConnection = class {
8624
8932
  });
8625
8933
  this.ws.send(JSON.stringify(message));
8626
8934
  }
8935
+ getRealtimeInputSequence(message) {
8936
+ const payload = message.payload;
8937
+ if (!payload || typeof payload !== "object") return;
8938
+ const streamId = Reflect.get(payload, "streamId");
8939
+ const sequence = Reflect.get(payload, "sequence");
8940
+ if (typeof streamId !== "string" || streamId.length === 0 || !Number.isInteger(sequence) || sequence < 0) {
8941
+ return;
8942
+ }
8943
+ return {
8944
+ key: `${message.queue}\0${streamId}`,
8945
+ sequence
8946
+ };
8947
+ }
8948
+ getContiguousRealtimeInputCount() {
8949
+ const sequences = new Map(this.lastSentRealtimeSequences);
8950
+ let count = 0;
8951
+ for (const message of this.queuedRealtimeInputs) {
8952
+ if (count >= MAX_CLIENT_MESSAGE_BATCH_SIZE) break;
8953
+ const input = this.getRealtimeInputSequence(message);
8954
+ if (!input) break;
8955
+ const previous = sequences.get(input.key);
8956
+ if (input.sequence !== (previous === void 0 ? 0 : previous + 1)) {
8957
+ break;
8958
+ }
8959
+ sequences.set(input.key, input.sequence);
8960
+ count += 1;
8961
+ }
8962
+ return { count, sequences };
8963
+ }
8964
+ flushRealtimeBatch(receipt) {
8965
+ if (this.closedForever || this.ws.state !== "OPEN") return false;
8966
+ const canSendInputBatch = this.unconfirmedRealtimeBatches.length < MAX_REALTIME_INPUT_BATCHES_IN_FLIGHT;
8967
+ const contiguous = this.getContiguousRealtimeInputCount();
8968
+ const messages = canSendInputBatch ? this.queuedRealtimeInputs.slice(0, contiguous.count) : [];
8969
+ if (!receipt && messages.length === 0) return false;
8970
+ const batch = {
8971
+ type: "clientMessageBatch",
8972
+ ...receipt && { receipt },
8973
+ ...messages.length > 0 && {
8974
+ inputBatch: {
8975
+ sessionId: this.realtimeInputSessionId,
8976
+ sequence: this.nextRealtimeInputBatchSequence
8977
+ }
8978
+ },
8979
+ messages
8980
+ };
8981
+ this.sendTransportMessage(batch);
8982
+ if (messages.length > 0) {
8983
+ this.queuedRealtimeInputs.splice(0, messages.length);
8984
+ this.lastSentRealtimeSequences = contiguous.sequences;
8985
+ this.unconfirmedRealtimeBatches.push(batch);
8986
+ this.nextRealtimeInputBatchSequence += 1;
8987
+ this.realtimeInputsSent += messages.length;
8988
+ }
8989
+ this.realtimeBatchesSent += 1;
8990
+ return true;
8991
+ }
8992
+ flushPendingFrameReceipts() {
8993
+ for (const [scriptId, receipt] of Array.from(
8994
+ this.pendingFrameReceipts.entries()
8995
+ )) {
8996
+ if (!this.flushRealtimeBatch(receipt)) continue;
8997
+ if (this.pendingFrameReceipts.get(scriptId) === receipt) {
8998
+ this.pendingFrameReceipts.delete(scriptId);
8999
+ }
9000
+ }
9001
+ if (this.queuedRealtimeInputs.length > 0) {
9002
+ this.armRealtimeInputWatchdog();
9003
+ } else {
9004
+ this.stopRealtimeInputWatchdog();
9005
+ }
9006
+ }
9007
+ armRealtimeInputWatchdog() {
9008
+ if (this.realtimeInputWatchdogHandle !== void 0 || this.queuedRealtimeInputs.length === 0 || this.closedForever || this.ws.state !== "OPEN") {
9009
+ return;
9010
+ }
9011
+ const configuredDelay = this.options.realtimeInputWatchdogMs;
9012
+ const delay = configuredDelay !== void 0 && Number.isFinite(configuredDelay) ? Math.max(1, Math.floor(configuredDelay)) : 200;
9013
+ this.realtimeInputWatchdogHandle = setTimeout(() => {
9014
+ this.realtimeInputWatchdogHandle = void 0;
9015
+ this.flushRealtimeBatch();
9016
+ if (this.queuedRealtimeInputs.length > 0) {
9017
+ this.armRealtimeInputWatchdog();
9018
+ }
9019
+ }, delay);
9020
+ }
9021
+ stopRealtimeInputWatchdog() {
9022
+ if (this.realtimeInputWatchdogHandle === void 0) return;
9023
+ clearTimeout(this.realtimeInputWatchdogHandle);
9024
+ this.realtimeInputWatchdogHandle = void 0;
9025
+ }
9026
+ replayUnconfirmedRealtimeBatches() {
9027
+ if (this.closedForever || this.ws.state !== "OPEN") return;
9028
+ for (const batch of this.unconfirmedRealtimeBatches) {
9029
+ this.sendTransportMessage(batch);
9030
+ }
9031
+ }
9032
+ confirmRealtimeInputBatches(sessionId, sequence) {
9033
+ if (sessionId !== this.realtimeInputSessionId || !Number.isInteger(sequence) || sequence < 0) {
9034
+ return;
9035
+ }
9036
+ this.unconfirmedRealtimeBatches = this.unconfirmedRealtimeBatches.filter(
9037
+ (batch) => !batch.inputBatch || batch.inputBatch.sequence > sequence
9038
+ );
9039
+ if (this.queuedRealtimeInputs.length > 0) {
9040
+ this.armRealtimeInputWatchdog();
9041
+ }
9042
+ }
8627
9043
  close() {
8628
9044
  if (this.closedForever) return;
8629
9045
  this.closedForever = true;
9046
+ this.stopHeartbeat();
9047
+ this.stopSimulationClockPulses();
9048
+ this.stopRealtimeInputWatchdog();
9049
+ this.queuedRealtimeInputs = [];
9050
+ this.unconfirmedRealtimeBatches = [];
9051
+ this.lastSentRealtimeSequences.clear();
9052
+ this.deliveredSimulationFrameCursors.clear();
8630
9053
  this.ws.shutdown();
8631
9054
  this.ws.listeners.forEach((listener) => {
8632
9055
  this.ws.removeListener(listener);
@@ -8662,6 +9085,7 @@ export {
8662
9085
  importAll,
8663
9086
  initializeNoyaSingleton,
8664
9087
  parseAppDataParameter,
9088
+ parseInspectorParameter,
8665
9089
  replaceDeep,
8666
9090
  resourceToCreateParameters,
8667
9091
  resourceToMediaItem,