@noya-app/noya-multiplayer-react 0.1.45 → 0.1.46

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.mjs CHANGED
@@ -89,18 +89,12 @@ var UserPointersOverlay = memoGeneric(function UserPointers({
89
89
 
90
90
  // src/hooks.ts
91
91
  import {
92
- AIManager,
93
- AssetManager,
94
- ConnectedUsersManager,
95
- EphemeralUserDataManager,
96
- MultiplayerStateManager,
97
- RPCManager,
92
+ NoyaManager,
98
93
  StateManager,
99
- stubSync,
100
- WorkflowManager
94
+ stubSync
101
95
  } from "@noya-app/state-manager";
102
96
  import {
103
- createElement,
97
+ createElement as createElement2,
104
98
  useCallback,
105
99
  useEffect as useEffect3,
106
100
  useMemo as useMemo2,
@@ -110,25 +104,9 @@ import {
110
104
  } from "react";
111
105
  import { createRoot as createRoot2 } from "react-dom/client";
112
106
 
113
- // ../noya-state-manager/src/SecretManager.ts
114
- import { Observable } from "@noya-app/observable";
115
- var SecretManager = class {
116
- constructor(rpcManager) {
117
- this.rpcManager = rpcManager;
118
- }
119
- secrets$ = new Observable([]);
120
- isInitialized$ = new Observable(false);
121
- async createSecret(name, value) {
122
- return await this.rpcManager.request({ type: "createSecret", name, value });
123
- }
124
- async deleteSecret(name) {
125
- return await this.rpcManager.request({ type: "deleteSecret", name });
126
- }
127
- };
128
-
129
107
  // src/components/ErrorOverlay.tsx
130
- import React2 from "react";
131
- function ErrorOverlay({
108
+ import * as React2 from "react";
109
+ var ErrorOverlay = React2.memo(function ErrorOverlay2({
132
110
  error
133
111
  }) {
134
112
  return /* @__PURE__ */ React2.createElement(
@@ -198,12 +176,7 @@ function ErrorOverlay({
198
176
  "Reload"
199
177
  )
200
178
  );
201
- }
202
-
203
- // src/globals.ts
204
- import { Observable as Observable2 } from "@noya-app/observable";
205
- var shouldTrackEvents$ = new Observable2(false);
206
- var shouldTrackTasks$ = new Observable2(false);
179
+ });
207
180
 
208
181
  // src/inspector/useStateInspector.tsx
209
182
  import React5, { useLayoutEffect as useLayoutEffect2 } from "react";
@@ -428,21 +401,23 @@ function InspectorRow({
428
401
  }
429
402
  var HISTORY_ELEMENT_PREFIX = "noya-multiplayer-history-";
430
403
  var StateInspector = memoGeneric2(function StateInspector2({
431
- state,
432
- connectionEvents,
433
- tasks,
434
- unstyled,
404
+ noyaManager,
435
405
  colorScheme = "light",
436
- multiplayerStateManager,
437
- assetManager,
438
- secretManager,
439
- connectedUsersManager,
440
- ephemeralUserDataManager,
441
406
  anchor = "right",
442
- forceInit,
407
+ unstyled = false,
443
408
  ...props
444
409
  }) {
410
+ const {
411
+ multiplayerStateManager,
412
+ assetManager,
413
+ connectedUsersManager,
414
+ secretManager,
415
+ ephemeralUserDataManager,
416
+ connectionEventManager,
417
+ taskManager
418
+ } = noyaManager;
445
419
  const [didMount, setDidMount] = React4.useState(false);
420
+ const tasks = useObservable(taskManager.tasks$);
446
421
  useLayoutEffect(() => {
447
422
  setDidMount(true);
448
423
  }, []);
@@ -484,23 +459,24 @@ var StateInspector = memoGeneric2(function StateInspector2({
484
459
  "noya-multiplayer-react-show-secrets",
485
460
  false
486
461
  );
487
- useEffect2(() => {
488
- shouldTrackEvents$.set(showEvents);
489
- }, [showEvents]);
490
- useEffect2(() => {
491
- shouldTrackTasks$.set(showTasks);
492
- }, [showTasks]);
462
+ const connectionEvents = useObservable(connectionEventManager.events$);
493
463
  useEffect2(() => {
494
464
  if (eventsContainerRef.current) {
495
465
  eventsContainerRef.current.scrollTop = eventsContainerRef.current.scrollHeight;
496
466
  }
497
467
  }, [connectionEvents]);
468
+ const multipeerStateInitialized = useObservable(
469
+ multiplayerStateManager.isInitialized$
470
+ );
498
471
  const ephemeral = useObservable(ephemeralUserDataManager.data$);
499
472
  const historySnapshot = useManagedHistory(multiplayerStateManager.sm);
500
473
  const assets = useObservable(assetManager.assets$);
474
+ const assetsInitialized = useObservable(assetManager.isInitialized$);
501
475
  const secrets = useObservable(secretManager.secrets$);
476
+ const secretsInitialized = useObservable(secretManager.isInitialized$);
502
477
  const connectedUsers = useObservable(connectedUsersManager.connectedUsers$);
503
478
  const userId = useObservable(connectedUsersManager.currentUserId$);
479
+ const state = useObservable(multiplayerStateManager.optimisticState$);
504
480
  useEffect2(() => {
505
481
  if (historyContainerRef.current) {
506
482
  historyContainerRef.current.scrollTop = historyContainerRef.current.scrollHeight;
@@ -640,7 +616,12 @@ var StateInspector = memoGeneric2(function StateInspector2({
640
616
  /* @__PURE__ */ React4.createElement(
641
617
  DisclosureSection,
642
618
  {
643
- title: "Data",
619
+ title: /* @__PURE__ */ React4.createElement(TitleLabel, null, "Data", /* @__PURE__ */ React4.createElement(
620
+ ColoredDot,
621
+ {
622
+ type: multipeerStateInitialized ? "success" : "error"
623
+ }
624
+ )),
644
625
  colorScheme,
645
626
  setOpen: setShowData,
646
627
  open: showData,
@@ -657,7 +638,7 @@ var StateInspector = memoGeneric2(function StateInspector2({
657
638
  {
658
639
  theme,
659
640
  onClick: () => {
660
- forceInit();
641
+ noyaManager.forceInit();
661
642
  }
662
643
  },
663
644
  "Reset"
@@ -821,9 +802,22 @@ var StateInspector = memoGeneric2(function StateInspector2({
821
802
  {
822
803
  open: showAssets,
823
804
  setOpen: setShowAssets,
824
- title: "Assets",
805
+ title: /* @__PURE__ */ React4.createElement(TitleLabel, null, "Assets", /* @__PURE__ */ React4.createElement(ColoredDot, { type: assetsInitialized ? "success" : "error" })),
825
806
  colorScheme,
826
- right: /* @__PURE__ */ React4.createElement("span", { style: { display: "flex", gap: "4px" } }, /* @__PURE__ */ React4.createElement(
807
+ right: /* @__PURE__ */ React4.createElement("span", { style: { display: "flex", gap: "10px" } }, /* @__PURE__ */ React4.createElement(
808
+ SmallButton,
809
+ {
810
+ theme,
811
+ onClick: () => {
812
+ console.info(
813
+ "[StateInspector] Reloading assets",
814
+ noyaManager.id
815
+ );
816
+ assetManager.fetch();
817
+ }
818
+ },
819
+ "Reload"
820
+ ), /* @__PURE__ */ React4.createElement(
827
821
  SmallButton,
828
822
  {
829
823
  theme,
@@ -886,7 +880,7 @@ var StateInspector = memoGeneric2(function StateInspector2({
886
880
  /* @__PURE__ */ React4.createElement(
887
881
  DisclosureSection,
888
882
  {
889
- title: "Secrets",
883
+ title: /* @__PURE__ */ React4.createElement(TitleLabel, null, "Secrets", /* @__PURE__ */ React4.createElement(ColoredDot, { type: secretsInitialized ? "success" : "error" })),
890
884
  colorScheme,
891
885
  open: showSecrets,
892
886
  setOpen: setShowSecrets,
@@ -960,8 +954,7 @@ var StateInspector = memoGeneric2(function StateInspector2({
960
954
  depth,
961
955
  name,
962
956
  data,
963
- isNonenumerable,
964
- expanded
957
+ isNonenumerable
965
958
  }) => {
966
959
  const direction = event.type === "send" ? "up" : "down";
967
960
  return depth === 0 ? /* @__PURE__ */ React4.createElement(ObjectRootLabel, { direction, data }) : /* @__PURE__ */ React4.createElement(
@@ -1097,6 +1090,24 @@ function SmallButton({
1097
1090
  children
1098
1091
  );
1099
1092
  }
1093
+ function ColoredDot({ type }) {
1094
+ return /* @__PURE__ */ React4.createElement(
1095
+ "span",
1096
+ {
1097
+ style: {
1098
+ display: "inline-block",
1099
+ width: 6,
1100
+ height: 6,
1101
+ background: type === "success" ? "rgba(0,200,0,0.8)" : "rgba(200,0,0,0.8)",
1102
+ borderRadius: "50%",
1103
+ verticalAlign: "middle"
1104
+ }
1105
+ }
1106
+ );
1107
+ }
1108
+ function TitleLabel({ children }) {
1109
+ return /* @__PURE__ */ React4.createElement("span", { style: { display: "flex", alignItems: "center", gap: "4px" } }, children);
1110
+ }
1100
1111
 
1101
1112
  // src/inspector/useStateInspector.tsx
1102
1113
  function createShadowRootElement() {
@@ -1108,18 +1119,10 @@ function createShadowRootElement() {
1108
1119
  return { host, container };
1109
1120
  }
1110
1121
  function useStateInspector({
1111
- state,
1112
- connectionEvents,
1113
- tasks,
1122
+ noyaManager,
1114
1123
  disabled = false,
1115
1124
  colorScheme,
1116
- anchor,
1117
- multiplayerStateManager,
1118
- assetManager,
1119
- secretManager,
1120
- connectedUsersManager,
1121
- ephemeralUserDataManager,
1122
- forceInit
1125
+ anchor
1123
1126
  }) {
1124
1127
  const [root, setRoot] = React5.useState(null);
1125
1128
  useLayoutEffect2(() => {
@@ -1138,34 +1141,13 @@ function useStateInspector({
1138
1141
  /* @__PURE__ */ React5.createElement(
1139
1142
  StateInspector,
1140
1143
  {
1141
- state,
1142
- connectionEvents,
1143
- tasks,
1144
1144
  colorScheme,
1145
1145
  anchor,
1146
- forceInit,
1147
- multiplayerStateManager,
1148
- assetManager,
1149
- secretManager,
1150
- ephemeralUserDataManager,
1151
- connectedUsersManager
1146
+ noyaManager
1152
1147
  }
1153
1148
  )
1154
1149
  );
1155
- }, [
1156
- anchor,
1157
- colorScheme,
1158
- connectionEvents,
1159
- root,
1160
- state,
1161
- multiplayerStateManager,
1162
- tasks,
1163
- forceInit,
1164
- ephemeralUserDataManager,
1165
- assetManager,
1166
- secretManager,
1167
- connectedUsersManager
1168
- ]);
1150
+ }, [noyaManager, anchor, colorScheme, root]);
1169
1151
  }
1170
1152
 
1171
1153
  // src/hooks.ts
@@ -1227,170 +1209,81 @@ function useManagedHistory(stateManager) {
1227
1209
  stateManager.getHistorySnapshot
1228
1210
  );
1229
1211
  }
1230
- function useSyncMultiplayerStateManager(multiplayerStateManager) {
1231
- return useSyncExternalStore2(
1232
- multiplayerStateManager.addListener,
1233
- multiplayerStateManager.getOptimisticState,
1234
- multiplayerStateManager.getOptimisticState
1235
- );
1236
- }
1237
1212
  var defaultStubSync = stubSync();
1238
1213
  function useMultiplayerState(initialState, options) {
1239
- const { sync = defaultStubSync, ...rest } = options ?? {};
1240
- const [rpcManager] = useState2(
1241
- () => new RPCManager()
1242
- );
1243
- const [multiplayerStateManager] = useState2(
1244
- () => new MultiplayerStateManager(initialState, rest)
1214
+ const {
1215
+ sync = defaultStubSync,
1216
+ inspector,
1217
+ ...rest
1218
+ } = options ?? {};
1219
+ const [noyaManager] = useState2(
1220
+ () => new NoyaManager(initialState, rest)
1245
1221
  );
1246
- const [assetManager] = useState2(() => new AssetManager());
1247
- const [aiManager] = useState2(() => new AIManager(rpcManager));
1248
- const [workflowManager] = useState2(() => new WorkflowManager(rpcManager));
1249
- const [secretManager] = useState2(() => new SecretManager(rpcManager));
1250
- const [connectedUsersManager] = useState2(() => new ConnectedUsersManager());
1251
- const syncRef = useRef(sync);
1252
- const [connectionEvents, setConnectionEvents] = useState2();
1253
- const [ephemeralUserDataManager] = useState2(
1254
- () => new EphemeralUserDataManager()
1222
+ const state = useObservable(
1223
+ noyaManager.multiplayerStateManager.optimisticState$
1255
1224
  );
1256
- const [tasks, setTasks] = useState2([]);
1257
- const assets = useObservable(assetManager.assets$);
1225
+ const assets = useObservable(noyaManager.assetManager.assets$);
1258
1226
  const extras = useMemo2(() => {
1259
1227
  return {
1260
- tasks,
1261
- connectionEvents,
1262
- connectedUsersManager,
1263
- ephemeralUserDataManager,
1264
- multiplayerStateManager,
1265
- assets,
1266
- assetManager,
1267
- aiManager,
1268
- rpcManager,
1269
- workflowManager,
1270
- secretManager,
1271
- createAsset: assetManager.create,
1272
- deleteAsset: assetManager.delete
1273
- // connectionStatus,
1274
- // isConnected: connectionStatus === "connected",
1228
+ noyaManager,
1229
+ taskManager: noyaManager.taskManager,
1230
+ connectionEventManager: noyaManager.connectionEventManager,
1231
+ connectedUsersManager: noyaManager.connectedUsersManager,
1232
+ ephemeralUserDataManager: noyaManager.ephemeralUserDataManager,
1233
+ multiplayerStateManager: noyaManager.multiplayerStateManager,
1234
+ assetManager: noyaManager.assetManager,
1235
+ aiManager: noyaManager.aiManager,
1236
+ rpcManager: noyaManager.rpcManager,
1237
+ workflowManager: noyaManager.workflowManager,
1238
+ secretManager: noyaManager.secretManager,
1239
+ menuItemsManager: noyaManager.menuManager,
1240
+ createAsset: noyaManager.assetManager.create,
1241
+ deleteAsset: noyaManager.assetManager.delete,
1242
+ assets
1275
1243
  };
1276
- }, [
1277
- tasks,
1278
- connectionEvents,
1279
- connectedUsersManager,
1280
- ephemeralUserDataManager,
1281
- multiplayerStateManager,
1282
- assets,
1283
- assetManager,
1284
- aiManager,
1285
- rpcManager,
1286
- workflowManager,
1287
- secretManager
1288
- ]);
1289
- const globalTrackEvents = useObservable(shouldTrackEvents$);
1290
- const globalTrackTasks = useObservable(shouldTrackTasks$);
1291
- const shouldTrackEvents = options?.trackConnectionEvents ?? globalTrackEvents;
1292
- const shouldTrackTasks = options?.trackTasks ?? globalTrackTasks;
1293
- const trackEventsCallbackRef = useRef();
1294
- const trackTasksCallbackRef = useRef();
1295
- useEffect3(() => {
1296
- trackEventsCallbackRef.current = shouldTrackEvents ? (e) => setConnectionEvents((events) => {
1297
- const updated = events ? [...events, e] : [e];
1298
- return updated.length > 50 ? updated.slice(-50) : updated;
1299
- }) : void 0;
1300
- }, [shouldTrackEvents]);
1301
- useEffect3(() => {
1302
- trackTasksCallbackRef.current = shouldTrackTasks ? (tasks2) => {
1303
- setTasks((old) => {
1304
- const newIds = new Set(tasks2.map((t) => t.id));
1305
- const unchanged = old.filter((t) => !newIds.has(t.id));
1306
- return [...unchanged, ...tasks2];
1307
- });
1308
- } : void 0;
1309
- }, [shouldTrackTasks]);
1244
+ }, [noyaManager, assets]);
1245
+ const syncRef = useRef(sync);
1310
1246
  useEffect3(() => {
1311
1247
  if (syncRef.current) {
1312
- return syncRef.current({
1313
- ms: multiplayerStateManager,
1314
- assetManager,
1315
- workflowManager,
1316
- secretManager,
1317
- rpcManager,
1318
- aiManager,
1319
- ephemeralUserDataManager,
1320
- connectedUsersManager,
1321
- onConnectionEvent: (e) => trackEventsCallbackRef.current?.(e),
1322
- onChangeTasks: (tasks2) => trackTasksCallbackRef.current?.(tasks2)
1323
- });
1248
+ return syncRef.current({ noyaManager });
1324
1249
  }
1325
- }, [
1326
- assetManager,
1327
- ephemeralUserDataManager,
1328
- multiplayerStateManager,
1329
- workflowManager,
1330
- secretManager,
1331
- rpcManager,
1332
- aiManager,
1333
- connectedUsersManager
1334
- ]);
1335
- const state = useSyncMultiplayerStateManager(multiplayerStateManager);
1336
- const forceInit = useCallback(() => {
1337
- const state2 = initialState instanceof Function ? initialState() : initialState;
1338
- const schema = options?.schema;
1339
- multiplayerStateManager.sm.clearHistory();
1340
- multiplayerStateManager.sendInit({ force: true, state: state2, schema });
1341
- }, [initialState, multiplayerStateManager, options?.schema]);
1342
- const [unrecoverableError, setUnrecoverableError] = useState2();
1343
- useEffect3(() => {
1344
- return multiplayerStateManager.errorEmitter.addListener((error) => {
1345
- switch (error.reason) {
1346
- case "invalidDataType":
1347
- case "schemaMismatch":
1348
- case "outdatedSchema":
1349
- case "dataMigrationFailure":
1350
- case "schemaMigration":
1351
- setUnrecoverableError(error);
1352
- break;
1353
- }
1354
- });
1355
- }, [multiplayerStateManager]);
1250
+ }, [noyaManager]);
1251
+ useErrorOverlay(noyaManager);
1252
+ useStateInspector({
1253
+ noyaManager,
1254
+ disabled: !inspector,
1255
+ ...typeof inspector === "object" && inspector
1256
+ });
1257
+ return [state, noyaManager.multiplayerStateManager.setState, extras];
1258
+ }
1259
+ function useErrorOverlay(noyaManager) {
1260
+ const unrecoverableError = useObservable(noyaManager.unrecoverableError);
1356
1261
  useEffect3(() => {
1357
1262
  if (!unrecoverableError) return;
1358
1263
  const el = document.createElement("div");
1359
1264
  el.id = "noya-multiplayer-react-error-overlay";
1360
1265
  document.body.appendChild(el);
1361
1266
  const root = createRoot2(el);
1362
- root.render(createElement(ErrorOverlay, { error: unrecoverableError }));
1267
+ root.render(createElement2(ErrorOverlay, { error: unrecoverableError }));
1363
1268
  return () => {
1364
1269
  root.unmount();
1365
1270
  el.remove();
1366
1271
  };
1367
1272
  });
1368
- useStateInspector({
1369
- ephemeralUserDataManager,
1370
- multiplayerStateManager,
1371
- assetManager,
1372
- secretManager,
1373
- state,
1374
- connectionEvents,
1375
- connectedUsersManager,
1376
- tasks,
1377
- forceInit,
1378
- disabled: !options?.inspector,
1379
- ...typeof options?.inspector === "object" && options.inspector
1380
- });
1381
- return [state, multiplayerStateManager.setState, extras];
1382
1273
  }
1383
1274
 
1384
1275
  // src/noyaApp.ts
1385
1276
  import {
1386
1277
  createOrCastValue,
1387
1278
  findAllDefs,
1279
+ isEmbeddedApp,
1388
1280
  localStorageSync,
1281
+ parentFrameSync,
1389
1282
  stubSync as stubSync2,
1390
1283
  TypeGuard,
1391
1284
  webSocketSync
1392
1285
  } from "@noya-app/state-manager";
1393
- import { useEffect as useEffect4, useMemo as useMemo3, useState as useState3 } from "react";
1286
+ import { useMemo as useMemo3, useState as useState3 } from "react";
1394
1287
  function createDefaultAppData(initialState) {
1395
1288
  return {
1396
1289
  theme: "light",
@@ -1429,43 +1322,6 @@ function getAppData(initialState, schema, options) {
1429
1322
  appData.initialState = enforceSchema(appData.initialState, schema);
1430
1323
  return appData;
1431
1324
  }
1432
- function useBroadcastConnectedUsers(connectedUsersManager) {
1433
- useEffect4(() => {
1434
- if (!isEmbeddedApp()) return;
1435
- const unsubscribe = connectedUsersManager.connectedUsers$.subscribe(
1436
- (users) => {
1437
- const message = {
1438
- type: "multiplayer.setUsers",
1439
- payload: { users }
1440
- };
1441
- parent?.postMessage(message, "*");
1442
- }
1443
- );
1444
- return unsubscribe;
1445
- }, [connectedUsersManager]);
1446
- }
1447
- function useBroadcastMenuItems(menuItems, handleSelectMenuItem) {
1448
- useEffect4(() => {
1449
- if (!isEmbeddedApp()) return;
1450
- const message = {
1451
- type: "application.setMenuItems",
1452
- payload: { menuItems }
1453
- };
1454
- parent?.postMessage(message, "*");
1455
- const listener = (event) => {
1456
- if (event.data.type === "application.selectMenuItem") {
1457
- handleSelectMenuItem(event.data.payload.value);
1458
- }
1459
- };
1460
- window.addEventListener("message", listener);
1461
- return () => {
1462
- window.removeEventListener("message", listener);
1463
- };
1464
- }, [handleSelectMenuItem, menuItems]);
1465
- }
1466
- function isEmbeddedApp() {
1467
- return window.self !== window.top;
1468
- }
1469
1325
  function useNoyaState(...args) {
1470
1326
  const [initialState, options] = typeof args[0] === "object" && args[0] !== null && "schema" in args[0] && TypeGuard.IsSchema(args[0].schema) ? [null, args[0]] : [args[0], args[1]];
1471
1327
  const [
@@ -1482,15 +1338,14 @@ function useNoyaState(...args) {
1482
1338
  });
1483
1339
  });
1484
1340
  const sync = useMemo3(() => {
1485
- return multiplayerUrl ? webSocketSync(multiplayerUrl) : options?.offlineStorageKey ? localStorageSync({ key: options.offlineStorageKey }) : stubSync2();
1486
- }, [multiplayerUrl, options?.offlineStorageKey]);
1341
+ return isEmbeddedApp() ? parentFrameSync({ debug: options?.debug }) : multiplayerUrl ? webSocketSync(multiplayerUrl) : options?.offlineStorageKey ? localStorageSync({ key: options.offlineStorageKey }) : stubSync2();
1342
+ }, [multiplayerUrl, options?.offlineStorageKey, options?.debug]);
1487
1343
  const result = useMultiplayerState(noyaAppInitialState, {
1488
1344
  ...options,
1489
1345
  inspector: options?.inspector ?? inspector,
1490
1346
  sync: options?.sync ?? sync
1491
1347
  });
1492
1348
  const [, , extras] = result;
1493
- useBroadcastConnectedUsers(extras.connectedUsersManager);
1494
1349
  const mergedExtras = useMemo3(() => {
1495
1350
  return { ...extras, theme, viewType };
1496
1351
  }, [extras, theme, viewType]);
@@ -1499,12 +1354,13 @@ function useNoyaState(...args) {
1499
1354
 
1500
1355
  // src/NoyaStateContext.tsx
1501
1356
  import {
1502
- Observable as Observable3
1357
+ Observable
1503
1358
  } from "@noya-app/observable";
1504
1359
  import React6, {
1505
1360
  createContext,
1506
1361
  useCallback as useCallback2,
1507
1362
  useContext,
1363
+ useEffect as useEffect4,
1508
1364
  useMemo as useMemo4
1509
1365
  } from "react";
1510
1366
  var AnyNoyaStateContext = createContext(void 0);
@@ -1559,9 +1415,13 @@ function useSecret(name) {
1559
1415
  );
1560
1416
  }
1561
1417
  function useIsInitialized() {
1562
- const { ms, secretManager, assetManager } = useAnyNoyaStateContext();
1418
+ const {
1419
+ multiplayerStateManager: ms,
1420
+ secretManager,
1421
+ assetManager
1422
+ } = useAnyNoyaStateContext();
1563
1423
  const isInitializedObservable = useMemo4(
1564
- () => Observable3.combine(
1424
+ () => Observable.combine(
1565
1425
  [
1566
1426
  ms.isInitialized$,
1567
1427
  secretManager.isInitialized$,
@@ -1607,49 +1467,27 @@ function createNoyaContext({
1607
1467
  initialState,
1608
1468
  ...options
1609
1469
  }) {
1610
- const [
1611
- ,
1612
- ,
1613
- {
1614
- multiplayerStateManager,
1615
- assetManager,
1616
- workflowManager,
1617
- secretManager,
1618
- aiManager,
1619
- ephemeralUserDataManager,
1620
- connectedUsersManager
1621
- }
1622
- ] = useNoyaState(initialState, {
1470
+ const [, , { noyaManager }] = useNoyaState(initialState, {
1623
1471
  ...options,
1624
1472
  schema,
1625
1473
  mergeHistoryEntries
1626
1474
  });
1627
- const value = useMemo4(
1628
- () => ({
1629
- ms: multiplayerStateManager,
1630
- assetManager,
1631
- workflowManager,
1632
- secretManager,
1633
- aiManager
1634
- }),
1635
- [
1636
- multiplayerStateManager,
1637
- assetManager,
1638
- workflowManager,
1639
- secretManager,
1640
- aiManager
1641
- ]
1642
- );
1643
- return /* @__PURE__ */ React6.createElement(AnyNoyaStateContext.Provider, { value }, /* @__PURE__ */ React6.createElement(ConnectedUsersContext.Provider, { value: connectedUsersManager }, /* @__PURE__ */ React6.createElement(
1644
- EphemeralUserDataManagerContext.Provider,
1475
+ return /* @__PURE__ */ React6.createElement(AnyNoyaStateContext.Provider, { value: noyaManager }, /* @__PURE__ */ React6.createElement(
1476
+ ConnectedUsersContext.Provider,
1645
1477
  {
1646
- value: ephemeralUserDataManager
1478
+ value: noyaManager.connectedUsersManager
1647
1479
  },
1648
- options.fallback !== void 0 ? /* @__PURE__ */ React6.createElement(FallbackUntilInitialized, { fallback: options.fallback }, children) : children
1649
- )));
1480
+ /* @__PURE__ */ React6.createElement(
1481
+ EphemeralUserDataManagerContext.Provider,
1482
+ {
1483
+ value: noyaManager.ephemeralUserDataManager
1484
+ },
1485
+ options.fallback !== void 0 ? /* @__PURE__ */ React6.createElement(FallbackUntilInitialized, { fallback: options.fallback }, children) : children
1486
+ )
1487
+ ));
1650
1488
  }
1651
1489
  function useValue(path, options) {
1652
- const { ms } = useAnyNoyaStateContext();
1490
+ const { multiplayerStateManager: ms } = useAnyNoyaStateContext();
1653
1491
  let actualPath = typeof path === "function" || !path ? "" : path;
1654
1492
  const mappedObservable = useMemo4(() => {
1655
1493
  if (typeof path === "function") {
@@ -1661,12 +1499,11 @@ function createNoyaContext({
1661
1499
  return value;
1662
1500
  }
1663
1501
  function useSetValue(path) {
1664
- const { ms } = useAnyNoyaStateContext();
1502
+ const { multiplayerStateManager: ms } = useAnyNoyaStateContext();
1665
1503
  const setValue = useCallback2(
1666
1504
  (...args) => {
1667
- const [metadata, value] = args.length === 2 ? args : [void 0, args[0]];
1668
- const setStateAtPath = ms.setStateAtPath;
1669
- setStateAtPath(metadata, path ?? "", value);
1505
+ const [metadata, value] = args.length === 2 ? args : [{}, args[0]];
1506
+ ms.setStateAtPath(metadata, path ?? "", value);
1670
1507
  },
1671
1508
  [ms, path]
1672
1509
  );
@@ -1679,7 +1516,7 @@ function createNoyaContext({
1679
1516
  return [value, setValue];
1680
1517
  }
1681
1518
  function useStateManager() {
1682
- const { ms } = useAnyNoyaStateContext();
1519
+ const { multiplayerStateManager: ms } = useAnyNoyaStateContext();
1683
1520
  return ms;
1684
1521
  }
1685
1522
  function useEphemeralUserDataManager() {
@@ -1691,6 +1528,27 @@ function createNoyaContext({
1691
1528
  }
1692
1529
  return ephemeralUserData;
1693
1530
  }
1531
+ function useNoyaManager() {
1532
+ return useAnyNoyaStateContext();
1533
+ }
1534
+ function useLeftMenuItems() {
1535
+ const { menuManager } = useNoyaManager();
1536
+ return useObservable(menuManager.leftMenuItems$);
1537
+ }
1538
+ function useRightMenuItems() {
1539
+ const { menuManager } = useNoyaManager();
1540
+ return useObservable(menuManager.rightMenuItems$);
1541
+ }
1542
+ function useHandleMenuItem(callback) {
1543
+ const { menuManager } = useNoyaManager();
1544
+ useEffect4(() => {
1545
+ return menuManager.addListener(callback);
1546
+ }, [menuManager, callback]);
1547
+ }
1548
+ function useOnSelectMenuItemCallback() {
1549
+ const { menuManager } = useNoyaManager();
1550
+ return useCallback2((type) => menuManager.emit(type), [menuManager]);
1551
+ }
1694
1552
  return {
1695
1553
  Context: NoyaStateContext,
1696
1554
  Provider: NoyaStateProvider,
@@ -1698,31 +1556,130 @@ function createNoyaContext({
1698
1556
  useSetValue,
1699
1557
  useValueState,
1700
1558
  useStateManager,
1701
- useEphemeralUserDataManager
1702
- // useAssets,
1703
- // useAssetManager,
1704
- // useAsset,
1559
+ useEphemeralUserDataManager,
1560
+ useLeftMenuItems,
1561
+ useRightMenuItems,
1562
+ useHandleMenuItem,
1563
+ useOnSelectMenuItemCallback,
1564
+ useNoyaManager
1705
1565
  };
1706
1566
  }
1567
+
1568
+ // src/WebSocketConnection.ts
1569
+ import {
1570
+ ReconnectingWebSocket,
1571
+ UserActivityDetector
1572
+ } from "@noya-app/state-manager";
1573
+ var WebSocketConnection = class {
1574
+ constructor(url, options = {}) {
1575
+ this.url = url;
1576
+ this.options = options;
1577
+ this.ws = new ReconnectingWebSocket({
1578
+ debug: options.debug,
1579
+ onopen: this.handleOpen,
1580
+ onmessage: this.handleMessage,
1581
+ onclose: this.handleClose,
1582
+ activityDetector: new UserActivityDetector()
1583
+ });
1584
+ }
1585
+ ws;
1586
+ pingCount = 0;
1587
+ pendingPingId;
1588
+ intervalId;
1589
+ async connect() {
1590
+ this.ws.connect(this.url.toString());
1591
+ this.intervalId = setInterval(() => {
1592
+ if (this.ws.state !== "OPEN") return;
1593
+ if (this.pendingPingId) {
1594
+ this.ws.close();
1595
+ return;
1596
+ }
1597
+ const pingId = (this.pingCount++).toString();
1598
+ const message = {
1599
+ type: "ping",
1600
+ id: pingId
1601
+ };
1602
+ this.pendingPingId = message.id;
1603
+ this.options.onConnectionEvent?.({
1604
+ type: "send",
1605
+ message
1606
+ });
1607
+ this.ws.send(JSON.stringify(message));
1608
+ }, 1e4);
1609
+ }
1610
+ handleOpen = () => {
1611
+ this.pendingPingId = void 0;
1612
+ if (this.options.debug) {
1613
+ console.info("ws connected");
1614
+ }
1615
+ this.options.onConnectionEvent?.({
1616
+ type: "stateChange",
1617
+ state: "OPEN"
1618
+ });
1619
+ };
1620
+ handleMessage = (event) => {
1621
+ if (this.options.debug) {
1622
+ console.info("ws receiving message ", event.data);
1623
+ }
1624
+ const parsed = JSON.parse(event.data);
1625
+ this.options.onConnectionEvent?.({
1626
+ type: "receive",
1627
+ message: parsed
1628
+ });
1629
+ if (parsed.type === "pong" && parsed.id === this.pendingPingId) {
1630
+ this.pendingPingId = void 0;
1631
+ }
1632
+ };
1633
+ handleClose = () => {
1634
+ this.pendingPingId = void 0;
1635
+ if (this.options.debug) {
1636
+ console.info("ws disconnected");
1637
+ }
1638
+ this.options.onConnectionEvent?.({
1639
+ type: "stateChange",
1640
+ state: "CLOSED"
1641
+ });
1642
+ };
1643
+ send(message) {
1644
+ if (this.options.debug) {
1645
+ console.info("ws sending message", message);
1646
+ }
1647
+ this.options.onConnectionEvent?.({
1648
+ type: "send",
1649
+ message
1650
+ });
1651
+ this.ws.send(JSON.stringify(message));
1652
+ }
1653
+ close() {
1654
+ if (this.intervalId) {
1655
+ clearInterval(this.intervalId);
1656
+ }
1657
+ this.ws.shutdown();
1658
+ }
1659
+ addListener(listener) {
1660
+ return this.ws.addListener(listener);
1661
+ }
1662
+ removeListener(listener) {
1663
+ return this.ws.removeListener(listener);
1664
+ }
1665
+ };
1707
1666
  export {
1708
1667
  AnyEphemeralUserDataManagerContext,
1709
1668
  ConnectedUsersContext,
1710
1669
  FallbackUntilInitialized,
1711
1670
  StateInspector,
1712
1671
  UserPointersOverlay,
1672
+ WebSocketConnection,
1713
1673
  createDefaultAppData,
1714
1674
  createNoyaContext,
1715
1675
  enforceSchema,
1716
1676
  getAppData,
1717
- isEmbeddedApp,
1718
1677
  parseAppDataParameter,
1719
1678
  useAIManager,
1720
1679
  useAnyEphemeralUserData,
1721
1680
  useAsset,
1722
1681
  useAssetManager,
1723
1682
  useAssets,
1724
- useBroadcastConnectedUsers,
1725
- useBroadcastMenuItems,
1726
1683
  useConnectedUsersManager,
1727
1684
  useCurrentUserId,
1728
1685
  useIsInitialized,
@@ -1734,7 +1691,6 @@ export {
1734
1691
  useSecret,
1735
1692
  useSecretManager,
1736
1693
  useSecrets,
1737
- useSyncMultiplayerStateManager,
1738
1694
  useSyncStateManager,
1739
1695
  useWorkflowManager
1740
1696
  };