Skip to content

Screenflow Service - Infrastructure

Infrastructure setup

Databases

Uses Prisma with PostgreSQL.

Schema Location: services/screenflow/prisma/schema.prisma

bash
docker compose -f 'docker-compose.yml' up -d --build 'databases'

to stop:

bash
docker compose -f 'docker-compose.yml' down 'databases'

Prisma Commands

bash
nx run screenflow:prisma:generate # generate types
nx run screenflow:prisma:reset # reset the database depending on the variable DATABASE_URL

Performing a Migration

  1. Modify the schema.prisma file of the service you're working with
  2. Trigger the nx command migrate:dev:create, which will automatically generate the migration file within the /migrations folder. Check the result as this command can be dangerous to merge/roll back
  3. Run the migration with the nx command migrate:dev
  4. Generate the schemas with the nx command prisma:generate.
  5. Obviously test your changes

You can then create your PR.

For deployment to the environments, you'll notice in the Dockerfile that a script entrypoint.sh is triggered. This script will automatically deploy your migration, meaning your migration will automatically be applied across the environments once the service starts.

Valkey (queues & event system)

bash
docker compose -f 'docker-compose.yml' up -d --build 'valkey'

to stop:

bash
docker compose -f 'docker-compose.yml' down 'valkey'

Architecture & Connections

The ScreenFlow service acts as an intermediary layer between the frontend, Guacamole service, and remote Windows instances. The following diagram details all connections:

Connection Flow Diagram

mermaid
graph TB
    subgraph Frontend["Frontend (Browser)"]
        FE_HTTP[HTTP/REST API Client]
        FE_WS[WebSocket Client<br/>RDP Tunnel]
        FE_SOCKET[Socket.IO Client<br/>Events]
    end

    subgraph ScreenFlow["ScreenFlow Service"]
        SF_API[REST API<br/>Controllers]
        SF_WS_GATEWAY[WebSocket Gateway<br/>/ws/guacamole-tunnel/:id]
        SF_WS_PROXY[WebSocket Proxy Service]
        SF_SOCKET[Socket.IO Gateway<br/>Events]
        SF_GUAC_CLIENT[Guacamole API Client]
    end

    subgraph Guacamole["Guacamole Service"]
        GUAC_API[REST API<br/>/guacamole/api]
        GUAC_WS[WebSocket Tunnel<br/>/websocket-tunnel]
        GUACD[guacd Daemon<br/>RDP Handler]
        GUAC_DB[(Guacamole DB)]
    end

    subgraph Instances["Windows Instances (EC2)"]
        INST_RDP[RDP Server<br/>Port 3389]
        INST_API[Windows Instance API<br/>Port 8000]
    end

    %% Frontend to ScreenFlow
    FE_HTTP -->|HTTPS<br/>REST API| SF_API
    FE_WS -->|WSS<br/>guacamole subprotocol| SF_WS_GATEWAY
    FE_SOCKET -->|WSS<br/>Socket.IO| SF_SOCKET

    %% ScreenFlow to Guacamole
    SF_API -->|HTTP/HTTPS<br/>Connection Management| GUAC_API
    SF_WS_PROXY -->|WSS<br/>guacamole subprotocol| GUAC_WS
    SF_GUAC_CLIENT -->|HTTP/HTTPS<br/>API Calls| GUAC_API
    SF_WS_GATEWAY --> SF_WS_PROXY

    %% Guacamole to Instances
    GUAC_WS --> GUACD
    GUACD -->|RDP<br/>TCP:3389| INST_RDP
    GUAC_API --> GUAC_DB

    %% ScreenFlow to Instances
    SF_API -->|HTTP/HTTPS<br/>Screenshots/Scripts| INST_API

    %% Styling
    classDef frontend fill:#e1f5ff,stroke:#01579b,stroke-width:2px
    classDef screenflow fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef guacamole fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
    classDef instances fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px
    classDef database fill:#fce4ec,stroke:#880e4f,stroke-width:2px

    class FE_HTTP,FE_WS,FE_SOCKET frontend
    class SF_API,SF_WS_GATEWAY,SF_WS_PROXY,SF_SOCKET,SF_GUAC_CLIENT screenflow
    class GUAC_API,GUAC_WS,GUACD guacamole
    class INST_RDP,INST_API instances
    class GUAC_DB database

Detailed Connections

1. Frontend → ScreenFlow

HTTP/REST API (HTTPS)

  • Protocol: HTTP/HTTPS
  • Purpose: All REST API calls for managing instances, workflows, tasks, attempts, and commands
  • Client: typedScreenflowClient (generated from OpenAPI spec) and screenflowClient (Axios-based)
  • Base URL: Configured via SCREENFLOW_API_DOMAIN environment variable
  • Authentication: Cookie-based (credentials: 'include')
  • Endpoints: All CRUD operations for screenflow resources (instances, workflows, runs, tasks, attempts, commands, etc.)

WebSocket (WSS) - RDP Tunnel

  • Protocol: WebSocket Secure (WSS) with 'guacamole' subprotocol
  • Purpose: Real-time RDP connection tunnel for remote desktop access
  • Endpoint: /ws/guacamole-tunnel/{instanceId}
  • Client: GuacamoleWebSocketService in frontend
  • Implementation: Uses Guacamole JavaScript client library to establish WebSocket connection
  • Flow: Frontend connects to ScreenFlow WebSocket gateway, which proxies to Guacamole

Socket.IO (WSS)

  • Protocol: Socket.IO over WebSocket Secure
  • Purpose: Real-time event notifications (command recorded, command created)
  • Client: screenflowSocket (Socket.IO client)
  • Events:
    • COMMAND_RECORDED: Broadcasts when a command is recorded during RDP session
    • COMMAND_CREATED: Broadcasts when a new command is created
  • Authentication: Token-based authentication with expiration handling

2. ScreenFlow → Guacamole

HTTP REST API (HTTP/HTTPS)

  • Protocol: HTTP/HTTPS
  • Purpose: Guacamole API operations (authentication, connection management)
  • Client: GuacamoleApiClientService (generated OpenAPI client)
  • Base URL: Configured via GUACAMOLE_URL environment variable (e.g., http://localhost:8080/guacamole)
  • Operations:
    • generateToken: Authenticate and get admin auth token
    • createConnection: Create RDP connection configuration in Guacamole
    • getConnections: List connections by instance ID
    • getConnectionDetails: Get specific connection details
    • deleteConnection: Remove connection configuration
  • Authentication: Admin username/password stored in config (guacamoleAdminUsername, guacamoleAdminPassword)
  • Connection Parameters: When creating connections, ScreenFlow provides:
    • hostname: Private IP address of the EC2 instance
    • port: 3389 (standard RDP port)
    • username: Screenflow instance username (from config)
    • password: Screenflow instance password (from config)
    • protocol: RDP
    • Various RDP-specific parameters (security, display settings, etc.)

WebSocket (WSS) - RDP Tunnel Proxy

  • Protocol: WebSocket Secure (WSS) with 'guacamole' subprotocol
  • Purpose: Proxy RDP traffic between frontend and Guacamole
  • Endpoint: {GUACAMOLE_URL}/websocket-tunnel?token={token}&GUAC_DATA_SOURCE={dataSource}&GUAC_ID={connectionId}&GUAC_TYPE=c
  • Implementation: GuacamoleWebSocketProxyService creates bidirectional proxy
  • Flow:
    1. Frontend connects to ScreenFlow at /ws/guacamole-tunnel/{instanceId}
    2. ScreenFlow looks up Guacamole connection by instanceId
    3. ScreenFlow authenticates with Guacamole and gets connection ID
    4. ScreenFlow establishes WebSocket connection to Guacamole's websocket-tunnel endpoint
    5. ScreenFlow proxies all messages bidirectionally between frontend and Guacamole
    6. ScreenFlow also parses and records input commands for analytics

3. Guacamole → Instances

RDP Protocol (TCP)

  • Protocol: Remote Desktop Protocol (RDP) over TCP
  • Port: 3389 (standard RDP port)
  • Purpose: Direct RDP connection to Windows EC2 instances
  • Connection Details:
    • Hostname: Private IP address of the EC2 instance (retrieved from resource repository)
    • Port: 3389
    • Credentials: Screenflow instance username/password (configured in ScreenFlow service)
    • Security: 'any' (accepts any RDP security method)
    • Certificate Validation: Disabled ('ignore-cert': 'true')
  • Implementation: Guacamole uses its guacd daemon to establish and manage RDP connections
  • Connection Lifecycle:
    • ScreenFlow creates connection configuration in Guacamole via API
    • When frontend requests RDP session, Guacamole uses this configuration to connect to the instance
    • Guacamole manages the RDP session lifecycle

Database (Guacamole DB)

  • Protocol: Database connection (PostgreSQL/MySQL)
  • Purpose: Guacamole stores connection configurations, user sessions, and connection history
  • Access: Guacamole service manages its own database connections

4. ScreenFlow → Instances (Windows Instance Service)

HTTP API (HTTP/HTTPS)

  • Protocol: HTTP/HTTPS
  • Purpose: Screenshot functionality and script execution on Windows instances
  • Host: Configured via WINDOWS_INSTANCE_HOST (local dev) or direct private IP (production)
  • Port: Configured via WINDOWS_INSTANCE_PORT (default: 8000)
  • Authentication: API key via WINDOWS_INSTANCE_API_KEY
  • Usage: Used for taking screenshots and running scripts on Windows instances outside of RDP sessions

Connection Sequence for RDP Session

  1. Frontend initiates connection:

    • Frontend calls ScreenFlow REST API to ensure Guacamole connection exists (or creates it)
    • Frontend establishes WebSocket connection to ScreenFlow: wss://screenflow.domain/ws/guacamole-tunnel/{instanceId}
  2. ScreenFlow proxies to Guacamole:

    • ScreenFlow looks up Guacamole connection configuration by instanceId
    • ScreenFlow authenticates with Guacamole API to get admin token
    • ScreenFlow establishes WebSocket connection to Guacamole: wss://guacamole/websocket-tunnel?token=...&GUAC_ID={connectionId}
  3. Guacamole connects to instance:

    • Guacamole uses stored connection configuration (hostname, port 3389, credentials)
    • Guacamole's guacd daemon establishes RDP connection to Windows instance
    • RDP session is established on port 3389
  4. Data flow:

    • RDP display updates flow: Instance → Guacamole → ScreenFlow → Frontend
    • User input flows: Frontend → ScreenFlow → Guacamole → Instance
    • ScreenFlow intercepts and records input commands for analytics

Security Considerations

  • RDP Connections: Use private IP addresses within VPC, not exposed publicly
  • WebSocket Tunnels: All WebSocket connections use WSS (secure) in production
  • Authentication:
    • Frontend uses cookie-based authentication for REST API
    • ScreenFlow uses admin credentials for Guacamole API
    • Guacamole uses configured instance credentials for RDP
  • Network Isolation: All connections between services occur within AWS VPC in production

Local Development Setup

This section covers the local development setup for the screenflow service. Where we connect through bastions to:

  • screenflow and guacamole staging databases.
  • guacamole staging service.
  • windows instance.

Start services locally

  • nx run coding:dev (for auth and tenant)
  • nx run screenflow:dev
  • nx run frontend:dev

Bastion connection scripts

Use the connection scripts from various guides and add them following to your .zshrc file (don't forget to source it afterwards: source ~/.zshrc).

Hint: You can reuse the same _PARALLEL_CONFIG for all scripts and paste the rest of the scripts in the same file.

Hint: test you connection by going to http://localhost:8080/guacamole/ and logging in with the .env credentials. Or by running curl -X POST "http://localhost:8080/guacamole/api/tokens" -H "Content-Type: application/x-www-form-urlencoded" -d "username=guacadmin&password=<guacamole_admin_password>"

Hint: test you connection by running curl -X GET "http://localhost:8000/computer/display/dimensions" -H "x-api-key: placeholder_key".

ZSHRC Configuration example (refer to the linked pages for the most up to date scripts)
bash
# ...your personal zshrc configs


# Configuration
typeset -A _PARALLEL_CONFIG
_PARALLEL_CONFIG=(
  # Bastion instances
  [bastion_prod]="i-0430e03b4e1c1bbdd"
  [bastion_staging]="i-0f052721e4ba2c318"

  # RDS cluster identifiers
  [rds_suffix_prod]="clwscu6m8xeq.eu-west-3.rds.amazonaws.com"
  [rds_suffix_staging]="c5wmseg8am3n.eu-west-3.rds.amazonaws.com"

  # Environment prefixes
  [prefix_prod]="parallel-euw3-prod"
  [prefix_staging]="parallel-euw3-staging"

  # Default local port ranges (prod: 554xx, staging: 654xx)
  [port_base_prod]="55432"
  [port_base_staging]="65432"
)

# ============================================
# Database Connections (via Bastion)
# ============================================

# Database port offsets
typeset -A _DB_PORT_OFFSET
_DB_PORT_OFFSET=(
  [coding]=0
  [valuation]=1
  [codingsnapshot]=2
  [dashboard]=3
  [screenflow]=4
  [guacamole]=5
)

# Generic function to connect to any database
_connect-db() {
  local ENV="$1"
  local DB_NAME="$2"
  local CUSTOM_PORT="$3"

  aws-sso-util login --profile "parallel_${ENV}"
  export AWS_PROFILE="parallel_${ENV}"

  local BASTION="${_PARALLEL_CONFIG[bastion_${ENV}]}"
  local RDS_SUFFIX="${_PARALLEL_CONFIG[rds_suffix_${ENV}]}"
  local PREFIX="${_PARALLEL_CONFIG[prefix_${ENV}]}"
  local PORT_BASE="${_PARALLEL_CONFIG[port_base_${ENV}]}"
  local PORT_OFFSET="${_DB_PORT_OFFSET[${DB_NAME}]:-0}"

  local HOST="${PREFIX}-${DB_NAME}-db.${RDS_SUFFIX}"
  local LOCAL_PORT="${CUSTOM_PORT:-$((PORT_BASE + PORT_OFFSET))}"

  echo ""
  echo "🗄️  Connecting to ${DB_NAME} database (${ENV})..."
  echo "   Host: ${HOST}"
  echo "   Port: 5432 → localhost:${LOCAL_PORT}"
  echo ""

  aws ssm start-session \
    --target "$BASTION" \
    --document-name "AWS-StartPortForwardingSessionToRemoteHost" \
    --parameters "{\"host\":[\"${HOST}\"],\"portNumber\":[\"5432\"],\"localPortNumber\":[\"${LOCAL_PORT}\"]}"
}

# ================== PROD ==================
connect-coding-db-prod() { _connect-db "prod" "coding" "$1"; }
connect-valuation-db-prod() { _connect-db "prod" "valuation" "$1"; }
connect-codingsnapshot-db-prod() { _connect-db "prod" "codingsnapshot" "$1"; }
connect-dashboard-db-prod() { _connect-db "prod" "dashboard" "$1"; }
connect-screenflow-db-prod() { _connect-db "prod" "screenflow" "$1"; }
connect-guacamole-db-prod() { _connect-db "prod" "guacamole" "$1"; }

# ================ STAGING =================
connect-coding-db-staging() { _connect-db "staging" "coding" "$1"; }
connect-valuation-db-staging() { _connect-db "staging" "valuation" "$1"; }
connect-codingsnapshot-db-staging() { _connect-db "staging" "codingsnapshot" "$1"; }
connect-dashboard-db-staging() { _connect-db "staging" "dashboard" "$1"; }
connect-screenflow-db-staging() { _connect-db "staging" "screenflow" "$1"; }
connect-guacamole-db-staging() { _connect-db "staging" "guacamole" "$1"; }


# ============================================
# Connect to Specific EC2 (via Bastion)
# ============================================

_connect-ec2-instance() {
  local ENV="$1"
  local INSTANCE_ID="$2"
  local LOCAL_PORT="$3"
  local REMOTE_PORT="${4:-80}" # Default to port 80 if not specified

  # 1. Login & Set Context
  aws-sso-util login --profile "parallel_${ENV}"
  export AWS_PROFILE="parallel_${ENV}"

  local BASTION="${_PARALLEL_CONFIG[bastion_${ENV}]}"

  # 2. Get the Private IP of the specific EC2 Instance
  echo "🔍 Resolving IP for Instance ID: ${INSTANCE_ID}..."

  local PRIVATE_IP
  PRIVATE_IP=$(aws ec2 describe-instances \
    --instance-ids "$INSTANCE_ID" \
    --query 'Reservations[0].Instances[0].PrivateIpAddress' \
    --output text)

  if [[ "$PRIVATE_IP" == "None" ]] || [[ -z "$PRIVATE_IP" ]]; then
    echo "❌ Could not find Private IP for instance: $INSTANCE_ID"
    echo "   (Check if the instance ID is correct and the profile has permission to view it)"
    return 1
  fi

  # 3. Connect via SSM (Tunneling through Bastion)
  echo ""
  echo "🔗 Connecting to EC2 Instance (${ENV})..."
  echo "   Instance: $INSTANCE_ID"
  echo "   Target IP: $PRIVATE_IP"
  echo "   Via Bastion: $BASTION"
  echo ""
  echo "📍 Open: http://localhost:${LOCAL_PORT}/"
  echo ""

  aws ssm start-session \
    --target "$BASTION" \
    --document-name "AWS-StartPortForwardingSessionToRemoteHost" \
    --parameters "{\"host\":[\"${PRIVATE_IP}\"],\"portNumber\":[\"${REMOTE_PORT}\"],\"localPortNumber\":[\"${LOCAL_PORT}\"]}"
}

connect-custom-ec2() {
  local ENV="$1"
  local INSTANCE_ID="$2"
  local REMOTE_PORT="$3"
  local LOCAL_PORT="${4:-$REMOTE_PORT}" # Defaults local port to match remote port if not set

  if [[ -z "$ENV" || -z "$INSTANCE_ID" || -z "$REMOTE_PORT" ]]; then
    echo "❌ Usage: connect-custom-ec2 <env> <instance-id> <remote-port> [local-port]"
    echo "   Ex: connect-custom-ec2 staging i-0b31cb0b11ed4036e 8000"
    return 1
  fi

  _connect-ec2-instance "$ENV" "$INSTANCE_ID" "$LOCAL_PORT" "$REMOTE_PORT"
}

# ============================================
# Connect to Generic Task Service
# ============================================

_connect-service() {
  local ENV="$1"
  local SERVICE="$2"
  local LOCAL_PORT="$3"
  local REMOTE_PORT="$4"

  aws-sso-util login --profile "parallel_${ENV}"
  export AWS_PROFILE="parallel_${ENV}"

  local BASTION="${_PARALLEL_CONFIG[bastion_${ENV}]}"
  local PREFIX="${_PARALLEL_CONFIG[prefix_${ENV}]}"
  local CLUSTER="${PREFIX}-ecs-cluster"
  local ECS_SERVICE="${PREFIX}-ecs-${SERVICE}"

  echo "🔍 Finding task for ${SERVICE}..."

  # 1. Get Task ARN
  local TASK_ARN
  TASK_ARN=$(aws ecs list-tasks \
    --cluster "$CLUSTER" \
    --service-name "$ECS_SERVICE" \
    --query 'taskArns[0]' \
    --output text)

  if [[ "$TASK_ARN" == "None" ]] || [[ -z "$TASK_ARN" ]]; then
    echo "❌ No running task found for service: $SERVICE"
    return 1
  fi

  local HOST_IP=""

  # 2. Get Task Details (Raw JSON) to find ENI
  # We grep for the specific pattern "eni-..." inside the task description
  local TASK_JSON
  TASK_JSON=$(aws ecs describe-tasks --cluster "$CLUSTER" --tasks "$TASK_ARN" --output json)

  # Extract ENI ID using grep (looks for "value": "eni-...")
  local ENI_ID
  ENI_ID=$(echo "$TASK_JSON" | grep -o '"value": "eni-[^"]*"' | cut -d'"' -f4 | head -n 1)

  if [[ -n "$ENI_ID" ]]; then
    HOST_IP=$(aws ec2 describe-network-interfaces \
      --network-interface-ids "$ENI_ID" \
      --query 'NetworkInterfaces[0].PrivateIpAddress' \
      --output text)
  fi

  # 3. Fallback: Check for EC2 Instance ID (if grep failed or it's not Fargate)
  if [[ -z "$HOST_IP" || "$HOST_IP" == "None" ]]; then
    # Extract Instance ID using grep
    local INSTANCE_ID
    INSTANCE_ID=$(echo "$TASK_JSON" | grep -o '"ec2InstanceId": "i-[^"]*"' | cut -d'"' -f4 | head -n 1)

    if [[ -n "$INSTANCE_ID" ]]; then
       HOST_IP=$(aws ec2 describe-instances \
          --instance-ids "$INSTANCE_ID" \
          --query 'Reservations[0].Instances[0].PrivateIpAddress' \
          --output text)
    fi
  fi

  if [[ -z "$HOST_IP" || "$HOST_IP" == "None" ]]; then
    echo "❌ Could not resolve Host IP. The task might be pending or the network mode is unsupported."
    return 1
  fi

  echo ""
  echo "🥑 Connecting to ${SERVICE} service (${ENV})..."
  echo "   Task: $TASK_ARN"
  echo "   Host: $HOST_IP"
  echo "   Port: ${REMOTE_PORT} → localhost:${LOCAL_PORT}"
  echo ""

  aws ssm start-session \
    --target "$BASTION" \
    --document-name "AWS-StartPortForwardingSessionToRemoteHost" \
    --parameters "{\"host\":[\"${HOST_IP}\"],\"portNumber\":[\"${REMOTE_PORT}\"],\"localPortNumber\":[\"${LOCAL_PORT}\"]}"
}

# ================== PROD ==================
# Prod usually starts at 9080.
connect-guacamole-prod() { _connect-service "prod" "guacamole" "8080" "9080"; }

# ================ STAGING =================
# Staging usually starts at 8080.
connect-guacamole-staging() { _connect-service "staging" "guacamole" "8080" "8080"; }

Environment Configuration

Make sure the following environment variables are defined in your screenflow .env.dev file:

bash
# have the env variables point to staging dbs
# beware of the ?s in the password, they need to be encoded.
DATABASE_URL=postgresql://screenflow:<staging_db_pw>@localhost:65436/screenflow?schema=public
GUACAMOLE_DATABASE_URL=postgresql://guacamole:<staging_guacamole_db_pw>@localhost:65437/guacamole?schema=public

DATABASE_USERNAME=screenflow
DATABASE_PASSWORD="<staging_db_pw>"
DATABASE_ADDRESS=localhost
DATABASE_PORT=65436
DATABASE_NAME=screenflow

GUACAMOLE_DATABASE_USERNAME=guacamole
GUACAMOLE_DATABASE_PASSWORD="<staging_guacamole_db_pw>"
GUACAMOLE_DATABASE_ADDRESS=localhost
GUACAMOLE_DATABASE_PORT=65437
GUACAMOLE_DATABASE_NAME=guacamole

GUACAMOLE_URL=http://localhost:8080/guacamole
WINDOWS_INSTANCE_HOST="http://localhost:8000"
Troubleshooting
  • Password Encoding: If you encounter the following error, it means the database URL is not properly encoded. It's likely due to the database password containing special characters that need to be escaped in the url.
prisma:error Cannot read properties of undefined (reading 'searchParams')
  • Disable SSL: If you encounter the following error, check if your db url contains &disable_ssl=true. Remove it.
prisma:error
Invalid `this.screenflowPrismaService.defaultInstance.instance.count()` invocation in
/Users/eliott/Desktop/parallel/alphacode/services/screenflow/src/infrastructure-layer/prisma/screenflow/repositories/entities/instance/prisma-instance.repository.ts:41:76

  38 async count(options?: InstanceRepositoryOptions): Promise<number> {
  39     const { tenantId } = options ?? {}
  40

→ 41     return await this.screenflowPrismaService.defaultInstance.instance.count(
User was denied access on the database `screenflow`

Local Development Setup - other commands

Port Forwarding to Guacamole

To run screenflow locally, you must first establish a port forwarding session to the Guacamole ECS container.

Note: The instance ID must be fetched from AWS. Use the EC2 instance named *-bastion-*.

bash
aws ssm start-session --target "i-04bc51048b5fcfcda" --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"host":["staging-guacamole.internal"],"portNumber":["8080"], "localPortNumber":["8080"]}'

Replace i-04bc51048b5fcfcda with the current bastion instance ID from AWS.

Port Forwarding to Window Instance

When working locally with window instances (for screenshot functionality), you need to establish a port forwarding session to the EC2 instance running the window instance service.

Local Development Setup

  1. Set the following environment variables in your .env.dev file:
bash
WINDOWS_INSTANCE_HOST=local.beparallel.com:8000
WINDOWS_INSTANCE_PORT=8000
WINDOWS_INSTANCE_API_KEY=your-api-key
  1. Start the SSM port forwarding session:
bash
aws ssm start-session \
  --target i-0dacabcb36c8bfd2a \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["8000"],"localPortNumber":["8000"]}' \
  --region eu-west-3 \
  --profile parallel_staging

Note: Replace the instance ID (i-0dacabcb36c8bfd2a) with the current window instance EC2 ID from AWS if it has changed.

How It Works

  • Local Development: The screenflow service uses WINDOWS_INSTANCE_HOST (e.g., local.beparallel.com:8000) to connect through the SSM tunnel to the window instance running on AWS.
  • Deployed on AWS: When deployed, the service uses the private IP address of the window instance directly for internal VPC communication. The WINDOWS_INSTANCE_HOST environment variable is not set in production.

Running the Service

Install Dependencies

bash
pnpm install

Environment variables

  1. Add a .env.dev at the service root
  2. Get the environment variables from 1password for the corresponding service

Commands

bash
# Development
nx run screenflow:dev

# Build
nx run screenflow:build

# Test
nx run screenflow:test

# Lint
nx run screenflow:lint

# Run in Docker
docker compose -f 'docker-compose.yml' up -d --build 'screenflow'

# Check dependencies
nx show project screenflow

# Show affected projects (based on git changes)
nx affected:graph

Shared Packages

The shared packages (dto, models, utils) are automatically linked and can be imported in both backend and frontend applications. Any changes to these packages will trigger rebuilds in dependent applications.

These packages mostly contain global functions (like logger, utils, ...) shared among services, providing common functionality and ensuring consistency across the monorepo.

Available Packages

  • dto - Data Transfer Objects
  • models - Shared data models
  • utils - Utility functions and helpers