botversion-sdk 1.0.3 → 1.0.5

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,119 @@
1
1
  // botversion-sdk/interceptor.js
2
2
  "use strict";
3
3
 
4
+ /**
5
+ * Makes an internal HTTP request to the user's own backend.
6
+ * Forwards cookies, auth headers, and CSRF tokens.
7
+ */
8
+ function makeInternalRequest(method, path, body, cookies, headers, baseUrl) {
9
+ return new Promise(function (resolve) {
10
+ var http = require("http");
11
+ var https = require("https");
12
+ var url = require("url");
13
+
14
+ baseUrl = baseUrl || "http://127.0.0.1:8000";
15
+ var fullUrl = baseUrl + path;
16
+ var parsedUrl = url.parse(fullUrl);
17
+ var isHttps = parsedUrl.protocol === "https:";
18
+ var lib = isHttps ? https : http;
19
+
20
+ var bodyStr = body ? JSON.stringify(body) : null;
21
+
22
+ var reqHeaders = {
23
+ "Content-Type": "application/json",
24
+ };
25
+
26
+ if (bodyStr) {
27
+ reqHeaders["Content-Length"] = String(Buffer.byteLength(bodyStr));
28
+ }
29
+
30
+ // Forward cookies — this is what identifies the user
31
+ if (cookies) reqHeaders["Cookie"] = cookies;
32
+
33
+ // Forward auth header
34
+ var authHeader =
35
+ headers && (headers["authorization"] || headers["Authorization"]);
36
+ if (authHeader) reqHeaders["Authorization"] = authHeader;
37
+
38
+ // Forward CSRF token
39
+ var csrf =
40
+ headers &&
41
+ (headers["x-csrftoken"] ||
42
+ headers["X-CSRFToken"] ||
43
+ headers["x-xsrf-token"] ||
44
+ headers["X-XSRF-TOKEN"]);
45
+ if (csrf) reqHeaders["X-CSRFToken"] = csrf;
46
+
47
+ var reqOptions = {
48
+ hostname: parsedUrl.hostname,
49
+ port: parsedUrl.port || (isHttps ? 443 : 80),
50
+ path: parsedUrl.path,
51
+ method: method.toUpperCase(),
52
+ headers: reqHeaders,
53
+ timeout: 30000,
54
+ };
55
+
56
+ var req = lib.request(reqOptions, function (res) {
57
+ var data = "";
58
+ res.on("data", function (chunk) {
59
+ data += chunk;
60
+ });
61
+ res.on("end", function () {
62
+ var parsed;
63
+ try {
64
+ parsed = JSON.parse(data);
65
+ } catch (e) {
66
+ parsed = { raw: data };
67
+ }
68
+
69
+ // Handle redirects
70
+ if (
71
+ res.statusCode >= 300 &&
72
+ res.statusCode < 400 &&
73
+ res.headers.location
74
+ ) {
75
+ makeInternalRequest(
76
+ res.statusCode === 303 ? "GET" : method,
77
+ res.headers.location,
78
+ res.statusCode === 303 ? null : body,
79
+ cookies,
80
+ headers,
81
+ baseUrl,
82
+ ).then(resolve);
83
+ return;
84
+ }
85
+
86
+ resolve({
87
+ status: res.statusCode,
88
+ ok: res.statusCode >= 200 && res.statusCode < 300,
89
+ data: parsed,
90
+ });
91
+ });
92
+ });
93
+
94
+ req.on("error", function (err) {
95
+ resolve({ status: 500, ok: false, data: { error: err.message } });
96
+ });
97
+
98
+ req.on("timeout", function () {
99
+ req.destroy();
100
+ resolve({ status: 500, ok: false, data: { error: "Request timed out" } });
101
+ });
102
+
103
+ if (bodyStr) req.write(bodyStr);
104
+ req.end();
105
+ });
106
+ }
107
+
4
108
  /**
5
109
  * Attaches a middleware to the Express app that
6
110
  * silently intercepts every request and reports
7
- * new endpoints to BotVersion platform
111
+ * new endpoints to BotVersion platform.
112
+ * Auth and user context are handled client-side — not here.
8
113
  */
9
114
  function attachInterceptor(app, client, options) {
10
115
  options = options || {};
11
- var getUserContext = options.getUserContext || null;
12
116
 
13
- // Paths to always ignore
14
117
  const ignorePaths = [
15
118
  "/health",
16
119
  "/favicon.ico",
@@ -20,11 +123,9 @@ function attachInterceptor(app, client, options) {
20
123
  ].concat(options.exclude || []);
21
124
 
22
125
  // Track which endpoints we have already reported
23
- // so we don't spam the platform on every request
24
126
  const reportedEndpoints = new Set();
25
127
 
26
128
  app.use(function botVersionInterceptor(req, res, next) {
27
- // Skip ignored paths
28
129
  const path = req.path || req.url || "";
29
130
  const shouldIgnore = ignorePaths.some(function (p) {
30
131
  return path.startsWith(p);
@@ -34,21 +135,15 @@ function attachInterceptor(app, client, options) {
34
135
  return next();
35
136
  }
36
137
 
37
- // Skip non-API paths if configured
38
138
  if (options.apiPrefix && !path.startsWith(options.apiPrefix)) {
39
139
  return next();
40
140
  }
41
141
 
42
142
  const method = req.method.toUpperCase();
43
-
44
- // Normalize path — replace IDs with :param
45
143
  const normalizedPath = normalizePath(path);
46
144
  const endpointKey = method + ":" + normalizedPath;
47
145
 
48
- // Only report if we haven't seen this endpoint before
49
146
  const bodyStructure = buildBodyStructure(req.body);
50
- console.log("[DEBUG] endpoint:", endpointKey);
51
- console.log("[DEBUG] bodyStructure:", JSON.stringify(bodyStructure));
52
147
  const bodyKey =
53
148
  endpointKey +
54
149
  ":" +
@@ -59,7 +154,6 @@ function attachInterceptor(app, client, options) {
59
154
  if (!reportedEndpoints.has(bodyKey)) {
60
155
  reportedEndpoints.add(bodyKey);
61
156
 
62
- // Convert to JSON Schema format so Gemini can read field names
63
157
  const jsonSchema = bodyStructure
64
158
  ? {
65
159
  type: "object",
@@ -78,9 +172,8 @@ function attachInterceptor(app, client, options) {
78
172
  ),
79
173
  }
80
174
  : null;
81
- console.log("[DEBUG] jsonSchema:", JSON.stringify(jsonSchema));
82
175
 
83
- // Report async — don't block the request
176
+ // Report async — never block the request
84
177
  setImmediate(function () {
85
178
  client
86
179
  .updateEndpoint({
@@ -90,7 +183,6 @@ function attachInterceptor(app, client, options) {
90
183
  detectedBy: "runtime",
91
184
  })
92
185
  .catch(function (err) {
93
- // Silent fail — never crash customer's app
94
186
  if (options.debug) {
95
187
  console.warn(
96
188
  "[BotVersion SDK] Failed to report endpoint:",
@@ -103,52 +195,10 @@ function attachInterceptor(app, client, options) {
103
195
 
104
196
  next();
105
197
  });
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
- };
198
+ // Register executor so WebSocket can forward calls to user's backend
199
+ client.setExecutor(function (method, path, body, cookies, headers, baseUrl) {
200
+ return makeInternalRequest(method, path, body, cookies, headers, baseUrl);
201
+ });
152
202
  }
153
203
 
154
204
  /**
@@ -172,7 +222,7 @@ function normalizePath(path) {
172
222
  // Numeric ID
173
223
  if (/^\d+$/.test(segment)) return ":id";
174
224
 
175
- // cuid pattern (your system uses these)
225
+ // cuid pattern
176
226
  if (/^c[a-z0-9]{20,}$/i.test(segment)) return ":id";
177
227
 
178
228
  // MongoDB ObjectId
@@ -201,7 +251,6 @@ function buildBodyStructure(body) {
201
251
  const structure = {};
202
252
 
203
253
  Object.keys(body).forEach(function (key) {
204
- // Never store sensitive values
205
254
  const sensitiveKeys = [
206
255
  "password",
207
256
  "token",
@@ -242,64 +291,3 @@ module.exports = {
242
291
  normalizePath,
243
292
  buildBodyStructure,
244
293
  };
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
- }
257
-
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
- };
281
-
282
- if (body) options.headers["Content-Length"] = Buffer.byteLength(body);
283
-
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 } });
294
- }
295
- });
296
- });
297
-
298
- apiReq.on("error", function (err) {
299
- resolve({ status: 500, error: err.message });
300
- });
301
-
302
- if (body) apiReq.write(body);
303
- apiReq.end();
304
- });
305
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botversion-sdk",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "BotVersion SDK — auto-detect and register your API endpoints",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "author": "Saurav Dhakal",
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
- "express": "^4.18.2"
19
+ "express": "^4.18.2",
20
+ "ws": "^8.20.0"
20
21
  }
21
22
  }