api-ape 0.0.0 → 1.0.1

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 (63) hide show
  1. package/README.md +261 -0
  2. package/client/README.md +69 -0
  3. package/client/browser.js +17 -0
  4. package/client/connectSocket.js +260 -0
  5. package/dist/ape.js +454 -0
  6. package/example/ExpressJs/README.md +97 -0
  7. package/example/ExpressJs/api/message.js +11 -0
  8. package/example/ExpressJs/backend.js +37 -0
  9. package/example/ExpressJs/index.html +88 -0
  10. package/example/ExpressJs/package-lock.json +834 -0
  11. package/example/ExpressJs/package.json +10 -0
  12. package/example/ExpressJs/styles.css +128 -0
  13. package/example/NextJs/.dockerignore +29 -0
  14. package/example/NextJs/Dockerfile +52 -0
  15. package/example/NextJs/Dockerfile.dev +27 -0
  16. package/example/NextJs/README.md +113 -0
  17. package/example/NextJs/ape/client.js +66 -0
  18. package/example/NextJs/ape/embed.js +12 -0
  19. package/example/NextJs/ape/index.js +23 -0
  20. package/example/NextJs/ape/logic/chat.js +62 -0
  21. package/example/NextJs/ape/onConnect.js +69 -0
  22. package/example/NextJs/ape/onDisconnect.js +13 -0
  23. package/example/NextJs/ape/onError.js +9 -0
  24. package/example/NextJs/ape/onReceive.js +15 -0
  25. package/example/NextJs/ape/onSend.js +15 -0
  26. package/example/NextJs/api/message.js +44 -0
  27. package/example/NextJs/docker-compose.yml +22 -0
  28. package/example/NextJs/next-env.d.ts +5 -0
  29. package/example/NextJs/next.config.js +8 -0
  30. package/example/NextJs/package-lock.json +5107 -0
  31. package/example/NextJs/package.json +25 -0
  32. package/example/NextJs/pages/_app.tsx +6 -0
  33. package/example/NextJs/pages/index.tsx +182 -0
  34. package/example/NextJs/public/favicon.ico +0 -0
  35. package/example/NextJs/public/vercel.svg +4 -0
  36. package/example/NextJs/server.js +40 -0
  37. package/example/NextJs/styles/Chat.module.css +194 -0
  38. package/example/NextJs/styles/Home.module.css +129 -0
  39. package/example/NextJs/styles/globals.css +26 -0
  40. package/example/NextJs/tsconfig.json +20 -0
  41. package/example/README.md +66 -0
  42. package/index.d.ts +179 -0
  43. package/index.js +11 -0
  44. package/package.json +11 -4
  45. package/server/README.md +93 -0
  46. package/server/index.js +6 -0
  47. package/server/lib/broadcast.js +63 -0
  48. package/server/lib/loader.js +10 -0
  49. package/server/lib/main.js +23 -0
  50. package/server/lib/wiring.js +94 -0
  51. package/server/security/extractRootDomain.js +21 -0
  52. package/server/security/origin.js +13 -0
  53. package/server/security/reply.js +21 -0
  54. package/server/socket/open.js +10 -0
  55. package/server/socket/receive.js +66 -0
  56. package/server/socket/send.js +55 -0
  57. package/server/utils/deepRequire.js +45 -0
  58. package/server/utils/genId.js +24 -0
  59. package/todo.md +85 -0
  60. package/utils/jss.js +273 -0
  61. package/utils/jss.test.js +261 -0
  62. package/utils/messageHash.js +43 -0
  63. package/utils/messageHash.test.js +56 -0
package/index.d.ts ADDED
@@ -0,0 +1,179 @@
1
+ // Type definitions for api-ape
2
+ // Project: https://github.com/codemeasandwich/api-ape
3
+
4
+ import { Application } from 'express'
5
+ import { WebSocket } from 'ws'
6
+ import { IncomingMessage } from 'http'
7
+
8
+ // =============================================================================
9
+ // SERVER TYPES
10
+ // =============================================================================
11
+
12
+ /**
13
+ * Controller context available as `this` inside controller functions
14
+ */
15
+ export interface ControllerContext {
16
+ /** Send to ALL connected clients */
17
+ broadcast(type: string, data: any): void
18
+ /** Send to all clients EXCEPT the caller */
19
+ broadcastOthers(type: string, data: any): void
20
+ /** Get count of connected clients */
21
+ online(): number
22
+ /** Get array of connected hostIds */
23
+ getClients(): string[]
24
+ /** Unique ID of the calling client */
25
+ hostId: string
26
+ /** Original HTTP request */
27
+ req: IncomingMessage
28
+ /** WebSocket instance */
29
+ socket: WebSocket
30
+ /** Parsed user-agent info */
31
+ agent: {
32
+ browser: { name?: string; version?: string }
33
+ os: { name?: string; version?: string }
34
+ device: { type?: string; vendor?: string; model?: string }
35
+ }
36
+ /** Custom embedded values from onConnent */
37
+ [key: string]: any
38
+ }
39
+
40
+ /**
41
+ * Controller function type
42
+ */
43
+ export type ControllerFunction<T = any, R = any> = (
44
+ this: ControllerContext,
45
+ data: T
46
+ ) => R | Promise<R>
47
+
48
+ /**
49
+ * Send function provided to onConnent
50
+ */
51
+ export type SendFunction = {
52
+ (type: string, data: any): void
53
+ toString(): string
54
+ }
55
+
56
+ /**
57
+ * After hook returned from onReceive/onSend
58
+ */
59
+ export type AfterHook = (err: Error | null, result: any) => void
60
+
61
+ /**
62
+ * Connection lifecycle hooks returned from onConnent
63
+ */
64
+ export interface ConnectionHandlers {
65
+ /** Values to embed into controller context */
66
+ embed?: Record<string, any>
67
+ /** Called before processing incoming message, return after hook */
68
+ onReceive?: (queryId: string, data: any, type: string) => AfterHook | void
69
+ /** Called before sending message, return after hook */
70
+ onSend?: (data: any, type: string) => AfterHook | void
71
+ /** Called on error */
72
+ onError?: (errorString: string) => void
73
+ /** Called when client disconnects */
74
+ onDisconnent?: () => void
75
+ }
76
+
77
+ /**
78
+ * onConnent callback signature
79
+ */
80
+ export type OnConnectCallback = (
81
+ socket: WebSocket,
82
+ req: IncomingMessage,
83
+ send: SendFunction
84
+ ) => ConnectionHandlers | void
85
+
86
+ /**
87
+ * Server options for ape()
88
+ */
89
+ export interface ApeServerOptions {
90
+ /** Directory containing controller files */
91
+ where: string
92
+ /** Connection lifecycle hook */
93
+ onConnent?: OnConnectCallback
94
+ }
95
+
96
+ /**
97
+ * Initialize api-ape on an Express app
98
+ */
99
+ declare function ape(app: Application, options: ApeServerOptions): void
100
+
101
+ export default ape
102
+
103
+ // =============================================================================
104
+ // CLIENT TYPES
105
+ // =============================================================================
106
+
107
+ /**
108
+ * Message received from server
109
+ */
110
+ export interface ReceivedMessage<T = any> {
111
+ err?: Error | string
112
+ type: string
113
+ data: T
114
+ }
115
+
116
+ /**
117
+ * Message handler callback
118
+ */
119
+ export type MessageHandler<T = any> = (message: ReceivedMessage<T>) => void
120
+
121
+ /**
122
+ * Proxy-based sender - any property access creates a callable path
123
+ * Example: sender.users.list() calls type="/users/list"
124
+ */
125
+ export interface ApeSender {
126
+ [key: string]: ApeSender & (<T = any, R = any>(data?: T) => Promise<R>)
127
+ }
128
+
129
+ /**
130
+ * Set receiver for specific message type or all messages
131
+ */
132
+ export type SetOnReceiver = {
133
+ (type: string, handler: MessageHandler): void
134
+ (handler: MessageHandler): void
135
+ }
136
+
137
+ /**
138
+ * Connected client interface
139
+ */
140
+ export interface ApeClient {
141
+ sender: ApeSender
142
+ setOnReciver: SetOnReceiver
143
+ }
144
+
145
+ /**
146
+ * Configuration options for client
147
+ */
148
+ export interface ApeClientConfig {
149
+ /** WebSocket port */
150
+ port?: number
151
+ /** WebSocket host */
152
+ host?: string
153
+ }
154
+
155
+ /**
156
+ * Connect socket function with configuration methods
157
+ */
158
+ export interface ConnectSocket {
159
+ (): ApeClient
160
+ /** Configure connection options */
161
+ configure(options: ApeClientConfig): void
162
+ /** Enable auto-reconnection on disconnect */
163
+ autoReconnect(): void
164
+ }
165
+
166
+ /**
167
+ * Client module default export
168
+ */
169
+ declare const connectSocket: ConnectSocket
170
+
171
+ export { connectSocket }
172
+
173
+ // =============================================================================
174
+ // BROADCAST MODULE
175
+ // =============================================================================
176
+
177
+ export declare const broadcast: (type: string, data: any) => void
178
+ export declare const online: () => number
179
+ export declare const getClients: () => string[]
package/index.js ADDED
@@ -0,0 +1,11 @@
1
+
2
+ let apiApe;
3
+
4
+ if ('undefined' === typeof window
5
+ || 'undefined' === typeof window.document) {
6
+ apiApe = require('./server');
7
+ } else {
8
+ apiApe = require('./client');
9
+ }
10
+
11
+ module.exports = apiApe
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "api-ape",
3
- "version": "0.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Remote procedure events",
5
5
  "main": "index.js",
6
- "scripts": {
6
+ "types": "index.d.ts",
7
+ "scripts": {
7
8
  "test": "jest --no-cache ",
8
9
  "test:go": "npm test -- --watch --coverage",
9
10
  "test:update": "npm test -- --updateSnapshot",
@@ -20,12 +21,18 @@
20
21
  "ape"
21
22
  ],
22
23
  "author": "brian shannon",
23
- "license": "ISC",
24
+ "license": "MIT",
24
25
  "bugs": {
25
26
  "url": "https://github.com/codemeasandwich/api-ape/issues"
26
27
  },
27
28
  "homepage": "https://github.com/codemeasandwich/api-ape#readme",
28
29
  "dependencies": {
29
- "jest": "^29.3.1"
30
+ "express-ws": "^5.0.2",
31
+ "jest": "^29.3.1",
32
+ "ua-parser-js": "^1.0.37",
33
+ "ws": "^8.14.0"
34
+ },
35
+ "devDependencies": {
36
+ "esbuild": "^0.27.2"
30
37
  }
31
38
  }
@@ -0,0 +1,93 @@
1
+ # 🦍 api-ape Server
2
+
3
+ Express.js integration for WebSocket-based Remote Procedure Events (RPE).
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ server/
9
+ ├── index.js # Entry point (exports lib/main)
10
+ ├── lib/
11
+ │ ├── main.js # Express integration & setup
12
+ │ ├── loader.js # Auto-loads controller files from folder
13
+ │ ├── broadcast.js # Client tracking & broadcast utilities
14
+ │ └── wiring.js # WebSocket handler setup
15
+ ├── socket/
16
+ │ ├── receive.js # Incoming message handler
17
+ │ └── send.js # Outgoing message handler
18
+ ├── security/
19
+ │ └── reply.js # Duplicate request protection
20
+ └── utils/
21
+ └── ... # Server utilities
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ npm i api-ape
28
+ ```
29
+
30
+ ```js
31
+ const express = require('express')
32
+ const ape = require('api-ape')
33
+
34
+ const app = express()
35
+
36
+ ape(app, {
37
+ where: 'api', // Controller directory
38
+ onConnent: (socket, req, send) => ({
39
+ embed: { userId: req.session?.userId },
40
+ onDisconnent: () => console.log('Client left')
41
+ })
42
+ })
43
+
44
+ app.listen(3000)
45
+ ```
46
+
47
+ ## API
48
+
49
+ ### `ape(app, options)`
50
+
51
+ | Option | Type | Description |
52
+ |--------|------|-------------|
53
+ | `where` | `string` | Directory containing controller files |
54
+ | `onConnent` | `function` | Connection lifecycle hook |
55
+
56
+ ### Controller Context (`this`)
57
+
58
+ | Property | Description |
59
+ |----------|-------------|
60
+ | `this.broadcast(type, data)` | Send to ALL connected clients |
61
+ | `this.broadcastOthers(type, data)` | Send to all EXCEPT the caller |
62
+ | `this.online()` | Get count of connected clients |
63
+ | `this.getClients()` | Get array of connected hostIds |
64
+ | `this.hostId` | Unique ID of the calling client |
65
+ | `this.req` | Original HTTP request |
66
+ | `this.socket` | WebSocket instance |
67
+ | `this.agent` | Parsed user-agent |
68
+
69
+ ### Connection Lifecycle Hooks
70
+
71
+ ```js
72
+ onConnent(socket, req, send) {
73
+ return {
74
+ embed: { ... }, // Values available as this.* in controllers
75
+ onReceive: (queryId, data, type) => afterFn,
76
+ onSend: (data, type) => afterFn,
77
+ onError: (errStr) => { ... },
78
+ onDisconnent: () => { ... }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Auto-Routing
84
+
85
+ Drop JS files in your `where` directory:
86
+
87
+ ```
88
+ api/
89
+ ├── hello.js → ape.hello(data)
90
+ ├── users/
91
+ │ ├── list.js → ape.users.list(data)
92
+ │ └── create.js → ape.users.create(data)
93
+ ```
@@ -0,0 +1,6 @@
1
+ /**
2
+ * api-ape server entry point
3
+ * Exports the main ape function
4
+ */
5
+
6
+ module.exports = require('./lib/main')
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Broadcast utilities for api-ape
3
+ * Tracks connected clients and provides broadcast functions
4
+ */
5
+
6
+ // Track all connected clients for broadcast
7
+ const connectedClients = new Set()
8
+
9
+ /**
10
+ * Add a client to the connected set
11
+ */
12
+ function addClient(clientInfo) {
13
+ connectedClients.add(clientInfo)
14
+ }
15
+
16
+ /**
17
+ * Remove a client from the connected set
18
+ */
19
+ function removeClient(clientInfo) {
20
+ connectedClients.delete(clientInfo)
21
+ }
22
+
23
+ /**
24
+ * Broadcast to all connected clients
25
+ * @param {string} type - Message type
26
+ * @param {any} data - Data to send
27
+ * @param {string} [excludeHostId] - Optional hostId to exclude (e.g., sender)
28
+ */
29
+ function broadcast(type, data, excludeHostId) {
30
+ console.log(`📢 Broadcasting "${type}" to ${connectedClients.size} clients`, excludeHostId ? `(excluding ${excludeHostId})` : '')
31
+ connectedClients.forEach(client => {
32
+ if (excludeHostId && client.hostId === excludeHostId) {
33
+ return // Skip excluded client
34
+ }
35
+ try {
36
+ client.send(false, type, data, false)
37
+ } catch (e) {
38
+ console.error(`📢 Broadcast failed to ${client.hostId}:`, e.message)
39
+ }
40
+ })
41
+ }
42
+
43
+ /**
44
+ * Get count of online clients
45
+ */
46
+ function online() {
47
+ return connectedClients.size
48
+ }
49
+
50
+ /**
51
+ * Get all connected client hostIds
52
+ */
53
+ function getClients() {
54
+ return Array.from(connectedClients).map(c => c.hostId)
55
+ }
56
+
57
+ module.exports = {
58
+ addClient,
59
+ removeClient,
60
+ broadcast,
61
+ online,
62
+ getClients
63
+ }
@@ -0,0 +1,10 @@
1
+ const deeprequire = require('../utils/deepRequire')
2
+ const path = require('path')
3
+
4
+ // Use the current working directory (where node was started)
5
+ // This ensures 'where' folder is relative to the calling application
6
+ const currentDir = process.cwd()
7
+
8
+ module.exports = function (dirname, selector) {
9
+ return deeprequire(path.join(currentDir, dirname), selector)
10
+ }
@@ -0,0 +1,23 @@
1
+ const loader = require('./loader')
2
+ const wiring = require('./wiring')
3
+ const expressWs = require('express-ws');
4
+ const path = require('path');
5
+
6
+ let created = false
7
+
8
+ module.exports = function (app, { where, onConnent }) {
9
+
10
+ if (created) {
11
+ throw new Error("Api-Ape already started")
12
+ }
13
+ created = true;
14
+ expressWs(app)
15
+ const controllers = loader(where)
16
+
17
+ // Serve bundled client at /ape.js
18
+ app.get('/api/ape.js', (req, res) => {
19
+ res.sendFile(path.join(__dirname, '../../dist/ape.js'))
20
+ })
21
+
22
+ app.ws('/api/ape', wiring(controllers, onConnent))
23
+ }
@@ -0,0 +1,94 @@
1
+ const replySecurity = require('../security/reply')
2
+ const socketOpen = require('../socket/open')
3
+ const socketReceive = require('../socket/receive')
4
+ const socketSend = require('../socket/send')
5
+ const makeid = require('../utils/genId')
6
+ const UAParser = require('ua-parser-js');
7
+ const { addClient, removeClient } = require('./broadcast')
8
+
9
+ const parser = new UAParser();
10
+
11
+ // connent, beforeSend, beforeReceive, error, afterSend, afterReceive, disconnent
12
+
13
+
14
+ function defaultEvents(events = {}) {
15
+ const fallBackEvents = {
16
+ embed: {},
17
+ onReceive: () => { },
18
+ onSend: () => { },
19
+ onError: (errSt) => console.error(errSt),
20
+ onDisconnent: () => { },
21
+ } // END fallBackEvents
22
+ return Object.assign({}, fallBackEvents, events)
23
+ } // END defaultEvents
24
+
25
+ //=====================================================
26
+ //============================================== wiring
27
+ //=====================================================
28
+
29
+ module.exports = function wiring(controllers, onConnent) {
30
+ onConnent = onConnent || (() => { });
31
+ return function webSocketHandler(socket, req) {
32
+
33
+ let send;
34
+ let sentBufferAr = []
35
+ const sentBufferFn = (...args) => {
36
+ if (send) {
37
+ send(...args)
38
+ } else {
39
+ sentBufferAr.push(args)
40
+ }
41
+ } // END sentBufferFn
42
+
43
+ const hostId = makeid(20)
44
+ const agent = parser.setUA(req.headers['user-agent']).getResult()
45
+ const sharedValues = {
46
+ socket, req, agent, send: (type, data, err) => sentBufferFn(false, type, data, err)
47
+ }
48
+ sharedValues.send.toString = () => hostId
49
+
50
+
51
+ let result = onConnent(socket, req, sharedValues.send)
52
+ if (!result || !result.then) {
53
+ result = Promise.resolve(result)
54
+ }
55
+ result.then(defaultEvents)
56
+ .then(({ embed, onReceive, onSend, onError, onDisconnent }) => {
57
+ const isOk = socketOpen(socket, req, onError)
58
+
59
+ if (!isOk) {
60
+ return;
61
+ }
62
+
63
+
64
+ const checkReply = replySecurity()
65
+ const ape = {
66
+ socket,
67
+ req,
68
+ hostId,
69
+ checkReply,
70
+ events: { onReceive, onSend, onError, onDisconnent },
71
+ controllers,
72
+ sharedValues,
73
+ embedValues: embed
74
+ }// END ape
75
+ send = socketSend(ape)
76
+ ape.send = send
77
+
78
+ // Track this client for broadcast
79
+ const clientInfo = { hostId, send, embed }
80
+ addClient(clientInfo)
81
+
82
+ // Remove client on disconnect
83
+ socket.on('close', () => {
84
+ removeClient(clientInfo)
85
+ onDisconnent()
86
+ })
87
+
88
+ sentBufferAr.forEach(args => send(...args))
89
+ sentBufferAr = []
90
+ socket.on('message', socketReceive(ape))
91
+ }) // END result.then
92
+
93
+ } // END webSocketHandler
94
+ } // END wiring
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Extract root domain from a URL
3
+ * e.g., "https://sub.example.com:3000/path" -> "example.com"
4
+ */
5
+ module.exports = function extractRootDomain(url) {
6
+ if (!url) return ''
7
+ try {
8
+ // Handle full URLs
9
+ if (url.includes('://')) {
10
+ const hostname = new URL(url).hostname
11
+ const parts = hostname.split('.')
12
+ return parts.length > 2 ? parts.slice(-2).join('.') : hostname
13
+ }
14
+ // Handle hostname:port format
15
+ const hostname = url.split(':')[0]
16
+ const parts = hostname.split('.')
17
+ return parts.length > 2 ? parts.slice(-2).join('.') : hostname
18
+ } catch {
19
+ return url.split(':')[0]
20
+ }
21
+ }
@@ -0,0 +1,13 @@
1
+ const extractRootDomain = require('./extractRootDomain')
2
+
3
+ module.exports = function (socket, req, onError) {
4
+ onError = onError || console.error
5
+ const origin = extractRootDomain(req.header('Origin') || "");
6
+ const host = extractRootDomain(req.header('Host'));
7
+ if (origin && origin !== host) {
8
+ onError("REJECTING socket from " + req.header('Origin') + " miss-match with " + req.header('Host'))
9
+ socket.destroy()
10
+ return false
11
+ }
12
+ return true
13
+ }
@@ -0,0 +1,21 @@
1
+ module.exports = function(){
2
+ let requestCheck = []
3
+ return (queryId,createdAt)=>{
4
+ const startTime = Date.now();
5
+ if (createdAt > startTime) {
6
+ throw new Error("createdAt ahead of server by `${(createdAt - startTime) / 1000}secs. +${msg}`")
7
+ }
8
+ const tenSecAgo = startTime - 10000
9
+ if(createdAt < tenSecAgo) {
10
+ throw new Error("request is old by `${(startTime - createdAt) / 1000}secs. +${msg}`")
11
+ }
12
+
13
+ requestCheck = requestCheck.filter(([passQueryId,createdWhen])=>{
14
+ if (passQueryId === queryId) {
15
+ throw new Error(`Reply: ${queryId} ${msg}`)
16
+ }
17
+ return createdWhen > tenSecAgo
18
+ })
19
+ requestCheck.push([queryId,createdAt])
20
+ } // END checkReply
21
+ } // END replySecurity
@@ -0,0 +1,10 @@
1
+ const originSecurity = require( '../security/origin')
2
+
3
+ module.exports = function open(socket, req, onError){
4
+
5
+ const isSecure = originSecurity(socket, req, onError)
6
+ if ( ! isSecure) {
7
+ return false;
8
+ }
9
+ return true
10
+ }
@@ -0,0 +1,66 @@
1
+ const messageHash = require('../../utils/messageHash')
2
+ const { broadcast, online, getClients } = require('../lib/broadcast')
3
+ const jss = require('../../utils/jss')
4
+
5
+ module.exports = function receiveHandler(ape) {
6
+ const { send, checkReply, events, controllers, sharedValues, hostId, embedValues } = ape
7
+
8
+ // Build `this` context for controllers
9
+ // Includes: client metadata + api-ape utilities
10
+ const that = {
11
+ ...sharedValues,
12
+ ...embedValues,
13
+ // api-ape utilities available via `this`
14
+ broadcast: (type, data) => broadcast(type, data),
15
+ broadcastOthers: (type, data) => broadcast(type, data, hostId), // exclude self
16
+ online,
17
+ getClients,
18
+ hostId
19
+ }
20
+
21
+ return function onReceive(msg) {
22
+ // Convert Buffer to string - WebSocket messages may arrive as binary
23
+ const msgString = typeof msg === 'string' ? msg : msg.toString('utf8');
24
+ const queryId = messageHash(msgString);
25
+ try {
26
+ const { type: rawType, data, referer, createdAt, requestedAt } = jss.parse(msgString);
27
+
28
+ // Normalize type: strip leading slash, lowercase
29
+ const type = rawType.replace(/^\//, '').toLowerCase()
30
+
31
+ // Call onReceive hook - it should return a finish callback
32
+ const onFinish = events.onReceive(queryId, data, type) || (() => { })
33
+
34
+ const result = new Promise((resolve, reject) => {
35
+ try {
36
+ const controller = controllers[type]
37
+ if (!controller) {
38
+ throw `TypeError: "${type}" was not found`
39
+ }
40
+ checkReply(queryId, createdAt)
41
+ resolve(controller.call(that, data))
42
+ } catch (err) {
43
+ reject(err)
44
+ }
45
+ })
46
+ result.then(val => {
47
+ if (undefined !== val) {
48
+ send(queryId, false, val, false)
49
+ }
50
+ if (typeof onFinish === 'function') {
51
+ onFinish(false, val)
52
+ }
53
+ }).catch(err => {
54
+ send(queryId, false, false, err)
55
+ if (typeof onFinish === 'function') {
56
+ onFinish(err, true)
57
+ }
58
+ })
59
+
60
+ } catch (err) {
61
+ const errMessage = err.message || err
62
+ events.onError(hostId, queryId, errMessage)
63
+ } // END catch
64
+
65
+ } // END onReceive
66
+ } // END receiveHandler