@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.js CHANGED
@@ -1517,6 +1517,7 @@ __export(src_exports, {
1517
1517
  importAll: () => importAll,
1518
1518
  initializeNoyaSingleton: () => initializeNoyaSingleton,
1519
1519
  parseAppDataParameter: () => parseAppDataParameter,
1520
+ parseInspectorParameter: () => parseInspectorParameter,
1520
1521
  replaceDeep: () => replaceDeep,
1521
1522
  resourceToCreateParameters: () => resourceToCreateParameters,
1522
1523
  resourceToMediaItem: () => resourceToMediaItem,
@@ -4179,49 +4180,67 @@ var gistSchema = Type.Object({
4179
4180
 
4180
4181
  // ../noya-schemas/src/jsonSchema.ts
4181
4182
  var jsonSchema = Type.Recursive(
4182
- (This) => Type.Union(
4183
- [
4184
- Type.Object({
4185
- type: Type.Literal("string"),
4186
- default: Type.Optional(Type.String()),
4187
- _kind: Type.Literal("String")
4188
- }),
4189
- Type.Object({
4190
- type: Type.Literal("number"),
4191
- default: Type.Optional(Type.Number()),
4192
- minimum: Type.Optional(Type.Number()),
4193
- maximum: Type.Optional(Type.Number()),
4194
- _kind: Type.Literal("Number")
4195
- }),
4196
- Type.Object({
4197
- type: Type.Literal("boolean"),
4198
- default: Type.Optional(Type.Boolean()),
4199
- _kind: Type.Literal("Boolean")
4200
- }),
4201
- Type.Object({
4202
- type: Type.Literal("null"),
4203
- _kind: Type.Literal("Null")
4204
- }),
4205
- Type.Object({
4206
- type: Type.Literal("array"),
4207
- items: This,
4208
- // default: Type.Optional(Type.Array(Type.Any())),
4209
- // minItems: Type.Optional(Type.Number()),
4210
- // maxItems: Type.Optional(Type.Number()),
4211
- _kind: Type.Literal("Array")
4212
- }),
4213
- Type.Object({
4214
- type: Type.Literal("object"),
4215
- properties: Type.Record(Type.String(), This),
4216
- required: Type.Optional(Type.Array(Type.String())),
4217
- // default: Type.Optional(Type.Record(Type.String(), Type.Any())),
4218
- _kind: Type.Literal("Object")
4219
- })
4220
- ],
4221
- {
4222
- discriminator: "type"
4223
- }
4224
- ),
4183
+ (This) => Type.Union([
4184
+ Type.Object({
4185
+ type: Type.Literal("string"),
4186
+ default: Type.Optional(Type.String()),
4187
+ _kind: Type.Literal("String")
4188
+ }),
4189
+ Type.Object({
4190
+ type: Type.Literal("number"),
4191
+ default: Type.Optional(Type.Number()),
4192
+ minimum: Type.Optional(Type.Number()),
4193
+ maximum: Type.Optional(Type.Number()),
4194
+ _kind: Type.Literal("Number")
4195
+ }),
4196
+ Type.Object({
4197
+ type: Type.Literal("boolean"),
4198
+ default: Type.Optional(Type.Boolean()),
4199
+ _kind: Type.Literal("Boolean")
4200
+ }),
4201
+ Type.Object({
4202
+ type: Type.Literal("null"),
4203
+ _kind: Type.Literal("Null")
4204
+ }),
4205
+ Type.Object({
4206
+ type: Type.Literal("array"),
4207
+ items: This,
4208
+ // default: Type.Optional(Type.Array(Type.Any())),
4209
+ // minItems: Type.Optional(Type.Number()),
4210
+ // maxItems: Type.Optional(Type.Number()),
4211
+ _kind: Type.Literal("Array")
4212
+ }),
4213
+ Type.Object({
4214
+ type: Type.Literal("object"),
4215
+ properties: Type.Record(Type.String(), This),
4216
+ required: Type.Optional(Type.Array(Type.String())),
4217
+ // default: Type.Optional(Type.Record(Type.String(), Type.Any())),
4218
+ _kind: Type.Literal("Object")
4219
+ }),
4220
+ Type.Object({
4221
+ const: Type.Union([
4222
+ Type.String(),
4223
+ Type.Number(),
4224
+ Type.Boolean(),
4225
+ Type.Null()
4226
+ ]),
4227
+ type: Type.Optional(
4228
+ Type.Union([
4229
+ Type.Literal("string"),
4230
+ Type.Literal("number"),
4231
+ Type.Literal("boolean"),
4232
+ Type.Literal("null")
4233
+ ])
4234
+ ),
4235
+ _kind: Type.Literal("Literal")
4236
+ }),
4237
+ Type.Object({
4238
+ anyOf: Type.Array(This, { minItems: 1 }),
4239
+ discriminator: Type.Optional(Type.String()),
4240
+ default: Type.Optional(Type.Any()),
4241
+ _kind: Type.Literal("Union")
4242
+ })
4243
+ ]),
4225
4244
  {
4226
4245
  $id: "jsonSchema"
4227
4246
  }
@@ -4491,6 +4510,13 @@ function findAllDefs(schema) {
4491
4510
  }
4492
4511
 
4493
4512
  // ../noya-schemas/src/resourceSchema.ts
4513
+ var targetFileSchema = Type.Object({
4514
+ id: Type.String(),
4515
+ toolId: Type.String(),
4516
+ tool: Type.Object({
4517
+ theme: Type.Optional(Type.Any())
4518
+ })
4519
+ });
4494
4520
  var baseSchemaProperties = {
4495
4521
  id: Type.String(),
4496
4522
  stableId: Type.String(),
@@ -4506,6 +4532,7 @@ var baseSchemaProperties = {
4506
4532
  })
4507
4533
  ),
4508
4534
  accessibleByFileVersionId: Nullable(Type.String()),
4535
+ file: Type.Optional(Nullable(targetFileSchema)),
4509
4536
  url: Type.Optional(Type.String()),
4510
4537
  transformUrl: Type.Optional(Type.String())
4511
4538
  };
@@ -7772,6 +7799,16 @@ function parseAppDataParameter(options) {
7772
7799
  return null;
7773
7800
  }
7774
7801
  }
7802
+ function parseInspectorParameter(options) {
7803
+ const window2 = options?.window ?? globalThis;
7804
+ const location = window2.location;
7805
+ if (!location) return null;
7806
+ const params = new URLSearchParams((location.hash || "").replace(/^#/, ""));
7807
+ const inspector = params.get("inspector");
7808
+ if (inspector === "true") return true;
7809
+ if (inspector === "false") return false;
7810
+ return null;
7811
+ }
7775
7812
  function applyClientIdentityFromAppData(appData) {
7776
7813
  if (appData.clientId !== void 0) {
7777
7814
  (0, import_state_manager3.setClientId)(appData.clientId);
@@ -7786,6 +7823,8 @@ function applyClientIdentityFromAppData(appData) {
7786
7823
  function getAppData(initialState, schema, options) {
7787
7824
  const resolvedInitialState = initialState instanceof Function ? initialState() : initialState;
7788
7825
  let appData = parseAppDataParameter(options) ?? createDefaultAppData(resolvedInitialState);
7826
+ const inspector = parseInspectorParameter(options);
7827
+ if (inspector !== null) appData.inspector = inspector;
7789
7828
  if (options?.overrideExistingState || appData.initialState === null) {
7790
7829
  appData.initialState = resolvedInitialState;
7791
7830
  }
@@ -8559,18 +8598,44 @@ var UserPointersOverlay = (0, import_react_utils4.memoGeneric)(function UserPoin
8559
8598
 
8560
8599
  // src/useResourceUrl.ts
8561
8600
  var import_state_manager6 = require("@noya-app/state-manager");
8562
- function useResourceUrl(idOrStableId) {
8601
+ function useResourceUrl(idOrStableId, options) {
8563
8602
  const { noyaManager } = useAnyNoyaStateContext();
8564
8603
  const resources = useObservable(
8565
8604
  noyaManager.resourceManager.optimisticResources$
8566
8605
  );
8567
8606
  const assets = useObservable(noyaManager.assetManager.assets$);
8568
8607
  if (!idOrStableId) return void 0;
8569
- return (0, import_state_manager6.getResourceUrl)(idOrStableId, resources, assets);
8608
+ return (0, import_state_manager6.getResourceUrl)(idOrStableId, resources, assets, options);
8570
8609
  }
8571
8610
 
8572
8611
  // src/WebSocketConnection.ts
8573
8612
  var import_state_manager7 = require("@noya-app/state-manager");
8613
+ function simulationSessionIdentity(state) {
8614
+ if (!state || typeof state !== "object" || Array.isArray(state)) return;
8615
+ const game = Reflect.get(state, "$game");
8616
+ if (!game || typeof game !== "object" || Array.isArray(game)) return;
8617
+ const startedAt = Reflect.get(game, "startedAt");
8618
+ if (startedAt === null || typeof startedAt === "string" || typeof startedAt === "number" && Number.isFinite(startedAt)) {
8619
+ return `startedAt:${typeof startedAt}:${String(startedAt)}`;
8620
+ }
8621
+ }
8622
+ function simulationStateSequence(state) {
8623
+ if (!state || typeof state !== "object" || Array.isArray(state)) return;
8624
+ const simulation = Reflect.get(state, "simulation");
8625
+ const simulationTick = simulation && typeof simulation === "object" ? Reflect.get(simulation, "tick") : void 0;
8626
+ if (Number.isSafeInteger(simulationTick) && simulationTick >= 0) {
8627
+ return simulationTick;
8628
+ }
8629
+ const game = Reflect.get(state, "$game");
8630
+ const gameTick = game && typeof game === "object" ? Reflect.get(game, "simulationTick") : void 0;
8631
+ return Number.isSafeInteger(gameTick) && gameTick >= 0 ? gameTick : void 0;
8632
+ }
8633
+ function sameSimulationSession(left, right) {
8634
+ return left.sessionIdentity === void 0 || right.sessionIdentity === void 0 || left.sessionIdentity === right.sessionIdentity;
8635
+ }
8636
+ function createRealtimeInputSessionId() {
8637
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
8638
+ }
8574
8639
  var WebSocketConnection = class {
8575
8640
  constructor(url, options = {}) {
8576
8641
  this.url = url;
@@ -8585,12 +8650,40 @@ var WebSocketConnection = class {
8585
8650
  }
8586
8651
  ws;
8587
8652
  closedForever = false;
8653
+ heartbeatIntervalHandle;
8654
+ silentTimeoutHandle;
8655
+ simulationClockPulseIntervalHandle;
8656
+ simulationClockPulseSocket;
8657
+ simulationClockDirectFailed = false;
8658
+ heartbeatSequence = 0;
8659
+ pendingSimulationFrames = /* @__PURE__ */ new Map();
8660
+ frameFlushHandle;
8661
+ frameFlushScheduler;
8662
+ droppedSimulationFrames = 0;
8663
+ pendingFrameReceipts = /* @__PURE__ */ new Map();
8664
+ deliveredSimulationFrameCursors = /* @__PURE__ */ new Map();
8665
+ queuedRealtimeInputs = [];
8666
+ /**
8667
+ * A batch stays here until the server explicitly confirms ingestion.
8668
+ * Reconnects resend these exact batches, so a frame that was already in
8669
+ * flight can never create a hole in an input stream.
8670
+ */
8671
+ unconfirmedRealtimeBatches = [];
8672
+ lastSentRealtimeSequences = /* @__PURE__ */ new Map();
8673
+ realtimeInputSessionId = createRealtimeInputSessionId();
8674
+ nextRealtimeInputBatchSequence = 0;
8675
+ realtimeInputWatchdogHandle;
8676
+ realtimeBatchesSent = 0;
8677
+ realtimeInputsSent = 0;
8588
8678
  async connect() {
8589
8679
  if (this.closedForever) return;
8590
8680
  this.ws.connect(this.url.toString());
8591
8681
  }
8592
8682
  handleOpen = () => {
8593
8683
  if (this.closedForever) return;
8684
+ this.startHeartbeat();
8685
+ this.replayUnconfirmedRealtimeBatches();
8686
+ this.armRealtimeInputWatchdog();
8594
8687
  if (this.options.debug) {
8595
8688
  console.info("ws connected");
8596
8689
  }
@@ -8601,16 +8694,124 @@ var WebSocketConnection = class {
8601
8694
  };
8602
8695
  handleMessage = (event) => {
8603
8696
  if (this.closedForever) return;
8697
+ this.armSilentTimeout();
8604
8698
  if (this.options.debug) {
8605
8699
  console.info("ws receiving message ", event.data);
8606
8700
  }
8607
8701
  const parsed = JSON.parse(event.data);
8702
+ if (parsed.type === "simulationClockConfig") {
8703
+ this.applySimulationClockConfig(parsed);
8704
+ return;
8705
+ }
8706
+ if (parsed.type === "simulationFrame") {
8707
+ const cursor = {
8708
+ sequence: parsed.frame.sequence,
8709
+ sessionIdentity: simulationSessionIdentity(parsed.frame.state)
8710
+ };
8711
+ const receipt = {
8712
+ type: "simulationFrameReceipt",
8713
+ scriptId: parsed.frame.scriptId,
8714
+ sequence: parsed.frame.sequence
8715
+ };
8716
+ const pendingReceipt = this.pendingFrameReceipts.get(
8717
+ parsed.frame.scriptId
8718
+ );
8719
+ if (!pendingReceipt || receipt.sequence > pendingReceipt.sequence) {
8720
+ this.pendingFrameReceipts.set(parsed.frame.scriptId, receipt);
8721
+ }
8722
+ const delivered = this.deliveredSimulationFrameCursors.get(
8723
+ parsed.frame.scriptId
8724
+ );
8725
+ const pending = this.pendingSimulationFrames.get(parsed.frame.scriptId);
8726
+ const pendingCursor = pending ? {
8727
+ sequence: pending.frame.sequence,
8728
+ sessionIdentity: simulationSessionIdentity(pending.frame.state)
8729
+ } : void 0;
8730
+ if (delivered && sameSimulationSession(delivered, cursor) && cursor.sequence <= delivered.sequence || pendingCursor && sameSimulationSession(pendingCursor, cursor) && cursor.sequence <= pendingCursor.sequence) {
8731
+ this.droppedSimulationFrames += 1;
8732
+ this.scheduleSimulationFrameFlush();
8733
+ return;
8734
+ }
8735
+ if (pending) this.droppedSimulationFrames += 1;
8736
+ this.pendingSimulationFrames.set(parsed.frame.scriptId, parsed);
8737
+ this.scheduleSimulationFrameFlush();
8738
+ return;
8739
+ }
8740
+ if (parsed.type === "object" && this.shouldSuppressNonMonotonicObject(parsed.object)) {
8741
+ return;
8742
+ }
8743
+ if (parsed.type === "realtimeInputReceipt") {
8744
+ this.confirmRealtimeInputBatches(parsed.sessionId, parsed.sequence);
8745
+ return;
8746
+ }
8747
+ this.flushSimulationFrames();
8748
+ this.emitReceivedMessage(parsed);
8749
+ };
8750
+ emitReceivedMessage(message) {
8608
8751
  this.options.onConnectionEvent?.({
8609
8752
  type: "receive",
8610
- message: parsed
8753
+ message
8611
8754
  });
8612
- };
8755
+ }
8756
+ scheduleSimulationFrameFlush() {
8757
+ if (this.frameFlushHandle !== void 0) return;
8758
+ const flush = () => {
8759
+ this.frameFlushHandle = void 0;
8760
+ this.frameFlushScheduler = void 0;
8761
+ this.flushSimulationFrames();
8762
+ };
8763
+ if (typeof requestAnimationFrame === "function") {
8764
+ this.frameFlushScheduler = "animationFrame";
8765
+ this.frameFlushHandle = requestAnimationFrame(flush);
8766
+ } else {
8767
+ this.frameFlushScheduler = "timeout";
8768
+ this.frameFlushHandle = setTimeout(flush, 0);
8769
+ }
8770
+ }
8771
+ flushSimulationFrames() {
8772
+ this.cancelSimulationFrameFlush();
8773
+ this.flushPendingFrameReceipts();
8774
+ const frames = Array.from(this.pendingSimulationFrames.values());
8775
+ this.pendingSimulationFrames.clear();
8776
+ for (const frame of frames) {
8777
+ this.deliveredSimulationFrameCursors.set(frame.frame.scriptId, {
8778
+ sequence: frame.frame.sequence,
8779
+ sessionIdentity: simulationSessionIdentity(frame.frame.state)
8780
+ });
8781
+ this.emitReceivedMessage(frame);
8782
+ }
8783
+ }
8784
+ shouldSuppressNonMonotonicObject(state) {
8785
+ const sequence = simulationStateSequence(state);
8786
+ if (sequence === void 0) return false;
8787
+ const cursor = {
8788
+ sequence,
8789
+ sessionIdentity: simulationSessionIdentity(state)
8790
+ };
8791
+ const previous = Array.from(this.deliveredSimulationFrameCursors.values());
8792
+ const comparable = previous.filter(
8793
+ (entry) => sameSimulationSession(entry, cursor)
8794
+ );
8795
+ if (cursor.sessionIdentity !== void 0 && previous.some((entry) => entry.sessionIdentity !== void 0) && comparable.length === 0) {
8796
+ this.deliveredSimulationFrameCursors.clear();
8797
+ return false;
8798
+ }
8799
+ return comparable.some((entry) => entry.sequence > cursor.sequence);
8800
+ }
8801
+ cancelSimulationFrameFlush() {
8802
+ if (this.frameFlushHandle === void 0) return;
8803
+ if (this.frameFlushScheduler === "animationFrame") {
8804
+ cancelAnimationFrame(this.frameFlushHandle);
8805
+ } else {
8806
+ clearTimeout(this.frameFlushHandle);
8807
+ }
8808
+ this.frameFlushHandle = void 0;
8809
+ this.frameFlushScheduler = void 0;
8810
+ }
8613
8811
  handleClose = () => {
8812
+ this.stopHeartbeat();
8813
+ this.stopSimulationClockPulses();
8814
+ this.stopRealtimeInputWatchdog();
8614
8815
  if (this.closedForever) return;
8615
8816
  if (this.options.debug) {
8616
8817
  console.info("ws disconnected");
@@ -8620,8 +8821,140 @@ var WebSocketConnection = class {
8620
8821
  state: "CLOSED"
8621
8822
  });
8622
8823
  };
8824
+ startHeartbeat() {
8825
+ this.stopHeartbeat();
8826
+ this.armSilentTimeout();
8827
+ const intervalMs = this.options.heartbeatIntervalMs ?? 15e3;
8828
+ if (intervalMs <= 0) return;
8829
+ this.heartbeatIntervalHandle = setInterval(() => {
8830
+ if (this.closedForever || this.ws.state !== "OPEN") return;
8831
+ this.send({
8832
+ type: "ping",
8833
+ id: `heartbeat-${Date.now()}-${++this.heartbeatSequence}`,
8834
+ clientSentAt: Date.now()
8835
+ });
8836
+ }, intervalMs);
8837
+ }
8838
+ armSilentTimeout() {
8839
+ if (this.silentTimeoutHandle !== void 0) {
8840
+ clearTimeout(this.silentTimeoutHandle);
8841
+ this.silentTimeoutHandle = void 0;
8842
+ }
8843
+ const timeoutMs = this.options.silentTimeoutMs ?? 45e3;
8844
+ if (timeoutMs <= 0 || this.closedForever || this.ws.state !== "OPEN")
8845
+ return;
8846
+ this.silentTimeoutHandle = setTimeout(() => {
8847
+ this.silentTimeoutHandle = void 0;
8848
+ if (this.closedForever || this.ws.state !== "OPEN") return;
8849
+ this.stopSimulationClockPulses();
8850
+ this.stopHeartbeat();
8851
+ this.ws.reconnect();
8852
+ }, timeoutMs);
8853
+ }
8854
+ stopHeartbeat() {
8855
+ if (this.heartbeatIntervalHandle !== void 0) {
8856
+ clearInterval(this.heartbeatIntervalHandle);
8857
+ this.heartbeatIntervalHandle = void 0;
8858
+ }
8859
+ if (this.silentTimeoutHandle !== void 0) {
8860
+ clearTimeout(this.silentTimeoutHandle);
8861
+ this.silentTimeoutHandle = void 0;
8862
+ }
8863
+ this.cancelSimulationFrameFlush();
8864
+ this.pendingSimulationFrames.clear();
8865
+ this.pendingFrameReceipts.clear();
8866
+ }
8867
+ applySimulationClockConfig(config) {
8868
+ this.stopSimulationClockPulses();
8869
+ if (config.enabled !== true || !Number.isFinite(config.intervalMs) || config.intervalMs <= 0 || this.closedForever || this.ws.state !== "OPEN") {
8870
+ return;
8871
+ }
8872
+ const intervalMs = Math.max(1, Math.floor(config.intervalMs));
8873
+ if (config.pulsePath !== void 0) {
8874
+ this.openSimulationClockPulseSocket(config.pulsePath);
8875
+ }
8876
+ this.simulationClockPulseIntervalHandle = setInterval(() => {
8877
+ if (this.closedForever || this.ws.state !== "OPEN") return;
8878
+ const pulse = JSON.stringify({ type: "simulationClockPulse" });
8879
+ if (this.simulationClockPulseSocket?.state === "OPEN") {
8880
+ this.simulationClockPulseSocket.send(pulse);
8881
+ return;
8882
+ }
8883
+ if (this.simulationClockPulseSocket && !this.simulationClockDirectFailed) {
8884
+ return;
8885
+ }
8886
+ this.sendTransportMessage({ type: "simulationClockPulse" });
8887
+ }, intervalMs);
8888
+ }
8889
+ openSimulationClockPulseSocket(pulsePath) {
8890
+ if (!pulsePath.startsWith("/") || pulsePath.includes("?") || pulsePath.includes("#")) {
8891
+ this.simulationClockDirectFailed = true;
8892
+ return;
8893
+ }
8894
+ const pulseUrl = new URL(this.url);
8895
+ pulseUrl.pathname = pulsePath;
8896
+ pulseUrl.hash = "";
8897
+ const socket = new import_state_manager7.ReconnectingWebSocket({
8898
+ onopen: () => {
8899
+ if (this.simulationClockPulseSocket !== socket) return;
8900
+ this.simulationClockDirectFailed = false;
8901
+ },
8902
+ onclose: () => {
8903
+ if (this.simulationClockPulseSocket !== socket) return;
8904
+ this.simulationClockDirectFailed = true;
8905
+ },
8906
+ onerror: () => {
8907
+ if (this.simulationClockPulseSocket !== socket) return;
8908
+ this.simulationClockDirectFailed = true;
8909
+ }
8910
+ });
8911
+ this.simulationClockPulseSocket = socket;
8912
+ this.simulationClockDirectFailed = false;
8913
+ try {
8914
+ socket.connect(pulseUrl.toString());
8915
+ } catch {
8916
+ this.simulationClockPulseSocket = void 0;
8917
+ this.simulationClockDirectFailed = true;
8918
+ socket.shutdown();
8919
+ }
8920
+ }
8921
+ stopSimulationClockPulses() {
8922
+ if (this.simulationClockPulseIntervalHandle !== void 0) {
8923
+ clearInterval(this.simulationClockPulseIntervalHandle);
8924
+ this.simulationClockPulseIntervalHandle = void 0;
8925
+ }
8926
+ const socket = this.simulationClockPulseSocket;
8927
+ this.simulationClockPulseSocket = void 0;
8928
+ this.simulationClockDirectFailed = false;
8929
+ socket?.shutdown();
8930
+ }
8931
+ getSimulationFrameDiagnostics() {
8932
+ return {
8933
+ pending: this.pendingSimulationFrames.size,
8934
+ dropped: this.droppedSimulationFrames,
8935
+ pendingReceipts: this.pendingFrameReceipts.size,
8936
+ queuedRealtimeInputs: this.queuedRealtimeInputs.length,
8937
+ replayRealtimeInputs: this.unconfirmedRealtimeBatches.reduce(
8938
+ (count, batch) => count + batch.messages.length,
8939
+ 0
8940
+ ),
8941
+ unconfirmedRealtimeBatches: this.unconfirmedRealtimeBatches.length,
8942
+ realtimeBatchesSent: this.realtimeBatchesSent,
8943
+ realtimeInputsSent: this.realtimeInputsSent,
8944
+ realtimeWatchdogArmed: this.realtimeInputWatchdogHandle !== void 0,
8945
+ simulationClockPulsesActive: this.simulationClockPulseIntervalHandle !== void 0
8946
+ };
8947
+ }
8623
8948
  send(message) {
8624
8949
  if (this.closedForever) return;
8950
+ if (message.type === "enqueueInput" && message.transport === "realtime" && this.getRealtimeInputSequence(message)) {
8951
+ this.queuedRealtimeInputs.push(message);
8952
+ this.armRealtimeInputWatchdog();
8953
+ return;
8954
+ }
8955
+ this.sendTransportMessage(message);
8956
+ }
8957
+ sendTransportMessage(message) {
8625
8958
  if (this.options.debug) {
8626
8959
  console.info("ws sending message", message);
8627
8960
  }
@@ -8631,9 +8964,124 @@ var WebSocketConnection = class {
8631
8964
  });
8632
8965
  this.ws.send(JSON.stringify(message));
8633
8966
  }
8967
+ getRealtimeInputSequence(message) {
8968
+ const payload = message.payload;
8969
+ if (!payload || typeof payload !== "object") return;
8970
+ const streamId = Reflect.get(payload, "streamId");
8971
+ const sequence = Reflect.get(payload, "sequence");
8972
+ if (typeof streamId !== "string" || streamId.length === 0 || !Number.isInteger(sequence) || sequence < 0) {
8973
+ return;
8974
+ }
8975
+ return {
8976
+ key: `${message.queue}\0${streamId}`,
8977
+ sequence
8978
+ };
8979
+ }
8980
+ getContiguousRealtimeInputCount() {
8981
+ const sequences = new Map(this.lastSentRealtimeSequences);
8982
+ let count = 0;
8983
+ for (const message of this.queuedRealtimeInputs) {
8984
+ if (count >= import_state_manager7.MAX_CLIENT_MESSAGE_BATCH_SIZE) break;
8985
+ const input = this.getRealtimeInputSequence(message);
8986
+ if (!input) break;
8987
+ const previous = sequences.get(input.key);
8988
+ if (input.sequence !== (previous === void 0 ? 0 : previous + 1)) {
8989
+ break;
8990
+ }
8991
+ sequences.set(input.key, input.sequence);
8992
+ count += 1;
8993
+ }
8994
+ return { count, sequences };
8995
+ }
8996
+ flushRealtimeBatch(receipt) {
8997
+ if (this.closedForever || this.ws.state !== "OPEN") return false;
8998
+ const canSendInputBatch = this.unconfirmedRealtimeBatches.length < import_state_manager7.MAX_REALTIME_INPUT_BATCHES_IN_FLIGHT;
8999
+ const contiguous = this.getContiguousRealtimeInputCount();
9000
+ const messages = canSendInputBatch ? this.queuedRealtimeInputs.slice(0, contiguous.count) : [];
9001
+ if (!receipt && messages.length === 0) return false;
9002
+ const batch = {
9003
+ type: "clientMessageBatch",
9004
+ ...receipt && { receipt },
9005
+ ...messages.length > 0 && {
9006
+ inputBatch: {
9007
+ sessionId: this.realtimeInputSessionId,
9008
+ sequence: this.nextRealtimeInputBatchSequence
9009
+ }
9010
+ },
9011
+ messages
9012
+ };
9013
+ this.sendTransportMessage(batch);
9014
+ if (messages.length > 0) {
9015
+ this.queuedRealtimeInputs.splice(0, messages.length);
9016
+ this.lastSentRealtimeSequences = contiguous.sequences;
9017
+ this.unconfirmedRealtimeBatches.push(batch);
9018
+ this.nextRealtimeInputBatchSequence += 1;
9019
+ this.realtimeInputsSent += messages.length;
9020
+ }
9021
+ this.realtimeBatchesSent += 1;
9022
+ return true;
9023
+ }
9024
+ flushPendingFrameReceipts() {
9025
+ for (const [scriptId, receipt] of Array.from(
9026
+ this.pendingFrameReceipts.entries()
9027
+ )) {
9028
+ if (!this.flushRealtimeBatch(receipt)) continue;
9029
+ if (this.pendingFrameReceipts.get(scriptId) === receipt) {
9030
+ this.pendingFrameReceipts.delete(scriptId);
9031
+ }
9032
+ }
9033
+ if (this.queuedRealtimeInputs.length > 0) {
9034
+ this.armRealtimeInputWatchdog();
9035
+ } else {
9036
+ this.stopRealtimeInputWatchdog();
9037
+ }
9038
+ }
9039
+ armRealtimeInputWatchdog() {
9040
+ if (this.realtimeInputWatchdogHandle !== void 0 || this.queuedRealtimeInputs.length === 0 || this.closedForever || this.ws.state !== "OPEN") {
9041
+ return;
9042
+ }
9043
+ const configuredDelay = this.options.realtimeInputWatchdogMs;
9044
+ const delay = configuredDelay !== void 0 && Number.isFinite(configuredDelay) ? Math.max(1, Math.floor(configuredDelay)) : 200;
9045
+ this.realtimeInputWatchdogHandle = setTimeout(() => {
9046
+ this.realtimeInputWatchdogHandle = void 0;
9047
+ this.flushRealtimeBatch();
9048
+ if (this.queuedRealtimeInputs.length > 0) {
9049
+ this.armRealtimeInputWatchdog();
9050
+ }
9051
+ }, delay);
9052
+ }
9053
+ stopRealtimeInputWatchdog() {
9054
+ if (this.realtimeInputWatchdogHandle === void 0) return;
9055
+ clearTimeout(this.realtimeInputWatchdogHandle);
9056
+ this.realtimeInputWatchdogHandle = void 0;
9057
+ }
9058
+ replayUnconfirmedRealtimeBatches() {
9059
+ if (this.closedForever || this.ws.state !== "OPEN") return;
9060
+ for (const batch of this.unconfirmedRealtimeBatches) {
9061
+ this.sendTransportMessage(batch);
9062
+ }
9063
+ }
9064
+ confirmRealtimeInputBatches(sessionId, sequence) {
9065
+ if (sessionId !== this.realtimeInputSessionId || !Number.isInteger(sequence) || sequence < 0) {
9066
+ return;
9067
+ }
9068
+ this.unconfirmedRealtimeBatches = this.unconfirmedRealtimeBatches.filter(
9069
+ (batch) => !batch.inputBatch || batch.inputBatch.sequence > sequence
9070
+ );
9071
+ if (this.queuedRealtimeInputs.length > 0) {
9072
+ this.armRealtimeInputWatchdog();
9073
+ }
9074
+ }
8634
9075
  close() {
8635
9076
  if (this.closedForever) return;
8636
9077
  this.closedForever = true;
9078
+ this.stopHeartbeat();
9079
+ this.stopSimulationClockPulses();
9080
+ this.stopRealtimeInputWatchdog();
9081
+ this.queuedRealtimeInputs = [];
9082
+ this.unconfirmedRealtimeBatches = [];
9083
+ this.lastSentRealtimeSequences.clear();
9084
+ this.deliveredSimulationFrameCursors.clear();
8637
9085
  this.ws.shutdown();
8638
9086
  this.ws.listeners.forEach((listener) => {
8639
9087
  this.ws.removeListener(listener);
@@ -8670,6 +9118,7 @@ var WebSocketConnection = class {
8670
9118
  importAll,
8671
9119
  initializeNoyaSingleton,
8672
9120
  parseAppDataParameter,
9121
+ parseInspectorParameter,
8673
9122
  replaceDeep,
8674
9123
  resourceToCreateParameters,
8675
9124
  resourceToMediaItem,