Skip to content

Commit 5f87866

Browse files
committed
fix hocuspocus integration for chatbox issues
1 parent 44779a6 commit 5f87866

File tree

11 files changed

+139
-105
lines changed

11 files changed

+139
-105
lines changed

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"shelljs": "^0.8.5",
5757
"svgo": "^3.0.0",
5858
"ts-node": "^10.4.0",
59-
"typescript": "^4.8.4",
59+
"typescript": "^5.6.2",
6060
"whatwg-fetch": "^3.6.2"
6161
},
6262
"lint-staged": {

client/packages/lowcoder/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
"http-proxy-middleware": "^2.0.6",
160160
"rollup-plugin-terser": "^7.0.2",
161161
"rollup-plugin-visualizer": "^5.9.2",
162-
"typescript": "^4.8.4",
162+
"typescript": "^5.6.2",
163163
"vite": "^4.5.5",
164164
"vite-plugin-checker": "^0.5.1",
165165
"vite-plugin-dynamic-import": "^1.5.0",

client/packages/lowcoder/src/app-env.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ declare var REACT_APP_ENV: string;
4141
declare var REACT_APP_BUILD_ID: string;
4242
declare var REACT_APP_LOG_LEVEL: string;
4343
declare var REACT_APP_SERVER_IPS: string;
44+
declare var REACT_APP_HOCUSPOCUS_URL: string;
45+
declare var REACT_APP_HOCUSPOCUS_SECRET: string;
4446
declare var REACT_APP_BUNDLE_TYPE: "sdk" | "app";
4547
declare var REACT_APP_DISABLE_JS_SANDBOX: string;
4648
declare var REACT_APP_BUNDLE_BUILTIN_PLUGIN: string;

client/packages/lowcoder/src/base/codeEditor/completion/exposingCompletionSource.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class ExposingCompletionSource extends CompletionSource {
1919
return null;
2020
}
2121
const matchPath = context.matchBefore(
22-
/(?:[A-Za-z_$][\w$]*(?:\[\s*(?:\d+|(["'])(?:[^\1\\]|\\.)*?\1)\s*\])*\.)*(?:[A-Za-z_$][\w$]*)?/
22+
/(?:[A-Za-z_$][\w$]*(?:\[\s*(?:\d+|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")\s*\])*\.)*(?:[A-Za-z_$][\w$]*)?/
2323
);
2424
if (!matchPath) {
2525
return null;

client/packages/lowcoder/src/components/StepModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export interface StepModalProps extends CustomModalProps {
2525
export default function StepModal(props: StepModalProps) {
2626
const { steps, activeStepKey, onStepChange, ...modalProps } = props;
2727
const [current, setCurrent] = useState(steps[0]?.key);
28-
const currentStepIndex = steps.findIndex((i) => i.key === activeStepKey ?? current);
28+
const currentStepKey = activeStepKey ?? current;
29+
const currentStepIndex = steps.findIndex((i) => i.key === currentStepKey);
2930
const currentStep = currentStepIndex >= 0 ? steps[currentStepIndex] : null;
3031

3132
const handleChangeStep = (key: string) => {

client/packages/lowcoder/src/comps/comps/chatBoxComponentv2/store/hocuspocusClient.ts renamed to client/packages/lowcoder/src/comps/comps/chatBoxComponentv2/store/hocuspocusClient.tsx

Lines changed: 92 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,24 @@ import {
1414

1515
// ── Environment config ───────────────────────────────────────────────────────
1616

17-
const WS_URL: string =
18-
(typeof import.meta !== "undefined" &&
19-
(import.meta as any).env?.VITE_HOCUSPOCUS_URL) ||
20-
(typeof globalThis !== "undefined" &&
21-
(globalThis as any).__HOCUSPOCUS_URL__) ||
22-
"ws://localhost:3006";
23-
24-
const AUTH_TOKEN: string =
25-
(typeof import.meta !== "undefined" &&
26-
(import.meta as any).env?.VITE_HOCUSPOCUS_SECRET) ||
27-
"";
17+
const WS_URL = REACT_APP_HOCUSPOCUS_URL || "ws://localhost:3006";
2818

29-
type ConnectionState = "connecting" | "open" | "closed";
19+
const AUTH_TOKEN = REACT_APP_HOCUSPOCUS_SECRET || "";
3020

31-
function mapWebSocketStatus(status?: string): ConnectionState {
32-
if (status === WebSocketStatus.Connected) {
33-
return "open";
34-
}
21+
type ConnectionState = "connecting" | "open" | "closed";
3522

36-
if (status === WebSocketStatus.Connecting) {
37-
return "connecting";
23+
function mapWebSocketStatus(status?: WebSocketStatus): ConnectionState {
24+
switch (status) {
25+
case WebSocketStatus.Connected:
26+
return "open";
27+
case WebSocketStatus.Connecting:
28+
return "connecting";
29+
default:
30+
return "closed";
3831
}
39-
40-
return "closed";
4132
}
4233

43-
// ── Context ──────────────────────────────────────────────────────────────────
34+
// ── Context ────────────────────────────────────────────────────────────────────
4435

4536
interface HocuspocusContextValue {
4637
provider: HocuspocusProvider;
@@ -65,21 +56,19 @@ interface HocuspocusRoomProviderProps {
6556
/** Document/room name — all clients with the same name share state. */
6657
room: string;
6758
/** Initial presence fields to set on connect. */
68-
initialPresence?: Record<string, any>;
59+
initialPresence?: Record<string, unknown>;
6960
/** Called when auth fails. */
70-
onAuthenticationFailed?: (error: any) => void;
61+
onAuthenticationFailed?: (error: unknown) => void;
7162
children: React.ReactNode;
7263
}
7364

74-
function HocuspocusRoomProviderInner({
65+
export function HocuspocusRoomProvider({
7566
room,
7667
initialPresence,
7768
onAuthenticationFailed,
7869
children,
7970
}: HocuspocusRoomProviderProps) {
80-
const stableInitialPresence = useMemo(() => initialPresence ?? null, [
81-
JSON.stringify(initialPresence ?? null),
82-
]);
71+
const initialPresenceKey = JSON.stringify(initialPresence ?? null);
8372

8473
const value = useMemo(() => {
8574
const doc = new Y.Doc();
@@ -88,7 +77,7 @@ function HocuspocusRoomProviderInner({
8877
name: room,
8978
document: doc,
9079
token: AUTH_TOKEN || undefined,
91-
onAuthenticationFailed: (data: any) => {
80+
onAuthenticationFailed: (data: unknown) => {
9281
console.error("[Hocuspocus] Auth failed:", data);
9382
onAuthenticationFailed?.(data);
9483
},
@@ -100,10 +89,10 @@ function HocuspocusRoomProviderInner({
10089
}, [room]);
10190

10291
useEffect(() => {
103-
if (stableInitialPresence) {
104-
value.provider.setAwarenessField("user", stableInitialPresence);
92+
if (initialPresenceKey !== "null") {
93+
value.provider.setAwarenessField("user", JSON.parse(initialPresenceKey));
10594
}
106-
}, [stableInitialPresence, value.provider]);
95+
}, [initialPresenceKey, value.provider]);
10796

10897
useEffect(() => {
10998
return () => {
@@ -112,119 +101,127 @@ function HocuspocusRoomProviderInner({
112101
};
113102
}, [value]);
114103

115-
return React.createElement(HocuspocusContext.Provider, { value }, children);
104+
return (
105+
<HocuspocusContext.Provider value={value}>
106+
{children}
107+
</HocuspocusContext.Provider>
108+
);
116109
}
117110

118-
export const HocuspocusRoomProvider = HocuspocusRoomProviderInner;
119-
120111
// ── Hook: useConnection ──────────────────────────────────────────────────────
121112

122113
export function useConnection(): { state: ConnectionState } {
123114
const { provider } = useHocuspocusContext();
124-
const [state, setState] = useState<ConnectionState>(() =>
125-
mapWebSocketStatus(provider.configuration.websocketProvider.status),
115+
116+
const getStatus = useCallback(
117+
() => mapWebSocketStatus(provider.configuration.websocketProvider.status),
118+
[provider],
126119
);
127120

121+
const [state, setState] = useState<ConnectionState>(getStatus);
122+
128123
useEffect(() => {
129-
const sync = () => {
130-
setState(mapWebSocketStatus(provider.configuration.websocketProvider.status));
131-
};
124+
// Sync immediately when provider changes
125+
setState(getStatus());
132126

133-
const onStatus = () => {
134-
sync();
127+
const handleStatus = ({ status }: { status: WebSocketStatus }) => {
128+
setState(mapWebSocketStatus(status));
135129
};
136130

137-
sync();
138-
provider.on("status", onStatus);
139-
provider.on("connect", onStatus);
140-
provider.on("disconnect", onStatus);
131+
provider.on("status", handleStatus);
141132

142133
return () => {
143-
provider.off("status", onStatus);
144-
provider.off("connect", onStatus);
145-
provider.off("disconnect", onStatus);
134+
provider.off("status", handleStatus);
146135
};
147-
}, [provider]);
136+
}, [provider, getStatus]);
148137

149138
return { state };
150139
}
151140

152141
// ── Hook: useMyPresence ──────────────────────────────────────────────────────
153142

154143
export function useMyPresence(): [
155-
Record<string, any>,
156-
(fields: Record<string, any>) => void,
144+
Record<string, unknown>,
145+
(fields: Record<string, unknown>) => void,
157146
] {
158147
const { provider } = useHocuspocusContext();
159148

160-
const [presence, setPresenceState] = useState<Record<string, any>>(
149+
const getPresence = useCallback(
161150
() => provider.awareness?.getLocalState()?.user ?? {},
151+
[provider],
162152
);
163153

164-
const setPresence = useCallback(
165-
(fields: Record<string, any>) => {
154+
const [presence, setPresence] = useState<Record<string, unknown>>(getPresence);
155+
156+
const updatePresence = useCallback(
157+
(fields: Record<string, unknown>) => {
166158
provider.setAwarenessField("user", fields);
167-
setPresenceState(fields);
159+
setPresence(fields);
168160
},
169161
[provider],
170162
);
171163

172164
useEffect(() => {
173165
const awareness = provider.awareness;
174-
if (!awareness) {
175-
return;
176-
}
166+
if (!awareness) return;
177167

178-
const sync = () => {
179-
setPresenceState(awareness.getLocalState()?.user ?? {});
168+
const handleChange = () => {
169+
setPresence(getPresence());
180170
};
181171

182-
sync();
183-
awareness.on("change", sync);
172+
awareness.on("change", handleChange);
184173

185174
return () => {
186-
awareness.off("change", sync);
175+
awareness.off("change", handleChange);
187176
};
188-
}, [provider]);
177+
}, [provider, getPresence]);
189178

190-
return [presence, setPresence];
179+
return [presence, updatePresence];
191180
}
192181

193182
// ── Hook: useOthers ──────────────────────────────────────────────────────────
194183

195184
interface OtherUser {
196185
clientId: number;
197-
[key: string]: any;
186+
presence: Record<string, unknown>;
198187
}
199188

200189
export function useOthers(): OtherUser[] {
201190
const { provider } = useHocuspocusContext();
202-
const [others, setOthers] = useState<OtherUser[]>([]);
191+
192+
const getOthers = useCallback((): OtherUser[] => {
193+
const awareness = provider.awareness;
194+
if (!awareness) return [];
195+
196+
const localClientId = awareness.clientID;
197+
const others: OtherUser[] = [];
198+
199+
awareness.getStates().forEach((state: Record<string, unknown>, clientId: number) => {
200+
if (clientId === localClientId) return;
201+
if (state?.user) {
202+
others.push({ clientId, presence: state.user as Record<string, unknown> });
203+
}
204+
});
205+
206+
return others;
207+
}, [provider]);
208+
209+
const [others, setOthers] = useState<OtherUser[]>(getOthers);
203210

204211
useEffect(() => {
205212
const awareness = provider.awareness;
206213
if (!awareness) return;
207214

208-
const update = () => {
209-
const localClientId = awareness.clientID;
210-
const states: OtherUser[] = [];
211-
awareness.getStates().forEach((state: Record<string, any>, clientId: number) => {
212-
if (clientId === localClientId) return;
213-
if (state?.user) {
214-
states.push({ clientId, presence: state.user });
215-
}
216-
});
217-
setOthers(states);
215+
const handleChange = () => {
216+
setOthers(getOthers());
218217
};
219218

220-
update();
221-
awareness.on("change", update);
222-
awareness.on("update", update);
219+
awareness.on("change", handleChange);
220+
223221
return () => {
224-
awareness.off("change", update);
225-
awareness.off("update", update);
222+
awareness.off("change", handleChange);
226223
};
227-
}, [provider]);
224+
}, [provider, getOthers]);
228225

229226
return others;
230227
}
@@ -235,28 +232,31 @@ export function useOthers(): OtherUser[] {
235232

236233
export function useStorage(
237234
mapName: string,
238-
): [Record<string, any> | null, Y.Map<any> | null] {
235+
): [Record<string, unknown> | null, Y.Map<unknown> | null] {
239236
const { doc } = useHocuspocusContext();
240237

241238
const yMap = useMemo(() => doc.getMap(mapName), [doc, mapName]);
242239

243-
const [snapshot, setSnapshot] = useState<Record<string, any> | null>(() =>
244-
yMap ? Object.fromEntries(yMap.entries()) : null,
240+
const getSnapshot = useCallback(
241+
() => (yMap ? Object.fromEntries(yMap.entries()) : null),
242+
[yMap],
245243
);
246244

245+
const [snapshot, setSnapshot] = useState<Record<string, unknown> | null>(getSnapshot);
246+
247247
useEffect(() => {
248248
if (!yMap) return;
249249

250-
const sync = () => {
251-
setSnapshot(Object.fromEntries(yMap.entries()));
250+
const handleChange = () => {
251+
setSnapshot(getSnapshot());
252252
};
253253

254-
sync();
255-
yMap.observeDeep(sync);
254+
yMap.observe(handleChange);
255+
256256
return () => {
257-
yMap.unobserveDeep(sync);
257+
yMap.unobserve(handleChange);
258258
};
259-
}, [yMap]);
259+
}, [yMap, getSnapshot]);
260260

261261
return [snapshot, yMap];
262262
}

client/packages/lowcoder/src/comps/comps/dateComp/dateComp.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ const timeValidationFields = (children: CommonChildrenType, dateType: PickerMode
139139
function validate(
140140
props: RecordConstructorToView<typeof validationChildren> & {
141141
value: { value: string };
142-
showTime: boolean;
143142
}
144143
): {
145144
validateStatus: "success" | "warning" | "error";
@@ -763,7 +762,7 @@ export let DateRangeComp = withExposingConfigs(dateRangeControl, [
763762
depsConfig({
764763
name: "invalid",
765764
desc: trans("export.invalidDesc"),
766-
depKeys: ["start", "end", "required", "minTime", "maxTime", "minDate", "maxDate", "customRule"],
765+
depKeys: ["start", "end", "showValidationWhenEmpty", "required", "minTime", "maxTime", "minDate", "maxDate", "customRule"],
767766
func: (input) =>
768767
validate({
769768
...input,
@@ -818,4 +817,4 @@ DateRangeComp = withMethodExposing(DateRangeComp, [
818817
comp.children.end.getView().onChange(data.end);
819818
},
820819
},
821-
]);
820+
]);

0 commit comments

Comments
 (0)