devfortress-sdk 4.2.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.
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DevFortress CLI Init Tool
4
+ *
5
+ * Usage: npx devfortress-init
6
+ *
7
+ * Scaffolds DevFortress SDK integration into your project:
8
+ * 1. Detects framework (Express, Next.js, Fastify, Hono)
9
+ * 2. Creates devfortress.config.ts with zero-config defaults
10
+ * 3. Shows integration snippet for your framework
11
+ * 4. Target: under 3 minutes from install to first event
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const readline = require('readline');
17
+
18
+ const rl = readline.createInterface({
19
+ input: process.stdin,
20
+ output: process.stdout,
21
+ });
22
+
23
+ function ask(question) {
24
+ return new Promise(resolve => {
25
+ rl.question(question, resolve);
26
+ });
27
+ }
28
+
29
+ function detectFramework() {
30
+ try {
31
+ const pkg = JSON.parse(
32
+ fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')
33
+ );
34
+ const deps = {
35
+ ...pkg.dependencies,
36
+ ...pkg.devDependencies,
37
+ };
38
+ if (deps.next) return 'nextjs';
39
+ if (deps.express) return 'express';
40
+ if (deps.fastify) return 'fastify';
41
+ if (deps.hono) return 'hono';
42
+ } catch {
43
+ /* no package.json */
44
+ }
45
+ return 'unknown';
46
+ }
47
+
48
+ const SNIPPETS = {
49
+ express: `// In your Express app entry point (e.g. app.ts or server.ts)
50
+ import { devFortressMiddleware } from 'devfortress-sdk';
51
+
52
+ // Add before your routes:
53
+ app.use(devFortressMiddleware({
54
+ apiKey: process.env.DEVFORTRESS_API_KEY!,
55
+ debug: process.env.NODE_ENV !== 'production',
56
+ }));`,
57
+
58
+ nextjs: `// In your Next.js middleware.ts (root of project)
59
+ import df from 'devfortress-sdk/quick';
60
+
61
+ const client = df.init({
62
+ apiKey: process.env.DEVFORTRESS_API_KEY!,
63
+ debug: process.env.NODE_ENV !== 'production',
64
+ });
65
+
66
+ // In your API routes:
67
+ export async function POST(req: NextRequest) {
68
+ await client.observe(req);
69
+ // ... your handler
70
+ }`,
71
+
72
+ fastify: `// In your Fastify server setup
73
+ import df from 'devfortress-sdk/quick';
74
+
75
+ const client = df.init({
76
+ apiKey: process.env.DEVFORTRESS_API_KEY!,
77
+ debug: process.env.NODE_ENV !== 'production',
78
+ });
79
+
80
+ fastify.addHook('onRequest', async (request) => {
81
+ await client.observe(request.raw);
82
+ });`,
83
+
84
+ hono: `// In your Hono app
85
+ import df from 'devfortress-sdk/quick';
86
+
87
+ const client = df.init({
88
+ apiKey: process.env.DEVFORTRESS_API_KEY!,
89
+ debug: process.env.NODE_ENV !== 'production',
90
+ });
91
+
92
+ app.use('*', async (c, next) => {
93
+ await client.observe(c.req.raw);
94
+ await next();
95
+ });`,
96
+
97
+ unknown: `// Generic integration
98
+ import df from 'devfortress-sdk/quick';
99
+
100
+ const client = df.init({
101
+ apiKey: process.env.DEVFORTRESS_API_KEY!,
102
+ debug: true,
103
+ });
104
+
105
+ // Call observe() on every inbound request:
106
+ await client.observe(request);`,
107
+ };
108
+
109
+ async function main() {
110
+ console.log('');
111
+ console.log('🛡️ DevFortress SDK Setup');
112
+ console.log('─'.repeat(40));
113
+ console.log('');
114
+
115
+ const framework = detectFramework();
116
+ console.log(
117
+ `📦 Detected framework: ${framework === 'unknown' ? 'Could not detect' : framework}`
118
+ );
119
+ console.log('');
120
+
121
+ const apiKey = await ask(
122
+ '🔑 Enter your DevFortress API key (from dashboard): '
123
+ );
124
+ if (!apiKey || !apiKey.startsWith('df_')) {
125
+ console.log('');
126
+ console.log(
127
+ '⚠️ API key should start with "df_". Get one at https://devfortress.net/dashboard/settings/api-keys'
128
+ );
129
+ console.log('');
130
+ console.log('Continuing with placeholder...');
131
+ }
132
+
133
+ const privacy = await ask('🔒 Privacy mode? (standard/strict) [standard]: ');
134
+ const privacyMode = privacy === 'strict' ? 'strict' : 'standard';
135
+
136
+ // Create config file
137
+ const configContent = `/**
138
+ * DevFortress Configuration
139
+ * Generated by devfortress-init on ${new Date().toISOString().split('T')[0]}
140
+ */
141
+
142
+ import df from 'devfortress-sdk/quick';
143
+
144
+ export const devfortress = df.init({
145
+ apiKey: process.env.DEVFORTRESS_API_KEY || '${apiKey || 'df_your_api_key'}',
146
+ privacy: '${privacyMode}',
147
+ debug: process.env.NODE_ENV !== 'production',
148
+ });
149
+ `;
150
+
151
+ const configPath = path.join(process.cwd(), 'devfortress.config.ts');
152
+ if (fs.existsSync(configPath)) {
153
+ const overwrite = await ask(
154
+ `⚠️ ${configPath} exists. Overwrite? (y/n) [n]: `
155
+ );
156
+ if (overwrite !== 'y') {
157
+ console.log('Skipping config file creation.');
158
+ } else {
159
+ fs.writeFileSync(configPath, configContent);
160
+ console.log(`✅ Created ${configPath}`);
161
+ }
162
+ } else {
163
+ fs.writeFileSync(configPath, configContent);
164
+ console.log(`✅ Created ${configPath}`);
165
+ }
166
+
167
+ // Add env variable
168
+ const envPath = path.join(process.cwd(), '.env.local');
169
+ const envLine = `DEVFORTRESS_API_KEY=${apiKey || 'df_your_api_key'}`;
170
+ if (fs.existsSync(envPath)) {
171
+ const envContent = fs.readFileSync(envPath, 'utf8');
172
+ if (!envContent.includes('DEVFORTRESS_API_KEY')) {
173
+ fs.appendFileSync(envPath, `\n${envLine}\n`);
174
+ console.log('✅ Added DEVFORTRESS_API_KEY to .env.local');
175
+ } else {
176
+ console.log('ℹ️ DEVFORTRESS_API_KEY already in .env.local');
177
+ }
178
+ } else {
179
+ fs.writeFileSync(envPath, `${envLine}\n`);
180
+ console.log('✅ Created .env.local with DEVFORTRESS_API_KEY');
181
+ }
182
+
183
+ // Show integration snippet
184
+ console.log('');
185
+ console.log('📝 Integration snippet for your framework:');
186
+ console.log('─'.repeat(40));
187
+ console.log('');
188
+ console.log(SNIPPETS[framework] || SNIPPETS.unknown);
189
+ console.log('');
190
+ console.log('─'.repeat(40));
191
+ console.log('');
192
+ console.log('🚀 Next steps:');
193
+ console.log(' 1. Add the integration snippet to your app');
194
+ console.log(' 2. Start your development server');
195
+ console.log(' 3. Make a request — see it in your DevFortress dashboard');
196
+ console.log('');
197
+ console.log('📖 Full docs: https://devfortress.net/docs/developer-guide');
198
+ console.log(
199
+ '🔒 Privacy info: https://devfortress.net/privacy/data-collected'
200
+ );
201
+ console.log('');
202
+
203
+ rl.close();
204
+ }
205
+
206
+ main().catch(console.error);
@@ -0,0 +1,61 @@
1
+ /**
2
+ * DevFortress SDK - Browser Entry Point
3
+ *
4
+ * Lightweight browser-compatible client for sending security events
5
+ * from client-side applications. Uses fetch API instead of axios.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export type { DevFortressClientOptions, LiveThreatEvent, EventType, SeverityLevel, ApiResponse, } from './types';
10
+ import type { DevFortressClientOptions } from './types';
11
+ /** @deprecated Use DevFortressClientOptions instead */
12
+ export type BrowserClientOptions = DevFortressClientOptions;
13
+ export declare class DevFortressBrowserClient {
14
+ private apiKey;
15
+ private endpoint;
16
+ private timeout;
17
+ private retries;
18
+ private debug;
19
+ constructor(options: DevFortressClientOptions);
20
+ /**
21
+ * Track a security event from the browser
22
+ */
23
+ trackEvent(event: {
24
+ eventType: string;
25
+ ip?: string;
26
+ method?: string;
27
+ path?: string;
28
+ userAgent?: string;
29
+ statusCode?: number;
30
+ responseTime?: number;
31
+ metadata?: Record<string, unknown>;
32
+ severity?: string;
33
+ reason?: string;
34
+ timestamp?: string;
35
+ }): Promise<{
36
+ success: boolean;
37
+ eventId?: string;
38
+ message?: string;
39
+ }>;
40
+ /**
41
+ * Track a page error as a security event
42
+ */
43
+ trackError(error: Error, context?: Record<string, unknown>): void;
44
+ /**
45
+ * Track a failed API response
46
+ */
47
+ trackApiFailure(url: string, status: number, method?: string): void;
48
+ /**
49
+ * Install global error handler for automatic tracking
50
+ */
51
+ installGlobalErrorHandler(): () => void;
52
+ /**
53
+ * Test connection to the surveillance API
54
+ */
55
+ testConnection(): Promise<boolean>;
56
+ private sendWithRetry;
57
+ }
58
+ /**
59
+ * Create a pre-configured browser client instance
60
+ */
61
+ export declare function createBrowserClient(options: DevFortressClientOptions): DevFortressBrowserClient;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ /**
3
+ * DevFortress SDK - Browser Entry Point
4
+ *
5
+ * Lightweight browser-compatible client for sending security events
6
+ * from client-side applications. Uses fetch API instead of axios.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.DevFortressBrowserClient = void 0;
12
+ exports.createBrowserClient = createBrowserClient;
13
+ const DEFAULT_ENDPOINT = 'https://www.devfortress.net/api/events/ingest';
14
+ const DEFAULT_TIMEOUT = 5000;
15
+ const DEFAULT_RETRIES = 3;
16
+ class DevFortressBrowserClient {
17
+ constructor(options) {
18
+ if (!options.apiKey) {
19
+ throw new Error('DevFortressBrowserClient: apiKey is required');
20
+ }
21
+ this.apiKey = options.apiKey;
22
+ this.endpoint = options.endpoint || DEFAULT_ENDPOINT;
23
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
24
+ this.retries = options.retries || DEFAULT_RETRIES;
25
+ this.debug = options.debug || false;
26
+ // Warn if endpoint is not HTTPS
27
+ if (this.endpoint.startsWith('http://') &&
28
+ !this.endpoint.includes('localhost') &&
29
+ !this.endpoint.includes('127.0.0.1')) {
30
+ // eslint-disable-next-line no-console
31
+ console.warn('[DevFortress] WARNING: Using non-HTTPS endpoint. API key will be transmitted in cleartext.');
32
+ }
33
+ }
34
+ /**
35
+ * Track a security event from the browser
36
+ */
37
+ async trackEvent(event) {
38
+ const payload = {
39
+ ...event,
40
+ ip: event.ip || 'browser-client',
41
+ userAgent: event.userAgent ||
42
+ (typeof navigator !== 'undefined' ? navigator.userAgent : undefined),
43
+ timestamp: event.timestamp || new Date().toISOString(),
44
+ };
45
+ return this.sendWithRetry(payload, 1);
46
+ }
47
+ /**
48
+ * Track a page error as a security event
49
+ */
50
+ trackError(error, context) {
51
+ this.trackEvent({
52
+ eventType: 'custom',
53
+ reason: error.message,
54
+ metadata: {
55
+ stack: error.stack,
56
+ ...context,
57
+ },
58
+ severity: 'MEDIUM',
59
+ }).catch(() => {
60
+ // Silently fail - don't crash the app for telemetry
61
+ });
62
+ }
63
+ /**
64
+ * Track a failed API response
65
+ */
66
+ trackApiFailure(url, status, method = 'GET') {
67
+ let eventType = 'custom';
68
+ let severity = 'LOW';
69
+ if (status === 401 || status === 403) {
70
+ eventType = 'auth_failure';
71
+ severity = 'MEDIUM';
72
+ }
73
+ else if (status === 429) {
74
+ eventType = 'rate_limit_exceeded';
75
+ severity = 'MEDIUM';
76
+ }
77
+ else if (status >= 500) {
78
+ eventType = '5xx_error';
79
+ severity = 'HIGH';
80
+ }
81
+ else if (status >= 400) {
82
+ eventType = '4xx_error';
83
+ severity = 'LOW';
84
+ }
85
+ this.trackEvent({
86
+ eventType,
87
+ method,
88
+ path: url,
89
+ statusCode: status,
90
+ severity,
91
+ reason: `API ${method} ${url} returned ${status}`,
92
+ }).catch(() => {
93
+ // Silently fail
94
+ });
95
+ }
96
+ /**
97
+ * Install global error handler for automatic tracking
98
+ */
99
+ installGlobalErrorHandler() {
100
+ if (typeof window === 'undefined')
101
+ return () => { };
102
+ const handler = (event) => {
103
+ this.trackError(event.error || new Error(event.message), {
104
+ filename: event.filename,
105
+ lineno: event.lineno,
106
+ colno: event.colno,
107
+ });
108
+ };
109
+ const rejectionHandler = (event) => {
110
+ const error = event.reason instanceof Error
111
+ ? event.reason
112
+ : new Error(String(event.reason));
113
+ this.trackError(error, { type: 'unhandledRejection' });
114
+ };
115
+ window.addEventListener('error', handler);
116
+ window.addEventListener('unhandledrejection', rejectionHandler);
117
+ // Return cleanup function
118
+ return () => {
119
+ window.removeEventListener('error', handler);
120
+ window.removeEventListener('unhandledrejection', rejectionHandler);
121
+ };
122
+ }
123
+ /**
124
+ * Test connection to the surveillance API
125
+ */
126
+ async testConnection() {
127
+ try {
128
+ await this.trackEvent({
129
+ eventType: 'custom',
130
+ ip: 'browser-test',
131
+ metadata: { test: true },
132
+ });
133
+ return true;
134
+ }
135
+ catch {
136
+ return false;
137
+ }
138
+ }
139
+ async sendWithRetry(payload, attempt) {
140
+ const controller = new AbortController();
141
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
142
+ try {
143
+ const response = await fetch(this.endpoint, {
144
+ method: 'POST',
145
+ headers: {
146
+ 'Content-Type': 'application/json',
147
+ 'X-DevFortress-Key': this.apiKey,
148
+ },
149
+ body: JSON.stringify(payload),
150
+ signal: controller.signal,
151
+ });
152
+ clearTimeout(timeoutId);
153
+ if (!response.ok) {
154
+ // Don't retry on client errors
155
+ if (response.status < 500) {
156
+ const errorData = await response.json().catch(() => ({}));
157
+ throw new Error(errorData.message || `HTTP ${response.status}`);
158
+ }
159
+ throw new Error(`Server error: ${response.status}`);
160
+ }
161
+ return await response.json();
162
+ }
163
+ catch (error) {
164
+ clearTimeout(timeoutId);
165
+ if (attempt < this.retries) {
166
+ const backoff = Math.pow(2, attempt) * 500;
167
+ if (this.debug) {
168
+ // eslint-disable-next-line no-console
169
+ console.log(`[DevFortress] Retrying in ${backoff}ms (attempt ${attempt}/${this.retries})`);
170
+ }
171
+ await new Promise(resolve => setTimeout(resolve, backoff));
172
+ return this.sendWithRetry(payload, attempt + 1);
173
+ }
174
+ throw error;
175
+ }
176
+ }
177
+ }
178
+ exports.DevFortressBrowserClient = DevFortressBrowserClient;
179
+ /**
180
+ * Create a pre-configured browser client instance
181
+ */
182
+ function createBrowserClient(options) {
183
+ return new DevFortressBrowserClient(options);
184
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * DevFortress SDK — Platform Circuit Breaker
3
+ *
4
+ * Monitors connectivity to the DevFortress platform and provides
5
+ * automatic failover from external to internal closed-loop mode.
6
+ *
7
+ * Three states:
8
+ * - CLOSED: Platform is reachable, external CL is active
9
+ * - OPEN: Platform is unreachable, internal CL takes over
10
+ * - HALF-OPEN: Testing if platform has recovered
11
+ *
12
+ * Transition rules:
13
+ * CLOSED → OPEN: After `failureThreshold` consecutive failures
14
+ * OPEN → HALF-OPEN: After `recoveryTimeMs` elapsed
15
+ * HALF-OPEN → CLOSED: First successful call
16
+ * HALF-OPEN → OPEN: First failed call
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+ export type CircuitState = 'closed' | 'open' | 'half-open';
21
+ export interface CircuitBreakerConfig {
22
+ /** Number of consecutive failures before opening the circuit. Default: 3 */
23
+ failureThreshold?: number;
24
+ /** Time in ms to wait before testing recovery. Default: 60_000 (1 min) */
25
+ recoveryTimeMs?: number;
26
+ /** Callback when circuit state changes */
27
+ onStateChange?: (from: CircuitState, to: CircuitState, reason: string) => void;
28
+ }
29
+ export declare class PlatformCircuitBreaker {
30
+ private state;
31
+ private failureCount;
32
+ private lastFailureTime;
33
+ private failureThreshold;
34
+ private recoveryTimeMs;
35
+ private onStateChange?;
36
+ constructor(config?: CircuitBreakerConfig);
37
+ /**
38
+ * Check if external platform calls should be attempted.
39
+ * Returns true if the circuit allows external calls (CLOSED or HALF-OPEN).
40
+ */
41
+ shouldCallExternal(): boolean;
42
+ /**
43
+ * Record a successful external call.
44
+ */
45
+ recordSuccess(): void;
46
+ /**
47
+ * Record a failed external call.
48
+ */
49
+ recordFailure(): void;
50
+ /**
51
+ * Get current circuit state.
52
+ */
53
+ getState(): CircuitState;
54
+ /**
55
+ * Get diagnostics info.
56
+ */
57
+ getDiagnostics(): {
58
+ state: CircuitState;
59
+ failureCount: number;
60
+ lastFailureTime: number;
61
+ msUntilRecoveryAttempt: number;
62
+ };
63
+ /**
64
+ * Force reset the circuit breaker to closed state.
65
+ */
66
+ reset(): void;
67
+ private transition;
68
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ /**
3
+ * DevFortress SDK — Platform Circuit Breaker
4
+ *
5
+ * Monitors connectivity to the DevFortress platform and provides
6
+ * automatic failover from external to internal closed-loop mode.
7
+ *
8
+ * Three states:
9
+ * - CLOSED: Platform is reachable, external CL is active
10
+ * - OPEN: Platform is unreachable, internal CL takes over
11
+ * - HALF-OPEN: Testing if platform has recovered
12
+ *
13
+ * Transition rules:
14
+ * CLOSED → OPEN: After `failureThreshold` consecutive failures
15
+ * OPEN → HALF-OPEN: After `recoveryTimeMs` elapsed
16
+ * HALF-OPEN → CLOSED: First successful call
17
+ * HALF-OPEN → OPEN: First failed call
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.PlatformCircuitBreaker = void 0;
23
+ class PlatformCircuitBreaker {
24
+ constructor(config = {}) {
25
+ this.state = 'closed';
26
+ this.failureCount = 0;
27
+ this.lastFailureTime = 0;
28
+ this.failureThreshold = config.failureThreshold ?? 3;
29
+ this.recoveryTimeMs = config.recoveryTimeMs ?? 60000;
30
+ this.onStateChange = config.onStateChange;
31
+ }
32
+ /**
33
+ * Check if external platform calls should be attempted.
34
+ * Returns true if the circuit allows external calls (CLOSED or HALF-OPEN).
35
+ */
36
+ shouldCallExternal() {
37
+ if (this.state === 'closed')
38
+ return true;
39
+ if (this.state === 'open') {
40
+ // Check if recovery time has elapsed
41
+ if (Date.now() - this.lastFailureTime >= this.recoveryTimeMs) {
42
+ this.transition('half-open', 'recovery_timeout_elapsed');
43
+ return true; // Allow one test call
44
+ }
45
+ return false;
46
+ }
47
+ // half-open — allow the test call
48
+ return true;
49
+ }
50
+ /**
51
+ * Record a successful external call.
52
+ */
53
+ recordSuccess() {
54
+ if (this.state === 'half-open') {
55
+ this.transition('closed', 'test_call_succeeded');
56
+ }
57
+ this.failureCount = 0;
58
+ }
59
+ /**
60
+ * Record a failed external call.
61
+ */
62
+ recordFailure() {
63
+ this.failureCount++;
64
+ this.lastFailureTime = Date.now();
65
+ if (this.state === 'half-open') {
66
+ this.transition('open', 'test_call_failed');
67
+ return;
68
+ }
69
+ if (this.state === 'closed' && this.failureCount >= this.failureThreshold) {
70
+ this.transition('open', `${this.failureCount}_consecutive_failures`);
71
+ }
72
+ }
73
+ /**
74
+ * Get current circuit state.
75
+ */
76
+ getState() {
77
+ return this.state;
78
+ }
79
+ /**
80
+ * Get diagnostics info.
81
+ */
82
+ getDiagnostics() {
83
+ const msUntilRecovery = this.state === 'open'
84
+ ? Math.max(0, this.recoveryTimeMs - (Date.now() - this.lastFailureTime))
85
+ : 0;
86
+ return {
87
+ state: this.state,
88
+ failureCount: this.failureCount,
89
+ lastFailureTime: this.lastFailureTime,
90
+ msUntilRecoveryAttempt: msUntilRecovery,
91
+ };
92
+ }
93
+ /**
94
+ * Force reset the circuit breaker to closed state.
95
+ */
96
+ reset() {
97
+ this.transition('closed', 'manual_reset');
98
+ this.failureCount = 0;
99
+ this.lastFailureTime = 0;
100
+ }
101
+ transition(to, reason) {
102
+ const from = this.state;
103
+ if (from === to)
104
+ return;
105
+ this.state = to;
106
+ if (this.onStateChange) {
107
+ try {
108
+ this.onStateChange(from, to, reason);
109
+ }
110
+ catch {
111
+ // Callback errors should not break the circuit breaker
112
+ }
113
+ }
114
+ }
115
+ }
116
+ exports.PlatformCircuitBreaker = PlatformCircuitBreaker;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * DevFortress API Client
3
+ * Core client for sending events to DevFortress surveillance API
4
+ */
5
+ import type { DevFortressClientOptions, LiveThreatEvent, ApiResponse } from './types';
6
+ export declare class DevFortressClient {
7
+ private apiKey;
8
+ private endpoint;
9
+ private timeout;
10
+ private retries;
11
+ private debug;
12
+ private axios;
13
+ constructor(options: DevFortressClientOptions);
14
+ /**
15
+ * Track a security event
16
+ */
17
+ trackEvent(event: LiveThreatEvent): Promise<ApiResponse>;
18
+ /**
19
+ * Send event with automatic retry logic
20
+ */
21
+ private sendWithRetry;
22
+ /**
23
+ * Test connection to surveillance API
24
+ */
25
+ testConnection(): Promise<boolean>;
26
+ }