@noya-app/noya-multiplayer-react 0.1.88 → 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
@@ -4100,49 +4100,67 @@ var gistSchema = Type.Object({
4100
4100
 
4101
4101
  // ../noya-schemas/src/jsonSchema.ts
4102
4102
  var jsonSchema = Type.Recursive(
4103
- (This) => Type.Union(
4104
- [
4105
- Type.Object({
4106
- type: Type.Literal("string"),
4107
- default: Type.Optional(Type.String()),
4108
- _kind: Type.Literal("String")
4109
- }),
4110
- Type.Object({
4111
- type: Type.Literal("number"),
4112
- default: Type.Optional(Type.Number()),
4113
- minimum: Type.Optional(Type.Number()),
4114
- maximum: Type.Optional(Type.Number()),
4115
- _kind: Type.Literal("Number")
4116
- }),
4117
- Type.Object({
4118
- type: Type.Literal("boolean"),
4119
- default: Type.Optional(Type.Boolean()),
4120
- _kind: Type.Literal("Boolean")
4121
- }),
4122
- Type.Object({
4123
- type: Type.Literal("null"),
4124
- _kind: Type.Literal("Null")
4125
- }),
4126
- Type.Object({
4127
- type: Type.Literal("array"),
4128
- items: This,
4129
- // default: Type.Optional(Type.Array(Type.Any())),
4130
- // minItems: Type.Optional(Type.Number()),
4131
- // maxItems: Type.Optional(Type.Number()),
4132
- _kind: Type.Literal("Array")
4133
- }),
4134
- Type.Object({
4135
- type: Type.Literal("object"),
4136
- properties: Type.Record(Type.String(), This),
4137
- required: Type.Optional(Type.Array(Type.String())),
4138
- // default: Type.Optional(Type.Record(Type.String(), Type.Any())),
4139
- _kind: Type.Literal("Object")
4140
- })
4141
- ],
4142
- {
4143
- discriminator: "type"
4144
- }
4145
- ),
4103
+ (This) => Type.Union([
4104
+ Type.Object({
4105
+ type: Type.Literal("string"),
4106
+ default: Type.Optional(Type.String()),
4107
+ _kind: Type.Literal("String")
4108
+ }),
4109
+ Type.Object({
4110
+ type: Type.Literal("number"),
4111
+ default: Type.Optional(Type.Number()),
4112
+ minimum: Type.Optional(Type.Number()),
4113
+ maximum: Type.Optional(Type.Number()),
4114
+ _kind: Type.Literal("Number")
4115
+ }),
4116
+ Type.Object({
4117
+ type: Type.Literal("boolean"),
4118
+ default: Type.Optional(Type.Boolean()),
4119
+ _kind: Type.Literal("Boolean")
4120
+ }),
4121
+ Type.Object({
4122
+ type: Type.Literal("null"),
4123
+ _kind: Type.Literal("Null")
4124
+ }),
4125
+ Type.Object({
4126
+ type: Type.Literal("array"),
4127
+ items: This,
4128
+ // default: Type.Optional(Type.Array(Type.Any())),
4129
+ // minItems: Type.Optional(Type.Number()),
4130
+ // maxItems: Type.Optional(Type.Number()),
4131
+ _kind: Type.Literal("Array")
4132
+ }),
4133
+ Type.Object({
4134
+ type: Type.Literal("object"),
4135
+ properties: Type.Record(Type.String(), This),
4136
+ required: Type.Optional(Type.Array(Type.String())),
4137
+ // default: Type.Optional(Type.Record(Type.String(), Type.Any())),
4138
+ _kind: Type.Literal("Object")
4139
+ }),
4140
+ Type.Object({
4141
+ const: Type.Union([
4142
+ Type.String(),
4143
+ Type.Number(),
4144
+ Type.Boolean(),
4145
+ Type.Null()
4146
+ ]),
4147
+ type: Type.Optional(
4148
+ Type.Union([
4149
+ Type.Literal("string"),
4150
+ Type.Literal("number"),
4151
+ Type.Literal("boolean"),
4152
+ Type.Literal("null")
4153
+ ])
4154
+ ),
4155
+ _kind: Type.Literal("Literal")
4156
+ }),
4157
+ Type.Object({
4158
+ anyOf: Type.Array(This, { minItems: 1 }),
4159
+ discriminator: Type.Optional(Type.String()),
4160
+ default: Type.Optional(Type.Any()),
4161
+ _kind: Type.Literal("Union")
4162
+ })
4163
+ ]),
4146
4164
  {
4147
4165
  $id: "jsonSchema"
4148
4166
  }
@@ -4412,6 +4430,13 @@ function findAllDefs(schema) {
4412
4430
  }
4413
4431
 
4414
4432
  // ../noya-schemas/src/resourceSchema.ts
4433
+ var targetFileSchema = Type.Object({
4434
+ id: Type.String(),
4435
+ toolId: Type.String(),
4436
+ tool: Type.Object({
4437
+ theme: Type.Optional(Type.Any())
4438
+ })
4439
+ });
4415
4440
  var baseSchemaProperties = {
4416
4441
  id: Type.String(),
4417
4442
  stableId: Type.String(),
@@ -4427,6 +4452,7 @@ var baseSchemaProperties = {
4427
4452
  })
4428
4453
  ),
4429
4454
  accessibleByFileVersionId: Nullable(Type.String()),
4455
+ file: Type.Optional(Nullable(targetFileSchema)),
4430
4456
  url: Type.Optional(Type.String()),
4431
4457
  transformUrl: Type.Optional(Type.String())
4432
4458
  };
@@ -4818,12 +4844,12 @@ var TOKEN_PRIMITIVE = Symbol("primitive");
4818
4844
  var TOKEN_ARGSEP = Symbol("argsep");
4819
4845
 
4820
4846
  // ../../node_modules/uuid/dist/esm-node/rng.js
4821
- import crypto from "crypto";
4847
+ import crypto2 from "crypto";
4822
4848
  var rnds8Pool = new Uint8Array(256);
4823
4849
  var poolPtr = rnds8Pool.length;
4824
4850
  function rng() {
4825
4851
  if (poolPtr > rnds8Pool.length - 16) {
4826
- crypto.randomFillSync(rnds8Pool);
4852
+ crypto2.randomFillSync(rnds8Pool);
4827
4853
  poolPtr = 0;
4828
4854
  }
4829
4855
  return rnds8Pool.slice(poolPtr, poolPtr += 16);
@@ -7734,6 +7760,16 @@ function parseAppDataParameter(options) {
7734
7760
  return null;
7735
7761
  }
7736
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
+ }
7737
7773
  function applyClientIdentityFromAppData(appData) {
7738
7774
  if (appData.clientId !== void 0) {
7739
7775
  setClientId(appData.clientId);
@@ -7748,6 +7784,8 @@ function applyClientIdentityFromAppData(appData) {
7748
7784
  function getAppData(initialState, schema, options) {
7749
7785
  const resolvedInitialState = initialState instanceof Function ? initialState() : initialState;
7750
7786
  let appData = parseAppDataParameter(options) ?? createDefaultAppData(resolvedInitialState);
7787
+ const inspector = parseInspectorParameter(options);
7788
+ if (inspector !== null) appData.inspector = inspector;
7751
7789
  if (options?.overrideExistingState || appData.initialState === null) {
7752
7790
  appData.initialState = resolvedInitialState;
7753
7791
  }
@@ -8523,21 +8561,49 @@ var UserPointersOverlay = memoGeneric2(function UserPointers({
8523
8561
 
8524
8562
  // src/useResourceUrl.ts
8525
8563
  import { getResourceUrl } from "@noya-app/state-manager";
8526
- function useResourceUrl(idOrStableId) {
8564
+ function useResourceUrl(idOrStableId, options) {
8527
8565
  const { noyaManager } = useAnyNoyaStateContext();
8528
8566
  const resources = useObservable(
8529
8567
  noyaManager.resourceManager.optimisticResources$
8530
8568
  );
8531
8569
  const assets = useObservable(noyaManager.assetManager.assets$);
8532
8570
  if (!idOrStableId) return void 0;
8533
- return getResourceUrl(idOrStableId, resources, assets);
8571
+ return getResourceUrl(idOrStableId, resources, assets, options);
8534
8572
  }
8535
8573
 
8536
8574
  // src/WebSocketConnection.ts
8537
8575
  import {
8576
+ MAX_CLIENT_MESSAGE_BATCH_SIZE,
8577
+ MAX_REALTIME_INPUT_BATCHES_IN_FLIGHT,
8538
8578
  ReconnectingWebSocket,
8539
8579
  UserActivityDetector
8540
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
+ }
8541
8607
  var WebSocketConnection = class {
8542
8608
  constructor(url, options = {}) {
8543
8609
  this.url = url;
@@ -8552,12 +8618,40 @@ var WebSocketConnection = class {
8552
8618
  }
8553
8619
  ws;
8554
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;
8555
8646
  async connect() {
8556
8647
  if (this.closedForever) return;
8557
8648
  this.ws.connect(this.url.toString());
8558
8649
  }
8559
8650
  handleOpen = () => {
8560
8651
  if (this.closedForever) return;
8652
+ this.startHeartbeat();
8653
+ this.replayUnconfirmedRealtimeBatches();
8654
+ this.armRealtimeInputWatchdog();
8561
8655
  if (this.options.debug) {
8562
8656
  console.info("ws connected");
8563
8657
  }
@@ -8568,16 +8662,124 @@ var WebSocketConnection = class {
8568
8662
  };
8569
8663
  handleMessage = (event) => {
8570
8664
  if (this.closedForever) return;
8665
+ this.armSilentTimeout();
8571
8666
  if (this.options.debug) {
8572
8667
  console.info("ws receiving message ", event.data);
8573
8668
  }
8574
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) {
8575
8719
  this.options.onConnectionEvent?.({
8576
8720
  type: "receive",
8577
- message: parsed
8721
+ message
8578
8722
  });
8579
- };
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
+ }
8580
8779
  handleClose = () => {
8780
+ this.stopHeartbeat();
8781
+ this.stopSimulationClockPulses();
8782
+ this.stopRealtimeInputWatchdog();
8581
8783
  if (this.closedForever) return;
8582
8784
  if (this.options.debug) {
8583
8785
  console.info("ws disconnected");
@@ -8587,8 +8789,140 @@ var WebSocketConnection = class {
8587
8789
  state: "CLOSED"
8588
8790
  });
8589
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
+ }
8590
8916
  send(message) {
8591
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) {
8592
8926
  if (this.options.debug) {
8593
8927
  console.info("ws sending message", message);
8594
8928
  }
@@ -8598,9 +8932,124 @@ var WebSocketConnection = class {
8598
8932
  });
8599
8933
  this.ws.send(JSON.stringify(message));
8600
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
+ }
8601
9043
  close() {
8602
9044
  if (this.closedForever) return;
8603
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();
8604
9053
  this.ws.shutdown();
8605
9054
  this.ws.listeners.forEach((listener) => {
8606
9055
  this.ws.removeListener(listener);
@@ -8636,6 +9085,7 @@ export {
8636
9085
  importAll,
8637
9086
  initializeNoyaSingleton,
8638
9087
  parseAppDataParameter,
9088
+ parseInspectorParameter,
8639
9089
  replaceDeep,
8640
9090
  resourceToCreateParameters,
8641
9091
  resourceToMediaItem,