@rawback/ccapi-js
v1.0.0
Published
Canon Camera Control API (CCAPI) client for TypeScript — Web-standard fetch/streams, zero runtime dependencies.
Maintainers
Readme
@rawback/ccapi-js
A Canon Camera Control API (CCAPI) client for TypeScript, built on the
Web standard fetch and Streams APIs with zero runtime dependencies. It
runs unchanged in browsers, Node 26+, Deno, and Bun.
CCAPI is a plain HTTP(S) REST API: the camera is the HTTP server, your
application is the client. Endpoints follow
http://[IP]:[Port]/ccapi/[Version]/... and use application/json bodies.
This is the TypeScript port of the Swift reference client (RawbackCCAPI) and is
driven by the same source of truth — the CCAPI Markdown reference under
docs/api/ in the rawback-app/ccc-api
repository.
Install
pnpm add @rawback/ccapi-jsQuick start
import { CCAPIClient } from '@rawback/ccapi-js';
// Older bodies serve HTTP on :8080; newer ones HTTPS on :443.
const client = new CCAPIClient({ host: '192.168.1.2', port: 8080, useTLS: false });
// 1. CCAPI is discovery-driven — connect() learns which version serves each endpoint.
const { apiVersion, device, storage } = await client.connect();
console.log(device.productName, apiVersion);
// 2. Drive the typed, namespaced endpoints — no version argument; it's auto-resolved.
const battery = await client.status.getBattery();
const beep = await client.settings.getBeep();
await client.settings.setBeep('disable');Discovery-driven — endpoints resolve their own version. The supported endpoints (and the version each lives at) vary by camera model and firmware, so call
connect()(orlistSupportedAPIs(), or passsupportedAPIsat construction) once; after that each endpoint method resolves its own version automatically — you never pass one. AVF-capable bodies don't supportGET /ccapi;connect()bootstraps vialistSupportedAPIsForDev('ver100')there.
API surface
The endpoints are grouped into namespaces on CCAPIClient, mirroring the
reference sections:
| Namespace | Docs § | Covers |
|---|---|---|
| top-level (connect, discoverVersion, listSupportedAPIs, getDeviceInformation, getStorage, getLens) | 4.2 / 4.3 / 4.4.1 / 4.4.6 | Discovery, fixed info, connection |
| client.status | 4.4 | Storage/directory destination, battery, temperature, power zoom |
| client.settings | 4.5 | Identity, date/time, format, beep, power, file naming, … |
| client.network | 4.5.11–4.5.34 | Network/Wi-Fi/SSL/CORS connection settings |
| client.customization | 4.6 | Exposure / ISO level increments |
| client.contents | 4.7 | Browse storages/directories, list/stream/get/modify/delete contents |
Listing and streaming contents
// One page (max 100 locators).
const page = await client.contents.listContents('card1', '100CANON', { type: 'jpeg' });
// Stream the full listing as page-sized arrays (Web async iteration).
for await (const locators of client.contents.streamContents('card1', '100CANON')) {
console.log(locators);
}
// Download as a Web ReadableStream (no in-memory buffering).
const stream = await client.contents.streamContentData({
storage: 'card1', directory: '100CANON', file: 'IMG_0001.JPG',
});
// e.g. in Node: import { Writable } from 'node:stream';
// await stream.pipeTo(Writable.toWeb(createWriteStream('IMG_0001.JPG')));
// Or get the bytes (optionally a byte range → HTTP 206).
const { data, contentType } = await client.contents.getContentData(locator, {
range: { start: 0, end: 1023 },
});Authentication
Digest auth (realm "CameraControlApi", algorithm "MD5", qop "auth") is
required only when a password is configured in the camera's CCAPI menu. Pass
credentials and the client answers the 401 challenge transparently and
pre-authenticates subsequent requests:
const client = new CCAPIClient({
host, port, credentials: { username: 'admin', password: 'secret' },
});CCAPI mandates MD5 for Digest, which the Web Crypto API intentionally does not implement, so a small self-contained MD5 is bundled. It is used only for the Digest handshake.
Errors
Every failure is a CCAPIError whose kind mirrors the documented HTTP status
codes (badRequest, unauthorized, forbidden, notActivated, conflict,
pairingRequired, rangeNotSatisfiable, serverError, notReachable, …). The
camera's { "message": "..." } body, when present, is attached as
cameraMessage. error.isRecoverable is true for pairingRequired /
notReachable.
import { CCAPIError } from '@rawback/ccapi-js';
try {
await client.contents.deleteContent(locator);
} catch (error) {
if (error instanceof CCAPIError && error.kind === 'conflict') {
console.warn('Protected:', error.cameraMessage); // "File protected"
}
}TLS and self-signed certificates (Node)
Newer cameras serve HTTPS with a self-signed certificate. Browsers handle
trust through their own UI; in Node you can inject a custom fetch backed by a
dispatcher that trusts the camera host:
import { Agent, fetch as undiciFetch } from 'undici';
const dispatcher = new Agent({ connect: { rejectUnauthorized: false } });
const client = new CCAPIClient({
host, port: 443,
fetch: ((input, init) => undiciFetch(input, { ...init, dispatcher })) as typeof fetch,
});Scope
- Camera discovery (SSDP / Bonjour
_ccapi._tcp) requires raw UDP sockets, which are not a Web standard and not available in browsers, so it is out of scope here. Supply the camera's host/port (from your own discovery, the camera screen, or your router) and the HTTP discovery endpoints (listSupportedAPIs) take it from there. - The underlying reference is generated from a PDF truncated at section
4.7.6, so only 4.2.1 → 4.7.6 (67 APIs) are implemented. Shooting Control,
Shooting Settings, Live View, and Chapters 5–6 are not yet covered — see the
ccc-apireference for details.
Example app
An interactive Vite + React + Tailwind playground lives in
examples/web/. Pull the repo, point it at your camera, and click
through every namespace (status, settings, customization, contents with thumbnails,
network/CORS, discovery) with a live request log. It consumes this package via
file:../.., so it also doubles as a manual smoke test of the public API.
cd examples/web
pnpm install
pnpm run dev # http://localhost:5173 — see examples/web/README.md for proxy/CORS setupDevelopment
pnpm install
pnpm run typecheck # tsc --noEmit
pnpm test # vitest (parses the shared JSON fixtures)
pnpm run build # tsdown → dist/ (ESM + CJS + .d.ts)All API content belongs to Canon Inc.; this client is a derived implementation of the published reference.
