@sansavision/create-pulse 0.4.0 → 0.4.2
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/README.md +27 -3
- package/dist/index.js +3 -1
- package/package.json +2 -2
- package/templates/nextjs-auth-demo/.env.example +6 -0
- package/templates/nextjs-auth-demo/README.md +74 -0
- package/templates/nextjs-auth-demo/_gitignore +33 -0
- package/templates/nextjs-auth-demo/drizzle.config.ts +10 -0
- package/templates/nextjs-auth-demo/eslint.config.mjs +18 -0
- package/templates/nextjs-auth-demo/next-env.d.ts +6 -0
- package/templates/nextjs-auth-demo/next.config.ts +7 -0
- package/templates/nextjs-auth-demo/package.json +34 -0
- package/templates/nextjs-auth-demo/postcss.config.mjs +7 -0
- package/templates/nextjs-auth-demo/public/file.svg +1 -0
- package/templates/nextjs-auth-demo/public/globe.svg +1 -0
- package/templates/nextjs-auth-demo/public/next.svg +1 -0
- package/templates/nextjs-auth-demo/public/vercel.svg +1 -0
- package/templates/nextjs-auth-demo/public/window.svg +1 -0
- package/templates/nextjs-auth-demo/src/app/api/auth/[...all]/route.ts +4 -0
- package/templates/nextjs-auth-demo/src/app/api/pulse/verify/route.ts +54 -0
- package/templates/nextjs-auth-demo/src/app/auth/sign-in/page.tsx +131 -0
- package/templates/nextjs-auth-demo/src/app/auth/sign-up/page.tsx +153 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/chat/page.tsx +248 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/encrypted-chat/page.tsx +198 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/game-sync/page.tsx +192 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/queues/page.tsx +297 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/watch-together/page.tsx +258 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/layout.tsx +109 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/page.tsx +147 -0
- package/templates/nextjs-auth-demo/src/app/favicon.ico +0 -0
- package/templates/nextjs-auth-demo/src/app/globals.css +96 -0
- package/templates/nextjs-auth-demo/src/app/layout.tsx +27 -0
- package/templates/nextjs-auth-demo/src/app/page.tsx +254 -0
- package/templates/nextjs-auth-demo/src/lib/auth-client.ts +15 -0
- package/templates/nextjs-auth-demo/src/lib/auth.ts +13 -0
- package/templates/nextjs-auth-demo/src/lib/db.ts +5 -0
- package/templates/nextjs-auth-demo/src/lib/pulse.ts +45 -0
- package/templates/nextjs-auth-demo/tsconfig.json +34 -0
- package/templates/react-all-features/package.json +2 -2
- package/templates/react-all-features/src/App.tsx +20 -39
- package/templates/react-all-features/src/components/EncryptedChat.tsx +8 -8
- package/templates/react-all-features/src/components/GameSync.tsx +38 -23
- package/templates/react-all-features/src/components/ServerMetrics.tsx +20 -15
- package/templates/react-queue-demo/README.md +6 -7
- package/templates/react-queue-demo/package.json +1 -1
- package/templates/react-queue-demo/src/App.tsx +229 -62
- package/templates/react-watch-together/package.json +2 -2
- package/templates/react-watch-together/src/App.tsx +18 -40
- package/src/index.ts +0 -115
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { useSession } from "@/lib/auth-client";
|
|
5
|
+
import {
|
|
6
|
+
MessageSquare,
|
|
7
|
+
Video,
|
|
8
|
+
Database,
|
|
9
|
+
Gamepad2,
|
|
10
|
+
Lock,
|
|
11
|
+
ArrowRight,
|
|
12
|
+
Shield,
|
|
13
|
+
Wifi,
|
|
14
|
+
Activity,
|
|
15
|
+
} from "lucide-react";
|
|
16
|
+
|
|
17
|
+
const demos = [
|
|
18
|
+
{
|
|
19
|
+
href: "/dashboard/demos/chat",
|
|
20
|
+
icon: MessageSquare,
|
|
21
|
+
title: "Real-time Chat",
|
|
22
|
+
desc: "Multi-user rooms with presence, typing indicators, and auth identity. Open a second tab with a different user to see live messaging.",
|
|
23
|
+
color: "from-blue-500 to-blue-600",
|
|
24
|
+
badge: "Pub/Sub",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
href: "/dashboard/demos/watch-together",
|
|
28
|
+
icon: Video,
|
|
29
|
+
title: "Watch Together",
|
|
30
|
+
desc: "Synchronized video playback. Play, pause, and seek in perfect sync across all connected users.",
|
|
31
|
+
color: "from-pink-500 to-rose-600",
|
|
32
|
+
badge: "Broadcast",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
href: "/dashboard/demos/queues",
|
|
36
|
+
icon: Database,
|
|
37
|
+
title: "Durable Queues",
|
|
38
|
+
desc: "Publish messages, simulate going offline, come back online — messages persist and are delivered. Full ACK/NACK flow.",
|
|
39
|
+
color: "from-amber-500 to-orange-600",
|
|
40
|
+
badge: "Queue",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
href: "/dashboard/demos/game-sync",
|
|
44
|
+
icon: Gamepad2,
|
|
45
|
+
title: "Game State Sync",
|
|
46
|
+
desc: "Shared game board with real-time state sync. Multiple users can interact with the same game state simultaneously.",
|
|
47
|
+
color: "from-green-500 to-emerald-600",
|
|
48
|
+
badge: "State",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
href: "/dashboard/demos/encrypted-chat",
|
|
52
|
+
icon: Lock,
|
|
53
|
+
title: "E2E Encrypted Chat",
|
|
54
|
+
desc: "End-to-end encrypted messaging where the relay cannot read content. Key exchange visualization included.",
|
|
55
|
+
color: "from-purple-500 to-violet-600",
|
|
56
|
+
badge: "P2P",
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export default function DashboardPage() {
|
|
61
|
+
const { data: session } = useSession();
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="p-8">
|
|
65
|
+
{/* Header */}
|
|
66
|
+
<div className="mb-8">
|
|
67
|
+
<h1 className="text-3xl font-bold mb-2">
|
|
68
|
+
Welcome, {session?.user?.name?.split(" ")[0] || "there"} 👋
|
|
69
|
+
</h1>
|
|
70
|
+
<p className="text-slate-400 text-lg">
|
|
71
|
+
Choose a demo to explore Pulse's capabilities with authenticated
|
|
72
|
+
connections.
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
{/* Connection status */}
|
|
77
|
+
<div className="glass rounded-xl p-5 mb-8 flex items-center gap-6">
|
|
78
|
+
<div className="flex items-center gap-2">
|
|
79
|
+
<Shield className="w-4 h-4 text-green-400" />
|
|
80
|
+
<span className="text-sm text-green-400 font-medium">
|
|
81
|
+
Authenticated
|
|
82
|
+
</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
<Wifi className="w-4 h-4 text-cyan-400" />
|
|
86
|
+
<span className="text-sm text-slate-400">
|
|
87
|
+
Relay:{" "}
|
|
88
|
+
<code className="text-cyan-400 bg-cyan-500/10 px-1.5 py-0.5 rounded text-xs">
|
|
89
|
+
{process.env.NEXT_PUBLIC_PULSE_URL || "ws://localhost:4001"}
|
|
90
|
+
</code>
|
|
91
|
+
</span>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="flex items-center gap-2">
|
|
94
|
+
<Activity className="w-4 h-4 text-purple-400" />
|
|
95
|
+
<span className="text-sm text-slate-400">
|
|
96
|
+
Auth Mode:{" "}
|
|
97
|
+
<code className="text-purple-400 bg-purple-500/10 px-1.5 py-0.5 rounded text-xs">
|
|
98
|
+
webhook
|
|
99
|
+
</code>
|
|
100
|
+
</span>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* Tip */}
|
|
105
|
+
<div className="glass rounded-xl p-5 mb-8 border-l-4 border-purple-500">
|
|
106
|
+
<h3 className="text-sm font-semibold text-purple-400 mb-1">
|
|
107
|
+
💡 Multi-User Tip
|
|
108
|
+
</h3>
|
|
109
|
+
<p className="text-sm text-slate-400">
|
|
110
|
+
Open a second browser tab (or incognito window), create a different
|
|
111
|
+
account, and interact simultaneously. Watch messages, video sync, and
|
|
112
|
+
game state flow in real-time between users.
|
|
113
|
+
</p>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
{/* Demo cards */}
|
|
117
|
+
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
118
|
+
{demos.map((demo) => (
|
|
119
|
+
<Link key={demo.href} href={demo.href}>
|
|
120
|
+
<div className="demo-card glass rounded-2xl p-6 h-full cursor-pointer group">
|
|
121
|
+
<div className="flex items-start justify-between mb-4">
|
|
122
|
+
<div
|
|
123
|
+
className={`w-12 h-12 rounded-xl bg-gradient-to-br ${demo.color} flex items-center justify-center`}
|
|
124
|
+
>
|
|
125
|
+
<demo.icon className="w-6 h-6 text-white" />
|
|
126
|
+
</div>
|
|
127
|
+
<span className="text-xs font-mono px-2 py-1 rounded-md bg-slate-800 text-slate-400 border border-slate-700">
|
|
128
|
+
{demo.badge}
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
<h3 className="text-lg font-semibold mb-2 group-hover:text-purple-400 transition-colors">
|
|
132
|
+
{demo.title}
|
|
133
|
+
</h3>
|
|
134
|
+
<p className="text-sm text-slate-400 leading-relaxed mb-4">
|
|
135
|
+
{demo.desc}
|
|
136
|
+
</p>
|
|
137
|
+
<div className="flex items-center gap-1 text-sm text-purple-400 font-medium">
|
|
138
|
+
Open Demo
|
|
139
|
+
<ArrowRight className="w-3.5 h-3.5 group-hover:translate-x-1 transition-transform" />
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</Link>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--color-primary: #7c3aed;
|
|
5
|
+
--color-primary-light: #a78bfa;
|
|
6
|
+
--color-accent: #06b6d4;
|
|
7
|
+
--color-surface: #0f172a;
|
|
8
|
+
--color-surface-elevated: #1e293b;
|
|
9
|
+
--color-surface-hover: #334155;
|
|
10
|
+
--color-text: #f1f5f9;
|
|
11
|
+
--color-text-muted: #94a3b8;
|
|
12
|
+
--color-border: #334155;
|
|
13
|
+
--color-danger: #ef4444;
|
|
14
|
+
--color-success: #10b981;
|
|
15
|
+
--color-warning: #f59e0b;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
* {
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
body {
|
|
23
|
+
font-family: "Inter", "SF Pro Display", -apple-system, system-ui, sans-serif;
|
|
24
|
+
background: var(--color-surface);
|
|
25
|
+
color: var(--color-text);
|
|
26
|
+
-webkit-font-smoothing: antialiased;
|
|
27
|
+
-moz-osx-font-smoothing: grayscale;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Smooth scrollbar */
|
|
31
|
+
::-webkit-scrollbar {
|
|
32
|
+
width: 6px;
|
|
33
|
+
}
|
|
34
|
+
::-webkit-scrollbar-track {
|
|
35
|
+
background: transparent;
|
|
36
|
+
}
|
|
37
|
+
::-webkit-scrollbar-thumb {
|
|
38
|
+
background: var(--color-border);
|
|
39
|
+
border-radius: 3px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Glassmorphism utility */
|
|
43
|
+
.glass {
|
|
44
|
+
background: rgba(30, 41, 59, 0.7);
|
|
45
|
+
backdrop-filter: blur(16px);
|
|
46
|
+
-webkit-backdrop-filter: blur(16px);
|
|
47
|
+
border: 1px solid rgba(148, 163, 184, 0.1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Gradient text */
|
|
51
|
+
.gradient-text {
|
|
52
|
+
background: linear-gradient(135deg, #7c3aed, #06b6d4, #10b981);
|
|
53
|
+
-webkit-background-clip: text;
|
|
54
|
+
-webkit-text-fill-color: transparent;
|
|
55
|
+
background-clip: text;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Pulse animation */
|
|
59
|
+
@keyframes pulse-glow {
|
|
60
|
+
0%, 100% { box-shadow: 0 0 20px rgba(124, 58, 237, 0.3); }
|
|
61
|
+
50% { box-shadow: 0 0 40px rgba(124, 58, 237, 0.6); }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.pulse-glow {
|
|
65
|
+
animation: pulse-glow 2s ease-in-out infinite;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Status indicator */
|
|
69
|
+
@keyframes status-blink {
|
|
70
|
+
0%, 100% { opacity: 1; }
|
|
71
|
+
50% { opacity: 0.5; }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.status-online {
|
|
75
|
+
width: 8px;
|
|
76
|
+
height: 8px;
|
|
77
|
+
border-radius: 50%;
|
|
78
|
+
background: var(--color-success);
|
|
79
|
+
animation: status-blink 2s ease-in-out infinite;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.status-offline {
|
|
83
|
+
width: 8px;
|
|
84
|
+
height: 8px;
|
|
85
|
+
border-radius: 50%;
|
|
86
|
+
background: var(--color-danger);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Card hover effect */
|
|
90
|
+
.demo-card {
|
|
91
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
92
|
+
}
|
|
93
|
+
.demo-card:hover {
|
|
94
|
+
transform: translateY(-4px);
|
|
95
|
+
box-shadow: 0 20px 60px rgba(124, 58, 237, 0.15);
|
|
96
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import "./globals.css";
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: "Pulse — Real-Time Protocol Demo",
|
|
6
|
+
description:
|
|
7
|
+
"Investor-ready demo showcasing PLP (Pulse Line Protocol) with auth, real-time chat, synchronized video, durable queues, and encrypted messaging.",
|
|
8
|
+
keywords: ["pulse", "real-time", "websocket", "protocol", "auth"],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default function RootLayout({
|
|
12
|
+
children,
|
|
13
|
+
}: {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<html lang="en" className="dark">
|
|
18
|
+
<head>
|
|
19
|
+
<link
|
|
20
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap"
|
|
21
|
+
rel="stylesheet"
|
|
22
|
+
/>
|
|
23
|
+
</head>
|
|
24
|
+
<body className="min-h-screen antialiased">{children}</body>
|
|
25
|
+
</html>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import {
|
|
5
|
+
Zap,
|
|
6
|
+
Shield,
|
|
7
|
+
Wifi,
|
|
8
|
+
MessageSquare,
|
|
9
|
+
Video,
|
|
10
|
+
Database,
|
|
11
|
+
Gamepad2,
|
|
12
|
+
Lock,
|
|
13
|
+
ArrowRight,
|
|
14
|
+
Globe,
|
|
15
|
+
Clock,
|
|
16
|
+
Activity,
|
|
17
|
+
} from "lucide-react";
|
|
18
|
+
|
|
19
|
+
export default function LandingPage() {
|
|
20
|
+
return (
|
|
21
|
+
<div className="min-h-screen">
|
|
22
|
+
{/* Nav */}
|
|
23
|
+
<nav className="fixed top-0 left-0 right-0 z-50 glass">
|
|
24
|
+
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
25
|
+
<div className="flex items-center gap-2">
|
|
26
|
+
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-purple-500 to-cyan-500 flex items-center justify-center">
|
|
27
|
+
<Zap className="w-5 h-5 text-white" />
|
|
28
|
+
</div>
|
|
29
|
+
<span className="text-xl font-bold">Pulse</span>
|
|
30
|
+
</div>
|
|
31
|
+
<div className="flex items-center gap-4">
|
|
32
|
+
<Link
|
|
33
|
+
href="/auth/sign-in"
|
|
34
|
+
className="px-4 py-2 text-sm text-slate-300 hover:text-white transition-colors"
|
|
35
|
+
>
|
|
36
|
+
Sign In
|
|
37
|
+
</Link>
|
|
38
|
+
<Link
|
|
39
|
+
href="/auth/sign-up"
|
|
40
|
+
className="px-5 py-2 text-sm bg-purple-600 hover:bg-purple-500 rounded-lg font-medium transition-all hover:shadow-lg hover:shadow-purple-500/25"
|
|
41
|
+
>
|
|
42
|
+
Get Started
|
|
43
|
+
</Link>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</nav>
|
|
47
|
+
|
|
48
|
+
{/* Hero */}
|
|
49
|
+
<section className="relative pt-32 pb-20 px-6 overflow-hidden">
|
|
50
|
+
{/* Background gradient orbs */}
|
|
51
|
+
<div className="absolute top-20 left-1/4 w-96 h-96 bg-purple-500/20 rounded-full blur-[128px]" />
|
|
52
|
+
<div className="absolute top-40 right-1/4 w-96 h-96 bg-cyan-500/20 rounded-full blur-[128px]" />
|
|
53
|
+
|
|
54
|
+
<div className="max-w-5xl mx-auto text-center relative z-10">
|
|
55
|
+
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-purple-500/30 bg-purple-500/10 text-purple-300 text-sm mb-8">
|
|
56
|
+
<Activity className="w-3.5 h-3.5" />
|
|
57
|
+
<span>PLP — Pulse Line Protocol v1</span>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<h1 className="text-5xl md:text-7xl font-extrabold tracking-tight leading-tight mb-6">
|
|
61
|
+
<span className="gradient-text">The Real-Time Protocol</span>
|
|
62
|
+
<br />
|
|
63
|
+
<span className="text-slate-200">for Modern Applications</span>
|
|
64
|
+
</h1>
|
|
65
|
+
|
|
66
|
+
<p className="text-xl text-slate-400 max-w-2xl mx-auto mb-10 leading-relaxed">
|
|
67
|
+
Sub-millisecond latency, end-to-end encryption, durable queues, and
|
|
68
|
+
external auth — all in one binary. Built with Rust, powered by QUIC.
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
<div className="flex items-center justify-center gap-4">
|
|
72
|
+
<Link
|
|
73
|
+
href="/auth/sign-up"
|
|
74
|
+
className="inline-flex items-center gap-2 px-8 py-3.5 bg-gradient-to-r from-purple-600 to-purple-500 hover:from-purple-500 hover:to-purple-400 rounded-xl font-semibold text-lg transition-all hover:shadow-xl hover:shadow-purple-500/25 pulse-glow"
|
|
75
|
+
>
|
|
76
|
+
Try the Demo
|
|
77
|
+
<ArrowRight className="w-5 h-5" />
|
|
78
|
+
</Link>
|
|
79
|
+
<a
|
|
80
|
+
href="https://github.com/Sansa-Organisation/pulse"
|
|
81
|
+
target="_blank"
|
|
82
|
+
rel="noopener"
|
|
83
|
+
className="px-8 py-3.5 rounded-xl font-semibold text-lg border border-slate-700 hover:border-slate-500 hover:bg-slate-800/50 transition-all"
|
|
84
|
+
>
|
|
85
|
+
GitHub
|
|
86
|
+
</a>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* Stats */}
|
|
90
|
+
<div className="grid grid-cols-3 gap-8 max-w-xl mx-auto mt-16">
|
|
91
|
+
{[
|
|
92
|
+
{ label: "Latency", value: "<1ms", icon: Clock },
|
|
93
|
+
{ label: "Connections", value: "100K+", icon: Globe },
|
|
94
|
+
{ label: "Uptime", value: "99.99%", icon: Activity },
|
|
95
|
+
].map((stat) => (
|
|
96
|
+
<div key={stat.label} className="text-center">
|
|
97
|
+
<stat.icon className="w-5 h-5 text-purple-400 mx-auto mb-2" />
|
|
98
|
+
<div className="text-2xl font-bold">{stat.value}</div>
|
|
99
|
+
<div className="text-sm text-slate-500">{stat.label}</div>
|
|
100
|
+
</div>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</section>
|
|
105
|
+
|
|
106
|
+
{/* Features */}
|
|
107
|
+
<section className="py-20 px-6">
|
|
108
|
+
<div className="max-w-6xl mx-auto">
|
|
109
|
+
<div className="text-center mb-16">
|
|
110
|
+
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
|
111
|
+
Everything you need, out of the box
|
|
112
|
+
</h2>
|
|
113
|
+
<p className="text-slate-400 text-lg max-w-xl mx-auto">
|
|
114
|
+
One protocol, five powerful modes. Each demo below is fully functional
|
|
115
|
+
and uses real PLP connections.
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
120
|
+
{[
|
|
121
|
+
{
|
|
122
|
+
icon: MessageSquare,
|
|
123
|
+
title: "Real-time Chat",
|
|
124
|
+
desc: "Multi-user rooms with typing indicators, presence, and message history.",
|
|
125
|
+
color: "from-blue-500 to-blue-600",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
icon: Video,
|
|
129
|
+
title: "Watch Together",
|
|
130
|
+
desc: "Synchronized video playback — play, pause, seek in perfect sync.",
|
|
131
|
+
color: "from-pink-500 to-rose-600",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
icon: Database,
|
|
135
|
+
title: "Durable Queues",
|
|
136
|
+
desc: "Persist messages, simulate offline, come back online — nothing lost.",
|
|
137
|
+
color: "from-amber-500 to-orange-600",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
icon: Gamepad2,
|
|
141
|
+
title: "Game State Sync",
|
|
142
|
+
desc: "Real-time shared state with conflict-free resolution.",
|
|
143
|
+
color: "from-green-500 to-emerald-600",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
icon: Lock,
|
|
147
|
+
title: "E2E Encrypted Chat",
|
|
148
|
+
desc: "End-to-end encryption with relay-blind transport.",
|
|
149
|
+
color: "from-purple-500 to-violet-600",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
icon: Shield,
|
|
153
|
+
title: "Auth Context",
|
|
154
|
+
desc: "External auth integration — Better Auth, Clerk, Auth0, and more.",
|
|
155
|
+
color: "from-cyan-500 to-teal-600",
|
|
156
|
+
},
|
|
157
|
+
].map((feature) => (
|
|
158
|
+
<div
|
|
159
|
+
key={feature.title}
|
|
160
|
+
className="demo-card glass rounded-2xl p-6"
|
|
161
|
+
>
|
|
162
|
+
<div
|
|
163
|
+
className={`w-12 h-12 rounded-xl bg-gradient-to-br ${feature.color} flex items-center justify-center mb-4`}
|
|
164
|
+
>
|
|
165
|
+
<feature.icon className="w-6 h-6 text-white" />
|
|
166
|
+
</div>
|
|
167
|
+
<h3 className="text-lg font-semibold mb-2">{feature.title}</h3>
|
|
168
|
+
<p className="text-slate-400 text-sm leading-relaxed">
|
|
169
|
+
{feature.desc}
|
|
170
|
+
</p>
|
|
171
|
+
</div>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</section>
|
|
176
|
+
|
|
177
|
+
{/* Architecture */}
|
|
178
|
+
<section className="py-20 px-6">
|
|
179
|
+
<div className="max-w-4xl mx-auto">
|
|
180
|
+
<div className="glass rounded-2xl p-8 md:p-12">
|
|
181
|
+
<h2 className="text-2xl font-bold mb-6 text-center">
|
|
182
|
+
How Auth Works
|
|
183
|
+
</h2>
|
|
184
|
+
<div className="flex flex-col md:flex-row items-center gap-6">
|
|
185
|
+
{[
|
|
186
|
+
{
|
|
187
|
+
step: 1,
|
|
188
|
+
icon: Shield,
|
|
189
|
+
title: "Sign In",
|
|
190
|
+
desc: "User authenticates via Better Auth (email/password)",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
step: 2,
|
|
194
|
+
icon: Wifi,
|
|
195
|
+
title: "Connect",
|
|
196
|
+
desc: "SDK sends bearer token in PLP CONNECT handshake",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
step: 3,
|
|
200
|
+
icon: Zap,
|
|
201
|
+
title: "Verify",
|
|
202
|
+
desc: "Relay calls webhook → Better Auth validates session",
|
|
203
|
+
},
|
|
204
|
+
].map((item, i) => (
|
|
205
|
+
<div key={item.step} className="flex-1 text-center">
|
|
206
|
+
<div className="w-10 h-10 rounded-full bg-purple-600 text-white font-bold flex items-center justify-center mx-auto mb-3">
|
|
207
|
+
{item.step}
|
|
208
|
+
</div>
|
|
209
|
+
<item.icon className="w-6 h-6 text-purple-400 mx-auto mb-2" />
|
|
210
|
+
<h4 className="font-semibold mb-1">{item.title}</h4>
|
|
211
|
+
<p className="text-sm text-slate-400">{item.desc}</p>
|
|
212
|
+
{i < 2 && (
|
|
213
|
+
<ArrowRight className="w-5 h-5 text-slate-600 mx-auto mt-4 hidden md:block rotate-0" />
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
))}
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</section>
|
|
221
|
+
|
|
222
|
+
{/* CTA */}
|
|
223
|
+
<section className="py-20 px-6">
|
|
224
|
+
<div className="max-w-3xl mx-auto text-center">
|
|
225
|
+
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
|
226
|
+
Ready to experience it?
|
|
227
|
+
</h2>
|
|
228
|
+
<p className="text-slate-400 text-lg mb-8">
|
|
229
|
+
Create an account and explore every feature live. Open two browser
|
|
230
|
+
tabs to see multi-user sync in action.
|
|
231
|
+
</p>
|
|
232
|
+
<Link
|
|
233
|
+
href="/auth/sign-up"
|
|
234
|
+
className="inline-flex items-center gap-2 px-10 py-4 bg-gradient-to-r from-purple-600 to-cyan-600 hover:from-purple-500 hover:to-cyan-500 rounded-xl font-semibold text-lg transition-all hover:shadow-xl hover:shadow-purple-500/25"
|
|
235
|
+
>
|
|
236
|
+
Create Account
|
|
237
|
+
<ArrowRight className="w-5 h-5" />
|
|
238
|
+
</Link>
|
|
239
|
+
</div>
|
|
240
|
+
</section>
|
|
241
|
+
|
|
242
|
+
{/* Footer */}
|
|
243
|
+
<footer className="border-t border-slate-800 py-8 px-6">
|
|
244
|
+
<div className="max-w-6xl mx-auto flex items-center justify-between text-sm text-slate-500">
|
|
245
|
+
<span>© 2026 Sansa Vision. Pulse Protocol.</span>
|
|
246
|
+
<div className="flex items-center gap-1">
|
|
247
|
+
<div className="status-online" />
|
|
248
|
+
<span>Relay Live</span>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</footer>
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createAuthClient } from "better-auth/react";
|
|
2
|
+
|
|
3
|
+
export const authClient = createAuthClient({
|
|
4
|
+
baseURL: typeof window !== "undefined" ? window.location.origin : "http://localhost:3000",
|
|
5
|
+
fetchOptions: {
|
|
6
|
+
onSuccess: (ctx) => {
|
|
7
|
+
const authToken = ctx.response.headers.get("set-auth-token");
|
|
8
|
+
if (authToken && typeof window !== "undefined") {
|
|
9
|
+
localStorage.setItem("pulse_bearer_token", authToken);
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const { signIn, signUp, signOut, useSession } = authClient;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { betterAuth } from "better-auth";
|
|
2
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
3
|
+
import { bearer } from "better-auth/plugins";
|
|
4
|
+
import { db } from "./db";
|
|
5
|
+
|
|
6
|
+
export const auth = betterAuth({
|
|
7
|
+
database: drizzleAdapter(db, { provider: "sqlite" }),
|
|
8
|
+
emailAndPassword: {
|
|
9
|
+
enabled: true,
|
|
10
|
+
},
|
|
11
|
+
plugins: [bearer()],
|
|
12
|
+
trustedOrigins: [process.env.BETTER_AUTH_URL || "http://localhost:3000"],
|
|
13
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Pulse } from "@sansavision/pulse-sdk";
|
|
4
|
+
|
|
5
|
+
let pulseInstance: Pulse | null = null;
|
|
6
|
+
|
|
7
|
+
export function getPulse(): Pulse {
|
|
8
|
+
if (!pulseInstance) {
|
|
9
|
+
pulseInstance = new Pulse({ apiKey: "demo" });
|
|
10
|
+
}
|
|
11
|
+
return pulseInstance;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the bearer token stored by Better Auth for Pulse auth.
|
|
16
|
+
*/
|
|
17
|
+
export function getPulseToken(): string | null {
|
|
18
|
+
if (typeof window === "undefined") return null;
|
|
19
|
+
return localStorage.getItem("pulse_bearer_token");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Connect to the Pulse relay with authentication.
|
|
24
|
+
* Uses the Better Auth bearer token for the PLP CONNECT handshake.
|
|
25
|
+
*/
|
|
26
|
+
export async function connectWithAuth() {
|
|
27
|
+
const pulse = getPulse();
|
|
28
|
+
const token = getPulseToken();
|
|
29
|
+
const url = process.env.NEXT_PUBLIC_PULSE_URL || "ws://localhost:4001";
|
|
30
|
+
|
|
31
|
+
const conn = await pulse.connect(url, {
|
|
32
|
+
encoding: "json",
|
|
33
|
+
autoReconnect: true,
|
|
34
|
+
...(token
|
|
35
|
+
? {
|
|
36
|
+
auth: {
|
|
37
|
+
token,
|
|
38
|
+
provider: "better-auth",
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
: {}),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return conn;
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"preview": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@sansavision/pulse-sdk": "0.
|
|
12
|
+
"@sansavision/pulse-sdk": "^0.4.1",
|
|
13
13
|
"react": "^18.3.1",
|
|
14
14
|
"react-dom": "^18.3.1",
|
|
15
15
|
"lucide-react": "^0.412.0"
|
|
@@ -24,4 +24,4 @@
|
|
|
24
24
|
"typescript": "^5.5.3",
|
|
25
25
|
"vite": "^5.3.4"
|
|
26
26
|
}
|
|
27
|
-
}
|
|
27
|
+
}
|