securenow 5.3.4 โ 5.5.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/cli/apps.js +294 -5
- package/cli/auth.js +4 -4
- package/cli/ui.js +45 -0
- package/cli.js +3 -1
- package/nextjs.js +22 -4
- package/package.json +3 -2
- package/tracing.js +25 -1
package/cli/apps.js
CHANGED
|
@@ -5,6 +5,7 @@ const config = require('./config');
|
|
|
5
5
|
const ui = require('./ui');
|
|
6
6
|
|
|
7
7
|
const FREE_TRIAL_URL = 'https://freetrial.securenow.ai:4318';
|
|
8
|
+
const DOMAIN_REGEX = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
8
9
|
|
|
9
10
|
function instanceUrl(inst) {
|
|
10
11
|
if (!inst) return FREE_TRIAL_URL;
|
|
@@ -143,6 +144,10 @@ async function create(args, flags) {
|
|
|
143
144
|
if (flags.json) {
|
|
144
145
|
ui.json({ ...app, instanceUrl: envUrl });
|
|
145
146
|
}
|
|
147
|
+
|
|
148
|
+
if (app.hosts && app.hosts.length > 0) {
|
|
149
|
+
await offerSubdomainDiscovery(app.hosts, selectedInstanceId, flags);
|
|
150
|
+
}
|
|
146
151
|
} catch (err) {
|
|
147
152
|
s.fail('Failed to create application');
|
|
148
153
|
throw err;
|
|
@@ -194,11 +199,11 @@ async function pickInstance() {
|
|
|
194
199
|
}
|
|
195
200
|
|
|
196
201
|
function openBrowser(url) {
|
|
197
|
-
const {
|
|
202
|
+
const { execFileSync } = require('child_process');
|
|
198
203
|
try {
|
|
199
|
-
if (process.platform === 'darwin')
|
|
200
|
-
else if (process.platform === 'win32')
|
|
201
|
-
else
|
|
204
|
+
if (process.platform === 'darwin') execFileSync('open', [url], { stdio: 'ignore' });
|
|
205
|
+
else if (process.platform === 'win32') execFileSync('cmd', ['/c', 'start', '', url], { stdio: 'ignore' });
|
|
206
|
+
else execFileSync('xdg-open', [url], { stdio: 'ignore' });
|
|
202
207
|
} catch {
|
|
203
208
|
ui.warn(`Could not open browser. Visit: ${url}`);
|
|
204
209
|
}
|
|
@@ -293,4 +298,288 @@ async function setDefault(args) {
|
|
|
293
298
|
ui.success(`Default application set to ${ui.c.bold(key)}`);
|
|
294
299
|
}
|
|
295
300
|
|
|
296
|
-
|
|
301
|
+
function extractRootDomains(hosts) {
|
|
302
|
+
const domains = new Set();
|
|
303
|
+
for (const host of hosts || []) {
|
|
304
|
+
let cleaned = host.trim().toLowerCase();
|
|
305
|
+
try {
|
|
306
|
+
if (cleaned.startsWith('http://') || cleaned.startsWith('https://')) {
|
|
307
|
+
cleaned = new URL(cleaned).hostname;
|
|
308
|
+
}
|
|
309
|
+
} catch {}
|
|
310
|
+
if (DOMAIN_REGEX.test(cleaned)) {
|
|
311
|
+
const parts = cleaned.split('.');
|
|
312
|
+
const root = parts.length > 2 ? parts.slice(-2).join('.') : cleaned;
|
|
313
|
+
domains.add(root);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return [...domains];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function offerSubdomainDiscovery(hosts, instanceId, flags) {
|
|
320
|
+
if (!process.stdin.isTTY) return;
|
|
321
|
+
|
|
322
|
+
const domains = extractRootDomains(hosts);
|
|
323
|
+
if (domains.length === 0) return;
|
|
324
|
+
|
|
325
|
+
const shouldDiscover = await ui.confirm(
|
|
326
|
+
`Domain${domains.length > 1 ? 's' : ''} detected (${domains.join(', ')}). Discover subdomains and add them as apps?`
|
|
327
|
+
);
|
|
328
|
+
if (!shouldDiscover) return;
|
|
329
|
+
|
|
330
|
+
await discoverAndAdd(domains, instanceId, flags);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function discoverAndAdd(domains, instanceId, flags) {
|
|
334
|
+
const s = ui.spinner(`Scanning ${domains.length} domain${domains.length > 1 ? 's' : ''} for subdomains`);
|
|
335
|
+
|
|
336
|
+
let allSubdomains = [];
|
|
337
|
+
try {
|
|
338
|
+
for (const domain of domains) {
|
|
339
|
+
s.update(`Scanning ${domain}...`);
|
|
340
|
+
const result = await api.get(`/subdomains`, { query: { domain } });
|
|
341
|
+
if (result.success && result.subdomains) {
|
|
342
|
+
allSubdomains.push(...result.subdomains);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
s.stop(`Found ${allSubdomains.length} subdomain${allSubdomains.length !== 1 ? 's' : ''}`);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
s.fail('Failed to discover subdomains');
|
|
348
|
+
throw err;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (allSubdomains.length === 0) {
|
|
352
|
+
ui.info('No subdomains found.');
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Filter out subdomains already registered as apps
|
|
357
|
+
let existingHosts;
|
|
358
|
+
try {
|
|
359
|
+
const appData = await api.get('/applications');
|
|
360
|
+
const apps = appData.applications || [];
|
|
361
|
+
existingHosts = new Set(apps.flatMap(a => (a.hosts || []).map(h => h.toLowerCase())));
|
|
362
|
+
} catch {
|
|
363
|
+
existingHosts = new Set();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const newSubdomains = allSubdomains.filter(
|
|
367
|
+
(s) => !existingHosts.has(s.subdomain.toLowerCase())
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
if (newSubdomains.length === 0) {
|
|
371
|
+
ui.info('All discovered subdomains are already registered as apps.');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
console.log('');
|
|
376
|
+
ui.info(`${newSubdomains.length} new subdomain${newSubdomains.length !== 1 ? 's' : ''} found (${allSubdomains.length - newSubdomains.length} already tracked):`);
|
|
377
|
+
|
|
378
|
+
if (flags && flags.json) {
|
|
379
|
+
ui.json(newSubdomains);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const choices = newSubdomains.map((sub) => ({
|
|
384
|
+
label: sub.subdomain,
|
|
385
|
+
detail: `IP: ${sub.ip || 'none'}${sub.cloudflare ? ' ยท Cloudflare' : ''}`,
|
|
386
|
+
value: sub.subdomain,
|
|
387
|
+
}));
|
|
388
|
+
|
|
389
|
+
const selected = await ui.multiSelect(
|
|
390
|
+
'Which subdomains should be added as monitored apps?',
|
|
391
|
+
choices
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
if (selected.length === 0) {
|
|
395
|
+
ui.info('No subdomains selected.');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const createSpinner = ui.spinner(`Creating ${selected.length} application${selected.length !== 1 ? 's' : ''}`);
|
|
400
|
+
try {
|
|
401
|
+
const result = await api.post('/applications/bulk', {
|
|
402
|
+
subdomains: selected,
|
|
403
|
+
instanceId: instanceId || null,
|
|
404
|
+
});
|
|
405
|
+
const count = result.count || selected.length;
|
|
406
|
+
createSpinner.stop(`Created ${count} application${count !== 1 ? 's' : ''}`);
|
|
407
|
+
|
|
408
|
+
console.log('');
|
|
409
|
+
if (result.applications) {
|
|
410
|
+
const rows = result.applications.map((app) => [
|
|
411
|
+
app.name,
|
|
412
|
+
ui.c.dim(app.key),
|
|
413
|
+
app.hosts?.join(', ') || '',
|
|
414
|
+
]);
|
|
415
|
+
ui.table(['Name', 'Key', 'Hosts'], rows);
|
|
416
|
+
}
|
|
417
|
+
console.log('');
|
|
418
|
+
} catch (err) {
|
|
419
|
+
createSpinner.fail('Failed to create applications');
|
|
420
|
+
throw err;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function discover(args, flags) {
|
|
425
|
+
requireAuth();
|
|
426
|
+
|
|
427
|
+
const appId = args[0];
|
|
428
|
+
let domains = [];
|
|
429
|
+
let instanceId = null;
|
|
430
|
+
|
|
431
|
+
if (appId) {
|
|
432
|
+
const s = ui.spinner('Fetching application');
|
|
433
|
+
try {
|
|
434
|
+
const data = await api.get(`/applications/${appId}`);
|
|
435
|
+
const app = data.application || data;
|
|
436
|
+
s.stop(`Application: ${app.name}`);
|
|
437
|
+
domains = extractRootDomains(app.hosts);
|
|
438
|
+
instanceId = app.instanceId;
|
|
439
|
+
} catch (err) {
|
|
440
|
+
s.fail('Failed to fetch application');
|
|
441
|
+
throw err;
|
|
442
|
+
}
|
|
443
|
+
} else if (flags.domain) {
|
|
444
|
+
domains = flags.domain.split(',').map(d => d.trim());
|
|
445
|
+
} else {
|
|
446
|
+
const domain = await ui.prompt('Domain to scan (e.g. example.com)');
|
|
447
|
+
if (!domain) {
|
|
448
|
+
ui.error('Domain is required');
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
domains = domain.split(',').map(d => d.trim());
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (domains.length === 0) {
|
|
455
|
+
ui.error('No domains found in application hosts. Specify a domain with --domain');
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (flags.instance) {
|
|
460
|
+
instanceId = flags.instance;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
await discoverAndAdd(domains, instanceId, flags);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function scan(args, flags) {
|
|
467
|
+
requireAuth();
|
|
468
|
+
const s = ui.spinner('Running subdomain discovery across all applications');
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
const appData = await api.get('/applications');
|
|
472
|
+
const apps = appData.applications || [];
|
|
473
|
+
|
|
474
|
+
const allDomains = new Set();
|
|
475
|
+
let firstInstanceId = null;
|
|
476
|
+
for (const app of apps) {
|
|
477
|
+
const roots = extractRootDomains(app.hosts);
|
|
478
|
+
for (const r of roots) allDomains.add(r);
|
|
479
|
+
if (!firstInstanceId && app.instanceId) firstInstanceId = app.instanceId;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (allDomains.size === 0) {
|
|
483
|
+
s.stop('No domains found across your applications');
|
|
484
|
+
ui.info('Add domains to your application hosts first, then run this command again.');
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
s.update(`Found ${allDomains.size} unique domain${allDomains.size !== 1 ? 's' : ''}, scanning...`);
|
|
489
|
+
|
|
490
|
+
const existingHosts = new Set(
|
|
491
|
+
apps.flatMap((a) => (a.hosts || []).map((h) => h.toLowerCase()))
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
let totalNew = 0;
|
|
495
|
+
let totalCreated = 0;
|
|
496
|
+
const allNew = [];
|
|
497
|
+
|
|
498
|
+
for (const domain of allDomains) {
|
|
499
|
+
s.update(`Scanning ${domain}...`);
|
|
500
|
+
try {
|
|
501
|
+
const result = await api.get('/subdomains', { query: { domain } });
|
|
502
|
+
if (result.success && result.subdomains) {
|
|
503
|
+
const newSubs = result.subdomains.filter(
|
|
504
|
+
(sub) => !existingHosts.has(sub.subdomain.toLowerCase())
|
|
505
|
+
);
|
|
506
|
+
totalNew += newSubs.length;
|
|
507
|
+
allNew.push(...newSubs);
|
|
508
|
+
for (const sub of newSubs) {
|
|
509
|
+
existingHosts.add(sub.subdomain.toLowerCase());
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
} catch {
|
|
513
|
+
ui.warn(`Failed to scan ${domain}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
s.stop(`Scanned ${allDomains.size} domain${allDomains.size !== 1 ? 's' : ''}, found ${totalNew} new subdomain${totalNew !== 1 ? 's' : ''}`);
|
|
518
|
+
|
|
519
|
+
if (totalNew === 0) {
|
|
520
|
+
ui.success('All subdomains are already tracked. Nothing to add.');
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (flags.yes || flags.force) {
|
|
525
|
+
const createSpinner = ui.spinner(`Creating ${totalNew} application${totalNew !== 1 ? 's' : ''}`);
|
|
526
|
+
try {
|
|
527
|
+
const subdomainNames = allNew.map((s) => s.subdomain);
|
|
528
|
+
const result = await api.post('/applications/bulk', {
|
|
529
|
+
subdomains: subdomainNames,
|
|
530
|
+
instanceId: firstInstanceId || null,
|
|
531
|
+
});
|
|
532
|
+
totalCreated = result.count || subdomainNames.length;
|
|
533
|
+
createSpinner.stop(`Created ${totalCreated} application${totalCreated !== 1 ? 's' : ''}`);
|
|
534
|
+
} catch (err) {
|
|
535
|
+
createSpinner.fail('Failed to create applications');
|
|
536
|
+
throw err;
|
|
537
|
+
}
|
|
538
|
+
} else if (process.stdin.isTTY) {
|
|
539
|
+
const choices = allNew.map((sub) => ({
|
|
540
|
+
label: sub.subdomain,
|
|
541
|
+
detail: `IP: ${sub.ip || 'none'}${sub.cloudflare ? ' ยท Cloudflare' : ''}`,
|
|
542
|
+
value: sub.subdomain,
|
|
543
|
+
}));
|
|
544
|
+
|
|
545
|
+
const selected = await ui.multiSelect(
|
|
546
|
+
'Which subdomains should be added as monitored apps?',
|
|
547
|
+
choices
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
if (selected.length === 0) {
|
|
551
|
+
ui.info('No subdomains selected.');
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const createSpinner = ui.spinner(`Creating ${selected.length} application${selected.length !== 1 ? 's' : ''}`);
|
|
556
|
+
try {
|
|
557
|
+
const result = await api.post('/applications/bulk', {
|
|
558
|
+
subdomains: selected,
|
|
559
|
+
instanceId: firstInstanceId || null,
|
|
560
|
+
});
|
|
561
|
+
totalCreated = result.count || selected.length;
|
|
562
|
+
createSpinner.stop(`Created ${totalCreated} application${totalCreated !== 1 ? 's' : ''}`);
|
|
563
|
+
} catch (err) {
|
|
564
|
+
createSpinner.fail('Failed to create applications');
|
|
565
|
+
throw err;
|
|
566
|
+
}
|
|
567
|
+
} else {
|
|
568
|
+
console.log('');
|
|
569
|
+
for (const sub of allNew) {
|
|
570
|
+
console.log(` ${sub.subdomain} ${ui.c.dim(`(IP: ${sub.ip || 'none'})`)}`);
|
|
571
|
+
}
|
|
572
|
+
console.log('');
|
|
573
|
+
ui.info(`Run with --yes to auto-create all ${totalNew} apps.`);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (flags.json) {
|
|
577
|
+
ui.json({ domainsScanned: allDomains.size, newSubdomains: totalNew, appsCreated: totalCreated });
|
|
578
|
+
}
|
|
579
|
+
} catch (err) {
|
|
580
|
+
s.fail('Subdomain scan failed');
|
|
581
|
+
throw err;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
module.exports = { list, create, info, remove, setDefault, discover, scan };
|
package/cli/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const http = require('http');
|
|
4
|
-
const {
|
|
4
|
+
const { execFileSync } = require('child_process');
|
|
5
5
|
const config = require('./config');
|
|
6
6
|
const { api, CLIError } = require('./client');
|
|
7
7
|
const ui = require('./ui');
|
|
@@ -9,9 +9,9 @@ const ui = require('./ui');
|
|
|
9
9
|
function openBrowser(url) {
|
|
10
10
|
try {
|
|
11
11
|
const platform = process.platform;
|
|
12
|
-
if (platform === 'darwin')
|
|
13
|
-
else if (platform === 'win32')
|
|
14
|
-
else
|
|
12
|
+
if (platform === 'darwin') execFileSync('open', [url], { stdio: 'ignore' });
|
|
13
|
+
else if (platform === 'win32') execFileSync('cmd', ['/c', 'start', '', url], { stdio: 'ignore' });
|
|
14
|
+
else execFileSync('xdg-open', [url], { stdio: 'ignore' });
|
|
15
15
|
return true;
|
|
16
16
|
} catch {
|
|
17
17
|
return false;
|
package/cli/ui.js
CHANGED
|
@@ -213,6 +213,50 @@ async function select(question, choices) {
|
|
|
213
213
|
return choices[idx].value ?? choices[idx];
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
async function multiSelect(question, choices) {
|
|
217
|
+
console.log(`\n ${c.cyan('?')} ${question}\n`);
|
|
218
|
+
choices.forEach((choice, i) => {
|
|
219
|
+
const label = choice.label || choice;
|
|
220
|
+
const detail = choice.detail ? c.dim(` โ ${choice.detail}`) : '';
|
|
221
|
+
console.log(` ${c.bold(String(i + 1).padStart(3))} ${label}${detail}`);
|
|
222
|
+
});
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log(c.dim(` Enter numbers separated by commas, a range (e.g. 1-5), or "all"`));
|
|
225
|
+
const answer = await prompt('Selection');
|
|
226
|
+
|
|
227
|
+
if (!answer) return [];
|
|
228
|
+
|
|
229
|
+
if (answer.toLowerCase() === 'all') {
|
|
230
|
+
return choices.map((ch) => ch.value ?? ch);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const indices = new Set();
|
|
234
|
+
for (const part of answer.split(',')) {
|
|
235
|
+
const trimmed = part.trim();
|
|
236
|
+
if (trimmed.includes('-')) {
|
|
237
|
+
const [startStr, endStr] = trimmed.split('-');
|
|
238
|
+
const start = parseInt(startStr, 10);
|
|
239
|
+
const end = parseInt(endStr, 10);
|
|
240
|
+
if (!isNaN(start) && !isNaN(end)) {
|
|
241
|
+
for (let i = Math.min(start, end); i <= Math.max(start, end); i++) {
|
|
242
|
+
indices.add(i);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
const num = parseInt(trimmed, 10);
|
|
247
|
+
if (!isNaN(num)) indices.add(num);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const selected = [];
|
|
252
|
+
for (const idx of indices) {
|
|
253
|
+
if (idx >= 1 && idx <= choices.length) {
|
|
254
|
+
selected.push(choices[idx - 1].value ?? choices[idx - 1]);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return selected;
|
|
258
|
+
}
|
|
259
|
+
|
|
216
260
|
function json(data) {
|
|
217
261
|
console.log(JSON.stringify(data, null, 2));
|
|
218
262
|
}
|
|
@@ -302,6 +346,7 @@ module.exports = {
|
|
|
302
346
|
prompt,
|
|
303
347
|
confirm,
|
|
304
348
|
select,
|
|
349
|
+
multiSelect,
|
|
305
350
|
json,
|
|
306
351
|
hr,
|
|
307
352
|
timeAgo,
|
package/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const ui = require('./cli/ui');
|
|
@@ -64,6 +64,8 @@ const COMMANDS = {
|
|
|
64
64
|
info: { desc: 'Show application details', usage: 'securenow apps info <id>', run: (a, f) => require('./cli/apps').info(a, f) },
|
|
65
65
|
delete: { desc: 'Delete an application', usage: 'securenow apps delete <id> [--force]', run: (a, f) => require('./cli/apps').remove(a, f) },
|
|
66
66
|
default: { desc: 'Set default application', usage: 'securenow apps default <key>', run: (a, f) => require('./cli/apps').setDefault(a, f) },
|
|
67
|
+
discover: { desc: 'Discover subdomains and add as apps', usage: 'securenow apps discover [appId] [--domain example.com]', run: (a, f) => require('./cli/apps').discover(a, f) },
|
|
68
|
+
scan: { desc: 'Scan all app domains for new subdomains', usage: 'securenow apps scan [--yes]', run: (a, f) => require('./cli/apps').scan(a, f) },
|
|
67
69
|
},
|
|
68
70
|
defaultSub: 'list',
|
|
69
71
|
},
|
package/nextjs.js
CHANGED
|
@@ -5,18 +5,36 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Usage in Next.js app:
|
|
7
7
|
*
|
|
8
|
-
* 1.
|
|
8
|
+
* 1. Add serverExternalPackages to next.config.js (REQUIRED to avoid webpack bundling issues):
|
|
9
|
+
*
|
|
10
|
+
* const nextConfig = {
|
|
11
|
+
* serverExternalPackages: [
|
|
12
|
+
* "securenow",
|
|
13
|
+
* "@opentelemetry/sdk-node",
|
|
14
|
+
* "@opentelemetry/auto-instrumentations-node",
|
|
15
|
+
* "@opentelemetry/instrumentation-http",
|
|
16
|
+
* "@opentelemetry/exporter-trace-otlp-http",
|
|
17
|
+
* "@opentelemetry/exporter-logs-otlp-http",
|
|
18
|
+
* "@opentelemetry/sdk-logs",
|
|
19
|
+
* "@opentelemetry/instrumentation",
|
|
20
|
+
* "@opentelemetry/resources",
|
|
21
|
+
* "@opentelemetry/semantic-conventions",
|
|
22
|
+
* "@opentelemetry/api",
|
|
23
|
+
* "@opentelemetry/api-logs",
|
|
24
|
+
* "@vercel/otel",
|
|
25
|
+
* ],
|
|
26
|
+
* };
|
|
27
|
+
*
|
|
28
|
+
* 2. Create instrumentation.ts (or .js) in your project root:
|
|
9
29
|
*
|
|
10
30
|
* import { registerSecureNow } from 'securenow/nextjs';
|
|
11
31
|
* export function register() {
|
|
12
32
|
* registerSecureNow();
|
|
13
33
|
* }
|
|
14
34
|
*
|
|
15
|
-
*
|
|
35
|
+
* 3. Set environment variables:
|
|
16
36
|
* SECURENOW_APPID=my-nextjs-app
|
|
17
37
|
* SECURENOW_INSTANCE=http://your-otlp-backend:4318
|
|
18
|
-
*
|
|
19
|
-
* That's it! ๐ No webpack warnings!
|
|
20
38
|
*/
|
|
21
39
|
|
|
22
40
|
const { v4: uuidv4 } = require('uuid');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securenow",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"description": "OpenTelemetry instrumentation for Node.js and Next.js - Send traces and logs to any OTLP-compatible backend",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "register.js",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"default": "./nextjs-wrapper.js"
|
|
65
65
|
},
|
|
66
66
|
"./nextjs-webpack-config": "./nextjs-webpack-config.js",
|
|
67
|
+
"./package.json": "./package.json",
|
|
67
68
|
"./register-vite": "./register-vite.js",
|
|
68
69
|
"./web-vite": {
|
|
69
70
|
"import": "./web-vite.mjs",
|
|
@@ -105,7 +106,7 @@
|
|
|
105
106
|
"@opentelemetry/instrumentation": "0.47.0",
|
|
106
107
|
"@opentelemetry/instrumentation-document-load": "0.47.0",
|
|
107
108
|
"@opentelemetry/instrumentation-fetch": "0.47.0",
|
|
108
|
-
"@opentelemetry/instrumentation-http": "
|
|
109
|
+
"@opentelemetry/instrumentation-http": "0.47.0",
|
|
109
110
|
"@opentelemetry/instrumentation-user-interaction": "0.47.0",
|
|
110
111
|
"@opentelemetry/instrumentation-xml-http-request": "0.47.0",
|
|
111
112
|
"@opentelemetry/resources": "1.20.0",
|
package/tracing.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Preload with:
|
|
4
|
+
* Preload with: node --require securenow/register app.js
|
|
5
|
+
*
|
|
6
|
+
* For ESM apps ("type": "module" in package.json), you MUST also add the ESM loader hook:
|
|
7
|
+
* node --import @opentelemetry/instrumentation/hook.mjs --require securenow/register app.js
|
|
5
8
|
*
|
|
6
9
|
* Env:
|
|
7
10
|
* SECURENOW_APPID=logical-name # or OTEL_SERVICE_NAME=logical-name
|
|
@@ -97,6 +100,27 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
97
100
|
return redacted;
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
// -------- ESM detection --------
|
|
104
|
+
(() => {
|
|
105
|
+
try {
|
|
106
|
+
const fs = require('fs');
|
|
107
|
+
const path = require('path');
|
|
108
|
+
const pkgPath = path.resolve(process.cwd(), 'package.json');
|
|
109
|
+
if (fs.existsSync(pkgPath)) {
|
|
110
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
111
|
+
if (pkg.type === 'module') {
|
|
112
|
+
const execArgv = process.execArgv.join(' ');
|
|
113
|
+
const hasEsmHook = execArgv.includes('hook.mjs') || execArgv.includes('import-in-the-middle');
|
|
114
|
+
if (!hasEsmHook) {
|
|
115
|
+
console.warn('[securenow] โ ๏ธ ESM app detected ("type": "module") but no ESM loader hook found.');
|
|
116
|
+
console.warn('[securenow] Instrumentations will NOT work without the ESM hook.');
|
|
117
|
+
console.warn('[securenow] Fix: node --import @opentelemetry/instrumentation/hook.mjs --require securenow/register app.js');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (_) {}
|
|
122
|
+
})();
|
|
123
|
+
|
|
100
124
|
// -------- diagnostics --------
|
|
101
125
|
(() => {
|
|
102
126
|
const L = (env('OTEL_LOG_LEVEL') || '').toLowerCase();
|