@sansavision/create-pulse 0.4.3 → 0.4.5

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.
Files changed (102) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1 -1
  3. package/package.json +3 -3
  4. package/templates/aurora-auth-node-demo/README.md +43 -0
  5. package/templates/aurora-auth-node-demo/aurora.config.ts +15 -0
  6. package/templates/aurora-auth-node-demo/bun.lock +679 -0
  7. package/templates/aurora-auth-node-demo/drizzle.config.ts +9 -0
  8. package/templates/aurora-auth-node-demo/package.json +39 -0
  9. package/templates/aurora-auth-node-demo/postcss.config.mjs +7 -0
  10. package/templates/aurora-auth-node-demo/server.mjs +46 -0
  11. package/templates/aurora-auth-node-demo/src/actions/createMessage.action.server.ts +31 -0
  12. package/templates/aurora-auth-node-demo/src/aurora.auth.ts +65 -0
  13. package/templates/aurora-auth-node-demo/src/lib/auth-client.ts +30 -0
  14. package/templates/aurora-auth-node-demo/src/lib/auth.server.ts +11 -0
  15. package/templates/aurora-auth-node-demo/src/lib/auth.ts +30 -0
  16. package/templates/aurora-auth-node-demo/src/lib/db.ts +6 -0
  17. package/templates/aurora-auth-node-demo/src/lib/pulse.ts +45 -0
  18. package/templates/aurora-auth-node-demo/src/lib/schema.ts +107 -0
  19. package/templates/aurora-auth-node-demo/src/queries/listMessages.server.ts +25 -0
  20. package/templates/aurora-auth-node-demo/src/routes/api/auth/[...slug]/handler.ts +14 -0
  21. package/templates/aurora-auth-node-demo/src/routes/api/pulse/verify/handler.ts +55 -0
  22. package/templates/aurora-auth-node-demo/src/routes/auth/sign-in/page.client.tsx +132 -0
  23. package/templates/aurora-auth-node-demo/src/routes/auth/sign-in/page.tsx +5 -0
  24. package/templates/aurora-auth-node-demo/src/routes/auth/sign-up/page.client.tsx +154 -0
  25. package/templates/aurora-auth-node-demo/src/routes/auth/sign-up/page.tsx +5 -0
  26. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/arena-game/page.client.tsx +640 -0
  27. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/arena-game/page.tsx +5 -0
  28. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/chat/page.client.tsx +349 -0
  29. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/chat/page.tsx +5 -0
  30. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/encrypted-chat/page.client.tsx +472 -0
  31. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/encrypted-chat/page.tsx +5 -0
  32. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/game-sync/page.client.tsx +375 -0
  33. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/game-sync/page.tsx +5 -0
  34. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/queues/page.client.tsx +423 -0
  35. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/queues/page.tsx +5 -0
  36. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/video-call/page.client.tsx +840 -0
  37. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/video-call/page.tsx +5 -0
  38. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/watch-together/page.client.tsx +722 -0
  39. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/watch-together/page.tsx +5 -0
  40. package/templates/aurora-auth-node-demo/src/routes/dashboard/layout.client.tsx +113 -0
  41. package/templates/aurora-auth-node-demo/src/routes/dashboard/layout.tsx +5 -0
  42. package/templates/aurora-auth-node-demo/src/routes/dashboard/page.client.tsx +195 -0
  43. package/templates/aurora-auth-node-demo/src/routes/dashboard/page.tsx +5 -0
  44. package/templates/aurora-auth-node-demo/src/routes/favicon.ico +0 -0
  45. package/templates/aurora-auth-node-demo/src/routes/layout.tsx +18 -0
  46. package/templates/aurora-auth-node-demo/src/routes/page.client.tsx +263 -0
  47. package/templates/aurora-auth-node-demo/src/routes/page.tsx +5 -0
  48. package/templates/aurora-auth-node-demo/src/styles/app.css +96 -0
  49. package/templates/aurora-auth-node-demo/tsconfig.json +27 -0
  50. package/templates/aurora-auth-node-demo/tsconfig.tsbuildinfo +1 -0
  51. package/templates/nextjs-auth-demo/README.md +1 -1
  52. package/templates/nextjs-auth-demo/next-env.d.ts +1 -1
  53. package/templates/nextjs-auth-demo/package.json +8 -7
  54. package/templates/nextjs-auth-demo/src/app/dashboard/demos/arena-game/page.tsx +640 -0
  55. package/templates/nextjs-auth-demo/src/app/dashboard/demos/chat/page.tsx +124 -23
  56. package/templates/nextjs-auth-demo/src/app/dashboard/demos/encrypted-chat/page.tsx +350 -76
  57. package/templates/nextjs-auth-demo/src/app/dashboard/demos/game-sync/page.tsx +232 -49
  58. package/templates/nextjs-auth-demo/src/app/dashboard/demos/queues/page.tsx +213 -87
  59. package/templates/nextjs-auth-demo/src/app/dashboard/demos/video-call/page.tsx +840 -0
  60. package/templates/nextjs-auth-demo/src/app/dashboard/demos/watch-together/page.tsx +589 -123
  61. package/templates/nextjs-auth-demo/src/app/dashboard/layout.tsx +4 -0
  62. package/templates/nextjs-auth-demo/src/app/dashboard/page.tsx +53 -5
  63. package/templates/nextjs-auth-demo/src/app/layout.tsx +1 -1
  64. package/templates/nextjs-auth-node-demo/.env.example +10 -0
  65. package/templates/nextjs-auth-node-demo/Dockerfile +19 -0
  66. package/templates/nextjs-auth-node-demo/README.md +159 -0
  67. package/templates/nextjs-auth-node-demo/_gitignore +33 -0
  68. package/templates/nextjs-auth-node-demo/drizzle.config.ts +10 -0
  69. package/templates/nextjs-auth-node-demo/eslint.config.mjs +18 -0
  70. package/templates/nextjs-auth-node-demo/next-env.d.ts +6 -0
  71. package/templates/nextjs-auth-node-demo/next.config.ts +7 -0
  72. package/templates/nextjs-auth-node-demo/package.json +38 -0
  73. package/templates/nextjs-auth-node-demo/postcss.config.mjs +7 -0
  74. package/templates/nextjs-auth-node-demo/public/file.svg +1 -0
  75. package/templates/nextjs-auth-node-demo/public/globe.svg +1 -0
  76. package/templates/nextjs-auth-node-demo/public/next.svg +1 -0
  77. package/templates/nextjs-auth-node-demo/public/vercel.svg +1 -0
  78. package/templates/nextjs-auth-node-demo/public/window.svg +1 -0
  79. package/templates/nextjs-auth-node-demo/server.mjs +45 -0
  80. package/templates/nextjs-auth-node-demo/src/app/api/auth/[...all]/route.ts +4 -0
  81. package/templates/nextjs-auth-node-demo/src/app/api/pulse/verify/route.ts +54 -0
  82. package/templates/nextjs-auth-node-demo/src/app/auth/sign-in/page.tsx +131 -0
  83. package/templates/nextjs-auth-node-demo/src/app/auth/sign-up/page.tsx +153 -0
  84. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/arena-game/page.tsx +640 -0
  85. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/chat/page.tsx +349 -0
  86. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/encrypted-chat/page.tsx +472 -0
  87. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/game-sync/page.tsx +375 -0
  88. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/queues/page.tsx +423 -0
  89. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/video-call/page.tsx +840 -0
  90. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/watch-together/page.tsx +724 -0
  91. package/templates/nextjs-auth-node-demo/src/app/dashboard/layout.tsx +113 -0
  92. package/templates/nextjs-auth-node-demo/src/app/dashboard/page.tsx +195 -0
  93. package/templates/nextjs-auth-node-demo/src/app/favicon.ico +0 -0
  94. package/templates/nextjs-auth-node-demo/src/app/globals.css +96 -0
  95. package/templates/nextjs-auth-node-demo/src/app/layout.tsx +27 -0
  96. package/templates/nextjs-auth-node-demo/src/app/page.tsx +254 -0
  97. package/templates/nextjs-auth-node-demo/src/lib/auth-client.ts +15 -0
  98. package/templates/nextjs-auth-node-demo/src/lib/auth.ts +14 -0
  99. package/templates/nextjs-auth-node-demo/src/lib/db.ts +6 -0
  100. package/templates/nextjs-auth-node-demo/src/lib/pulse.ts +45 -0
  101. package/templates/nextjs-auth-node-demo/src/lib/schema.ts +107 -0
  102. package/templates/nextjs-auth-node-demo/tsconfig.json +34 -0
@@ -25,7 +25,15 @@ interface ChatMessage {
25
25
  export default function ChatDemoPage() {
26
26
  const { data: session } = useSession();
27
27
  const [connected, setConnected] = useState(false);
28
- const [messages, setMessages] = useState<ChatMessage[]>([]);
28
+ const [messages, setMessages] = useState<ChatMessage[]>(() => {
29
+ if (typeof window !== "undefined") {
30
+ try {
31
+ const stored = localStorage.getItem("pulse-chat-messages");
32
+ if (stored) return JSON.parse(stored);
33
+ } catch { /* ignore */ }
34
+ }
35
+ return [];
36
+ });
29
37
  const [input, setInput] = useState("");
30
38
  const [authUser, setAuthUser] = useState<{
31
39
  id: string;
@@ -35,11 +43,24 @@ export default function ChatDemoPage() {
35
43
  const [connecting, setConnecting] = useState(true);
36
44
  const messagesEndRef = useRef<HTMLDivElement>(null);
37
45
  const connRef = useRef<PulseConnection | null>(null);
46
+ const streamRef = useRef<ReturnType<PulseConnection["stream"]> | null>(null);
47
+ const sessionRef = useRef(session);
48
+ const seenIdsRef = useRef(new Set<string>());
49
+
50
+ // Keep session ref in sync
51
+ useEffect(() => { sessionRef.current = session; }, [session]);
38
52
 
39
53
  const scrollToBottom = useCallback(() => {
40
54
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
41
55
  }, []);
42
56
 
57
+ useEffect(() => {
58
+ if (typeof window !== "undefined") {
59
+ localStorage.setItem("pulse-chat-messages", JSON.stringify(messages.slice(-100)));
60
+ }
61
+ }, [messages]);
62
+
63
+ // Connect to relay — run ONCE when session is available
43
64
  useEffect(() => {
44
65
  if (!session) return;
45
66
  let cancelled = false;
@@ -53,39 +74,50 @@ export default function ChatDemoPage() {
53
74
  setConnected(true);
54
75
  setConnecting(false);
55
76
 
56
- // Check auth user from the ACCEPT payload
57
77
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
78
  const user = (connection as any).user;
59
79
  if (user) setAuthUser(user);
60
80
 
61
- // Open a chat stream
81
+ // Open the chat stream ONCE and store in ref
62
82
  const stream = connection.stream("chat-room");
83
+ streamRef.current = stream;
63
84
 
64
85
  // Announce join
65
- stream.send(
66
- JSON.stringify({
67
- type: "join",
68
- user: session!.user.name,
69
- userId: session!.user.id,
70
- })
71
- );
72
-
73
- // Listen for messages
86
+ const sess = sessionRef.current;
87
+ if (sess) {
88
+ stream.send(
89
+ JSON.stringify({
90
+ type: "join",
91
+ user: sess.user.name,
92
+ userId: sess.user.id,
93
+ })
94
+ );
95
+ }
96
+
97
+ // Listen for messages — uses sessionRef to avoid stale closures
74
98
  stream.on("data", (data: Uint8Array) => {
75
99
  try {
76
100
  const msg = JSON.parse(new TextDecoder().decode(data));
77
- if (msg.type === "chat") {
101
+ const currentSess = sessionRef.current;
102
+ if (!currentSess) return;
103
+ const selfId = currentSess.user.id;
104
+
105
+ // Deduplication via message ID
106
+ if (msg.msgId && seenIdsRef.current.has(msg.msgId)) return;
107
+ if (msg.msgId) seenIdsRef.current.add(msg.msgId);
108
+
109
+ if (msg.type === "chat" && msg.userId !== selfId) {
78
110
  setMessages((prev) => [
79
111
  ...prev,
80
112
  {
81
- id: crypto.randomUUID(),
113
+ id: msg.msgId || crypto.randomUUID(),
82
114
  user: msg.user,
83
115
  userId: msg.userId,
84
116
  text: msg.text,
85
117
  timestamp: Date.now(),
86
118
  },
87
119
  ]);
88
- } else if (msg.type === "join") {
120
+ } else if (msg.type === "join" && msg.userId !== selfId) {
89
121
  setOnlineUsers((prev) =>
90
122
  prev.includes(msg.user) ? prev : [...prev, msg.user]
91
123
  );
@@ -99,6 +131,32 @@ export default function ChatDemoPage() {
99
131
  timestamp: Date.now(),
100
132
  },
101
133
  ]);
134
+
135
+ // Announce our presence back
136
+ const s = sessionRef.current;
137
+ if (s) {
138
+ stream.send(JSON.stringify({
139
+ type: "presence",
140
+ user: s.user.name,
141
+ userId: s.user.id,
142
+ }));
143
+ }
144
+ } else if (msg.type === "presence" && msg.userId !== selfId) {
145
+ setOnlineUsers((prev) =>
146
+ prev.includes(msg.user) ? prev : [...prev, msg.user]
147
+ );
148
+ } else if (msg.type === "leave" && msg.userId !== selfId) {
149
+ setOnlineUsers((prev) => prev.filter(u => u !== msg.user));
150
+ setMessages((prev) => [
151
+ ...prev,
152
+ {
153
+ id: crypto.randomUUID(),
154
+ user: "System",
155
+ userId: "system",
156
+ text: `${msg.user} left the chat`,
157
+ timestamp: Date.now(),
158
+ },
159
+ ]);
102
160
  }
103
161
  } catch {
104
162
  // ignore parse errors
@@ -106,7 +164,18 @@ export default function ChatDemoPage() {
106
164
  });
107
165
 
108
166
  connection.on("disconnect", () => setConnected(false));
109
- connection.on("reconnected", () => setConnected(true));
167
+ connection.on("reconnected", () => {
168
+ setConnected(true);
169
+ // Re-announce on reconnect
170
+ const s = sessionRef.current;
171
+ if (s && streamRef.current) {
172
+ streamRef.current.send(JSON.stringify({
173
+ type: "join",
174
+ user: s.user.name,
175
+ userId: s.user.id,
176
+ }));
177
+ }
178
+ });
110
179
  } catch (err) {
111
180
  console.error("Failed to connect:", err);
112
181
  setConnecting(false);
@@ -117,6 +186,17 @@ export default function ChatDemoPage() {
117
186
 
118
187
  return () => {
119
188
  cancelled = true;
189
+ const sess = sessionRef.current;
190
+ if (streamRef.current && sess) {
191
+ try {
192
+ streamRef.current.send(JSON.stringify({
193
+ type: "leave",
194
+ user: sess.user.name,
195
+ userId: sess.user.id
196
+ }));
197
+ } catch { /* ignore */ }
198
+ }
199
+ streamRef.current = null;
120
200
  connRef.current?.disconnect();
121
201
  };
122
202
  }, [session]);
@@ -125,17 +205,38 @@ export default function ChatDemoPage() {
125
205
 
126
206
  function sendMessage(e: React.FormEvent) {
127
207
  e.preventDefault();
128
- if (!input.trim() || !connRef.current) return;
208
+ if (!input.trim() || !streamRef.current) return;
129
209
 
130
- const stream = connRef.current.stream("chat-room");
131
- stream.send(
210
+ const text = input.trim();
211
+ const sess = sessionRef.current;
212
+ const user = sess?.user?.name || "Anonymous";
213
+ const usrId = sess?.user?.id || "unknown";
214
+ const msgId = crypto.randomUUID();
215
+
216
+ // Mark as seen so we don't add our own echo
217
+ seenIdsRef.current.add(msgId);
218
+
219
+ streamRef.current.send(
132
220
  JSON.stringify({
133
221
  type: "chat",
134
- user: session?.user?.name || "Anonymous",
135
- userId: session?.user?.id || "unknown",
136
- text: input.trim(),
222
+ user,
223
+ userId: usrId,
224
+ text,
225
+ msgId,
137
226
  })
138
227
  );
228
+
229
+ // Add to local state immediately
230
+ setMessages((prev) => [
231
+ ...prev,
232
+ {
233
+ id: msgId,
234
+ user,
235
+ userId: usrId,
236
+ text,
237
+ timestamp: Date.now(),
238
+ },
239
+ ]);
139
240
  setInput("");
140
241
  }
141
242
 
@@ -179,7 +280,7 @@ export default function ChatDemoPage() {
179
280
  <div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-slate-800">
180
281
  <Users className="w-3.5 h-3.5 text-slate-400" />
181
282
  <span className="text-xs text-slate-400">
182
- {onlineUsers.length || 1} online
283
+ {onlineUsers.length + 1} online
183
284
  </span>
184
285
  </div>
185
286
  </div>