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
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Robust User-Agent Parser
3
+ * Zero-dependency replacement for ua-parser-js
4
+ * Handles browsers, OS, devices, bots (including AI), and edge cases
5
+ */
6
+
7
+ // Browser detection patterns - ORDER MATTERS (specific before generic)
8
+ const BROWSERS = [
9
+ // AI Bots first (most specific)
10
+ { name: 'ChatGPT-User', pattern: /ChatGPT-User\/?([\d.]*)/i },
11
+ { name: 'GPTBot', pattern: /GPTBot\/?([\d.]*)/i },
12
+ { name: 'OAI-SearchBot', pattern: /OAI-SearchBot\/?([\d.]*)/i },
13
+ { name: 'ClaudeBot', pattern: /ClaudeBot\/?([\d.]*)/i },
14
+ { name: 'Claude-User', pattern: /Claude-User\/?([\d.]*)/i },
15
+ { name: 'Claude-SearchBot', pattern: /Claude-SearchBot\/?([\d.]*)/i },
16
+ { name: 'Claude-Web', pattern: /Claude-Web\/?([\d.]*)/i },
17
+ { name: 'PerplexityBot', pattern: /PerplexityBot\/?([\d.]*)/i },
18
+ { name: 'Perplexity-User', pattern: /Perplexity-User\/?([\d.]*)/i },
19
+ { name: 'Google-Extended', pattern: /Google-Extended/i },
20
+
21
+ // Traditional bots
22
+ { name: 'Googlebot', pattern: /Googlebot\/?([\d.]*)/i },
23
+ { name: 'Bingbot', pattern: /bingbot\/?([\d.]*)/i },
24
+ { name: 'YandexBot', pattern: /YandexBot\/?([\d.]*)/i },
25
+ { name: 'DuckDuckBot', pattern: /DuckDuckBot\/?([\d.]*)/i },
26
+ { name: 'Slurp', pattern: /Slurp/i },
27
+ { name: 'Baiduspider', pattern: /Baiduspider\/?([\d.]*)/i },
28
+ { name: 'curl', pattern: /curl\/?([\d.]*)/i },
29
+ { name: 'wget', pattern: /Wget\/?([\d.]*)/i },
30
+ { name: 'HeadlessChrome', pattern: /HeadlessChrome\/?([\d.]*)/i },
31
+ { name: 'PhantomJS', pattern: /PhantomJS\/?([\d.]*)/i },
32
+ { name: 'Puppeteer', pattern: /Puppeteer/i },
33
+ { name: 'Playwright', pattern: /Playwright/i },
34
+
35
+ // WebViews / In-app browsers (before generic browsers)
36
+ { name: 'Facebook', pattern: /\bFB[\w_]*\/?([\d.]*)/i },
37
+ { name: 'Instagram', pattern: /Instagram\s?([\d.]*)/i },
38
+ { name: 'Twitter', pattern: /Twitter/i },
39
+ { name: 'TikTok', pattern: /TikTok/i },
40
+ { name: 'Snapchat', pattern: /Snapchat/i },
41
+ { name: 'LinkedIn', pattern: /LinkedInApp/i },
42
+ { name: 'Pinterest', pattern: /Pinterest/i },
43
+ { name: 'WhatsApp', pattern: /WhatsApp\/?([\d.]*)/i },
44
+ { name: 'Telegram', pattern: /TelegramBot/i },
45
+
46
+ // Chromium-based (before Chrome)
47
+ { name: 'Edge', pattern: /Edg(?:e|A|iOS)?\/?([\d.]*)/i },
48
+ { name: 'Opera', pattern: /(?:OPR|Opera)\/?([\d.]*)/i },
49
+ { name: 'Brave', pattern: /Brave\/?([\d.]*)/i },
50
+ { name: 'Vivaldi', pattern: /Vivaldi\/?([\d.]*)/i },
51
+ { name: 'Yandex', pattern: /YaBrowser\/?([\d.]*)/i },
52
+ { name: 'Samsung Internet', pattern: /SamsungBrowser\/?([\d.]*)/i },
53
+ { name: 'UC Browser', pattern: /UCBrowser\/?([\d.]*)/i },
54
+ { name: 'QQ Browser', pattern: /QQBrowser\/?([\d.]*)/i },
55
+ { name: 'Whale', pattern: /Whale\/?([\d.]*)/i },
56
+
57
+ // Major browsers
58
+ { name: 'Chrome', pattern: /(?:Chrome|CriOS)\/?([\d.]*)/i },
59
+ { name: 'Firefox', pattern: /(?:Firefox|FxiOS)\/?([\d.]*)/i },
60
+ { name: 'Safari', pattern: /Version\/([\d.]*)\s.*Safari/i },
61
+
62
+ // Legacy IE
63
+ { name: 'IE', pattern: /(?:MSIE\s|Trident.*rv:)([\d.]*)/i },
64
+ ];
65
+
66
+ // OS detection patterns - ORDER MATTERS (specific before generic)
67
+ const OS_PATTERNS = [
68
+ { name: 'iOS', pattern: /(?:iPhone|iPad|iPod).*?OS\s([\d_]+)/i, versionSep: '_' },
69
+ // Android - exclude "like Android" (e.g., Kindle UA)
70
+ { name: 'Android', pattern: /(?<!like\s)Android\s?([\d.]*)/i },
71
+ { name: 'macOS', pattern: /Mac OS X\s?([\d_\.]*)/i, versionSep: '_' },
72
+ {
73
+ name: 'Windows', pattern: /Windows NT\s?([\d.]*)/i, versionMap: {
74
+ '10.0': '10', '6.3': '8.1', '6.2': '8', '6.1': '7', '6.0': 'Vista', '5.1': 'XP'
75
+ }
76
+ },
77
+ { name: 'Chrome OS', pattern: /CrOS\s\w+\s([\d.]*)/i },
78
+ // Specific distros before generic Linux
79
+ { name: 'Ubuntu', pattern: /Ubuntu/i },
80
+ { name: 'Fedora', pattern: /Fedora/i },
81
+ { name: 'FreeBSD', pattern: /FreeBSD/i },
82
+ { name: 'Linux', pattern: /Linux/i },
83
+ ];
84
+
85
+ // Engine detection patterns
86
+ const ENGINES = [
87
+ { name: 'Blink', pattern: /Chrome\/([\d.]+)/i }, // Modern Chrome, Edge, Opera
88
+ { name: 'Gecko', pattern: /Gecko\/([\d.]+)/i },
89
+ { name: 'WebKit', pattern: /AppleWebKit\/([\d.]+)/i },
90
+ { name: 'Trident', pattern: /Trident\/([\d.]+)/i },
91
+ { name: 'EdgeHTML', pattern: /Edge\/([\d.]+)/i },
92
+ { name: 'Presto', pattern: /Presto\/([\d.]+)/i },
93
+ ];
94
+
95
+ // Device type patterns - ORDER MATTERS (console before tablet/mobile to catch Xbox/PlayStation first)
96
+ const DEVICE_PATTERNS = [
97
+ { type: 'console', pattern: /PlayStation|Xbox|Nintendo/i },
98
+ { type: 'tablet', pattern: /iPad|Android(?!.*Mobile)|Tablet|PlayBook/i },
99
+ { type: 'mobile', pattern: /Mobile|Android.*Mobile|iPhone|iPod|BlackBerry|IEMobile|Opera Mini|Opera Mobi/i },
100
+ { type: 'smarttv', pattern: /SmartTV|Smart-TV|GoogleTV|AppleTV|BRAVIA|WebOS|Tizen|HbbTV|NetCast/i },
101
+ { type: 'wearable', pattern: /Watch|Fitbit/i },
102
+ { type: 'embedded', pattern: /Embedded/i },
103
+ ];
104
+
105
+ // Device vendor/model patterns
106
+ const DEVICE_VENDORS = [
107
+ { vendor: 'Apple', pattern: /iPhone|iPad|iPod|Macintosh|AppleTV/i },
108
+ { vendor: 'Samsung', pattern: /Samsung|SM-|GT-/i },
109
+ { vendor: 'Huawei', pattern: /Huawei|HUAWEI/i },
110
+ // Xiaomi: brand names + model codes (e.g., 24030PN60G, M2102J20SG)
111
+ { vendor: 'Xiaomi', pattern: /Xiaomi|Mi\s|Redmi|\b\d{5}[A-Z]{2}\d{2}[A-Z]\b|\bM\d{4}[A-Z]\d{2}[A-Z]{2}\b/i },
112
+ { vendor: 'Google', pattern: /Pixel|Nexus/i },
113
+ { vendor: 'OnePlus', pattern: /OnePlus|ONEPLUS/i },
114
+ { vendor: 'LG', pattern: /LG[-;\/\s]/i },
115
+ // Sony: brand + Xperia + tablet codes (SGP)
116
+ { vendor: 'Sony', pattern: /Sony|Xperia|PlayStation|\bSGP\d+\b/i },
117
+ { vendor: 'Motorola', pattern: /Motorola|Moto\s|\bmoto\s/i },
118
+ { vendor: 'HTC', pattern: /HTC/i },
119
+ { vendor: 'Nokia', pattern: /Nokia/i },
120
+ { vendor: 'Oppo', pattern: /OPPO/i },
121
+ { vendor: 'Vivo', pattern: /vivo/i },
122
+ { vendor: 'Realme', pattern: /RMX\d/i },
123
+ // Microsoft: Xbox, Surface, and "Microsoft;" in UA
124
+ { vendor: 'Microsoft', pattern: /Xbox|Surface|Microsoft;/i },
125
+ { vendor: 'Nintendo', pattern: /Nintendo/i },
126
+ ];
127
+
128
+ // CPU architecture patterns
129
+ const CPU_PATTERNS = [
130
+ { architecture: 'arm64', pattern: /aarch64|arm64/i },
131
+ { architecture: 'arm', pattern: /arm(?!64)/i },
132
+ { architecture: 'amd64', pattern: /x64|x86_64|amd64|Win64|WOW64/i },
133
+ { architecture: 'ia32', pattern: /x86|i[36]86/i },
134
+ ];
135
+
136
+ // Bot detection - comprehensive list
137
+ const BOT_PATTERNS = [
138
+ // AI bots
139
+ /ChatGPT|GPTBot|OAI-SearchBot|ClaudeBot|Claude-User|Claude-SearchBot|PerplexityBot|Perplexity-User|Google-Extended/i,
140
+ // Traditional search
141
+ /bot|crawl|spider|slurp|search|fetch|monitor|check|scan/i,
142
+ // Specific bots
143
+ /Googlebot|Bingbot|YandexBot|DuckDuckBot|Baiduspider|Sogou|Exabot|facebot|ia_archiver/i,
144
+ // Tools
145
+ /curl|wget|python|java|perl|ruby|php|http|node|axios|got\//i,
146
+ // Headless
147
+ /HeadlessChrome|PhantomJS|Puppeteer|Playwright|Selenium|WebDriver/i,
148
+ ];
149
+
150
+ // Model extraction patterns
151
+ const MODEL_PATTERNS = [
152
+ // Apple devices - including iPad16,3 format
153
+ { pattern: /(iPad\d+,\d+|iPhone\d+,\d+|iPod\d+,\d+)/, extract: (m) => m[1].replace(/\d+,\d+/, '') },
154
+ { pattern: /(iPhone|iPad|iPod)[\s;]/, extract: (m) => m[1] },
155
+ // Samsung
156
+ { pattern: /(SM-[A-Z0-9]+|GT-[A-Z0-9]+)/i, extract: (m) => m[1] },
157
+ // Google Pixel
158
+ { pattern: /(Pixel[\s]?\d*[a-z]?\s?(?:Pro|XL)?)/i, extract: (m) => m[1].trim() },
159
+ // Nexus
160
+ { pattern: /(Nexus\s?\d+[a-z]?)/i, extract: (m) => m[1] },
161
+ // Generic Android - "Build/MODEL" or "; MODEL Build"
162
+ { pattern: /;\s*([^;)]+)\s*Build\//i, extract: (m) => m[1].trim() },
163
+ ];
164
+
165
+ /**
166
+ * Parse a User-Agent string and extract browser, OS, device, and bot info
167
+ * @param {string|null|undefined} ua - The User-Agent string
168
+ * @returns {Object} Parsed results matching ua-parser-js structure
169
+ */
170
+ function parseUserAgent(ua) {
171
+ // Handle null/undefined
172
+ if (ua == null || typeof ua !== 'string') {
173
+ return createEmptyResult(ua);
174
+ }
175
+
176
+ const result = {
177
+ browser: { name: null, version: null, major: null },
178
+ engine: { name: null, version: null },
179
+ os: { name: null, version: null },
180
+ device: { type: null, vendor: null, model: null },
181
+ cpu: { architecture: null },
182
+ isBot: false,
183
+ raw: ua,
184
+ };
185
+
186
+ // Detect browser
187
+ for (const { name, pattern } of BROWSERS) {
188
+ const match = ua.match(pattern);
189
+ if (match) {
190
+ result.browser.name = name;
191
+ result.browser.version = match[1] || null;
192
+ result.browser.major = match[1] ? match[1].split('.')[0] : null;
193
+ break;
194
+ }
195
+ }
196
+
197
+ // Fallback: Safari without version
198
+ if (!result.browser.name && /Safari/i.test(ua) && !/Chrome/i.test(ua)) {
199
+ result.browser.name = 'Safari';
200
+ const safariMatch = ua.match(/Safari\/([\d.]+)/i);
201
+ result.browser.version = safariMatch ? safariMatch[1] : null;
202
+ result.browser.major = result.browser.version ? result.browser.version.split('.')[0] : null;
203
+ }
204
+
205
+ // Detect engine
206
+ for (const { name, pattern } of ENGINES) {
207
+ const match = ua.match(pattern);
208
+ if (match) {
209
+ result.engine.name = name;
210
+ result.engine.version = match[1] || null;
211
+ break;
212
+ }
213
+ }
214
+
215
+ // Detect OS
216
+ for (const { name, pattern, versionSep, versionMap } of OS_PATTERNS) {
217
+ const match = ua.match(pattern);
218
+ if (match) {
219
+ result.os.name = name;
220
+ let version = match[1] || null;
221
+ if (version && versionSep) {
222
+ version = version.replace(new RegExp(versionSep, 'g'), '.');
223
+ }
224
+ if (version && versionMap && versionMap[version]) {
225
+ version = versionMap[version];
226
+ }
227
+ result.os.version = version;
228
+ break;
229
+ }
230
+ }
231
+
232
+ // Detect device type (check tablet before mobile due to iPad containing 'Mobile')
233
+ for (const { type, pattern } of DEVICE_PATTERNS) {
234
+ if (pattern.test(ua)) {
235
+ result.device.type = type;
236
+ break;
237
+ }
238
+ }
239
+
240
+ // Detect device vendor
241
+ for (const { vendor, pattern } of DEVICE_VENDORS) {
242
+ if (pattern.test(ua)) {
243
+ result.device.vendor = vendor;
244
+ break;
245
+ }
246
+ }
247
+
248
+ // Detect device model
249
+ for (const { pattern, extract } of MODEL_PATTERNS) {
250
+ const match = ua.match(pattern);
251
+ if (match) {
252
+ result.device.model = extract(match);
253
+ break;
254
+ }
255
+ }
256
+
257
+ // Detect CPU architecture
258
+ for (const { architecture, pattern } of CPU_PATTERNS) {
259
+ if (pattern.test(ua)) {
260
+ result.cpu.architecture = architecture;
261
+ break;
262
+ }
263
+ }
264
+
265
+ // Detect bot
266
+ result.isBot = BOT_PATTERNS.some(pattern => pattern.test(ua));
267
+
268
+ return result;
269
+ }
270
+
271
+ /**
272
+ * Create an empty result for null/undefined/empty UA
273
+ */
274
+ function createEmptyResult(ua) {
275
+ return {
276
+ browser: { name: null, version: null, major: null },
277
+ engine: { name: null, version: null },
278
+ os: { name: null, version: null },
279
+ device: { type: null, vendor: null, model: null },
280
+ cpu: { architecture: null },
281
+ isBot: false,
282
+ raw: ua || null,
283
+ };
284
+ }
285
+
286
+ module.exports = parseUserAgent;
@@ -1,74 +0,0 @@
1
- # 🦍 Bun — Example
2
-
3
- A minimal real-time chat app using Bun's native HTTP server with api-ape.
4
-
5
- ## Quick Start
6
-
7
- ```bash
8
- bun install
9
- bun run start
10
- ```
11
-
12
- Open http://localhost:3000 in multiple browser windows.
13
-
14
- ## Project Structure
15
-
16
- ```
17
- Bun/
18
- ├── server.ts # Bun server with api-ape (TypeScript)
19
- ├── api/
20
- │ └── message.ts # Broadcast to other clients
21
- ├── index.html # Chat UI
22
- └── styles.css # Styling
23
- ```
24
-
25
- ## How It Works
26
-
27
- ### Server (server.js)
28
-
29
- ```js
30
- const ape = require('api-ape')
31
-
32
- const server = Bun.serve({
33
- port: 3000,
34
- fetch(req) {
35
- const url = new URL(req.url)
36
- if (url.pathname === '/') {
37
- return new Response(Bun.file('./index.html'))
38
- }
39
- return new Response('Not Found', { status: 404 })
40
- }
41
- })
42
-
43
- // Pass Bun's server to api-ape
44
- ape(server, {
45
- where: 'api',
46
- onConnent: (socket, req, send) => {
47
- send('init', { history: [], users: ape.online() })
48
- ape.broadcast('users', { count: ape.online() })
49
-
50
- return {
51
- onDisconnent: () => ape.broadcast('users', { count: ape.online() })
52
- }
53
- }
54
- })
55
- ```
56
-
57
- ## Why Bun?
58
-
59
- | Feature | Benefit |
60
- |---------|---------|
61
- | **No Express needed** | Bun has built-in HTTP server |
62
- | **Fast startup** | Bun starts in milliseconds |
63
- | **Native TypeScript** | No build step for TS files |
64
- | **Smaller footprint** | Fewer dependencies |
65
-
66
- ## Key Concepts Demonstrated
67
-
68
- | Concept | Example |
69
- |---------|---------|
70
- | Auto-wiring | `ape(server, { where: 'api' })` loads `api/*.js` |
71
- | onConnect hook | `onConnent: (socket, req, send) => { ... }` |
72
- | Push on connect | `send('init', { history, users })` |
73
- | Broadcast all | `broadcast('users', { count })` |
74
- | Broadcast others | `this.broadcastOthers('message', data)` |
@@ -1,11 +0,0 @@
1
- const messages: Array<{ user: string; text: string }> = []
2
-
3
- // Send message → broadcast to others
4
- module.exports = function (this: any, data: { user: string; text: string }) {
5
- messages.push(data)
6
- this.broadcastOthers('message', data)
7
- return data
8
- }
9
-
10
- // Export messages for history
11
- module.exports._messages = messages
@@ -1,76 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
-
4
- <head>
5
- <title>Chat - api-ape</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <link rel="stylesheet" href="/styles.css">
8
- </head>
9
-
10
- <body>
11
- <div id="app">
12
- <div class="chat-container">
13
- <div class="header">
14
- <span class="title">💬 Chat</span>
15
- <span class="online-count">🟢 {{ users }} online</span>
16
- <span class="user-badge">● {{ user }}</span>
17
- </div>
18
- <div class="messages">
19
- <div v-if="messages.length === 0" class="empty-state">No messages yet...</div>
20
- <div v-for="(m, i) in messages" :key="i" :class="['message', m.user === user ? 'mine' : '']">
21
- <span class="username">{{ m.user }}</span>
22
- <span class="text">{{ m.text }}</span>
23
- </div>
24
- </div>
25
- <div class="input-area">
26
- <input class="message-input" placeholder="Type a message..." v-model="input" @keydown.enter="send" autofocus>
27
- </div>
28
- </div>
29
- </div>
30
-
31
- <script src="https://unpkg.com/vue@3"></script>
32
- <script src="/api/ape.js"></script>
33
- <script>
34
- const { createApp, ref, onMounted } = Vue
35
-
36
- createApp({
37
- setup() {
38
- const user = ref('User' + Math.random().toString(36).slice(2, 6))
39
- const users = ref(0)
40
- const messages = ref([])
41
- const input = ref('')
42
-
43
- onMounted(() => {
44
- // Receive init with history + user count on connect
45
- ape.on('init', ({ data }) => {
46
- messages.value = data.history || []
47
- users.value = data.users || 0
48
- })
49
-
50
- // Listen for user count updates
51
- ape.on('users', ({ data }) => {
52
- users.value = data.count
53
- })
54
-
55
- // Listen for new messages from others
56
- ape.on('message', ({ data }) => {
57
- messages.value.push(data)
58
- })
59
- })
60
-
61
- function send() {
62
- if (!input.value) return
63
- const msg = { user: user.value, text: input.value }
64
- ape.message(msg).then(() => {
65
- messages.value.push(msg)
66
- })
67
- input.value = ''
68
- }
69
-
70
- return { user, users, messages, input, send }
71
- }
72
- }).mount('#app')
73
- </script>
74
- </body>
75
-
76
- </html>
@@ -1,9 +0,0 @@
1
- {
2
- "name": "bun-chat-example",
3
- "scripts": {
4
- "start": "bun run server.ts"
5
- },
6
- "dependencies": {
7
- "api-ape": "file:../.."
8
- }
9
- }
@@ -1,59 +0,0 @@
1
- /**
2
- * Bun server using api-ape library (TypeScript)
3
- * Bun natively supports TypeScript - no build step needed!
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
-
11
- const port = parseInt(process.env.PORT || '3000', 10)
12
-
13
- // Create HTTP server
14
- const server = createServer((req: IncomingMessage, res: ServerResponse) => {
15
- const url = new URL(req.url || '/', `http://localhost:${port}`)
16
-
17
- if (url.pathname === '/') {
18
- res.writeHead(200, { 'Content-Type': 'text/html' })
19
- res.end(fs.readFileSync(path.join(__dirname, 'index.html')))
20
- return
21
- }
22
-
23
- if (url.pathname === '/styles.css') {
24
- res.writeHead(200, { 'Content-Type': 'text/css' })
25
- res.end(fs.readFileSync(path.join(__dirname, 'styles.css')))
26
- return
27
- }
28
-
29
- res.writeHead(404)
30
- res.end('Not Found')
31
- })
32
-
33
- // Initialize api-ape
34
- ape(server, {
35
- where: 'api',
36
- onConnent: (socket, req, send) => {
37
- const messageModule = require('./api/message')
38
- setTimeout(() => {
39
- send('init', { history: messageModule._messages, users: ape.online() })
40
- ape.broadcast('users', { count: ape.online() })
41
- }, 100)
42
-
43
- return {
44
- onDisconnent: () => ape.broadcast('users', { count: ape.online() })
45
- }
46
- }
47
- })
48
-
49
- server.listen(port, () => {
50
- console.log(`
51
- ╔═══════════════════════════════════════════════════════╗
52
- ║ 🦍 api-ape Bun Example (TypeScript) ║
53
- ╠═══════════════════════════════════════════════════════╣
54
- ║ HTTP: http://localhost:${port}/ ║
55
- ║ WebSocket: ws://localhost:${port}/api/ape ║
56
- ║ Runtime: Bun 🥖 + TypeScript ║
57
- ╚═══════════════════════════════════════════════════════╝
58
- `)
59
- })
@@ -1,128 +0,0 @@
1
- * {
2
- box-sizing: border-box;
3
- margin: 0;
4
- padding: 0;
5
- }
6
-
7
- body {
8
- min-height: 100vh;
9
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
10
- color: #fff;
11
- font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;
12
- display: flex;
13
- justify-content: center;
14
- padding: 2rem;
15
- }
16
-
17
- #app {
18
- max-width: 500px;
19
- width: 100%;
20
- }
21
-
22
- .chat-container {
23
- background: rgba(255, 255, 255, 0.05);
24
- border-radius: 20px;
25
- overflow: hidden;
26
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
27
- }
28
-
29
- .header {
30
- display: flex;
31
- justify-content: space-between;
32
- align-items: center;
33
- padding: 1rem 1.5rem;
34
- background: rgba(255, 255, 255, 0.1);
35
- }
36
-
37
- .title {
38
- font-size: 1.25rem;
39
- font-weight: bold;
40
- background: linear-gradient(90deg, #00d2ff, #3a7bd5);
41
- -webkit-background-clip: text;
42
- -webkit-text-fill-color: transparent;
43
- background-clip: text;
44
- }
45
-
46
- .user-badge {
47
- font-size: 0.85rem;
48
- color: #0f0;
49
- font-weight: bold;
50
- }
51
-
52
- .messages {
53
- height: 350px;
54
- overflow-y: auto;
55
- padding: 1rem;
56
- }
57
-
58
- .messages::-webkit-scrollbar {
59
- width: 6px;
60
- }
61
-
62
- .messages::-webkit-scrollbar-track {
63
- background: rgba(255, 255, 255, 0.05);
64
- }
65
-
66
- .messages::-webkit-scrollbar-thumb {
67
- background: rgba(255, 255, 255, 0.2);
68
- border-radius: 3px;
69
- }
70
-
71
- .empty-state {
72
- text-align: center;
73
- color: #666;
74
- margin-top: 140px;
75
- }
76
-
77
- .message {
78
- display: flex;
79
- flex-direction: column;
80
- gap: 0.25rem;
81
- padding: 0.75rem 1rem;
82
- margin-bottom: 0.5rem;
83
- background: rgba(255, 255, 255, 0.05);
84
- border-radius: 12px;
85
- border-left: 3px solid #3a7bd5;
86
- }
87
-
88
- .message.mine {
89
- background: rgba(0, 210, 255, 0.15);
90
- border-left-color: #00d2ff;
91
- }
92
-
93
- .message .username {
94
- color: #00d2ff;
95
- font-size: 0.85rem;
96
- font-weight: bold;
97
- }
98
-
99
- .message .text {
100
- color: #fff;
101
- }
102
-
103
- .input-area {
104
- display: flex;
105
- gap: 0.5rem;
106
- padding: 1rem;
107
- border-top: 1px solid rgba(255, 255, 255, 0.1);
108
- }
109
-
110
- .message-input {
111
- flex: 1;
112
- padding: 0.75rem 1rem;
113
- font-size: 1rem;
114
- border: none;
115
- border-radius: 50px;
116
- background: rgba(255, 255, 255, 0.1);
117
- color: #fff;
118
- outline: none;
119
- transition: background 0.2s;
120
- }
121
-
122
- .message-input::placeholder {
123
- color: rgba(255, 255, 255, 0.5);
124
- }
125
-
126
- .message-input:focus {
127
- background: rgba(255, 255, 255, 0.15);
128
- }