botversion-sdk 1.0.4 → 1.0.6

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.
package/interceptor.js CHANGED
@@ -1,16 +1,17 @@
1
1
  // botversion-sdk/interceptor.js
2
2
  "use strict";
3
3
 
4
+ const reportedEndpoints = new Set();
5
+
4
6
  /**
5
7
  * Attaches a middleware to the Express app that
6
8
  * silently intercepts every request and reports
7
- * new endpoints to BotVersion platform
9
+ * new endpoints to BotVersion platform.
10
+ * Auth and user context are handled client-side — not here.
8
11
  */
9
12
  function attachInterceptor(app, client, options) {
10
13
  options = options || {};
11
- var getUserContext = options.getUserContext || null;
12
14
 
13
- // Paths to always ignore
14
15
  const ignorePaths = [
15
16
  "/health",
16
17
  "/favicon.ico",
@@ -19,12 +20,7 @@ function attachInterceptor(app, client, options) {
19
20
  "/public",
20
21
  ].concat(options.exclude || []);
21
22
 
22
- // Track which endpoints we have already reported
23
- // so we don't spam the platform on every request
24
- const reportedEndpoints = new Set();
25
-
26
23
  app.use(function botVersionInterceptor(req, res, next) {
27
- // Skip ignored paths
28
24
  const path = req.path || req.url || "";
29
25
  const shouldIgnore = ignorePaths.some(function (p) {
30
26
  return path.startsWith(p);
@@ -34,21 +30,15 @@ function attachInterceptor(app, client, options) {
34
30
  return next();
35
31
  }
36
32
 
37
- // Skip non-API paths if configured
38
33
  if (options.apiPrefix && !path.startsWith(options.apiPrefix)) {
39
34
  return next();
40
35
  }
41
36
 
42
37
  const method = req.method.toUpperCase();
43
-
44
- // Normalize path — replace IDs with :param
45
38
  const normalizedPath = normalizePath(path);
46
39
  const endpointKey = method + ":" + normalizedPath;
47
40
 
48
- // Only report if we haven't seen this endpoint before
49
41
  const bodyStructure = buildBodyStructure(req.body);
50
- console.log("[DEBUG] endpoint:", endpointKey);
51
- console.log("[DEBUG] bodyStructure:", JSON.stringify(bodyStructure));
52
42
  const bodyKey =
53
43
  endpointKey +
54
44
  ":" +
@@ -59,7 +49,6 @@ function attachInterceptor(app, client, options) {
59
49
  if (!reportedEndpoints.has(bodyKey)) {
60
50
  reportedEndpoints.add(bodyKey);
61
51
 
62
- // Convert to JSON Schema format so Gemini can read field names
63
52
  const jsonSchema = bodyStructure
64
53
  ? {
65
54
  type: "object",
@@ -78,9 +67,8 @@ function attachInterceptor(app, client, options) {
78
67
  ),
79
68
  }
80
69
  : null;
81
- console.log("[DEBUG] jsonSchema:", JSON.stringify(jsonSchema));
82
70
 
83
- // Report async — don't block the request
71
+ // Report async — never block the request
84
72
  setImmediate(function () {
85
73
  client
86
74
  .updateEndpoint({
@@ -90,65 +78,13 @@ function attachInterceptor(app, client, options) {
90
78
  detectedBy: "runtime",
91
79
  })
92
80
  .catch(function (err) {
93
- // Silent fail — never crash customer's app
94
- if (options.debug) {
95
- console.warn(
96
- "[BotVersion SDK] Failed to report endpoint:",
97
- err.message,
98
- );
99
- }
81
+ console.error("[botversion] update_endpoint failed:", err.message);
100
82
  });
101
83
  });
102
84
  }
103
85
 
104
86
  next();
105
87
  });
106
- // Attached to app so SaaS owner can call it in their agent route
107
- app.executeAgentCall = function (req, call) {
108
- var userContext = getUserContext
109
- ? getUserContext(req)
110
- : extractDefaultContext(req);
111
-
112
- return client
113
- .agentChat({
114
- message: req.body.message,
115
- conversationHistory: req.body.conversationHistory || [],
116
- pageContext: req.body.pageContext || {},
117
- userContext: userContext,
118
- })
119
- .then(function (response) {
120
- // Handle plain chat/greeting responses from ai-response.js
121
- if (response.answer) {
122
- return { action: "RESPOND", message: response.answer };
123
- }
124
-
125
- // Handle agent action
126
- if (response.action !== "EXECUTE_CALL") return response;
127
-
128
- return makeLocalCall(req, response.call).then(function (result) {
129
- return client
130
- .agentToolResult(
131
- response.sessionToken,
132
- result,
133
- response.sessionData,
134
- )
135
- .then(function (toolResponse) {
136
- if (toolResponse.action === "EXECUTE_CALL") {
137
- return makeLocalCall(req, toolResponse.call).then(
138
- function (result2) {
139
- return client.agentToolResult(
140
- toolResponse.sessionToken,
141
- result2,
142
- toolResponse.sessionData,
143
- );
144
- },
145
- );
146
- }
147
- return toolResponse;
148
- });
149
- });
150
- });
151
- };
152
88
  }
153
89
 
154
90
  /**
@@ -172,7 +108,7 @@ function normalizePath(path) {
172
108
  // Numeric ID
173
109
  if (/^\d+$/.test(segment)) return ":id";
174
110
 
175
- // cuid pattern (your system uses these)
111
+ // cuid pattern
176
112
  if (/^c[a-z0-9]{20,}$/i.test(segment)) return ":id";
177
113
 
178
114
  // MongoDB ObjectId
@@ -201,7 +137,6 @@ function buildBodyStructure(body) {
201
137
  const structure = {};
202
138
 
203
139
  Object.keys(body).forEach(function (key) {
204
- // Never store sensitive values
205
140
  const sensitiveKeys = [
206
141
  "password",
207
142
  "token",
@@ -237,69 +172,59 @@ function buildBodyStructure(body) {
237
172
  return structure;
238
173
  }
239
174
 
240
- module.exports = {
241
- attachInterceptor,
242
- normalizePath,
243
- buildBodyStructure,
244
- };
245
-
246
- // Default userContext extraction — reads common patterns
247
- // SaaS owner can override this with getUserContext option
248
- function extractDefaultContext(req) {
249
- var user = req.user || (req.session && req.session.user) || {};
250
- return {
251
- userId: user.id || user.userId || user._id || null,
252
- email: user.email || null,
253
- role: user.role || user.userRole || null,
254
- name: user.name || user.username || null,
255
- };
256
- }
175
+ function attachNextJsInterceptor(client, options) {
176
+ try {
177
+ const http = require("http");
178
+ const originalEmit = http.Server.prototype.emit;
257
179
 
258
- // Makes the HTTP call locally on the SaaS owner's server
259
- // forwarding the user's real auth headers
260
- function makeLocalCall(req, call) {
261
- return new Promise(function (resolve) {
262
- var http = require("http");
263
- var https = require("https");
264
-
265
- var body = call.body ? JSON.stringify(call.body) : null;
266
- var isHttps = req.protocol === "https" || req.secure;
267
- var lib = isHttps ? https : http;
268
-
269
- var options = {
270
- hostname: "127.0.0.1",
271
- port: process.env.PORT || 3000,
272
- path: call.path,
273
- method: call.method,
274
- headers: {
275
- "Content-Type": "application/json",
276
- // Forward the user's real auth token — this is the key part
277
- Authorization: req.headers["authorization"] || "",
278
- Cookie: req.headers["cookie"] || "",
279
- },
280
- };
180
+ http.Server.prototype.emit = function (event, req, res) {
181
+ if (event === "request") {
182
+ const path = req.url ? req.url.split("?")[0] : "";
183
+ const method = req.method ? req.method.toUpperCase() : "";
281
184
 
282
- if (body) options.headers["Content-Length"] = Buffer.byteLength(body);
185
+ const shouldIgnore = (options.exclude || [])
186
+ .concat(["/health", "/favicon.ico", "/_next", "/static", "/public"])
187
+ .some(function (p) {
188
+ return path.startsWith(p);
189
+ });
283
190
 
284
- var apiReq = lib.request(options, function (apiRes) {
285
- var data = "";
286
- apiRes.on("data", function (chunk) {
287
- data += chunk;
288
- });
289
- apiRes.on("end", function () {
290
- try {
291
- resolve({ status: apiRes.statusCode, data: JSON.parse(data) });
292
- } catch {
293
- resolve({ status: apiRes.statusCode, data: { raw: data } });
191
+ const isApiPath = path.startsWith(options.apiPrefix || "/api");
192
+
193
+ if (!shouldIgnore && isApiPath) {
194
+ const normalizedPath = normalizePath(path);
195
+ const endpointKey = method + ":" + normalizedPath;
196
+
197
+ if (!reportedEndpoints.has(endpointKey)) {
198
+ reportedEndpoints.add(endpointKey);
199
+
200
+ setImmediate(function () {
201
+ client
202
+ .updateEndpoint({
203
+ method: method,
204
+ path: normalizedPath,
205
+ detectedBy: "runtime",
206
+ })
207
+ .catch(function (err) {
208
+ console.error("[botversion] update failed:", err.message);
209
+ });
210
+ });
211
+ }
294
212
  }
295
- });
296
- });
297
-
298
- apiReq.on("error", function (err) {
299
- resolve({ status: 500, error: err.message });
300
- });
213
+ }
301
214
 
302
- if (body) apiReq.write(body);
303
- apiReq.end();
304
- });
215
+ return originalEmit.apply(this, arguments);
216
+ };
217
+ } catch (err) {
218
+ console.error(
219
+ "[botversion] failed to attach Next.js interceptor:",
220
+ err.message,
221
+ );
222
+ }
305
223
  }
224
+
225
+ module.exports = {
226
+ attachInterceptor,
227
+ attachNextJsInterceptor,
228
+ normalizePath,
229
+ buildBodyStructure,
230
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botversion-sdk",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "BotVersion SDK — auto-detect and register your API endpoints",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -16,6 +16,8 @@
16
16
  "author": "Saurav Dhakal",
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
+ "@babel/parser": "^7.29.3",
20
+ "@babel/traverse": "^7.29.0",
19
21
  "express": "^4.18.2"
20
22
  }
21
23
  }