botversion-sdk 1.1.7 → 2.0.0
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/bin/scan-frontend.js +49 -0
- package/client.js +12 -6
- package/index.js +150 -23
- package/interceptor.js +160 -2
- package/package.json +2 -2
- package/scanner.js +7 -106
- package/bin/init.js +0 -605
- package/cli/detector.js +0 -1149
- package/cli/generator.js +0 -187
- package/cli/prompts.js +0 -163
- package/cli/writer.js +0 -511
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var scanner = require("../scanner");
|
|
5
|
+
var BotVersionClient = require("../client");
|
|
6
|
+
var sdk = require("../index");
|
|
7
|
+
|
|
8
|
+
var apiKey = process.env.BOTVERSION_API_KEY;
|
|
9
|
+
var platformUrl =
|
|
10
|
+
process.env.BOTVERSION_PLATFORM_URL || "https://botversion.com";
|
|
11
|
+
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
console.error(
|
|
14
|
+
"[BotVersion] BOTVERSION_API_KEY environment variable is not set. Skipping frontend route scan.",
|
|
15
|
+
);
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
var cwd = process.cwd();
|
|
20
|
+
var client = new BotVersionClient({ apiKey: apiKey, platformUrl: platformUrl });
|
|
21
|
+
|
|
22
|
+
var detected = sdk.detectFrameworks(cwd);
|
|
23
|
+
var projectType = sdk.classifyInstallation(detected.backend, detected.frontend);
|
|
24
|
+
var routePatterns = scanner.scanFrontendRoutes(cwd);
|
|
25
|
+
|
|
26
|
+
client
|
|
27
|
+
.registerRoutePatterns(routePatterns, {
|
|
28
|
+
source: "cli",
|
|
29
|
+
projectType: projectType,
|
|
30
|
+
detectedBackend: detected.backend,
|
|
31
|
+
detectedFrontend: detected.frontend,
|
|
32
|
+
sdkLanguage: "javascript",
|
|
33
|
+
})
|
|
34
|
+
.then(function () {
|
|
35
|
+
console.log(
|
|
36
|
+
"[BotVersion] Reported " +
|
|
37
|
+
routePatterns.length +
|
|
38
|
+
" frontend routes. projectType=" +
|
|
39
|
+
projectType,
|
|
40
|
+
);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
})
|
|
43
|
+
.catch(function (err) {
|
|
44
|
+
console.error(
|
|
45
|
+
"[BotVersion] Failed to report frontend routes:",
|
|
46
|
+
err.message,
|
|
47
|
+
);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
});
|
package/client.js
CHANGED
|
@@ -99,14 +99,20 @@ BotVersionClient.prototype.updateEndpoint = function (endpoint) {
|
|
|
99
99
|
});
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
-
BotVersionClient.prototype.registerRoutePatterns = function (
|
|
102
|
+
BotVersionClient.prototype.registerRoutePatterns = function (
|
|
103
|
+
patterns,
|
|
104
|
+
classification,
|
|
105
|
+
) {
|
|
103
106
|
var self = this;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return self._post("/api/sdk/register-route-patterns", {
|
|
107
|
+
var body = {
|
|
107
108
|
workspaceKey: self.apiKey,
|
|
108
|
-
patterns: patterns,
|
|
109
|
-
|
|
109
|
+
patterns: patterns || [],
|
|
110
|
+
classification: classification || null,
|
|
111
|
+
};
|
|
112
|
+
if ((!patterns || patterns.length === 0) && !classification) {
|
|
113
|
+
return Promise.resolve();
|
|
114
|
+
}
|
|
115
|
+
return self._post("/api/sdk/register-route-patterns", body);
|
|
110
116
|
};
|
|
111
117
|
|
|
112
118
|
/**
|
package/index.js
CHANGED
|
@@ -161,6 +161,91 @@ function runBackendScanner(detectedBackend, app, options, cwd) {
|
|
|
161
161
|
return endpoints;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
var FULLSTACK_FRAMEWORKS = ["nextjs", "nuxt", "sveltekit", "remix"];
|
|
165
|
+
var BACKEND_ONLY_FRAMEWORKS = [
|
|
166
|
+
"express",
|
|
167
|
+
"fastify",
|
|
168
|
+
"nestjs",
|
|
169
|
+
"koa",
|
|
170
|
+
"hapi",
|
|
171
|
+
"adonis",
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
function classifyInstallation(detectedBackend, detectedFrontend) {
|
|
175
|
+
if (FULLSTACK_FRAMEWORKS.indexOf(detectedBackend) !== -1) {
|
|
176
|
+
return "fullstack";
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
BACKEND_ONLY_FRAMEWORKS.indexOf(detectedBackend) !== -1 &&
|
|
180
|
+
detectedFrontend === "unknown"
|
|
181
|
+
) {
|
|
182
|
+
return "backend-only";
|
|
183
|
+
}
|
|
184
|
+
if (detectedBackend === "unknown" && detectedFrontend !== "unknown") {
|
|
185
|
+
return "frontend-only";
|
|
186
|
+
}
|
|
187
|
+
if (detectedBackend === "unknown" && detectedFrontend === "unknown") {
|
|
188
|
+
return "unknown";
|
|
189
|
+
}
|
|
190
|
+
return "mixed";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── Run a full scan (backend endpoints + frontend routes) and report ────────
|
|
194
|
+
// This used to run automatically 3 seconds after server start. Now it only
|
|
195
|
+
// runs when the BotVersion dashboard's "Scan" button sends a request to this
|
|
196
|
+
// SDK's internal scan-trigger endpoint (see interceptor.js). This fixes scans
|
|
197
|
+
// not happening on serverless redeploys (Vercel, etc.) since there's no
|
|
198
|
+
// "restart" event to hook into there.
|
|
199
|
+
function runFullScan(
|
|
200
|
+
detectedBackend,
|
|
201
|
+
detectedFrontend,
|
|
202
|
+
app,
|
|
203
|
+
options,
|
|
204
|
+
cwd,
|
|
205
|
+
client,
|
|
206
|
+
) {
|
|
207
|
+
var projectType = classifyInstallation(detectedBackend, detectedFrontend);
|
|
208
|
+
|
|
209
|
+
var result = {
|
|
210
|
+
endpointCount: 0,
|
|
211
|
+
routeCount: 0,
|
|
212
|
+
projectType: projectType,
|
|
213
|
+
detectedBackend: detectedBackend,
|
|
214
|
+
detectedFrontend: detectedFrontend,
|
|
215
|
+
sdkLanguage: "javascript",
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
var shouldScanBackend =
|
|
219
|
+
projectType === "fullstack" ||
|
|
220
|
+
projectType === "backend-only" ||
|
|
221
|
+
projectType === "mixed";
|
|
222
|
+
|
|
223
|
+
var shouldScanFrontend =
|
|
224
|
+
projectType === "fullstack" || projectType === "frontend-only";
|
|
225
|
+
|
|
226
|
+
if (shouldScanBackend) {
|
|
227
|
+
var endpoints = runBackendScanner(detectedBackend, app, options, cwd);
|
|
228
|
+
result.endpointCount = endpoints.length;
|
|
229
|
+
|
|
230
|
+
if (endpoints.length > 0) {
|
|
231
|
+
client.registerEndpoints(endpoints);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (shouldScanFrontend) {
|
|
236
|
+
var routePatterns = scanner.scanFrontendRoutes(cwd);
|
|
237
|
+
result.routeCount = routePatterns.length;
|
|
238
|
+
|
|
239
|
+
if (routePatterns.length > 0) {
|
|
240
|
+
client.registerRoutePatterns(routePatterns).catch(function () {
|
|
241
|
+
// silently ignore — non-critical
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
|
|
164
249
|
// ── Attach the right runtime interceptor ─────────────────────────────────────
|
|
165
250
|
function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
166
251
|
switch (detectedBackend) {
|
|
@@ -169,6 +254,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
169
254
|
exclude: options.exclude || [],
|
|
170
255
|
apiPrefix: options.apiPrefix || null,
|
|
171
256
|
debug: options.debug || false,
|
|
257
|
+
scanSecret: options.scanSecret,
|
|
258
|
+
onScanRequested: options.onScanRequested,
|
|
172
259
|
});
|
|
173
260
|
break;
|
|
174
261
|
|
|
@@ -177,6 +264,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
177
264
|
exclude: options.exclude || [],
|
|
178
265
|
apiPrefix: options.apiPrefix || "/api",
|
|
179
266
|
debug: options.debug || false,
|
|
267
|
+
scanSecret: options.scanSecret,
|
|
268
|
+
onScanRequested: options.onScanRequested,
|
|
180
269
|
});
|
|
181
270
|
break;
|
|
182
271
|
|
|
@@ -186,6 +275,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
186
275
|
exclude: options.exclude || [],
|
|
187
276
|
apiPrefix: options.apiPrefix || null,
|
|
188
277
|
debug: options.debug || false,
|
|
278
|
+
scanSecret: options.scanSecret,
|
|
279
|
+
onScanRequested: options.onScanRequested,
|
|
189
280
|
});
|
|
190
281
|
}
|
|
191
282
|
break;
|
|
@@ -196,6 +287,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
196
287
|
exclude: options.exclude || [],
|
|
197
288
|
apiPrefix: options.apiPrefix || null,
|
|
198
289
|
debug: options.debug || false,
|
|
290
|
+
scanSecret: options.scanSecret,
|
|
291
|
+
onScanRequested: options.onScanRequested,
|
|
199
292
|
});
|
|
200
293
|
}
|
|
201
294
|
break;
|
|
@@ -206,6 +299,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
206
299
|
exclude: options.exclude || [],
|
|
207
300
|
apiPrefix: options.apiPrefix || null,
|
|
208
301
|
debug: options.debug || false,
|
|
302
|
+
scanSecret: options.scanSecret,
|
|
303
|
+
onScanRequested: options.onScanRequested,
|
|
209
304
|
});
|
|
210
305
|
}
|
|
211
306
|
break;
|
|
@@ -215,6 +310,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
215
310
|
exclude: options.exclude || [],
|
|
216
311
|
apiPrefix: options.apiPrefix || "/",
|
|
217
312
|
debug: options.debug || false,
|
|
313
|
+
scanSecret: options.scanSecret,
|
|
314
|
+
onScanRequested: options.onScanRequested,
|
|
218
315
|
});
|
|
219
316
|
break;
|
|
220
317
|
|
|
@@ -223,6 +320,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
223
320
|
exclude: options.exclude || [],
|
|
224
321
|
apiPrefix: options.apiPrefix || "/",
|
|
225
322
|
debug: options.debug || false,
|
|
323
|
+
scanSecret: options.scanSecret,
|
|
324
|
+
onScanRequested: options.onScanRequested,
|
|
226
325
|
});
|
|
227
326
|
break;
|
|
228
327
|
|
|
@@ -231,6 +330,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
231
330
|
exclude: options.exclude || [],
|
|
232
331
|
apiPrefix: options.apiPrefix || "/api",
|
|
233
332
|
debug: options.debug || false,
|
|
333
|
+
scanSecret: options.scanSecret,
|
|
334
|
+
onScanRequested: options.onScanRequested,
|
|
234
335
|
});
|
|
235
336
|
break;
|
|
236
337
|
|
|
@@ -239,6 +340,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
239
340
|
exclude: options.exclude || [],
|
|
240
341
|
apiPrefix: options.apiPrefix || "/",
|
|
241
342
|
debug: options.debug || false,
|
|
343
|
+
scanSecret: options.scanSecret,
|
|
344
|
+
onScanRequested: options.onScanRequested,
|
|
242
345
|
});
|
|
243
346
|
break;
|
|
244
347
|
|
|
@@ -247,6 +350,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
247
350
|
exclude: options.exclude || [],
|
|
248
351
|
apiPrefix: options.apiPrefix || "/",
|
|
249
352
|
debug: options.debug || false,
|
|
353
|
+
scanSecret: options.scanSecret,
|
|
354
|
+
onScanRequested: options.onScanRequested,
|
|
250
355
|
});
|
|
251
356
|
break;
|
|
252
357
|
|
|
@@ -256,6 +361,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
|
|
|
256
361
|
exclude: options.exclude || [],
|
|
257
362
|
apiPrefix: options.apiPrefix || "/",
|
|
258
363
|
debug: options.debug || false,
|
|
364
|
+
scanSecret: options.scanSecret,
|
|
365
|
+
onScanRequested: options.onScanRequested,
|
|
259
366
|
});
|
|
260
367
|
break;
|
|
261
368
|
}
|
|
@@ -289,7 +396,22 @@ var BotVersion = {
|
|
|
289
396
|
this._initialized = true;
|
|
290
397
|
const savedOptions = global._botVersionOptions || {};
|
|
291
398
|
const savedFramework = global._botVersionFramework || "nextjs";
|
|
292
|
-
|
|
399
|
+
const savedFrontend = global._botVersionFrontendFramework || "unknown";
|
|
400
|
+
const savedProjectType = classifyInstallation(
|
|
401
|
+
savedFramework,
|
|
402
|
+
savedFrontend,
|
|
403
|
+
);
|
|
404
|
+
if (
|
|
405
|
+
savedProjectType !== "frontend-only" &&
|
|
406
|
+
savedProjectType !== "unknown"
|
|
407
|
+
) {
|
|
408
|
+
attachRuntimeInterceptor(
|
|
409
|
+
savedFramework,
|
|
410
|
+
app,
|
|
411
|
+
this._client,
|
|
412
|
+
savedOptions,
|
|
413
|
+
);
|
|
414
|
+
}
|
|
293
415
|
return;
|
|
294
416
|
}
|
|
295
417
|
|
|
@@ -319,33 +441,36 @@ var BotVersion = {
|
|
|
319
441
|
var detectedFrontend = options.frontendFramework || detected.frontend;
|
|
320
442
|
|
|
321
443
|
global._botVersionFramework = detectedBackend;
|
|
444
|
+
global._botVersionFrontendFramework = detectedFrontend;
|
|
445
|
+
|
|
446
|
+
var projectType = classifyInstallation(detectedBackend, detectedFrontend);
|
|
322
447
|
|
|
323
448
|
if (debug) {
|
|
324
|
-
// Detected: detectedBackend, detectedFrontend
|
|
449
|
+
// Detected: detectedBackend, detectedFrontend, projectType
|
|
325
450
|
}
|
|
326
451
|
|
|
327
|
-
// ──
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
452
|
+
// ── Wire up the on-demand scan trigger ───────────────────────────────────
|
|
453
|
+
// Instead of scanning automatically on restart, the interceptor now
|
|
454
|
+
// listens for a special request from the BotVersion dashboard and runs
|
|
455
|
+
// the scan only when the user clicks "Scan" there.
|
|
456
|
+
options.scanSecret = options.apiKey;
|
|
457
|
+
options.onScanRequested = function () {
|
|
458
|
+
return runFullScan(
|
|
459
|
+
detectedBackend,
|
|
460
|
+
detectedFrontend,
|
|
461
|
+
app,
|
|
462
|
+
options,
|
|
463
|
+
cwd,
|
|
464
|
+
self._client,
|
|
465
|
+
);
|
|
466
|
+
};
|
|
342
467
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
468
|
+
// ── Attach runtime interceptor ───────────────────────────────────────────
|
|
469
|
+
// Skip for frontend-only or unknown projects — there's no backend
|
|
470
|
+
// server on this process worth intercepting live traffic on.
|
|
471
|
+
if (projectType !== "frontend-only" && projectType !== "unknown") {
|
|
472
|
+
attachRuntimeInterceptor(detectedBackend, app, self._client, options);
|
|
473
|
+
}
|
|
349
474
|
},
|
|
350
475
|
|
|
351
476
|
getEndpoints: function () {
|
|
@@ -377,3 +502,5 @@ module.exports.default = BotVersion;
|
|
|
377
502
|
module.exports.init = BotVersion.init;
|
|
378
503
|
module.exports.getEndpoints = BotVersion.getEndpoints;
|
|
379
504
|
module.exports.registerEndpoint = BotVersion.registerEndpoint;
|
|
505
|
+
module.exports.detectFrameworks = detectFrameworks;
|
|
506
|
+
module.exports.classifyInstallation = classifyInstallation;
|
package/interceptor.js
CHANGED
|
@@ -46,6 +46,39 @@ function attachInterceptor(app, client, options) {
|
|
|
46
46
|
|
|
47
47
|
app.use(function botVersionInterceptor(req, res, next) {
|
|
48
48
|
const path = req.path || req.url || "";
|
|
49
|
+
|
|
50
|
+
// ── Scan trigger from BotVersion dashboard ───────────────────────────
|
|
51
|
+
if (path === "/__botversion/scan" && req.method === "POST") {
|
|
52
|
+
const providedKey = req.headers["x-botversion-scan-key"] || "";
|
|
53
|
+
if (
|
|
54
|
+
options.scanSecret &&
|
|
55
|
+
providedKey === options.scanSecret &&
|
|
56
|
+
typeof options.onScanRequested === "function"
|
|
57
|
+
) {
|
|
58
|
+
Promise.resolve(options.onScanRequested())
|
|
59
|
+
.then(function (result) {
|
|
60
|
+
res.statusCode = 200;
|
|
61
|
+
res.setHeader("Content-Type", "application/json");
|
|
62
|
+
res.end(JSON.stringify({ success: true, result: result || null }));
|
|
63
|
+
})
|
|
64
|
+
.catch(function (err) {
|
|
65
|
+
res.statusCode = 500;
|
|
66
|
+
res.setHeader("Content-Type", "application/json");
|
|
67
|
+
res.end(
|
|
68
|
+
JSON.stringify({
|
|
69
|
+
success: false,
|
|
70
|
+
error: String((err && err.message) || err),
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
} else {
|
|
75
|
+
res.statusCode = 401;
|
|
76
|
+
res.setHeader("Content-Type", "application/json");
|
|
77
|
+
res.end(JSON.stringify({ success: false, error: "Unauthorized" }));
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
49
82
|
const shouldIgnore = ignorePaths.some(function (p) {
|
|
50
83
|
return path.startsWith(p);
|
|
51
84
|
});
|
|
@@ -248,6 +281,40 @@ function attachNextJsInterceptor(client, options) {
|
|
|
248
281
|
const path = req.url ? req.url.split("?")[0] : "";
|
|
249
282
|
const method = req.method ? req.method.toUpperCase() : "";
|
|
250
283
|
|
|
284
|
+
// ── Scan trigger from BotVersion dashboard ───────────────────────
|
|
285
|
+
if (path === "/__botversion/scan" && method === "POST") {
|
|
286
|
+
const providedKey = req.headers["x-botversion-scan-key"] || "";
|
|
287
|
+
if (
|
|
288
|
+
options.scanSecret &&
|
|
289
|
+
providedKey === options.scanSecret &&
|
|
290
|
+
typeof options.onScanRequested === "function"
|
|
291
|
+
) {
|
|
292
|
+
Promise.resolve(options.onScanRequested())
|
|
293
|
+
.then(function (result) {
|
|
294
|
+
res.statusCode = 200;
|
|
295
|
+
res.setHeader("Content-Type", "application/json");
|
|
296
|
+
res.end(
|
|
297
|
+
JSON.stringify({ success: true, result: result || null }),
|
|
298
|
+
);
|
|
299
|
+
})
|
|
300
|
+
.catch(function (err) {
|
|
301
|
+
res.statusCode = 500;
|
|
302
|
+
res.setHeader("Content-Type", "application/json");
|
|
303
|
+
res.end(
|
|
304
|
+
JSON.stringify({
|
|
305
|
+
success: false,
|
|
306
|
+
error: String((err && err.message) || err),
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
res.statusCode = 401;
|
|
312
|
+
res.setHeader("Content-Type", "application/json");
|
|
313
|
+
res.end(JSON.stringify({ success: false, error: "Unauthorized" }));
|
|
314
|
+
}
|
|
315
|
+
return; // handled directly — don't pass through to the app
|
|
316
|
+
}
|
|
317
|
+
|
|
251
318
|
const shouldIgnore = (options.exclude || [])
|
|
252
319
|
.concat([
|
|
253
320
|
"/health",
|
|
@@ -464,6 +531,29 @@ function attachFastifyInterceptor(fastify, client, options) {
|
|
|
464
531
|
const path = request.url ? request.url.split("?")[0] : "";
|
|
465
532
|
const method = request.method ? request.method.toUpperCase() : "";
|
|
466
533
|
|
|
534
|
+
// ── Scan trigger from BotVersion dashboard ───────────────────────────
|
|
535
|
+
if (path === "/__botversion/scan" && method === "POST") {
|
|
536
|
+
const providedKey = request.headers["x-botversion-scan-key"] || "";
|
|
537
|
+
if (
|
|
538
|
+
options.scanSecret &&
|
|
539
|
+
providedKey === options.scanSecret &&
|
|
540
|
+
typeof options.onScanRequested === "function"
|
|
541
|
+
) {
|
|
542
|
+
try {
|
|
543
|
+
const result = await options.onScanRequested();
|
|
544
|
+
reply.code(200).send({ success: true, result: result || null });
|
|
545
|
+
} catch (err) {
|
|
546
|
+
reply.code(500).send({
|
|
547
|
+
success: false,
|
|
548
|
+
error: String((err && err.message) || err),
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
reply.code(401).send({ success: false, error: "Unauthorized" });
|
|
553
|
+
}
|
|
554
|
+
return reply;
|
|
555
|
+
}
|
|
556
|
+
|
|
467
557
|
const shouldIgnore = ignorePaths.some(function (p) {
|
|
468
558
|
return path.startsWith(p);
|
|
469
559
|
});
|
|
@@ -541,11 +631,37 @@ function attachKoaInterceptor(app, client, options) {
|
|
|
541
631
|
// IMPORTANT: This interceptor must be added AFTER koa-bodyparser middleware
|
|
542
632
|
// so that ctx.request.body is already populated when we read it.
|
|
543
633
|
app.use(async function botVersionKoaInterceptor(ctx, next) {
|
|
544
|
-
await next();
|
|
545
|
-
|
|
546
634
|
const path = ctx.path || "";
|
|
547
635
|
const method = ctx.method ? ctx.method.toUpperCase() : "";
|
|
548
636
|
|
|
637
|
+
// ── Scan trigger from BotVersion dashboard ───────────────────────────
|
|
638
|
+
if (path === "/__botversion/scan" && method === "POST") {
|
|
639
|
+
const providedKey = ctx.headers["x-botversion-scan-key"] || "";
|
|
640
|
+
if (
|
|
641
|
+
options.scanSecret &&
|
|
642
|
+
providedKey === options.scanSecret &&
|
|
643
|
+
typeof options.onScanRequested === "function"
|
|
644
|
+
) {
|
|
645
|
+
try {
|
|
646
|
+
const result = await options.onScanRequested();
|
|
647
|
+
ctx.status = 200;
|
|
648
|
+
ctx.body = { success: true, result: result || null };
|
|
649
|
+
} catch (err) {
|
|
650
|
+
ctx.status = 500;
|
|
651
|
+
ctx.body = {
|
|
652
|
+
success: false,
|
|
653
|
+
error: String((err && err.message) || err),
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
} else {
|
|
657
|
+
ctx.status = 401;
|
|
658
|
+
ctx.body = { success: false, error: "Unauthorized" };
|
|
659
|
+
}
|
|
660
|
+
return; // handled directly — don't call next()
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
await next();
|
|
664
|
+
|
|
549
665
|
const shouldIgnore = ignorePaths.some(function (p) {
|
|
550
666
|
return path.startsWith(p);
|
|
551
667
|
});
|
|
@@ -605,6 +721,38 @@ function attachHapiInterceptor(server, client, options) {
|
|
|
605
721
|
const path = request.path || "";
|
|
606
722
|
const method = request.method ? request.method.toUpperCase() : "";
|
|
607
723
|
|
|
724
|
+
// ── Scan trigger from BotVersion dashboard ───────────────────────────
|
|
725
|
+
if (path === "/__botversion/scan" && method === "POST") {
|
|
726
|
+
const providedKey =
|
|
727
|
+
(request.headers && request.headers["x-botversion-scan-key"]) || "";
|
|
728
|
+
if (
|
|
729
|
+
options.scanSecret &&
|
|
730
|
+
providedKey === options.scanSecret &&
|
|
731
|
+
typeof options.onScanRequested === "function"
|
|
732
|
+
) {
|
|
733
|
+
return Promise.resolve(options.onScanRequested())
|
|
734
|
+
.then(function (result) {
|
|
735
|
+
return h
|
|
736
|
+
.response({ success: true, result: result || null })
|
|
737
|
+
.code(200)
|
|
738
|
+
.takeover();
|
|
739
|
+
})
|
|
740
|
+
.catch(function (err) {
|
|
741
|
+
return h
|
|
742
|
+
.response({
|
|
743
|
+
success: false,
|
|
744
|
+
error: String((err && err.message) || err),
|
|
745
|
+
})
|
|
746
|
+
.code(500)
|
|
747
|
+
.takeover();
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
return h
|
|
751
|
+
.response({ success: false, error: "Unauthorized" })
|
|
752
|
+
.code(401)
|
|
753
|
+
.takeover();
|
|
754
|
+
}
|
|
755
|
+
|
|
608
756
|
const shouldIgnore = ignorePaths.some(function (p) {
|
|
609
757
|
return path.startsWith(p);
|
|
610
758
|
});
|
|
@@ -659,6 +807,8 @@ function attachNestJsInterceptor(client, options) {
|
|
|
659
807
|
exclude: (options || {}).exclude || [],
|
|
660
808
|
apiPrefix: (options || {}).apiPrefix || "/",
|
|
661
809
|
debug: (options || {}).debug || false,
|
|
810
|
+
scanSecret: (options || {}).scanSecret,
|
|
811
|
+
onScanRequested: (options || {}).onScanRequested,
|
|
662
812
|
});
|
|
663
813
|
}
|
|
664
814
|
|
|
@@ -671,6 +821,8 @@ function attachSvelteKitInterceptor(client, options) {
|
|
|
671
821
|
exclude: (options || {}).exclude || [],
|
|
672
822
|
apiPrefix: (options || {}).apiPrefix || "/",
|
|
673
823
|
debug: (options || {}).debug || false,
|
|
824
|
+
scanSecret: (options || {}).scanSecret,
|
|
825
|
+
onScanRequested: (options || {}).onScanRequested,
|
|
674
826
|
});
|
|
675
827
|
}
|
|
676
828
|
|
|
@@ -679,6 +831,8 @@ function attachNuxtInterceptor(client, options) {
|
|
|
679
831
|
exclude: (options || {}).exclude || [],
|
|
680
832
|
apiPrefix: (options || {}).apiPrefix || "/api",
|
|
681
833
|
debug: (options || {}).debug || false,
|
|
834
|
+
scanSecret: (options || {}).scanSecret,
|
|
835
|
+
onScanRequested: (options || {}).onScanRequested,
|
|
682
836
|
});
|
|
683
837
|
}
|
|
684
838
|
|
|
@@ -687,6 +841,8 @@ function attachRemixInterceptor(client, options) {
|
|
|
687
841
|
exclude: (options || {}).exclude || [],
|
|
688
842
|
apiPrefix: (options || {}).apiPrefix || "/",
|
|
689
843
|
debug: (options || {}).debug || false,
|
|
844
|
+
scanSecret: (options || {}).scanSecret,
|
|
845
|
+
onScanRequested: (options || {}).onScanRequested,
|
|
690
846
|
});
|
|
691
847
|
}
|
|
692
848
|
|
|
@@ -695,6 +851,8 @@ function attachAdonisInterceptor(client, options) {
|
|
|
695
851
|
exclude: (options || {}).exclude || [],
|
|
696
852
|
apiPrefix: (options || {}).apiPrefix || "/",
|
|
697
853
|
debug: (options || {}).debug || false,
|
|
854
|
+
scanSecret: (options || {}).scanSecret,
|
|
855
|
+
onScanRequested: (options || {}).onScanRequested,
|
|
698
856
|
});
|
|
699
857
|
}
|
|
700
858
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botversion-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "BotVersion SDK — auto-detect and register your API endpoints",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"botversion-
|
|
7
|
+
"botversion-scan-frontend": "./bin/scan-frontend.js"
|
|
8
8
|
},
|
|
9
9
|
"keywords": [
|
|
10
10
|
"botversion",
|