e-bon
e-bon.ro
Realtime

Scheme

Cadrele de protocol purtate de canalul realtime — șapte mesaje server→dispozitiv, trei mesaje dispozitiv→server și granița clientMessageId.

Scheme

Canalul realtime poartă două forme diferite de mesaj:

  1. Plicuri de evenimente partenere — { type, data, timestamp } — documentate pe Evenimente. Aici sunt majoritatea integratorilor interesați.
  2. Cadre de protocol — tipate, validate, fără înveliș data — folosite între gateway-ul e-bon și aplicația E-BON pentru Android — vezi Prezentare generală arhitectură.

Ai nevoie de această pagină doar dacă îți construiești un client custom care vorbește direct protocolul. Dacă folosești @e-bon/realtime, schemele de mai jos sunt validate pentru tine și dispecerizate prin același API on / off / once.

Toate schemele sunt scheme zod exportate de pachetul @e-bon/realtime. Schemele sunt sursa unică de adevăr, iar tipurile TypeScript sunt inferate prin z.infer.

Mesaje server → dispozitiv

Cele șapte tipuri de mesaj pe care serverul le poate trimite unui dispozitiv:

Literal de tipPayload
device.updated{ deviceId, patch: Partial<Device>, version: number }
location.updated{ locationId, patch: Partial<Location>, version: number }
location.deleted{ locationId }
receipt.created{ receiptId, receipt: Partial<Receipt> }
receipt.voided{ receiptId, voidedAt: ISO8601, voidedBy }
command.dispatch{ commandId, command: Partial<FiscalCommand> }
sync.resume{ since: ISO8601 }

Uniunea discriminată completă este exportată ca serverToClientMessageSchema și ServerToClientMessage:

import {
  serverToClientMessageSchema,
  type ServerToClientMessage,
  SERVER_TO_CLIENT_TYPES,
  isServerToClientType,
} from '@e-bon/realtime';

const result = serverToClientMessageSchema.safeParse(raw);
if (!result.success) {
  // Aruncă cadrul — nu te încrede niciodată într-un parse parțial.
  return;
}
const message: ServerToClientMessage = result.data;

SERVER_TO_CLIENT_TYPES este tuplul readonly al celor șapte literali; isServerToClientType(value) este garda de tip de runtime.

::callout

icon: i-lucide-info

Corpurile patch, receipt și command sunt tipate astăzi ca Record<string, unknown> (jsonObjectSchema) — @e-bon/types încă nu livrează oglinzi zod pentru modelele de domeniu de bază. O trecere mai strictă de tipare este urmărită sub E-1c+. ::

Mesaje dispozitiv → server

Cele trei tipuri de mesaj pe care un dispozitiv le poate trimite înapoi serverului. Fiecare cadru dispozitiv→server trebuie să poarte un clientMessageId, ca serverul să poată de-duplica pe livrare at-least-once.

Literal de tipPayload
command.result{ clientMessageId, commandId, result: Record<string, unknown> }
command.error{ clientMessageId, commandId, error: Record<string, unknown> }
status{ clientMessageId, deviceStatus: Record<string, unknown> }

Uniunea discriminată este exportată ca clientToServerMessageSchema și ClientToServerMessage:

import {
  clientToServerMessageSchema,
  type ClientToServerMessage,
  CLIENT_TO_SERVER_TYPES,
  isClientToServerType,
} from '@e-bon/realtime';

CLIENT_TO_SERVER_TYPES este tuplul readonly al celor trei literali; isClientToServerType(value) este garda de tip de runtime.

Granița clientMessageId

clientMessageId este un șir nevid ales de dispozitiv și este singurul câmp pe care serverul îl folosește pentru a de-duplica cadrele. Serverul tratează două cadre cu același clientMessageId (în fereastra de de-duplicare) ca pe un singur cadru, deci:

  • Generează un clientMessageId proaspăt pentru fiecare mesaj logic (UUID v4 este suficient).
  • Reutilizează același clientMessageId la reîncercarea aceluiași mesaj logic după o reconectare — nu emite niciodată unul nou pentru o reîncercare.

Trimiterea cadrelor de pe dispozitiv

@e-bon/realtime expune un API public dispozitiv→server. Apelează-l din clientul tău după ce connected s-a emis; comportamentul alpha este intenționat minimal — nu există coadă în memorie și nici retry, deci cadrele trimise când socketul nu este deschis sunt aruncate cu un console.warn.

// Nivel jos: construiești singur învelișul.
realtime.send({
  type: 'command.result',
  clientMessageId: 'b4a4f2c7-…', // UUID v4, vezi regula granței de mai sus
  commandId: 'cmd_01H…',
  result: { receiptNumber: 4711 },
});

// Helperi de conveniență — aceeași formă de pe fir, mai puține taste.
realtime.sendCommandResult({
  commandId: 'cmd_01H…',
  clientMessageId: 'b4a4f2c7-…',
  result: { receiptNumber: 4711 },
});

realtime.sendCommandError({
  commandId: 'cmd_01H…',
  clientMessageId: 'b4a4f2c7-…',
  error: { code: 'PRINTER_OFFLINE', message: 'fără răspuns de la dispozitiv' },
});

Garanții ale helperilor:

  • Fiecare cadru este validat cu clientToServerMessageSchema înainte să ajungă pe fir. Payload-urile invalide sunt aruncate și logate — un call site malformat nu poate pune niciodată gunoi pe protocol.
  • Dacă readyState !== OPEN (socket în închidere, suspendat sau în reconectare), cadrul este aruncat cu un console.warn în loc să fie buffer-uit. Apelanții care au nevoie de semantici at-least-once trebuie să persiste intenția singuri și să retrimită după următorul eveniment connected, reutilizând același clientMessageId ca serverul să de-duplice reîncercarea.
  • Fără batching automat, fără compresie, fără generare implicită de clientMessageId — fiecare câmp de pe fir este unul pe care l-ai trimis tu.

Această secțiune documentează și forma de pe fir astfel încât autorii de middleware și constructorii de clienți custom (Go, Java, Swift, …) să poată vorbi același protocol fără să depindă de @e-bon/realtime.

Ajutoare partajate

  • iso8601Schema — z.iso.datetime({ offset: true }), folosit pentru fiecare câmp timestamp și since.
  • jsonObjectSchema — z.record(z.string(), z.unknown()), înlocuitorul pentru corpurile Device / Location / Receipt / FiscalCommand până când tipurile de bază livrează oglinzi zod.

Ambele sunt exportate din @e-bon/realtime.

Unde mergi mai departe