Skip to content

Latest commit

 

History

History
201 lines (152 loc) · 5.27 KB

File metadata and controls

201 lines (152 loc) · 5.27 KB

printsocket

Node.js client for the PrintSocket cloud print API.

A lightweight agent runs on a machine, connects outbound to PrintSocket, and exposes that machine's printers (and scales) to a REST API. This library wraps API v1: devices, printers, scales, documents, print jobs, webhooks, and API keys, plus webhook signature verification for your receiver.

Zero runtime dependencies. Requires Node.js 18.17 or newer. Full API documentation lives at www.printsocket.com/docs.

Install

npm install printsocket

Quickstart

An sk_test_ key comes with a virtual device and printer that runs the full job lifecycle, so this works before any hardware is enrolled:

import PrintSocket from "printsocket";

const ps = new PrintSocket({ apiKey: process.env.PRINTSOCKET_API_KEY });

const { data: printers } = await ps.printers.list({ state: "online" });

const job = await ps.jobs.create({
  printer_id: printers[0].id,
  title: "Order #12345 label",
  content: { format: "pdf", url: "https://example.com/label.pdf" },
  metadata: { order_id: "12345" },
});

console.log(job.id, job.status); // job_...  queued

CommonJS works too:

const { PrintSocket } = require("printsocket");

Configuration

const ps = new PrintSocket({
  apiKey: "sk_live_...",           // required
  baseUrl: "https://api.printsocket.com/v1", // default
  timeout: 30_000,                 // ms per attempt
  maxRetries: 2,                   // connection failures, 429s, and 5xx
});

Errors

Every API error throws a typed subclass of APIError carrying status, type, code, param, and requestId (quote the request id in support requests):

import { BillingError, ConflictError, NotFoundError } from "printsocket";

try {
  await ps.jobs.cancel(jobId);
} catch (err) {
  if (err instanceof ConflictError && err.code === "job_not_cancelable") {
    // already printing or finished
  } else {
    throw err;
  }
}

The classes are InvalidRequestError, AuthenticationError, PermissionError, NotFoundError, ConflictError, RateLimitError, BillingError, and ServerError, one per error.type the API returns. Requests that never got a response throw APIConnectionError.

Retries and idempotency

Connection failures, 429s, and 5xx responses are retried automatically (maxRetries, default 2), honoring Retry-After. Every POST carries an Idempotency-Key header, generated when you do not pass one, and the key is identical across the client's own retry attempts, so a retried create cannot produce a duplicate job. To extend the guarantee across your own retries, pass a key derived from your record:

await ps.jobs.create(params, { idempotencyKey: `order-12345-label` });

Pagination

List calls return one page (data, has_more, next_cursor). Each list resource also has iterate(), which follows cursors for you:

for await (const job of ps.jobs.iterate({ status: "failed", limit: 100 })) {
  console.log(job.id, job.error?.message);
}

Documents

Upload once, print many times:

import { readFile } from "node:fs/promises";

const doc = await ps.documents.upload({
  content: await readFile("packing-slip.pdf"),
  content_type: "application/pdf",
  expire_after_seconds: 3600,
});

await ps.jobs.create({
  printer_id: "prn_...",
  content: { format: "pdf", document_id: doc.id },
});

documents.createFromURL({ source_url }) has the API fetch the file server-side instead.

Webhooks

Subscribe with the client, verify deliveries with the webhooks helpers. Verification needs the raw request body; a parsed and re-serialized body will not match the signature.

import { webhooks, WebhookVerificationError } from "printsocket";
import express from "express";

const endpoint = await ps.webhooks.create({
  url: "https://example.com/printsocket/webhook",
  events: ["job.*", "printer.state_changed"],
});
// endpoint.secret is shown only this once; store it.

const app = express();
app.post(
  "/printsocket/webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    let event;
    try {
      event = webhooks.constructEvent(
        req.body,
        req.headers["printsocket-signature"],
        process.env.PRINTSOCKET_WEBHOOK_SECRET,
      );
    } catch (err) {
      if (err instanceof WebhookVerificationError) return res.sendStatus(400);
      throw err;
    }
    // Delivery is at-least-once: dedupe on event.id before acting.
    console.log(event.type, event.data.id);
    res.sendStatus(200);
  },
);

Enrolling devices

Generate a short-lived, single-use token server-side and hand it to the agent installer, so your API keys never touch a customer machine:

const { token } = await ps.enrollmentTokens.create({ name: "Front desk PC" });

Scales

const scale = await ps.scales.get("scl_...");
if (scale.reading?.stable) {
  console.log(scale.reading.weight_grams, "g at", scale.reading.captured_at);
}

TypeScript

Written in TypeScript; types ship with the package. Wire fields are snake_case, matching the API reference exactly. The API adds fields and enum values without a version bump, so string unions are deliberately open.

Development

npm install
npm test   # builds ESM + CJS, then runs node --test

License

MIT