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,109 @@
1
+ /**
2
+ * WebSocketServer class
3
+ * Handles HTTP upgrade requests and creates WebSocket connections
4
+ */
5
+
6
+ const { EventEmitter } = require('events')
7
+ const { generateAcceptKey } = require('./frames')
8
+ const { WebSocket } = require('./socket')
9
+
10
+ class WebSocketServer extends EventEmitter {
11
+ /**
12
+ * Create WebSocket server
13
+ * @param {{ noServer?: boolean }} options
14
+ */
15
+ constructor(options = {}) {
16
+ super()
17
+ this._noServer = options.noServer || false
18
+ this._clients = new Set()
19
+ }
20
+
21
+ /**
22
+ * Get all connected clients
23
+ * @returns {Set<WebSocket>}
24
+ */
25
+ get clients() {
26
+ return this._clients
27
+ }
28
+
29
+ /**
30
+ * Handle HTTP upgrade request
31
+ * @param {http.IncomingMessage} req - HTTP request
32
+ * @param {net.Socket} socket - TCP socket
33
+ * @param {Buffer} head - First packet of upgraded stream
34
+ * @param {function} callback - Called with WebSocket instance
35
+ */
36
+ handleUpgrade(req, socket, head, callback) {
37
+ // Validate WebSocket upgrade request
38
+ const upgrade = req.headers['upgrade']
39
+ if (!upgrade || upgrade.toLowerCase() !== 'websocket') {
40
+ socket.destroy()
41
+ return
42
+ }
43
+
44
+ const key = req.headers['sec-websocket-key']
45
+ if (!key) {
46
+ socket.destroy()
47
+ return
48
+ }
49
+
50
+ // Validate key is valid base64 (16 bytes = 24 chars base64)
51
+ if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) {
52
+ socket.destroy()
53
+ return
54
+ }
55
+
56
+ // Generate accept key
57
+ const acceptKey = generateAcceptKey(key)
58
+
59
+ // Build HTTP 101 response
60
+ const headers = [
61
+ 'HTTP/1.1 101 Switching Protocols',
62
+ 'Upgrade: websocket',
63
+ 'Connection: Upgrade',
64
+ `Sec-WebSocket-Accept: ${acceptKey}`,
65
+ '', '' // Empty line to end headers
66
+ ].join('\r\n')
67
+
68
+ // Send handshake response
69
+ socket.write(headers)
70
+
71
+ // If there's buffered data after upgrade, process it
72
+ // The 'head' contains any data received after the upgrade request
73
+ // We'll handle this in the WebSocket's buffer
74
+
75
+ // Create WebSocket wrapper
76
+ const ws = new WebSocket(socket)
77
+ this._clients.add(ws)
78
+
79
+ // Remove from clients on close
80
+ ws.on('close', () => {
81
+ this._clients.delete(ws)
82
+ })
83
+
84
+ // Handle any buffered data
85
+ if (head && head.length > 0) {
86
+ socket.unshift(head)
87
+ }
88
+
89
+ // Call callback with the WebSocket
90
+ if (callback) {
91
+ callback(ws)
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Close server and all connections
97
+ */
98
+ close(callback) {
99
+ for (const client of this._clients) {
100
+ client.close(1001, 'Server shutting down')
101
+ }
102
+ this._clients.clear()
103
+ if (callback) {
104
+ callback()
105
+ }
106
+ }
107
+ }
108
+
109
+ module.exports = { WebSocketServer }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * WebSocket connection class
3
+ * Wraps a TCP socket and handles WebSocket frame protocol
4
+ */
5
+
6
+ const { EventEmitter } = require('events')
7
+ const {
8
+ OPCODES,
9
+ parseFrame,
10
+ buildFrame,
11
+ buildCloseFrame,
12
+ buildPongFrame,
13
+ parseClosePayload
14
+ } = require('./frames')
15
+
16
+ // WebSocket ready states (matching ws library)
17
+ const READY_STATES = {
18
+ CONNECTING: 0,
19
+ OPEN: 1,
20
+ CLOSING: 2,
21
+ CLOSED: 3
22
+ }
23
+
24
+ class WebSocket extends EventEmitter {
25
+ constructor(socket) {
26
+ super()
27
+ this._socket = socket
28
+ this._readyState = READY_STATES.OPEN
29
+ this._buffer = Buffer.alloc(0)
30
+ this._fragments = []
31
+ this._fragmentOpcode = null
32
+
33
+ // Define constants on instance (matching ws library API)
34
+ this.CONNECTING = READY_STATES.CONNECTING
35
+ this.OPEN = READY_STATES.OPEN
36
+ this.CLOSING = READY_STATES.CLOSING
37
+ this.CLOSED = READY_STATES.CLOSED
38
+
39
+ this._setupSocketListeners()
40
+ }
41
+
42
+ get readyState() {
43
+ return this._readyState
44
+ }
45
+
46
+ /**
47
+ * Send data to the client
48
+ * @param {string|Buffer} data - Data to send
49
+ */
50
+ send(data) {
51
+ if (this._readyState !== READY_STATES.OPEN) {
52
+ throw new Error('WebSocket is not open')
53
+ }
54
+
55
+ const opcode = Buffer.isBuffer(data) ? OPCODES.BINARY : OPCODES.TEXT
56
+ const frame = buildFrame(data, opcode)
57
+ this._socket.write(frame)
58
+ }
59
+
60
+ /**
61
+ * Close the WebSocket connection
62
+ * @param {number} code - Status code
63
+ * @param {string} reason - Close reason
64
+ */
65
+ close(code = 1000, reason = '') {
66
+ if (this._readyState === READY_STATES.CLOSING ||
67
+ this._readyState === READY_STATES.CLOSED) {
68
+ return
69
+ }
70
+
71
+ this._readyState = READY_STATES.CLOSING
72
+ const frame = buildCloseFrame(code, reason)
73
+ this._socket.write(frame)
74
+
75
+ // Give time for close frame to send, then destroy
76
+ setTimeout(() => {
77
+ if (this._readyState !== READY_STATES.CLOSED) {
78
+ this._socket.destroy()
79
+ }
80
+ }, 100)
81
+ }
82
+
83
+ /**
84
+ * Set up TCP socket event handlers
85
+ */
86
+ _setupSocketListeners() {
87
+ this._socket.on('data', (data) => {
88
+ this._handleData(data)
89
+ })
90
+
91
+ this._socket.on('close', () => {
92
+ this._readyState = READY_STATES.CLOSED
93
+ this.emit('close')
94
+ })
95
+
96
+ this._socket.on('error', (err) => {
97
+ this.emit('error', err)
98
+ })
99
+ }
100
+
101
+ /**
102
+ * Handle incoming data, parse frames
103
+ * @param {Buffer} data - Incoming data chunk
104
+ */
105
+ _handleData(data) {
106
+ // Append to buffer
107
+ this._buffer = Buffer.concat([this._buffer, data])
108
+
109
+ // Process all complete frames in buffer
110
+ while (this._buffer.length > 0) {
111
+ const result = parseFrame(this._buffer)
112
+ if (!result) break // Incomplete frame
113
+
114
+ const { frame, bytesConsumed } = result
115
+ this._buffer = this._buffer.slice(bytesConsumed)
116
+
117
+ this._handleFrame(frame)
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Handle a parsed frame
123
+ * @param {{ fin: boolean, opcode: number, payload: Buffer }} frame
124
+ */
125
+ _handleFrame(frame) {
126
+ const { fin, opcode, payload } = frame
127
+
128
+ switch (opcode) {
129
+ case OPCODES.CONTINUATION:
130
+ this._handleContinuation(fin, payload)
131
+ break
132
+
133
+ case OPCODES.TEXT:
134
+ case OPCODES.BINARY:
135
+ if (fin) {
136
+ // Complete message
137
+ this._emitMessage(opcode, payload)
138
+ } else {
139
+ // Start of fragmented message
140
+ this._fragments = [payload]
141
+ this._fragmentOpcode = opcode
142
+ }
143
+ break
144
+
145
+ case OPCODES.CLOSE:
146
+ this._handleClose(payload)
147
+ break
148
+
149
+ case OPCODES.PING:
150
+ this._handlePing(payload)
151
+ break
152
+
153
+ case OPCODES.PONG:
154
+ // Pong received, could emit event if needed
155
+ break
156
+
157
+ default:
158
+ // Unknown opcode, close connection
159
+ this.close(1002, 'Unknown opcode')
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Handle continuation frame for fragmented messages
165
+ */
166
+ _handleContinuation(fin, payload) {
167
+ if (!this._fragmentOpcode) {
168
+ this.close(1002, 'Unexpected continuation frame')
169
+ return
170
+ }
171
+
172
+ this._fragments.push(payload)
173
+
174
+ if (fin) {
175
+ const completePayload = Buffer.concat(this._fragments)
176
+ this._emitMessage(this._fragmentOpcode, completePayload)
177
+ this._fragments = []
178
+ this._fragmentOpcode = null
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Emit message event
184
+ */
185
+ _emitMessage(opcode, payload) {
186
+ // For text, convert to string; for binary, keep as Buffer
187
+ // But ws library emits Buffer by default, so we match that
188
+ this.emit('message', payload)
189
+ }
190
+
191
+ /**
192
+ * Handle close frame
193
+ */
194
+ _handleClose(payload) {
195
+ const { code, reason } = parseClosePayload(payload)
196
+
197
+ if (this._readyState === READY_STATES.OPEN) {
198
+ // Send close frame back
199
+ this._readyState = READY_STATES.CLOSING
200
+ const frame = buildCloseFrame(code)
201
+ this._socket.write(frame, () => {
202
+ this._socket.destroy()
203
+ })
204
+ } else {
205
+ this._socket.destroy()
206
+ }
207
+
208
+ this._readyState = READY_STATES.CLOSED
209
+ }
210
+
211
+ /**
212
+ * Handle ping frame - respond with pong
213
+ */
214
+ _handlePing(payload) {
215
+ if (this._readyState === READY_STATES.OPEN) {
216
+ const pong = buildPongFrame(payload)
217
+ this._socket.write(pong)
218
+ }
219
+ }
220
+ }
221
+
222
+ module.exports = { WebSocket, READY_STATES }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * WebSocket Provider
3
+ * Detects runtime capabilities and provides appropriate WebSocket implementation
4
+ *
5
+ * Priority:
6
+ * 1. Deno native WebSocket (Deno.upgradeWebSocket)
7
+ * 2. Bun native WebSocket (Bun.serve websocket)
8
+ * 3. Node.js 24+ stable native WebSocketServer (node:ws module)
9
+ * 4. Custom polyfill (RFC 6455 compliant)
10
+ */
11
+
12
+ /**
13
+ * Check if running in Deno runtime
14
+ * @returns {boolean}
15
+ */
16
+ function isDeno() {
17
+ return typeof Deno !== 'undefined' && typeof Deno.upgradeWebSocket === 'function'
18
+ }
19
+
20
+ /**
21
+ * Check if running in Bun runtime
22
+ * Uses process.versions.bun which is the recommended detection method
23
+ * @returns {boolean}
24
+ */
25
+ function isBun() {
26
+ return typeof process !== 'undefined' && !!process.versions?.bun
27
+ }
28
+
29
+ /**
30
+ * Check if Node.js version is 24+ stable (not pre-release)
31
+ * @returns {boolean}
32
+ */
33
+ function isNode24Stable() {
34
+ if (typeof process === 'undefined' || !process.versions || !process.versions.node) {
35
+ return false
36
+ }
37
+
38
+ // Skip if this is actually Bun (Bun also has process.versions.node)
39
+ if (isBun()) {
40
+ return false
41
+ }
42
+
43
+ const versionStr = process.versions.node
44
+ const majorVersion = parseInt(versionStr.split('.')[0], 10)
45
+
46
+ // Must be Node 24+
47
+ if (majorVersion < 24) {
48
+ return false
49
+ }
50
+
51
+ // Check if this is a stable release (not RC, alpha, beta)
52
+ // Pre-release versions contain hyphens like "24.0.0-rc.1"
53
+ if (versionStr.includes('-')) {
54
+ return false
55
+ }
56
+
57
+ return true
58
+ }
59
+
60
+ /**
61
+ * Get the detected runtime type
62
+ * @returns {'deno' | 'bun' | 'node' | 'unknown'}
63
+ */
64
+ function getRuntime() {
65
+ if (isDeno()) return 'deno'
66
+ if (isBun()) return 'bun'
67
+ if (typeof process !== 'undefined' && process.versions?.node) return 'node'
68
+ return 'unknown'
69
+ }
70
+
71
+ /**
72
+ * Get WebSocket provider based on runtime capabilities
73
+ * @returns {{ type: string, WebSocketServer: typeof import('./ws').WebSocketServer, runtime: string }}
74
+ */
75
+ function getWebSocketProvider() {
76
+ const runtime = getRuntime()
77
+
78
+ // 1. Check for Deno runtime
79
+ if (runtime === 'deno') {
80
+ try {
81
+ const { DenoWebSocketServer } = require('./ws/adapters/deno')
82
+ return { type: 'deno-native', WebSocketServer: DenoWebSocketServer, runtime }
83
+ } catch {
84
+ // Adapter not available, fall through to polyfill
85
+ }
86
+ }
87
+
88
+ // 2. Check for Bun runtime
89
+ if (runtime === 'bun') {
90
+ try {
91
+ const { BunWebSocketServer } = require('./ws/adapters/bun')
92
+ return { type: 'bun-native', WebSocketServer: BunWebSocketServer, runtime }
93
+ } catch {
94
+ // Adapter not available, fall through to polyfill
95
+ }
96
+ }
97
+
98
+ // 3. Try Node.js 24+ native WebSocketServer
99
+ if (isNode24Stable()) {
100
+ try {
101
+ const { WebSocketServer } = require('node:ws')
102
+ if (WebSocketServer) {
103
+ return { type: 'node-native', WebSocketServer, runtime }
104
+ }
105
+ } catch {
106
+ // node:ws not available, fall through
107
+ }
108
+ }
109
+
110
+ // 4. Fall back to our polyfill
111
+ const { WebSocketServer } = require('./ws/index')
112
+ return { type: 'polyfill', WebSocketServer, runtime }
113
+ }
114
+
115
+ // Cache the provider for consistent usage
116
+ let cachedProvider = null
117
+
118
+ /**
119
+ * Get cached WebSocket provider
120
+ * @returns {{ type: string, WebSocketServer: typeof import('./ws').WebSocketServer, runtime: string }}
121
+ */
122
+ function getCachedProvider() {
123
+ if (!cachedProvider) {
124
+ cachedProvider = getWebSocketProvider()
125
+ }
126
+ return cachedProvider
127
+ }
128
+
129
+ module.exports = {
130
+ getWebSocketProvider: getCachedProvider,
131
+ getRuntime,
132
+ isDeno,
133
+ isBun,
134
+ isNode24Stable
135
+ }
@@ -1,12 +1,24 @@
1
1
  const extractRootDomain = require('./extractRootDomain')
2
2
 
3
+ // Helper to get header that works with both Express and raw Node http
4
+ function getHeader(req, name) {
5
+ // Express-style
6
+ if (typeof req.header === 'function') {
7
+ return req.header(name)
8
+ }
9
+ // Raw Node.js http request
10
+ return req.headers[name.toLowerCase()]
11
+ }
12
+
3
13
  module.exports = function (socket, req, onError) {
4
14
  onError = onError || console.error
5
- const origin = extractRootDomain(req.header('Origin') || "");
6
- const host = extractRootDomain(req.header('Host'));
15
+ const origin = extractRootDomain(getHeader(req, 'Origin') || "")
16
+ const host = extractRootDomain(getHeader(req, 'Host'))
7
17
  if (origin && origin !== host) {
8
- onError("REJECTING socket from " + req.header('Origin') + " miss-match with " + req.header('Host'))
9
- socket.destroy()
18
+ onError("REJECTING socket from " + getHeader(req, 'Origin') + " miss-match with " + getHeader(req, 'Host'))
19
+ if (socket && socket.destroy) {
20
+ socket.destroy()
21
+ }
10
22
  return false
11
23
  }
12
24
  return true
@@ -89,9 +89,21 @@ function setValueAtPath(obj, path, value) {
89
89
  current[parts[parts.length - 1]] = value
90
90
  }
91
91
 
92
+ /**
93
+ * Extract sessionId cookie from request headers
94
+ */
95
+ function getSessionId(req) {
96
+ const cookies = req?.headers?.cookie || ''
97
+ const match = cookies.match(/(?:^|;\s*)sessionId=([^;]*)/)
98
+ return match ? match[1] : null
99
+ }
100
+
92
101
  module.exports = function receiveHandler(ape) {
93
102
  const { send, checkReply, events, controllers, sharedValues, hostId, embedValues, fileTransfer } = ape
94
103
 
104
+ // Extract sessionId from request cookies (set by outer framework session management)
105
+ const sessionId = getSessionId(sharedValues.req)
106
+
95
107
  // Build `this` context for controllers
96
108
  // Includes: client metadata + api-ape utilities
97
109
  const that = {
@@ -102,7 +114,8 @@ module.exports = function receiveHandler(ape) {
102
114
  broadcastOthers: (type, data) => broadcast(type, data, hostId), // exclude self
103
115
  online,
104
116
  getClients,
105
- hostId
117
+ hostId,
118
+ sessionId // Session ID from cookie (set by outer framework)
106
119
  }
107
120
 
108
121
  return async function onReceive(msg) {
@@ -40,7 +40,7 @@ function detectContentType(data) {
40
40
  * Process data object, replacing binary values with L-tagged hashes
41
41
  * Returns { processedData, binaryEntries }
42
42
  */
43
- function processBinaryData(data, queryId, fileTransfer, hostId, path = '') {
43
+ function processBinaryData(data, queryId, fileTransfer, clientId, path = '') {
44
44
  if (data === null || data === undefined) {
45
45
  return { processedData: data, binaryEntries: [] }
46
46
  }
@@ -49,7 +49,7 @@ function processBinaryData(data, queryId, fileTransfer, hostId, path = '') {
49
49
  // This is binary data - register and return hash
50
50
  const hash = FileTransferManager.generateHash(queryId, path || 'root')
51
51
  const contentType = detectContentType(data)
52
- fileTransfer.registerDownload(hash, data, contentType, hostId)
52
+ fileTransfer.registerDownload(hash, data, contentType, clientId)
53
53
 
54
54
  return {
55
55
  processedData: { [`__ape_link__`]: hash },
@@ -64,7 +64,7 @@ function processBinaryData(data, queryId, fileTransfer, hostId, path = '') {
64
64
  for (let i = 0; i < data.length; i++) {
65
65
  const itemPath = path ? `${path}.${i}` : String(i)
66
66
  const { processedData, binaryEntries } = processBinaryData(
67
- data[i], queryId, fileTransfer, hostId, itemPath
67
+ data[i], queryId, fileTransfer, clientId, itemPath
68
68
  )
69
69
  processedArray.push(processedData)
70
70
  allBinaryEntries.push(...binaryEntries)
@@ -80,7 +80,7 @@ function processBinaryData(data, queryId, fileTransfer, hostId, path = '') {
80
80
  for (const key of Object.keys(data)) {
81
81
  const itemPath = path ? `${path}.${key}` : key
82
82
  const { processedData, binaryEntries } = processBinaryData(
83
- data[key], queryId, fileTransfer, hostId, itemPath
83
+ data[key], queryId, fileTransfer, clientId, itemPath
84
84
  )
85
85
 
86
86
  // If this was binary data, mark the key with <!L> tag
@@ -99,7 +99,7 @@ function processBinaryData(data, queryId, fileTransfer, hostId, path = '') {
99
99
  return { processedData: data, binaryEntries: [] }
100
100
  }
101
101
 
102
- module.exports = function sendHandler({ socket, events, hostId, fileTransfer }) {
102
+ module.exports = function sendHandler({ socket, events, clientId, fileTransfer }) {
103
103
 
104
104
  return function send(queryId, type, data, err) {
105
105
  if (!type && !queryId) {
@@ -130,7 +130,7 @@ module.exports = function sendHandler({ socket, events, hostId, fileTransfer })
130
130
  let processedData = data
131
131
  if (fileTransfer && data && !err) {
132
132
  const { processedData: processed, binaryEntries } = processBinaryData(
133
- data, queryId || type, fileTransfer, hostId
133
+ data, queryId || type, fileTransfer, clientId
134
134
  )
135
135
  processedData = processed
136
136
  if (binaryEntries.length > 0) {
@@ -1,4 +1,4 @@
1
- if(!global.process){//(!!process && typeof process !== 'object'){
1
+ if (!global.process) {//(!!process && typeof process !== 'object'){
2
2
  throw new Error("deepRequire need to be run on Node server")
3
3
  }
4
4
 
@@ -18,7 +18,7 @@ function getFilesFromDir(dir, fileTypes) {
18
18
  if (fs.statSync(curFile).isFile() && fileTypes.indexOf(path.extname(curFile)) != -1) {
19
19
  filesToReturn.push(curFile.replace(dir, ''));
20
20
  } else if (fs.statSync(curFile).isDirectory()) {
21
- walkDir(curFile);
21
+ walkDir(curFile);
22
22
  }
23
23
  }
24
24
  };
@@ -27,19 +27,34 @@ function getFilesFromDir(dir, fileTypes) {
27
27
  }
28
28
  const re = /(?:\.([^.]+))?$/;
29
29
 
30
- module.exports = function(dirname,selector){
30
+ module.exports = function (dirname, selector) {
31
31
  selector = selector || ["js"]
32
- return getFilesFromDir(dirname, selector.map(ext=>`.${ext}`)).reduce((packages,file) =>{
32
+ const endpointSources = {} // Track which file defines each endpoint
33
33
 
34
- if(file === "/index.js") return packages
34
+ return getFilesFromDir(dirname, selector.map(ext => `.${ext}`)).reduce((packages, file) => {
35
+
36
+ if (file === "/index.js") return packages
35
37
  //if(file[0] !== "/") file = "/"+file;
36
38
 
37
- const pathParts = file.replace(re.exec(file)[0],"").split("/").slice(1)
38
- if(pathParts[pathParts.length-1] === "index")
39
- pathParts.pop()
39
+ const pathParts = file.replace(re.exec(file)[0], "").split("/").slice(1)
40
+ if (pathParts[pathParts.length - 1] === "index")
41
+ pathParts.pop()
42
+
43
+ const endpoint = pathParts.join("/").toLowerCase()
44
+
45
+ // Check for duplicate endpoints
46
+ if (packages[endpoint] !== undefined) {
47
+ throw new Error(
48
+ `🦍 Duplicate endpoint detected: "${endpoint}"\n` +
49
+ ` - ${endpointSources[endpoint]}\n` +
50
+ ` - ${file}\n` +
51
+ ` Remove one of these files to fix this conflict.`
52
+ )
53
+ }
40
54
 
41
- packages[pathParts.join("/").toLowerCase()] = require(dirname+`/${file}`)
55
+ endpointSources[endpoint] = file
56
+ packages[endpoint] = require(dirname + `/${file}`)
42
57
  return packages;
43
- },{});
58
+ }, {});
44
59
 
45
60
  }