@noya-app/noya-multiplayer-react 0.1.27 → 0.1.29
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 +19 -9
- package/CHANGELOG.md +15 -0
- package/bundle.config.ts +20 -0
- package/dist/index.bundle.js +51 -0
- package/dist/index.d.mts +10 -9
- package/dist/index.d.ts +10 -9
- package/dist/index.js +41 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +40 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -3
- package/src/__tests__/noyaApp.test.ts +107 -0
- package/src/noyaApp.ts +55 -44
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { expect, it } from "bun:test";
|
|
2
|
+
import { Type } from "..";
|
|
3
|
+
import { getAppData } from "../noyaApp";
|
|
4
|
+
|
|
5
|
+
it("gets AppData with no data", () => {
|
|
6
|
+
const initialState = { foo: "bar" };
|
|
7
|
+
const data = getAppData(initialState, undefined, {
|
|
8
|
+
window: { location: { hash: "" } },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
expect(data).toEqual({
|
|
12
|
+
theme: "light",
|
|
13
|
+
viewType: "editable",
|
|
14
|
+
initialState,
|
|
15
|
+
inspector: false,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("gets AppData with data param", () => {
|
|
20
|
+
const dataParam = JSON.stringify({
|
|
21
|
+
theme: "light",
|
|
22
|
+
viewType: "editable",
|
|
23
|
+
initialState: { foo: "bar" },
|
|
24
|
+
inspector: false,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const data = getAppData(null as unknown, undefined, {
|
|
28
|
+
window: {
|
|
29
|
+
location: {
|
|
30
|
+
hash: `#data=${dataParam}`,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(data).toEqual({
|
|
36
|
+
theme: "light",
|
|
37
|
+
viewType: "editable",
|
|
38
|
+
initialState: { foo: "bar" },
|
|
39
|
+
inspector: false,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("uses initial state if data doesn't parse", () => {
|
|
44
|
+
const data = getAppData({ foo: "bar" }, undefined, {
|
|
45
|
+
window: { location: { hash: "#data=not-json" } },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(data).toEqual({
|
|
49
|
+
theme: "light",
|
|
50
|
+
viewType: "editable",
|
|
51
|
+
initialState: { foo: "bar" },
|
|
52
|
+
inspector: false,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("gets AppData with schema", () => {
|
|
57
|
+
const schema = Type.Object({
|
|
58
|
+
foo: Type.String({ default: "bar" }),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const data = getAppData(null as unknown, schema, {
|
|
62
|
+
window: { location: { hash: "" } },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(data).toEqual({
|
|
66
|
+
theme: "light",
|
|
67
|
+
viewType: "editable",
|
|
68
|
+
initialState: { foo: "bar" },
|
|
69
|
+
inspector: false,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// it("enforces schema on data param", () => {
|
|
74
|
+
// const schema = Type.Object({
|
|
75
|
+
// foo: Type.String({ default: "bar" }),
|
|
76
|
+
// });
|
|
77
|
+
|
|
78
|
+
// const data = getAppData({ foo: "baz" }, schema, {
|
|
79
|
+
// window: {
|
|
80
|
+
// location: {
|
|
81
|
+
// hash: `#data=${JSON.stringify({ a: 123 })}`,
|
|
82
|
+
// },
|
|
83
|
+
// },
|
|
84
|
+
// });
|
|
85
|
+
|
|
86
|
+
// expect(data).toEqual({
|
|
87
|
+
// theme: "light",
|
|
88
|
+
// viewType: "editable",
|
|
89
|
+
// initialState: { foo: "bar" },
|
|
90
|
+
// inspector: false,
|
|
91
|
+
// });
|
|
92
|
+
// });
|
|
93
|
+
|
|
94
|
+
it("gets AppData with function initial state", () => {
|
|
95
|
+
const initialState = () => ({ foo: "bar" });
|
|
96
|
+
|
|
97
|
+
const data = getAppData(initialState, undefined, {
|
|
98
|
+
window: { location: { hash: "" } },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(data).toEqual({
|
|
102
|
+
theme: "light",
|
|
103
|
+
viewType: "editable",
|
|
104
|
+
initialState: { foo: "bar" },
|
|
105
|
+
inspector: false,
|
|
106
|
+
});
|
|
107
|
+
});
|
package/src/noyaApp.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-restricted-globals */
|
|
2
2
|
import {
|
|
3
3
|
createOrCastValue,
|
|
4
|
+
findAllDefs,
|
|
4
5
|
localStorageSync,
|
|
5
6
|
MultiplayerUser,
|
|
6
7
|
SyncAdapter,
|
|
@@ -34,46 +35,56 @@ export function createDefaultAppData<State>(
|
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
export type GetAppDataOptions = {
|
|
39
|
+
window?: { location: { hash: string } };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function enforceSchema<State>(
|
|
43
|
+
initialState: State,
|
|
44
|
+
schema?: TSchema
|
|
45
|
+
): State {
|
|
46
|
+
return schema
|
|
47
|
+
? (createOrCastValue({
|
|
48
|
+
schema,
|
|
49
|
+
value: initialState,
|
|
50
|
+
defs: findAllDefs(schema),
|
|
51
|
+
}) as State)
|
|
52
|
+
: initialState;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function parseAppDataParameter(options?: GetAppDataOptions) {
|
|
56
|
+
const window = options?.window ?? globalThis;
|
|
57
|
+
const urlHash = (window.location.hash || "").replace(/^#/, "");
|
|
58
|
+
const params = new URLSearchParams(urlHash);
|
|
59
|
+
const data = params.get("data");
|
|
60
|
+
if (!data) return null;
|
|
42
61
|
try {
|
|
43
|
-
|
|
44
|
-
const params = new URLSearchParams(urlHash);
|
|
45
|
-
const data = params.get("data");
|
|
46
|
-
if (!data) {
|
|
47
|
-
return initialState ? createDefaultAppData(initialState) : undefined;
|
|
48
|
-
}
|
|
49
|
-
const parsed = JSON.parse(data);
|
|
50
|
-
if (!parsed.initialState) {
|
|
51
|
-
parsed.initialState = initialState;
|
|
52
|
-
}
|
|
53
|
-
return parsed;
|
|
62
|
+
return JSON.parse(data);
|
|
54
63
|
} catch (e) {
|
|
55
|
-
return
|
|
64
|
+
return null;
|
|
56
65
|
}
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
export function getAppData<State>(
|
|
69
|
+
initialState: State | (() => State),
|
|
70
|
+
schema?: TSchema,
|
|
71
|
+
options?: GetAppDataOptions
|
|
72
|
+
): AppData<State> {
|
|
73
|
+
const resolvedInitialState =
|
|
74
|
+
initialState instanceof Function ? initialState() : initialState;
|
|
62
75
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
initialState: S,
|
|
68
|
-
{ schema }: AppDataOptions = {}
|
|
69
|
-
) {
|
|
70
|
-
const appData = useMemo(() => {
|
|
71
|
-
return getAppData(
|
|
72
|
-
schema ? (createOrCastValue(schema, initialState) as S) : initialState
|
|
73
|
-
);
|
|
74
|
-
}, [initialState, schema]);
|
|
76
|
+
try {
|
|
77
|
+
const appData: AppData<unknown> =
|
|
78
|
+
parseAppDataParameter(options) ??
|
|
79
|
+
createDefaultAppData(resolvedInitialState);
|
|
75
80
|
|
|
76
|
-
|
|
81
|
+
return {
|
|
82
|
+
...appData,
|
|
83
|
+
initialState: enforceSchema(appData.initialState, schema) as State,
|
|
84
|
+
};
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return createDefaultAppData(enforceSchema(resolvedInitialState, schema));
|
|
87
|
+
}
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
export type IframeToHostMessage =
|
|
@@ -148,17 +159,17 @@ export function useNoyaAppState<S, M = void, E = void>(
|
|
|
148
159
|
localStorageKey?: string;
|
|
149
160
|
}
|
|
150
161
|
) {
|
|
151
|
-
//
|
|
152
|
-
const [
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
// Keep these stable after initial render
|
|
163
|
+
const [
|
|
164
|
+
{
|
|
165
|
+
multiplayerUrl,
|
|
166
|
+
inspector,
|
|
167
|
+
initialState: noyaAppInitialState,
|
|
168
|
+
theme,
|
|
169
|
+
viewType,
|
|
170
|
+
},
|
|
171
|
+
] = useState(() => {
|
|
172
|
+
return getAppData(initialState, options.schema);
|
|
162
173
|
});
|
|
163
174
|
|
|
164
175
|
const sync = useMemo((): SyncAdapter<S, M, E> => {
|