@couch-kit/host 1.7.17 → 2.0.1
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/README.md +12 -2
- package/lib/server.d.ts.map +1 -1
- package/lib/websocket.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/server.ts +27 -7
- package/src/websocket.ts +2 -6
package/README.md
CHANGED
|
@@ -216,12 +216,19 @@ Records game actions with timestamps for session replay and debugging. Useful fo
|
|
|
216
216
|
|
|
217
217
|
```tsx
|
|
218
218
|
import { useActionRecorder, useGameHost } from "@couch-kit/host";
|
|
219
|
+
import type { GameAction } from "./shared/gameLogic";
|
|
219
220
|
|
|
220
221
|
function GameScreen() {
|
|
221
|
-
const { state } = useGameHost();
|
|
222
|
+
const { state, dispatch } = useGameHost();
|
|
222
223
|
const { isRecording, recordedCount, startRecording, stopRecording, recordAction } =
|
|
223
224
|
useActionRecorder();
|
|
224
225
|
|
|
226
|
+
// Recording is not automatic: call recordAction for each action you dispatch.
|
|
227
|
+
const dispatchAndRecord = (action: GameAction) => {
|
|
228
|
+
dispatch(action);
|
|
229
|
+
recordAction(action);
|
|
230
|
+
};
|
|
231
|
+
|
|
225
232
|
const handleStartRecording = () => startRecording(state);
|
|
226
233
|
const handleStop = () => {
|
|
227
234
|
const recording = stopRecording();
|
|
@@ -233,6 +240,7 @@ function GameScreen() {
|
|
|
233
240
|
<View>
|
|
234
241
|
<Text>Actions recorded: {recordedCount}</Text>
|
|
235
242
|
<Button title={isRecording ? "Stop" : "Record"} onPress={isRecording ? handleStop : handleStartRecording} />
|
|
243
|
+
<Button title="Reset" onPress={() => dispatchAndRecord({ type: "RESET" })} />
|
|
236
244
|
</View>
|
|
237
245
|
);
|
|
238
246
|
}
|
|
@@ -244,7 +252,9 @@ Returns:
|
|
|
244
252
|
- `recordedCount`: number of actions captured so far
|
|
245
253
|
- `startRecording(currentState, metadata?)`: begin recording from the given state
|
|
246
254
|
- `stopRecording()`: end recording and return the `ActionRecording` object (or `null`)
|
|
247
|
-
- `recordAction(action
|
|
255
|
+
- `recordAction(action)`: capture a single action with a timestamp. Nothing is recorded automatically — call it alongside `dispatch`. Resulting states are not stored; replay recomputes them from `initialState` and the reducer. No-op when no recording is active.
|
|
256
|
+
- `exportRecording()`: the current recording as a JSON string (or `null`)
|
|
257
|
+
- `discardRecording()`: drop the current recording and reset the count
|
|
248
258
|
|
|
249
259
|
### `getBestIpAddress()`
|
|
250
260
|
|
package/lib/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,WAAY,kBAAkB;;;;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,WAAY,kBAAkB;;;;CA6FzD,CAAC"}
|
package/lib/websocket.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO/C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8CAA8C;AAC9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAC;AAOF,qBAAa,mBAAoB,SAAQ,YAAY,CAAC,qBAAqB,CAAC;IAC1E,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,eAAe,CAAS;IAEhC,YAAY,MAAM,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO/C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8CAA8C;AAC9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAC;AAOF,qBAAa,mBAAoB,SAAQ,YAAY,CAAC,qBAAqB,CAAC;IAC1E,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,eAAe,CAAS;IAEhC,YAAY,MAAM,EAAE,eAAe,EASlC;IAED,OAAO,CAAC,GAAG;IAME,KAAK,kBAkGjB;IAEY,IAAI,kBAkBhB;IAED;;;OAGG;IACI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,QAoB1C;IAED;;;OAGG;IACI,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAkBjD;IAED,yDAAyD;IACzD,IAAW,WAAW,IAAI,MAAM,CAE/B;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@couch-kit/host",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": true
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@couch-kit/core": "0.10.0",
|
|
61
|
-
"@couch-kit/runtime": "0.
|
|
62
|
-
"react-native-nitro-http-server": "
|
|
61
|
+
"@couch-kit/runtime": "0.4.0",
|
|
62
|
+
"react-native-nitro-http-server": "^1.9.2"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@happy-dom/global-registrator": "^20.10.6",
|
|
@@ -69,13 +69,14 @@
|
|
|
69
69
|
"react": "19.3.0",
|
|
70
70
|
"react-dom": "19.3.0",
|
|
71
71
|
"react-native": "0.87.1",
|
|
72
|
+
"react-native-nitro-modules": "0.37.1",
|
|
72
73
|
"del-cli": "^7.0.0",
|
|
73
74
|
"jest": "^30.0.0"
|
|
74
75
|
},
|
|
75
76
|
"peerDependencies": {
|
|
76
77
|
"react": ">=18.2.0",
|
|
77
78
|
"react-native": ">=0.72.0",
|
|
78
|
-
"react-native-nitro-modules": ">=0.
|
|
79
|
+
"react-native-nitro-modules": ">=0.35.0",
|
|
79
80
|
"expo": ">=51.0.0",
|
|
80
81
|
"expo-file-system": ">=19.0.0",
|
|
81
82
|
"expo-network": ">=7.0.0"
|
package/src/server.ts
CHANGED
|
@@ -29,13 +29,26 @@ export const useStaticServer = (config: CouchKitHostConfig) => {
|
|
|
29
29
|
|
|
30
30
|
useEffect(() => {
|
|
31
31
|
let server: StaticServer | null = null;
|
|
32
|
+
// Set by cleanup. Starting is asynchronous, so an effect that was torn down
|
|
33
|
+
// mid-start (a config change, StrictMode's double mount, an unmount) must
|
|
34
|
+
// neither publish its result nor leave its server holding the port.
|
|
35
|
+
let cancelled = false;
|
|
32
36
|
setLoading(true);
|
|
37
|
+
setError(null);
|
|
38
|
+
|
|
39
|
+
const stop = (target: StaticServer | null) => {
|
|
40
|
+
if (!target) return;
|
|
41
|
+
void Promise.resolve(target.stop()).catch(() => {
|
|
42
|
+
// Nothing useful to do if a server we no longer want fails to stop.
|
|
43
|
+
});
|
|
44
|
+
};
|
|
33
45
|
|
|
34
46
|
const startServer = async () => {
|
|
35
47
|
// In Dev Mode, we don't start the static server.
|
|
36
48
|
// We just resolve the IP so the host knows where it is.
|
|
37
49
|
if (config.devMode && config.devServerUrl) {
|
|
38
50
|
const ip = await getBestIpAddress();
|
|
51
|
+
if (cancelled) return;
|
|
39
52
|
if (ip) {
|
|
40
53
|
// In dev mode, the URL is the laptop's dev server,
|
|
41
54
|
// but we might need the TV's IP for the WebSocket connection later.
|
|
@@ -65,13 +78,21 @@ export const useStaticServer = (config: CouchKitHostConfig) => {
|
|
|
65
78
|
}
|
|
66
79
|
const port = config.port || DEFAULT_HTTP_PORT;
|
|
67
80
|
|
|
68
|
-
|
|
81
|
+
const started = new StaticServer();
|
|
82
|
+
server = started;
|
|
69
83
|
|
|
70
84
|
// Use '0.0.0.0' to bind to all interfaces (local network)
|
|
71
|
-
await
|
|
85
|
+
await started.start(port, path, "0.0.0.0");
|
|
86
|
+
if (cancelled) {
|
|
87
|
+
// Cleanup ran while the server was still starting, when a stop may
|
|
88
|
+
// not have taken; stop it again now that it is actually up.
|
|
89
|
+
stop(started);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
72
92
|
|
|
73
93
|
// We prefer the actual IP over "localhost" returned by some libs
|
|
74
94
|
const ip = await getBestIpAddress();
|
|
95
|
+
if (cancelled) return;
|
|
75
96
|
if (ip) {
|
|
76
97
|
setUrl(`http://${ip}:${port}/index.html`);
|
|
77
98
|
} else {
|
|
@@ -79,18 +100,17 @@ export const useStaticServer = (config: CouchKitHostConfig) => {
|
|
|
79
100
|
setUrl(`http://localhost:${port}/index.html`);
|
|
80
101
|
}
|
|
81
102
|
} catch (e) {
|
|
82
|
-
setError(new Error(toErrorMessage(e)));
|
|
103
|
+
if (!cancelled) setError(new Error(toErrorMessage(e)));
|
|
83
104
|
} finally {
|
|
84
|
-
setLoading(false);
|
|
105
|
+
if (!cancelled) setLoading(false);
|
|
85
106
|
}
|
|
86
107
|
};
|
|
87
108
|
|
|
88
109
|
startServer();
|
|
89
110
|
|
|
90
111
|
return () => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
112
|
+
cancelled = true;
|
|
113
|
+
stop(server);
|
|
94
114
|
};
|
|
95
115
|
}, [config.port, config.devMode, config.devServerUrl, config.staticDir]);
|
|
96
116
|
|
package/src/websocket.ts
CHANGED
|
@@ -73,8 +73,7 @@ export class GameWebSocketServer extends EventEmitter<WebSocketServerEvents> {
|
|
|
73
73
|
super();
|
|
74
74
|
this.port = config.port;
|
|
75
75
|
this.debug = !!config.debug;
|
|
76
|
-
this.maxMessageBytes =
|
|
77
|
-
config.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
|
|
76
|
+
this.maxMessageBytes = config.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
|
|
78
77
|
// Only `port`, `debug`, and `maxMessageBytes` are honored. `maxFrameSize`,
|
|
79
78
|
// `keepaliveInterval`, and `keepaliveTimeout` are deprecated no-ops: the
|
|
80
79
|
// nitro-http WebSocket transport does not expose these knobs, so they are
|
|
@@ -150,10 +149,7 @@ export class GameWebSocketServer extends EventEmitter<WebSocketServerEvents> {
|
|
|
150
149
|
// Handle errors
|
|
151
150
|
ws.onerror = (event: { message: string }) => {
|
|
152
151
|
this.log(`[WebSocket] Error on ${socketId}:`, event.message);
|
|
153
|
-
this.emit(
|
|
154
|
-
"error",
|
|
155
|
-
new Error(`WebSocket error: ${event.message}`),
|
|
156
|
-
);
|
|
152
|
+
this.emit("error", new Error(`WebSocket error: ${event.message}`));
|
|
157
153
|
};
|
|
158
154
|
},
|
|
159
155
|
);
|