@noya-app/noya-multiplayer-react 0.1.84 → 0.1.86
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 +11 -11
- package/CHANGELOG.md +16 -0
- package/dist/index.bundle.js +37 -50
- package/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +64 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +59 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/NoyaStateContext.tsx +18 -16
- package/src/__tests__/useResourceUrl.test.tsx +92 -0
- package/src/index.ts +1 -0
- package/src/noyaApp.ts +30 -2
- package/src/singleton.tsx +13 -1
- package/src/useResourceUrl.ts +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-multiplayer-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.86",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"dev": "npm run build -- --watch"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@noya-app/state-manager": "0.1.
|
|
14
|
+
"@noya-app/state-manager": "0.1.68",
|
|
15
15
|
"@noya-app/emitter": "0.1.0",
|
|
16
16
|
"@noya-app/observable": "0.1.12",
|
|
17
17
|
"@noya-app/task-runner": "0.1.7",
|
package/src/NoyaStateContext.tsx
CHANGED
|
@@ -170,26 +170,28 @@ export const AnyNoyaStateContext = createContext<
|
|
|
170
170
|
AnyNoyaStateContextValue | undefined
|
|
171
171
|
>(undefined);
|
|
172
172
|
|
|
173
|
-
export function
|
|
173
|
+
export function useOptionalAnyNoyaStateContext() {
|
|
174
174
|
const value = useContext(AnyNoyaStateContext);
|
|
175
|
+
const bindings = getNoyaSingletonBindings();
|
|
175
176
|
|
|
176
|
-
if (
|
|
177
|
-
|
|
177
|
+
if (value) return value;
|
|
178
|
+
if (!bindings) return undefined;
|
|
178
179
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"useNoyaStateContext must be used within a NoyaStateProvider"
|
|
189
|
-
);
|
|
190
|
-
}
|
|
180
|
+
return {
|
|
181
|
+
noyaManager: getNoyaManagerSingleton(),
|
|
182
|
+
theme: bindings.theme,
|
|
183
|
+
viewType: bindings.viewType,
|
|
184
|
+
} as AnyNoyaStateContextValue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function useAnyNoyaStateContext() {
|
|
188
|
+
const value = useOptionalAnyNoyaStateContext();
|
|
191
189
|
|
|
192
|
-
return value;
|
|
190
|
+
if (value) return value;
|
|
191
|
+
|
|
192
|
+
throw new Error(
|
|
193
|
+
"useNoyaStateContext must be used within a NoyaStateProvider"
|
|
194
|
+
);
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
export function useAnyNoyaManager() {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Asset, Resource } from "@noya-app/noya-schemas";
|
|
2
|
+
import { NoyaManager } from "@noya-app/state-manager";
|
|
3
|
+
import { act, cleanup, renderHook } from "@testing-library/react";
|
|
4
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
5
|
+
import { PropsWithChildren } from "react";
|
|
6
|
+
import { AnyNoyaStateContext } from "../NoyaStateContext";
|
|
7
|
+
import { useResourceUrl } from "../useResourceUrl";
|
|
8
|
+
|
|
9
|
+
const asset = {
|
|
10
|
+
id: "asset-id",
|
|
11
|
+
stableId: "asset-stable-id",
|
|
12
|
+
url: "blob:http://localhost/image",
|
|
13
|
+
} as Asset;
|
|
14
|
+
|
|
15
|
+
const resource = {
|
|
16
|
+
accessibleByFileId: null,
|
|
17
|
+
accessibleByFileVersionId: null,
|
|
18
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
19
|
+
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
20
|
+
id: "resource-id",
|
|
21
|
+
stableId: "resource-stable-id",
|
|
22
|
+
path: "image.png",
|
|
23
|
+
type: "asset",
|
|
24
|
+
assetId: asset.id,
|
|
25
|
+
url: "/api/resources/resource-id",
|
|
26
|
+
} satisfies Resource;
|
|
27
|
+
|
|
28
|
+
afterEach(cleanup);
|
|
29
|
+
|
|
30
|
+
describe("useResourceUrl", () => {
|
|
31
|
+
it("resolves IDs and stable IDs and responds to data updates", () => {
|
|
32
|
+
const noyaManager = new NoyaManager(
|
|
33
|
+
{},
|
|
34
|
+
{
|
|
35
|
+
initialAssets: [asset],
|
|
36
|
+
initialResources: [resource],
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
const wrapper = ({ children }: PropsWithChildren) => (
|
|
40
|
+
<AnyNoyaStateContext.Provider
|
|
41
|
+
value={{ noyaManager, theme: "light", viewType: "editable" }}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</AnyNoyaStateContext.Provider>
|
|
45
|
+
);
|
|
46
|
+
const { result, rerender } = renderHook(
|
|
47
|
+
({ id }: { id: string | undefined }) => useResourceUrl(id),
|
|
48
|
+
{
|
|
49
|
+
initialProps: { id: resource.id },
|
|
50
|
+
wrapper,
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(result.current).toBe(asset.url);
|
|
55
|
+
|
|
56
|
+
rerender({ id: resource.stableId });
|
|
57
|
+
expect(result.current).toBe(asset.url);
|
|
58
|
+
|
|
59
|
+
act(() => {
|
|
60
|
+
noyaManager.assetManager.set([
|
|
61
|
+
{ ...asset, url: "blob:http://localhost/updated-image" },
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
expect(result.current).toBe("blob:http://localhost/updated-image");
|
|
65
|
+
|
|
66
|
+
act(() => {
|
|
67
|
+
noyaManager.assetManager.set([]);
|
|
68
|
+
});
|
|
69
|
+
expect(result.current).toBe(resource.url);
|
|
70
|
+
|
|
71
|
+
act(() => {
|
|
72
|
+
noyaManager.resourceManager.setResources([
|
|
73
|
+
{ ...resource, url: "/api/resources/updated-resource-id" },
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
expect(result.current).toBe("/api/resources/updated-resource-id");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns undefined when no resource ID is provided", () => {
|
|
80
|
+
const noyaManager = new NoyaManager({});
|
|
81
|
+
const wrapper = ({ children }: PropsWithChildren) => (
|
|
82
|
+
<AnyNoyaStateContext.Provider
|
|
83
|
+
value={{ noyaManager, theme: "light", viewType: "editable" }}
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</AnyNoyaStateContext.Provider>
|
|
87
|
+
);
|
|
88
|
+
const { result } = renderHook(() => useResourceUrl(undefined), { wrapper });
|
|
89
|
+
|
|
90
|
+
expect(result.current).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -9,5 +9,6 @@ export * from "./noyaApp";
|
|
|
9
9
|
export * from "./NoyaStateContext";
|
|
10
10
|
export * from "./singleton";
|
|
11
11
|
export * from "./useObservable";
|
|
12
|
+
export * from "./useResourceUrl";
|
|
12
13
|
export { WebSocketConnection } from "./WebSocketConnection";
|
|
13
14
|
export type { ConnectionEvent, ConnectionOptions } from "./WebSocketConnection";
|
package/src/noyaApp.ts
CHANGED
|
@@ -40,6 +40,8 @@ export type AppData<State> = {
|
|
|
40
40
|
clientId?: string;
|
|
41
41
|
clientName?: string;
|
|
42
42
|
clientImage?: string;
|
|
43
|
+
parentOrigin?: string;
|
|
44
|
+
enableDocumentAI?: boolean;
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
export function createDefaultAppData<State>(
|
|
@@ -150,6 +152,8 @@ export function getSyncAdapter<
|
|
|
150
152
|
policyAuthContext?: PolicyAuthContext;
|
|
151
153
|
serverScripts?: LocalStorageSyncOptions["serverScripts"];
|
|
152
154
|
git?: LocalStorageSyncOptions["git"];
|
|
155
|
+
parentOrigin?: string;
|
|
156
|
+
enableDocumentAI?: boolean;
|
|
153
157
|
}): SyncAdapter<S, M, E, MenuT, I> {
|
|
154
158
|
const {
|
|
155
159
|
multiplayerUrl,
|
|
@@ -158,10 +162,17 @@ export function getSyncAdapter<
|
|
|
158
162
|
policyAuthContext,
|
|
159
163
|
serverScripts,
|
|
160
164
|
git,
|
|
165
|
+
parentOrigin,
|
|
166
|
+
enableDocumentAI,
|
|
161
167
|
} = params ?? {};
|
|
162
168
|
|
|
163
169
|
return isEmbeddedApp()
|
|
164
|
-
? parentFrameSync<S, M, E, MenuT, I>({
|
|
170
|
+
? parentFrameSync<S, M, E, MenuT, I>({
|
|
171
|
+
debug,
|
|
172
|
+
policyAuthContext,
|
|
173
|
+
origin: parentOrigin,
|
|
174
|
+
enableDocumentAI,
|
|
175
|
+
})
|
|
165
176
|
: multiplayerUrl
|
|
166
177
|
? webSocketSync<S, M, E, MenuT, I>({ url: multiplayerUrl, debug })
|
|
167
178
|
: offlineStorageKey
|
|
@@ -258,6 +269,8 @@ export function useNoyaStateInternal<
|
|
|
258
269
|
userId,
|
|
259
270
|
isDemo,
|
|
260
271
|
access,
|
|
272
|
+
parentOrigin,
|
|
273
|
+
enableDocumentAI,
|
|
261
274
|
},
|
|
262
275
|
] = useState(() => {
|
|
263
276
|
return getAppData(initialState, options?.schema, {
|
|
@@ -289,6 +302,8 @@ export function useNoyaStateInternal<
|
|
|
289
302
|
policyAuthContext: basePolicyAuthContext,
|
|
290
303
|
serverScripts: options?.serverScripts,
|
|
291
304
|
git: options?.git,
|
|
305
|
+
parentOrigin,
|
|
306
|
+
enableDocumentAI,
|
|
292
307
|
});
|
|
293
308
|
}, [
|
|
294
309
|
multiplayerUrl,
|
|
@@ -297,10 +312,23 @@ export function useNoyaStateInternal<
|
|
|
297
312
|
basePolicyAuthContext,
|
|
298
313
|
options?.serverScripts,
|
|
299
314
|
options?.git,
|
|
315
|
+
parentOrigin,
|
|
316
|
+
enableDocumentAI,
|
|
300
317
|
]);
|
|
301
318
|
|
|
302
319
|
const [noyaManager] = useState(
|
|
303
|
-
() =>
|
|
320
|
+
() =>
|
|
321
|
+
new NoyaManager<S, M, E, MenuT, I>(noyaAppInitialState as S, {
|
|
322
|
+
...options,
|
|
323
|
+
resourceUrlBase:
|
|
324
|
+
options?.resourceUrlBase ??
|
|
325
|
+
parentOrigin ??
|
|
326
|
+
(typeof globalThis.location === "undefined"
|
|
327
|
+
? undefined
|
|
328
|
+
: globalThis.location.origin === "null"
|
|
329
|
+
? undefined
|
|
330
|
+
: globalThis.location.origin),
|
|
331
|
+
})
|
|
304
332
|
);
|
|
305
333
|
|
|
306
334
|
useEffect(() => {
|
package/src/singleton.tsx
CHANGED
|
@@ -89,7 +89,17 @@ export function initializeNoyaSingleton<
|
|
|
89
89
|
|
|
90
90
|
noyaManagerSingleton = new NoyaManager<S, M, E, MenuT, I>(
|
|
91
91
|
appData.initialState as S,
|
|
92
|
-
|
|
92
|
+
{
|
|
93
|
+
...managerOptions,
|
|
94
|
+
resourceUrlBase:
|
|
95
|
+
managerOptions.resourceUrlBase ??
|
|
96
|
+
appData.parentOrigin ??
|
|
97
|
+
(typeof globalThis.location === "undefined"
|
|
98
|
+
? undefined
|
|
99
|
+
: globalThis.location.origin === "null"
|
|
100
|
+
? undefined
|
|
101
|
+
: globalThis.location.origin),
|
|
102
|
+
}
|
|
93
103
|
);
|
|
94
104
|
|
|
95
105
|
if (appData.userId) {
|
|
@@ -109,6 +119,8 @@ export function initializeNoyaSingleton<
|
|
|
109
119
|
serverScripts: managerOptions?.serverScripts,
|
|
110
120
|
git: managerOptions?.git,
|
|
111
121
|
policyAuthContext,
|
|
122
|
+
parentOrigin: appData.parentOrigin,
|
|
123
|
+
enableDocumentAI: appData.enableDocumentAI,
|
|
112
124
|
});
|
|
113
125
|
|
|
114
126
|
// Activate sync immediately for the singleton
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getResourceUrl } from "@noya-app/state-manager";
|
|
4
|
+
import { useAnyNoyaStateContext } from "./NoyaStateContext";
|
|
5
|
+
import { useObservable } from "./useObservable";
|
|
6
|
+
|
|
7
|
+
export function useResourceUrl(
|
|
8
|
+
idOrStableId: string | undefined
|
|
9
|
+
): string | undefined {
|
|
10
|
+
const { noyaManager } = useAnyNoyaStateContext();
|
|
11
|
+
const resources = useObservable(
|
|
12
|
+
noyaManager.resourceManager.optimisticResources$
|
|
13
|
+
);
|
|
14
|
+
const assets = useObservable(noyaManager.assetManager.assets$);
|
|
15
|
+
|
|
16
|
+
if (!idOrStableId) return undefined;
|
|
17
|
+
|
|
18
|
+
return getResourceUrl(idOrStableId, resources, assets);
|
|
19
|
+
}
|