api-ape 2.0.0 → 2.2.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 +203 -124
- package/client/README.md +37 -30
- package/client/browser.js +10 -8
- package/client/connectSocket.js +662 -381
- package/client/index.js +171 -0
- package/client/transports/streaming.js +240 -0
- package/dist/ape.js +2 -699
- package/dist/ape.js.map +7 -0
- package/dist/api-ape.min.js +2 -0
- package/dist/api-ape.min.js.map +7 -0
- package/index.d.ts +71 -18
- package/package.json +50 -15
- package/server/README.md +99 -13
- package/server/lib/broadcast.js +25 -8
- package/server/lib/bun.js +122 -0
- package/server/lib/longPolling.js +226 -0
- package/server/lib/main.js +381 -38
- package/server/lib/wiring.js +19 -12
- package/server/lib/ws/adapters/bun.js +225 -0
- package/server/lib/ws/adapters/deno.js +186 -0
- package/server/lib/ws/frames.js +217 -0
- package/server/lib/ws/index.js +15 -0
- package/server/lib/ws/server.js +109 -0
- package/server/lib/ws/socket.js +222 -0
- package/server/lib/wsProvider.js +135 -0
- package/server/security/origin.js +16 -4
- package/server/socket/receive.js +14 -1
- package/server/socket/send.js +6 -6
- package/server/utils/deepRequire.js +25 -10
- package/server/utils/parseUserAgent.js +286 -0
- package/example/Bun/README.md +0 -74
- package/example/Bun/api/message.ts +0 -11
- package/example/Bun/index.html +0 -76
- package/example/Bun/package.json +0 -9
- package/example/Bun/server.ts +0 -59
- package/example/Bun/styles.css +0 -128
- package/example/ExpressJs/README.md +0 -95
- package/example/ExpressJs/api/message.js +0 -11
- package/example/ExpressJs/backend.js +0 -39
- package/example/ExpressJs/index.html +0 -88
- package/example/ExpressJs/package-lock.json +0 -834
- package/example/ExpressJs/package.json +0 -10
- package/example/ExpressJs/styles.css +0 -128
- package/example/NextJs/.dockerignore +0 -29
- package/example/NextJs/Dockerfile +0 -52
- package/example/NextJs/Dockerfile.dev +0 -27
- package/example/NextJs/README.md +0 -113
- package/example/NextJs/ape/client.js +0 -66
- package/example/NextJs/ape/embed.js +0 -12
- package/example/NextJs/ape/index.js +0 -23
- package/example/NextJs/ape/logic/chat.js +0 -62
- package/example/NextJs/ape/onConnect.js +0 -69
- package/example/NextJs/ape/onDisconnect.js +0 -13
- package/example/NextJs/ape/onError.js +0 -9
- package/example/NextJs/ape/onReceive.js +0 -15
- package/example/NextJs/ape/onSend.js +0 -15
- package/example/NextJs/api/message.js +0 -44
- package/example/NextJs/docker-compose.yml +0 -22
- package/example/NextJs/next-env.d.ts +0 -5
- package/example/NextJs/next.config.js +0 -8
- package/example/NextJs/package-lock.json +0 -6400
- package/example/NextJs/package.json +0 -24
- package/example/NextJs/pages/Info.tsx +0 -153
- package/example/NextJs/pages/_app.tsx +0 -6
- package/example/NextJs/pages/index.tsx +0 -275
- package/example/NextJs/public/favicon.ico +0 -0
- package/example/NextJs/public/vercel.svg +0 -4
- package/example/NextJs/server.js +0 -36
- package/example/NextJs/styles/Chat.module.css +0 -448
- package/example/NextJs/styles/Home.module.css +0 -129
- package/example/NextJs/styles/globals.css +0 -26
- package/example/NextJs/tsconfig.json +0 -20
- package/example/README.md +0 -117
- package/example/Vite/README.md +0 -68
- package/example/Vite/ape/client.ts +0 -66
- package/example/Vite/ape/onConnect.ts +0 -52
- package/example/Vite/api/message.ts +0 -57
- package/example/Vite/index.html +0 -16
- package/example/Vite/package.json +0 -19
- package/example/Vite/server.ts +0 -62
- package/example/Vite/src/App.vue +0 -170
- package/example/Vite/src/components/Info.vue +0 -352
- package/example/Vite/src/main.ts +0 -5
- package/example/Vite/src/style.css +0 -200
- package/example/Vite/src/vite-env.d.ts +0 -7
- package/example/Vite/vite.config.ts +0 -20
- package/todo.md +0 -85
- package/utils/jss.test.js +0 -261
- package/utils/messageHash.test.js +0 -56
package/example/Vite/README.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# api-ape Vite + Vue Example
|
|
2
|
-
|
|
3
|
-
A modern Vue 3 + Vite frontend with a Bun backend, demonstrating `api-ape` real-time chat.
|
|
4
|
-
|
|
5
|
-
Matches the NextJs example structure and features.
|
|
6
|
-
|
|
7
|
-
## Project Structure
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
Vite/
|
|
11
|
-
├── src/
|
|
12
|
-
│ ├── App.vue # Main chat component
|
|
13
|
-
│ ├── components/
|
|
14
|
-
│ │ └── Info.vue # How api-ape works explanation
|
|
15
|
-
│ ├── main.ts # Vue app entry
|
|
16
|
-
│ └── style.css # Styles
|
|
17
|
-
├── ape/
|
|
18
|
-
│ ├── client.ts # api-ape client wrapper (singleton)
|
|
19
|
-
│ └── onConnect.ts # Connection lifecycle hooks
|
|
20
|
-
├── api/
|
|
21
|
-
│ └── message.ts # Message handler with broadcastOthers
|
|
22
|
-
├── server.ts # Bun backend server
|
|
23
|
-
├── vite.config.ts # Vite config with proxy
|
|
24
|
-
└── package.json
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Quick Start
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
# Install dependencies
|
|
31
|
-
npm install
|
|
32
|
-
|
|
33
|
-
# Terminal 1: Start backend
|
|
34
|
-
npm run dev
|
|
35
|
-
|
|
36
|
-
# Terminal 2: Start Vue frontend
|
|
37
|
-
npm run dev:vue
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Open http://localhost:5173 in multiple tabs to test the chat.
|
|
41
|
-
|
|
42
|
-
## Production Build
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
# Build Vue app
|
|
46
|
-
npm run build
|
|
47
|
-
|
|
48
|
-
# Run backend in production mode
|
|
49
|
-
NODE_ENV=production npm run dev
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## How It Works
|
|
53
|
-
|
|
54
|
-
- **Development**: Vite runs on `:5173` with a proxy forwarding `/api` requests to the Bun server on `:3000`
|
|
55
|
-
- **Production**: Bun serves the built Vue app from `dist/`
|
|
56
|
-
- **api-ape**: Handles WebSocket connections for real-time messaging
|
|
57
|
-
|
|
58
|
-
## Features
|
|
59
|
-
|
|
60
|
-
- 🚀 Vue 3 Composition API with TypeScript
|
|
61
|
-
- ⚡ Vite for instant HMR
|
|
62
|
-
- 🦍 api-ape for WebSocket communication
|
|
63
|
-
- 🥖 Bun for fast backend runtime
|
|
64
|
-
- 👤 Join form before entering chat
|
|
65
|
-
- 📊 Connection state management
|
|
66
|
-
- 👥 Live user count
|
|
67
|
-
- ⏰ Message timestamps
|
|
68
|
-
- 📚 Info panel explaining api-ape concepts
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* api-ape client singleton
|
|
3
|
-
* Initializes once and is ready for use throughout the app
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
let apeClient = null
|
|
7
|
-
let isConnecting = false
|
|
8
|
-
let connectionPromise = null
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Get the api-ape client - initializes on first call
|
|
12
|
-
*/
|
|
13
|
-
export async function getApeClient() {
|
|
14
|
-
if (apeClient) {
|
|
15
|
-
return apeClient
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (isConnecting) {
|
|
19
|
-
return connectionPromise
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
isConnecting = true
|
|
23
|
-
connectionPromise = initClient()
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
apeClient = await connectionPromise
|
|
27
|
-
return apeClient
|
|
28
|
-
} finally {
|
|
29
|
-
isConnecting = false
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Initialize the api-ape client
|
|
35
|
-
*/
|
|
36
|
-
async function initClient() {
|
|
37
|
-
if (typeof window === 'undefined') {
|
|
38
|
-
return null
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const module = await import('api-ape/client/connectSocket')
|
|
42
|
-
const connectSocket = module.default
|
|
43
|
-
|
|
44
|
-
// Configure for current port
|
|
45
|
-
const port = window.location.port || (window.location.protocol === 'https:' ? 443 : 80)
|
|
46
|
-
connectSocket.configure({ port: parseInt(port, 10) })
|
|
47
|
-
|
|
48
|
-
// Connect and get sender/receiver/connection state
|
|
49
|
-
const { sender, setOnReciver, onConnectionChange } = connectSocket()
|
|
50
|
-
|
|
51
|
-
// Enable auto-reconnect
|
|
52
|
-
connectSocket.autoReconnect()
|
|
53
|
-
|
|
54
|
-
console.log('🦍 api-ape client initialized')
|
|
55
|
-
|
|
56
|
-
return { sender, setOnReciver, onConnectionChange, connectSocket }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Check if client is connected
|
|
61
|
-
*/
|
|
62
|
-
export function isConnected() {
|
|
63
|
-
return apeClient !== null
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export default getApeClient
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* api-ape onConnect handler
|
|
3
|
-
* Creates the handlers object returned from onConnent
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import ape from 'api-ape'
|
|
7
|
-
|
|
8
|
-
// Get message history from the message controller
|
|
9
|
-
function getHistory() {
|
|
10
|
-
try {
|
|
11
|
-
const messageController = require('../api/message')
|
|
12
|
-
return messageController.getHistory ? messageController.getHistory() : []
|
|
13
|
-
} catch (e) {
|
|
14
|
-
return []
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function onConnect(socket: any, req: any, send: any) {
|
|
19
|
-
const clientID = send.toString()
|
|
20
|
-
console.log(`🦍 Client connected: ${clientID}`)
|
|
21
|
-
|
|
22
|
-
// Send init message with history and user count after a tiny delay
|
|
23
|
-
// (to ensure client is ready to receive)
|
|
24
|
-
setTimeout(() => {
|
|
25
|
-
console.log(`📤 Sending init to ${clientID}, users: ${ape.online()}`)
|
|
26
|
-
try {
|
|
27
|
-
send('init', {
|
|
28
|
-
history: getHistory(),
|
|
29
|
-
users: ape.online()
|
|
30
|
-
})
|
|
31
|
-
console.log(`✅ Init sent to ${clientID}`)
|
|
32
|
-
} catch (e) {
|
|
33
|
-
console.error(`❌ Failed to send init:`, e)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Broadcast updated user count to all clients
|
|
37
|
-
ape.broadcast('users', { count: ape.online() })
|
|
38
|
-
}, 100)
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
onDisconnent: () => {
|
|
42
|
-
console.info(`👋 Disconnected [${clientID}]`)
|
|
43
|
-
// Broadcast updated user count after disconnect
|
|
44
|
-
// Use setTimeout to ensure client is removed first
|
|
45
|
-
setTimeout(() => {
|
|
46
|
-
ape.broadcast('users', { count: ape.online() })
|
|
47
|
-
}, 50)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export default onConnect
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message controller for api-ape
|
|
3
|
-
* Called when client sends type="message"
|
|
4
|
-
*
|
|
5
|
-
* Uses this.broadcastOthers from api-ape to broadcast to all other clients
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// In-memory message store
|
|
9
|
-
const messages: Array<{ user: string; text: string; time: string }> = []
|
|
10
|
-
const MAX_MESSAGES = 100
|
|
11
|
-
|
|
12
|
-
interface MessageData {
|
|
13
|
-
user: string
|
|
14
|
-
text: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface MessageContext {
|
|
18
|
-
broadcastOthers: (type: string, data: any) => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Message handler - receives { user, text } from client
|
|
23
|
-
* Broadcasts to all OTHER clients, returns to sender
|
|
24
|
-
*/
|
|
25
|
-
function message(this: MessageContext, data: MessageData) {
|
|
26
|
-
const { user, text } = data
|
|
27
|
-
|
|
28
|
-
if (!user || !text) {
|
|
29
|
-
throw new Error('Missing user or text')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const msg = {
|
|
33
|
-
user,
|
|
34
|
-
text,
|
|
35
|
-
time: new Date().toISOString()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Store message
|
|
39
|
-
messages.push(msg)
|
|
40
|
-
if (messages.length > MAX_MESSAGES) {
|
|
41
|
-
messages.shift()
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Broadcast to all OTHER clients (exclude sender)
|
|
45
|
-
// this.broadcastOthers is provided by api-ape!
|
|
46
|
-
this.broadcastOthers('message', { message: msg })
|
|
47
|
-
|
|
48
|
-
// Return to sender (fulfills promise)
|
|
49
|
-
return { ok: true, message: msg }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Export history for other uses
|
|
53
|
-
export function getHistory() {
|
|
54
|
-
return messages.slice(-50)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default message
|
package/example/Vite/index.html
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>Chat - api-ape + Vue</title>
|
|
8
|
-
<link rel="stylesheet" href="/src/style.css">
|
|
9
|
-
</head>
|
|
10
|
-
|
|
11
|
-
<body>
|
|
12
|
-
<div id="app"></div>
|
|
13
|
-
<script type="module" src="/src/main.ts"></script>
|
|
14
|
-
</body>
|
|
15
|
-
|
|
16
|
-
</html>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vite-example",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "bun run server.ts",
|
|
7
|
-
"dev:vue": "vite",
|
|
8
|
-
"build": "vite build",
|
|
9
|
-
"preview": "vite preview"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"api-ape": "file:../../"
|
|
13
|
-
},
|
|
14
|
-
"devDependencies": {
|
|
15
|
-
"@vitejs/plugin-vue": "^5.0.0",
|
|
16
|
-
"vite": "^5.0.0",
|
|
17
|
-
"vue": "^3.4.0"
|
|
18
|
-
}
|
|
19
|
-
}
|
package/example/Vite/server.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vite + Vue server using api-ape library (TypeScript)
|
|
3
|
-
* Matches the NextJs example structure
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createServer, IncomingMessage, ServerResponse } from 'http'
|
|
7
|
-
import path from 'path'
|
|
8
|
-
import fs from 'fs'
|
|
9
|
-
import ape from 'api-ape'
|
|
10
|
-
import { onConnect } from './ape/onConnect'
|
|
11
|
-
|
|
12
|
-
const port = parseInt(process.env.PORT || '3000', 10)
|
|
13
|
-
const isProd = process.env.NODE_ENV === 'production'
|
|
14
|
-
const distPath = path.join(__dirname, 'dist')
|
|
15
|
-
|
|
16
|
-
// Create HTTP server
|
|
17
|
-
const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
18
|
-
const url = new URL(req.url || '/', `http://localhost:${port}`)
|
|
19
|
-
|
|
20
|
-
// In production, serve built Vue app
|
|
21
|
-
if (isProd) {
|
|
22
|
-
let filePath = path.join(distPath, url.pathname === '/' ? 'index.html' : url.pathname)
|
|
23
|
-
|
|
24
|
-
if (fs.existsSync(filePath)) {
|
|
25
|
-
const ext = path.extname(filePath)
|
|
26
|
-
const contentTypes: Record<string, string> = {
|
|
27
|
-
'.html': 'text/html',
|
|
28
|
-
'.js': 'application/javascript',
|
|
29
|
-
'.css': 'text/css',
|
|
30
|
-
'.svg': 'image/svg+xml',
|
|
31
|
-
'.png': 'image/png',
|
|
32
|
-
'.jpg': 'image/jpeg',
|
|
33
|
-
}
|
|
34
|
-
res.writeHead(200, { 'Content-Type': contentTypes[ext] || 'application/octet-stream' })
|
|
35
|
-
res.end(fs.readFileSync(filePath))
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
res.writeHead(404)
|
|
41
|
-
res.end('Not Found - Run Vite dev server on port 5173 for development')
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// Initialize api-ape with onConnect handler from ape folder
|
|
45
|
-
ape(server, {
|
|
46
|
-
where: 'api',
|
|
47
|
-
onConnent: onConnect
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
server.listen(port, () => {
|
|
51
|
-
console.log(`
|
|
52
|
-
╔═══════════════════════════════════════════════════════╗
|
|
53
|
-
║ 🦍 api-ape Vite + Vue Example ║
|
|
54
|
-
╠═══════════════════════════════════════════════════════╣
|
|
55
|
-
║ Backend: http://localhost:${port}/ ║
|
|
56
|
-
║ WebSocket: ws://localhost:${port}/api/ape ║
|
|
57
|
-
${isProd
|
|
58
|
-
? `║ Mode: Production (serving dist/) ║`
|
|
59
|
-
: `║ Frontend: http://localhost:5173/ (run npm run dev:vue)║`}
|
|
60
|
-
╚═══════════════════════════════════════════════════════╝
|
|
61
|
-
`)
|
|
62
|
-
})
|
package/example/Vite/src/App.vue
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* 🦍 api-ape Vue Chat Example
|
|
4
|
-
*
|
|
5
|
-
* This component demonstrates how to use api-ape in a Vue application:
|
|
6
|
-
*
|
|
7
|
-
* 1. **Client Initialization**: Connect to api-ape WebSocket server
|
|
8
|
-
* 2. **Proxy Pattern**: Use `client.sender` as a Proxy to call server functions
|
|
9
|
-
* 3. **Event Listeners**: Listen for server broadcasts using `setOnReciver`
|
|
10
|
-
* 4. **Promise-based Calls**: Server functions return Promises automatically
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { ref, onMounted } from 'vue'
|
|
14
|
-
import { getApeClient } from '../ape/client'
|
|
15
|
-
import Info from './components/Info.vue'
|
|
16
|
-
|
|
17
|
-
interface Message {
|
|
18
|
-
user: string
|
|
19
|
-
text: string
|
|
20
|
-
time: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Component state
|
|
24
|
-
const messages = ref<Message[]>([])
|
|
25
|
-
const input = ref('')
|
|
26
|
-
const username = ref('')
|
|
27
|
-
const joined = ref(false)
|
|
28
|
-
const userCount = ref(0)
|
|
29
|
-
const sending = ref(false)
|
|
30
|
-
const connectionState = ref<'disconnected' | 'connecting' | 'connected'>('connecting')
|
|
31
|
-
|
|
32
|
-
// Store the api-ape sender Proxy
|
|
33
|
-
let api: any = null
|
|
34
|
-
|
|
35
|
-
onMounted(async () => {
|
|
36
|
-
const client = await getApeClient()
|
|
37
|
-
if (!client) return
|
|
38
|
-
|
|
39
|
-
// Store the sender Proxy
|
|
40
|
-
api = client.sender
|
|
41
|
-
console.log('🦍 api-ape client connected')
|
|
42
|
-
|
|
43
|
-
// Subscribe to connection state changes
|
|
44
|
-
client.onConnectionChange((state: string) => {
|
|
45
|
-
connectionState.value = state as any
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// Set up event listeners for server broadcasts
|
|
49
|
-
client.setOnReciver('init', ({ data }: { data: { history: Message[], users: number } }) => {
|
|
50
|
-
messages.value = data.history || []
|
|
51
|
-
userCount.value = data.users || 0
|
|
52
|
-
console.log('🦍 Initialized with', data.history?.length || 0, 'messages')
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
client.setOnReciver('message', ({ data }: { data: { message: Message } }) => {
|
|
56
|
-
messages.value.push(data.message)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
client.setOnReciver('users', ({ data }: { data: { count: number } }) => {
|
|
60
|
-
userCount.value = data.count
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
async function sendMessage() {
|
|
65
|
-
if (!input.value.trim() || !api || sending.value) return
|
|
66
|
-
|
|
67
|
-
sending.value = true
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const response = await api.message({ user: username.value, text: input.value })
|
|
71
|
-
if (response?.message) {
|
|
72
|
-
messages.value.push(response.message)
|
|
73
|
-
}
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.error('Send failed:', err)
|
|
76
|
-
} finally {
|
|
77
|
-
sending.value = false
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
input.value = ''
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function handleJoin() {
|
|
84
|
-
if (username.value.trim()) {
|
|
85
|
-
joined.value = true
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function formatTime(time: string) {
|
|
90
|
-
return new Date(time).toLocaleTimeString()
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function getConnectionStatus() {
|
|
94
|
-
if (connectionState.value === 'connected') {
|
|
95
|
-
if (userCount.value === 1) return '✅ Connected • Only You are online'
|
|
96
|
-
if (userCount.value > 1) return `✅ Connected • You + ${userCount.value - 1} are online`
|
|
97
|
-
return '✅ Connected'
|
|
98
|
-
}
|
|
99
|
-
if (connectionState.value === 'connecting') return '⏳ Connecting...'
|
|
100
|
-
return '❌ Disconnected'
|
|
101
|
-
}
|
|
102
|
-
</script>
|
|
103
|
-
|
|
104
|
-
<template>
|
|
105
|
-
<div class="container">
|
|
106
|
-
<main class="main">
|
|
107
|
-
<h1 class="title">
|
|
108
|
-
🦍 <span class="gradient">api-ape</span> Chat
|
|
109
|
-
</h1>
|
|
110
|
-
<p class="subtitle">{{ getConnectionStatus() }}</p>
|
|
111
|
-
|
|
112
|
-
<!-- Join Form -->
|
|
113
|
-
<form v-if="!joined" @submit.prevent="handleJoin" class="join-form">
|
|
114
|
-
<input
|
|
115
|
-
type="text"
|
|
116
|
-
placeholder="Enter your name..."
|
|
117
|
-
v-model="username"
|
|
118
|
-
class="input"
|
|
119
|
-
autofocus
|
|
120
|
-
/>
|
|
121
|
-
<button
|
|
122
|
-
type="submit"
|
|
123
|
-
class="button"
|
|
124
|
-
:disabled="connectionState !== 'connected'"
|
|
125
|
-
>
|
|
126
|
-
{{ connectionState === 'connected' ? 'Join Chat →' : 'Connecting...' }}
|
|
127
|
-
</button>
|
|
128
|
-
</form>
|
|
129
|
-
|
|
130
|
-
<!-- Chat Interface -->
|
|
131
|
-
<div v-else class="chat-container">
|
|
132
|
-
<div class="header">
|
|
133
|
-
<span>💬 {{ username }}</span>
|
|
134
|
-
<span class="user-count">🟢 {{ userCount }} online</span>
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
|
-
<div class="messages">
|
|
138
|
-
<p v-if="messages.length === 0" class="empty-state">
|
|
139
|
-
No messages yet. Say hi! 👋
|
|
140
|
-
</p>
|
|
141
|
-
<div
|
|
142
|
-
v-for="(msg, i) in messages"
|
|
143
|
-
:key="i"
|
|
144
|
-
:class="['message', msg.user === username ? 'my-message' : '']"
|
|
145
|
-
>
|
|
146
|
-
<strong class="username">{{ msg.user }}</strong>
|
|
147
|
-
<span>{{ msg.text }}</span>
|
|
148
|
-
<span class="time">{{ formatTime(msg.time) }}</span>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
<form @submit.prevent="sendMessage" class="input-form">
|
|
153
|
-
<input
|
|
154
|
-
type="text"
|
|
155
|
-
placeholder="Type a message..."
|
|
156
|
-
v-model="input"
|
|
157
|
-
class="message-input"
|
|
158
|
-
:disabled="sending"
|
|
159
|
-
autofocus
|
|
160
|
-
/>
|
|
161
|
-
<button type="submit" class="send-button" :disabled="sending">
|
|
162
|
-
{{ sending ? '...' : 'Send' }}
|
|
163
|
-
</button>
|
|
164
|
-
</form>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
<Info />
|
|
168
|
-
</main>
|
|
169
|
-
</div>
|
|
170
|
-
</template>
|