relayx-webjs 1.0.0

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.
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "relay-chat-demo",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "dependencies": {
11
+ "react": "^18.2.0",
12
+ "react-dom": "^18.2.0",
13
+ "react-router-dom": "^6.23.0",
14
+ "framer-motion": "^11.0.0",
15
+ "lucide-react": "^0.295.0"
16
+ },
17
+ "devDependencies": {
18
+ "@vitejs/plugin-react": "^4.1.0",
19
+ "tailwindcss": "^3.4.0",
20
+ "autoprefixer": "^10.4.0",
21
+ "postcss": "^8.4.0",
22
+ "vite": "^5.0.0"
23
+ }
24
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,193 @@
1
+ import React, { useState, useEffect, useRef, createContext, useContext } from 'react';
2
+ import {
3
+ BrowserRouter as Router,
4
+ Routes,
5
+ Route,
6
+ useNavigate,
7
+ useParams,
8
+ } from 'react-router-dom';
9
+ import { Realtime, CONNECTED, DISCONNECTED, RECONNECT } from '../../../realtime/realtime.js';
10
+ import { Button } from '@/components/ui/button';
11
+ import { Card, CardContent } from '@/components/ui/card';
12
+ import { Input } from '@/components/ui/input';
13
+ import { SendHorizonal } from 'lucide-react';
14
+ import { motion } from 'framer-motion';
15
+
16
+ /**
17
+ * Environment vars expected (add to your .env.* file or CI secrets)
18
+ * VITE_RELAY_API_KEY=<your_api_key>
19
+ * VITE_RELAY_SECRET=<your_secret_key>
20
+ */
21
+
22
+ /* -------------------------------------------------------------------------------------------------
23
+ * Relay context – one connection app‑wide
24
+ * ------------------------------------------------------------------------------------------------*/
25
+ const RelayContext = createContext(null);
26
+
27
+ function RelayProvider({ children }) {
28
+ const realtimeRef = useRef(null);
29
+ const [connected, setConnected] = useState(false);
30
+
31
+ // Connect exactly once – on mount of the provider
32
+ useEffect(() => {
33
+ const rt = new Realtime({
34
+ api_key: import.meta.env.VITE_API_KEY,
35
+ secret: import.meta.env.VITE_SECRET,
36
+ });
37
+
38
+ rt.init({
39
+ debug: true
40
+ });
41
+
42
+ rt.on(CONNECTED, () => setConnected(true));
43
+ rt.on(DISCONNECTED, () => setConnected(false));
44
+ rt.on(RECONNECT, () => setConnected(false));
45
+
46
+ rt.connect(); // 🔗 now "on mount" of the provider
47
+
48
+ realtimeRef.current = rt;
49
+ return () => rt.close();
50
+ }, []);
51
+
52
+ return (
53
+ <RelayContext.Provider value={{ realtime: realtimeRef.current, connected }}>
54
+ {children}
55
+ </RelayContext.Provider>
56
+ );
57
+ }
58
+
59
+ function useRelay() {
60
+ const ctx = useContext(RelayContext);
61
+ if (!ctx) throw new Error('useRelay must be used inside <RelayProvider>');
62
+ return ctx;
63
+ }
64
+
65
+ /* -------------------------------------------------------------------------------------------------
66
+ * Landing page – pick a room
67
+ * ------------------------------------------------------------------------------------------------*/
68
+ function LandingPage() {
69
+ const [room, setRoom] = useState('');
70
+ const navigate = useNavigate();
71
+
72
+ const joinRoom = () => {
73
+ if (!room.trim()) return;
74
+ navigate(`/chat/${encodeURIComponent(room.trim())}`);
75
+ };
76
+
77
+ return (
78
+ <motion.div
79
+ className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-tr from-indigo-50 to-sky-100 p-4"
80
+ initial={{ opacity: 0 }}
81
+ animate={{ opacity: 1 }}
82
+ >
83
+ <Card className="w-full max-w-md shadow-2xl">
84
+ <CardContent className="flex flex-col gap-4 p-8">
85
+ <h1 className="text-center text-3xl font-bold tracking-tight text-indigo-700">
86
+ Relay Chat Demo
87
+ </h1>
88
+ <Input
89
+ placeholder="Enter room name"
90
+ value={room}
91
+ onChange={(e) => setRoom(e.target.value)}
92
+ className="text-lg"
93
+ />
94
+ <Button onClick={joinRoom} disabled={!room.trim()} size="lg">
95
+ Join
96
+ </Button>
97
+ </CardContent>
98
+ </Card>
99
+ </motion.div>
100
+ );
101
+ }
102
+
103
+ /* -------------------------------------------------------------------------------------------------
104
+ * Chat page – uses the shared connection; subscribes on mount
105
+ * ------------------------------------------------------------------------------------------------*/
106
+ function ChatPage() {
107
+ const { room } = useParams();
108
+ const { realtime, connected } = useRelay();
109
+ const [input, setInput] = useState('');
110
+ const [messages, setMessages] = useState([]);
111
+
112
+ // Subscribe/unsubscribe to this room when component mounts/unmounts
113
+ useEffect(() => {
114
+ if (!realtime) return; // connection not yet established
115
+
116
+ const handler = (data) =>
117
+ setMessages((prev) => [...prev, { from: 'remote', text: data.data }]);
118
+
119
+ realtime.on(room, handler);
120
+ return () => realtime.off(room, handler);
121
+ }, [realtime, room]);
122
+
123
+ const sendMessage = async () => {
124
+ if (!input.trim() || !realtime) return;
125
+ const ok = await realtime.publish(room, input.trim());
126
+ if (ok) {
127
+ setMessages((prev) => [...prev, { from: 'me', text: input.trim() }]);
128
+ setInput('');
129
+ }
130
+ };
131
+
132
+ return (
133
+ <div className="flex min-h-screen flex-col bg-slate-50">
134
+ <header className="flex items-center justify-between bg-indigo-600 p-4 text-white shadow-md">
135
+ <h2 className="text-xl font-semibold">Room: {room}</h2>
136
+ <span className={`text-sm ${connected ? 'text-emerald-300' : 'text-red-300'}`}>
137
+ {connected ? 'Connected' : 'Offline'}
138
+ </span>
139
+ </header>
140
+
141
+ <main className="flex-1 overflow-y-auto p-4">
142
+ {messages.length === 0 && (
143
+ <p className="mt-4 text-center text-gray-500">No messages yet – start the conversation!</p>
144
+ )}
145
+
146
+ {messages.map((m, i) => (
147
+ <motion.div
148
+ key={i}
149
+ className={`mb-2 max-w-[75%] rounded-2xl px-4 py-2 text-base shadow ${
150
+ m.from === 'me'
151
+ ? 'ml-auto bg-indigo-500 text-white'
152
+ : 'mr-auto bg-white text-gray-900'
153
+ }`}
154
+ initial={{ y: 10, opacity: 0 }}
155
+ animate={{ y: 0, opacity: 1 }}
156
+ >
157
+ {m.text}
158
+ </motion.div>
159
+ ))}
160
+ </main>
161
+
162
+ <footer className="flex items-center gap-2 border-t bg-white p-4 shadow-lg">
163
+ <Input
164
+ value={input}
165
+ onChange={(e) => setInput(e.target.value)}
166
+ onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
167
+ placeholder={connected ? 'Type a message…' : 'Connecting to Relay…'}
168
+ disabled={!connected}
169
+ className="flex-1"
170
+ />
171
+ <Button size="icon" onClick={sendMessage} disabled={!connected || !input.trim()}>
172
+ <SendHorizonal className="h-5 w-5" />
173
+ </Button>
174
+ </footer>
175
+ </div>
176
+ );
177
+ }
178
+
179
+ /* -------------------------------------------------------------------------------------------------
180
+ * Root app
181
+ * ------------------------------------------------------------------------------------------------*/
182
+ export default function App() {
183
+ return (
184
+ <Router>
185
+ <RelayProvider>
186
+ <Routes>
187
+ <Route path="/" element={<LandingPage />} />
188
+ <Route path="/chat/:room" element={<ChatPage />} />
189
+ </Routes>
190
+ </RelayProvider>
191
+ </Router>
192
+ );
193
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+
3
+ export function Button({ children, size = 'md', disabled, className = '', ...props }) {
4
+ const base = 'inline-flex items-center justify-center rounded-2xl shadow font-medium transition';
5
+ const sizes = {
6
+ icon: 'h-10 w-10',
7
+ lg: 'h-11 px-6 text-lg',
8
+ md: 'h-10 px-4',
9
+ };
10
+ const cls = `${base} bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50 ${sizes[size]} ${className}`;
11
+ return (
12
+ <button className={cls} disabled={disabled} {...props}>
13
+ {children}
14
+ </button>
15
+ );
16
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+
3
+ export function Card({ className = '', children }) {
4
+ return <div className={`rounded-2xl bg-white ${className}`}>{children}</div>;
5
+ }
6
+
7
+ export function CardContent({ className = '', children }) {
8
+ return <div className={className}>{children}</div>;
9
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+
3
+ export const Input = React.forwardRef(function Input({ className = '', ...props }, ref) {
4
+ return (
5
+ <input
6
+ ref={ref}
7
+ className={`rounded-xl border border-slate-300 p-3 focus:border-indigo-500 focus:outline-none ${className}`}
8
+ {...props}
9
+ />
10
+ );
11
+ });
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App.jsx';
4
+ import './index.css';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
@@ -0,0 +1,8 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [],
8
+ };
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ '@': path.resolve(__dirname, 'src'),
10
+ },
11
+ },
12
+ });
Binary file
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "relayx-webjs",
3
+ "version": "1.0.0",
4
+ "description": "A powerful library for integrating real-time communication into your webapps, powered by the Relay Network.",
5
+ "main": "realtime/realtime.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "NODE_ENV=test node tests/test.js"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Realtime-Relay/relayx-webjs.git"
13
+ },
14
+ "keywords": [
15
+ "realtime",
16
+ "realtime-communication",
17
+ "relay",
18
+ "relay-x",
19
+ "relayx-webjs"
20
+ ],
21
+ "author": "Relay",
22
+ "license": "Apache 2.0",
23
+ "bugs": {
24
+ "url": "https://github.com/Realtime-Relay/relayx-webjs/issues"
25
+ },
26
+ "homepage": "https://github.com/Realtime-Relay/relayx-webjs#readme",
27
+ "dependencies": {
28
+ "@msgpack/msgpack": "^3.1.2",
29
+ "@nats-io/jetstream": "3.0.0-35",
30
+ "jest": "30.0.4",
31
+ "nats.ws": "1.30.3",
32
+ "uuid": "11.1.0"
33
+ }
34
+ }