UNPKG

camstreamerlib

Version:

Helper library for CamStreamer ACAP applications.

252 lines (251 loc) 9.22 kB
import { paramToUrl, responseStringify } from './internal/utils'; import { ImageType, } from './types/CamOverlayAPI'; import { ParsingBlobError, ServiceNotFoundError } from './errors/errors'; import { networkCameraListSchema } from './types/common'; import { z } from 'zod'; import { widgetsSchema } from './models/CamOverlayAPI/widgetsSchema'; import { fileListSchema, storageSchema } from './models/CamOverlayAPI/fileSchema'; export const BASE_URL = '/local/camoverlay/api'; export class CamOverlayAPI { client; constructor(client) { this.client = client; } static getProxyUrlPath = () => `${BASE_URL}/proxy.cgi`; static getFilePreviewPath = (path) => `${BASE_URL}/image.cgi?path=${encodeURIComponent(path)}`; async checkCameraTime() { const responseSchema = z.discriminatedUnion('state', [ z.object({ state: z.literal(true), code: z.number(), }), z.object({ state: z.literal(false), code: z.number(), reason: z.union([ z.literal('INVALID_TIME'), z.literal('COULDNT_RESOLVE_HOST'), z.literal('CONNECTION_ERROR'), ]), message: z.string(), }), ]); const response = await this._get(`${BASE_URL}/camera_time.cgi`); const cameraTime = responseSchema.parse(response); if (!cameraTime.state) { console.error(`Camera time check failed: ${cameraTime.reason} - ${cameraTime.message}`); } return cameraTime.state; } async getNetworkCameraList() { const response = await this._get(`${BASE_URL}/network_camera_list.cgi`); return networkCameraListSchema.parse(response.camera_list); } async wsAuthorization() { const responseSchema = z.object({ status: z.number(), message: z.string(), data: z.string(), }); const response = await this._get(`${BASE_URL}/ws_authorization.cgi`); return responseSchema.parse(response).data; } async getMjpegStreamImage(mjpegUrl) { return await this._getBlob(`${BASE_URL}/fetch_mjpeg_image.cgi?mjpeg_url=${encodeURIComponent(decodeURIComponent(mjpegUrl))}`); } async listFiles(fileType) { const fileDataSchema = z.object({ code: z.number(), list: fileListSchema, }); const files = await this._get(`${BASE_URL}/upload_${fileType}.cgi`, { action: 'list', }); return fileListSchema.parse(files.list); } async uploadFile(fileType, formData, storage) { const path = `${BASE_URL}/upload_${fileType}.cgi`; await this._post(path, formData, { action: 'upload', storage: storage, }); } async removeFile(fileType, fileParams) { const path = `${BASE_URL}/upload_${fileType}.cgi`; await this._postUrlEncoded(path, { action: 'remove', ...fileParams, }); } async getFileStorage(fileType) { const storageDataListSchema = z.array(z.object({ type: storageSchema, state: z.string(), })); const responseSchema = z.object({ code: z.number(), list: storageDataListSchema, }); const data = await this._get(`${BASE_URL}/upload_${fileType}.cgi`, { action: 'get_storage', }); if (data.code !== 200) { throw new Error('Error occured while fetching file storage data'); } return storageDataListSchema.parse(data.list); } async getFilePreviewFromCamera(path) { return await this._getBlob(CamOverlayAPI.getFilePreviewPath(path)); } async updateInfoticker(serviceID, text) { await this._get(`${BASE_URL}/infoticker.cgi?service_id=${serviceID}&text=${text}`); } async setEnabled(serviceID, enabled) { await this._post(`${BASE_URL}/enabled.cgi?id_${serviceID}=${enabled ? 1 : 0}`, ''); } async isEnabled(serviceID) { const res = await this.client.get(`${BASE_URL}/services.cgi?action=get`); if (res.ok) { const data = JSON.parse(await res.text()); for (const service of data.services) { if (service.id === serviceID) { return service.enabled === 1; } } throw new ServiceNotFoundError(); } else { throw new Error(await responseStringify(res)); } } async getSingleWidget(serviceId) { const data = await this._get(`${BASE_URL}/services.cgi`, { action: 'get', service_id: serviceId.toString(), }); return widgetsSchema.parse(data); } async getWidgets() { const widgetList = await this._get(`${BASE_URL}/services.cgi`, { action: 'get', }); const widgets = widgetList.services; widgets.forEach((widget) => { const parsedWidget = widgetsSchema.safeParse(widget); if (!parsedWidget.success) { console.warn(`[SERVICE SCHEMA MISMATCH]: Service ${widget.name} (${widget.id}) does not match the current schema, or is a hidden service.`); } }); return widgets; } async updateSingleWidget(widget) { const path = `${BASE_URL}/services.cgi`; await this._postJsonEncoded(path, JSON.stringify(widget), { action: 'set', service_id: widget.id.toString(), }); } async updateWidgets(widgets) { const path = `${BASE_URL}/services.cgi`; await this._postJsonEncoded(path, JSON.stringify({ services: widgets }), { action: 'set', }); } updateCGText(serviceID, fields) { const params = {}; for (const field of fields) { const name = field.field_name; params[name] = field.text; if (field.color !== undefined) { params[`${name}_color`] = field.color; } } return this.promiseCGUpdate(serviceID, 'update_text', params); } updateCGImagePos(serviceID, coordinates = '', x = 0, y = 0) { const params = { coord_system: coordinates, pos_x: x, pos_y: y, }; return this.promiseCGUpdate(serviceID, 'update_image', params); } updateCGImage(serviceID, path, coordinates = '', x = 0, y = 0) { const params = { coord_system: coordinates, pos_x: x, pos_y: y, image: path, }; return this.promiseCGUpdate(serviceID, 'update_image', params); } updateCGImageFromData(serviceID, imageType, imageData, coordinates = '', x = 0, y = 0) { const contentType = imageType === ImageType.PNG ? 'image/png' : 'image/jpeg'; const params = { coord_system: coordinates, pos_x: x, pos_y: y, }; return this.promiseCGUpdate(serviceID, 'update_image', params, contentType, imageData); } async promiseCGUpdate(serviceID, action, params = {}, contentType, data) { const path = `${BASE_URL}/customGraphics.cgi`; let headers = {}; if (contentType !== undefined && data) { headers = { 'Content-Type': contentType }; } const res = await this.client.post(path, data ?? '', { action: action, service_id: serviceID.toString(), ...params, }, headers); if (!res.ok) { throw new Error(await responseStringify(res)); } } async _get(...args) { const res = await this.client.get(...args); if (res.ok) { return (await res.json()); } else { throw new Error(await responseStringify(res)); } } async _post(...args) { const res = await this.client.post(...args); if (res.ok) { return (await res.json()); } else { throw new Error(await responseStringify(res)); } } async _getBlob(...args) { const res = await this.client.get(...args); if (res.ok) { return await this.parseBlobResponse(res); } else { throw new Error(await responseStringify(res)); } } async parseBlobResponse(response) { try { return (await response.blob()); } catch (err) { throw new ParsingBlobError(err); } } async _postUrlEncoded(path, params, headers) { const data = paramToUrl(params); const baseHeaders = { 'Content-Type': 'application/x-www-form-urlencoded' }; return this._post(path, data, {}, { ...baseHeaders, ...headers }); } async _postJsonEncoded(...args) { const [path, data, params, headers] = args; const baseHeaders = { 'Accept': 'application/json', 'Content-Type': 'application/json' }; return this._post(path, data, params, { ...baseHeaders, ...headers }); } }