platformservice/readme.hints.md

5.8 KiB

Implementation Hints and Learnings

SmartProxy Usage

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
// NEW: Route-based SmartProxy configuration
const smartProxy = new plugins.smartproxy.SmartProxy({
  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
    }
  },
  acme: {
    accountEmail: 'admin@example.com',
    enabled: true,
    useProduction: true
  }
});

Migration from Old to New

// 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

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:
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
  • 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
    • IAcmeOptions: ACME certificate configuration
    • TTlsMode: TLS handling modes ('passthrough' | 'terminate' | 'terminate-and-reencrypt')

New Route Configuration

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[];
  };
}

Required Properties

  • For ISmartProxyOptions, routes array is the main configuration
  • For IAcmeOptions, use accountEmail for the contact email
  • Routes must have name, match, and action properties

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
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:
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)
  • 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

// 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)
  • Domain-specific TLS can be configured per email rule