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
| Type | Code | Description |
|---|---|---|
| Local | WSL | Direct 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. |
| Remote | WSR | Messages sent via REST POST to the relay server. One-way request/response only. No keep-alive or push notifications. |
Methods
| Method | Signature | Description |
|---|---|---|
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): void | Closes 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 Connect | Connects, 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)
| Member | Type | Description |
|---|---|---|
svcname | string | null | Service name reported by the remote side (e.g. 'RECEIPT_PRINTER'). Updated after the first successful message exchange. |
version | string | null | Service version reported by the remote side. |
contype | WsConnType | 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() | void | Closes the connection and emits OnDisconnected. |
OnDisconnected | Observable<void> | Emits when the connection is lost or intentionally closed. |
OnMessageSent | Observable<any> | Emits when a message is sent. |
OnMessageRecv | Observable<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)
| Member | Type | Description |
|---|---|---|
OnInputRead | Observable<string> | Emits data read from the device (e.g. weight readings from a scale). |
OnInputLost | Observable<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
PINGto theBASEmodule 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.
InputScaleonly works with local connections: continuous reading requires bidirectional communication, which is only available with WSL connections.Disconnect()clears service references: after disconnecting, the internalLocalServiceandRemoteServiceinstances are set tonull, so the nextConnectcall 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
| Method | Signature | Description |
|---|---|---|
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 toAppConstants.baseURL. - Error messages are translated: on server errors, the service returns translated error strings from
languageService.tr(). - Multipart serializer: when running on Cordova,
httpServiceis configured withserializer: '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
| Method | Signature | Description |
|---|---|---|
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 usedownloadServicedirectly if you need the rawArrayBuffer.
Related services
| Service | Relationship |
|---|---|
httpService | uploadService and downloadService use it for HTTP requests. deviceService uses it for the remote WebSocket relay. |
adhocService | deviceService uses it for the Available() check. adhocService.Download() wraps downloadService. |
toastService | adhocService.Download() shows/hides the loading indicator. |
languageService | uploadService and downloadService use it for error message translation. |