@noya-app/noya-multiplayer-react 0.1.22 → 0.1.24

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
@@ -193,77 +193,6 @@ var UserPointerIcon = memo(function CursorIcon({
193
193
  );
194
194
  });
195
195
 
196
- // src/embedded.ts
197
- import { createValue } from "@noya-app/state-manager";
198
- import { useEffect as useEffect2, useMemo as useMemo3 } from "react";
199
- function createDefaultAppData(initialState) {
200
- return {
201
- theme: "light",
202
- viewType: "editable",
203
- initialState,
204
- inspector: false
205
- };
206
- }
207
- function getAppData(initialState) {
208
- try {
209
- const urlHash = (window.location.hash || "").replace(/^#/, "");
210
- const params = new URLSearchParams(urlHash);
211
- const data = params.get("data");
212
- if (!data) {
213
- return initialState ? createDefaultAppData(initialState) : void 0;
214
- }
215
- const parsed = JSON.parse(data);
216
- if (!parsed.initialState) {
217
- parsed.initialState = initialState;
218
- }
219
- return parsed;
220
- } catch (e) {
221
- return initialState ? createDefaultAppData(initialState) : void 0;
222
- }
223
- }
224
- function useNoyaApp(initialState, { schema } = {}) {
225
- const appData = useMemo3(() => {
226
- return getAppData(
227
- schema ? createValue(schema, initialState) : initialState
228
- );
229
- }, [initialState, schema]);
230
- return appData;
231
- }
232
- function useBroadcastConnectedUsers(connectedUsers) {
233
- useEffect2(() => {
234
- if (!isEmbeddedApp())
235
- return;
236
- const message = {
237
- type: "multiplayer.setUsers",
238
- payload: { users: connectedUsers }
239
- };
240
- parent?.postMessage(message, "*");
241
- }, [connectedUsers]);
242
- }
243
- function useBroadcastMenuItems(menuItems, handleSelectMenuItem) {
244
- useEffect2(() => {
245
- if (!isEmbeddedApp())
246
- return;
247
- const message = {
248
- type: "application.setMenuItems",
249
- payload: { menuItems }
250
- };
251
- parent?.postMessage(message, "*");
252
- const listener = (event) => {
253
- if (event.data.type === "application.selectMenuItem") {
254
- handleSelectMenuItem(event.data.payload.value);
255
- }
256
- };
257
- window.addEventListener("message", listener);
258
- return () => {
259
- window.removeEventListener("message", listener);
260
- };
261
- }, [handleSelectMenuItem, menuItems]);
262
- }
263
- function isEmbeddedApp() {
264
- return window.self !== window.top;
265
- }
266
-
267
196
  // src/hooks.ts
268
197
  import {
269
198
  EphemeralUserData,
@@ -273,8 +202,8 @@ import {
273
202
  } from "@noya-app/state-manager";
274
203
  import {
275
204
  useCallback,
276
- useEffect as useEffect4,
277
- useMemo as useMemo4,
205
+ useEffect as useEffect3,
206
+ useMemo as useMemo3,
278
207
  useRef,
279
208
  useState as useState2,
280
209
  useSyncExternalStore as useSyncExternalStore2
@@ -292,7 +221,7 @@ import { createRoot } from "react-dom/client";
292
221
  // src/inspector/StateInspector.tsx
293
222
  import React3, {
294
223
  memo as memo2,
295
- useEffect as useEffect3,
224
+ useEffect as useEffect2,
296
225
  useLayoutEffect
297
226
  } from "react";
298
227
  import {
@@ -555,25 +484,25 @@ var StateInspector = memo2(function StateInspector2({
555
484
  "noya-multiplayer-react-show-ephemeral",
556
485
  false
557
486
  );
558
- useEffect3(() => {
487
+ useEffect2(() => {
559
488
  shouldTrackEvents$.set(showEvents);
560
489
  }, [showEvents]);
561
- useEffect3(() => {
490
+ useEffect2(() => {
562
491
  shouldTrackTasks$.set(showTasks);
563
492
  }, [showTasks]);
564
- useEffect3(() => {
493
+ useEffect2(() => {
565
494
  if (eventsContainerRef.current) {
566
495
  eventsContainerRef.current.scrollTop = eventsContainerRef.current.scrollHeight;
567
496
  }
568
497
  }, [connectionEvents]);
569
498
  const ephemeral = useEphemeralUserData(ephemeralUserData);
570
499
  const historySnapshot = useManagedHistory(multiplayerStateManager.sm);
571
- useEffect3(() => {
500
+ useEffect2(() => {
572
501
  if (historyContainerRef.current) {
573
502
  historyContainerRef.current.scrollTop = historyContainerRef.current.scrollHeight;
574
503
  }
575
504
  }, [historySnapshot]);
576
- useEffect3(() => {
505
+ useEffect2(() => {
577
506
  if (!historyContainerRef.current)
578
507
  return;
579
508
  const historyEntry = historyContainerRef.current.querySelector(
@@ -855,6 +784,17 @@ var StateInspector = memo2(function StateInspector2({
855
784
  background: index + 1 === historySnapshot.historyIndex ? "rgb(59 130 246 / 15%)" : void 0
856
785
  }
857
786
  },
787
+ "name" in entry.metadata && typeof entry.metadata.name === "string" && /* @__PURE__ */ React3.createElement(
788
+ "pre",
789
+ {
790
+ style: {
791
+ fontSize: "11px",
792
+ display: "flex",
793
+ flexWrap: "wrap"
794
+ }
795
+ },
796
+ entry.metadata.name
797
+ ),
858
798
  entry.redoPatches?.map((patch, j) => /* @__PURE__ */ React3.createElement(
859
799
  "pre",
860
800
  {
@@ -1216,9 +1156,41 @@ function useSyncMultiplayerStateManager(multiplayerStateManager) {
1216
1156
  multiplayerStateManager.getOptimisticState
1217
1157
  );
1218
1158
  }
1219
- function useEphemeralUserData(ephemeralUserData) {
1159
+ function throttle(fn, ms) {
1160
+ let lastCall = 0;
1161
+ let timeoutId = null;
1162
+ return (...args) => {
1163
+ const now = Date.now();
1164
+ if (now - lastCall < ms) {
1165
+ if (timeoutId)
1166
+ clearTimeout(timeoutId);
1167
+ timeoutId = setTimeout(
1168
+ () => {
1169
+ lastCall = now;
1170
+ fn(...args);
1171
+ },
1172
+ ms - (now - lastCall)
1173
+ );
1174
+ return;
1175
+ }
1176
+ lastCall = now;
1177
+ return fn(...args);
1178
+ };
1179
+ }
1180
+ function useEphemeralUserData(ephemeralUserData, options) {
1181
+ const throttledAddListener = useMemo3(() => {
1182
+ const throttleMs = options?.throttle ?? 0;
1183
+ if (throttleMs > 0) {
1184
+ const addListener = (fn) => {
1185
+ const throttledFn = throttle(fn, throttleMs);
1186
+ return ephemeralUserData.addListener(throttledFn);
1187
+ };
1188
+ return addListener;
1189
+ }
1190
+ return ephemeralUserData.addListener;
1191
+ }, [ephemeralUserData, options?.throttle]);
1220
1192
  return useSyncExternalStore2(
1221
- ephemeralUserData.addListener,
1193
+ throttledAddListener,
1222
1194
  ephemeralUserData.getSnapshot,
1223
1195
  ephemeralUserData.getSnapshot
1224
1196
  );
@@ -1237,13 +1209,14 @@ function useMultiplayerState(initialState, options) {
1237
1209
  );
1238
1210
  const [tasks, setTasks] = useState2([]);
1239
1211
  const [userId, setUserId] = useState2();
1240
- const extras = useMemo4(() => {
1212
+ const extras = useMemo3(() => {
1241
1213
  return {
1242
1214
  tasks,
1243
1215
  userId,
1244
1216
  connectionEvents,
1245
1217
  connectedUsers,
1246
- ephemeralUserData
1218
+ ephemeralUserData,
1219
+ multiplayerStateManager
1247
1220
  // evaluate: async (code: string) => {
1248
1221
  // const payload = await rpcManager.request({ type: "eval", code });
1249
1222
  // if (payload.type !== "eval") {
@@ -1251,7 +1224,14 @@ function useMultiplayerState(initialState, options) {
1251
1224
  // }
1252
1225
  // },
1253
1226
  };
1254
- }, [tasks, connectionEvents, connectedUsers, ephemeralUserData, userId]);
1227
+ }, [
1228
+ tasks,
1229
+ userId,
1230
+ connectionEvents,
1231
+ connectedUsers,
1232
+ ephemeralUserData,
1233
+ multiplayerStateManager
1234
+ ]);
1255
1235
  const globalTrackEvents = useObservable(shouldTrackEvents$);
1256
1236
  const globalTrackTasks = useObservable(shouldTrackTasks$);
1257
1237
  const shouldTrackEvents = options?.trackConnectionEvents ?? globalTrackEvents;
@@ -1259,19 +1239,19 @@ function useMultiplayerState(initialState, options) {
1259
1239
  const trackEventsCallbackRef = useRef();
1260
1240
  const trackConnectedUsersCallbackRef = useRef();
1261
1241
  const trackTasksCallbackRef = useRef();
1262
- useEffect4(() => {
1242
+ useEffect3(() => {
1263
1243
  trackEventsCallbackRef.current = shouldTrackEvents ? (e) => setConnectionEvents((events) => {
1264
1244
  const updated = events ? [...events, e] : [e];
1265
1245
  return updated.length > 50 ? updated.slice(-50) : updated;
1266
1246
  }) : void 0;
1267
1247
  }, [shouldTrackEvents]);
1268
- useEffect4(() => {
1248
+ useEffect3(() => {
1269
1249
  trackConnectedUsersCallbackRef.current = (users, userId2) => {
1270
1250
  setConnectedUsers(users);
1271
1251
  setUserId(userId2);
1272
1252
  };
1273
1253
  }, []);
1274
- useEffect4(() => {
1254
+ useEffect3(() => {
1275
1255
  trackTasksCallbackRef.current = shouldTrackTasks ? (tasks2) => {
1276
1256
  setTasks((old) => {
1277
1257
  const newIds = new Set(tasks2.map((t) => t.id));
@@ -1280,7 +1260,7 @@ function useMultiplayerState(initialState, options) {
1280
1260
  });
1281
1261
  } : void 0;
1282
1262
  }, [shouldTrackTasks]);
1283
- useEffect4(() => {
1263
+ useEffect3(() => {
1284
1264
  if (syncRef.current) {
1285
1265
  return syncRef.current({
1286
1266
  ms: multiplayerStateManager,
@@ -1310,13 +1290,108 @@ function useMultiplayerState(initialState, options) {
1310
1290
  disabled: !options?.inspector,
1311
1291
  ...typeof options?.inspector === "object" && options.inspector
1312
1292
  });
1313
- useBroadcastConnectedUsers(connectedUsers);
1314
- return [
1315
- state,
1316
- multiplayerStateManager.setState,
1317
- multiplayerStateManager,
1318
- extras
1319
- ];
1293
+ return [state, multiplayerStateManager.setState, extras];
1294
+ }
1295
+
1296
+ // src/noyaApp.ts
1297
+ import {
1298
+ createValue,
1299
+ localStorageSync,
1300
+ webSocketSync
1301
+ } from "@noya-app/state-manager";
1302
+ import { useEffect as useEffect4, useMemo as useMemo4, useState as useState3 } from "react";
1303
+ function createDefaultAppData(initialState) {
1304
+ return {
1305
+ theme: "light",
1306
+ viewType: "editable",
1307
+ initialState,
1308
+ inspector: false
1309
+ };
1310
+ }
1311
+ function getAppData(initialState) {
1312
+ try {
1313
+ const urlHash = (window.location.hash || "").replace(/^#/, "");
1314
+ const params = new URLSearchParams(urlHash);
1315
+ const data = params.get("data");
1316
+ if (!data) {
1317
+ return initialState ? createDefaultAppData(initialState) : void 0;
1318
+ }
1319
+ const parsed = JSON.parse(data);
1320
+ if (!parsed.initialState) {
1321
+ parsed.initialState = initialState;
1322
+ }
1323
+ return parsed;
1324
+ } catch (e) {
1325
+ return initialState ? createDefaultAppData(initialState) : void 0;
1326
+ }
1327
+ }
1328
+ function useAppData(initialState, { schema } = {}) {
1329
+ const appData = useMemo4(() => {
1330
+ return getAppData(
1331
+ schema ? createValue(schema, initialState) : initialState
1332
+ );
1333
+ }, [initialState, schema]);
1334
+ return appData;
1335
+ }
1336
+ function useBroadcastConnectedUsers(connectedUsers) {
1337
+ useEffect4(() => {
1338
+ if (!isEmbeddedApp())
1339
+ return;
1340
+ const message = {
1341
+ type: "multiplayer.setUsers",
1342
+ payload: { users: connectedUsers }
1343
+ };
1344
+ parent?.postMessage(message, "*");
1345
+ }, [connectedUsers]);
1346
+ }
1347
+ function useBroadcastMenuItems(menuItems, handleSelectMenuItem) {
1348
+ useEffect4(() => {
1349
+ if (!isEmbeddedApp())
1350
+ return;
1351
+ const message = {
1352
+ type: "application.setMenuItems",
1353
+ payload: { menuItems }
1354
+ };
1355
+ parent?.postMessage(message, "*");
1356
+ const listener = (event) => {
1357
+ if (event.data.type === "application.selectMenuItem") {
1358
+ handleSelectMenuItem(event.data.payload.value);
1359
+ }
1360
+ };
1361
+ window.addEventListener("message", listener);
1362
+ return () => {
1363
+ window.removeEventListener("message", listener);
1364
+ };
1365
+ }, [handleSelectMenuItem, menuItems]);
1366
+ }
1367
+ function isEmbeddedApp() {
1368
+ return window.self !== window.top;
1369
+ }
1370
+ function useNoyaAppState(initialState, options) {
1371
+ const [state] = useState3(initialState);
1372
+ const {
1373
+ multiplayerUrl,
1374
+ inspector,
1375
+ initialState: noyaAppInitialState,
1376
+ theme,
1377
+ viewType
1378
+ } = useAppData(state, {
1379
+ schema: options.schema
1380
+ });
1381
+ const sync = useMemo4(() => {
1382
+ return multiplayerUrl ? webSocketSync(multiplayerUrl) : localStorageSync({ key: options.localStorageKey || "noya-app" });
1383
+ }, [multiplayerUrl, options.localStorageKey]);
1384
+ const result = useMultiplayerState(noyaAppInitialState, {
1385
+ ...options,
1386
+ inspector: options.inspector ?? inspector,
1387
+ sync
1388
+ });
1389
+ const [, , extras] = result;
1390
+ useBroadcastConnectedUsers(extras.connectedUsers);
1391
+ const mergedExtras = useMemo4(() => {
1392
+ return { ...extras, theme, viewType };
1393
+ }, [extras, theme, viewType]);
1394
+ return [result[0], result[1], mergedExtras];
1320
1395
  }
1321
1396
  export {
1322
1397
  StateInspector,
@@ -1330,13 +1405,15 @@ export {
1330
1405
  getAvatarInitials,
1331
1406
  getAvatarStyle,
1332
1407
  isEmbeddedApp,
1408
+ useAppData,
1333
1409
  useBroadcastConnectedUsers,
1334
1410
  useBroadcastMenuItems,
1335
1411
  useEphemeralUserData,
1336
1412
  useManagedHistory,
1337
1413
  useManagedState,
1338
1414
  useMultiplayerState,
1339
- useNoyaApp,
1415
+ useNoyaAppState,
1416
+ useObservable,
1340
1417
  useSyncMultiplayerStateManager,
1341
1418
  useSyncStateManager
1342
1419
  };