@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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +53 -0
- package/dist/index.bundle.js +34 -30
- package/dist/index.d.mts +187 -18
- package/dist/index.d.ts +187 -18
- package/dist/index.js +496 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +499 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/WebSocketConnection.ts +556 -20
- package/src/__tests__/WebSocketConnection.test.ts +1180 -0
- package/src/__tests__/noyaApp.test.ts +28 -0
- package/src/noyaApp.ts +15 -0
- package/src/useResourceUrl.ts +4 -3
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.
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
Type.
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
Type.
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
Type.
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
Type.
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
Type.
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
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
|
|
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,
|