anydrop/shared/src/protocol.ts
ordinarthur d6f7a2374b
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 43s
feat: permanent device pairing for cross-network sharing
Adds a groupId-based pairing system so devices can always see
each other regardless of network. Scan a QR code once from the
other device, and they're permanently linked via a shared group
stored in localStorage. No account, no email — just one-time QR
scan like Bluetooth pairing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 12:39:21 +02:00

175 lines
3.7 KiB
TypeScript

import type { DeviceType } from "./names.js";
// ── Client → Server messages ──
export interface HelloMessage {
type: "hello";
deviceId: string;
deviceName: string;
deviceType: DeviceType;
avatar?: string;
localIP?: string; // e.g. "192.168.1.42" — used for LAN detection fallback
groupId?: string; // permanent device group for cross-network pairing
joinCode?: string;
}
export interface CreatePublicRoomMessage {
type: "create-public-room";
}
export interface SignalMessage {
type: "signal";
to: string;
data: unknown;
}
export interface LeaveMessage {
type: "leave";
}
/** Client sends its push subscription so the server can wake it when offline */
export interface SubscribePushMessage {
type: "subscribe-push";
deviceId: string;
subscription: PushSubscriptionJSON;
}
/** Client asks the server to send a push notification to wake a specific offline device */
export interface WakePeerMessage {
type: "wake-peer";
deviceId: string;
}
export type ClientMessage =
| HelloMessage
| CreatePublicRoomMessage
| SignalMessage
| LeaveMessage
| SubscribePushMessage
| WakePeerMessage;
// ── Server → Client messages ──
export interface PeerInfo {
peerId: string;
displayName: string;
deviceType: DeviceType;
avatar?: string;
online?: boolean; // default true; false = offline but reachable via push
deviceId?: string; // for offline peers (used to wake them)
}
export interface WelcomeMessage {
type: "welcome";
peerId: string;
roomId: string;
peers: PeerInfo[];
vapidPublicKey?: string;
}
export interface PublicRoomCreatedMessage {
type: "public-room-created";
code: string;
url: string;
expiresAt: string;
}
export interface PeerJoinedMessage {
type: "peer-joined";
peerId: string;
displayName: string;
deviceType: DeviceType;
avatar?: string;
}
export interface PeerLeftMessage {
type: "peer-left";
peerId: string;
}
export interface SignalRelayMessage {
type: "signal";
from: string;
data: unknown;
}
export type ErrorCode = "room-not-found" | "room-expired" | "rate-limit";
export interface ErrorMessage {
type: "error";
code: ErrorCode;
message: string;
}
export type ServerMessage =
| WelcomeMessage
| PublicRoomCreatedMessage
| PeerJoinedMessage
| PeerLeftMessage
| SignalRelayMessage
| ErrorMessage;
// ── Data channel messages (peer-to-peer) ──
export interface FileMetaMessage {
type: "file-meta";
id: string;
name: string;
size: number;
mime: string;
}
export interface FileEndMessage {
type: "file-end";
id: string;
}
export interface TextMessage {
type: "text";
id: string;
content: string;
}
export interface TransferRequestMessage {
type: "transfer-request";
files: { id: string; name: string; size: number; mime: string }[];
text?: string;
}
export interface TransferResponseMessage {
type: "transfer-response";
accepted: boolean;
}
export type DataChannelMessage =
| FileMetaMessage
| FileEndMessage
| TextMessage
| TransferRequestMessage
| TransferResponseMessage;
// ── Push notification payload ──
export interface PushPayload {
type: "peer-nearby";
displayName: string;
deviceType: DeviceType;
}
// ── Constants ──
export const CHUNK_SIZE = 16 * 1024; // 16 KB
export const MAX_BUFFER = 1024 * 1024; // 1 MB backpressure threshold
export const SHORT_CODE_ALPHABET = "abcdefghjkmnpqrstuvwxyz23456789";
export const SHORT_CODE_LENGTH = 3;
export const SHORT_CODE_MAX_LENGTH = 4;
export const SHORT_CODE_MAX_RETRIES = 5;
export const PUBLIC_ROOM_TTL_MS = 10 * 60 * 1000; // 10 minutes
export const ICE_SERVERS: RTCIceServer[] = [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun1.l.google.com:19302" },
];