@hypen-space/core 0.4.25 → 0.4.27
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.browser.js +21 -1
- package/dist/index.browser.js.map +3 -3
- package/dist/index.js +21 -1
- package/dist/index.js.map +3 -3
- package/dist/remote/client.d.ts +10 -0
- package/dist/remote/client.js +21 -1
- package/dist/remote/client.js.map +3 -3
- package/dist/remote/index.js +75 -5
- package/dist/remote/index.js.map +4 -4
- package/dist/remote/server.d.ts +7 -0
- package/dist/remote/server.js +55 -5
- package/dist/remote/server.js.map +3 -3
- package/dist/remote/types.d.ts +18 -1
- package/package.json +2 -1
- package/src/remote/client.ts +35 -0
- package/src/remote/server.ts +69 -3
- package/src/remote/types.ts +21 -0
- package/wasm-browser/hypen_engine_bg.wasm +0 -0
- package/wasm-browser/package.json +1 -1
- package/wasm-node/hypen_engine_bg.wasm +0 -0
- package/wasm-node/package.json +1 -1
package/src/remote/server.ts
CHANGED
|
@@ -37,6 +37,8 @@ import type {
|
|
|
37
37
|
RemoteServerConfig,
|
|
38
38
|
InitialTreeMessage,
|
|
39
39
|
PatchMessage,
|
|
40
|
+
StateUpdateMessage,
|
|
41
|
+
UpdateStateMessage,
|
|
40
42
|
DispatchActionMessage,
|
|
41
43
|
HelloMessage,
|
|
42
44
|
SessionAckMessage,
|
|
@@ -68,6 +70,8 @@ interface ClientData {
|
|
|
68
70
|
helloReceived: boolean;
|
|
69
71
|
/** Timeout for legacy clients that don't send hello */
|
|
70
72
|
helloTimeout?: ReturnType<typeof setTimeout>;
|
|
73
|
+
/** Whether this client subscribes to state updates after each render */
|
|
74
|
+
stateSubscribed: boolean;
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
/**
|
|
@@ -87,6 +91,7 @@ export class RemoteServer {
|
|
|
87
91
|
private nextClientId = 1;
|
|
88
92
|
private server: ReturnType<typeof Bun.serve> | null = null;
|
|
89
93
|
private sessionManager: SessionManager | null = null;
|
|
94
|
+
private _syncActions: boolean = false;
|
|
90
95
|
|
|
91
96
|
/**
|
|
92
97
|
* Set the module for this app
|
|
@@ -141,6 +146,16 @@ export class RemoteServer {
|
|
|
141
146
|
return this;
|
|
142
147
|
}
|
|
143
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Enable action synchronization across all connected clients.
|
|
151
|
+
* When enabled, an action dispatched by any client is also dispatched
|
|
152
|
+
* to every other client's engine, keeping all clients in sync.
|
|
153
|
+
*/
|
|
154
|
+
syncActions(): this {
|
|
155
|
+
this._syncActions = true;
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
144
159
|
/**
|
|
145
160
|
* Register connection callback
|
|
146
161
|
*/
|
|
@@ -330,6 +345,7 @@ export class RemoteServer {
|
|
|
330
345
|
connectedAt,
|
|
331
346
|
sessionId: "",
|
|
332
347
|
helloReceived: false,
|
|
348
|
+
stateSubscribed: false,
|
|
333
349
|
};
|
|
334
350
|
|
|
335
351
|
this.clients.set(ws, clientData);
|
|
@@ -486,14 +502,25 @@ export class RemoteServer {
|
|
|
486
502
|
|
|
487
503
|
data.revision++;
|
|
488
504
|
|
|
489
|
-
const
|
|
505
|
+
const patchMessage: PatchMessage = {
|
|
490
506
|
type: "patch",
|
|
491
507
|
module: this._moduleName,
|
|
492
508
|
patches,
|
|
493
509
|
revision: data.revision,
|
|
494
510
|
};
|
|
495
511
|
|
|
496
|
-
ws.send(JSON.stringify(
|
|
512
|
+
ws.send(JSON.stringify(patchMessage));
|
|
513
|
+
|
|
514
|
+
// Only send state snapshot to clients that opted in (e.g. Studio)
|
|
515
|
+
if (data.stateSubscribed) {
|
|
516
|
+
const stateMessage: StateUpdateMessage = {
|
|
517
|
+
type: "stateUpdate",
|
|
518
|
+
module: this._moduleName,
|
|
519
|
+
state: data.moduleInstance.getState(),
|
|
520
|
+
revision: data.revision,
|
|
521
|
+
};
|
|
522
|
+
ws.send(JSON.stringify(stateMessage));
|
|
523
|
+
}
|
|
497
524
|
|
|
498
525
|
// If allow-multiple, broadcast to other connections on same session
|
|
499
526
|
if (
|
|
@@ -504,7 +531,18 @@ export class RemoteServer {
|
|
|
504
531
|
if (connections) {
|
|
505
532
|
for (const conn of connections) {
|
|
506
533
|
if (conn !== ws) {
|
|
507
|
-
|
|
534
|
+
const otherWs = conn as ServerWebSocket<unknown>;
|
|
535
|
+
const otherData = this.clients.get(otherWs);
|
|
536
|
+
otherWs.send(JSON.stringify(patchMessage));
|
|
537
|
+
if (otherData?.stateSubscribed) {
|
|
538
|
+
const stateMessage: StateUpdateMessage = {
|
|
539
|
+
type: "stateUpdate",
|
|
540
|
+
module: this._moduleName,
|
|
541
|
+
state: data.moduleInstance.getState(),
|
|
542
|
+
revision: data.revision,
|
|
543
|
+
};
|
|
544
|
+
otherWs.send(JSON.stringify(stateMessage));
|
|
545
|
+
}
|
|
508
546
|
}
|
|
509
547
|
}
|
|
510
548
|
}
|
|
@@ -615,6 +653,34 @@ export class RemoteServer {
|
|
|
615
653
|
case "dispatchAction": {
|
|
616
654
|
const actionMsg = msg as DispatchActionMessage;
|
|
617
655
|
clientData.engine.dispatchAction(actionMsg.action, actionMsg.payload);
|
|
656
|
+
|
|
657
|
+
// Sync mode: dispatch to all other clients' engines
|
|
658
|
+
if (this._syncActions) {
|
|
659
|
+
for (const [otherWs, otherClient] of this.clients) {
|
|
660
|
+
if (otherWs === ws || !otherClient.helloReceived) continue;
|
|
661
|
+
otherClient.engine.dispatchAction(actionMsg.action, actionMsg.payload);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
case "updateState": {
|
|
668
|
+
const stateMsg = msg as UpdateStateMessage;
|
|
669
|
+
clientData.moduleInstance.updateState(stateMsg.state);
|
|
670
|
+
|
|
671
|
+
// Sync mode: update state on all other clients' engines
|
|
672
|
+
if (this._syncActions) {
|
|
673
|
+
for (const [otherWs, otherClient] of this.clients) {
|
|
674
|
+
if (otherWs === ws || !otherClient.helloReceived) continue;
|
|
675
|
+
otherClient.moduleInstance.updateState(stateMsg.state);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
case "subscribeState": {
|
|
682
|
+
clientData.stateSubscribed = true;
|
|
683
|
+
log.info(`Client ${clientData.id} subscribed to state updates`);
|
|
618
684
|
break;
|
|
619
685
|
}
|
|
620
686
|
|
package/src/remote/types.ts
CHANGED
|
@@ -8,6 +8,8 @@ export type RemoteMessage =
|
|
|
8
8
|
| InitialTreeMessage
|
|
9
9
|
| PatchMessage
|
|
10
10
|
| StateUpdateMessage
|
|
11
|
+
| UpdateStateMessage
|
|
12
|
+
| SubscribeStateMessage
|
|
11
13
|
| DispatchActionMessage
|
|
12
14
|
| HelloMessage
|
|
13
15
|
| SessionAckMessage
|
|
@@ -42,6 +44,25 @@ export interface DispatchActionMessage {
|
|
|
42
44
|
payload?: any;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Client → Server: Override the module's state (e.g. for time-travel)
|
|
49
|
+
* Server will update the module instance and re-render, sending back
|
|
50
|
+
* patch + stateUpdate messages.
|
|
51
|
+
*/
|
|
52
|
+
export interface UpdateStateMessage {
|
|
53
|
+
type: "updateState";
|
|
54
|
+
module: string;
|
|
55
|
+
state: any;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Client → Server: Opt-in to receiving stateUpdate messages after each render.
|
|
60
|
+
* Only subscribed clients receive state snapshots (e.g. Studio for time-travel).
|
|
61
|
+
*/
|
|
62
|
+
export interface SubscribeStateMessage {
|
|
63
|
+
type: "subscribeState";
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
/**
|
|
46
67
|
* Client → Server: First message after WebSocket opens
|
|
47
68
|
* Used to establish or resume a session
|
|
Binary file
|
|
Binary file
|