2025-05-07 23:04:54 +00:00
# Implementation Hints and Learnings
## SmartProxy Usage
2025-05-16 15:50:46 +00:00
### New Route-Based Architecture (v18+)
- SmartProxy now uses a route-based configuration system
- Routes define match criteria and actions instead of simple port-to-port forwarding
- All traffic types (HTTP, HTTPS, TCP, WebSocket) are configured through routes
2025-05-07 23:04:54 +00:00
```typescript
2025-05-16 15:50:46 +00:00
// NEW: Route-based SmartProxy configuration
2025-05-07 23:04:54 +00:00
const smartProxy = new plugins.smartproxy.SmartProxy({
2025-05-16 15:50:46 +00:00
routes: [
{
name: 'https-traffic',
match: {
ports: 443,
domains: ['example.com', '*.example.com']
},
action: {
type: 'forward',
target: {
host: 'backend.server.com',
port: 8080
}
},
tls: {
mode: 'terminate',
certificate: 'auto'
}
}
],
defaults: {
target: {
host: 'fallback.server.com',
port: 8080
}
},
2025-05-07 23:04:54 +00:00
acme: {
2025-05-16 15:50:46 +00:00
accountEmail: 'admin@example .com',
2025-05-07 23:04:54 +00:00
enabled: true,
2025-05-16 15:50:46 +00:00
useProduction: true
}
2025-05-07 23:04:54 +00:00
});
```
2025-05-16 15:50:46 +00:00
### Migration from Old to New
```typescript
// OLD configuration style (deprecated)
{
fromPort: 443,
toPort: 8080,
targetIP: 'backend.server.com',
domainConfigs: [...]
}
// NEW route-based style
{
routes: [{
name: 'main-route',
match: { ports: 443 },
action: {
type: 'forward',
target: { host: 'backend.server.com', port: 8080 }
}
}]
}
```
### Direct Component Usage
- Use SmartProxy components directly instead of creating your own wrappers
- SmartProxy already includes Port80Handler and NetworkProxy functionality
- When using SmartProxy, configure it directly rather than instantiating Port80Handler or NetworkProxy separately
2025-05-07 23:04:54 +00:00
### Certificate Management
- SmartProxy has built-in ACME certificate management
- Configure it in the `acme` property of SmartProxy options
- Use `accountEmail` (not `email` ) for the ACME contact email
- SmartProxy handles both HTTP-01 challenges and certificate application automatically
## qenv Usage
### Direct Usage
- Use qenv directly instead of creating environment variable wrappers
- Instantiate qenv with appropriate basePath and nogitPath:
```typescript
const qenv = new plugins.qenv.Qenv('./', '.nogit/');
const value = await qenv.getEnvVarOnDemand('ENV_VAR_NAME');
```
## TypeScript Interfaces
### SmartProxy Interfaces
- Always check the interfaces from the node_modules to ensure correct property names
2025-05-16 15:50:46 +00:00
- Important interfaces for the new architecture:
- `ISmartProxyOptions` : Main configuration with `routes` array
- `IRouteConfig` : Individual route configuration
- `IRouteMatch` : Match criteria for routes
- `IRouteTarget` : Target configuration for forwarding
2025-05-07 23:04:54 +00:00
- `IAcmeOptions` : ACME certificate configuration
2025-05-16 15:50:46 +00:00
- `TTlsMode` : TLS handling modes ('passthrough' | 'terminate' | 'terminate-and-reencrypt')
### New Route Configuration
```typescript
interface IRouteConfig {
name: string;
match: {
ports: number | number[];
domains?: string | string[];
path?: string;
headers?: Record< string , string | RegExp > ;
};
action: {
type: 'forward' | 'redirect' | 'block' | 'static';
target?: {
host: string | string[] | ((context) => string);
port: number | 'preserve' | ((context) => number);
};
};
tls?: {
mode: TTlsMode;
certificate?: 'auto' | { key: string; cert: string; };
};
security?: {
authentication?: IRouteAuthentication;
rateLimit?: IRouteRateLimit;
ipAllowList?: string[];
ipBlockList?: string[];
};
}
```
2025-05-07 23:04:54 +00:00
### Required Properties
2025-05-16 15:50:46 +00:00
- For `ISmartProxyOptions` , `routes` array is the main configuration
2025-05-07 23:04:54 +00:00
- For `IAcmeOptions` , use `accountEmail` for the contact email
2025-05-16 15:50:46 +00:00
- Routes must have `name` , `match` , and `action` properties
2025-05-07 23:04:54 +00:00
## Testing
### Test Structure
- Follow the project's test structure, using `@push.rocks/tapbundle`
- Use `expect(value).toEqual(expected)` for equality checks
- Use `expect(value).toBeTruthy()` for boolean assertions
```typescript
tap.test('test description', async () => {
const result = someFunction();
expect(result.property).toEqual('expected value');
expect(result.valid).toBeTruthy();
});
```
### Cleanup
- Include a cleanup test to ensure proper test resource handling
- Add a `stop` test to forcefully end the test when needed:
```typescript
tap.test('stop', async () => {
await tap.stopForcefully();
});
```
## Architecture Principles
### Simplicity
- Prefer direct usage of libraries instead of creating wrappers
- Don't reinvent functionality that already exists in dependencies
- Keep interfaces clean and focused, avoiding unnecessary abstraction layers
### Component Integration
- Leverage built-in integrations between components (like SmartProxy's ACME handling)
- Use parallel operations for performance (like in the `stop()` method)
2025-05-16 15:50:46 +00:00
- Separate concerns clearly (HTTP handling vs. SMTP handling)
## Email Integration with SmartProxy
### Architecture
- Email traffic is routed through SmartProxy using automatic route generation
- Email server runs on internal ports and receives forwarded traffic from SmartProxy
- SmartProxy handles external ports (25, 587, 465) and forwards to internal ports
### Email Route Generation
```typescript
// Email configuration automatically generates SmartProxy routes
emailConfig: {
ports: [25, 587, 465],
hostname: 'mail.example.com',
domainRules: [...]
}
// Generates routes like:
{
name: 'smtp-route',
match: { ports: [25] },
action: {
type: 'forward',
target: { host: 'localhost', port: 10025 }
},
tls: { mode: 'passthrough' } // STARTTLS handled by email server
}
```
### Port Mapping
- External port 25 → Internal port 10025 (SMTP)
- External port 587 → Internal port 10587 (Submission)
- External port 465 → Internal port 10465 (SMTPS)
### TLS Handling
- Ports 25 and 587: Use 'passthrough' mode (STARTTLS handled by email server)
- Port 465: Use 'terminate' mode (SmartProxy handles TLS termination)
2025-05-23 19:03:44 +00:00
- Domain-specific TLS can be configured per email rule
## SMTP Test Migration
### Test Framework
- Tests migrated from custom framework to @push .rocks/tapbundle
- Each test file is self-contained with its own server lifecycle management
- Test files use pattern `test.*.ts` for automatic discovery by tstest
### Server Lifecycle
- SMTP server uses `listen()` method to start (not `start()` )
- SMTP server uses `close()` method to stop (not `stop()` or `destroy()` )
- Server loader module manages server lifecycle for tests
### Test Structure
```typescript
import { expect, tap } from '@push .rocks/tapbundle';
import { startTestServer, stopTestServer } from '../server.loader.js';
const TEST_PORT = 2525;
const TEST_TIMEOUT = 10000;
tap.test('prepare server', async () => {
await startTestServer();
await new Promise(resolve => setTimeout(resolve, 100));
});
tap.test('test name', async (tools) => {
const done = tools.defer();
// test implementation
done.resolve();
});
tap.test('cleanup server', async () => {
await stopTestServer();
});
tap.start();
```
### Common Issues and Solutions
1. **Multi-line SMTP responses** : Handle response buffering carefully, especially for EHLO
2. **Timing issues** : Use proper state management instead of string matching
3. **ES Module imports** : Use `import` statements, not `require()`
4. **Server cleanup** : Always close connections properly to avoid hanging tests
5. **Response buffer management** : Clear the response buffer after processing each state to avoid false matches from previous responses. Use specific response patterns (e.g., '250 OK' instead of just '250') to avoid ambiguity.
### SMTP Protocol Testing
- Server generates self-signed certificates automatically for testing
- Default test port is 2525
- Connection timeout is typically 10 seconds
- Always check for complete SMTP responses (ending with space after code)