Skip to content

Commit 33b9efe

Browse files
committed
fix pluv integration issues
1 parent a8a7522 commit 33b9efe

7 files changed

Lines changed: 459 additions & 319 deletions

File tree

client/packages/lowcoder/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,12 @@
136136
"y-protocols": "^1.0.6",
137137
"y-websocket": "^3.0.0",
138138
"yjs": "^13.6.27",
139-
"zod": "^4.3.6"
139+
"zod": "^3.25.76"
140140
},
141141
"scripts": {
142142
"supportedBrowsers": "yarn dlx browserslist-useragent-regexp --allowHigherVersions '>0.2%,not dead,not op_mini all,chrome >=69'",
143143
"start": "REACT_APP_LOG_LEVEL=debug REACT_APP_ENV=local vite",
144+
"start:pluv": "node pluv-server.js",
144145
"build": "vite build && cp ../../VERSION ./build/VERSION",
145146
"preview": "vite preview",
146147
"prepare": "husky install"

client/packages/lowcoder/pluv-server.js

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,32 @@ app.get("/health", (_req, res) => {
9090
});
9191
});
9292

93-
// Auth endpoint — creates a JWT token for the requesting user
93+
// Webhook endpoint — pluv.io sends server events here
94+
app.post("/api/pluv/webhook", async (req, res) => {
95+
try {
96+
// Convert express req/res to a standard Request for ioServer.fetch
97+
const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
98+
const headers = new Headers();
99+
for (const [key, value] of Object.entries(req.headers)) {
100+
if (typeof value === "string") headers.set(key, value);
101+
}
102+
103+
const fetchReq = new Request(url, {
104+
method: req.method,
105+
headers,
106+
body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
107+
});
108+
109+
const fetchRes = await ioServer.fetch(fetchReq);
110+
const body = await fetchRes.text();
111+
res.status(fetchRes.status).send(body);
112+
} catch (err) {
113+
console.error("[pluv] Webhook error:", err);
114+
res.status(500).json({ error: "Webhook handling failed" });
115+
}
116+
});
117+
118+
// Auth endpoint — creates a JWT token for the requesting user (must be after webhook route)
94119
app.get("/api/auth/pluv", async (req, res) => {
95120
try {
96121
const room = req.query.room;
@@ -117,36 +142,11 @@ app.get("/api/auth/pluv", async (req, res) => {
117142
}
118143
});
119144

120-
// Webhook endpoint — pluv.io sends server events here
121-
app.all("/api/pluv", async (req, res) => {
122-
try {
123-
// Convert express req/res to a standard Request for ioServer.fetch
124-
const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
125-
const headers = new Headers();
126-
for (const [key, value] of Object.entries(req.headers)) {
127-
if (typeof value === "string") headers.set(key, value);
128-
}
129-
130-
const fetchReq = new Request(url, {
131-
method: req.method,
132-
headers,
133-
body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
134-
});
135-
136-
const fetchRes = await ioServer.fetch(fetchReq);
137-
const body = await fetchRes.text();
138-
res.status(fetchRes.status).send(body);
139-
} catch (err) {
140-
console.error("[pluv] Webhook error:", err);
141-
res.status(500).json({ error: "Webhook handling failed" });
142-
}
143-
});
144-
145145
// ── Start server ──────────────────────────────────────────────────────────
146146

147147
app.listen(PORT, HOST, () => {
148148
console.log(`\n Pluv Chat Server running on http://${HOST}:${PORT}`);
149149
console.log(` Auth endpoint: GET /api/auth/pluv?room=...&userId=...`);
150-
console.log(` Webhook endpoint: POST /api/pluv`);
150+
console.log(` Webhook endpoint: POST /api/pluv/webhook`);
151151
console.log(` Health check: GET /health\n`);
152152
});

client/packages/lowcoder/src/comps/comps/chatBoxComponentv2/chatBoxComp.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,13 @@ let ChatBoxV2Tmp = (function () {
147147
const userName = props.userName.value || "User";
148148
const roomName = `chatv2_${appId}`;
149149

150-
// Update the module-level config before pluv connects
150+
// Update the module-level config before pluv connects.
151+
// publicKey MUST be set here so resolvePluvPublicKey() returns the right
152+
// value when PluvRoomProvider opens its WebSocket connection.
151153
pluvConfig.userId = userId;
152154
pluvConfig.userName = userName;
153155
pluvConfig.authUrl = props.pluvAuthUrl || "/api/auth/pluv";
156+
pluvConfig.publicKey = props.pluvPublicKey || "";
154157

155158
return (
156159
<PluvRoomProvider

client/packages/lowcoder/src/comps/comps/chatBoxComponentv2/store/index.ts

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,21 @@ export type {
22
ChatMessage,
33
ChatRoom,
44
RoomMember,
5+
RoomInvite,
56
TypingUser,
6-
ChangeType,
7-
ChatStoreListener,
87
} from "./types";
9-
export { uid, LLM_BOT_AUTHOR_ID } from "./types";
10-
export { ChatStore } from "./ChatStore";
11-
12-
import { ChatStore } from "./ChatStore";
13-
14-
// ─── Reference-counted singleton cache ───────────────────────────────────────
15-
16-
interface CacheEntry {
17-
store: ChatStore;
18-
refCount: number;
19-
}
208

21-
const storeCache = new Map<string, CacheEntry>();
22-
23-
export function getChatStore(
24-
applicationId: string,
25-
wsUrl = "ws://localhost:3005",
26-
): ChatStore {
27-
const entry = storeCache.get(applicationId);
28-
if (entry) {
29-
entry.refCount++;
30-
return entry.store;
31-
}
32-
33-
const store = new ChatStore(applicationId, wsUrl);
34-
storeCache.set(applicationId, { store, refCount: 1 });
35-
return store;
36-
}
37-
38-
export function releaseChatStore(applicationId: string): void {
39-
const entry = storeCache.get(applicationId);
40-
if (!entry) return;
9+
export { uid, LLM_BOT_AUTHOR_ID } from "./types";
4110

42-
entry.refCount--;
43-
if (entry.refCount <= 0) {
44-
entry.store.destroy();
45-
storeCache.delete(applicationId);
46-
}
47-
}
11+
export {
12+
PluvRoomProvider,
13+
useStorage,
14+
useTransact,
15+
useMyPresence,
16+
useMyself,
17+
useOthers,
18+
useRoom,
19+
useConnection,
20+
useDoc,
21+
pluvConfig,
22+
} from "./pluvClient";
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { createClient } from "@pluv/client";
2+
import { yjs } from "@pluv/crdt-yjs";
3+
import { createBundle } from "@pluv/react";
4+
import { z } from "zod";
5+
6+
/**
7+
* Module-level config object updated by the component before connecting.
8+
* This allows dynamic auth without recreating the client.
9+
*
10+
* IMPORTANT: set pluvConfig.publicKey (from the component's pluvPublicKey prop)
11+
* BEFORE PluvRoomProvider mounts so the pluv client has the key at connection time.
12+
*/
13+
export const pluvConfig = {
14+
userId: "",
15+
userName: "",
16+
authUrl: "/api/auth/pluv",
17+
/** Populated from the component's "Public Key" property-panel field. */
18+
publicKey: "",
19+
};
20+
21+
/**
22+
* Returns the public key at call-time (lazy) so the component can set
23+
* pluvConfig.publicKey before pluv opens its first WebSocket connection.
24+
* Falls back to build-time env vars / globalThis for non-component usages.
25+
*/
26+
function resolvePluvPublicKey(): string {
27+
return (
28+
pluvConfig.publicKey ||
29+
(typeof globalThis !== "undefined"
30+
? (globalThis as any).__PLUV_PUBLIC_KEY__
31+
: "") ||
32+
(typeof import.meta !== "undefined"
33+
? (import.meta as any).env?.VITE_PLUV_PUBLIC_KEY
34+
: "") ||
35+
""
36+
);
37+
}
38+
39+
const client = createClient({
40+
authEndpoint: (({ room }: { room: string }) => {
41+
const params = new URLSearchParams({
42+
room,
43+
userId: pluvConfig.userId,
44+
userName: pluvConfig.userName,
45+
});
46+
return `${pluvConfig.authUrl}?${params}`;
47+
}) as any,
48+
publicKey: resolvePluvPublicKey as any,
49+
initialStorage: yjs.doc((t: any) => ({
50+
rooms: t.map("rooms", []),
51+
members: t.map("members", []),
52+
invites: t.map("invites", []),
53+
messages: t.map("messages", []),
54+
})),
55+
presence: z.object({
56+
typing: z
57+
.object({
58+
userId: z.string(),
59+
userName: z.string(),
60+
roomId: z.string(),
61+
})
62+
.nullable(),
63+
}),
64+
} as any);
65+
66+
export const {
67+
PluvRoomProvider,
68+
useStorage,
69+
useTransact,
70+
useMyPresence,
71+
useMyself,
72+
useOthers,
73+
useRoom,
74+
useConnection,
75+
useDoc,
76+
} = createBundle(client);

0 commit comments

Comments
 (0)