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
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "apiape_example",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "node server.js",
|
|
7
|
-
"build": "next build",
|
|
8
|
-
"start": "node server.js",
|
|
9
|
-
"lint": "next lint"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"@types/node": "18.11.13",
|
|
13
|
-
"@types/react": "18.0.26",
|
|
14
|
-
"@types/react-dom": "18.0.9",
|
|
15
|
-
"api-ape": "file:../..",
|
|
16
|
-
"eslint": "8.29.0",
|
|
17
|
-
"eslint-config-next": "13.0.6",
|
|
18
|
-
"next": "13.0.6",
|
|
19
|
-
"react": "18.2.0",
|
|
20
|
-
"react-dom": "18.2.0",
|
|
21
|
-
"typescript": "4.9.4",
|
|
22
|
-
"ws": "^8.14.0"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import styles from '../styles/Chat.module.css'
|
|
2
|
-
|
|
3
|
-
export default function Info() {
|
|
4
|
-
return (
|
|
5
|
-
<div className={styles.codeSection}>
|
|
6
|
-
<h3 className={styles.codeTitle}>📚 How api-ape Works</h3>
|
|
7
|
-
|
|
8
|
-
<div className={styles.gridContainer}>
|
|
9
|
-
<div className={styles.gridLayout}>
|
|
10
|
-
{/* Top Left: Key Concepts */}
|
|
11
|
-
<div>
|
|
12
|
-
<h4 className={styles.sectionHeading}>
|
|
13
|
-
💡 Key Concepts
|
|
14
|
-
</h4>
|
|
15
|
-
<pre className={styles.code}>
|
|
16
|
-
{`• Proxy Pattern: api.message() → api/message.js
|
|
17
|
-
• Auto-wiring: Drop files in api/ folder, they become endpoints
|
|
18
|
-
• Promises: All calls return Promises automatically
|
|
19
|
-
• Broadcasts: Use this.broadcast() or this.broadcastOthers()
|
|
20
|
-
• Context: this.broadcast, this.hostId, this.req available in controllers
|
|
21
|
-
• Auto-reconnect: Client reconnects automatically on disconnect`}
|
|
22
|
-
</pre>
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
{/* Top Right: Data Flow */}
|
|
26
|
-
<div>
|
|
27
|
-
<h4 className={styles.sectionHeadingLarge}>
|
|
28
|
-
🔄 Data Flow
|
|
29
|
-
</h4>
|
|
30
|
-
<div className={styles.dataFlowGrid}>
|
|
31
|
-
{/* Column Headers */}
|
|
32
|
-
<div className={styles.columnHeaderClient}>
|
|
33
|
-
Client
|
|
34
|
-
</div>
|
|
35
|
-
<div className={styles.gridCell}></div>
|
|
36
|
-
<div className={styles.columnHeaderServer}>
|
|
37
|
-
Server
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
{/* Step 1: Client sends */}
|
|
41
|
-
<div className={styles.clientBoxSpan3}>
|
|
42
|
-
api.message(data)
|
|
43
|
-
</div>
|
|
44
|
-
<div className={styles.arrowContainerRow2}>
|
|
45
|
-
<div className={styles.arrowLineSend}></div>
|
|
46
|
-
<span className={styles.arrowLabelBlue}>Send</span>
|
|
47
|
-
<div className={styles.arrowHeadRight}></div>
|
|
48
|
-
</div>
|
|
49
|
-
<div className={styles.emptyGridCell}></div>
|
|
50
|
-
|
|
51
|
-
{/* Step 2: Server receives */}
|
|
52
|
-
<div className={styles.emptyGridCellRow3}></div>
|
|
53
|
-
<div className={styles.arrowContainerRow3}>
|
|
54
|
-
<div className={styles.arrowHeadLeft}></div>
|
|
55
|
-
<span className={styles.arrowLabelGreen}>Return</span>
|
|
56
|
-
<div className={styles.arrowLineReturn}></div>
|
|
57
|
-
</div>
|
|
58
|
-
<div className={styles.serverBoxSpan2}>
|
|
59
|
-
api/message.js
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
{/* Step 3: Server broadcasts */}
|
|
63
|
-
<div className={styles.emptyGridCellRow4}></div>
|
|
64
|
-
<div className={styles.arrowContainerRow4}>
|
|
65
|
-
<div className={styles.arrowLineBroadcast}></div>
|
|
66
|
-
<span className={styles.arrowLabelGreen}>Broadcast</span>
|
|
67
|
-
<div className={styles.arrowHeadRight}></div>
|
|
68
|
-
</div>
|
|
69
|
-
<div className={styles.serverBoxSpan3}>
|
|
70
|
-
Broadcast to others
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
{/* Step 4: Other clients receive */}
|
|
74
|
-
<div className={styles.clientBoxSingle}>
|
|
75
|
-
Other clients
|
|
76
|
-
</div>
|
|
77
|
-
<div className={styles.arrowContainerRow5}>
|
|
78
|
-
<div className={styles.arrowHeadLeftBlue}></div>
|
|
79
|
-
<span className={styles.arrowLabelBlue}>Broadcast</span>
|
|
80
|
-
<div className={styles.arrowLineBroadcastReturn}></div>
|
|
81
|
-
</div>
|
|
82
|
-
<div className={styles.emptyGridCellRow5}></div>
|
|
83
|
-
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
{/* Bottom Left: Client-Side */}
|
|
88
|
-
<div>
|
|
89
|
-
<h4 className={styles.sectionHeading}>
|
|
90
|
-
🔵 Client-Side (Browser)
|
|
91
|
-
</h4>
|
|
92
|
-
<pre className={styles.code}>
|
|
93
|
-
{`// 1. Initialize api-ape client
|
|
94
|
-
const client = await getApeClient()
|
|
95
|
-
const api = client.sender // Proxy object
|
|
96
|
-
|
|
97
|
-
// 2. Call server function - property name = file path
|
|
98
|
-
// api.message() → calls api/message.js
|
|
99
|
-
api.message({ user: 'Alice', text: 'Hello!' })
|
|
100
|
-
.then(response => {
|
|
101
|
-
// Server returned: { ok: true, message: {...} }
|
|
102
|
-
console.log('Response:', response)
|
|
103
|
-
})
|
|
104
|
-
.catch(err => {
|
|
105
|
-
// Server threw an error
|
|
106
|
-
console.error('Error:', err)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// 3. Listen for server broadcasts
|
|
110
|
-
client.setOnReciver('message', ({ data }) => {
|
|
111
|
-
// Server called: this.broadcastOthers('message', data)
|
|
112
|
-
// This fires for ALL clients except the sender
|
|
113
|
-
console.log('Broadcast received:', data.message)
|
|
114
|
-
})`}
|
|
115
|
-
</pre>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
{/* Bottom Right: Server-Side */}
|
|
119
|
-
<div>
|
|
120
|
-
<h4 className={styles.sectionHeading}>
|
|
121
|
-
🟢 Server-Side (api/message.js)
|
|
122
|
-
</h4>
|
|
123
|
-
<pre className={styles.code}>
|
|
124
|
-
{`// File: api/message.js
|
|
125
|
-
// This function is called when client does: api.message(data)
|
|
126
|
-
|
|
127
|
-
module.exports = function message(data) {
|
|
128
|
-
const { user, text } = data
|
|
129
|
-
|
|
130
|
-
// Validate input
|
|
131
|
-
if (!user || !text) {
|
|
132
|
-
throw new Error('Missing user or text')
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const msg = {
|
|
136
|
-
user,
|
|
137
|
-
text,
|
|
138
|
-
time: new Date().toISOString()
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Broadcast to ALL OTHER clients (not the sender)
|
|
142
|
-
this.broadcastOthers('message', { message: msg })
|
|
143
|
-
|
|
144
|
-
// Return response to sender (fulfills Promise)
|
|
145
|
-
return { ok: true, message: msg }
|
|
146
|
-
}`}
|
|
147
|
-
</pre>
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
)
|
|
153
|
-
}
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🦍 api-ape Next.js Chat Example
|
|
3
|
-
*
|
|
4
|
-
* This component demonstrates how to use api-ape in a React/Next.js application:
|
|
5
|
-
*
|
|
6
|
-
* 1. **Client Initialization**: Connect to api-ape WebSocket server
|
|
7
|
-
* 2. **Proxy Pattern**: Use `client.sender` as a Proxy to call server functions
|
|
8
|
-
* 3. **Event Listeners**: Listen for server broadcasts using `setOnReciver`
|
|
9
|
-
* 4. **Promise-based Calls**: Server functions return Promises automatically
|
|
10
|
-
*
|
|
11
|
-
* Server-side: api/message.js handles incoming messages and broadcasts to other clients
|
|
12
|
-
* Client-side: This component sends messages and receives broadcasts
|
|
13
|
-
*
|
|
14
|
-
* Key api-ape concepts:
|
|
15
|
-
* - `client.sender` is a Proxy - accessing `sender.message()` calls server function
|
|
16
|
-
* - Property name (`message`) maps to server file: `api/message.js`
|
|
17
|
-
* - `setOnReciver(type, handler)` listens for server broadcasts
|
|
18
|
-
* - All calls return Promises - server response is automatically matched by queryId
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import Head from 'next/head'
|
|
22
|
-
import { useState, useEffect, useRef } from 'react'
|
|
23
|
-
import styles from '../styles/Chat.module.css'
|
|
24
|
-
import { getApeClient } from '../ape/client'
|
|
25
|
-
import Info from './Info'
|
|
26
|
-
|
|
27
|
-
export default function Home() {
|
|
28
|
-
// Component state
|
|
29
|
-
const [messages, setMessages] = useState([])
|
|
30
|
-
const [input, setInput] = useState('')
|
|
31
|
-
const [username, setUsername] = useState('')
|
|
32
|
-
const [joined, setJoined] = useState(false)
|
|
33
|
-
const [userCount, setUserCount] = useState(0)
|
|
34
|
-
const [sending, setSending] = useState(false)
|
|
35
|
-
const [connectionState, setConnectionState] = useState('connecting')
|
|
36
|
-
|
|
37
|
-
// Refs
|
|
38
|
-
const apiRef = useRef(null) // Stores the api-ape sender Proxy
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Initialize api-ape client on component mount
|
|
42
|
-
*
|
|
43
|
-
* This effect:
|
|
44
|
-
* 1. Gets the api-ape client singleton (connects to WebSocket)
|
|
45
|
-
* 2. Stores the `sender` Proxy in a ref for later use
|
|
46
|
-
* 3. Sets up event listeners for server broadcasts
|
|
47
|
-
*
|
|
48
|
-
* The client auto-reconnects if the connection drops.
|
|
49
|
-
*/
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
// Skip on server-side rendering
|
|
52
|
-
if (typeof window === 'undefined') return
|
|
53
|
-
|
|
54
|
-
getApeClient().then((client) => {
|
|
55
|
-
if (!client) return
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Store the sender Proxy
|
|
59
|
-
*
|
|
60
|
-
* `client.sender` is a Proxy object that allows you to call server functions
|
|
61
|
-
* by accessing properties. For example:
|
|
62
|
-
* - `sender.message(data)` calls `api/message.js` on the server
|
|
63
|
-
* - The property name (`message`) maps to the server file path
|
|
64
|
-
* - All calls return Promises that resolve with the server's response
|
|
65
|
-
*/
|
|
66
|
-
apiRef.current = client.sender
|
|
67
|
-
console.log('🦍 api-ape client connected')
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Subscribe to connection state changes
|
|
71
|
-
*
|
|
72
|
-
* `onConnectionChange` gets called with the current state immediately,
|
|
73
|
-
* then on each state transition. States: 'disconnected' | 'connecting' | 'connected'
|
|
74
|
-
*/
|
|
75
|
-
const unsubscribe = client.onConnectionChange(setConnectionState)
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Set up event listeners for server broadcasts
|
|
79
|
-
*
|
|
80
|
-
* `setOnReciver(type, handler)` listens for broadcasts from the server.
|
|
81
|
-
* The server can broadcast using `this.broadcast()` or `this.broadcastOthers()`
|
|
82
|
-
* in controller functions (see api/message.js).
|
|
83
|
-
*
|
|
84
|
-
* Broadcast types:
|
|
85
|
-
* - 'init': Initial data when client connects (history, user count)
|
|
86
|
-
* - 'message': New message from another client
|
|
87
|
-
* - 'users': Updated user count
|
|
88
|
-
*/
|
|
89
|
-
client.setOnReciver('init', ({ data }) => {
|
|
90
|
-
// Server sent initial data (happens on connect)
|
|
91
|
-
setMessages(data.history || [])
|
|
92
|
-
setUserCount(data.users || 0)
|
|
93
|
-
console.log('🦍 Initialized with', data.history?.length || 0, 'messages')
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
client.setOnReciver('message', ({ data }) => {
|
|
97
|
-
// Server broadcasted a new message from another client
|
|
98
|
-
// This is NOT the response to our own send - it's a broadcast!
|
|
99
|
-
setMessages(prev => [...prev, data.message])
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
client.setOnReciver('users', ({ data }) => {
|
|
103
|
-
// Server broadcasted updated user count
|
|
104
|
-
setUserCount(data.count)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
return () => unsubscribe()
|
|
108
|
-
})
|
|
109
|
-
}, [])
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Send a message to the server
|
|
113
|
-
*
|
|
114
|
-
* This demonstrates the api-ape Proxy pattern:
|
|
115
|
-
*
|
|
116
|
-
* 1. Access `api.message()` - the property name 'message' maps to `api/message.js`
|
|
117
|
-
* 2. Call it with data - returns a Promise
|
|
118
|
-
* 3. Server processes the request in `api/message.js`
|
|
119
|
-
* 4. Server can:
|
|
120
|
-
* - Return a value (fulfills the Promise)
|
|
121
|
-
* - Broadcast to other clients using `this.broadcastOthers()`
|
|
122
|
-
* - Throw an error (rejects the Promise)
|
|
123
|
-
*
|
|
124
|
-
* The Promise resolves with whatever the server function returns.
|
|
125
|
-
* The server also broadcasts to other clients (see api/message.js).
|
|
126
|
-
*/
|
|
127
|
-
const sendMessage = (e) => {
|
|
128
|
-
e.preventDefault()
|
|
129
|
-
if (!input.trim() || !apiRef.current || sending) return
|
|
130
|
-
|
|
131
|
-
const api = apiRef.current
|
|
132
|
-
setSending(true)
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Call server function using Proxy pattern
|
|
136
|
-
*
|
|
137
|
-
* `api.message({ user, text })`:
|
|
138
|
-
* - Calls the `message` function in `api/message.js`
|
|
139
|
-
* - Sends `{ user, text }` as the function argument
|
|
140
|
-
* - Returns a Promise that resolves with the server's return value
|
|
141
|
-
* - Server automatically broadcasts to other clients (see api/message.js)
|
|
142
|
-
*
|
|
143
|
-
* The server function receives the data and can:
|
|
144
|
-
* - Validate input
|
|
145
|
-
* - Store the message
|
|
146
|
-
* - Broadcast to others: `this.broadcastOthers('message', { message: msg })`
|
|
147
|
-
* - Return a response: `return { ok: true, message: msg }`
|
|
148
|
-
*/
|
|
149
|
-
api.message({ user: username, text: input })
|
|
150
|
-
.then((response) => {
|
|
151
|
-
/**
|
|
152
|
-
* Server responded successfully
|
|
153
|
-
*
|
|
154
|
-
* The response is whatever the server function returned.
|
|
155
|
-
* In this case, api/message.js returns: `{ ok: true, message: msg }`
|
|
156
|
-
*
|
|
157
|
-
* Note: Other clients receive the message via broadcast (setOnReciver above),
|
|
158
|
-
* but we add it here from the server's response to show it immediately.
|
|
159
|
-
*/
|
|
160
|
-
if (response?.message) {
|
|
161
|
-
setMessages(prev => [...prev, response.message])
|
|
162
|
-
}
|
|
163
|
-
setSending(false)
|
|
164
|
-
})
|
|
165
|
-
.catch((err) => {
|
|
166
|
-
/**
|
|
167
|
-
* Server function threw an error or connection failed
|
|
168
|
-
*
|
|
169
|
-
* Errors from server functions are automatically caught and
|
|
170
|
-
* the Promise is rejected with the error.
|
|
171
|
-
*/
|
|
172
|
-
console.error('Send failed:', err)
|
|
173
|
-
setSending(false)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
setInput('')
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Handle user joining the chat
|
|
181
|
-
* Simply sets the joined state to show the chat interface
|
|
182
|
-
*/
|
|
183
|
-
const handleJoin = (e) => {
|
|
184
|
-
e.preventDefault()
|
|
185
|
-
if (username.trim()) {
|
|
186
|
-
setJoined(true)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return (
|
|
191
|
-
<div className={styles.container}>
|
|
192
|
-
<Head>
|
|
193
|
-
<title>🦍 api-ape Chat</title>
|
|
194
|
-
<meta name="description" content="Real-time WebSocket chat using api-ape" />
|
|
195
|
-
</Head>
|
|
196
|
-
|
|
197
|
-
<main className={styles.main}>
|
|
198
|
-
<h1 className={styles.title}>
|
|
199
|
-
🦍 <span className={styles.gradient}>api-ape</span> Chat
|
|
200
|
-
</h1>
|
|
201
|
-
<p className={styles.subtitle}>
|
|
202
|
-
{connectionState === 'connected' ? (
|
|
203
|
-
userCount === 1
|
|
204
|
-
? '✅ Connected • Only You are online'
|
|
205
|
-
: userCount > 1
|
|
206
|
-
? `✅ Connected • You + ${userCount - 1} are online`
|
|
207
|
-
: '✅ Connected'
|
|
208
|
-
) : connectionState === 'connecting'
|
|
209
|
-
? '⏳ Connecting...'
|
|
210
|
-
: '❌ Disconnected'}
|
|
211
|
-
</p>
|
|
212
|
-
|
|
213
|
-
{!joined ? (
|
|
214
|
-
<form onSubmit={handleJoin} className={styles.joinForm}>
|
|
215
|
-
<input
|
|
216
|
-
type="text"
|
|
217
|
-
placeholder="Enter your name..."
|
|
218
|
-
value={username}
|
|
219
|
-
onChange={(e) => setUsername(e.target.value)}
|
|
220
|
-
className={styles.input}
|
|
221
|
-
autoFocus
|
|
222
|
-
/>
|
|
223
|
-
<button type="submit" className={styles.button} disabled={connectionState !== 'connected'}>
|
|
224
|
-
{connectionState === 'connected' ? 'Join Chat →' : 'Connecting...'}
|
|
225
|
-
</button>
|
|
226
|
-
</form>
|
|
227
|
-
) : (
|
|
228
|
-
<div className={styles.chatContainer}>
|
|
229
|
-
<div className={styles.header}>
|
|
230
|
-
<span>💬 {username}</span>
|
|
231
|
-
<span className={styles.userCount}>
|
|
232
|
-
🟢 {userCount} online
|
|
233
|
-
</span>
|
|
234
|
-
</div>
|
|
235
|
-
|
|
236
|
-
<div className={styles.messages}>
|
|
237
|
-
{messages.length === 0 && (
|
|
238
|
-
<p className={styles.emptyState}>No messages yet. Say hi! 👋</p>
|
|
239
|
-
)}
|
|
240
|
-
{messages.map((msg, i) => (
|
|
241
|
-
<div
|
|
242
|
-
key={i}
|
|
243
|
-
className={`${styles.message} ${msg.user === username ? styles.myMessage : ''}`}
|
|
244
|
-
>
|
|
245
|
-
<strong className={styles.username}>{msg.user}</strong>
|
|
246
|
-
<span>{msg.text}</span>
|
|
247
|
-
<span className={styles.time}>
|
|
248
|
-
{new Date(msg.time).toLocaleTimeString()}
|
|
249
|
-
</span>
|
|
250
|
-
</div>
|
|
251
|
-
))}
|
|
252
|
-
</div>
|
|
253
|
-
|
|
254
|
-
<form onSubmit={sendMessage} className={styles.inputForm}>
|
|
255
|
-
<input
|
|
256
|
-
type="text"
|
|
257
|
-
placeholder="Type a message..."
|
|
258
|
-
value={input}
|
|
259
|
-
onChange={(e) => setInput(e.target.value)}
|
|
260
|
-
className={styles.messageInput}
|
|
261
|
-
disabled={sending}
|
|
262
|
-
autoFocus
|
|
263
|
-
/>
|
|
264
|
-
<button type="submit" className={styles.sendButton} disabled={sending}>
|
|
265
|
-
{sending ? '...' : 'Send'}
|
|
266
|
-
</button>
|
|
267
|
-
</form>
|
|
268
|
-
</div>
|
|
269
|
-
)}
|
|
270
|
-
|
|
271
|
-
<Info />
|
|
272
|
-
</main>
|
|
273
|
-
</div>
|
|
274
|
-
)
|
|
275
|
-
}
|
|
Binary file
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
|
2
|
-
xmlns="http://www.w3.org/2000/svg">
|
|
3
|
-
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
|
4
|
-
</svg>
|
package/example/NextJs/server.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Custom Next.js server using api-ape library without Express
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { createServer } = require('http')
|
|
6
|
-
const next = require('next')
|
|
7
|
-
const ape = require('api-ape')
|
|
8
|
-
const { onConnect } = require('./ape/onConnect')
|
|
9
|
-
|
|
10
|
-
const dev = process.env.NODE_ENV !== 'production'
|
|
11
|
-
const hostname = '0.0.0.0'
|
|
12
|
-
const port = parseInt(process.env.PORT, 10) || 3000
|
|
13
|
-
|
|
14
|
-
const app = next({ dev, hostname, port })
|
|
15
|
-
const handle = app.getRequestHandler()
|
|
16
|
-
|
|
17
|
-
app.prepare().then(() => {
|
|
18
|
-
const server = createServer((req, res) => {
|
|
19
|
-
return handle(req, res)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
// Initialize api-ape with the raw http server
|
|
23
|
-
ape(server, { where: 'api', onConnent: onConnect })
|
|
24
|
-
|
|
25
|
-
server.listen(port, () => {
|
|
26
|
-
console.log(`
|
|
27
|
-
╔═══════════════════════════════════════════════════════╗
|
|
28
|
-
║ 🦍 api-ape NextJS Demo ║
|
|
29
|
-
╠═══════════════════════════════════════════════════════╣
|
|
30
|
-
║ HTTP: http://localhost:${port}/ ║
|
|
31
|
-
║ WebSocket: ws://localhost:${port}/api/ape ║
|
|
32
|
-
║ ape(server, { where: "api", onConnent }) ║
|
|
33
|
-
╚═══════════════════════════════════════════════════════╝
|
|
34
|
-
`)
|
|
35
|
-
})
|
|
36
|
-
})
|