@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/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +15 -0
- package/dist/index.d.mts +65 -46
- package/dist/index.d.ts +65 -46
- package/dist/index.js +265 -190
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +173 -96
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/hooks.ts +69 -25
- package/src/index.ts +2 -1
- package/src/inspector/StateInspector.tsx +25 -5
- package/src/{embedded.ts → noyaApp.ts} +58 -7
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
|
|
277
|
-
useMemo as
|
|
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
|
|
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
|
-
|
|
487
|
+
useEffect2(() => {
|
|
559
488
|
shouldTrackEvents$.set(showEvents);
|
|
560
489
|
}, [showEvents]);
|
|
561
|
-
|
|
490
|
+
useEffect2(() => {
|
|
562
491
|
shouldTrackTasks$.set(showTasks);
|
|
563
492
|
}, [showTasks]);
|
|
564
|
-
|
|
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
|
-
|
|
500
|
+
useEffect2(() => {
|
|
572
501
|
if (historyContainerRef.current) {
|
|
573
502
|
historyContainerRef.current.scrollTop = historyContainerRef.current.scrollHeight;
|
|
574
503
|
}
|
|
575
504
|
}, [historySnapshot]);
|
|
576
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
-
|
|
1248
|
+
useEffect3(() => {
|
|
1269
1249
|
trackConnectedUsersCallbackRef.current = (users, userId2) => {
|
|
1270
1250
|
setConnectedUsers(users);
|
|
1271
1251
|
setUserId(userId2);
|
|
1272
1252
|
};
|
|
1273
1253
|
}, []);
|
|
1274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
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
|
-
|
|
1415
|
+
useNoyaAppState,
|
|
1416
|
+
useObservable,
|
|
1340
1417
|
useSyncMultiplayerStateManager,
|
|
1341
1418
|
useSyncStateManager
|
|
1342
1419
|
};
|