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.
Files changed (89) hide show
  1. package/README.md +203 -124
  2. package/client/README.md +37 -30
  3. package/client/browser.js +10 -8
  4. package/client/connectSocket.js +662 -381
  5. package/client/index.js +171 -0
  6. package/client/transports/streaming.js +240 -0
  7. package/dist/ape.js +2 -699
  8. package/dist/ape.js.map +7 -0
  9. package/dist/api-ape.min.js +2 -0
  10. package/dist/api-ape.min.js.map +7 -0
  11. package/index.d.ts +71 -18
  12. package/package.json +50 -15
  13. package/server/README.md +99 -13
  14. package/server/lib/broadcast.js +25 -8
  15. package/server/lib/bun.js +122 -0
  16. package/server/lib/longPolling.js +226 -0
  17. package/server/lib/main.js +381 -38
  18. package/server/lib/wiring.js +19 -12
  19. package/server/lib/ws/adapters/bun.js +225 -0
  20. package/server/lib/ws/adapters/deno.js +186 -0
  21. package/server/lib/ws/frames.js +217 -0
  22. package/server/lib/ws/index.js +15 -0
  23. package/server/lib/ws/server.js +109 -0
  24. package/server/lib/ws/socket.js +222 -0
  25. package/server/lib/wsProvider.js +135 -0
  26. package/server/security/origin.js +16 -4
  27. package/server/socket/receive.js +14 -1
  28. package/server/socket/send.js +6 -6
  29. package/server/utils/deepRequire.js +25 -10
  30. package/server/utils/parseUserAgent.js +286 -0
  31. package/example/Bun/README.md +0 -74
  32. package/example/Bun/api/message.ts +0 -11
  33. package/example/Bun/index.html +0 -76
  34. package/example/Bun/package.json +0 -9
  35. package/example/Bun/server.ts +0 -59
  36. package/example/Bun/styles.css +0 -128
  37. package/example/ExpressJs/README.md +0 -95
  38. package/example/ExpressJs/api/message.js +0 -11
  39. package/example/ExpressJs/backend.js +0 -39
  40. package/example/ExpressJs/index.html +0 -88
  41. package/example/ExpressJs/package-lock.json +0 -834
  42. package/example/ExpressJs/package.json +0 -10
  43. package/example/ExpressJs/styles.css +0 -128
  44. package/example/NextJs/.dockerignore +0 -29
  45. package/example/NextJs/Dockerfile +0 -52
  46. package/example/NextJs/Dockerfile.dev +0 -27
  47. package/example/NextJs/README.md +0 -113
  48. package/example/NextJs/ape/client.js +0 -66
  49. package/example/NextJs/ape/embed.js +0 -12
  50. package/example/NextJs/ape/index.js +0 -23
  51. package/example/NextJs/ape/logic/chat.js +0 -62
  52. package/example/NextJs/ape/onConnect.js +0 -69
  53. package/example/NextJs/ape/onDisconnect.js +0 -13
  54. package/example/NextJs/ape/onError.js +0 -9
  55. package/example/NextJs/ape/onReceive.js +0 -15
  56. package/example/NextJs/ape/onSend.js +0 -15
  57. package/example/NextJs/api/message.js +0 -44
  58. package/example/NextJs/docker-compose.yml +0 -22
  59. package/example/NextJs/next-env.d.ts +0 -5
  60. package/example/NextJs/next.config.js +0 -8
  61. package/example/NextJs/package-lock.json +0 -6400
  62. package/example/NextJs/package.json +0 -24
  63. package/example/NextJs/pages/Info.tsx +0 -153
  64. package/example/NextJs/pages/_app.tsx +0 -6
  65. package/example/NextJs/pages/index.tsx +0 -275
  66. package/example/NextJs/public/favicon.ico +0 -0
  67. package/example/NextJs/public/vercel.svg +0 -4
  68. package/example/NextJs/server.js +0 -36
  69. package/example/NextJs/styles/Chat.module.css +0 -448
  70. package/example/NextJs/styles/Home.module.css +0 -129
  71. package/example/NextJs/styles/globals.css +0 -26
  72. package/example/NextJs/tsconfig.json +0 -20
  73. package/example/README.md +0 -117
  74. package/example/Vite/README.md +0 -68
  75. package/example/Vite/ape/client.ts +0 -66
  76. package/example/Vite/ape/onConnect.ts +0 -52
  77. package/example/Vite/api/message.ts +0 -57
  78. package/example/Vite/index.html +0 -16
  79. package/example/Vite/package.json +0 -19
  80. package/example/Vite/server.ts +0 -62
  81. package/example/Vite/src/App.vue +0 -170
  82. package/example/Vite/src/components/Info.vue +0 -352
  83. package/example/Vite/src/main.ts +0 -5
  84. package/example/Vite/src/style.css +0 -200
  85. package/example/Vite/src/vite-env.d.ts +0 -7
  86. package/example/Vite/vite.config.ts +0 -20
  87. package/todo.md +0 -85
  88. package/utils/jss.test.js +0 -261
  89. 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,6 +0,0 @@
1
- import '../styles/globals.css'
2
- import type { AppProps } from 'next/app'
3
-
4
- export default function App({ Component, pageProps }: AppProps) {
5
- return <Component {...pageProps} />
6
- }
@@ -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>
@@ -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
- })