botversion-sdk 1.0.6 → 1.1.7

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/client.js CHANGED
@@ -80,9 +80,7 @@ BotVersionClient.prototype._flush = function () {
80
80
  endpoints: toSend,
81
81
  })
82
82
  .then(function (data) {})
83
- .catch(function (err) {
84
- console.error("[botversion] flush failed:", err.message);
85
- });
83
+ .catch(function (err) {});
86
84
  };
87
85
 
88
86
  /**
@@ -156,6 +154,19 @@ BotVersionClient.prototype._post = function (path, data) {
156
154
 
157
155
  res.on("end", function () {
158
156
  try {
157
+ if (!responseData || responseData.trim() === "") {
158
+ if (res.statusCode >= 200 && res.statusCode < 300) {
159
+ resolve({});
160
+ return;
161
+ } else {
162
+ reject(
163
+ new Error(
164
+ "Platform returned " + res.statusCode + ": empty response",
165
+ ),
166
+ );
167
+ return;
168
+ }
169
+ }
159
170
  var parsed = JSON.parse(responseData);
160
171
  if (res.statusCode >= 200 && res.statusCode < 300) {
161
172
  resolve(parsed);
@@ -220,6 +231,19 @@ BotVersionClient.prototype._get = function (path) {
220
231
 
221
232
  res.on("end", function () {
222
233
  try {
234
+ if (!responseData || responseData.trim() === "") {
235
+ if (res.statusCode >= 200 && res.statusCode < 300) {
236
+ resolve({});
237
+ return;
238
+ } else {
239
+ reject(
240
+ new Error(
241
+ "Platform returned " + res.statusCode + ": empty response",
242
+ ),
243
+ );
244
+ return;
245
+ }
246
+ }
223
247
  var parsed = JSON.parse(responseData);
224
248
  if (res.statusCode >= 200 && res.statusCode < 300) {
225
249
  resolve(parsed);
@@ -234,7 +258,11 @@ BotVersionClient.prototype._get = function (path) {
234
258
  );
235
259
  }
236
260
  } catch (e) {
237
- reject(new Error("Invalid JSON response from platform"));
261
+ if (res.statusCode >= 200 && res.statusCode < 300) {
262
+ resolve({});
263
+ } else {
264
+ reject(new Error("Invalid JSON response from platform"));
265
+ }
238
266
  }
239
267
  });
240
268
  });
package/index.js CHANGED
@@ -1,10 +1,266 @@
1
- // botversion-sdk/index.js
2
1
  "use strict";
3
2
 
4
3
  var scanner = require("./scanner");
5
4
  var interceptor = require("./interceptor");
6
5
  var BotVersionClient = require("./client");
7
6
 
7
+ // ── Framework auto-detection ─────────────────────────────────────────────────
8
+ // Reads package.json from the customer's codebase and figures out
9
+ // which backend and frontend frameworks they are using.
10
+ function detectFrameworks(cwd) {
11
+ const fs = require("fs");
12
+ const path = require("path");
13
+
14
+ const result = {
15
+ backend: "unknown",
16
+ frontend: "unknown",
17
+ };
18
+
19
+ let pkg = {};
20
+ try {
21
+ const pkgPath = path.join(cwd, "package.json");
22
+ if (fs.existsSync(pkgPath)) {
23
+ pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
24
+ }
25
+ } catch {
26
+ return result;
27
+ }
28
+
29
+ const deps = Object.assign(
30
+ {},
31
+ pkg.dependencies || {},
32
+ pkg.devDependencies || {},
33
+ );
34
+
35
+ // ── Backend detection ──────────────────────────────────────────────────────
36
+ if (deps["@nestjs/core"]) {
37
+ result.backend = "nestjs";
38
+ } else if (deps["fastify"]) {
39
+ result.backend = "fastify";
40
+ } else if (deps["@adonisjs/core"]) {
41
+ result.backend = "adonis";
42
+ } else if (deps["@hapi/hapi"] || deps["hapi"]) {
43
+ result.backend = "hapi";
44
+ } else if (deps["koa"]) {
45
+ result.backend = "koa";
46
+ } else if (deps["next"]) {
47
+ result.backend = "nextjs";
48
+ } else if (deps["express"]) {
49
+ result.backend = "express";
50
+ } else if (deps["@remix-run/node"] || deps["@remix-run/serve"]) {
51
+ result.backend = "remix";
52
+ } else if (deps["nuxt"] || deps["nuxt3"] || deps["@nuxt/core"]) {
53
+ result.backend = "nuxt";
54
+ } else if (deps["@sveltejs/kit"]) {
55
+ result.backend = "sveltekit";
56
+ }
57
+
58
+ // ── Frontend detection ─────────────────────────────────────────────────────
59
+ if (deps["next"]) {
60
+ result.frontend = "nextjs";
61
+ } else if (deps["nuxt"] || deps["nuxt3"] || deps["@nuxt/core"]) {
62
+ result.frontend = "nuxt";
63
+ } else if (deps["@sveltejs/kit"]) {
64
+ result.frontend = "sveltekit";
65
+ } else if (deps["@remix-run/react"]) {
66
+ result.frontend = "remix";
67
+ } else if (deps["@tanstack/router"] || deps["@tanstack/react-router"]) {
68
+ result.frontend = "tanstack";
69
+ } else if (deps["vue"] && (deps["vue-router"] || deps["@nuxtjs/router"])) {
70
+ result.frontend = "vue";
71
+ } else if (deps["@angular/core"]) {
72
+ result.frontend = "angular";
73
+ } else if (deps["svelte"]) {
74
+ result.frontend = "svelte";
75
+ } else if (deps["react"] && deps["react-router-dom"]) {
76
+ result.frontend = "react-router";
77
+ } else if (deps["astro"]) {
78
+ result.frontend = "astro";
79
+ } else if (deps["react"]) {
80
+ result.frontend = "react";
81
+ }
82
+
83
+ return result;
84
+ }
85
+
86
+ // ── Run the right backend scanner ────────────────────────────────────────────
87
+ function runBackendScanner(detectedBackend, app, options, cwd) {
88
+ const endpoints = [];
89
+
90
+ switch (detectedBackend) {
91
+ case "express":
92
+ endpoints.push(...scanner.scanExpressRoutes(app, cwd));
93
+ break;
94
+
95
+ case "nextjs": {
96
+ const fs = require("fs");
97
+ const path = require("path");
98
+
99
+ const possibleAppDirs = [
100
+ path.join(cwd, "app"),
101
+ path.join(cwd, "src", "app"),
102
+ ];
103
+ const possiblePagesDirs = [
104
+ path.join(cwd, "pages"),
105
+ path.join(cwd, "src", "pages"),
106
+ ];
107
+
108
+ for (const dir of possibleAppDirs) {
109
+ if (fs.existsSync(dir)) {
110
+ endpoints.push(...scanner.scanNextJsAppRoutes(dir));
111
+ }
112
+ }
113
+ for (const dir of possiblePagesDirs) {
114
+ if (fs.existsSync(dir)) {
115
+ endpoints.push(...scanner.scanNextJsRoutes(dir));
116
+ }
117
+ }
118
+ break;
119
+ }
120
+
121
+ case "fastify":
122
+ endpoints.push(...scanner.scanFastifyRoutes(cwd));
123
+ break;
124
+
125
+ case "nestjs":
126
+ endpoints.push(...scanner.scanNestJsRoutes(cwd));
127
+ break;
128
+
129
+ case "koa":
130
+ endpoints.push(...scanner.scanKoaRoutes(cwd));
131
+ break;
132
+
133
+ case "hapi":
134
+ endpoints.push(...scanner.scanHapiRoutes(cwd));
135
+ break;
136
+
137
+ case "adonis":
138
+ endpoints.push(...scanner.scanAdonisRoutes(cwd));
139
+ break;
140
+
141
+ case "nuxt":
142
+ endpoints.push(...scanner.scanNuxtServerRoutes(cwd));
143
+ break;
144
+
145
+ case "sveltekit":
146
+ endpoints.push(...scanner.scanSvelteKitServerRoutes(cwd));
147
+ break;
148
+
149
+ case "remix":
150
+ endpoints.push(...scanner.scanRemixServerRoutes(cwd));
151
+ break;
152
+
153
+ default:
154
+ // Unknown backend — try Express as best guess
155
+ if (app) {
156
+ endpoints.push(...scanner.scanExpressRoutes(app, cwd));
157
+ }
158
+ break;
159
+ }
160
+
161
+ return endpoints;
162
+ }
163
+
164
+ // ── Attach the right runtime interceptor ─────────────────────────────────────
165
+ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
166
+ switch (detectedBackend) {
167
+ case "express":
168
+ interceptor.attachInterceptor(app, client, {
169
+ exclude: options.exclude || [],
170
+ apiPrefix: options.apiPrefix || null,
171
+ debug: options.debug || false,
172
+ });
173
+ break;
174
+
175
+ case "nextjs":
176
+ interceptor.attachNextJsInterceptor(client, {
177
+ exclude: options.exclude || [],
178
+ apiPrefix: options.apiPrefix || "/api",
179
+ debug: options.debug || false,
180
+ });
181
+ break;
182
+
183
+ case "fastify":
184
+ if (app) {
185
+ interceptor.attachFastifyInterceptor(app, client, {
186
+ exclude: options.exclude || [],
187
+ apiPrefix: options.apiPrefix || null,
188
+ debug: options.debug || false,
189
+ });
190
+ }
191
+ break;
192
+
193
+ case "koa":
194
+ if (app) {
195
+ interceptor.attachKoaInterceptor(app, client, {
196
+ exclude: options.exclude || [],
197
+ apiPrefix: options.apiPrefix || null,
198
+ debug: options.debug || false,
199
+ });
200
+ }
201
+ break;
202
+
203
+ case "hapi":
204
+ if (app) {
205
+ interceptor.attachHapiInterceptor(app, client, {
206
+ exclude: options.exclude || [],
207
+ apiPrefix: options.apiPrefix || null,
208
+ debug: options.debug || false,
209
+ });
210
+ }
211
+ break;
212
+
213
+ case "nestjs":
214
+ interceptor.attachNestJsInterceptor(client, {
215
+ exclude: options.exclude || [],
216
+ apiPrefix: options.apiPrefix || "/",
217
+ debug: options.debug || false,
218
+ });
219
+ break;
220
+
221
+ case "sveltekit":
222
+ interceptor.attachSvelteKitInterceptor(client, {
223
+ exclude: options.exclude || [],
224
+ apiPrefix: options.apiPrefix || "/",
225
+ debug: options.debug || false,
226
+ });
227
+ break;
228
+
229
+ case "nuxt":
230
+ interceptor.attachNuxtInterceptor(client, {
231
+ exclude: options.exclude || [],
232
+ apiPrefix: options.apiPrefix || "/api",
233
+ debug: options.debug || false,
234
+ });
235
+ break;
236
+
237
+ case "remix":
238
+ interceptor.attachRemixInterceptor(client, {
239
+ exclude: options.exclude || [],
240
+ apiPrefix: options.apiPrefix || "/",
241
+ debug: options.debug || false,
242
+ });
243
+ break;
244
+
245
+ case "adonis":
246
+ interceptor.attachAdonisInterceptor(client, {
247
+ exclude: options.exclude || [],
248
+ apiPrefix: options.apiPrefix || "/",
249
+ debug: options.debug || false,
250
+ });
251
+ break;
252
+
253
+ default:
254
+ // Unknown — fall back to Next.js http server patch as best guess
255
+ interceptor.attachNextJsInterceptor(client, {
256
+ exclude: options.exclude || [],
257
+ apiPrefix: options.apiPrefix || "/",
258
+ debug: options.debug || false,
259
+ });
260
+ break;
261
+ }
262
+ }
263
+
8
264
  var BotVersion = {
9
265
  _client: null,
10
266
  _initialized: false,
@@ -31,12 +287,9 @@ var BotVersion = {
31
287
  this._client = global._botVersionClient;
32
288
  this._options = global._botVersionOptions;
33
289
  this._initialized = true;
34
- // Re-attach interceptor after hot reload
35
- interceptor.attachNextJsInterceptor(this._client, {
36
- exclude: (global._botVersionOptions || {}).exclude || [],
37
- apiPrefix: (global._botVersionOptions || {}).apiPrefix || "/api",
38
- debug: (global._botVersionOptions || {}).debug || false,
39
- });
290
+ const savedOptions = global._botVersionOptions || {};
291
+ const savedFramework = global._botVersionFramework || "nextjs";
292
+ attachRuntimeInterceptor(savedFramework, app, this._client, savedOptions);
40
293
  return;
41
294
  }
42
295
 
@@ -55,80 +308,44 @@ var BotVersion = {
55
308
  global._botVersionOptions = options;
56
309
 
57
310
  var self = this;
311
+ var cwd = options.cwd || process.cwd();
58
312
  var debug = options.debug || false;
59
313
 
60
- // ── Runtime interceptor — Express or Next.js ─────────────────────────────
61
- if (app && app.use) {
62
- // Express
63
- interceptor.attachInterceptor(app, self._client, {
64
- exclude: options.exclude || [],
65
- apiPrefix: options.apiPrefix || null,
66
- debug: debug,
67
- });
68
- } else {
69
- // Next.js — patch the global fetch/http server to intercept API calls
70
- interceptor.attachNextJsInterceptor(self._client, {
71
- exclude: options.exclude || [],
72
- apiPrefix: options.apiPrefix || "/api",
73
- debug: debug,
74
- });
75
- }
314
+ // ── Auto-detect frameworks ───────────────────────────────────────────────
315
+ var detected = detectFrameworks(cwd);
76
316
 
77
- // ── Static scan ──────────────────────────────────────────────────────────
78
- setTimeout(function () {
79
- var endpoints = [];
317
+ // Allow manual override from options
318
+ var detectedBackend = options.framework || detected.backend;
319
+ var detectedFrontend = options.frontendFramework || detected.frontend;
80
320
 
81
- // Express scan
82
- if (app) {
83
- var cwd = options.cwd || process.cwd();
84
- endpoints = scanner.scanExpressRoutes(app, cwd);
85
- }
321
+ global._botVersionFramework = detectedBackend;
86
322
 
87
- // Next.js scan
88
- if (!app) {
89
- const fs = require("fs");
90
- const path = require("path");
91
- const cwd = process.cwd();
92
-
93
- const possibleAppDirs = [
94
- path.join(cwd, "app"),
95
- path.join(cwd, "src", "app"),
96
- ];
97
-
98
- const possiblePagesDirs = [
99
- path.join(cwd, "pages"),
100
- path.join(cwd, "src", "pages"),
101
- ];
102
-
103
- for (const dir of possibleAppDirs) {
104
- if (fs.existsSync(dir)) {
105
- const routes = scanner.scanNextJsAppRoutes(dir);
106
- endpoints = endpoints.concat(routes);
107
- }
108
- }
323
+ if (debug) {
324
+ // Detected: detectedBackend, detectedFrontend
325
+ }
109
326
 
110
- for (const dir of possiblePagesDirs) {
111
- if (fs.existsSync(dir)) {
112
- const routes = scanner.scanNextJsRoutes(dir);
113
- endpoints = endpoints.concat(routes);
114
- }
115
- }
116
- }
327
+ // ── Attach runtime interceptor ───────────────────────────────────────────
328
+ attachRuntimeInterceptor(detectedBackend, app, self._client, options);
117
329
 
118
- // Send to platform
330
+ // ── Static scan ──────────────────────────────────────────────────────────
331
+ setTimeout(function () {
332
+ // Run backend scanner
333
+ var endpoints = runBackendScanner(detectedBackend, app, options, cwd);
334
+
335
+ // Send endpoints to platform
119
336
  if (endpoints.length > 0) {
120
337
  self._client.registerEndpoints(endpoints);
121
338
  }
122
339
 
123
- var routePatterns = scanner.scanFrontendRoutes(
124
- options.cwd || process.cwd(),
125
- );
340
+ // Run frontend route scanner
341
+ var routePatterns = scanner.scanFrontendRoutes(cwd);
342
+
126
343
  if (routePatterns.length > 0) {
127
- self._client
128
- .registerRoutePatterns(routePatterns)
129
- .catch(function (err) {});
344
+ self._client.registerRoutePatterns(routePatterns).catch(function () {
345
+ // silently ignore — non-critical
346
+ });
130
347
  }
131
- }, 500);
348
+ }, 3000);
132
349
  },
133
350
 
134
351
  getEndpoints: function () {