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,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
@@ -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
- }
@@ -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
- })
@@ -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>