Skip to main content

Networking: WebSocket, Upload & Download

These three services handle non-standard networking: deviceService manages WebSocket connections to peripheral devices (printers, scales), uploadService handles file uploads, and downloadService handles file downloads through a server-side proxy. They complement httpService and adhocService, which handle standard REST API communication.

deviceService (WebSocket connections)

Introduction

deviceService manages local and remote WebSocket connections used for communicating with peripheral devices like receipt printers, cash drawers, and weighing scales. In a POS environment, the browser needs to talk to hardware that is connected to the local machine or accessible through a relay server. deviceService abstracts this into a simple connect/send/disconnect API, hiding the differences between local WebSocket connections and remote relay-based connections.

When to use

  • Printing receipts: connect to a local or remote print service and send HTML content for printing.
  • Opening cash drawers: send an open command to the cash drawer device.
  • Reading from scales: connect to a weighing scale and receive continuous weight readings.
  • Testing connections: verify that a peripheral service is available and responsive.

How it works

There are two connection types:

Local (WSL): the browser connects directly to a WebSocket server running on the local machine (e.g. ws://localhost:9100). This is a full-duplex connection that supports bidirectional messaging and keep-alive pings every 20 seconds. Messages follow a JSON protocol with UUID-based request/response correlation.

  Browser ◄──WebSocket──► Local app (ws://localhost:9100)

Remote (WSR): messages are sent via REST POST to a relay server (AppConstants.restURL), which forwards them to the target WebSocket server. This is a one-way request/response pattern -- the browser sends a message and gets a response, but cannot receive unsolicited messages. Used when the device is not on the local network.

  Browser ──REST POST──► Relay server ──WebSocket──► Target device
◄── response ◄──────────────────
import { deviceService, WSConnection, WSInput, WsConnType } from '@unpispas/upp-base';

constructor(private devices: deviceService) {}

Connection types

TypeCodeDescription
LocalWSLDirect WebSocket from browser to a locally running application. Supports bidirectional messaging and keep-alive pings every 20 seconds. If a ping is not answered within 5 seconds, the connection is terminated.
RemoteWSRMessages sent via REST POST to the relay server. One-way request/response only. No keep-alive or push notifications.

Methods

MethodSignatureDescription
Connect(host, port, service, tolocal)(host, port, service, tolocal): Promise<WSConnection | null>Establishes a WebSocket connection. tolocal=true creates a local connection, false creates a remote one. Returns null on failure. On success, performs a CHECK command to verify the service is responsive.
Disconnect(connection)(connection: WSConnection): voidCloses the connection, stops keep-alive pings, and cleans up internal service references.
Available(service)(service: string): Promise<boolean>Checks if a named service is available for the current place via sockets/available.php. The place parameter is sent automatically by adhocService.
Test(host, port, service, tolocal)Same as ConnectConnects, verifies with a CHECK command, then immediately disconnects. Returns the WSConnection on success (for inspecting svcname and version) or null.
doPrint(connection, device, html, copies?)(connection, device, html, copies?): Promise<any>Sends a print command with base64-encoded HTML content to a printer device. Default copies: 1. The HTML is encoded using GenericUtils.stringToBase64.
doOpen(connection, device)(connection, device): Promise<any>Sends a cash-drawer open command to a device.
WSInputScale(host, port, name, device)(host, port, name, device): Promise<WSInput | null>Connects to a scale device and creates an InputScale reader. Returns null if connection fails.

WSConnection (abstract base class)

MemberTypeDescription
svcnamestring | nullService name reported by the remote side (e.g. 'RECEIPT_PRINTER'). Updated after the first successful message exchange.
versionstring | nullService version reported by the remote side.
contypeWsConnType | null'WSL' (local) or 'WSR' (remote).
Send(module, message)Promise<any>Sends a message and returns the response. For local connections, uses UUID-based correlation. For remote connections, uses REST POST.
Disconnect()voidCloses the connection and emits OnDisconnected.
OnDisconnectedObservable<void>Emits when the connection is lost or intentionally closed.
OnMessageSentObservable<any>Emits when a message is sent.
OnMessageRecvObservable<any>Emits when a message is received (local only).

Message protocol (local connections)

Messages are JSON objects with a specific structure:

// Outbound (browser to device)
{ UUIDV4: string, TARGET: string, ACTION: any }

// Inbound (device to browser)
{
SERVICE_NAME: string, // identifies the service on the device
SERVICE_VERS: string, // service version
MESSAGE_UUID: string, // matches the UUIDV4 of the request
MESSAGE_EXEC: string, // 'SUCCESS' or error description
MESSAGE_DATA: any // response payload
}

The UUID-based correlation allows the service to match responses to requests, even if messages arrive out of order.

WSInput (abstract base class for continuous readers)

MemberTypeDescription
OnInputReadObservable<string>Emits data read from the device (e.g. weight readings from a scale).
OnInputLostObservable<void>Emits when the connection is lost.
doIniRead(args)Promise<any>Starts reading (sends a START command).
doEndRead()Promise<any>Stops reading (sends a STOP command).
OnDestroy()Promise<void>Cleanup: stops reading, unsubscribes, disconnects. Always call this when you are done with the input reader.

Usage examples

Connect to a local printer and print a receipt

const conn = await this.devices.Connect('localhost', 9100, 'PRINTER', true);
if (conn) {
// Print 2 copies of an HTML receipt
await this.devices.doPrint(conn, 'RECEIPT', '<html><body><h1>Receipt</h1>...</body></html>', 2);

// Open the cash drawer
await this.devices.doOpen(conn, 'DRAWER');

// Disconnect when done
this.devices.Disconnect(conn);
} else {
this.toast.ShowAlert('danger', 'Could not connect to printer');
}

Test if a service is available before connecting

// First check if the place has a printer registered
const available = await this.devices.Available('PRINTER');
if (available) {
// Then test the connection
const testConn = await this.devices.Test('localhost', 9100, 'PRINTER', true);
if (testConn) {
console.log(`Printer: ${testConn.svcname} v${testConn.version}`);
this.printerReady = true;
}
}

Read from a weighing scale

const scaleInput = await this.devices.WSInputScale('localhost', 9200, 'SCALE', this.scaleDevice);
if (scaleInput) {
// Start reading
await scaleInput.doIniRead({ unit: 'kg' });

// Subscribe to weight readings
scaleInput.OnInputRead.subscribe(weight => {
this.currentWeight = parseFloat(weight);
});

// Handle connection loss
scaleInput.OnInputLost.subscribe(() => {
this.toast.ShowAlert('warning', 'Scale disconnected');
});

// When done (e.g. component destroy)
// await scaleInput.OnDestroy();
}

React to unexpected disconnection

const conn = await this.devices.Connect('localhost', 9100, 'PRINTER', true);
if (conn) {
conn.OnDisconnected.subscribe(() => {
this.toast.ShowAlert('warning', 'Printer connection lost');
this.printerReady = false;
});
}

Gotchas and tips

  • Local connections require a WebSocket server on the machine: the browser connects to ws://localhost:PORT. This requires a companion application (the UnPisPas device service) running on the same machine.
  • Keep-alive pings every 20 seconds: local connections send a PING to the BASE module every 20 seconds. If the response does not arrive within 5 seconds, the connection is terminated. This ensures stale connections are detected quickly.
  • Remote connections use REST, not WebSocket: despite the name, remote connections do not use WebSocket from the browser's perspective. They POST to the relay server, which forwards via WebSocket.
  • InputScale only works with local connections: continuous reading requires bidirectional communication, which is only available with WSL connections.
  • Disconnect() clears service references: after disconnecting, the internal LocalService and RemoteService instances are set to null, so the next Connect call creates fresh instances.

uploadService

Introduction

uploadService handles file uploads to the server using multipart FormData. It is a focused service with a single responsibility: take file data, wrap it in a FormData object, POST it to the server's upload endpoint, and return the server-assigned filename.

When to use

  • Uploading file attachments (images, documents) from the application.
import { uploadService, uploadSource } from '@unpispas/upp-base';

constructor(private upload: uploadService) {}

Methods

MethodSignatureDescription
post(data, source?)(data: any, source?: uploadSource | null): Promise<string>Uploads file data as multipart form. The data object's keys become form fields; each value must be a File or Blob with a .name property. Returns the server-assigned filename on success. The source parameter adds a ?source=attach query parameter for server-side routing.

Usage example

// From a file input element
const fileInput = document.getElementById('file') as HTMLInputElement;
const file = fileInput.files[0];

try {
const serverFilename = await this.upload.post({ file }, uploadSource.attach);
console.log('Uploaded as:', serverFilename);
// Use serverFilename to reference the file in subsequent API calls
} catch (error) {
this.toast.ShowAlert('danger', error);
}

Gotchas and tips

  • The upload endpoint is common/upload.php: this is hardcoded relative to AppConstants.baseURL.
  • Error messages are translated: on server errors, the service returns translated error strings from languageService.tr().
  • Multipart serializer: when running on Cordova, httpService is configured with serializer: 'multipart' for this request.

downloadService

Introduction

downloadService downloads files through the server's common/download.php proxy endpoint. Rather than downloading directly from the source URL (which might require authentication or have CORS restrictions), the server fetches the file and returns it as a base64-encoded JSON response. The service then decodes this to an ArrayBuffer that can be used to create a Blob.

When to use

  • Downloading reports, PDFs, or other files through the backend proxy.
  • This service is used internally by adhocService.Download(), which adds loading indicators and browser download triggers.
import { downloadService } from '@unpispas/upp-base';

constructor(private download: downloadService) {}

Methods

MethodSignatureDescription
file(url)(url: string): Promise<ArrayBuffer>Downloads a file by URL through the server proxy. The URL is passed as a query parameter to common/download.php. The server fetches the resource and returns { errorcode: 0, base64: "..." }. The service decodes the base64 to an ArrayBuffer.

Usage example

try {
const buffer = await this.download.file('https://example.com/report.pdf');
const blob = new Blob([buffer], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
// Use url in an <a> tag or window.open()
} catch (error) {
console.error('Download failed:', error);
}

Gotchas and tips

  • All downloads go through the server: the browser never fetches the file directly. This means the server must have network access to the source URL.
  • Base64 encoding increases transfer size by ~33%: this is the tradeoff for using a JSON-based proxy. Large files will take longer to transfer.
  • Prefer adhocService.Download(): it wraps this service with loading indicators and automatic browser download triggering. Only use downloadService directly if you need the raw ArrayBuffer.
ServiceRelationship
httpServiceuploadService and downloadService use it for HTTP requests. deviceService uses it for the remote WebSocket relay.
adhocServicedeviceService uses it for the Available() check. adhocService.Download() wraps downloadService.
toastServiceadhocService.Download() shows/hides the loading indicator.
languageServiceuploadService and downloadService use it for error message translation.