@noya-app/noya-multiplayer-react 0.1.23 → 0.1.25
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 +10 -10
- package/CHANGELOG.md +17 -0
- package/dist/index.d.mts +18 -12
- package/dist/index.d.ts +18 -12
- package/dist/index.js +243 -115
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +203 -75
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/ErrorOverlay.tsx +89 -0
- package/src/hooks.ts +82 -16
- package/src/index.ts +1 -0
- package/src/inspector/StateInspector.tsx +2 -6
- package/src/inspector/useStateInspector.tsx +1 -1
- package/src/noyaApp.ts +3 -3
package/src/hooks.ts
CHANGED
|
@@ -6,16 +6,18 @@ import {
|
|
|
6
6
|
EphemeralUserData,
|
|
7
7
|
Get,
|
|
8
8
|
MultiplayerStateManager,
|
|
9
|
+
MultiplayerStateManagerError,
|
|
9
10
|
MultiplayerStateManagerOptions,
|
|
10
11
|
MultiplayerUser,
|
|
11
12
|
StateManager,
|
|
12
13
|
StateManagerOptions,
|
|
14
|
+
stubSync,
|
|
13
15
|
SyncAdapter,
|
|
14
16
|
SyncAdapterOptions,
|
|
15
17
|
Task,
|
|
16
|
-
stubSync,
|
|
17
18
|
} from "@noya-app/state-manager";
|
|
18
19
|
import {
|
|
20
|
+
createElement,
|
|
19
21
|
SetStateAction,
|
|
20
22
|
useCallback,
|
|
21
23
|
useEffect,
|
|
@@ -24,19 +26,19 @@ import {
|
|
|
24
26
|
useState,
|
|
25
27
|
useSyncExternalStore,
|
|
26
28
|
} from "react";
|
|
29
|
+
import { createRoot } from "react-dom/client";
|
|
30
|
+
import { ErrorOverlay } from "./components/ErrorOverlay";
|
|
27
31
|
import { shouldTrackEvents$, shouldTrackTasks$ } from "./globals";
|
|
28
32
|
import { StateInspectorOptions } from "./inspector/StateInspector";
|
|
29
33
|
import { useStateInspector } from "./inspector/useStateInspector";
|
|
30
34
|
import { useObservable } from "./useObservable";
|
|
31
35
|
|
|
32
|
-
export function useSyncStateManager<S
|
|
33
|
-
|
|
34
|
-
): S;
|
|
35
|
-
export function useSyncStateManager<S extends object, M, P extends string>(
|
|
36
|
+
export function useSyncStateManager<S, M>(stateManager: StateManager<S, M>): S;
|
|
37
|
+
export function useSyncStateManager<S, M, P extends string>(
|
|
36
38
|
stateManager: StateManager<S, M>,
|
|
37
39
|
path: P
|
|
38
40
|
): Get<S, P, unknown>;
|
|
39
|
-
export function useSyncStateManager<S
|
|
41
|
+
export function useSyncStateManager<S, M>(
|
|
40
42
|
stateManager: StateManager<S, M>,
|
|
41
43
|
path?: string
|
|
42
44
|
) {
|
|
@@ -55,7 +57,7 @@ export function useSyncStateManager<S extends object, M>(
|
|
|
55
57
|
);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
export function useManagedState<S
|
|
60
|
+
export function useManagedState<S, M = void>(
|
|
59
61
|
createInitialState: () => S,
|
|
60
62
|
options?: StateManagerOptions<S, M>
|
|
61
63
|
) {
|
|
@@ -79,7 +81,7 @@ export function useManagedState<S extends object, M = void>(
|
|
|
79
81
|
handler: ActionHandler<S>
|
|
80
82
|
) => void;
|
|
81
83
|
|
|
82
|
-
if (
|
|
84
|
+
if (action instanceof Function) {
|
|
83
85
|
performActionNoMetadata({
|
|
84
86
|
run: action,
|
|
85
87
|
undo: () => state,
|
|
@@ -96,7 +98,7 @@ export function useManagedState<S extends object, M = void>(
|
|
|
96
98
|
handler: ActionHandler<S>
|
|
97
99
|
) => void;
|
|
98
100
|
|
|
99
|
-
if (
|
|
101
|
+
if (action instanceof Function) {
|
|
100
102
|
performActionWithMetadata(metadata, {
|
|
101
103
|
run: action,
|
|
102
104
|
undo: () => state,
|
|
@@ -115,7 +117,7 @@ export function useManagedState<S extends object, M = void>(
|
|
|
115
117
|
return [state, setState, stateManager] as const;
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
export function useManagedHistory<S
|
|
120
|
+
export function useManagedHistory<S, M = void>(
|
|
119
121
|
stateManager: StateManager<S, M>
|
|
120
122
|
) {
|
|
121
123
|
return useSyncExternalStore(
|
|
@@ -125,7 +127,7 @@ export function useManagedHistory<S extends object, M = void>(
|
|
|
125
127
|
);
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
export function useSyncMultiplayerStateManager<S
|
|
130
|
+
export function useSyncMultiplayerStateManager<S, M = void>(
|
|
129
131
|
multiplayerStateManager: MultiplayerStateManager<S, M>
|
|
130
132
|
) {
|
|
131
133
|
return useSyncExternalStore(
|
|
@@ -135,11 +137,52 @@ export function useSyncMultiplayerStateManager<S extends object, M = void>(
|
|
|
135
137
|
);
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
function throttle<F extends (...args: any[]) => any>(fn: F, ms: number) {
|
|
141
|
+
let lastCall = 0;
|
|
142
|
+
let timeoutId: number | null = null;
|
|
143
|
+
return (...args: Parameters<F>) => {
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
if (now - lastCall < ms) {
|
|
146
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
147
|
+
timeoutId = setTimeout(
|
|
148
|
+
() => {
|
|
149
|
+
lastCall = now;
|
|
150
|
+
fn(...args);
|
|
151
|
+
},
|
|
152
|
+
ms - (now - lastCall)
|
|
153
|
+
) as unknown as number;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
lastCall = now;
|
|
157
|
+
return fn(...args);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
138
161
|
export function useEphemeralUserData<E>(
|
|
139
|
-
ephemeralUserData: EphemeralUserData<E
|
|
162
|
+
ephemeralUserData: EphemeralUserData<E>,
|
|
163
|
+
options?: {
|
|
164
|
+
throttle?: number;
|
|
165
|
+
}
|
|
140
166
|
) {
|
|
167
|
+
const throttledAddListener = useMemo(() => {
|
|
168
|
+
const throttleMs = options?.throttle ?? 0;
|
|
169
|
+
|
|
170
|
+
if (throttleMs > 0) {
|
|
171
|
+
const addListener = (
|
|
172
|
+
fn: Parameters<typeof ephemeralUserData.addListener>[0]
|
|
173
|
+
) => {
|
|
174
|
+
const throttledFn = throttle(fn, throttleMs);
|
|
175
|
+
return ephemeralUserData.addListener(throttledFn);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return addListener;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return ephemeralUserData.addListener;
|
|
182
|
+
}, [ephemeralUserData, options?.throttle]);
|
|
183
|
+
|
|
141
184
|
return useSyncExternalStore(
|
|
142
|
-
|
|
185
|
+
throttledAddListener,
|
|
143
186
|
ephemeralUserData.getSnapshot,
|
|
144
187
|
ephemeralUserData.getSnapshot
|
|
145
188
|
);
|
|
@@ -148,7 +191,7 @@ export function useEphemeralUserData<E>(
|
|
|
148
191
|
const defaultStubSync = stubSync();
|
|
149
192
|
|
|
150
193
|
export type UseMultiplayerStateOptions<
|
|
151
|
-
S
|
|
194
|
+
S,
|
|
152
195
|
M = void,
|
|
153
196
|
E = void,
|
|
154
197
|
> = MultiplayerStateManagerOptions<S, M> & {
|
|
@@ -160,7 +203,7 @@ export type UseMultiplayerStateOptions<
|
|
|
160
203
|
inspector?: boolean | StateInspectorOptions;
|
|
161
204
|
};
|
|
162
205
|
|
|
163
|
-
export function useMultiplayerState<S
|
|
206
|
+
export function useMultiplayerState<S, M = void, E = void>(
|
|
164
207
|
initialState: S | (() => S),
|
|
165
208
|
options?: UseMultiplayerStateOptions<S, M, E>
|
|
166
209
|
) {
|
|
@@ -271,12 +314,35 @@ export function useMultiplayerState<S extends object, M = void, E = void>(
|
|
|
271
314
|
|
|
272
315
|
const forceInit = useCallback(() => {
|
|
273
316
|
const state =
|
|
274
|
-
|
|
317
|
+
initialState instanceof Function ? initialState() : initialState;
|
|
275
318
|
const schema = options?.schema;
|
|
276
319
|
multiplayerStateManager.sm.clearHistory();
|
|
277
320
|
multiplayerStateManager.sendInit({ force: true, state, schema });
|
|
278
321
|
}, [initialState, multiplayerStateManager, options?.schema]);
|
|
279
322
|
|
|
323
|
+
const [unrecoverableError, setUnrecoverableError] = useState<
|
|
324
|
+
MultiplayerStateManagerError | undefined
|
|
325
|
+
>();
|
|
326
|
+
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
return multiplayerStateManager.errorEmitter.addListener((error) => {
|
|
329
|
+
setUnrecoverableError(error);
|
|
330
|
+
});
|
|
331
|
+
}, [multiplayerStateManager]);
|
|
332
|
+
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
if (!unrecoverableError) return;
|
|
335
|
+
const el = document.createElement("div");
|
|
336
|
+
el.id = "noya-multiplayer-react-error-overlay";
|
|
337
|
+
document.body.appendChild(el);
|
|
338
|
+
const root = createRoot(el);
|
|
339
|
+
root.render(createElement(ErrorOverlay, { error: unrecoverableError }));
|
|
340
|
+
return () => {
|
|
341
|
+
root.unmount();
|
|
342
|
+
el.remove();
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
|
|
280
346
|
useStateInspector({
|
|
281
347
|
ephemeralUserData,
|
|
282
348
|
multiplayerStateManager,
|
package/src/index.ts
CHANGED
|
@@ -249,11 +249,7 @@ export type StateInspectorOptions = {
|
|
|
249
249
|
anchor?: StateInspectorAnchor;
|
|
250
250
|
};
|
|
251
251
|
|
|
252
|
-
export const StateInspector = memo(function StateInspector<
|
|
253
|
-
S extends object,
|
|
254
|
-
M,
|
|
255
|
-
E,
|
|
256
|
-
>({
|
|
252
|
+
export const StateInspector = memo(function StateInspector<S, M, E>({
|
|
257
253
|
state,
|
|
258
254
|
connectionEvents,
|
|
259
255
|
connectedUsers,
|
|
@@ -757,7 +753,7 @@ export const StateInspector = memo(function StateInspector<
|
|
|
757
753
|
}}
|
|
758
754
|
>
|
|
759
755
|
<ObjectInspector
|
|
760
|
-
data={event.message}
|
|
756
|
+
data={event.type === "error" ? event.error : event.message}
|
|
761
757
|
theme={theme}
|
|
762
758
|
nodeRenderer={({
|
|
763
759
|
depth,
|
package/src/noyaApp.ts
CHANGED
|
@@ -63,13 +63,13 @@ type AppDataOptions = {
|
|
|
63
63
|
/**
|
|
64
64
|
* Get data from host app. Fall back to default data.
|
|
65
65
|
*/
|
|
66
|
-
export function useAppData<S
|
|
66
|
+
export function useAppData<S>(
|
|
67
67
|
initialState: S,
|
|
68
68
|
{ schema }: AppDataOptions = {}
|
|
69
69
|
) {
|
|
70
70
|
const appData = useMemo(() => {
|
|
71
71
|
return getAppData(
|
|
72
|
-
schema ? createValue(schema, initialState) : initialState
|
|
72
|
+
schema ? (createValue(schema, initialState) as S) : initialState
|
|
73
73
|
);
|
|
74
74
|
}, [initialState, schema]);
|
|
75
75
|
|
|
@@ -142,7 +142,7 @@ export function isEmbeddedApp() {
|
|
|
142
142
|
return window.self !== window.top;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
export function useNoyaAppState<S
|
|
145
|
+
export function useNoyaAppState<S, M = void, E = void>(
|
|
146
146
|
initialState: S | (() => S),
|
|
147
147
|
options: UseMultiplayerStateOptions<S, M, E> & {
|
|
148
148
|
localStorageKey?: string;
|