Scheme
Scheme
Canalul realtime poartă două forme diferite de mesaj:
- Plicuri de evenimente partenere —
{ type, data, timestamp }— documentate pe Evenimente. Aici sunt majoritatea integratorilor interesați. - 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 tip | Payload |
|---|---|
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 tip | Payload |
|---|---|
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
clientMessageIdproaspăt pentru fiecare mesaj logic (UUID v4 este suficient). - Reutilizează același
clientMessageIdla 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 unconsole.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 evenimentconnected, reutilizând acelașiclientMessageIdca 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âmptimestampșisince.jsonObjectSchema—z.record(z.string(), z.unknown()), înlocuitorul pentru corpurileDevice/Location/Receipt/FiscalCommandpână când tipurile de bază livrează oglinzi zod.
Ambele sunt exportate din @e-bon/realtime.
Unde mergi mai departe
- Evenimente — învelișul partener pe care această pagină îl completează.
- Reconectare și ciclu de viață — backoff, suspend / resume, hook cursor-store.
- Prezentare generală arhitectură — contextul realtime mai larg.
Reconectare și ciclu de viață
Programul de auto-reconectare, controalele close / suspend / resume și hook-ul CursorStore pentru livrarea viitoare at-least-once.
Panou de control
Pagina pe care aterizezi după autentificarea în Portal - o privire de ansamblu pe un singur ecran a dispozitivelor și bonurilor de astăzi din organizația activă, actualizată live prin WebSocket cu polling de rezervă la 30 de secunde.