@sansavision/create-pulse 0.1.0-alpha.6 → 0.1.0-alpha.7

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": "@sansavision/create-pulse",
3
- "version": "0.1.0-alpha.6",
3
+ "version": "0.1.0-alpha.7",
4
4
  "description": "Scaffold a new Pulse application",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- import { useState, useCallback } from 'react';
1
+ import { useState, useCallback, useRef } from 'react';
2
2
  import { usePulse } from './hooks/usePulse';
3
3
  import { VideoPlayer } from './components/VideoPlayer';
4
4
  import { GameSync } from './components/GameSync';
@@ -22,8 +22,12 @@ export default function App() {
22
22
  const [isPlaying, setIsPlaying] = useState(false);
23
23
  const [events, setEvents] = useState<{ id: number, action: string, time: string }[]>([]);
24
24
 
25
+ // Track relay message delay
26
+ const [lastDelay, setLastDelay] = useState<number | null>(null);
27
+ const delayHistoryRef = useRef<number[]>([]);
28
+
25
29
  const logEvent = useCallback((action: string) => {
26
- setEvents(prev => [...prev.slice(-4), {
30
+ setEvents(prev => [...prev.slice(-19), {
27
31
  id: Date.now(),
28
32
  action,
29
33
  time: new Date().toISOString().substring(11, 23)
@@ -32,7 +36,15 @@ export default function App() {
32
36
 
33
37
  const handleSyncMessage = useCallback((msg: any) => {
34
38
  if (activeFeature !== 'watch') return;
35
- logEvent(`RECV: ${msg.action}`);
39
+
40
+ // Track delivery delay
41
+ if (msg.ts) {
42
+ const delay = Date.now() - msg.ts;
43
+ setLastDelay(delay);
44
+ delayHistoryRef.current = [...delayHistoryRef.current.slice(-29), delay];
45
+ }
46
+
47
+ logEvent(`RECV: ${msg.action} @ ${msg.time?.toFixed(2)}s`);
36
48
 
37
49
  switch (msg.action) {
38
50
  case 'play':
@@ -56,10 +68,30 @@ export default function App() {
56
68
  const { broadcast } = useChannel(STREAM_NAME, handleSyncMessage);
57
69
 
58
70
  const broadcastAction = useCallback((action: string, time: number) => {
59
- logEvent(`SEND: ${action}`);
60
- broadcast({ action, time, ts: Date.now() });
71
+ logEvent(`SEND: ${action} @ ${time.toFixed(2)}s`);
72
+ const msg = { action, time, ts: Date.now() };
73
+ broadcast(msg);
74
+
75
+ // Local loopback: since the relay doesn't echo back to the sender,
76
+ // we directly update peerTime for the same-tab demo experience.
77
+ if (action === 'tick') {
78
+ setPeerTime(time);
79
+ } else if (action === 'play') {
80
+ setIsPlaying(true);
81
+ setPeerTime(time);
82
+ } else if (action === 'pause') {
83
+ setIsPlaying(false);
84
+ setPeerTime(time);
85
+ } else if (action === 'seek') {
86
+ setPeerTime(time);
87
+ }
61
88
  }, [broadcast, logEvent]);
62
89
 
90
+ // Computed delay stats
91
+ const avgDelay = delayHistoryRef.current.length > 0
92
+ ? Math.round(delayHistoryRef.current.reduce((a: number, b: number) => a + b, 0) / delayHistoryRef.current.length)
93
+ : null;
94
+
63
95
  return (
64
96
  <div className="min-h-screen">
65
97
  {/* Header */}
@@ -260,7 +292,20 @@ export default function App() {
260
292
  <div className="flex justify-between items-center p-3 rounded-lg bg-black/30 border border-pulseBorder">
261
293
  <span className="text-textMuted">Drift</span>
262
294
  <span className={`font-semibold tracking-wider ${Math.abs(hostTime - peerTime) > 0.15 ? 'text-brandAmber' : 'text-brandEmerald'}`}>
263
- {Math.abs(hostTime - peerTime).toFixed(3)}s
295
+ {Math.abs(hostTime - peerTime) < 0.001 ? '0.000' : Math.abs(hostTime - peerTime).toFixed(3)}s
296
+ </span>
297
+ </div>
298
+ <div className="flex justify-between items-center p-3 rounded-lg bg-black/30 border border-pulseBorder">
299
+ <span className="text-textMuted">Relay RTT</span>
300
+ <span className="text-textMain font-semibold tracking-wider">
301
+ {metrics ? `${Math.round(metrics.rtt)}ms` : '—'}
302
+ </span>
303
+ </div>
304
+ <div className="flex justify-between items-center p-3 rounded-lg bg-black/30 border border-pulseBorder">
305
+ <span className="text-textMuted">Msg Delay</span>
306
+ <span className={`font-semibold tracking-wider ${lastDelay !== null && lastDelay > 50 ? 'text-brandAmber' : 'text-brandEmerald'}`}>
307
+ {lastDelay !== null ? `${lastDelay}ms` : '—'}
308
+ {avgDelay !== null && <span className="text-textDim text-[10px] ml-1">avg {avgDelay}ms</span>}
264
309
  </span>
265
310
  </div>
266
311
  </div>
@@ -1,4 +1,4 @@
1
- import { useState, useCallback } from 'react';
1
+ import { useState, useCallback, useRef } from 'react';
2
2
  import { usePulse } from './hooks/usePulse';
3
3
  import { VideoPlayer } from './components/VideoPlayer';
4
4
  import { Activity, Users, Video } from 'lucide-react';
@@ -17,8 +17,12 @@ export default function App() {
17
17
  const [isPlaying, setIsPlaying] = useState(false);
18
18
  const [events, setEvents] = useState<{ id: number, action: string, time: string }[]>([]);
19
19
 
20
+ // Track message round-trip delay
21
+ const [lastDelay, setLastDelay] = useState<number | null>(null);
22
+ const delayHistoryRef = useRef<number[]>([]);
23
+
20
24
  const logEvent = useCallback((action: string) => {
21
- setEvents(prev => [...prev.slice(-4), {
25
+ setEvents((prev: { id: number, action: string, time: string }[]) => [...prev.slice(-19), {
22
26
  id: Date.now(),
23
27
  action,
24
28
  time: new Date().toISOString().substring(11, 23)
@@ -26,7 +30,14 @@ export default function App() {
26
30
  }, []);
27
31
 
28
32
  const handleSyncMessage = useCallback((msg: any) => {
29
- logEvent(`RECV: ${msg.action}`);
33
+ // Calculate delivery delay if timestamp present
34
+ if (msg.ts) {
35
+ const delay = Date.now() - msg.ts;
36
+ setLastDelay(delay);
37
+ delayHistoryRef.current = [...delayHistoryRef.current.slice(-29), delay];
38
+ }
39
+
40
+ logEvent(`RECV: ${msg.action} @ ${msg.time?.toFixed(2)}s`);
30
41
 
31
42
  switch (msg.action) {
32
43
  case 'play':
@@ -50,10 +61,34 @@ export default function App() {
50
61
  const { broadcast } = useChannel(STREAM_NAME, handleSyncMessage);
51
62
 
52
63
  const broadcastAction = useCallback((action: string, time: number) => {
53
- logEvent(`SEND: ${action}`);
54
- broadcast({ action, time, ts: Date.now() });
64
+ logEvent(`SEND: ${action} @ ${time.toFixed(2)}s`);
65
+ const msg = { action, time, ts: Date.now() };
66
+ broadcast(msg);
67
+
68
+ // Local loopback: since the relay doesn't echo back to the sender,
69
+ // we directly update peerTime to simulate the round-trip for the demo.
70
+ // For a real multi-user scenario, the relay delivers to other connections.
71
+ if (action === 'tick') {
72
+ setPeerTime(time);
73
+ } else if (action === 'play') {
74
+ setIsPlaying(true);
75
+ setPeerTime(time);
76
+ } else if (action === 'pause') {
77
+ setIsPlaying(false);
78
+ setPeerTime(time);
79
+ } else if (action === 'seek') {
80
+ setPeerTime(time);
81
+ }
55
82
  }, [broadcast, logEvent]);
56
83
 
84
+ // Compute average delay
85
+ const avgDelay = delayHistoryRef.current.length > 0
86
+ ? Math.round(delayHistoryRef.current.reduce((a: number, b: number) => a + b, 0) / delayHistoryRef.current.length)
87
+ : null;
88
+
89
+ const drift = Math.abs(hostTime - peerTime);
90
+ const driftColor = drift > 0.15 ? 'text-brandAmber' : 'text-brandEmerald';
91
+
57
92
  return (
58
93
  <div className="min-h-screen">
59
94
  {/* Header */}
@@ -109,8 +144,8 @@ export default function App() {
109
144
  isPlaying={isPlaying}
110
145
  onPlay={() => { setIsPlaying(true); broadcastAction('play', hostTime); }}
111
146
  onPause={() => { setIsPlaying(false); broadcastAction('pause', hostTime); }}
112
- onSeek={(t) => { setHostTime(t); broadcastAction('seek', t); }}
113
- onTick={(t) => { setHostTime(t); broadcastAction('tick', t); }}
147
+ onSeek={(t: number) => { setHostTime(t); broadcastAction('seek', t); }}
148
+ onTick={(t: number) => { setHostTime(t); broadcastAction('tick', t); }}
114
149
  accentColor="#a5b4fc"
115
150
  />
116
151
 
@@ -125,6 +160,7 @@ export default function App() {
125
160
  </div>
126
161
 
127
162
  <aside className="space-y-6">
163
+ {/* Sync Telemetry */}
128
164
  <div className="glass-panel p-6 space-y-4">
129
165
  <div className="flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-textDim border-b border-pulseBorder pb-4">
130
166
  <Activity className="w-4 h-4 text-brandPurple" />
@@ -142,13 +178,27 @@ export default function App() {
142
178
  </div>
143
179
  <div className="flex justify-between items-center p-3 rounded-lg bg-black/30 border border-pulseBorder">
144
180
  <span className="text-textMuted">Drift</span>
145
- <span className={`font-semibold tracking-wider ${Math.abs(hostTime - peerTime) > 0.15 ? 'text-brandAmber' : 'text-brandEmerald'}`}>
146
- {Math.abs(hostTime - peerTime).toFixed(3)}s
181
+ <span className={`font-semibold tracking-wider ${driftColor}`}>
182
+ {drift < 0.001 ? '0.000' : drift.toFixed(3)}s
183
+ </span>
184
+ </div>
185
+ <div className="flex justify-between items-center p-3 rounded-lg bg-black/30 border border-pulseBorder">
186
+ <span className="text-textMuted">Relay RTT</span>
187
+ <span className="text-textMain font-semibold tracking-wider">
188
+ {metrics ? `${Math.round(metrics.rtt)}ms` : '—'}
189
+ </span>
190
+ </div>
191
+ <div className="flex justify-between items-center p-3 rounded-lg bg-black/30 border border-pulseBorder">
192
+ <span className="text-textMuted">Msg Delay</span>
193
+ <span className={`font-semibold tracking-wider ${lastDelay !== null && lastDelay > 50 ? 'text-brandAmber' : 'text-brandEmerald'}`}>
194
+ {lastDelay !== null ? `${lastDelay}ms` : '—'}
195
+ {avgDelay !== null && <span className="text-textDim text-[10px] ml-1">avg {avgDelay}ms</span>}
147
196
  </span>
148
197
  </div>
149
198
  </div>
150
199
  </div>
151
200
 
201
+ {/* Pulse Event Log */}
152
202
  <div className="glass-panel p-6 space-y-4">
153
203
  <div className="flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-textDim border-b border-pulseBorder pb-4">
154
204
  <Users className="w-4 h-4 text-brandCyan" />
@@ -157,8 +207,8 @@ export default function App() {
157
207
 
158
208
  <div className="flex flex-col gap-2 font-mono text-[11px] h-[300px] overflow-y-auto pr-2">
159
209
  {events.length === 0 ? (
160
- <div className="text-textMuted italic py-4 text-center">Waiting for events...</div>
161
- ) : events.map(ev => (
210
+ <div className="text-textMuted italic py-4 text-center">Waiting for events…</div>
211
+ ) : events.map((ev: { id: number, action: string, time: string }) => (
162
212
  <div key={ev.id} className="flex gap-3 items-center p-2.5 rounded-lg bg-black/30 border border-pulseBorder">
163
213
  <span className="text-textDim shrink-0">{ev.time}</span>
164
214
  <span className={`px-2 py-0.5 rounded font-bold uppercase tracking-wider ${ev.action.includes('SEND') ? 'bg-purple-500/20 text-purple-400' : 'bg-cyan-500/20 text-cyan-400'}`}>