@fluxy-chat/create-fluxy-chat 0.5.13 → 0.5.19
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/CHANGELOG.md +39 -0
- package/dist/index.js +9 -4
- package/package.json +1 -1
- package/readme.md +2 -1
- package/templates/basic/package.json +1 -1
- package/templates/comments-board/package.json +1 -1
- package/templates/deal-room/package.json +1 -1
- package/templates/deal-room/src/App.tsx +17 -1
- package/templates/discord/package.json +1 -1
- package/templates/draw/package.json +1 -1
- package/templates/fleet-panel/package.json +1 -1
- package/templates/full/README.md +14 -4
- package/templates/full/fluxy.hosted.json +15 -0
- package/templates/full/package.json +4 -3
- package/templates/full/scripts/fluxy-deploy.mjs +53 -0
- package/templates/full/scripts/fluxy-setup.mjs +31 -7
- package/templates/full/src/App.tsx +14 -0
- package/templates/full/src/session.ts +7 -2
- package/templates/full/src/vite-env.d.ts +2 -1
- package/templates/game-tick/package.json +1 -1
- package/templates/iot-panel/package.json +1 -1
- package/templates/javascript-live-cursors/package.json +1 -1
- package/templates/live-cursors/.env.example +8 -5
- package/templates/live-cursors/package.json +2 -2
- package/templates/live-cursors/src/App.tsx +19 -2
- package/templates/live-cursors/src/vite-env.d.ts +1 -0
- package/templates/live-cursors-chat/package.json +1 -1
- package/templates/minimal/.env.example +2 -1
- package/templates/minimal/package.json +2 -2
- package/templates/minimal/src/App.tsx +1 -0
- package/templates/polls/.env.example +13 -0
- package/templates/polls/README.md +13 -0
- package/templates/polls/index.html +12 -0
- package/templates/polls/package.json +24 -0
- package/templates/polls/src/App.tsx +135 -0
- package/templates/polls/src/index.css +85 -0
- package/templates/polls/src/main.tsx +10 -0
- package/templates/polls/src/session.ts +73 -0
- package/templates/polls/src/vite-env.d.ts +13 -0
- package/templates/polls/tsconfig.json +21 -0
- package/templates/polls/vite.config.ts +7 -0
- package/templates/react/.env.example +9 -5
- package/templates/react/README.md +7 -32
- package/templates/react/package.json +2 -2
- package/templates/react/src/App.tsx +31 -23
- package/templates/react/src/vite-env.d.ts +1 -0
- package/templates/slack/package.json +1 -1
- package/templates/telegram/package.json +1 -1
- package/templates/tiptap-room/package.json +1 -1
- package/templates/voice-stage/package.json +1 -1
- package/templates/war-room/package.json +1 -1
- package/templates/whiteboard/package.json +1 -1
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"preview": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@fluxy-chat/ui-kit": "^0.1.
|
|
12
|
+
"@fluxy-chat/ui-kit": "^0.1.4",
|
|
13
13
|
"@fluxy-chat/ui": "^0.1.4",
|
|
14
14
|
"@fluxy-chat/react": "^0.1.4",
|
|
15
|
-
"@fluxy-chat/sdk": "^0.6.
|
|
15
|
+
"@fluxy-chat/sdk": "^0.6.5",
|
|
16
16
|
"react": "^19.0.0",
|
|
17
17
|
"react-dom": "^19.0.0"
|
|
18
18
|
},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# FluxyChat worker URL (hosted or self-hosted)
|
|
2
|
+
VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
|
|
3
|
+
|
|
4
|
+
# Option A — public room, no member JWT (pin sdk 0.6.7+)
|
|
5
|
+
VITE_FLUXYCHAT_PUBLISHABLE_KEY=pk_...
|
|
6
|
+
VITE_FLUXYCHAT_ROOM_ID=your-public-room-id
|
|
7
|
+
|
|
8
|
+
# Option B — member JWT
|
|
9
|
+
# VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
|
|
10
|
+
# VITE_FLUXYCHAT_ROOM_ID=demo
|
|
11
|
+
|
|
12
|
+
# Option C — public guest session without pk_
|
|
13
|
+
# VITE_FLUXYCHAT_PUBLIC_ROOM_ID=your-public-room-id
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# FluxyChat polls
|
|
2
|
+
|
|
3
|
+
First-class room polls: `createPoll` / `votePoll` on the chat timeline.
|
|
4
|
+
|
|
5
|
+
Anonymous ballots (voter ids omitted from results) are a separate HTTP path: `POST /polls` with `isAnonymous: true`, then `POST /polls/:id/close`. That is not `createPoll`.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @fluxy-chat/create-fluxy-chat@latest my-polls --example polls
|
|
9
|
+
cp .env.example .env
|
|
10
|
+
npm run dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Fastest path: `VITE_FLUXYCHAT_WORKER_URL` + `VITE_FLUXYCHAT_PUBLISHABLE_KEY` (`pk_`) + a public room id. Pin `@fluxy-chat/sdk@0.6.7` and `@fluxy-chat/react@0.1.6` once those versions are on npm.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>FluxyChat polls</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fluxy-polls",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@fluxy-chat/react": "^0.1.6",
|
|
13
|
+
"@fluxy-chat/sdk": "^0.6.7",
|
|
14
|
+
"react": "^19.0.0",
|
|
15
|
+
"react-dom": "^19.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/react": "^19.0.0",
|
|
19
|
+
"@types/react-dom": "^19.0.0",
|
|
20
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
21
|
+
"typescript": "^5.6.0",
|
|
22
|
+
"vite": "^6.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
FluxyRealtimeProvider,
|
|
4
|
+
useChat,
|
|
5
|
+
useFluxyChat,
|
|
6
|
+
} from "@fluxy-chat/react";
|
|
7
|
+
import { useFluxySession, workerUrl } from "./session";
|
|
8
|
+
|
|
9
|
+
const publishableKey = import.meta.env.VITE_FLUXYCHAT_PUBLISHABLE_KEY?.trim();
|
|
10
|
+
const pkRoomId =
|
|
11
|
+
import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() ||
|
|
12
|
+
import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim() ||
|
|
13
|
+
"demo";
|
|
14
|
+
|
|
15
|
+
function PollRoom({ roomId }: { roomId: string }) {
|
|
16
|
+
const { client } = useFluxyChat();
|
|
17
|
+
const { messages } = useChat({ roomId });
|
|
18
|
+
const [question, setQuestion] = useState("Ship Friday?");
|
|
19
|
+
const [error, setError] = useState<string | null>(null);
|
|
20
|
+
|
|
21
|
+
async function createTimelinePoll() {
|
|
22
|
+
setError(null);
|
|
23
|
+
try {
|
|
24
|
+
await client.createPoll(roomId, {
|
|
25
|
+
question,
|
|
26
|
+
options: ["Yes", "No", "Abstain"],
|
|
27
|
+
});
|
|
28
|
+
} catch (err) {
|
|
29
|
+
setError(err instanceof Error ? err.message : "createPoll failed");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<section className="panel">
|
|
35
|
+
<header className="chat-header">
|
|
36
|
+
<strong>Polls · {roomId}</strong>
|
|
37
|
+
<span className="status">createPoll / votePoll on the timeline</span>
|
|
38
|
+
</header>
|
|
39
|
+
<div className="composer">
|
|
40
|
+
<input
|
|
41
|
+
value={question}
|
|
42
|
+
onChange={(event) => setQuestion(event.target.value)}
|
|
43
|
+
placeholder="Question"
|
|
44
|
+
/>
|
|
45
|
+
<button type="button" className="primary" onClick={() => void createTimelinePoll()}>
|
|
46
|
+
Create poll
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
{error ? <p className="error" style={{ padding: "0 1rem" }}>{error}</p> : null}
|
|
50
|
+
<ul className="log">
|
|
51
|
+
{messages.map((message) => (
|
|
52
|
+
<li key={message.id} className="poll-card">
|
|
53
|
+
{message.poll ? (
|
|
54
|
+
<>
|
|
55
|
+
<p>
|
|
56
|
+
<strong>{message.poll.question}</strong>
|
|
57
|
+
{message.poll.closed ? " · closed" : ""}
|
|
58
|
+
</p>
|
|
59
|
+
<div className="poll-options">
|
|
60
|
+
{message.poll.options.map((option) => (
|
|
61
|
+
<button
|
|
62
|
+
key={option.index}
|
|
63
|
+
type="button"
|
|
64
|
+
disabled={message.poll?.closed}
|
|
65
|
+
onClick={() => void client.votePoll(message.id, option.index)}
|
|
66
|
+
>
|
|
67
|
+
{option.text} · {option.votes}
|
|
68
|
+
</button>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
<p className="hint" style={{ marginTop: 8 }}>
|
|
72
|
+
{message.poll.totalVoters} voters
|
|
73
|
+
{message.poll.userVote != null ? ` · you voted ${message.poll.userVote}` : ""}
|
|
74
|
+
</p>
|
|
75
|
+
</>
|
|
76
|
+
) : (
|
|
77
|
+
<p>
|
|
78
|
+
<strong>{message.userId}</strong> {message.content}
|
|
79
|
+
</p>
|
|
80
|
+
)}
|
|
81
|
+
</li>
|
|
82
|
+
))}
|
|
83
|
+
</ul>
|
|
84
|
+
</section>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function App() {
|
|
89
|
+
if (!workerUrl) return <div className="shell">Set <code>VITE_FLUXYCHAT_WORKER_URL</code>.</div>;
|
|
90
|
+
|
|
91
|
+
if (publishableKey) {
|
|
92
|
+
return (
|
|
93
|
+
<div className="shell">
|
|
94
|
+
<p className="mode-badge">publishableKey · public room {pkRoomId}</p>
|
|
95
|
+
<p className="hint">
|
|
96
|
+
Timeline polls use createPoll. Anonymous ballots (no voter ids in results) are POST /polls with isAnonymous: true. See README.
|
|
97
|
+
</p>
|
|
98
|
+
<FluxyRealtimeProvider workerUrl={workerUrl} publishableKey={publishableKey}>
|
|
99
|
+
<PollRoom roomId={pkRoomId} />
|
|
100
|
+
</FluxyRealtimeProvider>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return <MemberOrGuestApp />;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function MemberOrGuestApp() {
|
|
109
|
+
const { session, loading, error } = useFluxySession();
|
|
110
|
+
if (loading) return <div className="shell">Starting…</div>;
|
|
111
|
+
if (error) return <div className="shell error">{error}</div>;
|
|
112
|
+
if (!session) {
|
|
113
|
+
return (
|
|
114
|
+
<div className="shell">
|
|
115
|
+
Set <code>VITE_FLUXYCHAT_PUBLISHABLE_KEY</code>, a member JWT, or a public room id.
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className="shell">
|
|
122
|
+
<p className="mode-badge">{session.mode} · {session.roomId}</p>
|
|
123
|
+
<p className="hint">
|
|
124
|
+
Timeline polls use createPoll. Anonymous ballots: POST /polls with isAnonymous: true. See README.
|
|
125
|
+
</p>
|
|
126
|
+
<FluxyRealtimeProvider
|
|
127
|
+
workerUrl={session.workerUrl}
|
|
128
|
+
authTokenProvider={session.token}
|
|
129
|
+
userId={session.userId}
|
|
130
|
+
>
|
|
131
|
+
<PollRoom roomId={session.roomId} />
|
|
132
|
+
</FluxyRealtimeProvider>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
3
|
+
line-height: 1.5;
|
|
4
|
+
color: #0f172a;
|
|
5
|
+
background: #f8fafc;
|
|
6
|
+
}
|
|
7
|
+
* { box-sizing: border-box; }
|
|
8
|
+
body { margin: 0; }
|
|
9
|
+
.shell { max-width: 720px; margin: 0 auto; padding: 1.5rem; }
|
|
10
|
+
.panel {
|
|
11
|
+
border: 1px solid #e2e8f0;
|
|
12
|
+
border-radius: 12px;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
background: #fff;
|
|
15
|
+
}
|
|
16
|
+
.chat-header {
|
|
17
|
+
display: flex;
|
|
18
|
+
justify-content: space-between;
|
|
19
|
+
padding: 0.75rem 1rem;
|
|
20
|
+
background: #0f172a;
|
|
21
|
+
color: #fff;
|
|
22
|
+
}
|
|
23
|
+
.status { font-size: 0.75rem; opacity: 0.9; }
|
|
24
|
+
.roster {
|
|
25
|
+
margin: 0;
|
|
26
|
+
padding: 0.5rem 1rem;
|
|
27
|
+
list-style: none;
|
|
28
|
+
font-size: 0.8rem;
|
|
29
|
+
color: #64748b;
|
|
30
|
+
border-bottom: 1px solid #e2e8f0;
|
|
31
|
+
}
|
|
32
|
+
.log { min-height: 240px; max-height: 50vh; overflow: auto; padding: 1rem; }
|
|
33
|
+
.composer { display: flex; gap: 0.5rem; padding: 0.75rem; border-top: 1px solid #e2e8f0; }
|
|
34
|
+
.composer input { flex: 1; padding: 0.5rem 0.75rem; border: 1px solid #cbd5e1; border-radius: 8px; }
|
|
35
|
+
.composer button, button.primary {
|
|
36
|
+
padding: 0.5rem 0.75rem;
|
|
37
|
+
border: 0;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
background: #2563eb;
|
|
40
|
+
color: #fff;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
}
|
|
43
|
+
.mode-badge { font-size: 0.75rem; color: #64748b; margin-bottom: 0.75rem; }
|
|
44
|
+
.error { color: #b91c1c; }
|
|
45
|
+
code { font-size: 0.85em; }
|
|
46
|
+
.hint { font-size: 0.8rem; color: #475569; white-space: pre-wrap; background: #f1f5f9; padding: 0.75rem; border-radius: 8px; }
|
|
47
|
+
.readings { margin: 0; padding: 0.75rem 1rem; list-style: none; font-size: 0.875rem; }
|
|
48
|
+
.canvas {
|
|
49
|
+
position: relative;
|
|
50
|
+
height: min(70vh, 480px);
|
|
51
|
+
background: #f8fafc;
|
|
52
|
+
touch-action: none;
|
|
53
|
+
}
|
|
54
|
+
.dot {
|
|
55
|
+
position: absolute;
|
|
56
|
+
width: 10px;
|
|
57
|
+
height: 10px;
|
|
58
|
+
border-radius: 999px;
|
|
59
|
+
transform: translate(-50%, -50%);
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
}
|
|
62
|
+
.peer-cursor {
|
|
63
|
+
position: absolute;
|
|
64
|
+
pointer-events: none;
|
|
65
|
+
z-index: 2;
|
|
66
|
+
}
|
|
67
|
+
.peer-cursor span {
|
|
68
|
+
display: block;
|
|
69
|
+
margin-top: 2px;
|
|
70
|
+
padding: 1px 6px;
|
|
71
|
+
border-radius: 4px;
|
|
72
|
+
background: currentColor;
|
|
73
|
+
color: #fff;
|
|
74
|
+
font-size: 11px;
|
|
75
|
+
}
|
|
76
|
+
.poll-card { margin: 0 0 1rem; padding-bottom: 0.75rem; border-bottom: 1px solid #e2e8f0; }
|
|
77
|
+
.poll-options { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
|
78
|
+
.poll-options button {
|
|
79
|
+
padding: 0.4rem 0.7rem;
|
|
80
|
+
border: 1px solid #cbd5e1;
|
|
81
|
+
border-radius: 8px;
|
|
82
|
+
background: #f8fafc;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
}
|
|
85
|
+
.poll-options button:disabled { opacity: 0.6; cursor: default; }
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { FluxyChatClient } from "@fluxy-chat/sdk";
|
|
3
|
+
|
|
4
|
+
const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL?.trim();
|
|
5
|
+
const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT?.trim();
|
|
6
|
+
const publicRoomId = import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim();
|
|
7
|
+
const configuredRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() || "demo";
|
|
8
|
+
|
|
9
|
+
export interface FluxySession {
|
|
10
|
+
workerUrl: string;
|
|
11
|
+
token: string;
|
|
12
|
+
userId: string;
|
|
13
|
+
roomId: string;
|
|
14
|
+
mode: "member" | "guest";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useFluxySession(): {
|
|
18
|
+
session: FluxySession | null;
|
|
19
|
+
loading: boolean;
|
|
20
|
+
error: string | null;
|
|
21
|
+
} {
|
|
22
|
+
const [session, setSession] = useState<FluxySession | null>(null);
|
|
23
|
+
const [loading, setLoading] = useState(Boolean(workerUrl));
|
|
24
|
+
const [error, setError] = useState<string | null>(null);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!workerUrl) {
|
|
28
|
+
setLoading(false);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (memberJwt) {
|
|
32
|
+
setSession({
|
|
33
|
+
workerUrl,
|
|
34
|
+
token: memberJwt,
|
|
35
|
+
userId: "demo-user",
|
|
36
|
+
roomId: configuredRoomId,
|
|
37
|
+
mode: "member",
|
|
38
|
+
});
|
|
39
|
+
setLoading(false);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (publicRoomId) {
|
|
43
|
+
let cancelled = false;
|
|
44
|
+
void FluxyChatClient.joinPublicRoomAsGuest(workerUrl, publicRoomId, {
|
|
45
|
+
displayName: "Guest",
|
|
46
|
+
})
|
|
47
|
+
.then((guest) => {
|
|
48
|
+
if (cancelled) return;
|
|
49
|
+
setSession({
|
|
50
|
+
workerUrl,
|
|
51
|
+
token: guest.token,
|
|
52
|
+
userId: guest.userId,
|
|
53
|
+
roomId: guest.roomId,
|
|
54
|
+
mode: "guest",
|
|
55
|
+
});
|
|
56
|
+
})
|
|
57
|
+
.catch((err) => {
|
|
58
|
+
if (!cancelled) setError(err instanceof Error ? err.message : "Guest session failed");
|
|
59
|
+
})
|
|
60
|
+
.finally(() => {
|
|
61
|
+
if (!cancelled) setLoading(false);
|
|
62
|
+
});
|
|
63
|
+
return () => {
|
|
64
|
+
cancelled = true;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
setLoading(false);
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
return { session, loading, error };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { workerUrl };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
interface ImportMetaEnv {
|
|
4
|
+
readonly VITE_FLUXYCHAT_WORKER_URL: string;
|
|
5
|
+
readonly VITE_FLUXYCHAT_PUBLISHABLE_KEY?: string;
|
|
6
|
+
readonly VITE_FLUXYCHAT_MEMBER_JWT?: string;
|
|
7
|
+
readonly VITE_FLUXYCHAT_ROOM_ID?: string;
|
|
8
|
+
readonly VITE_FLUXYCHAT_PUBLIC_ROOM_ID?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ImportMeta {
|
|
12
|
+
readonly env: ImportMetaEnv;
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"noUncheckedSideEffectImports": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src"]
|
|
21
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# FluxyChat worker URL (hosted or self-hosted)
|
|
2
|
-
VITE_FLUXYCHAT_WORKER_URL=https://
|
|
2
|
+
VITE_FLUXYCHAT_WORKER_URL=https://api.fluxychat.com
|
|
3
3
|
|
|
4
|
-
# Option A —
|
|
5
|
-
|
|
6
|
-
VITE_FLUXYCHAT_ROOM_ID=
|
|
4
|
+
# Option A — public room, no JWT (pin @fluxy-chat/sdk@0.6.7+)
|
|
5
|
+
VITE_FLUXYCHAT_PUBLISHABLE_KEY=pk_...
|
|
6
|
+
VITE_FLUXYCHAT_ROOM_ID=general
|
|
7
7
|
|
|
8
|
-
# Option B —
|
|
8
|
+
# Option B — member JWT
|
|
9
|
+
# VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
|
|
10
|
+
# VITE_FLUXYCHAT_ROOM_ID=demo
|
|
11
|
+
|
|
12
|
+
# Option C — guest session without pk_
|
|
9
13
|
# VITE_FLUXYCHAT_PUBLIC_ROOM_ID=your-public-room-id
|
|
@@ -1,43 +1,18 @@
|
|
|
1
1
|
# FluxyChat React starter
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Quick start (guest, fastest)
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
cp .env.example .env
|
|
9
|
-
# Set VITE_FLUXYCHAT_WORKER_URL + VITE_FLUXYCHAT_PUBLIC_ROOM_ID (public room from console)
|
|
10
|
-
npm install
|
|
11
|
-
npm run dev
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
Open http://localhost:5173. A guest JWT is minted automatically via `joinPublicRoomAsGuest`.
|
|
15
|
-
|
|
16
|
-
## Quick start (member JWT)
|
|
3
|
+
`FluxyRealtimeProvider` + `publishableKey` + `useChat`. No member JWT mint.
|
|
17
4
|
|
|
18
5
|
```bash
|
|
19
6
|
cp .env.example .env
|
|
20
|
-
# Set VITE_FLUXYCHAT_WORKER_URL +
|
|
7
|
+
# Set VITE_FLUXYCHAT_WORKER_URL + VITE_FLUXYCHAT_PUBLISHABLE_KEY + room id
|
|
21
8
|
npm install
|
|
22
9
|
npm run dev
|
|
23
10
|
```
|
|
24
11
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
1. **Guest:** create a **public** room in console, copy room ID, set `VITE_FLUXYCHAT_PUBLIC_ROOM_ID`
|
|
28
|
-
2. **Member:** [fluxychat.com/onboarding](https://fluxychat.com/onboarding) for Worker URL and JWT
|
|
29
|
-
3. **Local monorepo:** `pnpm run first-message` from the FluxyChat repo
|
|
30
|
-
|
|
31
|
-
## Scripts
|
|
32
|
-
|
|
33
|
-
| Command | Description |
|
|
34
|
-
|---------|-------------|
|
|
35
|
-
| `npm run dev` | Vite dev server |
|
|
36
|
-
| `npm run build` | Production build |
|
|
37
|
-
| `npm run preview` | Preview production build |
|
|
12
|
+
Pin `@fluxy-chat/sdk@0.6.7` and `@fluxy-chat/react@0.1.6` once those versions are on npm.
|
|
38
13
|
|
|
39
|
-
##
|
|
14
|
+
## Other paths
|
|
40
15
|
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
16
|
+
- Member JWT: `VITE_FLUXYCHAT_MEMBER_JWT`
|
|
17
|
+
- Guest session: `VITE_FLUXYCHAT_PUBLIC_ROOM_ID` (`joinPublicRoomAsGuest`)
|
|
18
|
+
- Credentials: [fluxychat.com/onboarding](https://fluxychat.com/onboarding) or `GET /public/demo-credentials` on a Worker that sets `PUBLIC_DEMO_PUBLISHABLE_KEY`
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"preview": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@fluxy-chat/react": "^0.1.
|
|
13
|
-
"@fluxy-chat/sdk": "^0.6.
|
|
12
|
+
"@fluxy-chat/react": "^0.1.6",
|
|
13
|
+
"@fluxy-chat/sdk": "^0.6.7",
|
|
14
14
|
"react": "^19.0.0",
|
|
15
15
|
"react-dom": "^19.0.0",
|
|
16
16
|
"zustand": "^5.0.0"
|
|
@@ -3,6 +3,7 @@ import { FluxyChatClient } from "@fluxy-chat/sdk";
|
|
|
3
3
|
import { FluxyRealtimeProvider, useChat } from "@fluxy-chat/react";
|
|
4
4
|
|
|
5
5
|
const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL?.trim();
|
|
6
|
+
const publishableKey = import.meta.env.VITE_FLUXYCHAT_PUBLISHABLE_KEY?.trim();
|
|
6
7
|
const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT?.trim();
|
|
7
8
|
const publicRoomId = import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim();
|
|
8
9
|
const configuredRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() || "demo";
|
|
@@ -21,11 +22,11 @@ function useFluxySession(): {
|
|
|
21
22
|
error: string | null;
|
|
22
23
|
} {
|
|
23
24
|
const [session, setSession] = useState<FluxySession | null>(null);
|
|
24
|
-
const [loading, setLoading] = useState(Boolean(workerUrl));
|
|
25
|
+
const [loading, setLoading] = useState(Boolean(workerUrl) && !publishableKey);
|
|
25
26
|
const [error, setError] = useState<string | null>(null);
|
|
26
27
|
|
|
27
28
|
useEffect(() => {
|
|
28
|
-
if (!workerUrl) {
|
|
29
|
+
if (!workerUrl || publishableKey) {
|
|
29
30
|
setLoading(false);
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
@@ -126,30 +127,44 @@ function ChatPanel({ roomId }: { roomId: string }) {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
export function App() {
|
|
129
|
-
const
|
|
130
|
-
const [roomId, setRoomId] = useState(configuredRoomId);
|
|
131
|
-
|
|
132
|
-
const activeRoomId = useMemo(() => {
|
|
133
|
-
if (session?.mode === "guest") return session.roomId;
|
|
134
|
-
return roomId.trim() || configuredRoomId;
|
|
135
|
-
}, [session, roomId]);
|
|
130
|
+
const pkRoomId = publicRoomId || configuredRoomId;
|
|
136
131
|
|
|
137
132
|
if (!workerUrl) {
|
|
138
133
|
return (
|
|
139
134
|
<main className="shell">
|
|
140
|
-
<h1>FluxyChat
|
|
135
|
+
<h1>FluxyChat</h1>
|
|
141
136
|
<p>
|
|
142
137
|
Copy <code>.env.example</code> to <code>.env</code> and set{" "}
|
|
143
|
-
<code>VITE_FLUXYCHAT_WORKER_URL</code>.
|
|
144
|
-
</p>
|
|
145
|
-
<p>
|
|
146
|
-
Then either <code>VITE_FLUXYCHAT_MEMBER_JWT</code> (member) or{" "}
|
|
147
|
-
<code>VITE_FLUXYCHAT_PUBLIC_ROOM_ID</code> (guest, ~60s to first message).
|
|
138
|
+
<code>VITE_FLUXYCHAT_WORKER_URL</code> plus <code>VITE_FLUXYCHAT_PUBLISHABLE_KEY</code>.
|
|
148
139
|
</p>
|
|
149
140
|
</main>
|
|
150
141
|
);
|
|
151
142
|
}
|
|
152
143
|
|
|
144
|
+
if (publishableKey) {
|
|
145
|
+
return (
|
|
146
|
+
<main className="shell">
|
|
147
|
+
<h1>FluxyChat</h1>
|
|
148
|
+
<p className="mode-badge">publishableKey · public room {pkRoomId}</p>
|
|
149
|
+
<FluxyRealtimeProvider workerUrl={workerUrl} publishableKey={publishableKey}>
|
|
150
|
+
<ChatPanel roomId={pkRoomId} />
|
|
151
|
+
</FluxyRealtimeProvider>
|
|
152
|
+
</main>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return <MemberOrGuestApp />;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function MemberOrGuestApp() {
|
|
160
|
+
const { session, loading, error } = useFluxySession();
|
|
161
|
+
const [roomId, setRoomId] = useState(configuredRoomId);
|
|
162
|
+
|
|
163
|
+
const activeRoomId = useMemo(() => {
|
|
164
|
+
if (session?.mode === "guest") return session.roomId;
|
|
165
|
+
return roomId.trim() || configuredRoomId;
|
|
166
|
+
}, [session, roomId]);
|
|
167
|
+
|
|
153
168
|
if (loading) {
|
|
154
169
|
return (
|
|
155
170
|
<main className="shell">
|
|
@@ -162,14 +177,7 @@ export function App() {
|
|
|
162
177
|
return (
|
|
163
178
|
<main className="shell">
|
|
164
179
|
<h1>FluxyChat</h1>
|
|
165
|
-
<p className="error">{error ?? "Set JWT or public room ID in .env"}</p>
|
|
166
|
-
<p>
|
|
167
|
-
Get credentials from{" "}
|
|
168
|
-
<a href="https://fluxychat.com/onboarding" target="_blank" rel="noreferrer">
|
|
169
|
-
onboarding
|
|
170
|
-
</a>{" "}
|
|
171
|
-
or use a public room ID for guest mode.
|
|
172
|
-
</p>
|
|
180
|
+
<p className="error">{error ?? "Set pk_, JWT, or public room ID in .env"}</p>
|
|
173
181
|
</main>
|
|
174
182
|
);
|
|
175
183
|
}
|