From 8bbf734e4fe8897006d627897e8ff69c0c035e35 Mon Sep 17 00:00:00 2001 From: skalkii Date: Mon, 20 Apr 2026 21:04:22 +0530 Subject: [PATCH 1/4] fix: add node: prefix to all Node.js built-in module imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #12 — prefixes all Node.js built-in imports with `node:` to make the library compatible with Deno and align with modern Node.js best practices (supported since Node.js v14.18+/v16+). Files updated: - src/utils/upload.ts (fs, path) - src/capture/installer.ts (fs, path, https, crypto, child_process) - src/capture/binaryManager.ts (child_process, readline, events, path) - src/capture/captureClient.ts (events) - lib/installer.js (fs, path, https, crypto, child_process) Co-Authored-By: Claude Sonnet 4.6 --- lib/installer.js | 10 +++++----- src/capture/binaryManager.ts | 14 +++++++------- src/capture/captureClient.ts | 7 ++++--- src/capture/installer.ts | 27 ++++++++++++++++----------- src/utils/upload.ts | 7 ++++--- 5 files changed, 36 insertions(+), 29 deletions(-) diff --git a/lib/installer.js b/lib/installer.js index c9a539e..329f8f6 100644 --- a/lib/installer.js +++ b/lib/installer.js @@ -1,8 +1,8 @@ -const fs = require('fs'); -const path = require('path'); -const https = require('https'); -const crypto = require('crypto'); -const { execFileSync } = require('child_process'); +const fs = require('node:fs'); +const path = require('node:path'); +const https = require('node:https'); +const crypto = require('node:crypto'); +const { execFileSync } = require('node:child_process'); const tar = require('tar'); const MACOS_APP_BUNDLE = 'VideoDBCapture.app'; diff --git a/src/capture/binaryManager.ts b/src/capture/binaryManager.ts index 5612b7f..36fe3ed 100644 --- a/src/capture/binaryManager.ts +++ b/src/capture/binaryManager.ts @@ -1,6 +1,6 @@ -import { spawn, type ChildProcess } from 'child_process'; -import * as readline from 'readline'; -import { EventEmitter } from 'events'; +import { spawn, type ChildProcess } from 'node:child_process'; +import * as readline from 'node:readline'; +import { EventEmitter } from 'node:events'; import { v4 as uuidv4 } from 'uuid'; import { PROTOCOL_PREFIX, @@ -49,7 +49,7 @@ export class BinaryManager extends EventEmitter { private getBinaryCommand(): { command: string; args: string[] } { if (this.isDev) { // In dev mode, use a mock binary (Node.js script) - const path = require('path'); + const path = require('node:path'); return { command: 'node', args: [path.join(__dirname, '..', '..', 'mock', 'binary.js')], @@ -88,7 +88,7 @@ export class BinaryManager extends EventEmitter { */ private handleMessage(msg: BinaryMessage): void { if (msg.type === 'response') { - const response = msg as BinaryResponse; + const response = msg; const { commandId, status, result } = response; const promise = this.pendingCommands.get(commandId); if (promise) { @@ -100,7 +100,7 @@ export class BinaryManager extends EventEmitter { this.pendingCommands.delete(commandId); } } else if (msg.type === 'event') { - const event = msg as BinaryEvent; + const event = msg; this.emit(event.event, event.payload); } } @@ -301,7 +301,7 @@ export class BinaryManager extends EventEmitter { resolve: resolve as (value: unknown) => void, reject, }); - this.process!.stdin!.write(payload, (err) => { + this.process!.stdin!.write(payload, err => { if (err) { const pending = this.pendingCommands.get(commandId); if (pending) { diff --git a/src/capture/captureClient.ts b/src/capture/captureClient.ts index 7f72675..81ce949 100644 --- a/src/capture/captureClient.ts +++ b/src/capture/captureClient.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from 'events'; +import { EventEmitter } from 'node:events'; import { BinaryManager } from './binaryManager'; import { PermissionType, @@ -244,8 +244,9 @@ export class CaptureClient extends EventEmitter implements ChannelClient { throw new Error('channels must include channelId for each channel'); } - const primaryVideo = channels.find(ch => ch.is_primary && ch.type === 'video') - || channels.find(ch => ch.type === 'video'); + const primaryVideo = + channels.find(ch => ch.is_primary && ch.type === 'video') || + channels.find(ch => ch.type === 'video'); await this.binaryManager.sendCommand('startRecording', { uploadToken: this.sessionToken, diff --git a/src/capture/installer.ts b/src/capture/installer.ts index caa6bf6..4e33a93 100644 --- a/src/capture/installer.ts +++ b/src/capture/installer.ts @@ -1,8 +1,8 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as https from 'https'; -import * as crypto from 'crypto'; -import { execFileSync } from 'child_process'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as https from 'node:https'; +import * as crypto from 'node:crypto'; +import { execFileSync } from 'node:child_process'; import type { BinaryConfig, PlatformInfo } from './types'; /** Name of the macOS .app bundle that wraps the capture binary for TCC compatibility */ @@ -39,9 +39,12 @@ export class RecorderInstaller { baseUrl: 'https://artifacts.videodb.io/capture', version: '0.3.1', checksums: { - 'darwin-x64': '8b456607ba3628092081d92c1a22fcf4e8156f4e83b2d3d119bf0244eaa870b2', - 'darwin-arm64': 'cefc35883acd53f63dc50f8deb186ea0a8e17c65e646af8e81a309251a220b9d', - 'win32-x64': 'e388639c15ab35ac32179d3fc05a363f0f71d4d90265c6b48fb1af56ecae7736', + 'darwin-x64': + '8b456607ba3628092081d92c1a22fcf4e8156f4e83b2d3d119bf0244eaa870b2', + 'darwin-arm64': + 'cefc35883acd53f63dc50f8deb186ea0a8e17c65e646af8e81a309251a220b9d', + 'win32-x64': + 'e388639c15ab35ac32179d3fc05a363f0f71d4d90265c6b48fb1af56ecae7736', }, }; @@ -93,7 +96,7 @@ export class RecorderInstaller { MACOS_APP_BUNDLE, 'Contents', 'MacOS', - 'capture', + 'capture' ); } else if (process.platform === 'win32') { binPath = path.join(this.binDir, 'capture.exe'); @@ -305,7 +308,9 @@ export class RecorderInstaller { execFileSync('codesign', ['--force', '--sign', '-', appBundlePath]); console.log('VideoDB Capture: Code signed .app bundle'); } catch (e) { - console.warn('VideoDB Capture: codesign failed, screen recording may not work on macOS 26+'); + console.warn( + 'VideoDB Capture: codesign failed, screen recording may not work on macOS 26+' + ); } } } @@ -353,7 +358,7 @@ export class RecorderInstaller { } return new Promise(resolve => { - const { spawn } = require('child_process'); + const { spawn } = require('node:child_process'); const binaryPath = this.getBinaryPath(); const proc = spawn(binaryPath, ['--version']); diff --git a/src/utils/upload.ts b/src/utils/upload.ts index d7bad5d..cb2a726 100644 --- a/src/utils/upload.ts +++ b/src/utils/upload.ts @@ -7,8 +7,8 @@ import { Audio } from '@/core/audio'; import { Image } from '@/core/image'; import type { AudioBase, ImageBase, VideoBase } from '@/interfaces/core'; import FormData from 'form-data'; -import { createReadStream } from 'fs'; -import { parse } from 'path'; +import { createReadStream } from 'node:fs'; +import { parse } from 'node:path'; import { HttpClient } from './httpClient'; const { upload_url, collection, upload } = ApiPath; @@ -62,7 +62,8 @@ export const uploadToServer = async ( urlToUpload = data.url; } - const name = data.name || ('filePath' in data ? parse(data.filePath).name : undefined); + const name = + data.name || ('filePath' in data ? parse(data.filePath).name : undefined); const finalData = { url: urlToUpload, From e587c559e0095eea6846235550e9dc4138b51df6 Mon Sep 17 00:00:00 2001 From: Om Gate Date: Mon, 1 Jun 2026 19:36:47 +0530 Subject: [PATCH 2/4] feat: custom headers --- src/core/connection.ts | 37 +++++++++++++++++++++++++--------- src/utils/httpClient.ts | 44 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/core/connection.ts b/src/core/connection.ts index cd0a1f1..dc3598c 100644 --- a/src/core/connection.ts +++ b/src/core/connection.ts @@ -18,7 +18,11 @@ import type { ListCaptureSessionsConfig, CreateCaptureSessionConfig, } from '@/types/capture'; -import { HttpClient, type HttpClientAuthConfig } from '@/utils/httpClient'; +import { + HttpClient, + type HttpClientAuthConfig, + type HttpClientOptions, +} from '@/utils/httpClient'; import { uploadToServer } from '@/utils/upload'; const { @@ -40,13 +44,18 @@ const { } = ApiPath; class VdbHttpClient extends HttpClient { - constructor(baseURL: string, authConfig: HttpClientAuthConfig); - constructor(baseURL: string, apiKey: string); constructor( baseURL: string, - authConfigOrApiKey: HttpClientAuthConfig | string + authConfig: HttpClientAuthConfig, + options?: HttpClientOptions + ); + constructor(baseURL: string, apiKey: string, options?: HttpClientOptions); + constructor( + baseURL: string, + authConfigOrApiKey: HttpClientAuthConfig | string, + options?: HttpClientOptions ) { - super(baseURL, authConfigOrApiKey as HttpClientAuthConfig); + super(baseURL, authConfigOrApiKey as HttpClientAuthConfig, options); } } @@ -63,22 +72,32 @@ export class Connection { * Create a connection with auth configuration * @param baseURL - Base URL for the API * @param authConfig - Authentication configuration (apiKey or sessionToken) + * @param options - Optional client configuration (custom headers). + * `headers` are auto-formatted to `x-kebab-case`, mirroring + * the kwarg-to-header behavior of `videodb-python`. */ - constructor(baseURL: string, authConfig: HttpClientAuthConfig); + constructor( + baseURL: string, + authConfig: HttpClientAuthConfig, + options?: HttpClientOptions + ); /** * Create a connection with API key (legacy signature) * @param baseURL - Base URL for the API * @param apiKey - API key for authentication + * @param options - Optional client configuration (custom headers) * @deprecated Use the object-based constructor instead */ - constructor(baseURL: string, apiKey: string); + constructor(baseURL: string, apiKey: string, options?: HttpClientOptions); constructor( baseURL: string, - authConfigOrApiKey: HttpClientAuthConfig | string + authConfigOrApiKey: HttpClientAuthConfig | string, + options?: HttpClientOptions ) { this.vhttp = new VdbHttpClient( baseURL, - authConfigOrApiKey as HttpClientAuthConfig + authConfigOrApiKey as HttpClientAuthConfig, + options ); } diff --git a/src/utils/httpClient.ts b/src/utils/httpClient.ts index f6339f1..6724dc7 100644 --- a/src/utils/httpClient.ts +++ b/src/utils/httpClient.ts @@ -34,6 +34,32 @@ export interface HttpClientAuthConfig { sessionToken?: string; } +/** + * Optional configuration for HttpClient + * + * `headers` is a free-form bag of extra headers merged into every + * request. Keys are auto-formatted to `x-kebab-case` to mirror + * `videodb-python`'s kwarg-to-header behavior, e.g. `org_id` → `x-org-id`. + */ +export interface HttpClientOptions { + headers?: Record; +} + +const formatExtraHeaders = ( + headers?: Record +): Record => { + if (!headers) return {}; + const formatted: Record = {}; + for (const [key, value] of Object.entries(headers)) { + const normalized = key.toLowerCase().replace(/_/g, '-'); + const headerKey = normalized.startsWith('x-') + ? normalized + : `x-${normalized}`; + formatted[headerKey] = value; + } + return formatted; +}; + /** * Api initialization to make axios config * options available to all child classes @@ -55,18 +81,29 @@ export class HttpClient { * Create an HttpClient with auth configuration * @param baseURL - Base URL for the API * @param authConfig - Authentication configuration (apiKey or sessionToken) + * @param options - Optional client configuration (custom headers) */ - protected constructor(baseURL: string, authConfig: HttpClientAuthConfig); + protected constructor( + baseURL: string, + authConfig: HttpClientAuthConfig, + options?: HttpClientOptions + ); /** * Create an HttpClient with API key (legacy signature) * @param baseURL - Base URL for the API * @param apiKey - API key for authentication + * @param options - Optional client configuration (custom headers) * @deprecated Use the object-based constructor instead */ - protected constructor(baseURL: string, apiKey: string); protected constructor( baseURL: string, - authConfigOrApiKey: HttpClientAuthConfig | string + apiKey: string, + options?: HttpClientOptions + ); + protected constructor( + baseURL: string, + authConfigOrApiKey: HttpClientAuthConfig | string, + options?: HttpClientOptions ) { // Handle both signatures const authConfig: HttpClientAuthConfig = @@ -83,6 +120,7 @@ export class HttpClient { headers: { 'x-access-token': token || '', 'x-videodb-client': SDK_CLIENT_HEADER, + ...formatExtraHeaders(options?.headers), }, timeout: HttpClientDefaultValues.timeout, }); From 001a881bf22dee5320c190aae18faba185464ac8 Mon Sep 17 00:00:00 2001 From: Om Gate Date: Mon, 1 Jun 2026 20:05:04 +0530 Subject: [PATCH 3/4] feat: handle custom headers in `connect` method --- src/index.ts | 14 +++++++++----- src/types/capture.ts | 6 ++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9396948..e358c35 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,7 +38,7 @@ function connect( return new Connection(baseURL, { apiKey }); } - // Handle new signature: connect({ apiKey?, sessionToken?, baseUrl? }) + // Handle new signature: connect({ apiKey?, sessionToken?, baseUrl?, headers? }) const config = configOrApiKey; const url = config.baseUrl || VIDEO_DB_API; @@ -53,10 +53,14 @@ function connect( ); } - return new Connection(url, { - apiKey: config.apiKey, - sessionToken: config.sessionToken, - }); + return new Connection( + url, + { + apiKey: config.apiKey, + sessionToken: config.sessionToken, + }, + config.headers ? { headers: config.headers } : undefined + ); } export { Collection } from './core/collection'; diff --git a/src/types/capture.ts b/src/types/capture.ts index e34f50a..1b2ae9c 100644 --- a/src/types/capture.ts +++ b/src/types/capture.ts @@ -12,6 +12,12 @@ export interface ConnectionConfig { sessionToken?: string; /** Base URL for the API (optional, defaults to https://api.videodb.io) */ baseUrl?: string; + /** + * Extra headers to attach to every request. Keys are auto-formatted to + * `x-kebab-case` (e.g. `org_id` → `x-org-id`), mirroring `videodb-python`'s + * kwarg-to-header behavior. + */ + headers?: Record; } /** From c505fe6ca7fb6c353d5f44c0f7d6554ca240e6df Mon Sep 17 00:00:00 2001 From: Om Gate Date: Wed, 3 Jun 2026 16:43:57 +0530 Subject: [PATCH 4/4] fix: subtitle casing --- src/core/video.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/video.ts b/src/core/video.ts index 9254b1d..892c31f 100644 --- a/src/core/video.ts +++ b/src/core/video.ts @@ -26,13 +26,14 @@ import { NoDataResponse, } from '@/types/response'; import type { Timeline, Transcript } from '@/types/video'; -import { playStream, SnakeKeysToCamelCase } from '@/utils'; +import { fromCamelToSnake, playStream, SnakeKeysToCamelCase } from '@/utils'; import { HttpClient } from '@/utils/httpClient'; import { DefaultIndexType, DefaultSearchType, IndexTypeValues, SceneExtractionType, + SubtitleStyleDefaultValues, } from '@/core/config'; import { SearchFactory } from './search'; import { SearchResult } from './search/searchResult'; @@ -635,12 +636,13 @@ export class Video implements IVideo { * */ public addSubtitle = async (config?: Partial) => { + const merged: SubtitleStyleProps = { ...SubtitleStyleDefaultValues, ...config }; const res = await this.#vhttp.post< GenerateStreamResponse, - { type: string; subtitle_style: Partial } + { type: string; subtitle_style: Record } >([video, this.id, workflow], { type: Workflows.addSubtitles, - subtitle_style: { ...config }, + subtitle_style: fromCamelToSnake(merged), }); return res.data.streamUrl; };