securenow 5.0.5 → 5.2.1

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.
Files changed (45) hide show
  1. package/CONSUMING-APPS-GUIDE.md +17 -17
  2. package/README.md +7 -7
  3. package/cli.js +6 -6
  4. package/console-instrumentation.js +2 -2
  5. package/docs/ARCHITECTURE.md +3 -3
  6. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  7. package/docs/AUTO-SETUP-SUMMARY.md +2 -2
  8. package/docs/AUTO-SETUP.md +4 -4
  9. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  10. package/docs/BODY-CAPTURE-FIX.md +1 -1
  11. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  12. package/docs/CHANGELOG-NEXTJS.md +1 -1
  13. package/docs/COMPLETION-REPORT.md +5 -5
  14. package/docs/CUSTOMER-GUIDE.md +16 -16
  15. package/docs/EASIEST-SETUP.md +5 -5
  16. package/docs/EXPRESS-BODY-CAPTURE.md +10 -10
  17. package/docs/IMPLEMENTATION-SUMMARY.md +10 -10
  18. package/docs/LOGGING-GUIDE.md +27 -27
  19. package/docs/LOGGING-QUICKSTART.md +13 -13
  20. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  21. package/docs/NEXTJS-GUIDE.md +14 -14
  22. package/docs/NEXTJS-QUICKSTART.md +1 -1
  23. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  24. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  25. package/docs/REDACTION-EXAMPLES.md +1 -1
  26. package/docs/REQUEST-BODY-CAPTURE.md +4 -4
  27. package/docs/SOLUTION-SUMMARY.md +4 -4
  28. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  29. package/examples/README.md +6 -6
  30. package/examples/instrumentation-with-auto-capture.ts +1 -1
  31. package/examples/nextjs-env-example.txt +2 -2
  32. package/examples/nextjs-instrumentation.js +1 -1
  33. package/examples/nextjs-instrumentation.ts +1 -1
  34. package/examples/nextjs-with-logging-example.md +6 -6
  35. package/examples/nextjs-with-options.ts +1 -1
  36. package/examples/test-nextjs-setup.js +1 -1
  37. package/free-trial-banner.js +154 -0
  38. package/nextjs.d.ts +1 -1
  39. package/nextjs.js +62 -1
  40. package/package.json +3 -3
  41. package/postinstall.js +6 -6
  42. package/register.d.ts +1 -1
  43. package/tracing.d.ts +1 -1
  44. package/tracing.js +6 -0
  45. package/web-vite.mjs +61 -0
@@ -39,9 +39,9 @@ SECURENOW_LOGGING_ENABLED=1
39
39
  SECURENOW_APPID=my-nextjs-app
40
40
  SECURENOW_INSTANCE=http://localhost:4318
41
41
 
42
- # For SigNoz Cloud:
43
- # SECURENOW_INSTANCE=https://ingest.<region>.signoz.cloud:443
44
- # OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-key>"
42
+ # For SecureNow or managed OTLP (example):
43
+ # SECURENOW_INSTANCE=https://ingest.<region>.securenow.ai:443
44
+ # OTEL_EXPORTER_OTLP_HEADERS="x-api-key=<your-key>"
45
45
  ```
46
46
 
47
47
  ### 4. Enable Instrumentation in `next.config.js`
@@ -239,9 +239,9 @@ You should see in the console:
239
239
  SecureNow initialized with logging enabled
240
240
  ```
241
241
 
242
- ## View Logs in SigNoz
242
+ ## View Logs in SecureNow
243
243
 
244
- 1. Open your SigNoz dashboard
244
+ 1. Open your SecureNow dashboard
245
245
  2. Navigate to **Logs** section
246
246
  3. Filter by `service.name = my-nextjs-app`
247
247
  4. See all your application logs with:
@@ -298,4 +298,4 @@ await import('securenow/console-instrumentation'); // Second
298
298
 
299
299
  - [Complete Logging Guide](../docs/LOGGING-GUIDE.md)
300
300
  - [Next.js Complete Guide](../docs/NEXTJS-GUIDE.md)
301
- - [View Logs in SigNoz](https://signoz.io/docs/logs-management/overview/)
301
+ - [SecureNow](https://securenow.ai/)
@@ -10,7 +10,7 @@ import { registerSecureNow } from 'securenow/nextjs';
10
10
  export function register() {
11
11
  registerSecureNow({
12
12
  serviceName: 'my-nextjs-app',
13
- endpoint: 'http://your-signoz-server:4318',
13
+ endpoint: 'http://your-otlp-collector:4318',
14
14
  noUuid: false,
15
15
  disableInstrumentations: ['fs', 'dns'],
16
16
  headers: {
@@ -58,7 +58,7 @@ setTimeout(() => {
58
58
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
59
59
  console.log('✅ All tests passed!');
60
60
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
61
- console.log('\nCheck your SigNoz dashboard for traces from "test-nextjs-app"\n');
61
+ console.log('\nCheck your SecureNow dashboard for traces from "test-nextjs-app"\n');
62
62
  process.exit(0);
63
63
  }, 2000);
64
64
 
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Free Trial Banner — auto-injects a visible "Testing Environment" banner
5
+ * into HTML pages served by apps using the SecureNow free trial instance.
6
+ *
7
+ * Opt-out: set SECURENOW_HIDE_BANNER=1
8
+ */
9
+
10
+ const FREETRIAL_HOST = 'freetrial.securenow.ai';
11
+
12
+ function isFreeTrial(endpointBase) {
13
+ return !!endpointBase && endpointBase.includes(FREETRIAL_HOST);
14
+ }
15
+
16
+ /* istanbul ignore next — runs in browser, not Node */
17
+ function _bannerClientCode() {
18
+ if (window.__snBanner) return;
19
+ window.__snBanner = 1;
20
+
21
+ function create() {
22
+ if (document.getElementById('sn-ft-banner')) return;
23
+
24
+ var d = document.createElement('div');
25
+ d.id = 'sn-ft-banner';
26
+ d.style.cssText =
27
+ 'position:fixed;top:0;left:0;right:0;z-index:2147483647;' +
28
+ 'background:#FEF3CD;color:#856404;padding:10px 16px;' +
29
+ 'font-family:system-ui,-apple-system,sans-serif;font-size:13px;' +
30
+ 'text-align:center;border-bottom:2px solid #FFE69C;' +
31
+ 'display:flex;align-items:center;justify-content:center;gap:6px;' +
32
+ 'box-shadow:0 2px 8px rgba(0,0,0,0.12)';
33
+
34
+ var icon = document.createElement('span');
35
+ icon.textContent = '\u26a0\ufe0f';
36
+ d.appendChild(icon);
37
+
38
+ var msg = document.createElement('span');
39
+ var strong = document.createElement('strong');
40
+ strong.textContent = 'Testing Environment:';
41
+ msg.appendChild(strong);
42
+ msg.appendChild(document.createTextNode(
43
+ ' Only add test applications. For production usage, please '
44
+ ));
45
+
46
+ var link = document.createElement('a');
47
+ link.href = 'https://securenow.ai/contact';
48
+ link.target = '_blank';
49
+ link.rel = 'noopener';
50
+ link.style.cssText = 'color:#664D03;font-weight:600;text-decoration:underline';
51
+ link.textContent = 'contact our team for a demo';
52
+ msg.appendChild(link);
53
+ msg.appendChild(document.createTextNode('.'));
54
+ d.appendChild(msg);
55
+
56
+ var close = document.createElement('button');
57
+ close.textContent = '\u00d7';
58
+ close.style.cssText =
59
+ 'background:none;border:none;color:#856404;font-size:18px;' +
60
+ 'cursor:pointer;margin-left:12px;padding:0 4px;line-height:1';
61
+ close.onclick = function () { d.style.display = 'none'; };
62
+ d.appendChild(close);
63
+
64
+ document.body.prepend(d);
65
+ }
66
+
67
+ if (document.readyState === 'loading') {
68
+ document.addEventListener('DOMContentLoaded', create);
69
+ } else {
70
+ create();
71
+ }
72
+ }
73
+
74
+ var BANNER_SCRIPT =
75
+ '<script data-securenow-banner>(' +
76
+ _bannerClientCode.toString() +
77
+ ')()</scr' + 'ipt>';
78
+
79
+ /**
80
+ * Monkey-patch http.ServerResponse to inject the banner script into HTML
81
+ * responses. Searches for `<head...>` and inserts the script right after it.
82
+ * Skips compressed responses and non-HTML content types.
83
+ */
84
+ function patchHttpForBanner() {
85
+ try {
86
+ var http = require('http');
87
+ var _origWrite = http.ServerResponse.prototype.write;
88
+ var _origEnd = http.ServerResponse.prototype.end;
89
+
90
+ function maybeInject(res, chunk) {
91
+ if (res._snBannerDone || !chunk) return chunk;
92
+
93
+ if (res._snIsHtml === undefined) {
94
+ var ct = res.getHeader('content-type');
95
+ var ce = res.getHeader('content-encoding');
96
+ res._snIsHtml = !!(ct && String(ct).includes('text/html') && !ce);
97
+ }
98
+ if (!res._snIsHtml) {
99
+ res._snBannerDone = true;
100
+ return chunk;
101
+ }
102
+
103
+ var isStr = typeof chunk === 'string';
104
+ var isBuf = Buffer.isBuffer(chunk);
105
+ if (!isStr && !isBuf) return chunk;
106
+
107
+ var str = isStr ? chunk : chunk.toString('utf8');
108
+ var headIdx = str.indexOf('<head');
109
+ if (headIdx === -1) return chunk;
110
+
111
+ var gt = str.indexOf('>', headIdx);
112
+ if (gt === -1) return chunk;
113
+
114
+ res._snBannerDone = true;
115
+ var result = str.slice(0, gt + 1) + BANNER_SCRIPT + str.slice(gt + 1);
116
+ return isStr ? result : Buffer.from(result, 'utf8');
117
+ }
118
+
119
+ http.ServerResponse.prototype.write = function (chunk, encoding, cb) {
120
+ try {
121
+ var modified = maybeInject(this, chunk);
122
+ if (modified !== chunk) {
123
+ var enc = typeof encoding === 'function' ? 'utf8' : encoding;
124
+ var callback = typeof encoding === 'function' ? encoding : cb;
125
+ return _origWrite.call(this, modified, enc, callback);
126
+ }
127
+ } catch (_) { /* never break the app */ }
128
+ return _origWrite.call(this, chunk, encoding, cb);
129
+ };
130
+
131
+ http.ServerResponse.prototype.end = function (chunk, encoding, cb) {
132
+ try {
133
+ var modified = maybeInject(this, chunk);
134
+ if (modified !== chunk) {
135
+ var enc = typeof encoding === 'function' ? 'utf8' : encoding;
136
+ var callback = typeof encoding === 'function' ? encoding : cb;
137
+ try {
138
+ if (this.getHeader('content-length')) {
139
+ this.setHeader('content-length', Buffer.byteLength(modified));
140
+ }
141
+ } catch (_) { /* headers already sent */ }
142
+ return _origEnd.call(this, modified, enc, callback);
143
+ }
144
+ } catch (_) { /* never break the app */ }
145
+ return _origEnd.call(this, chunk, encoding, cb);
146
+ };
147
+
148
+ console.log('[securenow] Free trial banner injection enabled');
149
+ } catch (err) {
150
+ console.warn('[securenow] Could not setup free trial banner:', err.message);
151
+ }
152
+ }
153
+
154
+ module.exports = { isFreeTrial, patchHttpForBanner, BANNER_SCRIPT };
package/nextjs.d.ts CHANGED
@@ -51,7 +51,7 @@ export interface RegisterOptions {
51
51
  * export function register() {
52
52
  * registerSecureNow({
53
53
  * serviceName: 'my-nextjs-app',
54
- * endpoint: 'http://signoz.company.com:4318',
54
+ * endpoint: 'http://your-otlp-backend.example.com:4318',
55
55
  * noUuid: true,
56
56
  * });
57
57
  * }
package/nextjs.js CHANGED
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * 2. Set environment variables:
16
16
  * SECURENOW_APPID=my-nextjs-app
17
- * SECURENOW_INSTANCE=http://your-signoz-host:4318
17
+ * SECURENOW_INSTANCE=http://your-otlp-backend:4318
18
18
  *
19
19
  * That's it! 🎉 No webpack warnings!
20
20
  */
@@ -261,6 +261,7 @@ function registerSecureNow(options = {}) {
261
261
  ).replace(/\/$/, '');
262
262
 
263
263
  const tracesUrl = env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
264
+ const logsUrl = env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') || `${endpointBase}/v1/logs`;
264
265
 
265
266
  // Set environment variables for @vercel/otel to pick up
266
267
  process.env.OTEL_SERVICE_NAME = serviceName;
@@ -504,9 +505,69 @@ function registerSecureNow(options = {}) {
504
505
 
505
506
  sdk.start();
506
507
  console.log('[securenow] 🎯 Vanilla SDK initialized for self-hosted environment');
508
+
509
+ // -------- Logging (self-hosted only) --------
510
+ const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) === '1' || String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() === 'true';
511
+ if (loggingEnabled) {
512
+ try {
513
+ const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
514
+ const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
515
+
516
+ const logExporter = new OTLPLogExporter({
517
+ url: logsUrl,
518
+ headers: parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS')),
519
+ });
520
+
521
+ const loggerProvider = new LoggerProvider({
522
+ resource: new Resource({
523
+ [SemanticResourceAttributes.SERVICE_NAME]: serviceName,
524
+ [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: env('NODE_ENV') || 'production',
525
+ }),
526
+ });
527
+ loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
528
+
529
+ // Patch console to forward logs as OTLP log records
530
+ const logger = loggerProvider.getLogger('console', '1.0.0');
531
+ const SeverityNumber = { INFO: 9, WARN: 13, ERROR: 17 };
532
+ const origLog = console.log;
533
+ const origWarn = console.warn;
534
+ const origError = console.error;
535
+
536
+ console.log = (...args) => {
537
+ origLog.apply(console, args);
538
+ try { logger.emit({ severityNumber: SeverityNumber.INFO, severityText: 'INFO', body: args.map(String).join(' ') }); } catch (_) {}
539
+ };
540
+ console.warn = (...args) => {
541
+ origWarn.apply(console, args);
542
+ try { logger.emit({ severityNumber: SeverityNumber.WARN, severityText: 'WARN', body: args.map(String).join(' ') }); } catch (_) {}
543
+ };
544
+ console.error = (...args) => {
545
+ origError.apply(console, args);
546
+ try { logger.emit({ severityNumber: SeverityNumber.ERROR, severityText: 'ERROR', body: args.map(String).join(' ') }); } catch (_) {}
547
+ };
548
+
549
+ console.log('[securenow] 📋 Logging: ENABLED → %s', logsUrl);
550
+
551
+ // Graceful shutdown for logs
552
+ const origShutdown = sdk.shutdown?.bind(sdk);
553
+ process.on('SIGTERM', async () => { try { await loggerProvider.shutdown(); } catch (_) {} });
554
+ process.on('SIGINT', async () => { try { await loggerProvider.shutdown(); } catch (_) {} });
555
+ } catch (e) {
556
+ console.warn('[securenow] ⚠️ Logging setup failed (missing @opentelemetry/exporter-logs-otlp-http or @opentelemetry/sdk-logs):', e.message);
557
+ }
558
+ } else {
559
+ console.log('[securenow] 📋 Logging: DISABLED (set SECURENOW_LOGGING_ENABLED=1 to enable)');
560
+ }
507
561
  }
508
562
 
509
563
  isRegistered = true;
564
+
565
+ // Free trial banner
566
+ const { isFreeTrial, patchHttpForBanner } = require('./free-trial-banner');
567
+ if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
568
+ patchHttpForBanner();
569
+ }
570
+
510
571
  console.log('[securenow] ✅ OpenTelemetry started for Next.js → %s', tracesUrl);
511
572
  console.log('[securenow] 📊 Auto-capturing comprehensive request metadata:');
512
573
  console.log('[securenow] • IP addresses (x-forwarded-for, x-real-ip, socket)');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "5.0.5",
4
- "description": "OpenTelemetry instrumentation for Node.js and Next.js - Send traces and logs to SigNoz or any OTLP backend",
3
+ "version": "5.2.1",
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",
7
7
  "types": "register.d.ts",
@@ -22,7 +22,6 @@
22
22
  "monitoring",
23
23
  "nextjs",
24
24
  "next.js",
25
- "signoz",
26
25
  "instrumentation",
27
26
  "telemetry",
28
27
  "distributed-tracing",
@@ -85,6 +84,7 @@
85
84
  "nextjs-wrapper.js",
86
85
  "nextjs-wrapper.d.ts",
87
86
  "cli.js",
87
+ "free-trial-banner.js",
88
88
  "postinstall.js",
89
89
  "register-vite.js",
90
90
  "web-vite.mjs",
package/postinstall.js CHANGED
@@ -53,7 +53,7 @@ export function register() {
53
53
  * SECURENOW_APPID=my-nextjs-app
54
54
  *
55
55
  * Optional:
56
- * SECURENOW_INSTANCE=http://your-signoz-server:4318
56
+ * SECURENOW_INSTANCE=http://your-otlp-backend:4318
57
57
  * OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
58
58
  * OTEL_LOG_LEVEL=info
59
59
  */
@@ -77,7 +77,7 @@ export function register() {
77
77
  * SECURENOW_APPID=my-nextjs-app
78
78
  *
79
79
  * Optional:
80
- * SECURENOW_INSTANCE=http://your-signoz-server:4318
80
+ * SECURENOW_INSTANCE=http://your-otlp-backend:4318
81
81
  * OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
82
82
  * OTEL_LOG_LEVEL=info
83
83
  *
@@ -150,9 +150,9 @@ function createEnvTemplate(targetPath) {
150
150
  # Required: Your application identifier
151
151
  SECURENOW_APPID=my-nextjs-app
152
152
 
153
- # Optional: Your SigNoz/OpenTelemetry collector endpoint
153
+ # Optional: Your OTLP-compatible backend / collector endpoint
154
154
  # Default: https://freetrial.securenow.ai:4318
155
- SECURENOW_INSTANCE=http://your-signoz-server:4318
155
+ SECURENOW_INSTANCE=http://your-otlp-backend:4318
156
156
 
157
157
  # Optional: API key or authentication headers
158
158
  # OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-api-key-here"
@@ -270,14 +270,14 @@ async function setup() {
270
270
  console.log('│ │');
271
271
  console.log('│ 1. Edit .env.local and set: │');
272
272
  console.log('│ SECURENOW_APPID=your-app-name │');
273
- console.log('│ SECURENOW_INSTANCE=http://signoz:4318 │');
273
+ console.log('│ SECURENOW_INSTANCE=http://your-otlp-backend:4318 │');
274
274
  if (shouldCreateMiddleware) {
275
275
  console.log('│ SECURENOW_CAPTURE_BODY=1 │');
276
276
  }
277
277
  console.log('│ │');
278
278
  console.log('│ 2. Run your app: npm run dev │');
279
279
  console.log('│ │');
280
- console.log('│ 3. Check SigNoz for traces! │');
280
+ console.log('│ 3. Check SecureNow for traces! │');
281
281
  console.log('│ │');
282
282
  if (shouldCreateMiddleware) {
283
283
  console.log('│ 📝 Body capture enabled with auto-redaction │');
package/register.d.ts CHANGED
@@ -56,7 +56,7 @@ export {};
56
56
  * node_args: '-r securenow/register',
57
57
  * env: {
58
58
  * SECURENOW_APPID: 'my-app',
59
- * SECURENOW_INSTANCE: 'http://signoz:4318',
59
+ * SECURENOW_INSTANCE: 'http://your-otlp-backend:4318',
60
60
  * SECURENOW_CAPTURE_BODY: '1',
61
61
  * }
62
62
  * }]
package/tracing.d.ts CHANGED
@@ -112,7 +112,7 @@ export interface LoggerProvider {
112
112
  }
113
113
 
114
114
  /**
115
- * Get a logger instance for sending structured logs to SigNoz
115
+ * Get a logger instance for sending structured logs to any OTLP-compatible backend
116
116
  *
117
117
  * @param name - Logger name (e.g., 'my-service', 'auth-module')
118
118
  * @param version - Logger version (optional, defaults to '1.0.0')
package/tracing.js CHANGED
@@ -303,6 +303,12 @@ const sdk = new NodeSDK({
303
303
  const tracer = api.trace.getTracer('securenow-smoke');
304
304
  const span = tracer.startSpan('securenow.startup.smoke'); span.end();
305
305
  }
306
+
307
+ // Free trial banner
308
+ const { isFreeTrial, patchHttpForBanner } = require('./free-trial-banner');
309
+ if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
310
+ patchHttpForBanner();
311
+ }
306
312
  } catch (e) {
307
313
  console.error('[securenow] OTel start failed:', e && e.stack || e);
308
314
  }
package/web-vite.mjs CHANGED
@@ -144,9 +144,70 @@ export function startSecurenowWeb() {
144
144
  console.log('[securenow] Web OTel started → %s', tracesUrl);
145
145
  }
146
146
 
147
+ // ---- Free trial banner (browser DOM injection) ----
148
+ function injectFreeTrialBanner(): void {
149
+ const FREETRIAL_HOST = 'freetrial.securenow.ai';
150
+ const hideBanner = String(env('SECURENOW_HIDE_BANNER')) === '1';
151
+ if (hideBanner || !endpointBase.includes(FREETRIAL_HOST)) return;
152
+ if (typeof document === 'undefined') return;
153
+
154
+ function create(): void {
155
+ if (document.getElementById('sn-ft-banner')) return;
156
+
157
+ const d = document.createElement('div');
158
+ d.id = 'sn-ft-banner';
159
+ d.style.cssText =
160
+ 'position:fixed;top:0;left:0;right:0;z-index:2147483647;' +
161
+ 'background:#FEF3CD;color:#856404;padding:10px 16px;' +
162
+ 'font-family:system-ui,-apple-system,sans-serif;font-size:13px;' +
163
+ 'text-align:center;border-bottom:2px solid #FFE69C;' +
164
+ 'display:flex;align-items:center;justify-content:center;gap:6px;' +
165
+ 'box-shadow:0 2px 8px rgba(0,0,0,0.12)';
166
+
167
+ const icon = document.createElement('span');
168
+ icon.textContent = '\u26a0\ufe0f';
169
+ d.appendChild(icon);
170
+
171
+ const msg = document.createElement('span');
172
+ const strong = document.createElement('strong');
173
+ strong.textContent = 'Testing Environment:';
174
+ msg.appendChild(strong);
175
+ msg.appendChild(document.createTextNode(
176
+ ' Only add test applications. For production usage, please '
177
+ ));
178
+
179
+ const link = document.createElement('a');
180
+ link.href = 'https://securenow.ai/contact';
181
+ link.target = '_blank';
182
+ link.rel = 'noopener';
183
+ link.style.cssText = 'color:#664D03;font-weight:600;text-decoration:underline';
184
+ link.textContent = 'contact our team for a demo';
185
+ msg.appendChild(link);
186
+ msg.appendChild(document.createTextNode('.'));
187
+ d.appendChild(msg);
188
+
189
+ const close = document.createElement('button');
190
+ close.textContent = '\u00d7';
191
+ close.style.cssText =
192
+ 'background:none;border:none;color:#856404;font-size:18px;' +
193
+ 'cursor:pointer;margin-left:12px;padding:0 4px;line-height:1';
194
+ close.onclick = () => { d.style.display = 'none'; };
195
+ d.appendChild(close);
196
+
197
+ document.body.prepend(d);
198
+ }
199
+
200
+ if (document.readyState === 'loading') {
201
+ document.addEventListener('DOMContentLoaded', create);
202
+ } else {
203
+ create();
204
+ }
205
+ }
206
+
147
207
  // Auto-start
148
208
  try {
149
209
  startSecurenowWeb();
210
+ injectFreeTrialBanner();
150
211
  } catch (e: any) {
151
212
  if (String(e?.message) !== '__SECURENOW_NO_START__') {
152
213
  console.error('[securenow/web-vite] failed to start:', e);