@noya-app/noya-multiplayer-react 0.1.10 → 0.1.12

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.
@@ -5,14 +5,14 @@
5
5
  CLI Target: esnext
6
6
  CJS Build start
7
7
  ESM Build start
8
- ESM dist/index.mjs 21.90 KB
9
- ESM dist/index.mjs.map 40.92 KB
10
- ESM ⚡️ Build success in 16ms
11
- CJS dist/index.js 25.37 KB
12
- CJS dist/index.js.map 40.72 KB
13
- CJS ⚡️ Build success in 16ms
8
+ ESM dist/index.mjs 23.51 KB
9
+ ESM dist/index.mjs.map 43.29 KB
10
+ ESM ⚡️ Build success in 19ms
11
+ CJS dist/index.js 27.13 KB
12
+ CJS dist/index.js.map 43.09 KB
13
+ CJS ⚡️ Build success in 19ms
14
14
  DTS Build start
15
15
  "ComponentPropsWithoutRef" is imported from external module "react" but never used in "src/inspector/StateInspector.tsx" and "src/hooks.ts".
16
- DTS ⚡️ Build success in 1580ms
17
- DTS dist/index.d.ts 2.25 KB
18
- DTS dist/index.d.mts 2.25 KB
16
+ DTS ⚡️ Build success in 1607ms
17
+ DTS dist/index.d.ts 2.50 KB
18
+ DTS dist/index.d.mts 2.50 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @noya-app/state-manager
2
2
 
3
+ ## 0.1.12
4
+
5
+ ### Patch Changes
6
+
7
+ - b06dd3a: Add user auth to ws sync
8
+ - Updated dependencies [b06dd3a]
9
+ - @noya-app/state-manager@0.1.12
10
+
11
+ ## 0.1.11
12
+
13
+ ### Patch Changes
14
+
15
+ - 843913b: Improve metadata and reconnection
16
+ - Updated dependencies [843913b]
17
+ - @noya-app/state-manager@0.1.11
18
+
3
19
  ## 0.1.10
4
20
 
5
21
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,12 +1,14 @@
1
1
  import * as _noya_app_state_manager from '@noya-app/state-manager';
2
- import { ConnectionEvent, StateManager, Get, StateManagerOptions, MultiplayerStateManager, MultiplayerPatchMetadata } from '@noya-app/state-manager';
2
+ import { ConnectionEvent, MultiplayerUser, StateManager, Get, StateManagerOptions, MultiplayerStateManager, MultiplayerStateManagerOptions } from '@noya-app/state-manager';
3
3
  export * from '@noya-app/state-manager';
4
4
  import React, { SetStateAction, ComponentProps } from 'react';
5
5
 
6
6
  type Anchor = "left" | "right";
7
- declare const StateInspector: React.MemoExoticComponent<(<S extends object, M>({ state, connectionEvents, unstyled, colorScheme, stateManager, anchor, ...props }: {
7
+ declare const StateInspector: React.MemoExoticComponent<(<S extends object, M>({ state, connectionEvents, connectedUsers, userId, unstyled, colorScheme, stateManager, anchor, ...props }: {
8
8
  state: S;
9
9
  connectionEvents?: ConnectionEvent<S>[] | undefined;
10
+ connectedUsers?: MultiplayerUser[] | undefined;
11
+ userId?: string | undefined;
10
12
  unstyled?: boolean | undefined;
11
13
  colorScheme?: "light" | "dark" | undefined;
12
14
  stateManager: StateManager<S, M>;
@@ -17,13 +19,16 @@ declare function useSyncStateManager<S extends object, M>(stateManager: StateMan
17
19
  declare function useSyncStateManager<S extends object, M, P extends string>(stateManager: StateManager<S, M>, path: P): Get<S, P, unknown>;
18
20
  declare function useManagedState<S extends object, M = void>(createInitialState: () => S, options?: StateManagerOptions<S, M>): readonly [S, (...args: M extends {} ? [metadata: M, action: SetStateAction<S>] : [action: SetStateAction<S>]) => void, StateManager<S, M>];
19
21
  declare function useManagedHistory<S extends object, M = void>(stateManager: StateManager<S, M>): _noya_app_state_manager.HistorySnapshot<S, M>;
20
- declare function useSyncMultiplayerStateManager<S extends object>(multiplayerStateManager: MultiplayerStateManager<S>): S;
21
- declare function useMultiplayerState<S extends object>(initialState: S | (() => S), options: StateManagerOptions<S, MultiplayerPatchMetadata> & {
22
- sync?: (self: MultiplayerStateManager<S>) => void;
22
+ declare function useSyncMultiplayerStateManager<S extends object, M = void>(multiplayerStateManager: MultiplayerStateManager<S, M>): S;
23
+ declare function useMultiplayerState<S extends object, M = void>(initialState: S | (() => S), options: MultiplayerStateManagerOptions<S, M> & {
24
+ sync?: (options: {
25
+ ms: MultiplayerStateManager<S, M>;
26
+ }) => void;
23
27
  trackConnectionEvents?: boolean;
24
28
  inspector?: boolean | Pick<ComponentProps<typeof StateInspector>, "colorScheme" | "anchor">;
25
- }): readonly [S, (metadata: MultiplayerPatchMetadata, action: S | ((prevState: S) => S)) => void, MultiplayerStateManager<S>, {
29
+ }): readonly [S, (...args: M extends {} ? [metadata: M, action: S | ((prevState: S) => S)] : [action: S | ((prevState: S) => S)]) => void, MultiplayerStateManager<S, M>, {
26
30
  connectionEvents: ConnectionEvent<S>[] | undefined;
31
+ connectedUsers: MultiplayerUser[];
27
32
  }];
28
33
 
29
34
  export { StateInspector, useManagedHistory, useManagedState, useMultiplayerState, useSyncMultiplayerStateManager, useSyncStateManager };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import * as _noya_app_state_manager from '@noya-app/state-manager';
2
- import { ConnectionEvent, StateManager, Get, StateManagerOptions, MultiplayerStateManager, MultiplayerPatchMetadata } from '@noya-app/state-manager';
2
+ import { ConnectionEvent, MultiplayerUser, StateManager, Get, StateManagerOptions, MultiplayerStateManager, MultiplayerStateManagerOptions } from '@noya-app/state-manager';
3
3
  export * from '@noya-app/state-manager';
4
4
  import React, { SetStateAction, ComponentProps } from 'react';
5
5
 
6
6
  type Anchor = "left" | "right";
7
- declare const StateInspector: React.MemoExoticComponent<(<S extends object, M>({ state, connectionEvents, unstyled, colorScheme, stateManager, anchor, ...props }: {
7
+ declare const StateInspector: React.MemoExoticComponent<(<S extends object, M>({ state, connectionEvents, connectedUsers, userId, unstyled, colorScheme, stateManager, anchor, ...props }: {
8
8
  state: S;
9
9
  connectionEvents?: ConnectionEvent<S>[] | undefined;
10
+ connectedUsers?: MultiplayerUser[] | undefined;
11
+ userId?: string | undefined;
10
12
  unstyled?: boolean | undefined;
11
13
  colorScheme?: "light" | "dark" | undefined;
12
14
  stateManager: StateManager<S, M>;
@@ -17,13 +19,16 @@ declare function useSyncStateManager<S extends object, M>(stateManager: StateMan
17
19
  declare function useSyncStateManager<S extends object, M, P extends string>(stateManager: StateManager<S, M>, path: P): Get<S, P, unknown>;
18
20
  declare function useManagedState<S extends object, M = void>(createInitialState: () => S, options?: StateManagerOptions<S, M>): readonly [S, (...args: M extends {} ? [metadata: M, action: SetStateAction<S>] : [action: SetStateAction<S>]) => void, StateManager<S, M>];
19
21
  declare function useManagedHistory<S extends object, M = void>(stateManager: StateManager<S, M>): _noya_app_state_manager.HistorySnapshot<S, M>;
20
- declare function useSyncMultiplayerStateManager<S extends object>(multiplayerStateManager: MultiplayerStateManager<S>): S;
21
- declare function useMultiplayerState<S extends object>(initialState: S | (() => S), options: StateManagerOptions<S, MultiplayerPatchMetadata> & {
22
- sync?: (self: MultiplayerStateManager<S>) => void;
22
+ declare function useSyncMultiplayerStateManager<S extends object, M = void>(multiplayerStateManager: MultiplayerStateManager<S, M>): S;
23
+ declare function useMultiplayerState<S extends object, M = void>(initialState: S | (() => S), options: MultiplayerStateManagerOptions<S, M> & {
24
+ sync?: (options: {
25
+ ms: MultiplayerStateManager<S, M>;
26
+ }) => void;
23
27
  trackConnectionEvents?: boolean;
24
28
  inspector?: boolean | Pick<ComponentProps<typeof StateInspector>, "colorScheme" | "anchor">;
25
- }): readonly [S, (metadata: MultiplayerPatchMetadata, action: S | ((prevState: S) => S)) => void, MultiplayerStateManager<S>, {
29
+ }): readonly [S, (...args: M extends {} ? [metadata: M, action: S | ((prevState: S) => S)] : [action: S | ((prevState: S) => S)]) => void, MultiplayerStateManager<S, M>, {
26
30
  connectionEvents: ConnectionEvent<S>[] | undefined;
31
+ connectedUsers: MultiplayerUser[];
27
32
  }];
28
33
 
29
34
  export { StateInspector, useManagedHistory, useManagedState, useMultiplayerState, useSyncMultiplayerStateManager, useSyncStateManager };
package/dist/index.js CHANGED
@@ -119,6 +119,15 @@ var darkTheme = {
119
119
  BASE_BACKGROUND_COLOR: "transparent",
120
120
  OBJECT_NAME_COLOR: "rgba(255,255,255,0.7)"
121
121
  };
122
+ var styles = {
123
+ sectionInner: {
124
+ flex: "1 1 0",
125
+ overflowY: "auto",
126
+ overflowX: "hidden",
127
+ display: "flex",
128
+ flexDirection: "column"
129
+ }
130
+ };
122
131
  function ToggleButton({
123
132
  showInspector,
124
133
  setShowInspector,
@@ -141,7 +150,10 @@ function ToggleButton({
141
150
  justifyContent: "center",
142
151
  padding: "2px 0"
143
152
  },
144
- onClick: () => setShowInspector(!showInspector)
153
+ onClick: (event) => {
154
+ event.stopPropagation();
155
+ setShowInspector(!showInspector);
156
+ }
145
157
  },
146
158
  /* @__PURE__ */ import_react3.default.createElement("span", { style: { width: "12px", height: "12px" } }, showInspector === (anchor === "right") ? /* @__PURE__ */ import_react3.default.createElement(
147
159
  "svg",
@@ -189,7 +201,8 @@ function DisclosureSection({
189
201
  right,
190
202
  children,
191
203
  colorScheme,
192
- isFirst
204
+ isFirst,
205
+ style
193
206
  }) {
194
207
  const theme = colorScheme === "light" ? lightTheme : darkTheme;
195
208
  const borderColor = colorScheme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)";
@@ -199,7 +212,8 @@ function DisclosureSection({
199
212
  style: {
200
213
  flex: open ? "1 1 0" : "0",
201
214
  display: "flex",
202
- flexDirection: "column"
215
+ flexDirection: "column",
216
+ ...style
203
217
  }
204
218
  },
205
219
  /* @__PURE__ */ import_react3.default.createElement(
@@ -233,10 +247,52 @@ function DisclosureSection({
233
247
  open && children
234
248
  );
235
249
  }
250
+ function InspectorRow({
251
+ children,
252
+ colorScheme,
253
+ selected,
254
+ style,
255
+ variant
256
+ }) {
257
+ const solidBorderColor = colorScheme === "light" ? "rgb(223 223 223)" : "rgb(29 29 29)";
258
+ return /* @__PURE__ */ import_react3.default.createElement(
259
+ "div",
260
+ {
261
+ style: {
262
+ borderBottom: `1px solid ${solidBorderColor}`,
263
+ fontSize: "12px",
264
+ fontFamily: "Menlo, monospace",
265
+ padding: "2px 12px 1px",
266
+ display: "flex",
267
+ alignItems: "center",
268
+ background: variant === "up" ? "rgba(0,255,0,0.1)" : variant === "down" ? "rgba(255,0,0,0.1)" : selected ? "rgb(59 130 246 / 15%)" : void 0,
269
+ // background:
270
+ // colorScheme === "light"
271
+ // ? "rgba(0,0,0,0.05)"
272
+ // : "rgba(255,255,255,0.05)",
273
+ ...style
274
+ }
275
+ },
276
+ /* @__PURE__ */ import_react3.default.createElement(
277
+ "span",
278
+ {
279
+ style: {
280
+ fontFamily: "Menlo, monospace",
281
+ fontSize: "11px",
282
+ borderRadius: 4,
283
+ display: "inline-block"
284
+ }
285
+ },
286
+ children
287
+ )
288
+ );
289
+ }
236
290
  var HISTORY_ELEMENT_PREFIX = "noya-multiplayer-history-";
237
291
  var StateInspector = (0, import_react3.memo)(function StateInspector2({
238
292
  state,
239
293
  connectionEvents,
294
+ connectedUsers,
295
+ userId,
240
296
  unstyled,
241
297
  colorScheme = "light",
242
298
  stateManager,
@@ -261,6 +317,14 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
261
317
  "noya-multiplayer-react-show-history",
262
318
  false
263
319
  );
320
+ const [showUsers, setShowUsers] = useLocalStorageState(
321
+ "noya-multiplayer-react-show-users",
322
+ true
323
+ );
324
+ const [showData, setShowData] = useLocalStorageState(
325
+ "noya-multiplayer-react-show-data",
326
+ true
327
+ );
264
328
  (0, import_react3.useEffect)(() => {
265
329
  trackEvents.set(showEvents);
266
330
  }, [showEvents]);
@@ -279,7 +343,7 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
279
343
  if (!historyContainerRef.current)
280
344
  return;
281
345
  const historyEntry = historyContainerRef.current.querySelector(
282
- `#${HISTORY_ELEMENT_PREFIX}${historySnapshot.historyIndex - 1}`
346
+ `#${HISTORY_ELEMENT_PREFIX}${historySnapshot.historyIndex}`
283
347
  );
284
348
  if (historyEntry) {
285
349
  historyEntry.scrollIntoView({
@@ -289,7 +353,6 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
289
353
  }
290
354
  }, [historySnapshot.historyIndex]);
291
355
  const theme = colorScheme === "light" ? lightTheme : darkTheme;
292
- const borderColor = colorScheme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)";
293
356
  const solidBorderColor = colorScheme === "light" ? "rgb(223 223 223)" : "rgb(29 29 29)";
294
357
  const baseStyle = {
295
358
  position: "fixed",
@@ -348,10 +411,15 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
348
411
  /* @__PURE__ */ import_react3.default.createElement(
349
412
  DisclosureSection,
350
413
  {
351
- open: true,
352
- title: "Data",
353
- colorScheme,
354
414
  isFirst: true,
415
+ open: showUsers,
416
+ setOpen: setShowUsers,
417
+ title: "Users",
418
+ colorScheme,
419
+ style: {
420
+ flex: "0 0 auto",
421
+ maxHeight: "200px"
422
+ },
355
423
  right: /* @__PURE__ */ import_react3.default.createElement(
356
424
  ToggleButton,
357
425
  {
@@ -362,14 +430,44 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
362
430
  }
363
431
  )
364
432
  },
365
- /* @__PURE__ */ import_react3.default.createElement(
433
+ /* @__PURE__ */ import_react3.default.createElement("div", { style: { ...styles.sectionInner, flex: "0 0 auto" } }, connectedUsers?.map((user) => /* @__PURE__ */ import_react3.default.createElement(
434
+ InspectorRow,
435
+ {
436
+ key: user.id,
437
+ colorScheme,
438
+ variant: user.id === userId ? "up" : void 0
439
+ },
440
+ user.name,
441
+ " (",
442
+ ellipsis(user.id.toString(), 8, "middle"),
443
+ ")"
444
+ )), !connectedUsers && /* @__PURE__ */ import_react3.default.createElement(
366
445
  "div",
367
446
  {
368
447
  style: {
369
- flex: "1 1 0",
370
- overflowY: "auto",
448
+ padding: "12px",
449
+ fontSize: "12px",
371
450
  display: "flex",
372
451
  flexDirection: "column",
452
+ gap: "4px"
453
+ }
454
+ },
455
+ /* @__PURE__ */ import_react3.default.createElement("span", null, "No connected users")
456
+ ))
457
+ ),
458
+ /* @__PURE__ */ import_react3.default.createElement(
459
+ DisclosureSection,
460
+ {
461
+ title: "Data",
462
+ colorScheme,
463
+ setOpen: setShowData,
464
+ open: showData
465
+ },
466
+ /* @__PURE__ */ import_react3.default.createElement(
467
+ "div",
468
+ {
469
+ style: {
470
+ ...styles.sectionInner,
373
471
  gap: "1px",
374
472
  padding: "1px 12px"
375
473
  }
@@ -385,72 +483,58 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
385
483
  title: "History",
386
484
  colorScheme
387
485
  },
388
- /* @__PURE__ */ import_react3.default.createElement(
486
+ /* @__PURE__ */ import_react3.default.createElement("div", { ref: historyContainerRef, style: styles.sectionInner }, /* @__PURE__ */ import_react3.default.createElement(
389
487
  "div",
390
488
  {
391
- ref: historyContainerRef,
489
+ id: `${HISTORY_ELEMENT_PREFIX}0`,
392
490
  style: {
393
- flex: "1 1 0",
394
- overflowY: "auto",
395
- overflowX: "hidden",
491
+ borderBottom: `1px solid ${solidBorderColor}`,
492
+ fontSize: "12px",
493
+ fontFamily: "Menlo, monospace",
494
+ padding: "2px 12px 1px",
396
495
  display: "flex",
397
- flexDirection: "column"
496
+ alignItems: "center",
497
+ background: historySnapshot.historyIndex === 0 ? "rgb(59 130 246 / 15%)" : void 0
398
498
  }
399
499
  },
400
- historySnapshot.history.length < 50 && /* @__PURE__ */ import_react3.default.createElement(
401
- "div",
500
+ /* @__PURE__ */ import_react3.default.createElement(
501
+ "span",
402
502
  {
403
- id: `${HISTORY_ELEMENT_PREFIX}0`,
404
503
  style: {
405
- borderBottom: `1px solid ${solidBorderColor}`,
406
- fontSize: "12px",
407
504
  fontFamily: "Menlo, monospace",
408
- padding: "2px 12px 1px",
409
- display: "flex",
410
- alignItems: "center",
411
- background: historySnapshot.historyIndex === 0 ? "rgb(59 130 246 / 15%)" : void 0
505
+ fontSize: "11px",
506
+ borderRadius: 4,
507
+ display: "inline-block"
412
508
  }
413
509
  },
414
- /* @__PURE__ */ import_react3.default.createElement(
415
- "span",
416
- {
417
- style: {
418
- fontFamily: "Menlo, monospace",
419
- fontSize: "11px",
420
- borderRadius: 4,
421
- display: "inline-block"
422
- }
423
- },
424
- "Initial state"
425
- )
426
- ),
427
- historySnapshot.history.slice(-50).map((entry, index) => /* @__PURE__ */ import_react3.default.createElement(
428
- "div",
510
+ "Initial state"
511
+ )
512
+ ), historySnapshot.history.map((entry, index) => /* @__PURE__ */ import_react3.default.createElement(
513
+ "div",
514
+ {
515
+ id: `${HISTORY_ELEMENT_PREFIX}${index + 1}`,
516
+ key: index,
517
+ style: {
518
+ padding: "0px 12px 1px",
519
+ borderBottom: `1px solid ${solidBorderColor}`,
520
+ background: index + 1 === historySnapshot.historyIndex ? "rgb(59 130 246 / 15%)" : void 0
521
+ }
522
+ },
523
+ entry.redoPatches?.map((patch, j) => /* @__PURE__ */ import_react3.default.createElement(
524
+ "pre",
429
525
  {
430
- id: `noya-multiplayer-history-${index}`,
431
- key: index,
526
+ key: j,
432
527
  style: {
433
- padding: "0px 12px 1px",
434
- borderBottom: `1px solid ${solidBorderColor}`,
435
- background: index === historySnapshot.historyIndex - 1 ? "rgb(59 130 246 / 15%)" : void 0
528
+ fontSize: "11px",
529
+ display: "flex",
530
+ flexWrap: "wrap"
436
531
  }
437
532
  },
438
- entry.redoPatches?.map((patch, j) => /* @__PURE__ */ import_react3.default.createElement(
439
- "pre",
440
- {
441
- key: j,
442
- style: {
443
- fontSize: "11px",
444
- display: "flex",
445
- flexWrap: "wrap"
446
- }
447
- },
448
- patch.op === "add" && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)), " = ", /* @__PURE__ */ import_react3.default.createElement(import_react_inspector.ObjectInspector, { data: patch.value, theme })),
449
- patch.op === "replace" && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)), " = ", /* @__PURE__ */ import_react3.default.createElement(import_react_inspector.ObjectInspector, { data: patch.value, theme })),
450
- patch.op === "remove" && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("span", { style: { color: theme.OBJECT_VALUE_STRING_COLOR } }, "delete", " "), /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)))
451
- ))
533
+ patch.op === "add" && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)), " = ", /* @__PURE__ */ import_react3.default.createElement(import_react_inspector.ObjectInspector, { data: patch.value, theme })),
534
+ patch.op === "replace" && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)), " = ", /* @__PURE__ */ import_react3.default.createElement(import_react_inspector.ObjectInspector, { data: patch.value, theme })),
535
+ patch.op === "remove" && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("span", { style: { color: theme.OBJECT_VALUE_STRING_COLOR } }, "delete", " "), /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)))
452
536
  ))
453
- )
537
+ )))
454
538
  ),
455
539
  /* @__PURE__ */ import_react3.default.createElement(
456
540
  DisclosureSection,
@@ -460,98 +544,56 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
460
544
  title: "Events",
461
545
  colorScheme
462
546
  },
463
- /* @__PURE__ */ import_react3.default.createElement(
464
- "div",
465
- {
466
- ref: eventsContainerRef,
467
- style: {
468
- flex: "1 1 0",
469
- overflowY: "auto",
470
- display: "flex",
471
- flexDirection: "column"
472
- }
473
- },
474
- connectionEvents?.map((event, index) => /* @__PURE__ */ import_react3.default.createElement(
475
- "div",
547
+ /* @__PURE__ */ import_react3.default.createElement("div", { ref: eventsContainerRef, style: styles.sectionInner }, connectionEvents?.map(
548
+ (event, index) => event.type === "stateChange" ? /* @__PURE__ */ import_react3.default.createElement(InspectorRow, { key: index, colorScheme }, "connection:", " ", /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontStyle: "italic" } }, event.state.toLowerCase())) : /* @__PURE__ */ import_react3.default.createElement(
549
+ InspectorRow,
476
550
  {
477
551
  key: index,
552
+ colorScheme,
553
+ variant: event.type === "send" ? "up" : "down",
478
554
  style: {
479
- borderBottom: `1px solid ${borderColor}`
555
+ padding: "0px 12px 1px"
480
556
  }
481
557
  },
482
- event.type === "stateChange" ? /* @__PURE__ */ import_react3.default.createElement(
483
- "div",
484
- {
485
- style: {
486
- padding: "2px 12px",
487
- background: colorScheme === "light" ? "rgba(0,0,0,0.05)" : "rgba(255,255,255,0.05)",
488
- display: "flex",
489
- alignItems: "center"
490
- }
491
- },
492
- /* @__PURE__ */ import_react3.default.createElement(
493
- "span",
494
- {
495
- style: {
496
- fontFamily: "Menlo, monospace",
497
- fontSize: "11px",
498
- borderRadius: 4,
499
- display: "inline-block"
500
- }
501
- },
502
- "connection:",
503
- " ",
504
- /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontStyle: "italic" } }, event.state.toLowerCase())
505
- )
506
- ) : /* @__PURE__ */ import_react3.default.createElement(
507
- "div",
558
+ /* @__PURE__ */ import_react3.default.createElement(
559
+ import_react_inspector.ObjectInspector,
508
560
  {
509
- style: {
510
- padding: "0px 12px 1px",
511
- background: event.type === "send" ? "rgba(0,255,0,0.1)" : "rgba(255,0,0,0.1)"
561
+ data: event.message,
562
+ theme,
563
+ nodeRenderer: ({
564
+ depth,
565
+ name,
566
+ data,
567
+ isNonenumerable,
568
+ expanded
569
+ }) => {
570
+ const direction = event.type === "send" ? "up" : "down";
571
+ return depth === 0 ? /* @__PURE__ */ import_react3.default.createElement(ObjectRootLabel, { direction, data }) : /* @__PURE__ */ import_react3.default.createElement(
572
+ import_react_inspector.ObjectLabel,
573
+ {
574
+ direction,
575
+ name,
576
+ data,
577
+ isNonenumerable
578
+ }
579
+ );
512
580
  }
513
- },
514
- /* @__PURE__ */ import_react3.default.createElement(
515
- import_react_inspector.ObjectInspector,
516
- {
517
- data: event.message,
518
- theme,
519
- nodeRenderer: ({
520
- depth,
521
- name,
522
- data,
523
- isNonenumerable,
524
- expanded
525
- }) => {
526
- const direction = event.type === "send" ? "up" : "down";
527
- return depth === 0 ? /* @__PURE__ */ import_react3.default.createElement(ObjectRootLabel, { direction, data }) : /* @__PURE__ */ import_react3.default.createElement(
528
- import_react_inspector.ObjectLabel,
529
- {
530
- direction,
531
- name,
532
- data,
533
- isNonenumerable
534
- }
535
- );
536
- }
537
- }
538
- )
539
- )
540
- )),
541
- !connectionEvents && /* @__PURE__ */ import_react3.default.createElement(
542
- "div",
543
- {
544
- style: {
545
- padding: "12px",
546
- fontSize: "12px",
547
- display: "flex",
548
- flexDirection: "column",
549
- gap: "4px"
550
581
  }
551
- },
552
- /* @__PURE__ */ import_react3.default.createElement("span", null, "No recorded events")
582
+ )
553
583
  )
554
- )
584
+ ), !connectionEvents && /* @__PURE__ */ import_react3.default.createElement(
585
+ "div",
586
+ {
587
+ style: {
588
+ padding: "12px",
589
+ fontSize: "12px",
590
+ display: "flex",
591
+ flexDirection: "column",
592
+ gap: "4px"
593
+ }
594
+ },
595
+ /* @__PURE__ */ import_react3.default.createElement("span", null, "No recorded events")
596
+ ))
555
597
  )
556
598
  );
557
599
  });
@@ -634,6 +676,8 @@ function createPortalElement() {
634
676
  function useStateInspector({
635
677
  state,
636
678
  connectionEvents,
679
+ connectedUsers,
680
+ userId,
637
681
  disabled = false,
638
682
  colorScheme,
639
683
  anchor,
@@ -660,13 +704,24 @@ function useStateInspector({
660
704
  {
661
705
  state,
662
706
  connectionEvents,
707
+ connectedUsers,
708
+ userId,
663
709
  colorScheme,
664
710
  anchor,
665
711
  stateManager
666
712
  }
667
713
  )
668
714
  );
669
- }, [anchor, colorScheme, connectionEvents, root, state, stateManager]);
715
+ }, [
716
+ anchor,
717
+ colorScheme,
718
+ connectedUsers,
719
+ connectionEvents,
720
+ root,
721
+ state,
722
+ stateManager,
723
+ userId
724
+ ]);
670
725
  }
671
726
 
672
727
  // src/hooks.ts
@@ -743,26 +798,37 @@ function useMultiplayerState(initialState, options) {
743
798
  );
744
799
  const syncRef = (0, import_react5.useRef)(sync);
745
800
  const [connectionEvents, setConnectionEvents] = (0, import_react5.useState)();
801
+ const [connectedUsers, setConnectedUsers] = (0, import_react5.useState)([]);
802
+ const [userId, setUserId] = (0, import_react5.useState)();
746
803
  const extras = (0, import_react5.useMemo)(() => {
747
804
  return {
748
- connectionEvents
805
+ connectionEvents,
806
+ connectedUsers
749
807
  };
750
- }, [connectionEvents]);
808
+ }, [connectionEvents, connectedUsers]);
751
809
  const globalTrackEvents = useObservable(trackEvents);
752
810
  const trackConnectionEvents = options.trackConnectionEvents ?? globalTrackEvents;
753
811
  const trackEventsCallbackRef = (0, import_react5.useRef)();
812
+ const trackConnectedUsersCallbackRef = (0, import_react5.useRef)();
754
813
  (0, import_react5.useEffect)(() => {
755
814
  trackEventsCallbackRef.current = trackConnectionEvents ? (e) => setConnectionEvents((events) => {
756
815
  const updated = events ? [...events, e] : [e];
757
816
  return updated.length > 50 ? updated.slice(-50) : updated;
758
817
  }) : void 0;
759
818
  }, [trackConnectionEvents]);
819
+ (0, import_react5.useEffect)(() => {
820
+ trackConnectedUsersCallbackRef.current = (users, userId2) => {
821
+ setConnectedUsers(users);
822
+ setUserId(userId2);
823
+ };
824
+ });
760
825
  (0, import_react5.useEffect)(() => {
761
826
  if (syncRef.current) {
762
- return syncRef.current(
763
- multiplayerStateManager,
764
- (e) => trackEventsCallbackRef.current?.(e)
765
- );
827
+ return syncRef.current({
828
+ ms: multiplayerStateManager,
829
+ onConnectionEvent: (e) => trackEventsCallbackRef.current?.(e),
830
+ onChangeConnectedUsers: (users, userId2) => trackConnectedUsersCallbackRef.current?.(users, userId2)
831
+ });
766
832
  }
767
833
  }, [multiplayerStateManager]);
768
834
  const state = useSyncMultiplayerStateManager(multiplayerStateManager);
@@ -770,6 +836,8 @@ function useMultiplayerState(initialState, options) {
770
836
  stateManager: multiplayerStateManager.sm,
771
837
  state,
772
838
  connectionEvents,
839
+ connectedUsers,
840
+ userId,
773
841
  disabled: !options.inspector,
774
842
  ...typeof options.inspector === "object" && options.inspector
775
843
  });