Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sync Architecture

Cross-device sync for the Hone IDE. Edit on desktop and see changes on mobile (and vice versa) in real time. Sync is off by default – users opt in via Settings > Sync.

Components

Auth Service (auth.hone.codes, port 8445)

Magic-link login, device token management, project entitlements. Built with Fastify, backed by MySQL on webserver.skelpo.net.

Relay Server (sync.hone.codes, ports 8443/8444)

WebSocket message router between devices in the same room. SQLite persistence for buffering messages during disconnects.

Auth Flow

1. User enters email in IDE settings
2. IDE calls GET /auth/login?email=...
3. Auth creates 64-char random hex token, stores in magic_links table (15-min expiry)
4. Email sent to user (or logged to console in dev mode)
5. User clicks link: GET /auth/verify?token=...&deviceName=...&platform=...
6. Auth validates token, creates user + device records, generates device token
7. IDE stores deviceToken locally

Device Token Format

userId:deviceId:timestamp.hash

Where hash is a double-djb2 HMAC. A shared secret between auth and relay allows the relay to validate tokens locally without calling back to auth.

Projects and Tiers

Projects map local workspaces to relay rooms. Each project has a projectKey, name, roomId, and userId.

TierSynced Projects
free0
personal1
prounlimited
teamunlimited

Project registration is idempotent – registering the same project key twice returns the existing record.

Relay Protocol

Join

{
  "type": "join",
  "room": "<roomId>",
  "device": "<deviceId>",
  "token": "<deviceToken>"
}

Joined (response)

{
  "type": "joined",
  "room": "<roomId>",
  "device": "<deviceId>"
}

Messages

{
  "from": "<deviceId>",
  "to": "host | broadcast | <targetDeviceId>",
  "room": "<roomId>",
  "payload": { ... }
}

Relay Behavior

  • Room-based routing – room IDs hashed via djb2 for slot assignment
  • Slot tracking – each connection occupies a slot in a room
  • Host election – first device to join a room becomes host
  • 60-second message buffer – messages buffered for reconnecting devices (covers brief disconnects)
  • Rate limiting – per-connection rate limiting to prevent abuse
  • Auth bypass – when auth.secret is empty, token validation is skipped (dev mode only)

Auth Endpoints

MethodPathPurpose
GET/auth/loginInitiate magic-link login (params: email)
GET/auth/verifyVerify magic link and register device (params: token, deviceName, platform)
GET/auth/deviceGet device info (header: Authorization)
GET/auth/devicesList all devices for the authenticated user
DELETE/auth/device/:idRemove a device
POST/auth/projectRegister a project for sync
GET/auth/projectsList synced projects
DELETE/auth/project/:idRemove a synced project
GET/auth/subscriptionGet current subscription tier

All endpoints except /auth/login and /auth/verify require the Authorization: Bearer <deviceToken> header.

Database Schema

users

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(255) NOT NULL UNIQUE,
  createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
);

devices

CREATE TABLE devices (
  id INT AUTO_INCREMENT PRIMARY KEY,
  userId INT NOT NULL,
  deviceName VARCHAR(255) NOT NULL,
  platform VARCHAR(50) NOT NULL,
  token VARCHAR(255) NOT NULL UNIQUE,
  createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
  lastSeen DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE magic_links (
  id INT AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(255) NOT NULL,
  token VARCHAR(255) NOT NULL UNIQUE,
  expiresAt DATETIME NOT NULL,
  used BOOLEAN DEFAULT FALSE,
  createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
);

projects

CREATE TABLE projects (
  id INT AUTO_INCREMENT PRIMARY KEY,
  userId INT NOT NULL,
  projectKey VARCHAR(255) NOT NULL,
  name VARCHAR(255) NOT NULL,
  roomId VARCHAR(255) NOT NULL UNIQUE,
  createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE,
  UNIQUE KEY unique_user_project (userId, projectKey)
);

subscriptions

CREATE TABLE subscriptions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  userId INT NOT NULL UNIQUE,
  tier ENUM('free', 'personal', 'pro', 'team') DEFAULT 'free',
  expiresAt DATETIME,
  createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
);

All identifiers use camelCase (matching Hone’s database convention).

IDE Settings

SettingDefaultDescription
syncEnabledfalseEnable cross-device sync
syncRelayUrlwss://sync.hone.codesRelay server WebSocket URL
syncAuthUrlhttps://auth.hone.codesAuth service URL
syncDeviceToken(empty)Stored device token after login

Deployment

Both services run on 84.32.223.50 (Ubuntu 24.04):

  • TLS termination via Let’s Encrypt + nginx reverse proxy
  • Auth config: auth.conf (KEY=VALUE format)
  • Relay config: relay.conf (KEY=VALUE format)
  • MySQL database: host=webserver.skelpo.net, user=hone

Config File Format

# auth.conf
db.host=webserver.skelpo.net
db.user=hone
db.password=...
db.name=hone
auth.secret=<shared-secret>
smtp.host=...
smtp.port=587
smtp.user=...
smtp.password=...
# relay.conf
auth.secret=<shared-secret>
sqlite.path=/var/lib/hone-relay/relay.db

Self-Hosted

Users can run their own relay and auth services. Both are single native binaries:

  1. Compile: perry compile src/app.ts --output hone-auth / hone-relay
  2. Create config file (auth.conf / relay.conf)
  3. Run the binary

Point the IDE settings (syncRelayUrl, syncAuthUrl) at your own server.