@noya-app/noya-multiplayer-react 0.1.49 → 0.1.51

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-multiplayer-react",
3
- "version": "0.1.49",
3
+ "version": "0.1.51",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -12,11 +12,11 @@
12
12
  "dev": "npm run build -- --watch"
13
13
  },
14
14
  "dependencies": {
15
- "@noya-app/state-manager": "0.1.38",
15
+ "@noya-app/state-manager": "0.1.40",
16
16
  "@noya-app/emitter": "0.1.0",
17
17
  "@noya-app/observable": "0.1.5",
18
18
  "@noya-app/task-runner": "0.1.5",
19
- "@noya-app/react-utils": "0.1.14",
19
+ "@noya-app/react-utils": "0.1.16",
20
20
  "react-inspector": "6.0.2"
21
21
  },
22
22
  "peerDependencies": {
@@ -1,4 +1,5 @@
1
1
  import { MenuItem } from "@noya-app/noya-designsystem";
2
+ import { Input, OutputTransform } from "@noya-app/noya-schemas";
2
3
  import {
3
4
  GetAtPath,
4
5
  Observable,
@@ -7,6 +8,7 @@ import {
7
8
  } from "@noya-app/observable";
8
9
  import {
9
10
  ConnectedUsersManager,
11
+ CreateParams,
10
12
  EphemeralUserDataManager,
11
13
  MultiplayerStateManager,
12
14
  NoyaManager,
@@ -134,6 +136,21 @@ export function useSecret(name: string) {
134
136
  );
135
137
  }
136
138
 
139
+ export function useInputs() {
140
+ const { noyaManager } = useAnyNoyaStateContext();
141
+ return useObservable(noyaManager.ioManager.inputs$);
142
+ }
143
+
144
+ export function useOutputTransforms() {
145
+ const { noyaManager } = useAnyNoyaStateContext();
146
+ return useObservable(noyaManager.ioManager.outputTransforms$);
147
+ }
148
+
149
+ export function useIOManager() {
150
+ const { noyaManager } = useAnyNoyaStateContext();
151
+ return noyaManager.ioManager;
152
+ }
153
+
137
154
  export function useIsInitialized() {
138
155
  const { noyaManager, viewType } = useAnyNoyaStateContext();
139
156
 
@@ -155,9 +172,9 @@ export function useIsInitialized() {
155
172
  return useObservable(isInitializedObservable);
156
173
  }
157
174
 
158
- export function useWorkflowManager() {
175
+ export function usePipelineManager() {
159
176
  const { noyaManager } = useAnyNoyaStateContext();
160
- return noyaManager.workflowManager;
177
+ return noyaManager.pipelineManager;
161
178
  }
162
179
 
163
180
  export const ConnectedUsersContext = createContext<
@@ -201,12 +218,16 @@ export function createNoyaContext<
201
218
  >({
202
219
  schema,
203
220
  mergeHistoryEntries,
221
+ outputTransforms,
222
+ inputs,
204
223
  }: {
205
224
  schema: Schema;
206
225
  mergeHistoryEntries?: StateManagerOptions<
207
226
  Static<Schema>,
208
227
  M
209
228
  >["mergeHistoryEntries"];
229
+ outputTransforms?: CreateParams<OutputTransform>[];
230
+ inputs?: CreateParams<Input>[];
210
231
  }) {
211
232
  type S = Static<Schema>;
212
233
 
@@ -227,9 +248,11 @@ export function createNoyaContext<
227
248
  const [, , { noyaManager, theme, viewType }] = useNoyaState<S, M, E, MenuT>(
228
249
  initialState,
229
250
  {
230
- ...(options as any),
231
251
  schema,
232
252
  mergeHistoryEntries,
253
+ outputTransforms,
254
+ inputs,
255
+ ...(options as any),
233
256
  }
234
257
  );
235
258
 
@@ -32,9 +32,7 @@ export type ConnectionOptions<State> = {
32
32
 
33
33
  export class WebSocketConnection<State> {
34
34
  private ws: ReconnectingWebSocket;
35
- private pingCount = 0;
36
- private pendingPingId?: string;
37
- private intervalId?: NodeJS.Timer;
35
+ private closedForever = false;
38
36
 
39
37
  constructor(
40
38
  private url: URL,
@@ -50,38 +48,13 @@ export class WebSocketConnection<State> {
50
48
  }
51
49
 
52
50
  async connect() {
53
- this.ws.connect(this.url.toString());
54
-
55
- // Start ping interval
56
- this.intervalId = setInterval(() => {
57
- if (this.ws.state !== "OPEN") return;
58
-
59
- // A response to a previous ping hasn't been received, so we'll reset the connection
60
- if (this.pendingPingId) {
61
- this.ws.close();
62
- return;
63
- }
64
-
65
- const pingId = (this.pingCount++).toString();
51
+ if (this.closedForever) return;
66
52
 
67
- const message: ClientToServerMessage<State> = {
68
- type: "ping",
69
- id: pingId,
70
- };
71
-
72
- this.pendingPingId = message.id;
73
-
74
- this.options.onConnectionEvent?.({
75
- type: "send",
76
- message,
77
- });
78
-
79
- this.ws.send(JSON.stringify(message));
80
- }, 10000);
53
+ this.ws.connect(this.url.toString());
81
54
  }
82
55
 
83
56
  private handleOpen = () => {
84
- this.pendingPingId = undefined;
57
+ if (this.closedForever) return;
85
58
 
86
59
  if (this.options.debug) {
87
60
  console.info("ws connected");
@@ -94,6 +67,8 @@ export class WebSocketConnection<State> {
94
67
  };
95
68
 
96
69
  private handleMessage = (event: MessageEvent) => {
70
+ if (this.closedForever) return;
71
+
97
72
  if (this.options.debug) {
98
73
  console.info("ws receiving message ", event.data);
99
74
  }
@@ -104,14 +79,10 @@ export class WebSocketConnection<State> {
104
79
  type: "receive",
105
80
  message: parsed,
106
81
  });
107
-
108
- if (parsed.type === "pong" && parsed.id === this.pendingPingId) {
109
- this.pendingPingId = undefined;
110
- }
111
82
  };
112
83
 
113
84
  private handleClose = () => {
114
- this.pendingPingId = undefined;
85
+ if (this.closedForever) return;
115
86
 
116
87
  if (this.options.debug) {
117
88
  console.info("ws disconnected");
@@ -124,6 +95,8 @@ export class WebSocketConnection<State> {
124
95
  };
125
96
 
126
97
  send(message: ClientToServerMessage<State>) {
98
+ if (this.closedForever) return;
99
+
127
100
  if (this.options.debug) {
128
101
  console.info("ws sending message", message);
129
102
  }
@@ -137,10 +110,15 @@ export class WebSocketConnection<State> {
137
110
  }
138
111
 
139
112
  close() {
140
- if (this.intervalId) {
141
- clearInterval(this.intervalId);
142
- }
113
+ if (this.closedForever) return;
114
+
115
+ this.closedForever = true;
143
116
  this.ws.shutdown();
117
+
118
+ // remove all listeners
119
+ this.ws.listeners.forEach((listener) => {
120
+ this.ws.removeListener(listener);
121
+ });
144
122
  }
145
123
 
146
124
  addListener(listener: (state: ReconnectingWebSocketState) => void) {
package/src/hooks.ts CHANGED
@@ -166,7 +166,7 @@ export function useMultiplayerState<
166
166
  assetManager: typeof noyaManager.assetManager;
167
167
  aiManager: typeof noyaManager.aiManager;
168
168
  rpcManager: typeof noyaManager.rpcManager;
169
- workflowManager: typeof noyaManager.workflowManager;
169
+ pipelineManager: typeof noyaManager.pipelineManager;
170
170
  secretManager: typeof noyaManager.secretManager;
171
171
  menuItemsManager: typeof noyaManager.menuManager;
172
172
  createAsset: typeof noyaManager.assetManager.create;
@@ -185,7 +185,7 @@ export function useMultiplayerState<
185
185
  assetManager: noyaManager.assetManager,
186
186
  aiManager: noyaManager.aiManager,
187
187
  rpcManager: noyaManager.rpcManager,
188
- workflowManager: noyaManager.workflowManager,
188
+ pipelineManager: noyaManager.pipelineManager,
189
189
  secretManager: noyaManager.secretManager,
190
190
  menuItemsManager: noyaManager.menuManager,
191
191
  createAsset: noyaManager.assetManager.create,
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { Input, OutputTransform } from "@noya-app/noya-schemas";
3
4
  import { memoGeneric } from "@noya-app/react-utils";
4
5
  import { ExtendedPathKey, NoyaManager } from "@noya-app/state-manager";
5
6
  import React, {
@@ -266,10 +267,13 @@ export const StateInspector = memoGeneric(function StateInspector<
266
267
  ephemeralUserDataManager,
267
268
  connectionEventManager,
268
269
  taskManager,
270
+ ioManager,
269
271
  } = noyaManager;
270
272
 
271
273
  const [didMount, setDidMount] = React.useState(false);
272
274
  const tasks = useObservable(taskManager.tasks$);
275
+ const inputs = useObservable(ioManager.inputs$);
276
+ const outputTransforms = useObservable(ioManager.outputTransforms$);
273
277
 
274
278
  useLayoutEffect(() => {
275
279
  setDidMount(true);
@@ -314,6 +318,14 @@ export const StateInspector = memoGeneric(function StateInspector<
314
318
  "noya-multiplayer-react-show-secrets",
315
319
  false
316
320
  );
321
+ const [showInputs, setShowInputs] = useLocalStorageState(
322
+ "noya-multiplayer-react-show-inputs",
323
+ false
324
+ );
325
+ const [showOutputTransforms, setShowOutputTransforms] = useLocalStorageState(
326
+ "noya-multiplayer-react-show-output-transforms",
327
+ false
328
+ );
317
329
 
318
330
  const connectionEvents = useObservable(connectionEventManager.events$);
319
331
 
@@ -336,6 +348,10 @@ export const StateInspector = memoGeneric(function StateInspector<
336
348
  const connectedUsers = useObservable(connectedUsersManager.connectedUsers$);
337
349
  const userId = useObservable(connectedUsersManager.currentUserId$);
338
350
  const state = useObservable(multiplayerStateManager.optimisticState$);
351
+ const inputsInitialized = useObservable(ioManager.inputsInitialized$);
352
+ const outputTransformsInitialized = useObservable(
353
+ ioManager.outputTransformsInitialized$
354
+ );
339
355
 
340
356
  useEffect(() => {
341
357
  if (historyContainerRef.current) {
@@ -759,35 +775,6 @@ export const StateInspector = memoGeneric(function StateInspector<
759
775
  ))}
760
776
  </div>
761
777
  </DisclosureSection>
762
- <DisclosureSection
763
- title="Tasks"
764
- colorScheme={colorScheme}
765
- open={showTasks}
766
- setOpen={setShowTasks}
767
- >
768
- <div style={styles.sectionInner}>
769
- {tasks?.map((task) => (
770
- <InspectorRow
771
- key={task.id}
772
- colorScheme={colorScheme}
773
- style={{
774
- backgroundColor:
775
- task.status === "done"
776
- ? "rgba(0,255,0,0.2)"
777
- : task.status === "error"
778
- ? "rgba(255,0,0,0.2)"
779
- : undefined,
780
- }}
781
- >
782
- <ObjectInspector
783
- name={task.name}
784
- data={task.payload}
785
- theme={theme}
786
- />
787
- </InspectorRow>
788
- ))}
789
- </div>
790
- </DisclosureSection>
791
778
  <DisclosureSection
792
779
  title={
793
780
  <TitleLabel>
@@ -826,6 +813,101 @@ export const StateInspector = memoGeneric(function StateInspector<
826
813
  </InspectorRow>
827
814
  ))}
828
815
  </div>
816
+ </DisclosureSection>{" "}
817
+ <DisclosureSection
818
+ open={showInputs}
819
+ setOpen={setShowInputs}
820
+ title={
821
+ <TitleLabel>
822
+ Inputs
823
+ <ColoredDot type={inputsInitialized ? "success" : "error"} />
824
+ </TitleLabel>
825
+ }
826
+ colorScheme={colorScheme}
827
+ >
828
+ <div style={styles.sectionInner}>
829
+ {inputs?.map((input: Input) => (
830
+ <InspectorRow key={input.id} colorScheme={colorScheme}>
831
+ <ObjectInspector data={input} theme={theme} />
832
+ </InspectorRow>
833
+ ))}
834
+ {!inputs?.length && (
835
+ <div
836
+ style={{
837
+ padding: "12px",
838
+ fontSize: "12px",
839
+ display: "flex",
840
+ flexDirection: "column",
841
+ gap: "4px",
842
+ }}
843
+ >
844
+ <span>No inputs</span>
845
+ </div>
846
+ )}
847
+ </div>
848
+ </DisclosureSection>
849
+ <DisclosureSection
850
+ open={showOutputTransforms}
851
+ setOpen={setShowOutputTransforms}
852
+ title={
853
+ <TitleLabel>
854
+ Output Transforms
855
+ <ColoredDot
856
+ type={outputTransformsInitialized ? "success" : "error"}
857
+ />
858
+ </TitleLabel>
859
+ }
860
+ colorScheme={colorScheme}
861
+ >
862
+ <div style={styles.sectionInner}>
863
+ {outputTransforms?.map((transform: OutputTransform) => (
864
+ <InspectorRow key={transform.id} colorScheme={colorScheme}>
865
+ <ObjectInspector data={transform} theme={theme} />
866
+ </InspectorRow>
867
+ ))}
868
+ {!outputTransforms?.length && (
869
+ <div
870
+ style={{
871
+ padding: "12px",
872
+ fontSize: "12px",
873
+ display: "flex",
874
+ flexDirection: "column",
875
+ gap: "4px",
876
+ }}
877
+ >
878
+ <span>No output transforms</span>
879
+ </div>
880
+ )}
881
+ </div>
882
+ </DisclosureSection>
883
+ <DisclosureSection
884
+ title="Tasks"
885
+ colorScheme={colorScheme}
886
+ open={showTasks}
887
+ setOpen={setShowTasks}
888
+ >
889
+ <div style={styles.sectionInner}>
890
+ {tasks?.map((task) => (
891
+ <InspectorRow
892
+ key={task.id}
893
+ colorScheme={colorScheme}
894
+ style={{
895
+ backgroundColor:
896
+ task.status === "done"
897
+ ? "rgba(0,255,0,0.2)"
898
+ : task.status === "error"
899
+ ? "rgba(255,0,0,0.2)"
900
+ : undefined,
901
+ }}
902
+ >
903
+ <ObjectInspector
904
+ name={task.name}
905
+ data={task.payload}
906
+ theme={theme}
907
+ />
908
+ </InspectorRow>
909
+ ))}
910
+ </div>
829
911
  </DisclosureSection>
830
912
  <DisclosureSection
831
913
  open={showEphemeral}
package/src/noyaApp.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable no-restricted-globals */
2
+ import { findAllDefs } from "@noya-app/noya-schemas";
2
3
  import {
3
4
  createOrCastValue,
4
- findAllDefs,
5
5
  isEmbeddedApp,
6
6
  localStorageSync,
7
7
  parentFrameSync,