# @device-farm/hub

Hub package with PostgreSQL database support using Sequelize ORM.

## Features

- PostgreSQL database connection management
- Sequelize ORM integration
- Type-safe database configuration
- Connection pooling
- Model initialization support

## Installation

Dependencies are managed at the root level. Run:

```bash
npm install
```

## Usage

### Basic Setup

```typescript
import { Database, initializeModels } from '@device-farm/hub';

// Create database instance
const db = new Database({
  host: 'localhost',
  port: 5432,
  database: 'device_farm',
  username: 'postgres',
  password: 'password',
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000,
  },
  logging: console.log, // Optional: log SQL queries
});

// Initialize models
initializeModels(db.getSequelize());

// Connect to database
await db.connect();

// Sync models (development only - use migrations in production)
await db.sync({ alter: true });

// ... use your database connection

// Disconnect when done
await db.disconnect();
```

### Creating Models

Create models in `src/models/` directory:

```typescript
import { Model, DataTypes, Sequelize } from 'sequelize';

export class User extends Model {
  public id!: number;
  public name!: string;
  public email!: string;
  public readonly createdAt!: Date;
  public readonly updatedAt!: Date;
}

export const initUser = (sequelize: Sequelize): typeof User => {
  User.init(
    {
      id: {
        type: DataTypes.INTEGER,
        autoIncrement: true,
        primaryKey: true,
      },
      name: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      email: {
        type: DataTypes.STRING,
        allowNull: false,
        unique: true,
      },
    },
    {
      sequelize,
      modelName: 'User',
      tableName: 'users',
    }
  );

  return User;
};
```

Then register it in `src/models/index.ts`:

```typescript
import { initUser } from './user';

export const initializeModels = (sequelize: Sequelize): void => {
  initUser(sequelize);
  // Add more models here
};
```

## Configuration

### Environment Variables

It's recommended to use environment variables for database configuration:

```env
DB_HOST=localhost
DB_PORT=5432
DB_NAME=device_farm
DB_USER=postgres
DB_PASSWORD=your_password
DB_POOL_MAX=5
DB_POOL_MIN=0
```

Then use them in your code:

```typescript
const db = new Database({
  host: process.env.DB_HOST || 'localhost',
  port: parseInt(process.env.DB_PORT || '5432', 10),
  database: process.env.DB_NAME || 'device_farm',
  username: process.env.DB_USER || 'postgres',
  password: process.env.DB_PASSWORD || '',
  pool: {
    max: parseInt(process.env.DB_POOL_MAX || '5', 10),
    min: parseInt(process.env.DB_POOL_MIN || '0', 10),
  },
});
```

### WebDriver Session Path Configuration

The WebDriver session endpoint path is configurable. The `webdriverSessionPath` specifies the base path, and the router automatically appends `/session` to it. For example, if `webdriverSessionPath` is `/wd/hub`, the final endpoint will be `/wd/hub/session`.

You can set it via:

1. **Configuration File** (`hub.config.json`):

   ```json
   {
     "webdriverSessionPath": "/wd/hub"
   }
   ```

2. **CLI Arguments**:

   ```bash
   npm run start -- --webdriver-session-path /custom/path
   # or short form:
   npm run start -- -w /custom/path
   ```

3. **Environment Variable**:

   ```env
   WEBDRIVER_SESSION_PATH=/wd/hub
   ```

4. **Custom Config File**:
   ```bash
   npm run start -- --config /path/to/hub.config.json
   # or short form:
   npm run start -- -c /path/to/hub.config.json
   ```

**Priority Order:** Environment variables > CLI arguments > Config file > Default (`/wd/hub`)

**Note:** The router automatically appends `/session` to the configured path, so the default endpoint will be available at `POST /wd/hub/session`.

### Prepare WDA Command (iOS)

The hub includes a command to build and sign WebDriverAgent (WDA) for iOS real device testing. This command:

- Finds or builds WebDriverAgent from Appium
- Signs it with a mobile provisioning profile
- Creates a signed IPA file ready for deployment

**Usage:**

```bash
# Interactive mode (will prompt for provisioning profile)
npm run start -- prepare-wda

# With specific provisioning file
npm run start -- prepare-wda --mobile-provisioning-file /path/to/profile.mobileprovision

# With custom WDA project path
npm run start -- prepare-wda --wda-project-path /path/to/WebDriverAgent

# Specify platform (ios, tvos, or both)
npm run start -- prepare-wda --platform both
```

**Options:**

| Option                       | Short | Description                                              |
| ---------------------------- | ----- | -------------------------------------------------------- |
| `--mobile-provisioning-file` | `-m`  | Path to the mobile provisioning file for signing         |
| `--wda-project-path`         | `-p`  | Path to WebDriverAgent Xcode project                     |
| `--platform`                 |       | Platform type: `ios`, `tvos`, or `both` (default: `ios`) |

**Requirements:**

- macOS with Xcode installed
- Valid Apple Developer provisioning profile
- Appium with XCUITest driver installed (for automatic WDA discovery)

### Node Monitor Configuration

The hub includes a node monitor service that tracks node liveness by checking for heartbeat signals. This service acts as a backup mechanism to mark nodes as offline when WebSocket disconnect events don't fire properly.

#### Configuration Options

You can configure the node monitor using either a configuration file or environment variables:

**1. Configuration File** (`hub.config.json`):

```json
{
  "nodeMonitor": {
    "checkInterval": 60000,
    "timeoutMs": 120000
  },
  "websocket": {
    "heartbeatInterval": 30000
  }
}
```

**2. Environment Variables**:

```env
# Node monitor check interval (default: 60000ms = 60 seconds)
NODE_MONITOR_INTERVAL=60000

# Timeout before marking a node as offline (default: 120000ms = 2 minutes)
NODE_TIMEOUT_MS=120000

# WebSocket heartbeat interval (default: 30000ms = 30 seconds)
WEBSOCKET_HEARTBEAT_INTERVAL=30000
```

**Priority Order:** Environment variables > Config file > Default values

#### Configuration Parameters

- **`checkInterval`** (default: 60000ms = 60 seconds)
  - How often the monitor checks for offline nodes (in milliseconds)
  - Lower values provide faster detection but increase database load
  - Recommended: 10-60 seconds

- **`timeoutMs`** (default: 120000ms = 2 minutes)
  - How long a node can go without sending a heartbeat before being marked offline (in milliseconds)
  - **Important:** This must be greater than `heartbeatInterval` to avoid false positives
  - Recommended: At least 2x the `heartbeatInterval` value (e.g., if heartbeat is 30s, use 60s+ for timeout)

- **`heartbeatInterval`** (default: 30000ms = 30 seconds)
  - How often the hub updates the `last_heartbeat_at` timestamp in the database (in milliseconds)
  - This is the interval at which the hub-side heartbeat mechanism runs
  - Lower values provide more frequent updates but increase database writes
  - Recommended: 20-60 seconds

#### How It Works

1. **Heartbeat Updates**: When a node connects via WebSocket, the hub sets up a periodic heartbeat that updates the `last_heartbeat_at` field in the database every `heartbeatInterval` milliseconds.

2. **Offline Detection**: The node monitor service runs every `checkInterval` milliseconds and checks for nodes that:
   - Are marked as `is_online: true` in the database
   - Have a `last_heartbeat_at` timestamp older than `timeoutMs` (or null)

3. **Automatic Cleanup**: When a stale node is detected, the service:
   - Marks the node as `is_online: false`
   - Marks all devices for that node as not busy and inactive

#### Important Notes

⚠️ **Configuration Warning**: If `timeoutMs` is set to a value less than `heartbeatInterval`, nodes will be incorrectly marked as offline before the next heartbeat arrives. The server will log a warning if this condition is detected.

**Example Configuration:**

```json
{
  "nodeMonitor": {
    "checkInterval": 10000, // Check every 10 seconds
    "timeoutMs": 60000 // Mark offline after 60 seconds of no heartbeat
  },
  "websocket": {
    "heartbeatInterval": 30000 // Update heartbeat every 30 seconds
  }
}
```

This configuration ensures:

- Heartbeats are updated every 30 seconds
- The monitor checks for stale nodes every 10 seconds
- Nodes are marked offline after 60 seconds (2x the heartbeat interval), providing a safety buffer

## Development

```bash
# Build the package
npm run build

# Watch mode
npm run build:watch

# Type check
npm run typecheck

# Lint
npm run lint
```

## Dependencies

- **sequelize**: ORM for Node.js
- **pg**: PostgreSQL client for Node.js
- **pg-hstore**: Serialization/deserialization of hstore data type

## Resources

- [Sequelize Documentation](https://sequelize.org/)
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
