cautpartener / client / src / lib / protocol.ts
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
 * Wire types shared by the p2p lib — board edition. See docs/PROTOCOL.md.
 *
 * The LogOp union is the app-op vocabulary of the replicated log. This fork
 * deliberately has NO `legacy` op (sueta's documented injection surface) and
 * carries two op families:
 *
 *  - board content ops (`add`/`edit`/`del`) — link-mode boards append them
 *    plain to the base log; strict-mode boards seal them inside `sealed`
 *    vault-log entries (see the access layer, lib/policy.ts + lib/epoch.ts)
 *  - control ops (`mode`/`cert`/`jreq`/`approve`/`deny`/`remove`/`rekey`) —
 *    the allow-list control plane, always on the base (link-derived) log
 */

// per-peer connection state surfaced to the app (imported by net.ts)

export type PeerConnState = "connecting" | "direct" | "relayed" | "disconnected";

// encrypted signaling payloads (plaintext shape) — restored verbatim from the
// upstream chat lib alongside media.ts (board's fork dropped both together;
// this README documents protocol.ts as carrying "signal payloads")

export type SignalPayload =
  | { kind: "offer"; sdp: string; name: string }
  | { kind: "answer"; sdp: string; name: string }
  | { kind: "ice"; candidate: RTCIceCandidateInit | null; name: string };

// ---- board elements (world coordinates) ------------------------------------

/** 0 = "ink" (auto black-on-white / white-on-black per local theme), 1..7 palette. */
export type ColorTok = number;

/** A connector endpoint: attached to an element's side, or a free point. */
export type Anchor = { el: string; side?: "n" | "s" | "e" | "w" | "c" } | { x: number; y: number };

export type ShapeKind = "rect" | "ellipse" | "line" | "arrow";

export type ElementSpec =
  | {
      id: string;
      k: "stroke";
      x: number; // origin (bbox min corner)
      y: number;
      /** Flat [dx0,dy0,dx1,dy1,…] fixed-point ints (world*100), delta-encoded from (x,y). */
      pts: number[];
      /** Optional per-point pressure 0..255 (Apple Pencil). */
      prs?: number[];
      w: number; // stroke width in world units
      c: ColorTok;
    }
  | { id: string; k: "text"; x: number; y: number; text: string; size: number; c: ColorTok; wrap?: number }
  | { id: string; k: "note"; x: number; y: number; w: number; h: number; text: string; c: ColorTok }
  | {
      id: string;
      k: "img";
      x: number;
      y: number;
      w: number;
      h: number;
      cid: string;
      mime: string;
      bytes: number;
      iw: number; // intrinsic pixel dims
      ih: number;
    }
  | {
      id: string;
      k: "shape";
      shape: ShapeKind;
      x: number;
      y: number;
      w: number; // line/arrow: (x,y)→(x+w,y+h); w/h may be negative
      h: number;
      c: ColorTok;
      sw: number; // stroke width
      fill?: ColorTok;
    }
  | { id: string; k: "conn"; a: Anchor; b: Anchor; c: ColorTok; sw: number; arrow?: "none" | "end" | "both" };

/** LWW-patchable fields; folded per-(id, field) by (clock, hash). */
export interface ElementPatch {
  x?: number;
  y?: number;
  w?: number;
  h?: number;
  text?: string;
  size?: number;
  c?: ColorTok;
  sw?: number;
  fill?: ColorTok | null; // null clears the fill
  wrap?: number;
  a?: Anchor;
  b?: Anchor;
  arrow?: "none" | "end" | "both";
}

// ---- access layer (control plane; see docs/PROTOCOL.md board appendix) -----

/** An epoch grant sealed to one identity's X25519 key (sealed-box style). */
export interface Wrap {
  to: string; // recipient Ed25519 identity pub (base64url)
  toX: string; // recipient X25519 pub the wrap was sealed to
  ephPub: string; // sender's ephemeral X25519 pub
  iv: string;
  ct: string; // AES-GCM(payload) — payload is {v: secretV, e: epochSecret}
}

/** An AEAD box under a derived room-wide key (keychain: E_n sealed under kChain(n+1)). */
export interface Box {
  iv: string;
  ct: string;
}

// ---- the replicated-log op union --------------------------------------------

export type LogOp =
  // board content (link mode: plain on the base log; strict mode: inside `sealed`)
  | { t: "add"; ts: number; name: string; el: ElementSpec }
  | { t: "edit"; ts: number; name: string; id: string; p: ElementPatch }
  | { t: "del"; ts: number; name: string; ids: string[] }
  // vault-log envelope: ct = AES-GCM(kBoard(e), JSON(inner add/edit/del op))
  | { t: "sealed"; e: number; iv: string; ct: string }
  // control plane (base log only)
  // mode: creator-only. First strict switch mints epoch 1 (no chain); a
  // re-strict after a relax mints a fresh epoch and carries the keychain box
  // (previous epoch secret sealed under the new epoch's kChain).
  | {
      t: "mode";
      ts: number;
      name: string;
      mode: "link" | "strict";
      epoch: number;
      approvals: "any" | "creator";
      wraps?: Wrap[];
      chain?: Box;
    }
  | { t: "cert"; ts: number; xPub: string; xSig: string }
  | { t: "jreq"; ts: number; name: string; msg: string; xPub: string; xSig: string }
  | { t: "approve"; ts: number; to: string; toX: string; name: string; epoch: number; wrap: Wrap }
  | { t: "deny"; ts: number; to: string }
  | { t: "remove"; ts: number; to: string; epoch: number; chain: Box; wraps: Wrap[] };

static mirror of HEAD · about · clone: git clone https://cod-sursa.cautpartener.ro/cautpartener.git