From 63d25ddaf500b630862cbc3cbc195febf759082d Mon Sep 17 00:00:00 2001 From: Luke Date: Sun, 28 Apr 2024 11:59:29 +0100 Subject: [PATCH 01/12] feat: abstract client rebuilt for namespace options --- src/lib/constants.ts | 17 ++--- src/lib/helpers.ts | 23 +++++-- src/lib/types/DeepgramClientOptions.ts | 74 ++++++++++++++++----- src/packages/AbstractClient.ts | 92 ++++++++------------------ src/packages/AbstractRestfulClient.ts | 6 +- 5 files changed, 112 insertions(+), 100 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index f19cb922..34812fe6 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,6 +1,5 @@ -import { isBrowser } from "./helpers"; -import { DeepgramClientOptions } from "./types/DeepgramClientOptions"; -import { FetchOptions } from "./types/Fetch"; +import { convertProtocolToWs, isBrowser } from "./helpers"; +import { DefaultNamespaceOptions, DefaultClientOptions } from "./types/DeepgramClientOptions"; import { version } from "./version"; export const NODE_VERSION = process.versions.node; @@ -13,15 +12,11 @@ export const DEFAULT_HEADERS = { export const DEFAULT_URL = "https://api.deepgram.com"; -export const DEFAULT_GLOBAL_OPTIONS = { - url: DEFAULT_URL, +export const DEFAULT_GLOBAL_OPTIONS: DefaultNamespaceOptions = { + fetch: { options: { url: DEFAULT_URL, headers: DEFAULT_HEADERS } }, + websocket: { options: { url: convertProtocolToWs(DEFAULT_URL) } }, }; -export const DEFAULT_FETCH_OPTIONS: FetchOptions = { - headers: DEFAULT_HEADERS, -}; - -export const DEFAULT_OPTIONS: DeepgramClientOptions = { +export const DEFAULT_OPTIONS: DefaultClientOptions = { global: DEFAULT_GLOBAL_OPTIONS, - fetch: DEFAULT_FETCH_OPTIONS, }; diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 8e18f190..5cd798b5 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -17,11 +17,8 @@ export function stripTrailingSlash(url: string): string { export const isBrowser = () => typeof window !== "undefined"; export const isServer = () => typeof process !== "undefined"; -export function applySettingDefaults( - options: DeepgramClientOptions, - defaults: DeepgramClientOptions -): DeepgramClientOptions { - return merge(defaults, options); +export function applyDefaults(options: Partial, subordinate: Partial): S { + return merge(subordinate, options); } export function appendSearchParams( @@ -83,5 +80,19 @@ const isReadStreamSource = (providedSource: PrerecordedSource): providedSource i }; export class CallbackUrl extends URL { - private callbackUrl = true; + public callbackUrl = true; } + +export const convertProtocolToWs = (url: string | URL) => { + const convert = (string: string) => string.toLowerCase().replace(/(http)(s)?/gi, "ws$2"); + + if (url instanceof URL) { + return new URL(convert(url.toString())); + } + + if (typeof url === "string") { + return new URL(convert(url)); + } + + throw new Error("URL must be a string or `URL` object"); +}; diff --git a/src/lib/types/DeepgramClientOptions.ts b/src/lib/types/DeepgramClientOptions.ts index 4b4d40ce..5c1c0801 100644 --- a/src/lib/types/DeepgramClientOptions.ts +++ b/src/lib/types/DeepgramClientOptions.ts @@ -1,20 +1,62 @@ -import { Fetch, FetchOptions } from "./Fetch"; +import { FetchOptions } from "./Fetch"; +export type IFetch = typeof fetch; +export type IWebSocket = typeof WebSocket; + +/** + * Configures the options for a Deepgram client. + * + * The `DeepgramClientOptions` interface defines the configuration options for a Deepgram client. It includes options for various namespaces, such as `global`, `listen`, `manage`, `onprem`, `read`, and `speak`. Each namespace has its own options for configuring the transport, including the URL, proxy, and options for the fetch and WebSocket clients. + * + * The `global` namespace is used to configure options that apply globally to the Deepgram client. The other namespaces are used to configure options specific to different Deepgram API endpoints. + */ export interface DeepgramClientOptions { - global?: { - /** - * Optional headers for initializing the client. - */ - headers?: Record; - - /** - * The URL used to interact with production, On-prem and other Deepgram environments. Defaults to `api.deepgram.com`. - */ - url?: string; - }; - fetch?: FetchOptions; - _experimentalCustomFetch?: Fetch; - restProxy?: { - url: null | string; + global?: NamespaceOptions; + listen?: NamespaceOptions; + manage?: NamespaceOptions; + onprem?: NamespaceOptions; + read?: NamespaceOptions; + speak?: NamespaceOptions; + + /** + * Support introductory format + */ + [index: string]: any; + // _experimentalCustomFetch?: Fetch; + // restProxy?: { + // url: null | string; + // }; +} + +interface TransportFetchOptions extends TransportOptions, FetchOptions {} + +type TransportUrl = URL | string; + +interface TransportOptions { + url?: TransportUrl; + proxy?: { + url?: null | TransportUrl; }; } + +interface ITransport { + client?: C; + options?: O; +} +export interface NamespaceOptions { + fetch?: ITransport; + websocket?: ITransport; +} + +export type DefaultNamespaceOptions = { + fetch: { + options: { url: TransportUrl }; + }; + websocket: { + options: { url: TransportUrl }; + }; +} & NamespaceOptions; + +export type DefaultClientOptions = { + global: DefaultNamespaceOptions; +} & DeepgramClientOptions; diff --git a/src/packages/AbstractClient.ts b/src/packages/AbstractClient.ts index 1ef95d49..46c3971d 100644 --- a/src/packages/AbstractClient.ts +++ b/src/packages/AbstractClient.ts @@ -1,18 +1,29 @@ -import { DEFAULT_OPTIONS, DEFAULT_URL } from "../lib/constants"; +import { DEFAULT_OPTIONS } from "../lib/constants"; import { DeepgramError } from "../lib/errors"; -import { applySettingDefaults, stripTrailingSlash } from "../lib/helpers"; +import { applyDefaults } from "../lib/helpers"; import { DeepgramClientOptions } from "../lib/types"; +import { + DefaultClientOptions, + DefaultNamespaceOptions, + NamespaceOptions, +} from "../lib/types/DeepgramClientOptions"; /** - * Deepgram Client. + * Represents an abstract Deepgram client that provides a base implementation for interacting with the Deepgram API. * - * An isomorphic Javascript client for interacting with the Deepgram API. - * @see https://developers.deepgram.com + * The `AbstractClient` class is responsible for: + * - Initializing the Deepgram API key + * - Applying default options for the client and namespace + * - Providing a namespace for organizing API requests + * + * Subclasses of `AbstractClient` should implement the specific functionality for interacting with the Deepgram API. */ export abstract class AbstractClient { - protected baseUrl: URL; + public namespace: string = "global"; + protected namespaceOptions: DefaultNamespaceOptions; + protected options: DefaultClientOptions; - constructor(protected key: string, protected options: DeepgramClientOptions) { + constructor(protected key: string, options: DeepgramClientOptions) { this.key = key; if (!key) { @@ -23,67 +34,20 @@ export abstract class AbstractClient { throw new DeepgramError("A deepgram API key is required"); } - this.options = applySettingDefaults(options, DEFAULT_OPTIONS); - - if (!this.options.global?.url) { - throw new DeepgramError( - `An API URL is required. It should be set to ${DEFAULT_URL} by default. No idea what happened!` - ); - } - - let baseUrlString: string = this.options.global.url; - let proxyUrlString: string; - /** - * Check if the base URL provided is missing a protocol and warn in the console. + * Apply default options. */ - if (!baseUrlString.startsWith("http") && !baseUrlString.startsWith("ws")) { - console.warn( - `The base URL provided does not begin with http, https, ws, or wss and will default to https as standard.` - ); - } + this.options = applyDefaults( + options, + DEFAULT_OPTIONS + ); /** - * Applying proxy to base URL. + * Roll up options for this namespace. */ - if (this.options.restProxy?.url) { - /** - * Prevent client using a real API key when using a proxy configuration. - */ - if (this.key !== "proxy") { - throw new DeepgramError( - `Do not attempt to pass any other API key than the string "proxy" when making proxied REST requests. Please ensure your proxy application is responsible for writing our API key to the Authorization header.` - ); - } - - proxyUrlString = this.options.restProxy.url; - - /** - * Check if the proxy URL provided is missing a protocol and warn in the console. - */ - if (!proxyUrlString.startsWith("http") && !proxyUrlString.startsWith("ws")) { - console.warn( - `The proxy URL provided does not begin with http, https, ws, or wss and will default to https as standard.` - ); - } - - baseUrlString = proxyUrlString; - } - - this.baseUrl = this.resolveBaseUrl(baseUrlString); - } - - protected resolveBaseUrl(url: string) { - if (!/^https?:\/\//i.test(url)) { - url = "https://" + url; - } - - return new URL(stripTrailingSlash(url)); - } - - protected willProxy() { - const proxyUrl = this.options.restProxy?.url; - - return !!proxyUrl; + this.namespaceOptions = applyDefaults( + this.options[this.namespace], + this.options.global! + ); } } diff --git a/src/packages/AbstractRestfulClient.ts b/src/packages/AbstractRestfulClient.ts index 1dd36cb7..80f412db 100644 --- a/src/packages/AbstractRestfulClient.ts +++ b/src/packages/AbstractRestfulClient.ts @@ -9,7 +9,7 @@ import { isBrowser } from "../lib/helpers"; export abstract class AbstractRestfulClient extends AbstractClient { protected fetch: Fetch; - constructor(protected key: string, protected options: DeepgramClientOptions) { + constructor(protected key: string, options: DeepgramClientOptions) { super(key, options); if (isBrowser() && !this._willProxy()) { @@ -18,7 +18,7 @@ export abstract class AbstractRestfulClient extends AbstractClient { ); } - this.fetch = fetchWithAuth(this.key, options._experimentalCustomFetch); + this.fetch = fetchWithAuth(this.key, this.namespaceOptions.fetch.client); } protected _getErrorMessage(err: any): string { @@ -153,7 +153,7 @@ export abstract class AbstractRestfulClient extends AbstractClient { } private _willProxy() { - const proxyUrl = this.options.restProxy?.url; + const proxyUrl = this.key === "proxy" && !!this.namespaceOptions.fetch.options.proxy?.url; return !!proxyUrl; } From 9a280d9a6f2e239037b7e527347164446a42c769 Mon Sep 17 00:00:00 2001 From: Luke Date: Sun, 28 Apr 2024 12:12:41 +0100 Subject: [PATCH 02/12] refactor: clearing up --- src/packages/AbstractLiveClient.ts | 8 ++++ ...RestfulClient.ts => AbstractRestClient.ts} | 14 +++---- src/packages/AbstractWsClient.ts | 42 ------------------- 3 files changed, 15 insertions(+), 49 deletions(-) create mode 100644 src/packages/AbstractLiveClient.ts rename src/packages/{AbstractRestfulClient.ts => AbstractRestClient.ts} (90%) delete mode 100644 src/packages/AbstractWsClient.ts diff --git a/src/packages/AbstractLiveClient.ts b/src/packages/AbstractLiveClient.ts new file mode 100644 index 00000000..5f32d4a3 --- /dev/null +++ b/src/packages/AbstractLiveClient.ts @@ -0,0 +1,8 @@ +import { DeepgramClientOptions } from "../lib/types"; +import { AbstractClient } from "./AbstractClient"; + +export abstract class AbstractLiveClient extends AbstractClient { + constructor(protected key: string, options: DeepgramClientOptions) { + super(key, options); + } +} diff --git a/src/packages/AbstractRestfulClient.ts b/src/packages/AbstractRestClient.ts similarity index 90% rename from src/packages/AbstractRestfulClient.ts rename to src/packages/AbstractRestClient.ts index 80f412db..85213dc0 100644 --- a/src/packages/AbstractRestfulClient.ts +++ b/src/packages/AbstractRestClient.ts @@ -6,15 +6,15 @@ import { AbstractClient } from "./AbstractClient"; import { DeepgramClientOptions } from "../lib/types"; import { isBrowser } from "../lib/helpers"; -export abstract class AbstractRestfulClient extends AbstractClient { +export abstract class AbstractRestClient extends AbstractClient { protected fetch: Fetch; constructor(protected key: string, options: DeepgramClientOptions) { super(key, options); - if (isBrowser() && !this._willProxy()) { + if (isBrowser() && !this.willProxy()) { throw new DeepgramError( - "Due to CORS we are unable to support REST-based API calls to our API from the browser. Please consider using a proxy, and including a `restProxy: { url: ''}` in your Deepgram client options." + "Due to CORS we are unable to support REST-based API calls to our API from the browser. Please consider using a proxy: https://dpgr.am/js-proxy for more information." ); } @@ -25,7 +25,7 @@ export abstract class AbstractRestfulClient extends AbstractClient { return err.msg || err.message || err.error_description || err.error || JSON.stringify(err); } - protected async handleError(error: unknown, reject: (reason?: any) => void) { + protected async _handleError(error: unknown, reject: (reason?: any) => void) { const Res = await resolveResponse(); if (error instanceof Res) { @@ -80,7 +80,7 @@ export abstract class AbstractRestfulClient extends AbstractClient { return result.json(); }) .then((data) => resolve(data)) - .catch((error) => this.handleError(error, reject)); + .catch((error) => this._handleError(error, reject)); }); } @@ -100,7 +100,7 @@ export abstract class AbstractRestfulClient extends AbstractClient { return result; }) .then((data) => resolve(data)) - .catch((error) => this.handleError(error, reject)); + .catch((error) => this._handleError(error, reject)); }); } @@ -152,7 +152,7 @@ export abstract class AbstractRestfulClient extends AbstractClient { return this._handleRequest(fetcher, "DELETE", url, headers, parameters); } - private _willProxy() { + protected willProxy() { const proxyUrl = this.key === "proxy" && !!this.namespaceOptions.fetch.options.proxy?.url; return !!proxyUrl; diff --git a/src/packages/AbstractWsClient.ts b/src/packages/AbstractWsClient.ts deleted file mode 100644 index 81a8b608..00000000 --- a/src/packages/AbstractWsClient.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { EventEmitter } from "events"; -import { DEFAULT_OPTIONS, DEFAULT_URL } from "../lib/constants"; -import { applySettingDefaults, stripTrailingSlash } from "../lib/helpers"; -import { DeepgramClientOptions } from "../lib/types"; - -export abstract class AbstractWsClient extends EventEmitter { - protected baseUrl: URL; - - constructor( - protected key: string, - protected options: DeepgramClientOptions | undefined = DEFAULT_OPTIONS - ) { - super(); - - this.key = key; - - if (!key) { - this.key = process.env.DEEPGRAM_API_KEY as string; - } - - if (!this.key) { - throw new Error("A deepgram API key is required"); - } - - this.options = applySettingDefaults(options, DEFAULT_OPTIONS); - - if (!this.options.global?.url) { - throw new Error( - `An API URL is required. It should be set to ${DEFAULT_URL} by default. No idea what happened!` - ); - } - - let url = this.options.global.url; - - if (!/^https?:\/\//i.test(url)) { - url = "https://" + url; - } - - this.baseUrl = new URL(stripTrailingSlash(url)); - this.baseUrl.protocol = this.baseUrl.protocol.toLowerCase().replace(/(http)(s)?/gi, "ws$2"); - } -} From 265bb24c9751f27b0a8cb83ebc850bef70e817a6 Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 29 Apr 2024 16:03:54 -0700 Subject: [PATCH 03/12] feat: rebuilding config and class interfaces --- index.ts | 14 +++++ src/DeepgramClient.ts | 48 +++++++++++---- src/index.ts | 40 +++++++++++-- src/lib/helpers.ts | 29 ++++++--- src/lib/types/DeepgramClientOptions.ts | 24 ++++++-- src/packages/AbstractClient.ts | 82 +++++++++++++++++++++++--- src/packages/AbstractLiveClient.ts | 13 +++- src/packages/AbstractRestClient.ts | 20 ++++--- src/packages/ListenClient.ts | 8 ++- src/packages/LiveClient.ts | 42 ++++++------- src/packages/ManageClient.ts | 54 +++++++++-------- src/packages/OnPremClient.ts | 14 +++-- src/packages/PrerecordedClient.ts | 14 +++-- src/packages/ReadClient.ts | 14 +++-- src/packages/SpeakClient.ts | 7 ++- src/packages/index.ts | 4 +- test/client.test.ts | 61 ++++++++++++++----- test/constants.test.ts | 4 +- test/helpers.test.ts | 4 +- 19 files changed, 360 insertions(+), 136 deletions(-) create mode 100644 index.ts diff --git a/index.ts b/index.ts new file mode 100644 index 00000000..54dd6c77 --- /dev/null +++ b/index.ts @@ -0,0 +1,14 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from "./src/index"; +export * from "./test/client.test"; +export * from "./test/constants.test"; +export * from "./test/errors.test"; +export * from "./test/helpers.test"; +export * from "./test/legacy.test"; +export * from "./test/live.test"; +export * from "./test/manage.test"; +export * from "./test/onprem.test"; +export * from "./test/prerecorded.test"; diff --git a/src/DeepgramClient.ts b/src/DeepgramClient.ts index 216abd3a..1aa09312 100644 --- a/src/DeepgramClient.ts +++ b/src/DeepgramClient.ts @@ -7,65 +7,91 @@ import { ReadClient } from "./packages/ReadClient"; import { SpeakClient } from "./packages/SpeakClient"; /** - * Deepgram Client. + * The DeepgramClient class provides access to various Deepgram API clients, including ListenClient, ManageClient, OnPremClient, ReadClient, and SpeakClient. * - * An isomorphic Javascript client for interacting with the Deepgram API. - * @see https://developers.deepgram.com/docs/js-sdk + * @see https://github.com/deepgram/deepgram-js-sdk */ export default class DeepgramClient extends AbstractClient { get listen(): ListenClient { - return new ListenClient(this.key, this.options); + return new ListenClient(this.options); } get manage(): ManageClient { - return new ManageClient(this.key, this.options); + return new ManageClient(this.options); } get onprem(): OnPremClient { - return new OnPremClient(this.key, this.options); + return new OnPremClient(this.options); } get read(): ReadClient { - return new ReadClient(this.key, this.options); + return new ReadClient(this.options); } get speak(): SpeakClient { - return new SpeakClient(this.key, this.options); + return new SpeakClient(this.options); } /** - * Major version fallback errors are below - * - * @see https://developers.deepgram.com/docs/js-sdk-v2-to-v3-migration-guide + * @deprecated + * @see https://dpgr.am/js-v3 */ get transcription(): any { throw new DeepgramVersionError(); } + /** + * @deprecated + * @see https://dpgr.am/js-v3 + */ get projects(): any { throw new DeepgramVersionError(); } + /** + * @deprecated + * @see https://dpgr.am/js-v3 + */ get keys(): any { throw new DeepgramVersionError(); } + /** + * @deprecated + * @see https://dpgr.am/js-v3 + */ get members(): any { throw new DeepgramVersionError(); } + /** + * @deprecated + * @see https://dpgr.am/js-v3 + */ get scopes(): any { throw new DeepgramVersionError(); } + /** + * @deprecated + * @see https://dpgr.am/js-v3 + */ get invitation(): any { throw new DeepgramVersionError(); } + /** + * @deprecated + * @see https://dpgr.am/js-v3 + */ get usage(): any { throw new DeepgramVersionError(); } + /** + * @deprecated + * @see https://dpgr.am/js-v3 + */ get billing(): any { throw new DeepgramVersionError(); } diff --git a/src/index.ts b/src/index.ts index 7c70a48b..d96c552d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,12 @@ import DeepgramClient from "./DeepgramClient"; import { DeepgramVersionError } from "./lib/errors"; -import type { DeepgramClientOptions } from "./lib/types"; +import { DeepgramClientOptions, IKeyFactory } from "./lib/types/DeepgramClientOptions"; /** - * Major version fallback error + * This class is deprecated and should not be used. It throws a `DeepgramVersionError` when instantiated. + * + * @deprecated + * @see https://dpgr.am/js-v3 */ class Deepgram { constructor(protected apiKey: string, protected apiUrl?: string, protected requireSSL?: boolean) { @@ -12,11 +15,36 @@ class Deepgram { } /** - * Creates a new Deepgram Client. + * Creates a new Deepgram client instance. + * + * @param {DeepgramClientArgs} args - Arguments to pass to the Deepgram client constructor. + * @returns A new Deepgram client instance. */ -const createClient = (apiKey: string, options: DeepgramClientOptions = {}): DeepgramClient => { - return new DeepgramClient(apiKey, options); -}; +// Constructor overloads +function createClient(): DeepgramClient; +function createClient(key?: string | IKeyFactory): DeepgramClient; +function createClient(options?: DeepgramClientOptions): DeepgramClient; +function createClient(key?: string | IKeyFactory, options?: DeepgramClientOptions): DeepgramClient; + +// Constructor implementation +function createClient( + keyOrOptions?: string | IKeyFactory | DeepgramClientOptions, + options?: DeepgramClientOptions +): DeepgramClient { + let resolvedOptions: DeepgramClientOptions = {}; + + if (typeof keyOrOptions === "string" || typeof keyOrOptions === "function") { + if (typeof options === "object") { + resolvedOptions = options; + } + + resolvedOptions.key = keyOrOptions; + } else if (typeof keyOrOptions === "object") { + resolvedOptions = keyOrOptions; + } + + return new DeepgramClient(resolvedOptions); +} export { createClient, DeepgramClient, Deepgram }; diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 5cd798b5..88738ef9 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -6,6 +6,8 @@ import { UrlSource, TextSource, AnalyzeSource, + LiveSchema, + TranscriptionSchema, } from "./types"; import { Readable } from "stream"; import merge from "deepmerge"; @@ -83,16 +85,27 @@ export class CallbackUrl extends URL { public callbackUrl = true; } -export const convertProtocolToWs = (url: string | URL) => { +export const convertProtocolToWs = (url: string) => { const convert = (string: string) => string.toLowerCase().replace(/(http)(s)?/gi, "ws$2"); - if (url instanceof URL) { - return new URL(convert(url.toString())); - } + return convert(url); +}; - if (typeof url === "string") { - return new URL(convert(url)); - } +export const buildRequestUrl = ( + endpoint: string, + baseUrl: string | URL, + transcriptionOptions: LiveSchema | TranscriptionSchema +): URL => { + const url = new URL(endpoint, baseUrl); + appendSearchParams(url.searchParams, transcriptionOptions); - throw new Error("URL must be a string or `URL` object"); + return url; }; + +export function isLiveSchema(arg: any): arg is LiveSchema { + return arg && typeof arg.interim_results !== "undefined"; +} + +export function isDeepgramClientOptions(arg: any): arg is DeepgramClientOptions { + return arg && typeof arg.global !== "undefined"; +} diff --git a/src/lib/types/DeepgramClientOptions.ts b/src/lib/types/DeepgramClientOptions.ts index 5c1c0801..981fdb89 100644 --- a/src/lib/types/DeepgramClientOptions.ts +++ b/src/lib/types/DeepgramClientOptions.ts @@ -1,8 +1,20 @@ import { FetchOptions } from "./Fetch"; +export type IKeyFactory = () => string; export type IFetch = typeof fetch; export type IWebSocket = typeof WebSocket; +/** + * Defines the arguments for creating a Deepgram client. + * + * The `DeepgramClientArgs` type represents the possible arguments that can be passed when creating a Deepgram client. It can be either: + * + * 1. An array with two elements: + * - The first element is a string or an `IKeyFactory` object, representing the API key. + * - The second element is a `DeepgramClientOptions` object, representing the configuration options for the Deepgram client. + * 2. An array with a single `DeepgramClientOptions` object, representing the configuration options for the Deepgram client. + */ + /** * Configures the options for a Deepgram client. * @@ -11,7 +23,8 @@ export type IWebSocket = typeof WebSocket; * The `global` namespace is used to configure options that apply globally to the Deepgram client. The other namespaces are used to configure options specific to different Deepgram API endpoints. */ export interface DeepgramClientOptions { - global?: NamespaceOptions; + key?: string | IKeyFactory; + global?: NamespaceOptions & { url?: string; headers?: { [index: string]: any } }; listen?: NamespaceOptions; manage?: NamespaceOptions; onprem?: NamespaceOptions; @@ -29,13 +42,16 @@ export interface DeepgramClientOptions { } interface TransportFetchOptions extends TransportOptions, FetchOptions {} +interface TransportWebSocketOptions extends TransportOptions { + _nodeOnlyHeaders?: { [index: string]: any }; +} -type TransportUrl = URL | string; +type TransportUrl = string; interface TransportOptions { url?: TransportUrl; proxy?: { - url?: null | TransportUrl; + url: TransportUrl; }; } @@ -45,7 +61,7 @@ interface ITransport { } export interface NamespaceOptions { fetch?: ITransport; - websocket?: ITransport; + websocket?: ITransport; } export type DefaultNamespaceOptions = { diff --git a/src/packages/AbstractClient.ts b/src/packages/AbstractClient.ts index 46c3971d..ab3fb7eb 100644 --- a/src/packages/AbstractClient.ts +++ b/src/packages/AbstractClient.ts @@ -1,7 +1,9 @@ +import EventEmitter from "events"; import { DEFAULT_OPTIONS } from "../lib/constants"; import { DeepgramError } from "../lib/errors"; import { applyDefaults } from "../lib/helpers"; import { DeepgramClientOptions } from "../lib/types"; +import merge from "deepmerge"; import { DefaultClientOptions, DefaultNamespaceOptions, @@ -18,22 +20,74 @@ import { * * Subclasses of `AbstractClient` should implement the specific functionality for interacting with the Deepgram API. */ -export abstract class AbstractClient { - public namespace: string = "global"; +export abstract class AbstractClient extends EventEmitter { + protected factory: Function | undefined = undefined; + protected key: string; protected namespaceOptions: DefaultNamespaceOptions; protected options: DefaultClientOptions; + public namespace: string = "global"; + public version: number = 1; - constructor(protected key: string, options: DeepgramClientOptions) { - this.key = key; + /** + * Constructs a new instance of the DeepgramClient class with the provided options. + * + * @param options - The options to configure the DeepgramClient instance. + * @param options.key - The Deepgram API key to use for authentication. If not provided, the `DEEPGRAM_API_KEY` environment variable will be used. + * @param options.global - Global options that apply to all requests made by the DeepgramClient instance. + * @param options.global.fetch - Options to configure the fetch requests made by the DeepgramClient instance. + * @param options.global.fetch.options - Additional options to pass to the fetch function, such as `url` and `headers`. + * @param options.namespace - Options specific to a particular namespace within the DeepgramClient instance. + */ + constructor(options: DeepgramClientOptions) { + super(); + + let key; + + if (typeof options.key === "function") { + this.factory = options.key; + key = this.factory(); + } else { + key = options.key; + } if (!key) { - this.key = process.env.DEEPGRAM_API_KEY as string; + key = process.env.DEEPGRAM_API_KEY as string; } - if (!this.key) { - throw new DeepgramError("A deepgram API key is required"); + if (!key) { + throw new DeepgramError("A deepgram API key is required."); } + this.key = key; + + const convertLegacyOptions = (optionsArg: DeepgramClientOptions): DeepgramClientOptions => { + const newOptions: DeepgramClientOptions = {}; + + if (optionsArg.global?.url) { + newOptions.global = { + fetch: { + options: { + url: optionsArg.global.url, + }, + }, + }; + } + + if (optionsArg.global?.headers) { + newOptions.global = { + fetch: { + options: { + headers: optionsArg.global?.headers, + }, + }, + }; + } + + return merge(optionsArg, newOptions); + }; + + options = convertLegacyOptions(options); + /** * Apply default options. */ @@ -50,4 +104,18 @@ export abstract class AbstractClient { this.options.global! ); } + + public v(version: number = 1) { + this.version = version; + + return this; + } + + /** + * Determines whether the current instance should proxy requests. + * @returns {boolean} true if the current instance should proxy requests; otherwise, false + */ + get proxy(): boolean { + return this.key === "proxy" && !!this.namespaceOptions.fetch.options.proxy?.url; + } } diff --git a/src/packages/AbstractLiveClient.ts b/src/packages/AbstractLiveClient.ts index 5f32d4a3..1e6294a9 100644 --- a/src/packages/AbstractLiveClient.ts +++ b/src/packages/AbstractLiveClient.ts @@ -2,7 +2,16 @@ import { DeepgramClientOptions } from "../lib/types"; import { AbstractClient } from "./AbstractClient"; export abstract class AbstractLiveClient extends AbstractClient { - constructor(protected key: string, options: DeepgramClientOptions) { - super(key, options); + protected baseUrl: string; + + // Constructor implementation + constructor(options: DeepgramClientOptions) { + super(options); + + if (this.proxy) { + this.baseUrl = this.namespaceOptions.websocket.options.proxy!.url; + } else { + this.baseUrl = this.namespaceOptions.websocket.options.url; + } } } diff --git a/src/packages/AbstractRestClient.ts b/src/packages/AbstractRestClient.ts index 85213dc0..65fbb9ec 100644 --- a/src/packages/AbstractRestClient.ts +++ b/src/packages/AbstractRestClient.ts @@ -8,17 +8,25 @@ import { isBrowser } from "../lib/helpers"; export abstract class AbstractRestClient extends AbstractClient { protected fetch: Fetch; + protected baseUrl: string; - constructor(protected key: string, options: DeepgramClientOptions) { - super(key, options); + // Constructor implementation + constructor(options: DeepgramClientOptions) { + super(options); - if (isBrowser() && !this.willProxy()) { + if (isBrowser() && !this.proxy) { throw new DeepgramError( "Due to CORS we are unable to support REST-based API calls to our API from the browser. Please consider using a proxy: https://dpgr.am/js-proxy for more information." ); } this.fetch = fetchWithAuth(this.key, this.namespaceOptions.fetch.client); + + if (this.proxy) { + this.baseUrl = this.namespaceOptions.fetch.options.proxy!.url; + } else { + this.baseUrl = this.namespaceOptions.fetch.options.url; + } } protected _getErrorMessage(err: any): string { @@ -151,10 +159,4 @@ export abstract class AbstractRestClient extends AbstractClient { ): Promise { return this._handleRequest(fetcher, "DELETE", url, headers, parameters); } - - protected willProxy() { - const proxyUrl = this.key === "proxy" && !!this.namespaceOptions.fetch.options.proxy?.url; - - return !!proxyUrl; - } } diff --git a/src/packages/ListenClient.ts b/src/packages/ListenClient.ts index 5d6d823b..82b4d822 100644 --- a/src/packages/ListenClient.ts +++ b/src/packages/ListenClient.ts @@ -4,11 +4,13 @@ import { LiveSchema } from "../lib/types"; import { PrerecordedClient } from "./PrerecordedClient"; export class ListenClient extends AbstractClient { + public namespace: string = "listen"; + get prerecorded() { - return new PrerecordedClient(this.key, this.options); + return new PrerecordedClient(this.options); } - public live(transcriptionOptions: LiveSchema, endpoint = "v1/listen") { - return new LiveClient(this.key, this.options, transcriptionOptions, endpoint); + public live(transcriptionOptions: LiveSchema = {}, endpoint = "{version}/listen") { + return new LiveClient(this.options, transcriptionOptions, endpoint); } } diff --git a/src/packages/LiveClient.ts b/src/packages/LiveClient.ts index d1353cb1..5899f3e4 100644 --- a/src/packages/LiveClient.ts +++ b/src/packages/LiveClient.ts @@ -1,34 +1,36 @@ -import { AbstractWsClient } from "./AbstractWsClient"; -import { appendSearchParams } from "../lib/helpers"; +import { AbstractLiveClient } from "./AbstractLiveClient"; +import { appendSearchParams, isDeepgramClientOptions, isLiveSchema } from "../lib/helpers"; import { DeepgramError } from "../lib/errors"; -import { DEFAULT_OPTIONS } from "../lib/constants"; import { LiveConnectionState, LiveTranscriptionEvents } from "../lib/enums"; import { w3cwebsocket } from "websocket"; -import type { - LiveSchema, - LiveConfigOptions, - LiveMetadataEvent, - LiveTranscriptionEvent, - DeepgramClientOptions, - UtteranceEndEvent, - SpeechStartedEvent, +import { + type LiveSchema, + type LiveConfigOptions, + type LiveMetadataEvent, + type LiveTranscriptionEvent, + type DeepgramClientOptions, + type UtteranceEndEvent, + type SpeechStartedEvent, } from "../lib/types"; -export class LiveClient extends AbstractWsClient { - private _socket: w3cwebsocket; +export class LiveClient extends AbstractLiveClient { + public namespace: string = "listen"; + protected _socket: w3cwebsocket; + // Constructor implementation constructor( - protected key: string, - protected options: DeepgramClientOptions | undefined = DEFAULT_OPTIONS, - private transcriptionOptions: LiveSchema = {}, - endpoint = "v1/listen" + options: DeepgramClientOptions, + transcriptionOptions: LiveSchema = {}, + endpoint: string = "{version}/listen" ) { - super(key, options); + super(options); - const url = new URL(endpoint, this.baseUrl); + // endpoint = endpoint.replace("{version}", this.version.toString()); + + const url = new URL(endpoint as string, this.baseUrl); url.protocol = url.protocol.toLowerCase().replace(/(http)(s)?/gi, "ws$2"); - appendSearchParams(url.searchParams, this.transcriptionOptions); + appendSearchParams(url.searchParams, transcriptionOptions); this._socket = new w3cwebsocket(url.toString(), ["token", this.key]); diff --git a/src/packages/ManageClient.ts b/src/packages/ManageClient.ts index 5278a416..b75b5855 100644 --- a/src/packages/ManageClient.ts +++ b/src/packages/ManageClient.ts @@ -1,4 +1,3 @@ -import { AbstractRestfulClient } from "./AbstractRestfulClient"; import { isDeepgramError } from "../lib/errors"; import { appendSearchParams } from "../lib/helpers"; import type { @@ -29,13 +28,16 @@ import type { VoidResponse, GetTokenDetailsResponse, } from "../lib/types"; +import { AbstractRestClient } from "./AbstractRestClient"; + +export class ManageClient extends AbstractRestClient { + public namespace: string = "manage"; -export class ManageClient extends AbstractRestfulClient { /** * @see https://developers.deepgram.com/docs/authenticating#test-request */ async getTokenDetails( - endpoint = "v1/auth/token" + endpoint = "{version}/auth/token" ): Promise> { try { const url = new URL(this.baseUrl); @@ -56,7 +58,9 @@ export class ManageClient extends AbstractRestfulClient { /** * @see https://developers.deepgram.com/reference/get-projects */ - async getProjects(endpoint = "v1/projects"): Promise> { + async getProjects( + endpoint = "{version}/projects" + ): Promise> { try { const url = new URL(this.baseUrl); url.pathname = endpoint; @@ -78,7 +82,7 @@ export class ManageClient extends AbstractRestfulClient { */ async getProject( projectId: string, - endpoint = "v1/projects/:projectId" + endpoint = "{version}/projects/:projectId" ): Promise> { try { const url = new URL(this.baseUrl); @@ -102,7 +106,7 @@ export class ManageClient extends AbstractRestfulClient { async updateProject( projectId: string, options: UpdateProjectSchema, - endpoint = "v1/projects/:projectId" + endpoint = "{version}/projects/:projectId" ): Promise> { try { const url = new URL(this.baseUrl); @@ -127,7 +131,7 @@ export class ManageClient extends AbstractRestfulClient { */ async deleteProject( projectId: string, - endpoint = "v1/projects/:projectId" + endpoint = "{version}/projects/:projectId" ): Promise { try { const url = new URL(this.baseUrl); @@ -150,7 +154,7 @@ export class ManageClient extends AbstractRestfulClient { */ async getProjectKeys( projectId: string, - endpoint = "v1/projects/:projectId/keys" + endpoint = "{version}/projects/:projectId/keys" ): Promise> { try { const url = new URL(this.baseUrl); @@ -174,7 +178,7 @@ export class ManageClient extends AbstractRestfulClient { async getProjectKey( projectId: string, keyId: string, - endpoint = "v1/projects/:projectId/keys/:keyId" + endpoint = "{version}/projects/:projectId/keys/:keyId" ): Promise> { try { const url = new URL(this.baseUrl); @@ -198,7 +202,7 @@ export class ManageClient extends AbstractRestfulClient { async createProjectKey( projectId: string, options: CreateProjectKeySchema, - endpoint = "v1/projects/:projectId/keys" + endpoint = "{version}/projects/:projectId/keys" ): Promise> { try { const url = new URL(this.baseUrl); @@ -224,7 +228,7 @@ export class ManageClient extends AbstractRestfulClient { async deleteProjectKey( projectId: string, keyId: string, - endpoint = "v1/projects/:projectId/keys/:keyId" + endpoint = "{version}/projects/:projectId/keys/:keyId" ): Promise { try { const url = new URL(this.baseUrl); @@ -247,7 +251,7 @@ export class ManageClient extends AbstractRestfulClient { */ async getProjectMembers( projectId: string, - endpoint = "v1/projects/:projectId/members" + endpoint = "{version}/projects/:projectId/members" ): Promise> { try { const url = new URL(this.baseUrl); @@ -271,7 +275,7 @@ export class ManageClient extends AbstractRestfulClient { async removeProjectMember( projectId: string, memberId: string, - endpoint = "v1/projects/:projectId/members/:memberId" + endpoint = "{version}/projects/:projectId/members/:memberId" ): Promise { try { const url = new URL(this.baseUrl); @@ -295,7 +299,7 @@ export class ManageClient extends AbstractRestfulClient { async getProjectMemberScopes( projectId: string, memberId: string, - endpoint = "v1/projects/:projectId/members/:memberId/scopes" + endpoint = "{version}/projects/:projectId/members/:memberId/scopes" ): Promise> { try { const url = new URL(this.baseUrl); @@ -320,7 +324,7 @@ export class ManageClient extends AbstractRestfulClient { projectId: string, memberId: string, options: UpdateProjectMemberScopeSchema, - endpoint = "v1/projects/:projectId/members/:memberId/scopes" + endpoint = "{version}/projects/:projectId/members/:memberId/scopes" ): Promise> { try { const url = new URL(this.baseUrl); @@ -345,7 +349,7 @@ export class ManageClient extends AbstractRestfulClient { */ async getProjectInvites( projectId: string, - endpoint = "v1/projects/:projectId/invites" + endpoint = "{version}/projects/:projectId/invites" ): Promise> { try { const url = new URL(this.baseUrl); @@ -369,7 +373,7 @@ export class ManageClient extends AbstractRestfulClient { async sendProjectInvite( projectId: string, options: SendProjectInviteSchema, - endpoint = "v1/projects/:projectId/invites" + endpoint = "{version}/projects/:projectId/invites" ): Promise> { try { const url = new URL(this.baseUrl); @@ -395,7 +399,7 @@ export class ManageClient extends AbstractRestfulClient { async deleteProjectInvite( projectId: string, email: string, - endpoint = "v1/projects/:projectId/invites/:email" + endpoint = "{version}/projects/:projectId/invites/:email" ): Promise { try { const url = new URL(this.baseUrl); @@ -418,7 +422,7 @@ export class ManageClient extends AbstractRestfulClient { */ async leaveProject( projectId: string, - endpoint = "v1/projects/:projectId/leave" + endpoint = "{version}/projects/:projectId/leave" ): Promise> { try { const url = new URL(this.baseUrl); @@ -442,7 +446,7 @@ export class ManageClient extends AbstractRestfulClient { async getProjectUsageRequests( projectId: string, options: GetProjectUsageRequestsSchema, - endpoint = "v1/projects/:projectId/requests" + endpoint = "{version}/projects/:projectId/requests" ): Promise> { try { const url = new URL(this.baseUrl); @@ -467,7 +471,7 @@ export class ManageClient extends AbstractRestfulClient { async getProjectUsageRequest( projectId: string, requestId: string, - endpoint = "v1/projects/:projectId/requests/:requestId" + endpoint = "{version}/projects/:projectId/requests/:requestId" ): Promise> { try { const url = new URL(this.baseUrl); @@ -491,7 +495,7 @@ export class ManageClient extends AbstractRestfulClient { async getProjectUsageSummary( projectId: string, options: GetProjectUsageSummarySchema, - endpoint = "v1/projects/:projectId/usage" + endpoint = "{version}/projects/:projectId/usage" ): Promise> { try { const url = new URL(this.baseUrl); @@ -516,7 +520,7 @@ export class ManageClient extends AbstractRestfulClient { async getProjectUsageFields( projectId: string, options: GetProjectUsageFieldsSchema, - endpoint = "v1/projects/:projectId/usage/fields" + endpoint = "{version}/projects/:projectId/usage/fields" ): Promise> { try { const url = new URL(this.baseUrl); @@ -540,7 +544,7 @@ export class ManageClient extends AbstractRestfulClient { */ async getProjectBalances( projectId: string, - endpoint = "v1/projects/:projectId/balances" + endpoint = "{version}/projects/:projectId/balances" ): Promise> { try { const url = new URL(this.baseUrl); @@ -564,7 +568,7 @@ export class ManageClient extends AbstractRestfulClient { async getProjectBalance( projectId: string, balanceId: string, - endpoint = "v1/projects/:projectId/balances/:balanceId" + endpoint = "{version}/projects/:projectId/balances/:balanceId" ): Promise> { try { const url = new URL(this.baseUrl); diff --git a/src/packages/OnPremClient.ts b/src/packages/OnPremClient.ts index e8da1ac9..9cd50e37 100644 --- a/src/packages/OnPremClient.ts +++ b/src/packages/OnPremClient.ts @@ -1,4 +1,3 @@ -import { AbstractRestfulClient } from "./AbstractRestfulClient"; import { isDeepgramError } from "../lib/errors"; import type { CreateOnPremCredentialsSchema, @@ -8,14 +7,17 @@ import type { MessageResponse, OnPremCredentialResponse, } from "../lib/types"; +import { AbstractRestClient } from "./AbstractRestClient"; + +export class OnPremClient extends AbstractRestClient { + public namespace: string = "onprem"; -export class OnPremClient extends AbstractRestfulClient { /** * @see https://developers.deepgram.com/reference/list-credentials */ async listCredentials( projectId: string, - endpoint = "v1/projects/:projectId/onprem/distribution/credentials" + endpoint = "{version}/projects/:projectId/onprem/distribution/credentials" ): Promise> { try { const url = new URL(this.baseUrl); @@ -39,7 +41,7 @@ export class OnPremClient extends AbstractRestfulClient { async getCredentials( projectId: string, credentialsId: string, - endpoint = "v1/projects/:projectId/onprem/distribution/credentials/:credentialsId" + endpoint = "{version}/projects/:projectId/onprem/distribution/credentials/:credentialsId" ): Promise> { try { const url = new URL(this.baseUrl); @@ -65,7 +67,7 @@ export class OnPremClient extends AbstractRestfulClient { async createCredentials( projectId: string, options: CreateOnPremCredentialsSchema, - endpoint = "v1/projects/:projectId/onprem/distribution/credentials" + endpoint = "{version}/projects/:projectId/onprem/distribution/credentials" ): Promise> { try { const url = new URL(this.baseUrl); @@ -91,7 +93,7 @@ export class OnPremClient extends AbstractRestfulClient { async deleteCredentials( projectId: string, credentialsId: string, - endpoint = "v1/projects/:projectId/onprem/distribution/credentials/:credentialsId" + endpoint = "{version}/projects/:projectId/onprem/distribution/credentials/:credentialsId" ): Promise> { try { const url = new URL(this.baseUrl); diff --git a/src/packages/PrerecordedClient.ts b/src/packages/PrerecordedClient.ts index 3575fa99..29cd8cfb 100644 --- a/src/packages/PrerecordedClient.ts +++ b/src/packages/PrerecordedClient.ts @@ -1,4 +1,3 @@ -import { AbstractRestfulClient } from "./AbstractRestfulClient"; import { CallbackUrl, appendSearchParams, isFileSource, isUrlSource } from "../lib/helpers"; import { DeepgramError, isDeepgramError } from "../lib/errors"; import type { @@ -10,12 +9,15 @@ import type { SyncPrerecordedResponse, UrlSource, } from "../lib/types"; +import { AbstractRestClient } from "./AbstractRestClient"; + +export class PrerecordedClient extends AbstractRestClient { + public namespace: string = "listen"; -export class PrerecordedClient extends AbstractRestfulClient { async transcribeUrl( source: UrlSource, options?: PrerecordedSchema, - endpoint = "v1/listen" + endpoint = "{version}/listen" ): Promise> { try { let body; @@ -52,7 +54,7 @@ export class PrerecordedClient extends AbstractRestfulClient { async transcribeFile( source: FileSource, options?: PrerecordedSchema, - endpoint = "v1/listen" + endpoint = "{version}/listen" ): Promise> { try { let body; @@ -92,7 +94,7 @@ export class PrerecordedClient extends AbstractRestfulClient { source: UrlSource, callback: CallbackUrl, options?: PrerecordedSchema, - endpoint = "v1/listen" + endpoint = "{version}/listen" ): Promise> { try { let body; @@ -127,7 +129,7 @@ export class PrerecordedClient extends AbstractRestfulClient { source: FileSource, callback: CallbackUrl, options?: PrerecordedSchema, - endpoint = "v1/listen" + endpoint = "{version}/listen" ): Promise> { try { let body; diff --git a/src/packages/ReadClient.ts b/src/packages/ReadClient.ts index 09a8c0e8..1bda177a 100644 --- a/src/packages/ReadClient.ts +++ b/src/packages/ReadClient.ts @@ -1,4 +1,3 @@ -import { AbstractRestfulClient } from "./AbstractRestfulClient"; import { CallbackUrl, appendSearchParams, isTextSource, isUrlSource } from "../lib/helpers"; import { DeepgramError, isDeepgramError } from "../lib/errors"; import type { @@ -11,12 +10,15 @@ import type { TextSource, UrlSource, } from "../lib/types"; +import { AbstractRestClient } from "./AbstractRestClient"; + +export class ReadClient extends AbstractRestClient { + public namespace: string = "read"; -export class ReadClient extends AbstractRestfulClient { async analyzeUrl( source: UrlSource, options?: AnalyzeSchema, - endpoint = "v1/read" + endpoint = "{version}/read" ): Promise> { try { let body; @@ -53,7 +55,7 @@ export class ReadClient extends AbstractRestfulClient { async analyzeText( source: TextSource, options?: AnalyzeSchema, - endpoint = "v1/read" + endpoint = "{version}/read" ): Promise> { try { let body; @@ -91,7 +93,7 @@ export class ReadClient extends AbstractRestfulClient { source: UrlSource, callback: CallbackUrl, options?: AnalyzeSchema, - endpoint = "v1/read" + endpoint = "{version}/read" ): Promise> { try { let body; @@ -126,7 +128,7 @@ export class ReadClient extends AbstractRestfulClient { source: TextSource, callback: CallbackUrl, options?: AnalyzeSchema, - endpoint = "v1/read" + endpoint = "{version}/read" ): Promise> { try { let body; diff --git a/src/packages/SpeakClient.ts b/src/packages/SpeakClient.ts index 9ca8d697..086fc288 100644 --- a/src/packages/SpeakClient.ts +++ b/src/packages/SpeakClient.ts @@ -1,9 +1,10 @@ -import { AbstractRestfulClient } from "./AbstractRestfulClient"; import { DeepgramError, DeepgramUnknownError, isDeepgramError } from "../lib/errors"; import { appendSearchParams, isTextSource } from "../lib/helpers"; import { Fetch, SpeakSchema, TextSource } from "../lib/types"; +import { AbstractRestClient } from "./AbstractRestClient"; -export class SpeakClient extends AbstractRestfulClient { +export class SpeakClient extends AbstractRestClient { + public namespace: string = "speak"; public result: undefined | Response; /** @@ -12,7 +13,7 @@ export class SpeakClient extends AbstractRestfulClient { async request( source: TextSource, options?: SpeakSchema, - endpoint = "v1/speak" + endpoint = "{version}/speak" ): Promise { try { let body; diff --git a/src/packages/index.ts b/src/packages/index.ts index 58ad0f9f..0a351ba9 100644 --- a/src/packages/index.ts +++ b/src/packages/index.ts @@ -1,6 +1,6 @@ export { AbstractClient } from "./AbstractClient"; -export { AbstractRestfulClient } from "./AbstractRestfulClient"; -export { AbstractWsClient } from "./AbstractWsClient"; +export { AbstractRestClient } from "./AbstractRestClient"; +export { AbstractLiveClient } from "./AbstractLiveClient"; export { ListenClient } from "./ListenClient"; export { LiveClient } from "./LiveClient"; export { ManageClient } from "./ManageClient"; diff --git a/test/client.test.ts b/test/client.test.ts index 426edf8c..4379635e 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -21,7 +21,7 @@ describe("testing creation of a deepgram client object", () => { it("it should have the default URL when no custom URL option is provided", () => { // @ts-ignore - const url = deepgram.baseUrl.hostname; + const url = deepgram.namespaceOptions.fetch.options.url; expect(url).to.equal(DEFAULT_URL); }); @@ -31,35 +31,68 @@ describe("testing creation of a deepgram client object", () => { }); it("it should throw an error if invalid options are provided", () => { - expect(() => createClient(faker.string.alphanumeric(40), { global: { url: "" } })).to.throw( - `An API URL is required. It should be set to ${DEFAULT_URL} by default. No idea what happened!` + // const client = createClient({ global: { fetch: { options: { url: "" } } } }); + // console.log(client); + // process.exit(1); + expect(() => createClient({ global: { fetch: { options: { url: "" } } } })).to.throw( + `A deepgram API key is required.` ); }); - it("it should create the client object with a custom domain", () => { - const domain = faker.internet.url({ appendSlash: false }); - const client = createClient(faker.string.alphanumeric(40), { - global: { url: domain }, + it("it should create the client object with legacy options", () => { + const mockUrl = faker.internet.url({ appendSlash: false }); + const mockKey = faker.string.alphanumeric(40); + const client = createClient(mockKey, { + global: { url: mockUrl }, }); // @ts-ignore - const baseUrl = client.baseUrl; + const url = client.namespaceOptions.fetch.options.url; + + // @ts-ignore + const key = client.options.key; expect(client).is.instanceOf(DeepgramClient); - expect(`${baseUrl.protocol}//${baseUrl.hostname}`).to.equal(domain); + expect(url).to.equal(mockUrl); + expect(key).to.equal(mockKey); + }); + + it("it should create the client object with a custom url", () => { + const mockUrl = faker.internet.url({ appendSlash: false }); + const mockKey = faker.string.alphanumeric(40); + const client = createClient({ + key: mockKey, + global: { fetch: { options: { url: mockUrl } } }, + }); + + // @ts-ignore + const url = client.namespaceOptions.fetch.options.url; + + // @ts-ignore + const key = client.options.key; + + expect(client).is.instanceOf(DeepgramClient); + expect(url).to.equal(mockUrl); + expect(key).to.equal(mockKey); }); it("it should strip trailing slashes off the API URL if they're supplied", () => { - const domain = faker.internet.url({ appendSlash: true }); - const client = createClient(faker.string.alphanumeric(40), { - global: { url: domain }, + const mockUrl = faker.internet.url({ appendSlash: false }); + const mockKey = faker.string.alphanumeric(40); + const client = createClient({ + key: mockKey, + global: { fetch: { options: { url: `${mockUrl}/` } } }, }); // @ts-ignore - const baseUrl = client.baseUrl; + const url = client.namespaceOptions.fetch.options.url; + + // @ts-ignore + const key = client.options.key; expect(client).is.instanceOf(DeepgramClient); - expect(`${baseUrl.protocol}//${baseUrl.hostname}`).to.equal(stripTrailingSlash(domain)); + expect(url).to.equal(mockUrl); + expect(key).to.equal(mockKey); }); it("it should still work when provided a URL without a protocol", () => { diff --git a/test/constants.test.ts b/test/constants.test.ts index 1bae3f82..92c3bf01 100644 --- a/test/constants.test.ts +++ b/test/constants.test.ts @@ -1,4 +1,4 @@ -import { applySettingDefaults } from "../src/lib/helpers"; +import { applyDefaults } from "../src/lib/helpers"; import { DeepgramClientOptions } from "../src/lib/types/DeepgramClientOptions"; import { DEFAULT_OPTIONS } from "../src/lib/constants"; import { expect } from "chai"; @@ -15,7 +15,7 @@ describe("testing constants", () => { const options = { global: { url: faker.internet.url({ appendSlash: false }) }, }; - const settings = applySettingDefaults(options, DEFAULT_OPTIONS); + const settings = applyDefaults(options, DEFAULT_OPTIONS); expect(settings).is.not.deep.equal(options); }); diff --git a/test/helpers.test.ts b/test/helpers.test.ts index e35e5b27..fd6b4deb 100644 --- a/test/helpers.test.ts +++ b/test/helpers.test.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { faker } from "@faker-js/faker"; -import { applySettingDefaults, stripTrailingSlash } from "../src/lib/helpers"; +import { applyDefaults, stripTrailingSlash } from "../src/lib/helpers"; import { DEFAULT_OPTIONS } from "../src/lib/constants"; describe("testing helpers", () => { @@ -13,6 +13,6 @@ describe("testing helpers", () => { it("it should override defaults with options provided", () => { const options = JSON.parse(JSON.stringify(DEFAULT_OPTIONS)); // deep copy DEFAULT_OPTIONS options.global.url = faker.internet.url({ appendSlash: false }); - expect(applySettingDefaults(options, DEFAULT_OPTIONS)).to.deep.equal(options); + expect(applyDefaults(options, DEFAULT_OPTIONS)).to.deep.equal(options); }); }); From 329303bb022817d020db1296008174a84ef54e97 Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 29 Apr 2024 16:09:52 -0700 Subject: [PATCH 04/12] fix: remove erroneous file --- index.ts | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 index.ts diff --git a/index.ts b/index.ts deleted file mode 100644 index 54dd6c77..00000000 --- a/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @file Automatically generated by barrelsby. - */ - -export * from "./src/index"; -export * from "./test/client.test"; -export * from "./test/constants.test"; -export * from "./test/errors.test"; -export * from "./test/helpers.test"; -export * from "./test/legacy.test"; -export * from "./test/live.test"; -export * from "./test/manage.test"; -export * from "./test/onprem.test"; -export * from "./test/prerecorded.test"; From 7bcc14c4e09e2acdceeba27554d1a36f9a48bbc0 Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 29 Apr 2024 16:55:22 -0700 Subject: [PATCH 05/12] feat: convert all legacy options to namespaced options --- src/packages/AbstractClient.ts | 40 ++++++++++++++++++++++++++++++++- test/client.test.ts | 41 +++++----------------------------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/packages/AbstractClient.ts b/src/packages/AbstractClient.ts index ab3fb7eb..1a254e4a 100644 --- a/src/packages/AbstractClient.ts +++ b/src/packages/AbstractClient.ts @@ -63,6 +63,30 @@ export abstract class AbstractClient extends EventEmitter { const convertLegacyOptions = (optionsArg: DeepgramClientOptions): DeepgramClientOptions => { const newOptions: DeepgramClientOptions = {}; + if (optionsArg._experimentalCustomFetch) { + newOptions.global = { + fetch: { + client: optionsArg._experimentalCustomFetch, + }, + }; + } + + optionsArg = merge(optionsArg, newOptions); + + if (optionsArg.restProxy?.url) { + newOptions.global = { + fetch: { + options: { + proxy: { + url: optionsArg.restProxy?.url, + }, + }, + }, + }; + } + + optionsArg = merge(optionsArg, newOptions); + if (optionsArg.global?.url) { newOptions.global = { fetch: { @@ -70,9 +94,16 @@ export abstract class AbstractClient extends EventEmitter { url: optionsArg.global.url, }, }, + websocket: { + options: { + url: optionsArg.global.url, + }, + }, }; } + optionsArg = merge(optionsArg, newOptions); + if (optionsArg.global?.headers) { newOptions.global = { fetch: { @@ -80,10 +111,17 @@ export abstract class AbstractClient extends EventEmitter { headers: optionsArg.global?.headers, }, }, + websocket: { + options: { + _nodeOnlyHeaders: optionsArg.global?.headers, + }, + }, }; } - return merge(optionsArg, newOptions); + optionsArg = merge(optionsArg, newOptions); + + return optionsArg; }; options = convertLegacyOptions(options); diff --git a/test/client.test.ts b/test/client.test.ts index 4379635e..ed444eaf 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -76,41 +76,11 @@ describe("testing creation of a deepgram client object", () => { expect(key).to.equal(mockKey); }); - it("it should strip trailing slashes off the API URL if they're supplied", () => { - const mockUrl = faker.internet.url({ appendSlash: false }); - const mockKey = faker.string.alphanumeric(40); - const client = createClient({ - key: mockKey, - global: { fetch: { options: { url: `${mockUrl}/` } } }, - }); - - // @ts-ignore - const url = client.namespaceOptions.fetch.options.url; - - // @ts-ignore - const key = client.options.key; - - expect(client).is.instanceOf(DeepgramClient); - expect(url).to.equal(mockUrl); - expect(key).to.equal(mockKey); - }); - - it("it should still work when provided a URL without a protocol", () => { - const domain = `api.mock.deepgram.com`; - const client = createClient(faker.string.alphanumeric(40), { - global: { url: domain }, - }); - - // @ts-ignore - const url = client.baseUrl.hostname; - - expect(client).is.instanceOf(DeepgramClient); - expect(url).to.equal("api.mock.deepgram.com"); - }); - it("it should allow for the supply of a custom header", () => { const client = createClient(faker.string.alphanumeric(40), { - global: { headers: { "X-dg-test": "testing" } }, + global: { + fetch: { options: { headers: { "X-dg-test": "testing" } } }, + }, }); expect(client).is.instanceOf(DeepgramClient); @@ -122,8 +92,9 @@ describe("testing creation of a deepgram client object", () => { }; const client = createClient(faker.string.alphanumeric(40), { - global: { url: "https://api.mock.deepgram.com" }, - _experimentalCustomFetch: fetch, + global: { + fetch: { client: fetch }, + }, }); const { result, error } = await client.manage.getProjectBalances(faker.string.uuid()); From 21046a75a186a91eb1dd04731a1b2d8e1592548e Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 29 Apr 2024 17:41:51 -0700 Subject: [PATCH 06/12] feat: plug live client into new options --- src/lib/helpers.ts | 64 +++++++++++++++++++ src/packages/AbstractClient.ts | 98 +++++++++--------------------- src/packages/AbstractLiveClient.ts | 2 - src/packages/AbstractRestClient.ts | 1 - src/packages/LiveClient.ts | 10 +-- test/live.test.ts | 2 +- 6 files changed, 95 insertions(+), 82 deletions(-) diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 88738ef9..ed3300c0 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -109,3 +109,67 @@ export function isLiveSchema(arg: any): arg is LiveSchema { export function isDeepgramClientOptions(arg: any): arg is DeepgramClientOptions { return arg && typeof arg.global !== "undefined"; } + +export const convertLegacyOptions = (optionsArg: DeepgramClientOptions): DeepgramClientOptions => { + const newOptions: DeepgramClientOptions = {}; + + if (optionsArg._experimentalCustomFetch) { + newOptions.global = { + fetch: { + client: optionsArg._experimentalCustomFetch, + }, + }; + } + + optionsArg = merge(optionsArg, newOptions); + + if (optionsArg.restProxy?.url) { + newOptions.global = { + fetch: { + options: { + proxy: { + url: optionsArg.restProxy?.url, + }, + }, + }, + }; + } + + optionsArg = merge(optionsArg, newOptions); + + if (optionsArg.global?.url) { + newOptions.global = { + fetch: { + options: { + url: optionsArg.global.url, + }, + }, + websocket: { + options: { + url: optionsArg.global.url, + }, + }, + }; + } + + optionsArg = merge(optionsArg, newOptions); + + if (optionsArg.global?.headers) { + newOptions.global = { + fetch: { + options: { + headers: optionsArg.global?.headers, + }, + }, + websocket: { + options: { + _nodeOnlyHeaders: optionsArg.global?.headers, + }, + }, + }; + } + + optionsArg = merge(optionsArg, newOptions); + + return optionsArg; +}; diff --git a/src/packages/AbstractClient.ts b/src/packages/AbstractClient.ts index 1a254e4a..50069186 100644 --- a/src/packages/AbstractClient.ts +++ b/src/packages/AbstractClient.ts @@ -1,9 +1,8 @@ import EventEmitter from "events"; -import { DEFAULT_OPTIONS } from "../lib/constants"; +import { DEFAULT_OPTIONS, DEFAULT_URL } from "../lib/constants"; import { DeepgramError } from "../lib/errors"; -import { applyDefaults } from "../lib/helpers"; -import { DeepgramClientOptions } from "../lib/types"; -import merge from "deepmerge"; +import { appendSearchParams, applyDefaults, convertLegacyOptions } from "../lib/helpers"; +import { DeepgramClientOptions, LiveSchema, TranscriptionSchema } from "../lib/types"; import { DefaultClientOptions, DefaultNamespaceOptions, @@ -26,7 +25,8 @@ export abstract class AbstractClient extends EventEmitter { protected namespaceOptions: DefaultNamespaceOptions; protected options: DefaultClientOptions; public namespace: string = "global"; - public version: number = 1; + public version: string = "v1"; + public baseUrl: string = DEFAULT_URL; /** * Constructs a new instance of the DeepgramClient class with the provided options. @@ -60,70 +60,6 @@ export abstract class AbstractClient extends EventEmitter { this.key = key; - const convertLegacyOptions = (optionsArg: DeepgramClientOptions): DeepgramClientOptions => { - const newOptions: DeepgramClientOptions = {}; - - if (optionsArg._experimentalCustomFetch) { - newOptions.global = { - fetch: { - client: optionsArg._experimentalCustomFetch, - }, - }; - } - - optionsArg = merge(optionsArg, newOptions); - - if (optionsArg.restProxy?.url) { - newOptions.global = { - fetch: { - options: { - proxy: { - url: optionsArg.restProxy?.url, - }, - }, - }, - }; - } - - optionsArg = merge(optionsArg, newOptions); - - if (optionsArg.global?.url) { - newOptions.global = { - fetch: { - options: { - url: optionsArg.global.url, - }, - }, - websocket: { - options: { - url: optionsArg.global.url, - }, - }, - }; - } - - optionsArg = merge(optionsArg, newOptions); - - if (optionsArg.global?.headers) { - newOptions.global = { - fetch: { - options: { - headers: optionsArg.global?.headers, - }, - }, - websocket: { - options: { - _nodeOnlyHeaders: optionsArg.global?.headers, - }, - }, - }; - } - - optionsArg = merge(optionsArg, newOptions); - - return optionsArg; - }; - options = convertLegacyOptions(options); /** @@ -143,7 +79,7 @@ export abstract class AbstractClient extends EventEmitter { ); } - public v(version: number = 1) { + public v(version: string = "v1"): this { this.version = version; return this; @@ -156,4 +92,26 @@ export abstract class AbstractClient extends EventEmitter { get proxy(): boolean { return this.key === "proxy" && !!this.namespaceOptions.fetch.options.proxy?.url; } + + /** + * Generates a URL for the specified endpoint and transcription options. + * + * @param endpoint - The endpoint URL, which may contain a "{version}" placeholder that will be replaced with the client's version. + * @param transcriptionOptions - The transcription options to include as query parameters in the URL. + * @returns A URL object representing the generated URL. + */ + public getRequestUrl( + endpoint: string, + transcriptionOptions: LiveSchema | TranscriptionSchema + ): URL { + /** + * Version the URL endpoints if they can be versioned. + */ + endpoint = endpoint.replace("{version}", this.version); + + const url = new URL(endpoint as string, this.baseUrl); + appendSearchParams(url.searchParams, transcriptionOptions); + + return url; + } } diff --git a/src/packages/AbstractLiveClient.ts b/src/packages/AbstractLiveClient.ts index 1e6294a9..da25fe16 100644 --- a/src/packages/AbstractLiveClient.ts +++ b/src/packages/AbstractLiveClient.ts @@ -2,8 +2,6 @@ import { DeepgramClientOptions } from "../lib/types"; import { AbstractClient } from "./AbstractClient"; export abstract class AbstractLiveClient extends AbstractClient { - protected baseUrl: string; - // Constructor implementation constructor(options: DeepgramClientOptions) { super(options); diff --git a/src/packages/AbstractRestClient.ts b/src/packages/AbstractRestClient.ts index 65fbb9ec..c499c069 100644 --- a/src/packages/AbstractRestClient.ts +++ b/src/packages/AbstractRestClient.ts @@ -8,7 +8,6 @@ import { isBrowser } from "../lib/helpers"; export abstract class AbstractRestClient extends AbstractClient { protected fetch: Fetch; - protected baseUrl: string; // Constructor implementation constructor(options: DeepgramClientOptions) { diff --git a/src/packages/LiveClient.ts b/src/packages/LiveClient.ts index 5899f3e4..28e0294f 100644 --- a/src/packages/LiveClient.ts +++ b/src/packages/LiveClient.ts @@ -1,5 +1,4 @@ import { AbstractLiveClient } from "./AbstractLiveClient"; -import { appendSearchParams, isDeepgramClientOptions, isLiveSchema } from "../lib/helpers"; import { DeepgramError } from "../lib/errors"; import { LiveConnectionState, LiveTranscriptionEvents } from "../lib/enums"; import { w3cwebsocket } from "websocket"; @@ -26,13 +25,8 @@ export class LiveClient extends AbstractLiveClient { ) { super(options); - // endpoint = endpoint.replace("{version}", this.version.toString()); - - const url = new URL(endpoint as string, this.baseUrl); - url.protocol = url.protocol.toLowerCase().replace(/(http)(s)?/gi, "ws$2"); - appendSearchParams(url.searchParams, transcriptionOptions); - - this._socket = new w3cwebsocket(url.toString(), ["token", this.key]); + const requestUrl = this.getRequestUrl(endpoint, transcriptionOptions); + this._socket = new w3cwebsocket(requestUrl.toString(), ["token", this.key]); this._socket.onopen = () => { this.emit(LiveTranscriptionEvents.Open, this); diff --git a/test/live.test.ts b/test/live.test.ts index b777af29..dbaef965 100644 --- a/test/live.test.ts +++ b/test/live.test.ts @@ -9,7 +9,7 @@ describe("connecting to our transcription websocket", () => { beforeEach(() => { deepgram = createClient(faker.string.alphanumeric(40), { - global: { url: "https://api.mock.deepgram.com" }, + global: { url: "wss://api.mock.deepgram.com" }, }); }); From 2ae14d0efefbd1fc7c159959f48e447cec62c0a4 Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 30 Apr 2024 15:02:58 -0700 Subject: [PATCH 07/12] feat: plug rest clients into new options --- src/lib/types/CreateProjectKeySchema.ts | 1 - src/lib/types/TranscriptionSchema.ts | 2 - .../types/UpdateProjectMemberScopeSchema.ts | 2 +- src/lib/types/UpdateProjectSchema.ts | 1 - src/packages/AbstractClient.ts | 36 +++- src/packages/ListenClient.ts | 2 +- src/packages/LiveClient.ts | 4 +- src/packages/ManageClient.ts | 203 ++++++++---------- src/packages/OnPremClient.ts | 40 ++-- src/packages/PrerecordedClient.ts | 88 ++++---- src/packages/ReadClient.ts | 54 ++--- src/packages/SpeakClient.ts | 2 +- 12 files changed, 204 insertions(+), 231 deletions(-) diff --git a/src/lib/types/CreateProjectKeySchema.ts b/src/lib/types/CreateProjectKeySchema.ts index 6ec1f65e..9f43b0b5 100644 --- a/src/lib/types/CreateProjectKeySchema.ts +++ b/src/lib/types/CreateProjectKeySchema.ts @@ -12,5 +12,4 @@ interface CommonOptions extends Record { comment: string; scopes: string[]; tags?: string[]; - [key: string]: unknown; } diff --git a/src/lib/types/TranscriptionSchema.ts b/src/lib/types/TranscriptionSchema.ts index 1ff2aaca..c35879e7 100644 --- a/src/lib/types/TranscriptionSchema.ts +++ b/src/lib/types/TranscriptionSchema.ts @@ -138,8 +138,6 @@ interface TranscriptionSchema extends Record { * @see https://developers.deepgram.com/docs/extra-metadata */ extra?: string[] | string; - - [key: string]: unknown; } interface PrerecordedSchema extends TranscriptionSchema { diff --git a/src/lib/types/UpdateProjectMemberScopeSchema.ts b/src/lib/types/UpdateProjectMemberScopeSchema.ts index 54193a48..f4a7e64a 100644 --- a/src/lib/types/UpdateProjectMemberScopeSchema.ts +++ b/src/lib/types/UpdateProjectMemberScopeSchema.ts @@ -1,3 +1,3 @@ -export interface UpdateProjectMemberScopeSchema { +export interface UpdateProjectMemberScopeSchema extends Record { scope: string; } diff --git a/src/lib/types/UpdateProjectSchema.ts b/src/lib/types/UpdateProjectSchema.ts index 329ea28f..f5b090e5 100644 --- a/src/lib/types/UpdateProjectSchema.ts +++ b/src/lib/types/UpdateProjectSchema.ts @@ -1,5 +1,4 @@ export interface UpdateProjectSchema extends Record { name?: string; company?: string; - [key: string]: unknown; } diff --git a/src/packages/AbstractClient.ts b/src/packages/AbstractClient.ts index 50069186..66216509 100644 --- a/src/packages/AbstractClient.ts +++ b/src/packages/AbstractClient.ts @@ -94,23 +94,43 @@ export abstract class AbstractClient extends EventEmitter { } /** - * Generates a URL for the specified endpoint and transcription options. + * Generates a URL for an API endpoint with optional query parameters and transcription options. * - * @param endpoint - The endpoint URL, which may contain a "{version}" placeholder that will be replaced with the client's version. - * @param transcriptionOptions - The transcription options to include as query parameters in the URL. - * @returns A URL object representing the generated URL. + * @param endpoint - The API endpoint URL, which may contain placeholders for fields. + * @param fields - An optional object containing key-value pairs to replace placeholders in the endpoint URL. + * @param transcriptionOptions - Optional transcription options to include as query parameters in the URL. + * @returns A URL object representing the constructed API request URL. */ public getRequestUrl( endpoint: string, - transcriptionOptions: LiveSchema | TranscriptionSchema + fields: { [key: string]: string } = { version: this.version }, + transcriptionOptions?: { + [key: string]: unknown; + } ): URL { /** - * Version the URL endpoints if they can be versioned. + * If we pass in fields without a version, set a version. */ - endpoint = endpoint.replace("{version}", this.version); + fields.version = this.version; + /** + * Version and template the endpoint for input argument.. + */ + endpoint = endpoint.replace(/:(\w+)/g, function (_, key) { + return fields![key]; + }); + + /** + * Create a URL object. + */ const url = new URL(endpoint as string, this.baseUrl); - appendSearchParams(url.searchParams, transcriptionOptions); + + /** + * If there are transcription options, append them to the request as URL querystring parameters + */ + if (transcriptionOptions) { + appendSearchParams(url.searchParams, transcriptionOptions); + } return url; } diff --git a/src/packages/ListenClient.ts b/src/packages/ListenClient.ts index 82b4d822..524b7d5d 100644 --- a/src/packages/ListenClient.ts +++ b/src/packages/ListenClient.ts @@ -10,7 +10,7 @@ export class ListenClient extends AbstractClient { return new PrerecordedClient(this.options); } - public live(transcriptionOptions: LiveSchema = {}, endpoint = "{version}/listen") { + public live(transcriptionOptions: LiveSchema = {}, endpoint = ":version/listen") { return new LiveClient(this.options, transcriptionOptions, endpoint); } } diff --git a/src/packages/LiveClient.ts b/src/packages/LiveClient.ts index 28e0294f..eb646bf6 100644 --- a/src/packages/LiveClient.ts +++ b/src/packages/LiveClient.ts @@ -21,11 +21,11 @@ export class LiveClient extends AbstractLiveClient { constructor( options: DeepgramClientOptions, transcriptionOptions: LiveSchema = {}, - endpoint: string = "{version}/listen" + endpoint: string = ":version/listen" ) { super(options); - const requestUrl = this.getRequestUrl(endpoint, transcriptionOptions); + const requestUrl = this.getRequestUrl(endpoint, {}, transcriptionOptions); this._socket = new w3cwebsocket(requestUrl.toString(), ["token", this.key]); this._socket.onopen = () => { diff --git a/src/packages/ManageClient.ts b/src/packages/ManageClient.ts index b75b5855..a97b5600 100644 --- a/src/packages/ManageClient.ts +++ b/src/packages/ManageClient.ts @@ -37,13 +37,11 @@ export class ManageClient extends AbstractRestClient { * @see https://developers.deepgram.com/docs/authenticating#test-request */ async getTokenDetails( - endpoint = "{version}/auth/token" + endpoint = ":version/auth/token" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint; - - const result: GetTokenDetailsResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint); + const result: GetTokenDetailsResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -59,13 +57,11 @@ export class ManageClient extends AbstractRestClient { * @see https://developers.deepgram.com/reference/get-projects */ async getProjects( - endpoint = "{version}/projects" + endpoint = ":version/projects" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint; - - const result: GetProjectsResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint); + const result: GetProjectsResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -82,13 +78,11 @@ export class ManageClient extends AbstractRestClient { */ async getProject( projectId: string, - endpoint = "{version}/projects/:projectId" + endpoint = ":version/projects/:projectId" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - - const result: GetProjectResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }); + const result: GetProjectResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -106,15 +100,13 @@ export class ManageClient extends AbstractRestClient { async updateProject( projectId: string, options: UpdateProjectSchema, - endpoint = "{version}/projects/:projectId" + endpoint = ":version/projects/:projectId" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - + const requestUrl = this.getRequestUrl(endpoint, { projectId }, options); const body = JSON.stringify(options); - const result: MessageResponse = await this.patch(this.fetch as Fetch, url, body); + const result: MessageResponse = await this.patch(this.fetch as Fetch, requestUrl, body); return { result, error: null }; } catch (error) { @@ -131,13 +123,11 @@ export class ManageClient extends AbstractRestClient { */ async deleteProject( projectId: string, - endpoint = "{version}/projects/:projectId" + endpoint = ":version/projects/:projectId" ): Promise { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - - await this.delete(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }); + await this.delete(this.fetch as Fetch, requestUrl); return { error: null }; } catch (error) { @@ -154,13 +144,11 @@ export class ManageClient extends AbstractRestClient { */ async getProjectKeys( projectId: string, - endpoint = "{version}/projects/:projectId/keys" + endpoint = ":version/projects/:projectId/keys" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - - const result: GetProjectKeysResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }); + const result: GetProjectKeysResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -178,13 +166,11 @@ export class ManageClient extends AbstractRestClient { async getProjectKey( projectId: string, keyId: string, - endpoint = "{version}/projects/:projectId/keys/:keyId" + endpoint = ":version/projects/:projectId/keys/:keyId" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId).replace(/:keyId/, keyId); - - const result: GetProjectKeyResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId, keyId }); + const result: GetProjectKeyResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -202,15 +188,17 @@ export class ManageClient extends AbstractRestClient { async createProjectKey( projectId: string, options: CreateProjectKeySchema, - endpoint = "{version}/projects/:projectId/keys" + endpoint = ":version/projects/:projectId/keys" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - + const requestUrl = this.getRequestUrl(endpoint, { projectId }, options); const body = JSON.stringify(options); - const result: CreateProjectKeyResponse = await this.post(this.fetch as Fetch, url, body); + const result: CreateProjectKeyResponse = await this.post( + this.fetch as Fetch, + requestUrl, + body + ); return { result, error: null }; } catch (error) { @@ -228,13 +216,11 @@ export class ManageClient extends AbstractRestClient { async deleteProjectKey( projectId: string, keyId: string, - endpoint = "{version}/projects/:projectId/keys/:keyId" + endpoint = ":version/projects/:projectId/keys/:keyId" ): Promise { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId).replace(/:keyId/, keyId); - - await this.delete(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId, keyId }); + await this.delete(this.fetch as Fetch, requestUrl); return { error: null }; } catch (error) { @@ -251,13 +237,11 @@ export class ManageClient extends AbstractRestClient { */ async getProjectMembers( projectId: string, - endpoint = "{version}/projects/:projectId/members" + endpoint = ":version/projects/:projectId/members" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - - const result: GetProjectMembersResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }); + const result: GetProjectMembersResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -275,13 +259,11 @@ export class ManageClient extends AbstractRestClient { async removeProjectMember( projectId: string, memberId: string, - endpoint = "{version}/projects/:projectId/members/:memberId" + endpoint = ":version/projects/:projectId/members/:memberId" ): Promise { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId).replace(/:memberId/, memberId); - - await this.delete(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId, memberId }); + await this.delete(this.fetch as Fetch, requestUrl); return { error: null }; } catch (error) { @@ -299,13 +281,14 @@ export class ManageClient extends AbstractRestClient { async getProjectMemberScopes( projectId: string, memberId: string, - endpoint = "{version}/projects/:projectId/members/:memberId/scopes" + endpoint = ":version/projects/:projectId/members/:memberId/scopes" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId).replace(/:memberId/, memberId); - - const result: GetProjectMemberScopesResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId, memberId }); + const result: GetProjectMemberScopesResponse = await this.get( + this.fetch as Fetch, + requestUrl + ); return { result, error: null }; } catch (error) { @@ -324,15 +307,13 @@ export class ManageClient extends AbstractRestClient { projectId: string, memberId: string, options: UpdateProjectMemberScopeSchema, - endpoint = "{version}/projects/:projectId/members/:memberId/scopes" + endpoint = ":version/projects/:projectId/members/:memberId/scopes" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId).replace(/:memberId/, memberId); - + const requestUrl = this.getRequestUrl(endpoint, { projectId, memberId }, options); const body = JSON.stringify(options); - const result: MessageResponse = await this.put(this.fetch as Fetch, url, body); + const result: MessageResponse = await this.put(this.fetch as Fetch, requestUrl, body); return { result, error: null }; } catch (error) { @@ -349,13 +330,11 @@ export class ManageClient extends AbstractRestClient { */ async getProjectInvites( projectId: string, - endpoint = "{version}/projects/:projectId/invites" + endpoint = ":version/projects/:projectId/invites" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - - const result: GetProjectInvitesResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }); + const result: GetProjectInvitesResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -373,15 +352,13 @@ export class ManageClient extends AbstractRestClient { async sendProjectInvite( projectId: string, options: SendProjectInviteSchema, - endpoint = "{version}/projects/:projectId/invites" + endpoint = ":version/projects/:projectId/invites" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - + const requestUrl = this.getRequestUrl(endpoint, { projectId }, options); const body = JSON.stringify(options); - const result: MessageResponse = await this.post(this.fetch as Fetch, url, body); + const result: MessageResponse = await this.post(this.fetch as Fetch, requestUrl, body); return { result, error: null }; } catch (error) { @@ -399,13 +376,11 @@ export class ManageClient extends AbstractRestClient { async deleteProjectInvite( projectId: string, email: string, - endpoint = "{version}/projects/:projectId/invites/:email" + endpoint = ":version/projects/:projectId/invites/:email" ): Promise { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId).replace(/:email/, email); - - await this.delete(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId, email }); + await this.delete(this.fetch as Fetch, requestUrl); return { error: null }; } catch (error) { @@ -422,13 +397,11 @@ export class ManageClient extends AbstractRestClient { */ async leaveProject( projectId: string, - endpoint = "{version}/projects/:projectId/leave" + endpoint = ":version/projects/:projectId/leave" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - - const result: MessageResponse = await this.delete(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }); + const result: MessageResponse = await this.delete(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -446,14 +419,14 @@ export class ManageClient extends AbstractRestClient { async getProjectUsageRequests( projectId: string, options: GetProjectUsageRequestsSchema, - endpoint = "{version}/projects/:projectId/requests" + endpoint = ":version/projects/:projectId/requests" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - appendSearchParams(url.searchParams, options); - - const result: GetProjectUsageRequestsResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }, options); + const result: GetProjectUsageRequestsResponse = await this.get( + this.fetch as Fetch, + requestUrl + ); return { result, error: null }; } catch (error) { @@ -471,13 +444,14 @@ export class ManageClient extends AbstractRestClient { async getProjectUsageRequest( projectId: string, requestId: string, - endpoint = "{version}/projects/:projectId/requests/:requestId" + endpoint = ":version/projects/:projectId/requests/:requestId" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId).replace(/:requestId/, requestId); - - const result: GetProjectUsageRequestResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId, requestId }); + const result: GetProjectUsageRequestResponse = await this.get( + this.fetch as Fetch, + requestUrl + ); return { result, error: null }; } catch (error) { @@ -495,14 +469,14 @@ export class ManageClient extends AbstractRestClient { async getProjectUsageSummary( projectId: string, options: GetProjectUsageSummarySchema, - endpoint = "{version}/projects/:projectId/usage" + endpoint = ":version/projects/:projectId/usage" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - appendSearchParams(url.searchParams, options); - - const result: GetProjectUsageSummaryResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }, options); + const result: GetProjectUsageSummaryResponse = await this.get( + this.fetch as Fetch, + requestUrl + ); return { result, error: null }; } catch (error) { @@ -520,14 +494,11 @@ export class ManageClient extends AbstractRestClient { async getProjectUsageFields( projectId: string, options: GetProjectUsageFieldsSchema, - endpoint = "{version}/projects/:projectId/usage/fields" + endpoint = ":version/projects/:projectId/usage/fields" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - appendSearchParams(url.searchParams, options); - - const result: GetProjectUsageFieldsResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }, options); + const result: GetProjectUsageFieldsResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -544,13 +515,11 @@ export class ManageClient extends AbstractRestClient { */ async getProjectBalances( projectId: string, - endpoint = "{version}/projects/:projectId/balances" + endpoint = ":version/projects/:projectId/balances" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - - const result: GetProjectBalancesResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }); + const result: GetProjectBalancesResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -568,13 +537,11 @@ export class ManageClient extends AbstractRestClient { async getProjectBalance( projectId: string, balanceId: string, - endpoint = "{version}/projects/:projectId/balances/:balanceId" + endpoint = ":version/projects/:projectId/balances/:balanceId" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId).replace(/:balanceId/, balanceId); - - const result: GetProjectBalanceResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId, balanceId }); + const result: GetProjectBalanceResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { diff --git a/src/packages/OnPremClient.ts b/src/packages/OnPremClient.ts index 9cd50e37..8a231c51 100644 --- a/src/packages/OnPremClient.ts +++ b/src/packages/OnPremClient.ts @@ -17,13 +17,11 @@ export class OnPremClient extends AbstractRestClient { */ async listCredentials( projectId: string, - endpoint = "{version}/projects/:projectId/onprem/distribution/credentials" + endpoint = ":version/projects/:projectId/onprem/distribution/credentials" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - - const result: ListOnPremCredentialsResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId }); + const result: ListOnPremCredentialsResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -41,15 +39,11 @@ export class OnPremClient extends AbstractRestClient { async getCredentials( projectId: string, credentialsId: string, - endpoint = "{version}/projects/:projectId/onprem/distribution/credentials/:credentialsId" + endpoint = ":version/projects/:projectId/onprem/distribution/credentials/:credentialsId" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint - .replace(/:projectId/, projectId) - .replace(/:credentialsId/, credentialsId); - - const result: OnPremCredentialResponse = await this.get(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId, credentialsId }); + const result: OnPremCredentialResponse = await this.get(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { @@ -67,15 +61,17 @@ export class OnPremClient extends AbstractRestClient { async createCredentials( projectId: string, options: CreateOnPremCredentialsSchema, - endpoint = "{version}/projects/:projectId/onprem/distribution/credentials" + endpoint = ":version/projects/:projectId/onprem/distribution/credentials" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint.replace(/:projectId/, projectId); - + const requestUrl = this.getRequestUrl(endpoint, { projectId }); const body = JSON.stringify(options); - const result: OnPremCredentialResponse = await this.post(this.fetch as Fetch, url, body); + const result: OnPremCredentialResponse = await this.post( + this.fetch as Fetch, + requestUrl, + body + ); return { result, error: null }; } catch (error) { @@ -93,15 +89,11 @@ export class OnPremClient extends AbstractRestClient { async deleteCredentials( projectId: string, credentialsId: string, - endpoint = "{version}/projects/:projectId/onprem/distribution/credentials/:credentialsId" + endpoint = ":version/projects/:projectId/onprem/distribution/credentials/:credentialsId" ): Promise> { try { - const url = new URL(this.baseUrl); - url.pathname = endpoint - .replace(/:projectId/, projectId) - .replace(/:credentialsId/, credentialsId); - - const result: MessageResponse = await this.delete(this.fetch as Fetch, url); + const requestUrl = this.getRequestUrl(endpoint, { projectId, credentialsId }); + const result: MessageResponse = await this.delete(this.fetch as Fetch, requestUrl); return { result, error: null }; } catch (error) { diff --git a/src/packages/PrerecordedClient.ts b/src/packages/PrerecordedClient.ts index 29cd8cfb..8a12d017 100644 --- a/src/packages/PrerecordedClient.ts +++ b/src/packages/PrerecordedClient.ts @@ -17,7 +17,7 @@ export class PrerecordedClient extends AbstractRestClient { async transcribeUrl( source: UrlSource, options?: PrerecordedSchema, - endpoint = "{version}/listen" + endpoint = ":version/listen" ): Promise> { try { let body; @@ -34,12 +34,12 @@ export class PrerecordedClient extends AbstractRestClient { ); } - const transcriptionOptions: PrerecordedSchema = { ...{}, ...options }; - - const url = new URL(endpoint, this.baseUrl); - appendSearchParams(url.searchParams, transcriptionOptions); - - const result: SyncPrerecordedResponse = await this.post(this.fetch as Fetch, url, body); + const requestUrl = this.getRequestUrl(endpoint, {}, { ...{}, ...options }); + const result: SyncPrerecordedResponse = await this.post( + this.fetch as Fetch, + requestUrl, + body + ); return { result, error: null }; } catch (error) { @@ -54,7 +54,7 @@ export class PrerecordedClient extends AbstractRestClient { async transcribeFile( source: FileSource, options?: PrerecordedSchema, - endpoint = "{version}/listen" + endpoint = ":version/listen" ): Promise> { try { let body; @@ -71,14 +71,15 @@ export class PrerecordedClient extends AbstractRestClient { ); } - const transcriptionOptions: PrerecordedSchema = { ...{}, ...options }; - - const url = new URL(endpoint, this.baseUrl); - appendSearchParams(url.searchParams, transcriptionOptions); - - const result: SyncPrerecordedResponse = await this.post(this.fetch as Fetch, url, body, { - "Content-Type": "deepgram/audio+video", - }); + const requestUrl = this.getRequestUrl(endpoint, {}, { ...{}, ...options }); + const result: SyncPrerecordedResponse = await this.post( + this.fetch as Fetch, + requestUrl, + body, + { + "Content-Type": "deepgram/audio+video", + } + ); return { result, error: null }; } catch (error) { @@ -94,7 +95,7 @@ export class PrerecordedClient extends AbstractRestClient { source: UrlSource, callback: CallbackUrl, options?: PrerecordedSchema, - endpoint = "{version}/listen" + endpoint = ":version/listen" ): Promise> { try { let body; @@ -105,15 +106,16 @@ export class PrerecordedClient extends AbstractRestClient { throw new DeepgramError("Unknown transcription source type"); } - const transcriptionOptions: PrerecordedSchema = { - ...options, - ...{ callback: callback.toString() }, - }; - - const url = new URL(endpoint, this.baseUrl); - appendSearchParams(url.searchParams, transcriptionOptions); - - const result: AsyncPrerecordedResponse = await this.post(this.fetch as Fetch, url, body); + const requestUrl = this.getRequestUrl( + endpoint, + {}, + { ...options, callback: callback.toString() } + ); + const result: AsyncPrerecordedResponse = await this.post( + this.fetch as Fetch, + requestUrl, + body + ); return { result, error: null }; } catch (error) { @@ -129,7 +131,7 @@ export class PrerecordedClient extends AbstractRestClient { source: FileSource, callback: CallbackUrl, options?: PrerecordedSchema, - endpoint = "{version}/listen" + endpoint = ":version/listen" ): Promise> { try { let body; @@ -140,17 +142,27 @@ export class PrerecordedClient extends AbstractRestClient { throw new DeepgramError("Unknown transcription source type"); } - const transcriptionOptions: PrerecordedSchema = { - ...options, - ...{ callback: callback.toString() }, - }; - - const url = new URL(endpoint, this.baseUrl); - appendSearchParams(url.searchParams, transcriptionOptions); - - const result: AsyncPrerecordedResponse = await this.post(this.fetch as Fetch, url, body, { - "Content-Type": "deepgram/audio+video", - }); + // const transcriptionOptions: PrerecordedSchema = { + // ...options, + // ...{ callback: callback.toString() }, + // }; + + // const url = new URL(endpoint, this.baseUrl); + // appendSearchParams(url.searchParams, transcriptionOptions); + + const requestUrl = this.getRequestUrl( + endpoint, + {}, + { ...options, callback: callback.toString() } + ); + const result: AsyncPrerecordedResponse = await this.post( + this.fetch as Fetch, + requestUrl, + body, + { + "Content-Type": "deepgram/audio+video", + } + ); return { result, error: null }; } catch (error) { diff --git a/src/packages/ReadClient.ts b/src/packages/ReadClient.ts index 1bda177a..6f444a41 100644 --- a/src/packages/ReadClient.ts +++ b/src/packages/ReadClient.ts @@ -18,7 +18,7 @@ export class ReadClient extends AbstractRestClient { async analyzeUrl( source: UrlSource, options?: AnalyzeSchema, - endpoint = "{version}/read" + endpoint = ":version/read" ): Promise> { try { let body; @@ -35,12 +35,8 @@ export class ReadClient extends AbstractRestClient { ); } - const analyzeOptions: AnalyzeSchema = { ...{}, ...options }; - - const url = new URL(endpoint, this.baseUrl); - appendSearchParams(url.searchParams, analyzeOptions); - - const result: SyncAnalyzeResponse = await this.post(this.fetch as Fetch, url, body); + const requestUrl = this.getRequestUrl(endpoint, {}, { ...{}, ...options }); + const result: SyncAnalyzeResponse = await this.post(this.fetch as Fetch, requestUrl, body); return { result, error: null }; } catch (error) { @@ -55,7 +51,7 @@ export class ReadClient extends AbstractRestClient { async analyzeText( source: TextSource, options?: AnalyzeSchema, - endpoint = "{version}/read" + endpoint = ":version/read" ): Promise> { try { let body; @@ -72,12 +68,8 @@ export class ReadClient extends AbstractRestClient { ); } - const analyzeOptions: AnalyzeSchema = { ...{}, ...options }; - - const url = new URL(endpoint, this.baseUrl); - appendSearchParams(url.searchParams, analyzeOptions); - - const result: SyncAnalyzeResponse = await this.post(this.fetch as Fetch, url, body); + const requestUrl = this.getRequestUrl(endpoint, {}, { ...{}, ...options }); + const result: SyncAnalyzeResponse = await this.post(this.fetch as Fetch, requestUrl, body); return { result, error: null }; } catch (error) { @@ -93,7 +85,7 @@ export class ReadClient extends AbstractRestClient { source: UrlSource, callback: CallbackUrl, options?: AnalyzeSchema, - endpoint = "{version}/read" + endpoint = ":version/read" ): Promise> { try { let body; @@ -104,15 +96,12 @@ export class ReadClient extends AbstractRestClient { throw new DeepgramError("Unknown source type"); } - const transcriptionOptions: PrerecordedSchema = { - ...options, - ...{ callback: callback.toString() }, - }; - - const url = new URL(endpoint, this.baseUrl); - appendSearchParams(url.searchParams, transcriptionOptions); - - const result: AsyncAnalyzeResponse = await this.post(this.fetch as Fetch, url, body); + const requestUrl = this.getRequestUrl( + endpoint, + {}, + { ...options, callback: callback.toString() } + ); + const result: AsyncAnalyzeResponse = await this.post(this.fetch as Fetch, requestUrl, body); return { result, error: null }; } catch (error) { @@ -128,7 +117,7 @@ export class ReadClient extends AbstractRestClient { source: TextSource, callback: CallbackUrl, options?: AnalyzeSchema, - endpoint = "{version}/read" + endpoint = ":version/read" ): Promise> { try { let body; @@ -139,15 +128,12 @@ export class ReadClient extends AbstractRestClient { throw new DeepgramError("Unknown source type"); } - const transcriptionOptions: PrerecordedSchema = { - ...options, - ...{ callback: callback.toString() }, - }; - - const url = new URL(endpoint, this.baseUrl); - appendSearchParams(url.searchParams, transcriptionOptions); - - const result: AsyncAnalyzeResponse = await this.post(this.fetch as Fetch, url, body, { + const requestUrl = this.getRequestUrl( + endpoint, + {}, + { ...options, callback: callback.toString() } + ); + const result: AsyncAnalyzeResponse = await this.post(this.fetch as Fetch, requestUrl, body, { "Content-Type": "deepgram/audio+video", }); diff --git a/src/packages/SpeakClient.ts b/src/packages/SpeakClient.ts index 086fc288..5f1899a4 100644 --- a/src/packages/SpeakClient.ts +++ b/src/packages/SpeakClient.ts @@ -13,7 +13,7 @@ export class SpeakClient extends AbstractRestClient { async request( source: TextSource, options?: SpeakSchema, - endpoint = "{version}/speak" + endpoint = ":version/speak" ): Promise { try { let body; From 5212cdb063f194204f678ad0509ac8f3bc3a8654 Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 30 Apr 2024 15:53:15 -0700 Subject: [PATCH 08/12] chore: update readme for this upgrade --- README.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 85b2ffce..ca3c189c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,14 @@ Official JavaScript SDK for [Deepgram](https://www.deepgram.com/). Power your ap - [Initialization](#initialization) - [Getting an API Key](#getting-an-api-key) - [Scoped Configuration](#scoped-configuration) - - [Rest requests in the browser](#rest-requests-in-the-browser) + - [1. Global Defaults](#1-global-defaults) + - [2. Namespace-specific Configurations](#2-namespace-specific-configurations) + - [3. Transport Options](#3-transport-options) + - [Change the API url used for all SDK methods](#change-the-api-url-used-for-all-sdk-methods) + - [Change the API url used for transcription only](#change-the-api-url-used-for-transcription-only) + - [Override fetch transmitter](#override-fetch-transmitter) + - [Proxy requests in the browser](#proxy-requests-in-the-browser) + - [Set custom headers for fetch](#set-custom-headers-for-fetch) - [Transcription (Synchronous)](#transcription-synchronous) - [Remote Files](#remote-files) - [Local Files](#local-files) @@ -55,7 +62,7 @@ Official JavaScript SDK for [Deepgram](https://www.deepgram.com/). Power your ap - [Get On-Prem credentials](#get-on-prem-credentials) - [Create On-Prem credentials](#create-on-prem-credentials) - [Delete On-Prem credentials](#delete-on-prem-credentials) -- [Backwards Compatibility](#backwards-compatibility) +- [Backwards Compatibility](#backwards-compatibility) - [Development and Contributing](#development-and-contributing) - [Debugging and making changes locally](#debugging-and-making-changes-locally) - [Getting Help](#getting-help) @@ -130,20 +137,74 @@ const deepgram = createClient(DEEPGRAM_API_KEY); # Scoped Configuration -A new feature is scoped configuration. You'll be able to configure various aspects of the SDK from the initialization. +The SDK supports scoped configurtion. You'll be able to configure various aspects of each namespace of the SDK from the initialization. Below outlines a flexible and customizable configuration system for the Deepgram SDK. Here’s how the namespace configuration works: + +## 1. Global Defaults + +- The `global` namespace serves as the foundational configuration applicable across all other namespaces unless overridden. +- Includes general settings like URL and headers applicable for all API calls. +- If no specific configurations are provided for other namespaces, the `global` defaults are used. + +## 2. Namespace-specific Configurations + +- Each namespace (`listen`, `manage`, `onprem`, `read`, `speak`) can have its specific configurations which override the `global` settings within their respective scopes. +- Allows for detailed control over different parts of the application interacting with various Deepgram API endpoints. + +## 3. Transport Options + +- Configurations for both `fetch` and `websocket` can be specified under each namespace, allowing different transport mechanisms for different operations. +- For example, the `fetch` configuration can have its own URL and proxy settings distinct from the `websocket`. +- The generic interfaces define a structure for transport options which include a client (like a `fetch` or `WebSocket` instance) and associated options (like headers, URL, proxy settings). + +This configuration system enables robust customization where defaults provide a foundation, but every aspect of the client's interaction with the API can be finely controlled and tailored to specific needs through namespace-specific settings. This enhances the maintainability and scalability of the application by localizing configurations to their relevant contexts. + +## Change the API url used for all SDK methods + +Useful for using different API environments (for e.g. beta). + +```js +import { createClient } from "@deepgram/sdk"; +// - or - +// const { createClient } = require("@deepgram/sdk"); + +const deepgram = createClient(DEEPGRAM_API_KEY, { + global: { fetch: { options: { url: "https://api.beta.deepgram.com" } } }, +}); +``` + +## Change the API url used for transcription only + +Useful for on-prem installations. Only affects requests to `/listen` endpoints. + +```js +import { createClient } from "@deepgram/sdk"; +// - or - +// const { createClient } = require("@deepgram/sdk"); + +const deepgram = createClient(DEEPGRAM_API_KEY, { + listen: { fetch: { options: { url: "http://localhost:8080" } } }, +}); +``` + +## Override fetch transmitter + +Useful for providing a custom http client. ```js import { createClient } from "@deepgram/sdk"; // - or - // const { createClient } = require("@deepgram/sdk"); +const yourFetch = async () => { + return Response("...etc"); +}; + const deepgram = createClient(DEEPGRAM_API_KEY, { - global: { url: "https://api.beta.deepgram.com" }, - // restProxy: { url: "http://localhost:8080" } + global: { fetch: { client: yourFetch } }, }); ``` -## Rest requests in the browser +## Proxy requests in the browser This SDK now works in the browser. If you'd like to make REST-based requests (pre-recorded transcription, on-premise, and management requests), then you'll need to use a proxy as we do not support custom CORS origins on our API. To set up your proxy, you configure the SDK like so: @@ -151,7 +212,7 @@ This SDK now works in the browser. If you'd like to make REST-based requests (pr import { createClient } from "@deepgram/sdk"; const deepgram = createClient("proxy", { - restProxy: { url: "http://localhost:8080" }, + global: { fetch: { options: { proxy: { url: "http://localhost:8080" } } } }, }); ``` @@ -161,6 +222,18 @@ Your proxy service should replace the Authorization header with `Authorization: Check out our example Node-based proxy here: [Deepgram Node Proxy](https://github.com/deepgram-devs/deepgram-node-proxy). +## Set custom headers for fetch + +Useful for many things. + +```js +import { createClient } from "@deepgram/sdk"; + +const deepgram = createClient("proxy", { + global: { fetch: { options: { headers: { "x-custom-header": "foo" } } } }, +}); +``` + # Transcription (Synchronous) ## Remote Files @@ -562,6 +635,8 @@ const { result, error } = await deepgram.onprem.deleteCredentials(projectId, cre Older SDK versions will receive Priority 1 (P1) bug support only. Security issues, both in our code and dependencies, are promptly addressed. Significant bugs without clear workarounds are also given priority attention. +We strictly follow semver, and will not introduce breaking changes to the publicly documented interfaces of the SDK. Use internal and undocumented interfaces without pinning your version, at your own risk. + # Development and Contributing Interested in contributing? We ❤️ pull requests! From 1300e91aa6c6c348affa9c3b0987678fc5b0ac87 Mon Sep 17 00:00:00 2001 From: Luke Date: Sun, 5 May 2024 16:21:33 -0700 Subject: [PATCH 09/12] feat: use api key from namespace config --- src/lib/constants.ts | 2 +- src/lib/types/DeepgramClientOptions.ts | 63 +++++----- src/packages/AbstractClient.ts | 21 ++-- src/packages/AbstractRestClient.ts | 2 +- src/packages/LiveClient.ts | 110 ------------------ test/client.test.ts | 2 + test/live.test.ts | 152 ++++++++++++------------- 7 files changed, 124 insertions(+), 228 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 34812fe6..bb1d867f 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -12,7 +12,7 @@ export const DEFAULT_HEADERS = { export const DEFAULT_URL = "https://api.deepgram.com"; -export const DEFAULT_GLOBAL_OPTIONS: DefaultNamespaceOptions = { +export const DEFAULT_GLOBAL_OPTIONS: Partial = { fetch: { options: { url: DEFAULT_URL, headers: DEFAULT_HEADERS } }, websocket: { options: { url: convertProtocolToWs(DEFAULT_URL) } }, }; diff --git a/src/lib/types/DeepgramClientOptions.ts b/src/lib/types/DeepgramClientOptions.ts index 981fdb89..c68549f4 100644 --- a/src/lib/types/DeepgramClientOptions.ts +++ b/src/lib/types/DeepgramClientOptions.ts @@ -15,32 +15,6 @@ export type IWebSocket = typeof WebSocket; * 2. An array with a single `DeepgramClientOptions` object, representing the configuration options for the Deepgram client. */ -/** - * Configures the options for a Deepgram client. - * - * The `DeepgramClientOptions` interface defines the configuration options for a Deepgram client. It includes options for various namespaces, such as `global`, `listen`, `manage`, `onprem`, `read`, and `speak`. Each namespace has its own options for configuring the transport, including the URL, proxy, and options for the fetch and WebSocket clients. - * - * The `global` namespace is used to configure options that apply globally to the Deepgram client. The other namespaces are used to configure options specific to different Deepgram API endpoints. - */ -export interface DeepgramClientOptions { - key?: string | IKeyFactory; - global?: NamespaceOptions & { url?: string; headers?: { [index: string]: any } }; - listen?: NamespaceOptions; - manage?: NamespaceOptions; - onprem?: NamespaceOptions; - read?: NamespaceOptions; - speak?: NamespaceOptions; - - /** - * Support introductory format - */ - [index: string]: any; - // _experimentalCustomFetch?: Fetch; - // restProxy?: { - // url: null | string; - // }; -} - interface TransportFetchOptions extends TransportOptions, FetchOptions {} interface TransportWebSocketOptions extends TransportOptions { _nodeOnlyHeaders?: { [index: string]: any }; @@ -59,12 +33,9 @@ interface ITransport { client?: C; options?: O; } -export interface NamespaceOptions { - fetch?: ITransport; - websocket?: ITransport; -} export type DefaultNamespaceOptions = { + key: string; fetch: { options: { url: TransportUrl }; }; @@ -73,6 +44,36 @@ export type DefaultNamespaceOptions = { }; } & NamespaceOptions; +export interface NamespaceOptions { + key?: string; + fetch?: ITransport; + websocket?: ITransport; +} + export type DefaultClientOptions = { - global: DefaultNamespaceOptions; + global: Partial; } & DeepgramClientOptions; + +/** + * Configures the options for a Deepgram client. + * + * The `DeepgramClientOptions` interface defines the configuration options for a Deepgram client. It includes options for various namespaces, such as `global`, `listen`, `manage`, `onprem`, `read`, and `speak`. Each namespace has its own options for configuring the transport, including the URL, proxy, and options for the fetch and WebSocket clients. + * + * The `global` namespace is used to configure options that apply globally to the Deepgram client. The other namespaces are used to configure options specific to different Deepgram API endpoints. + * Support introductory formats: + * - fetch: FetchOptions; + * - _experimentalCustomFetch?: Fetch; + * - restProxy?: { + * url: null | string; + * }; + */ +export interface DeepgramClientOptions { + key?: string | IKeyFactory; + global?: NamespaceOptions & { url?: string; headers?: { [index: string]: any } }; + listen?: NamespaceOptions; + manage?: NamespaceOptions; + onprem?: NamespaceOptions; + read?: NamespaceOptions; + speak?: NamespaceOptions; + [index: string]: any; +} diff --git a/src/packages/AbstractClient.ts b/src/packages/AbstractClient.ts index 66216509..9ee202fc 100644 --- a/src/packages/AbstractClient.ts +++ b/src/packages/AbstractClient.ts @@ -22,7 +22,6 @@ import { export abstract class AbstractClient extends EventEmitter { protected factory: Function | undefined = undefined; protected key: string; - protected namespaceOptions: DefaultNamespaceOptions; protected options: DefaultClientOptions; public namespace: string = "global"; public version: string = "v1"; @@ -69,14 +68,6 @@ export abstract class AbstractClient extends EventEmitter { options, DEFAULT_OPTIONS ); - - /** - * Roll up options for this namespace. - */ - this.namespaceOptions = applyDefaults( - this.options[this.namespace], - this.options.global! - ); } public v(version: string = "v1"): this { @@ -93,6 +84,18 @@ export abstract class AbstractClient extends EventEmitter { return this.key === "proxy" && !!this.namespaceOptions.fetch.options.proxy?.url; } + get namespaceOptions(): DefaultNamespaceOptions { + const defaults = applyDefaults( + this.options[this.namespace], + this.options.global + ); + + return { + ...defaults, + key: this.key, + }; + } + /** * Generates a URL for an API endpoint with optional query parameters and transcription options. * diff --git a/src/packages/AbstractRestClient.ts b/src/packages/AbstractRestClient.ts index c499c069..ef70e8d3 100644 --- a/src/packages/AbstractRestClient.ts +++ b/src/packages/AbstractRestClient.ts @@ -19,7 +19,7 @@ export abstract class AbstractRestClient extends AbstractClient { ); } - this.fetch = fetchWithAuth(this.key, this.namespaceOptions.fetch.client); + this.fetch = fetchWithAuth(this.namespaceOptions.key, this.namespaceOptions.fetch.client); if (this.proxy) { this.baseUrl = this.namespaceOptions.fetch.options.proxy!.url; diff --git a/src/packages/LiveClient.ts b/src/packages/LiveClient.ts index eb646bf6..f7998f2d 100644 --- a/src/packages/LiveClient.ts +++ b/src/packages/LiveClient.ts @@ -1,7 +1,6 @@ import { AbstractLiveClient } from "./AbstractLiveClient"; import { DeepgramError } from "../lib/errors"; import { LiveConnectionState, LiveTranscriptionEvents } from "../lib/enums"; -import { w3cwebsocket } from "websocket"; import { type LiveSchema, @@ -15,7 +14,6 @@ import { export class LiveClient extends AbstractLiveClient { public namespace: string = "listen"; - protected _socket: w3cwebsocket; // Constructor implementation constructor( @@ -24,113 +22,5 @@ export class LiveClient extends AbstractLiveClient { endpoint: string = ":version/listen" ) { super(options); - - const requestUrl = this.getRequestUrl(endpoint, {}, transcriptionOptions); - this._socket = new w3cwebsocket(requestUrl.toString(), ["token", this.key]); - - this._socket.onopen = () => { - this.emit(LiveTranscriptionEvents.Open, this); - }; - - this._socket.onclose = (event: any) => { - this.emit(LiveTranscriptionEvents.Close, event); - }; - - this._socket.onerror = (event) => { - this.emit(LiveTranscriptionEvents.Error, event); - }; - - this._socket.onmessage = (event) => { - try { - const data: any = JSON.parse(event.data.toString()); - - if (data.type === LiveTranscriptionEvents.Metadata) { - this.emit(LiveTranscriptionEvents.Metadata, data as LiveMetadataEvent); - } - - if (data.type === LiveTranscriptionEvents.Transcript) { - this.emit(LiveTranscriptionEvents.Transcript, data as LiveTranscriptionEvent); - } - - if (data.type === LiveTranscriptionEvents.UtteranceEnd) { - this.emit(LiveTranscriptionEvents.UtteranceEnd, data as UtteranceEndEvent); - } - - if (data.type === LiveTranscriptionEvents.SpeechStarted) { - this.emit(LiveTranscriptionEvents.SpeechStarted, data as SpeechStartedEvent); - } - } catch (error) { - this.emit(LiveTranscriptionEvents.Error, { - event, - message: "Unable to parse `data` as JSON.", - error, - }); - } - }; - } - - public configure(config: LiveConfigOptions): void { - this._socket.send( - JSON.stringify({ - type: "Configure", - processors: config, - }) - ); - } - - public keepAlive(): void { - this._socket.send( - JSON.stringify({ - type: "KeepAlive", - }) - ); - } - - public getReadyState(): LiveConnectionState { - return this._socket.readyState; - } - - /** - * Sends data to the Deepgram API via websocket connection - * @param data Audio data to send to Deepgram - * - * Conforms to RFC #146 for Node.js - does not send an empty byte. - * In the browser, a Blob will contain length with no audio. - * @see https://github.com/deepgram/deepgram-python-sdk/issues/146 - */ - public send(data: string | ArrayBufferLike | Blob): void { - if (this._socket.readyState === LiveConnectionState.OPEN) { - if (typeof data === "string") { - this._socket.send(data); // send text data - } else if ((data as any) instanceof Blob) { - this._socket.send(data as unknown as ArrayBufferLike); // send blob data - } else { - const buffer = data as ArrayBufferLike; - - if (buffer.byteLength > 0) { - this._socket.send(buffer); // send buffer when not zero-byte (or browser) - } else { - this.emit( - LiveTranscriptionEvents.Warning, - "Zero-byte detected, skipping. Send `CloseStream` if trying to close the connection." - ); - } - } - } else { - throw new DeepgramError("Could not send. Connection not open."); - } - } - - /** - * Denote that you are finished sending audio and close - * the websocket connection when transcription is finished - */ - public finish(): void { - // tell the server to close the socket - this._socket.send( - JSON.stringify({ - type: "CloseStream", - }) - ); } } diff --git a/test/client.test.ts b/test/client.test.ts index ed444eaf..bc2fbd8f 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -99,6 +99,8 @@ describe("testing creation of a deepgram client object", () => { const { result, error } = await client.manage.getProjectBalances(faker.string.uuid()); + console.log("result", result); + assert.isNull(error); assert.isNotNull(result); assert.containsAllDeepKeys(result, ["customFetch"]); diff --git a/test/live.test.ts b/test/live.test.ts index dbaef965..fb52ef90 100644 --- a/test/live.test.ts +++ b/test/live.test.ts @@ -1,76 +1,76 @@ -import { assert, expect } from "chai"; -import { createClient } from "../src"; -import { faker } from "@faker-js/faker"; -import DeepgramClient from "../src/DeepgramClient"; -import { LiveConnectionState, LiveTranscriptionEvents } from "../src/lib/enums"; - -describe("connecting to our transcription websocket", () => { - let deepgram: DeepgramClient; - - beforeEach(() => { - deepgram = createClient(faker.string.alphanumeric(40), { - global: { url: "wss://api.mock.deepgram.com" }, - }); - }); - - it("should create the client object", () => { - expect(deepgram).to.not.be.undefined; - expect(deepgram).is.instanceOf(DeepgramClient); - }); - - it("should connect to the websocket", function (done) { - const connection = deepgram.listen.live({ model: "general", tier: "enhanced" }); - - connection.on(LiveTranscriptionEvents.Open, (event) => { - expect(connection.getReadyState()).to.eq(LiveConnectionState.OPEN); - - connection.on(LiveTranscriptionEvents.Metadata, (data) => { - assert.isNotNull(data); - assert.containsAllDeepKeys(data, ["request_id"]); - - connection.finish(); - done(); - }); - }); - }); - - it("should send data and recieve a transcription object back", function (done) { - const connection = deepgram.listen.live({ model: "general", tier: "enhanced" }); - - connection.on(LiveTranscriptionEvents.Open, () => { - connection.on(LiveTranscriptionEvents.Metadata, (data) => { - assert.isNotNull(data); - assert.containsAllDeepKeys(data, ["request_id"]); - }); - - connection.on(LiveTranscriptionEvents.Transcript, (data) => { - assert.isNotNull(data); - assert.containsAllDeepKeys(data, ["channel"]); - - connection.finish(); - done(); - }); - - connection.send(new Uint8Array(100)); // mock ArrayBufferLike audio data - }); - }); - - it("should receive a warning if trying to send zero-byte length data", function (done) { - const connection = deepgram.listen.live({ model: "general", tier: "enhanced" }); - - connection.on(LiveTranscriptionEvents.Open, () => { - connection.on(LiveTranscriptionEvents.Warning, (data) => { - assert.isNotNull(data); - - expect(data).to.eq( - "Zero-byte detected, skipping. Send `CloseStream` if trying to close the connection." - ); - - connection.finish(); - done(); - }); - - connection.send(new Uint8Array(0)); - }); - }); -}); +// import { assert, expect } from "chai"; +// import { createClient } from "../src"; +// import { faker } from "@faker-js/faker"; +// import DeepgramClient from "../src/DeepgramClient"; +// import { LiveConnectionState, LiveTranscriptionEvents } from "../src/lib/enums"; + +// describe("connecting to our transcription websocket", () => { +// let deepgram: DeepgramClient; + +// beforeEach(() => { +// deepgram = createClient(faker.string.alphanumeric(40), { +// global: { url: "wss://api.mock.deepgram.com" }, +// }); +// }); + +// it("should create the client object", () => { +// expect(deepgram).to.not.be.undefined; +// expect(deepgram).is.instanceOf(DeepgramClient); +// }); + +// it("should connect to the websocket", function (done) { +// const connection = deepgram.listen.live({ model: "general", tier: "enhanced" }); + +// connection.on(LiveTranscriptionEvents.Open, (event) => { +// expect(connection.getReadyState()).to.eq(LiveConnectionState.OPEN); + +// connection.on(LiveTranscriptionEvents.Metadata, (data) => { +// assert.isNotNull(data); +// assert.containsAllDeepKeys(data, ["request_id"]); + +// connection.finish(); +// done(); +// }); +// }); +// }); + +// it("should send data and recieve a transcription object back", function (done) { +// const connection = deepgram.listen.live({ model: "general", tier: "enhanced" }); + +// connection.on(LiveTranscriptionEvents.Open, () => { +// connection.on(LiveTranscriptionEvents.Metadata, (data) => { +// assert.isNotNull(data); +// assert.containsAllDeepKeys(data, ["request_id"]); +// }); + +// connection.on(LiveTranscriptionEvents.Transcript, (data) => { +// assert.isNotNull(data); +// assert.containsAllDeepKeys(data, ["channel"]); + +// connection.finish(); +// done(); +// }); + +// connection.send(new Uint8Array(100)); // mock ArrayBufferLike audio data +// }); +// }); + +// it("should receive a warning if trying to send zero-byte length data", function (done) { +// const connection = deepgram.listen.live({ model: "general", tier: "enhanced" }); + +// connection.on(LiveTranscriptionEvents.Open, () => { +// connection.on(LiveTranscriptionEvents.Warning, (data) => { +// assert.isNotNull(data); + +// expect(data).to.eq( +// "Zero-byte detected, skipping. Send `CloseStream` if trying to close the connection." +// ); + +// connection.finish(); +// done(); +// }); + +// connection.send(new Uint8Array(0)); +// }); +// }); +// }); From bde5747c396ec7c2c306c9170dfbcdffbb49f48d Mon Sep 17 00:00:00 2001 From: Luke Oliff Date: Tue, 30 Apr 2024 15:14:47 +0100 Subject: [PATCH 10/12] fix: process is undefined error in browsers (#275) --- examples/browser-prerecorded/index.html | 31 +++++++++++++++++++++++++ webpack.config.js | 6 +++++ 2 files changed, 37 insertions(+) create mode 100644 examples/browser-prerecorded/index.html diff --git a/examples/browser-prerecorded/index.html b/examples/browser-prerecorded/index.html new file mode 100644 index 00000000..6cb98002 --- /dev/null +++ b/examples/browser-prerecorded/index.html @@ -0,0 +1,31 @@ + + + + + + + + Running test... check the developer console. + + diff --git a/webpack.config.js b/webpack.config.js index 04a8b84c..a6c95016 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,5 @@ const path = require("path"); +const webpack = require("webpack"); module.exports = { entry: "./src/index.ts", @@ -24,4 +25,9 @@ module.exports = { resolve: { extensions: [".ts", ".js", ".json"], }, + plugins: [ + new webpack.DefinePlugin({ + "process.versions.node": JSON.stringify(process.versions.node), + }), + ], }; From ffd67260ac89ecbc609d24bf521379b95e9bb2d9 Mon Sep 17 00:00:00 2001 From: Luke Date: Sun, 5 May 2024 09:43:13 -0700 Subject: [PATCH 11/12] feat: new abstract live client --- examples/browser-live/index.html | 31 +++++++ examples/browser-prerecorded/index.html | 30 ++++++- src/packages/LiveClient.ts | 108 ++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 examples/browser-live/index.html diff --git a/examples/browser-live/index.html b/examples/browser-live/index.html new file mode 100644 index 00000000..6cb98002 --- /dev/null +++ b/examples/browser-live/index.html @@ -0,0 +1,31 @@ + + + + + + + + Running test... check the developer console. + + diff --git a/examples/browser-prerecorded/index.html b/examples/browser-prerecorded/index.html index 6cb98002..f3b31e6c 100644 --- a/examples/browser-prerecorded/index.html +++ b/examples/browser-prerecorded/index.html @@ -16,12 +16,34 @@ console.log("Deepgram Instance: ", _deepgram); - (async () => { - const { result, error } = await _deepgram.manage.getProjects(); + const transcribeUrl = async () => { + const { result, error } = await _deepgram.listen.prerecorded.transcribeUrl( + { + url: "https://dpgr.am/spacewalk.wav", + }, + { + model: "nova", + } + ); + + if (error) throw error; + if (!error) console.dir(result, { depth: null }); + }; + + const transcribeFile = async () => { + const { result, error } = await _deepgram.listen.prerecorded.transcribeFile( + fs.readFileSync("./examples/nasa.mp4"), + { + model: "nova", + } + ); - console.log(result, error); - })(); + if (error) throw error; + if (!error) console.dir(result, { depth: null }); + }; + transcribeUrl(); + transcribeFile(); // ... diff --git a/src/packages/LiveClient.ts b/src/packages/LiveClient.ts index f7998f2d..5352546e 100644 --- a/src/packages/LiveClient.ts +++ b/src/packages/LiveClient.ts @@ -22,5 +22,113 @@ export class LiveClient extends AbstractLiveClient { endpoint: string = ":version/listen" ) { super(options); + + const requestUrl = this.getRequestUrl(endpoint, {}, transcriptionOptions); + this.conn = this.connect(requestUrl); + + this._socket.onopen = () => { + this.emit(LiveTranscriptionEvents.Open, this); + }; + + this._socket.onclose = (event: any) => { + this.emit(LiveTranscriptionEvents.Close, event); + }; + + this._socket.onerror = (event) => { + this.emit(LiveTranscriptionEvents.Error, event); + }; + + this._socket.onmessage = (event) => { + try { + const data: any = JSON.parse(event.data.toString()); + + if (data.type === LiveTranscriptionEvents.Metadata) { + this.emit(LiveTranscriptionEvents.Metadata, data as LiveMetadataEvent); + } + + if (data.type === LiveTranscriptionEvents.Transcript) { + this.emit(LiveTranscriptionEvents.Transcript, data as LiveTranscriptionEvent); + } + + if (data.type === LiveTranscriptionEvents.UtteranceEnd) { + this.emit(LiveTranscriptionEvents.UtteranceEnd, data as UtteranceEndEvent); + } + + if (data.type === LiveTranscriptionEvents.SpeechStarted) { + this.emit(LiveTranscriptionEvents.SpeechStarted, data as SpeechStartedEvent); + } + } catch (error) { + this.emit(LiveTranscriptionEvents.Error, { + event, + message: "Unable to parse `data` as JSON.", + error, + }); + } + }; + } + + public configure(config: LiveConfigOptions): void { + this._socket.send( + JSON.stringify({ + type: "Configure", + processors: config, + }) + ); + } + + public keepAlive(): void { + this._socket.send( + JSON.stringify({ + type: "KeepAlive", + }) + ); + } + + public getReadyState(): LiveConnectionState { + return this._socket.readyState; + } + + /** + * Sends data to the Deepgram API via websocket connection + * @param data Audio data to send to Deepgram + * + * Conforms to RFC #146 for Node.js - does not send an empty byte. + * In the browser, a Blob will contain length with no audio. + * @see https://github.com/deepgram/deepgram-python-sdk/issues/146 + */ + public send(data: string | ArrayBufferLike | Blob): void { + if (this._socket.readyState === LiveConnectionState.OPEN) { + if (typeof data === "string") { + this._socket.send(data); // send text data + } else if ((data as any) instanceof Blob) { + this._socket.send(data as unknown as ArrayBufferLike); // send blob data + } else { + const buffer = data as ArrayBufferLike; + + if (buffer.byteLength > 0) { + this._socket.send(buffer); // send buffer when not zero-byte (or browser) + } else { + this.emit( + LiveTranscriptionEvents.Warning, + "Zero-byte detected, skipping. Send `CloseStream` if trying to close the connection." + ); + } + } + } else { + throw new DeepgramError("Could not send. Connection not open."); + } + } + + /** + * Denote that you are finished sending audio and close + * the websocket connection when transcription is finished + */ + public finish(): void { + // tell the server to close the socket + this._socket.send( + JSON.stringify({ + type: "CloseStream", + }) + ); } } From fe9a862f8863410e6a9b7673a16fecce27e271a2 Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 7 May 2024 20:16:16 +0100 Subject: [PATCH 12/12] feat: rebuilding test suite entirely --- package-lock.json | 317 ++++++++++-------- package.json | 8 +- src/DeepgramClient.ts | 14 +- src/index.ts | 4 +- src/lib/constants.ts | 20 +- src/lib/enums/LiveConnectionState.ts | 13 +- src/lib/enums/LiveTranscriptionEvents.ts | 19 +- src/lib/enums/index.ts | 4 +- src/lib/fetch.ts | 4 +- src/lib/helpers.ts | 6 +- src/lib/types/DeepgramClientOptions.ts | 20 +- src/lib/types/LiveConfigOptions.ts | 10 +- src/lib/types/index.ts | 93 +++-- src/packages/AbstractClient.ts | 28 +- src/packages/AbstractLiveClient.ts | 273 ++++++++++++++- src/packages/AbstractRestClient.ts | 20 +- src/packages/ListenClient.ts | 8 +- src/packages/ListenLiveClient.ts | 110 ++++++ ...erecordedClient.ts => ListenRestClient.ts} | 6 +- src/packages/LiveClient.ts | 134 -------- src/packages/ManageClient.ts | 13 +- src/packages/OnPremClient.ts | 2 +- src/packages/ReadClient.ts | 5 +- src/packages/SpeakClient.ts | 4 +- src/packages/index.ts | 18 +- test/AbstractClient.test.ts | 83 +++++ test/AbstractLiveClient.test.ts | 101 ++++++ test/AbstractRestClient.test.ts | 217 ++++++++++++ test/ListenLiveClient.test.ts | 75 +++++ test/client.test.ts | 108 ------ test/constants.test.ts | 22 -- test/errors.test.ts | 31 -- test/helpers.test.ts | 18 - test/legacy.test.ts | 120 ------- test/live.test.ts | 76 ----- test/manage.test.ts | 231 ------------- test/onprem.test.ts | 61 ---- test/prerecorded.test.ts | 65 ---- 38 files changed, 1220 insertions(+), 1141 deletions(-) create mode 100644 src/packages/ListenLiveClient.ts rename src/packages/{PrerecordedClient.ts => ListenRestClient.ts} (96%) delete mode 100644 src/packages/LiveClient.ts create mode 100644 test/AbstractClient.test.ts create mode 100644 test/AbstractLiveClient.test.ts create mode 100644 test/AbstractRestClient.test.ts create mode 100644 test/ListenLiveClient.test.ts delete mode 100644 test/client.test.ts delete mode 100644 test/constants.test.ts delete mode 100644 test/errors.test.ts delete mode 100644 test/helpers.test.ts delete mode 100644 test/legacy.test.ts delete mode 100644 test/live.test.ts delete mode 100644 test/manage.test.ts delete mode 100644 test/onprem.test.ts delete mode 100644 test/prerecorded.test.ts diff --git a/package-lock.json b/package-lock.json index f71e9bf3..bbdbc9bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,10 @@ "license": "MIT", "dependencies": { "@deepgram/captions": "^1.1.1", - "@types/websocket": "^1.0.9", "cross-fetch": "^3.1.5", "deepmerge": "^4.3.1", "events": "^3.3.0", - "websocket": "^1.0.34" + "ws": "^8.17.0" }, "devDependencies": { "@commitlint/cli": "^17.6.7", @@ -23,6 +22,8 @@ "@flydotio/dockerfile": "^0.4.10", "@types/chai": "^4.3.5", "@types/mocha": "^9.1.1", + "@types/sinon": "^17.0.3", + "@types/ws": "^8.5.10", "chai": "^4.3.7", "cross-env": "^7.0.3", "husky": "^4.3.0", @@ -34,6 +35,7 @@ "pretty-quick": "^3.1.3", "rimraf": "^3.0.2", "semantic-release-plugin-update-version-in-files": "^1.1.0", + "sinon": "^17.0.1", "ts-loader": "^8.0.11", "ts-node": "^10.9.1", "typedoc": "^0.22.16", @@ -1072,6 +1074,50 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -1155,7 +1201,8 @@ "node_modules/@types/node": { "version": "14.18.53", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.53.tgz", - "integrity": "sha512-soGmOpVBUq+gaBMwom1M+krC/NNbWlosh4AtGA03SyWNDiqSKtwp7OulO1M6+mg8YkHMvJ/y0AkCeO8d1hNb7A==" + "integrity": "sha512-soGmOpVBUq+gaBMwom1M+krC/NNbWlosh4AtGA03SyWNDiqSKtwp7OulO1M6+mg8YkHMvJ/y0AkCeO8d1hNb7A==", + "dev": true }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -1169,10 +1216,26 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, - "node_modules/@types/websocket": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.9.tgz", - "integrity": "sha512-xrMBdqdKdlE+7L9Wg2PQblIkZGSgiMlEoP6UAaYKMHbbxqCJ6PV/pTZ2RcMcSSERurU2TtGbmO4lqpFOJd01ww==", + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -1706,6 +1769,8 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -2168,15 +2233,6 @@ "node": ">= 8" } }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "node_modules/dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", @@ -2539,46 +2595,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -2610,25 +2632,6 @@ "node": ">=8.0.0" } }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esniff/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -2672,15 +2675,6 @@ "node": ">=4.0" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -2724,19 +2718,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3736,7 +3717,8 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -4070,6 +4052,12 @@ "node": "*" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -4178,6 +4166,12 @@ "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "node_modules/lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", @@ -4647,17 +4641,25 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" - }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, "node_modules/node-fetch": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", @@ -4681,6 +4683,8 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.0.tgz", "integrity": "sha512-PbZERfeFdrHQOOXiAKOY0VPbykZy90ndPKk0d+CFDegTKmWp1VgOTz2xACVbr1BjCWxrQp68CXtvNsveFhqDJg==", + "optional": true, + "peer": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -5378,6 +5382,12 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -6329,6 +6339,54 @@ "node": ">=10" } }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6875,11 +6933,6 @@ "node": ">=0.3.1" } }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -6970,6 +7023,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -7093,6 +7147,8 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -7292,35 +7348,6 @@ "node": ">=10.13.0" } }, - "node_modules/websocket": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", - "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "dependencies": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.50", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/websocket/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/websocket/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -7475,6 +7502,26 @@ "typedarray-to-buffer": "^3.1.5" } }, + "node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7484,14 +7531,6 @@ "node": ">=10" } }, - "node_modules/yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "engines": { - "node": ">=0.10.32" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index a0daf61d..a0c43732 100644 --- a/package.json +++ b/package.json @@ -45,18 +45,17 @@ "build:module": "tsc -p tsconfig.module.json", "build:umd": "webpack --mode=production", "watch": "nodemon -e ts --watch src --exec \"npm run build\"", - "test": "mocha -r ts-node/register test/*test.ts test/**/*test.ts --insect --timeout 5000 || :", + "test": "mocha -r ts-node/register test/*test.ts test/**/*test.ts --inspect --exit", "test:coverage": "nyc --reporter=lcovonly --reporter=text --reporter=text-summary npm run test", "docs": "typedoc --entryPoints src/index.ts --out docs/v2 --includes src/**/*.ts", "docs:json": "typedoc --entryPoints src/index.ts --includes src/**/*.ts --json docs/v2/spec.json --excludeExternals" }, "dependencies": { "@deepgram/captions": "^1.1.1", - "@types/websocket": "^1.0.9", "cross-fetch": "^3.1.5", "deepmerge": "^4.3.1", "events": "^3.3.0", - "websocket": "^1.0.34" + "ws": "^8.17.0" }, "devDependencies": { "@commitlint/cli": "^17.6.7", @@ -65,6 +64,8 @@ "@flydotio/dockerfile": "^0.4.10", "@types/chai": "^4.3.5", "@types/mocha": "^9.1.1", + "@types/sinon": "^17.0.3", + "@types/ws": "^8.5.10", "chai": "^4.3.7", "cross-env": "^7.0.3", "husky": "^4.3.0", @@ -76,6 +77,7 @@ "pretty-quick": "^3.1.3", "rimraf": "^3.0.2", "semantic-release-plugin-update-version-in-files": "^1.1.0", + "sinon": "^17.0.1", "ts-loader": "^8.0.11", "ts-node": "^10.9.1", "typedoc": "^0.22.16", diff --git a/src/DeepgramClient.ts b/src/DeepgramClient.ts index 1aa09312..7b0fa04e 100644 --- a/src/DeepgramClient.ts +++ b/src/DeepgramClient.ts @@ -1,10 +1,12 @@ import { DeepgramVersionError } from "./lib/errors"; -import { AbstractClient } from "./packages/AbstractClient"; -import { ListenClient } from "./packages/ListenClient"; -import { ManageClient } from "./packages/ManageClient"; -import { OnPremClient } from "./packages/OnPremClient"; -import { ReadClient } from "./packages/ReadClient"; -import { SpeakClient } from "./packages/SpeakClient"; +import { + AbstractClient, + ListenClient, + ManageClient, + OnPremClient, + ReadClient, + SpeakClient, +} from "./packages"; /** * The DeepgramClient class provides access to various Deepgram API clients, including ListenClient, ManageClient, OnPremClient, ReadClient, and SpeakClient. diff --git a/src/index.ts b/src/index.ts index d96c552d..0c2925fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ -import DeepgramClient from "./DeepgramClient"; import { DeepgramVersionError } from "./lib/errors"; -import { DeepgramClientOptions, IKeyFactory } from "./lib/types/DeepgramClientOptions"; +import DeepgramClient from "./DeepgramClient"; +import type { DeepgramClientOptions, IKeyFactory } from "./lib/types"; /** * This class is deprecated and should not be used. It throws a `DeepgramVersionError` when instantiated. diff --git a/src/lib/constants.ts b/src/lib/constants.ts index bb1d867f..de6714c5 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,6 +1,6 @@ import { convertProtocolToWs, isBrowser } from "./helpers"; -import { DefaultNamespaceOptions, DefaultClientOptions } from "./types/DeepgramClientOptions"; import { version } from "./version"; +import type { DefaultNamespaceOptions, DefaultClientOptions } from "./types"; export const NODE_VERSION = process.versions.node; @@ -14,9 +14,25 @@ export const DEFAULT_URL = "https://api.deepgram.com"; export const DEFAULT_GLOBAL_OPTIONS: Partial = { fetch: { options: { url: DEFAULT_URL, headers: DEFAULT_HEADERS } }, - websocket: { options: { url: convertProtocolToWs(DEFAULT_URL) } }, + websocket: { + options: { url: convertProtocolToWs(DEFAULT_URL), _nodeOnlyHeaders: DEFAULT_HEADERS }, + }, }; export const DEFAULT_OPTIONS: DefaultClientOptions = { global: DEFAULT_GLOBAL_OPTIONS, }; + +export enum SOCKET_STATES { + connecting = 0, + open = 1, + closing = 2, + closed = 3, +} + +export enum CONNECTION_STATE { + Connecting = "connecting", + Open = "open", + Closing = "closing", + Closed = "closed", +} diff --git a/src/lib/enums/LiveConnectionState.ts b/src/lib/enums/LiveConnectionState.ts index 11538068..2d604871 100644 --- a/src/lib/enums/LiveConnectionState.ts +++ b/src/lib/enums/LiveConnectionState.ts @@ -1,6 +1,11 @@ +import { SOCKET_STATES } from "../constants"; + +/** + * @deprecated Since 3.4. use SOCKET_STATES for generic socket connection states. + */ export enum LiveConnectionState { - CONNECTING = 0, - OPEN = 1, - CLOSING = 2, - CLOSED = 3, + CONNECTING = SOCKET_STATES.connecting, + OPEN = SOCKET_STATES.open, + CLOSING = SOCKET_STATES.closing, + CLOSED = SOCKET_STATES.closed, } diff --git a/src/lib/enums/LiveTranscriptionEvents.ts b/src/lib/enums/LiveTranscriptionEvents.ts index cad0323c..06699771 100644 --- a/src/lib/enums/LiveTranscriptionEvents.ts +++ b/src/lib/enums/LiveTranscriptionEvents.ts @@ -1,10 +1,21 @@ export enum LiveTranscriptionEvents { + /** + * Built in socket events. + */ Open = "open", Close = "close", - Transcript = "Results", // exact match to data type from API - Metadata = "Metadata", // exact match to data type from API Error = "error", - Warning = "warning", - UtteranceEnd = "UtteranceEnd", // exact match to data type from API + + /** + * Message { type: string } + */ + Transcript = "Results", + Metadata = "Metadata", + UtteranceEnd = "UtteranceEnd", SpeechStarted = "SpeechStarted", + + /** + * Catch all for any other message event + */ + Unhandled = "Unhandled", } diff --git a/src/lib/enums/index.ts b/src/lib/enums/index.ts index 9c6ba0f6..42226c91 100644 --- a/src/lib/enums/index.ts +++ b/src/lib/enums/index.ts @@ -1,2 +1,2 @@ -export { LiveConnectionState } from "./LiveConnectionState"; -export { LiveTranscriptionEvents } from "./LiveTranscriptionEvents"; +export * from "./LiveConnectionState"; +export * from "./LiveTranscriptionEvents"; diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 37f7e594..8212f0a4 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,6 +1,6 @@ -import crossFetch from "cross-fetch"; import { resolveHeadersConstructor } from "./helpers"; -import type { Fetch } from "./types/Fetch"; +import crossFetch from "cross-fetch"; +import type { Fetch } from "./types"; export const resolveFetch = (customFetch?: Fetch): Fetch => { let _fetch: Fetch; diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index ed3300c0..d1809fbb 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -1,5 +1,7 @@ import { Headers as CrossFetchHeaders } from "cross-fetch"; -import { +import { Readable } from "stream"; +import merge from "deepmerge"; +import type { DeepgramClientOptions, FileSource, PrerecordedSource, @@ -9,8 +11,6 @@ import { LiveSchema, TranscriptionSchema, } from "./types"; -import { Readable } from "stream"; -import merge from "deepmerge"; export function stripTrailingSlash(url: string): string { return url.replace(/\/$/, ""); diff --git a/src/lib/types/DeepgramClientOptions.ts b/src/lib/types/DeepgramClientOptions.ts index c68549f4..758add3a 100644 --- a/src/lib/types/DeepgramClientOptions.ts +++ b/src/lib/types/DeepgramClientOptions.ts @@ -1,4 +1,4 @@ -import { FetchOptions } from "./Fetch"; +import { Fetch, FetchOptions } from "./Fetch"; export type IKeyFactory = () => string; export type IFetch = typeof fetch; @@ -75,5 +75,21 @@ export interface DeepgramClientOptions { onprem?: NamespaceOptions; read?: NamespaceOptions; speak?: NamespaceOptions; - [index: string]: any; + + /** + * @deprecated as of 3.4, use a namespace like `global` instead + */ + fetch?: FetchOptions; + + /** + * @deprecated as of 3.4, use a namespace like `global` instead + */ + _experimentalCustomFetch?: Fetch; + + /** + * @deprecated as of 3.4, use a namespace like `global` instead + */ + restProxy?: { + url: null | string; + }; } diff --git a/src/lib/types/LiveConfigOptions.ts b/src/lib/types/LiveConfigOptions.ts index 6a12954b..47b60c87 100644 --- a/src/lib/types/LiveConfigOptions.ts +++ b/src/lib/types/LiveConfigOptions.ts @@ -1,3 +1,7 @@ -export interface LiveConfigOptions { - numerals?: boolean; -} +import { LiveSchema } from "./TranscriptionSchema"; + +/** + * Partial configuration options for the LiveSchema, including: + * - `numerals`: Configures how numerals are handled in the live transcription. + */ +export type LiveConfigOptions = Partial>; diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 95ceb017..2f96bb17 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -1,54 +1,39 @@ -export type { AnalyzeSchema } from "./AnalyzeSchema"; -export type { AsyncPrerecordedResponse } from "./AsyncPrerecordedResponse"; -export type { AsyncAnalyzeResponse } from "./AsyncAnalyzeResponse"; -export type { CreateOnPremCredentialsSchema } from "./CreateOnPremCredentialsSchema"; -export type { CreateProjectKeySchema } from "./CreateProjectKeySchema"; -export type { CreateProjectKeyResponse } from "./CreateProjectKeyResponse"; -export type { DeepgramClientOptions } from "./DeepgramClientOptions"; -export type { DeepgramResponse } from "./DeepgramResponse"; -export type { Fetch } from "./Fetch"; -export type { - GetProjectBalanceResponse, - GetProjectBalancesResponse, -} from "./GetProjectBalancesResponse"; -export type { GetProjectInvitesResponse } from "./GetProjectInvitesResponse"; -export type { GetProjectKeyResponse, GetProjectKeysResponse } from "./GetProjectKeysResponse"; -export type { GetProjectMemberScopesResponse } from "./GetProjectMemberScopesResponse"; -export type { GetProjectMembersResponse } from "./GetProjectMembersResponse"; -export type { GetProjectResponse } from "./GetProjectResponse"; -export type { GetProjectsResponse } from "./GetProjectsResponse"; -export type { GetProjectUsageFieldsSchema } from "./GetProjectUsageFieldsSchema"; -export type { GetProjectUsageFieldsResponse } from "./GetProjectUsageFieldsResponse"; -export type { - GetProjectUsageRequestResponse, - GetProjectUsageRequestsResponse, -} from "./GetProjectUsageRequestsResponse"; -export type { GetProjectUsageRequestsSchema } from "./GetProjectUsageRequestsSchema"; -export type { GetProjectUsageSummarySchema } from "./GetProjectUsageSummarySchema"; -export type { GetProjectUsageSummaryResponse } from "./GetProjectUsageSummaryResponse"; -export type { GetTokenDetailsResponse } from "./GetTokenDetailsResponse"; -export type { - ListOnPremCredentialsResponse, - OnPremCredentialResponse, -} from "./ListOnPremCredentialsResponse"; -export type { LiveConfigOptions } from "./LiveConfigOptions"; -export type { LiveMetadataEvent } from "./LiveMetadataEvent"; -export type { LiveTranscriptionEvent } from "./LiveTranscriptionEvent"; -export type { MessageResponse } from "./MessageResponse"; -export type { - FileSource, - PrerecordedSource, - UrlSource, - TextSource, - AnalyzeSource, -} from "./DeepgramSource"; -export type { SendProjectInviteSchema } from "./SendProjectInviteSchema"; -export type { SpeakSchema } from "./SpeakSchema"; -export type { SpeechStartedEvent } from "./SpeechStartedEvent"; -export type { SyncPrerecordedResponse } from "./SyncPrerecordedResponse"; -export type { SyncAnalyzeResponse } from "./SyncAnalyzeResponse"; -export type { TranscriptionSchema, PrerecordedSchema, LiveSchema } from "./TranscriptionSchema"; -export type { UpdateProjectMemberScopeSchema } from "./UpdateProjectMemberScopeSchema"; -export type { UpdateProjectSchema } from "./UpdateProjectSchema"; -export type { UtteranceEndEvent } from "./UtteranceEndEvent"; -export type { VoidResponse } from "./VoidResponse"; +export type * from "./AnalyzeSchema"; +export type * from "./AsyncAnalyzeResponse"; +export type * from "./AsyncPrerecordedResponse"; +export type * from "./CreateOnPremCredentialsSchema"; +export type * from "./CreateProjectKeyResponse"; +export type * from "./CreateProjectKeySchema"; +export type * from "./DeepgramClientOptions"; +export type * from "./DeepgramResponse"; +export type * from "./DeepgramSource"; +export type * from "./Fetch"; +export type * from "./GetProjectBalancesResponse"; +export type * from "./GetProjectInvitesResponse"; +export type * from "./GetProjectKeysResponse"; +export type * from "./GetProjectMemberScopesResponse"; +export type * from "./GetProjectMembersResponse"; +export type * from "./GetProjectResponse"; +export type * from "./GetProjectsResponse"; +export type * from "./GetProjectUsageFieldsResponse"; +export type * from "./GetProjectUsageFieldsSchema"; +export type * from "./GetProjectUsageRequestsResponse"; +export type * from "./GetProjectUsageRequestsSchema"; +export type * from "./GetProjectUsageSummaryResponse"; +export type * from "./GetProjectUsageSummarySchema"; +export type * from "./GetTokenDetailsResponse"; +export type * from "./ListOnPremCredentialsResponse"; +export type * from "./LiveConfigOptions"; +export type * from "./LiveMetadataEvent"; +export type * from "./LiveTranscriptionEvent"; +export type * from "./MessageResponse"; +export type * from "./SendProjectInviteSchema"; +export type * from "./SpeakSchema"; +export type * from "./SpeechStartedEvent"; +export type * from "./SyncAnalyzeResponse"; +export type * from "./SyncPrerecordedResponse"; +export type * from "./TranscriptionSchema"; +export type * from "./UpdateProjectMemberScopeSchema"; +export type * from "./UpdateProjectSchema"; +export type * from "./UtteranceEndEvent"; +export type * from "./VoidResponse"; diff --git a/src/packages/AbstractClient.ts b/src/packages/AbstractClient.ts index 9ee202fc..9c8f3693 100644 --- a/src/packages/AbstractClient.ts +++ b/src/packages/AbstractClient.ts @@ -2,12 +2,14 @@ import EventEmitter from "events"; import { DEFAULT_OPTIONS, DEFAULT_URL } from "../lib/constants"; import { DeepgramError } from "../lib/errors"; import { appendSearchParams, applyDefaults, convertLegacyOptions } from "../lib/helpers"; -import { DeepgramClientOptions, LiveSchema, TranscriptionSchema } from "../lib/types"; -import { +import type { + DeepgramClientOptions, DefaultClientOptions, DefaultNamespaceOptions, NamespaceOptions, -} from "../lib/types/DeepgramClientOptions"; +} from "../lib/types"; + +export const noop = () => {}; /** * Represents an abstract Deepgram client that provides a base implementation for interacting with the Deepgram API. @@ -26,6 +28,7 @@ export abstract class AbstractClient extends EventEmitter { public namespace: string = "global"; public version: string = "v1"; public baseUrl: string = DEFAULT_URL; + public logger: Function = noop; /** * Constructs a new instance of the DeepgramClient class with the provided options. @@ -76,17 +79,9 @@ export abstract class AbstractClient extends EventEmitter { return this; } - /** - * Determines whether the current instance should proxy requests. - * @returns {boolean} true if the current instance should proxy requests; otherwise, false - */ - get proxy(): boolean { - return this.key === "proxy" && !!this.namespaceOptions.fetch.options.proxy?.url; - } - get namespaceOptions(): DefaultNamespaceOptions { const defaults = applyDefaults( - this.options[this.namespace], + (this.options as any)[this.namespace], this.options.global ); @@ -137,4 +132,13 @@ export abstract class AbstractClient extends EventEmitter { return url; } + + /** + * Logs the message. + * + * For customized logging, `this.logger` can be overridden. + */ + public log(kind: string, msg: string, data?: any) { + this.logger(kind, msg, data); + } } diff --git a/src/packages/AbstractLiveClient.ts b/src/packages/AbstractLiveClient.ts index da25fe16..3e222f83 100644 --- a/src/packages/AbstractLiveClient.ts +++ b/src/packages/AbstractLiveClient.ts @@ -1,15 +1,278 @@ -import { DeepgramClientOptions } from "../lib/types"; -import { AbstractClient } from "./AbstractClient"; +import { AbstractClient, noop } from "./AbstractClient"; +import { CONNECTION_STATE, SOCKET_STATES } from "../lib/constants"; +import type { DeepgramClientOptions, LiveSchema } from "../lib/types"; +import type { WebSocket as WSWebSocket } from "ws"; +/** + * Represents a constructor for a WebSocket-like object that can be used in the application. + * The constructor takes the following parameters: + * @param address - The URL or address of the WebSocket server. + * @param _ignored - An optional parameter that is ignored. + * @param options - An optional object containing headers to be included in the WebSocket connection. + * @returns A WebSocket-like object that implements the WebSocketLike interface. + */ +interface WebSocketLikeConstructor { + new ( + address: string | URL, + _ignored?: any, + options?: { headers: Object | undefined } + ): WebSocketLike; +} + +/** + * Represents the types of WebSocket-like connections that can be used in the application. + * This type is used to provide a common interface for different WebSocket implementations, + * such as the native WebSocket API, a WebSocket wrapper library, or a dummy implementation + * for testing purposes. + */ +type WebSocketLike = WebSocket | WSWebSocket | WSWebSocketDummy; + +/** + * Represents the types of data that can be sent or received over a WebSocket-like connection. + */ +type SocketDataLike = string | ArrayBufferLike | Blob; + +/** + * Represents an error that occurred in a WebSocket-like connection. + * @property {any} error - The underlying error object. + * @property {string} message - A human-readable error message. + * @property {string} type - The type of the error. + */ +interface WebSocketLikeError { + error: any; + message: string; + type: string; +} + +/** + * Indicates whether a native WebSocket implementation is available in the current environment. + */ +const NATIVE_WEBSOCKET_AVAILABLE = typeof WebSocket !== "undefined"; + +/** + * Represents an abstract live client that extends the AbstractClient class. + * The AbstractLiveClient class provides functionality for connecting, reconnecting, and disconnecting a WebSocket connection, as well as sending data over the connection. + * Subclasses of this class are responsible for setting up the connection event handlers. + * + * @abstract + */ export abstract class AbstractLiveClient extends AbstractClient { - // Constructor implementation + public headers: { [key: string]: string }; + public transport: WebSocketLikeConstructor | null; + public conn: WebSocketLike | null = null; + public sendBuffer: Function[] = []; + constructor(options: DeepgramClientOptions) { super(options); + const { + key, + websocket: { options: websocketOptions, client }, + } = this.namespaceOptions; + if (this.proxy) { - this.baseUrl = this.namespaceOptions.websocket.options.proxy!.url; + this.baseUrl = websocketOptions.proxy!.url; + } else { + this.baseUrl = websocketOptions.url; + } + + if (client) { + this.transport = client; + } else { + this.transport = null; + } + + if (websocketOptions._nodeOnlyHeaders) { + this.headers = websocketOptions._nodeOnlyHeaders; + } else { + this.headers = {}; + } + + if (!("Authorization" in this.headers)) { + this.headers["Authorization"] = `Token ${key}`; // Add default token + } + } + + /** + * Connects the socket, unless already connected. + * + * @protected Can only be called from within the class. + */ + protected connect(transcriptionOptions: LiveSchema, endpoint: string): void { + if (this.conn) { + return; + } + + this.reconnect = (options = transcriptionOptions) => { + this.connect(options, endpoint); + }; + + const requestUrl = this.getRequestUrl(endpoint, {}, transcriptionOptions); + + /** + * Custom websocket transport + */ + if (this.transport) { + this.conn = new this.transport(requestUrl, undefined, { + headers: this.headers, + }); + return; + } + + /** + * Native websocket transport (browser) + */ + if (NATIVE_WEBSOCKET_AVAILABLE) { + this.conn = new WebSocket(requestUrl, ["token", this.namespaceOptions.key]); + this.setupConnection(); + return; + } + + /** + * Dummy websocket + */ + this.conn = new WSWebSocketDummy(requestUrl, undefined, { + close: () => { + this.conn = null; + }, + }); + + /** + * WS package for node environment + */ + import("ws").then(({ default: WS }) => { + this.conn = new WS(requestUrl, undefined, { + headers: this.headers, + }); + this.setupConnection(); + }); + } + + /** + * Reconnects the socket using new or existing transcription options. + * + * @param options - The transcription options to use when reconnecting the socket. + */ + public reconnect: (options: LiveSchema) => void = noop; + + /** + * Disconnects the socket from the client. + * + * @param code A numeric status code to send on disconnect. + * @param reason A custom reason for the disconnect. + */ + public disconnect(code?: number, reason?: string): void { + if (this.conn) { + this.conn.onclose = function () {}; // noop + if (code) { + this.conn.close(code, reason ?? ""); + } else { + this.conn.close(); + } + this.conn = null; + } + } + + /** + * Returns the current connection state of the WebSocket connection. + * + * @returns The current connection state of the WebSocket connection. + */ + public connectionState(): CONNECTION_STATE { + switch (this.conn && this.conn.readyState) { + case SOCKET_STATES.connecting: + return CONNECTION_STATE.Connecting; + case SOCKET_STATES.open: + return CONNECTION_STATE.Open; + case SOCKET_STATES.closing: + return CONNECTION_STATE.Closing; + default: + return CONNECTION_STATE.Closed; + } + } + + /** + * Returns the current ready state of the WebSocket connection. + * + * @returns The current ready state of the WebSocket connection. + */ + public getReadyState(): SOCKET_STATES { + return this.conn?.readyState ?? SOCKET_STATES.closed; + } + + /** + * Returns `true` is the connection is open. + */ + public isConnected(): boolean { + return this.connectionState() === CONNECTION_STATE.Open; + } + + /** + * Sends data to the Deepgram API via websocket connection + * @param data Audio data to send to Deepgram + * + * Conforms to RFC #146 for Node.js - does not send an empty byte. + * @see https://github.com/deepgram/deepgram-python-sdk/issues/146 + */ + send(data: SocketDataLike): void { + const callback = async () => { + if (data instanceof Blob) { + if (data.size === 0) { + this.log("warn", "skipping `send` for zero-byte blob", data); + + return; + } + + data = await data.arrayBuffer(); + } + + if (typeof data !== "string") { + if (data.byteLength === 0) { + this.log("warn", "skipping `send` for zero-byte blob", data); + + return; + } + } + + this.conn?.send(data); + }; + + if (this.isConnected()) { + callback(); } else { - this.baseUrl = this.namespaceOptions.websocket.options.url; + this.sendBuffer.push(callback); } } + + /** + * Determines whether the current instance should proxy requests. + * @returns {boolean} true if the current instance should proxy requests; otherwise, false + */ + get proxy(): boolean { + return this.key === "proxy" && !!this.namespaceOptions.websocket.options.proxy?.url; + } + + /** + * Sets up the connection event handlers. + * + * @abstract Requires subclasses to set up context aware event handlers. + */ + abstract setupConnection(): void; +} + +class WSWebSocketDummy { + binaryType: string = "arraybuffer"; + close: Function; + onclose: Function = () => {}; + onerror: Function = () => {}; + onmessage: Function = () => {}; + onopen: Function = () => {}; + readyState: number = SOCKET_STATES.connecting; + send: Function = () => {}; + url: string | URL | null = null; + + constructor(address: URL, _protocols: undefined, options: { close: Function }) { + this.url = address.toString(); + this.close = options.close; + } } diff --git a/src/packages/AbstractRestClient.ts b/src/packages/AbstractRestClient.ts index ef70e8d3..2efaf283 100644 --- a/src/packages/AbstractRestClient.ts +++ b/src/packages/AbstractRestClient.ts @@ -1,10 +1,14 @@ +import { AbstractClient } from "./AbstractClient"; import { DeepgramApiError, DeepgramError, DeepgramUnknownError } from "../lib/errors"; -import { Readable } from "stream"; import { fetchWithAuth, resolveResponse } from "../lib/fetch"; -import type { Fetch, FetchParameters, RequestMethodType } from "../lib/types/Fetch"; -import { AbstractClient } from "./AbstractClient"; -import { DeepgramClientOptions } from "../lib/types"; import { isBrowser } from "../lib/helpers"; +import { Readable } from "stream"; +import type { + DeepgramClientOptions, + Fetch, + FetchParameters, + RequestMethodType, +} from "../lib/types"; export abstract class AbstractRestClient extends AbstractClient { protected fetch: Fetch; @@ -158,4 +162,12 @@ export abstract class AbstractRestClient extends AbstractClient { ): Promise { return this._handleRequest(fetcher, "DELETE", url, headers, parameters); } + + /** + * Determines whether the current instance should proxy requests. + * @returns {boolean} true if the current instance should proxy requests; otherwise, false + */ + get proxy(): boolean { + return this.key === "proxy" && !!this.namespaceOptions.fetch.options.proxy?.url; + } } diff --git a/src/packages/ListenClient.ts b/src/packages/ListenClient.ts index 524b7d5d..58fe854a 100644 --- a/src/packages/ListenClient.ts +++ b/src/packages/ListenClient.ts @@ -1,16 +1,16 @@ import { AbstractClient } from "./AbstractClient"; -import { LiveClient } from "./LiveClient"; +import { ListenLiveClient } from "./ListenLiveClient"; import { LiveSchema } from "../lib/types"; -import { PrerecordedClient } from "./PrerecordedClient"; +import { ListenRestClient } from "./ListenRestClient"; export class ListenClient extends AbstractClient { public namespace: string = "listen"; get prerecorded() { - return new PrerecordedClient(this.options); + return new ListenRestClient(this.options); } public live(transcriptionOptions: LiveSchema = {}, endpoint = ":version/listen") { - return new LiveClient(this.options, transcriptionOptions, endpoint); + return new ListenLiveClient(this.options, transcriptionOptions, endpoint); } } diff --git a/src/packages/ListenLiveClient.ts b/src/packages/ListenLiveClient.ts new file mode 100644 index 00000000..4b38e22a --- /dev/null +++ b/src/packages/ListenLiveClient.ts @@ -0,0 +1,110 @@ +import { AbstractLiveClient } from "./AbstractLiveClient"; +import { LiveTranscriptionEvents } from "../lib/enums"; +import type { LiveSchema, LiveConfigOptions, DeepgramClientOptions } from "../lib/types"; + +export class ListenLiveClient extends AbstractLiveClient { + public namespace: string = "listen"; + + // Constructor implementation + constructor( + options: DeepgramClientOptions, + transcriptionOptions: LiveSchema = {}, + endpoint: string = ":version/listen" + ) { + super(options); + + this.connect(transcriptionOptions, endpoint); + } + + /** + * Sets up the connection event handlers. + * This method is responsible for handling the various events that can occur on the WebSocket connection, such as opening, closing, and receiving messages. + * - When the connection is opened, it emits the `LiveTranscriptionEvents.Open` event. + * - When the connection is closed, it emits the `LiveTranscriptionEvents.Close` event. + * - When an error occurs on the connection, it emits the `LiveTranscriptionEvents.Error` event. + * - When a message is received, it parses the message and emits the appropriate event based on the message type, such as `LiveTranscriptionEvents.Metadata`, `LiveTranscriptionEvents.Transcript`, `LiveTranscriptionEvents.UtteranceEnd`, and `LiveTranscriptionEvents.SpeechStarted`. + */ + public setupConnection(): void { + if (this.conn) { + this.conn.onopen = () => { + this.emit(LiveTranscriptionEvents.Open, this); + }; + + this.conn.onclose = (event: any) => { + this.emit(LiveTranscriptionEvents.Close, event); + }; + + this.conn.onerror = (event: ErrorEvent) => { + this.emit(LiveTranscriptionEvents.Error, event); + }; + + this.conn.onmessage = (event: MessageEvent) => { + try { + const data: any = JSON.parse(event.data.toString()); + + if (data.type === LiveTranscriptionEvents.Metadata) { + this.emit(LiveTranscriptionEvents.Metadata, data); + } else if (data.type === LiveTranscriptionEvents.Transcript) { + this.emit(LiveTranscriptionEvents.Transcript, data); + } else if (data.type === LiveTranscriptionEvents.UtteranceEnd) { + this.emit(LiveTranscriptionEvents.UtteranceEnd, data); + } else if (data.type === LiveTranscriptionEvents.SpeechStarted) { + this.emit(LiveTranscriptionEvents.SpeechStarted, data); + } else { + this.emit(LiveTranscriptionEvents.Unhandled, data); + } + } catch (error) { + this.emit(LiveTranscriptionEvents.Error, { + event, + message: "Unable to parse `data` as JSON.", + error, + }); + } + }; + } + } + + /** + * Sends additional config to the connected session. + * + * @param config - The configuration options to apply to the LiveClient. + * @param config.numerals - We currently only support numerals. + */ + public configure(config: LiveConfigOptions): void { + this.send( + JSON.stringify({ + type: "Configure", + processors: config, + }) + ); + } + + /** + * Sends a "KeepAlive" message to the server to maintain the connection. + */ + public keepAlive(): void { + this.send( + JSON.stringify({ + type: "KeepAlive", + }) + ); + } + + /** + * @deprecated Since version 3.4. Will be removed in version 4.0. Use `close` instead. + */ + public finish(): void { + this.requestClose(); + } + + /** + * Requests the server close the connection. + */ + public requestClose(): void { + this.send( + JSON.stringify({ + type: "CloseStream", + }) + ); + } +} diff --git a/src/packages/PrerecordedClient.ts b/src/packages/ListenRestClient.ts similarity index 96% rename from src/packages/PrerecordedClient.ts rename to src/packages/ListenRestClient.ts index 8a12d017..ba1b3445 100644 --- a/src/packages/PrerecordedClient.ts +++ b/src/packages/ListenRestClient.ts @@ -1,4 +1,5 @@ -import { CallbackUrl, appendSearchParams, isFileSource, isUrlSource } from "../lib/helpers"; +import { AbstractRestClient } from "./AbstractRestClient"; +import { CallbackUrl, isFileSource, isUrlSource } from "../lib/helpers"; import { DeepgramError, isDeepgramError } from "../lib/errors"; import type { AsyncPrerecordedResponse, @@ -9,9 +10,8 @@ import type { SyncPrerecordedResponse, UrlSource, } from "../lib/types"; -import { AbstractRestClient } from "./AbstractRestClient"; -export class PrerecordedClient extends AbstractRestClient { +export class ListenRestClient extends AbstractRestClient { public namespace: string = "listen"; async transcribeUrl( diff --git a/src/packages/LiveClient.ts b/src/packages/LiveClient.ts deleted file mode 100644 index 5352546e..00000000 --- a/src/packages/LiveClient.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { AbstractLiveClient } from "./AbstractLiveClient"; -import { DeepgramError } from "../lib/errors"; -import { LiveConnectionState, LiveTranscriptionEvents } from "../lib/enums"; - -import { - type LiveSchema, - type LiveConfigOptions, - type LiveMetadataEvent, - type LiveTranscriptionEvent, - type DeepgramClientOptions, - type UtteranceEndEvent, - type SpeechStartedEvent, -} from "../lib/types"; - -export class LiveClient extends AbstractLiveClient { - public namespace: string = "listen"; - - // Constructor implementation - constructor( - options: DeepgramClientOptions, - transcriptionOptions: LiveSchema = {}, - endpoint: string = ":version/listen" - ) { - super(options); - - const requestUrl = this.getRequestUrl(endpoint, {}, transcriptionOptions); - this.conn = this.connect(requestUrl); - - this._socket.onopen = () => { - this.emit(LiveTranscriptionEvents.Open, this); - }; - - this._socket.onclose = (event: any) => { - this.emit(LiveTranscriptionEvents.Close, event); - }; - - this._socket.onerror = (event) => { - this.emit(LiveTranscriptionEvents.Error, event); - }; - - this._socket.onmessage = (event) => { - try { - const data: any = JSON.parse(event.data.toString()); - - if (data.type === LiveTranscriptionEvents.Metadata) { - this.emit(LiveTranscriptionEvents.Metadata, data as LiveMetadataEvent); - } - - if (data.type === LiveTranscriptionEvents.Transcript) { - this.emit(LiveTranscriptionEvents.Transcript, data as LiveTranscriptionEvent); - } - - if (data.type === LiveTranscriptionEvents.UtteranceEnd) { - this.emit(LiveTranscriptionEvents.UtteranceEnd, data as UtteranceEndEvent); - } - - if (data.type === LiveTranscriptionEvents.SpeechStarted) { - this.emit(LiveTranscriptionEvents.SpeechStarted, data as SpeechStartedEvent); - } - } catch (error) { - this.emit(LiveTranscriptionEvents.Error, { - event, - message: "Unable to parse `data` as JSON.", - error, - }); - } - }; - } - - public configure(config: LiveConfigOptions): void { - this._socket.send( - JSON.stringify({ - type: "Configure", - processors: config, - }) - ); - } - - public keepAlive(): void { - this._socket.send( - JSON.stringify({ - type: "KeepAlive", - }) - ); - } - - public getReadyState(): LiveConnectionState { - return this._socket.readyState; - } - - /** - * Sends data to the Deepgram API via websocket connection - * @param data Audio data to send to Deepgram - * - * Conforms to RFC #146 for Node.js - does not send an empty byte. - * In the browser, a Blob will contain length with no audio. - * @see https://github.com/deepgram/deepgram-python-sdk/issues/146 - */ - public send(data: string | ArrayBufferLike | Blob): void { - if (this._socket.readyState === LiveConnectionState.OPEN) { - if (typeof data === "string") { - this._socket.send(data); // send text data - } else if ((data as any) instanceof Blob) { - this._socket.send(data as unknown as ArrayBufferLike); // send blob data - } else { - const buffer = data as ArrayBufferLike; - - if (buffer.byteLength > 0) { - this._socket.send(buffer); // send buffer when not zero-byte (or browser) - } else { - this.emit( - LiveTranscriptionEvents.Warning, - "Zero-byte detected, skipping. Send `CloseStream` if trying to close the connection." - ); - } - } - } else { - throw new DeepgramError("Could not send. Connection not open."); - } - } - - /** - * Denote that you are finished sending audio and close - * the websocket connection when transcription is finished - */ - public finish(): void { - // tell the server to close the socket - this._socket.send( - JSON.stringify({ - type: "CloseStream", - }) - ); - } -} diff --git a/src/packages/ManageClient.ts b/src/packages/ManageClient.ts index a97b5600..9f6b49da 100644 --- a/src/packages/ManageClient.ts +++ b/src/packages/ManageClient.ts @@ -1,8 +1,8 @@ +import { AbstractRestClient } from "./AbstractRestClient"; import { isDeepgramError } from "../lib/errors"; -import { appendSearchParams } from "../lib/helpers"; import type { - CreateProjectKeySchema, CreateProjectKeyResponse, + CreateProjectKeySchema, DeepgramResponse, Fetch, GetProjectBalanceResponse, @@ -14,21 +14,20 @@ import type { GetProjectMembersResponse, GetProjectResponse, GetProjectsResponse, - GetProjectUsageFieldsSchema, GetProjectUsageFieldsResponse, + GetProjectUsageFieldsSchema, GetProjectUsageRequestResponse, - GetProjectUsageRequestsSchema, GetProjectUsageRequestsResponse, - GetProjectUsageSummarySchema, + GetProjectUsageRequestsSchema, GetProjectUsageSummaryResponse, + GetProjectUsageSummarySchema, + GetTokenDetailsResponse, MessageResponse, SendProjectInviteSchema, UpdateProjectMemberScopeSchema, UpdateProjectSchema, VoidResponse, - GetTokenDetailsResponse, } from "../lib/types"; -import { AbstractRestClient } from "./AbstractRestClient"; export class ManageClient extends AbstractRestClient { public namespace: string = "manage"; diff --git a/src/packages/OnPremClient.ts b/src/packages/OnPremClient.ts index 8a231c51..554a8622 100644 --- a/src/packages/OnPremClient.ts +++ b/src/packages/OnPremClient.ts @@ -1,3 +1,4 @@ +import { AbstractRestClient } from "./AbstractRestClient"; import { isDeepgramError } from "../lib/errors"; import type { CreateOnPremCredentialsSchema, @@ -7,7 +8,6 @@ import type { MessageResponse, OnPremCredentialResponse, } from "../lib/types"; -import { AbstractRestClient } from "./AbstractRestClient"; export class OnPremClient extends AbstractRestClient { public namespace: string = "onprem"; diff --git a/src/packages/ReadClient.ts b/src/packages/ReadClient.ts index 6f444a41..51e02fe4 100644 --- a/src/packages/ReadClient.ts +++ b/src/packages/ReadClient.ts @@ -1,16 +1,15 @@ -import { CallbackUrl, appendSearchParams, isTextSource, isUrlSource } from "../lib/helpers"; +import { AbstractRestClient } from "./AbstractRestClient"; +import { CallbackUrl, isTextSource, isUrlSource } from "../lib/helpers"; import { DeepgramError, isDeepgramError } from "../lib/errors"; import type { AnalyzeSchema, AsyncAnalyzeResponse, DeepgramResponse, Fetch, - PrerecordedSchema, SyncAnalyzeResponse, TextSource, UrlSource, } from "../lib/types"; -import { AbstractRestClient } from "./AbstractRestClient"; export class ReadClient extends AbstractRestClient { public namespace: string = "read"; diff --git a/src/packages/SpeakClient.ts b/src/packages/SpeakClient.ts index 5f1899a4..d01e4586 100644 --- a/src/packages/SpeakClient.ts +++ b/src/packages/SpeakClient.ts @@ -1,7 +1,7 @@ -import { DeepgramError, DeepgramUnknownError, isDeepgramError } from "../lib/errors"; +import { AbstractRestClient } from "./AbstractRestClient"; import { appendSearchParams, isTextSource } from "../lib/helpers"; +import { DeepgramError, DeepgramUnknownError } from "../lib/errors"; import { Fetch, SpeakSchema, TextSource } from "../lib/types"; -import { AbstractRestClient } from "./AbstractRestClient"; export class SpeakClient extends AbstractRestClient { public namespace: string = "speak"; diff --git a/src/packages/index.ts b/src/packages/index.ts index 0a351ba9..8c114f85 100644 --- a/src/packages/index.ts +++ b/src/packages/index.ts @@ -1,8 +1,10 @@ -export { AbstractClient } from "./AbstractClient"; -export { AbstractRestClient } from "./AbstractRestClient"; -export { AbstractLiveClient } from "./AbstractLiveClient"; -export { ListenClient } from "./ListenClient"; -export { LiveClient } from "./LiveClient"; -export { ManageClient } from "./ManageClient"; -export { OnPremClient } from "./OnPremClient"; -export { PrerecordedClient } from "./PrerecordedClient"; +export * from "./AbstractClient"; +export * from "./AbstractLiveClient"; +export * from "./AbstractRestClient"; +export * from "./ListenClient"; +export * from "./ListenLiveClient"; +export * from "./ListenRestClient"; +export * from "./ManageClient"; +export * from "./OnPremClient"; +export * from "./ReadClient"; +export * from "./SpeakClient"; diff --git a/test/AbstractClient.test.ts b/test/AbstractClient.test.ts new file mode 100644 index 00000000..4fc8d863 --- /dev/null +++ b/test/AbstractClient.test.ts @@ -0,0 +1,83 @@ +import { AbstractClient } from "../src/packages/AbstractClient"; +import { expect } from "chai"; +import merge from "deepmerge"; +import sinon from "sinon"; +import type { DeepgramClientOptions, DefaultNamespaceOptions } from "../src/lib/types"; + +import { DEFAULT_OPTIONS, DEFAULT_URL } from "../src/lib/constants"; + +class TestClient extends AbstractClient { + constructor(options: DeepgramClientOptions) { + super(options); + } +} + +describe("AbstractClient", () => { + let client: AbstractClient; + const options: DeepgramClientOptions = { + key: "test-key", + global: { + fetch: { options: { url: "https://api.mock.deepgram.com" } }, + websocket: { options: { url: "wss://api.mock.deepgram.com" } }, + }, + }; + + beforeEach(() => { + client = new TestClient(options); + }); + + it("should create an instance of AbstractClient", () => { + expect(client).to.be.an.instanceOf(AbstractClient); + }); + + it("should set the key property correctly", () => { + // @ts-expect-error + expect(client.key).to.equal("test-key"); + }); + + it("should set the options property correctly", () => { + const expectedOptions = merge(DEFAULT_OPTIONS, options); + + // @ts-expect-error + expect(expectedOptions).to.deep.equal(client.options); + }); + + it("should set the namespace property correctly", () => { + expect(client.namespace).to.equal("global"); + }); + + it("should set the version property correctly", () => { + expect(client.version).to.equal("v1"); + }); + + it("should set the baseUrl property correctly", () => { + expect(client.baseUrl).to.equal(DEFAULT_URL); + }); + + it("should set the logger property correctly", () => { + expect(client.logger).to.be.a("function"); + }); + + it("should set the namespaceOptions property correctly", () => { + const expectedOptions = merge(DEFAULT_OPTIONS.global, { ...options.global, key: "test-key" }); + + expect(expectedOptions).to.deep.equal(client.namespaceOptions); + }); + + it("should generate a request URL correctly", () => { + const endpoint = "/:version/transcription"; + const transcriptionOptions = { punctuate: true }; + const expectedUrl = new URL("https://api.deepgram.com/v2/transcription?punctuate=true"); + const actualUrl = client.v("v2").getRequestUrl(endpoint, {}, transcriptionOptions); + expect(actualUrl.toString()).to.equal(expectedUrl.toString()); + }); + + it("should log a message correctly", () => { + const loggerSpy = sinon.spy(client, "logger"); + const kind = "info"; + const msg = "Test message"; + const data = { foo: "bar" }; + client.log(kind, msg, data); + expect(loggerSpy.calledWith(kind, msg, data)).to.be.true; + }); +}); diff --git a/test/AbstractLiveClient.test.ts b/test/AbstractLiveClient.test.ts new file mode 100644 index 00000000..ef8b366c --- /dev/null +++ b/test/AbstractLiveClient.test.ts @@ -0,0 +1,101 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import { AbstractLiveClient } from "../src/packages/AbstractLiveClient"; +import { DeepgramClientOptions } from "../src/lib/types"; +import { CONNECTION_STATE, SOCKET_STATES } from "../src/lib/constants"; + +class TestLiveClient extends AbstractLiveClient { + constructor(options: DeepgramClientOptions) { + super(options); + } + + setupConnection(): void { + // Dummy implementation + } +} + +describe("AbstractLiveClient", () => { + let liveClient: TestLiveClient; + const options: DeepgramClientOptions = { + key: "test-key", + global: { + websocket: { + options: { + url: "wss://api.mock.deepgram.com", + }, + }, + }, + }; + + beforeEach(() => { + liveClient = new TestLiveClient(options); + }); + + it("should set the URL property correctly", () => { + expect(liveClient.baseUrl).to.equal("wss://api.mock.deepgram.com"); + }); + + it("should include the Authorization header", () => { + expect(liveClient.headers).to.have.property("Authorization", "Token test-key"); + }); + + it("should connect the socket", (done) => { + // @ts-expect-error + const connectSpy = sinon.spy(liveClient, "connect"); + const transcriptionOptions = { punctuate: true }; + const endpoint = "/v1/listen"; + // @ts-expect-error + liveClient.connect(transcriptionOptions, endpoint); + expect(connectSpy.calledOnce).to.be.true; + done(); + }); + + it("should reconnect the socket", () => { + const reconnectSpy = sinon.spy(liveClient, "reconnect"); + const transcriptionOptions = { numerals: true }; + liveClient.reconnect(transcriptionOptions); + expect(reconnectSpy.calledOnce).to.be.true; + }); + + it("should disconnect the socket", () => { + const disconnectSpy = sinon.spy(liveClient, "disconnect"); + liveClient.disconnect(); + expect(disconnectSpy.calledOnce).to.be.true; + }); + + it("should return the correct connection state", () => { + expect(liveClient.connectionState()).to.equal(CONNECTION_STATE.Closed); + // @ts-expect-error + liveClient.conn = { readyState: SOCKET_STATES.connecting }; + expect(liveClient.connectionState()).to.equal(CONNECTION_STATE.Connecting); + // @ts-expect-error + liveClient.conn = { readyState: SOCKET_STATES.open }; + expect(liveClient.connectionState()).to.equal(CONNECTION_STATE.Open); + // @ts-expect-error + liveClient.conn = { readyState: SOCKET_STATES.closing }; + expect(liveClient.connectionState()).to.equal(CONNECTION_STATE.Closing); + }); + + it("should return the correct ready state", () => { + expect(liveClient.getReadyState()).to.equal(SOCKET_STATES.closed); + // @ts-expect-error + liveClient.conn = { readyState: SOCKET_STATES.connecting }; + expect(liveClient.getReadyState()).to.equal(SOCKET_STATES.connecting); + }); + + it("should check if the connection is open", () => { + expect(liveClient.isConnected()).to.be.false; + // @ts-expect-error + liveClient.conn = { readyState: SOCKET_STATES.open }; + expect(liveClient.isConnected()).to.be.true; + }); + + it("should send data to the Deepgram API", () => { + const sendSpy = sinon.spy(liveClient, "send"); + const data = new Blob(["test data"]); + // @ts-expect-error + liveClient.conn = { send: () => {} }; + liveClient.send(data); + expect(sendSpy.calledOnce).to.be.true; + }); +}); diff --git a/test/AbstractRestClient.test.ts b/test/AbstractRestClient.test.ts new file mode 100644 index 00000000..a9c62879 --- /dev/null +++ b/test/AbstractRestClient.test.ts @@ -0,0 +1,217 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import { AbstractRestClient } from "../src/packages/AbstractRestClient"; +import { DeepgramClientOptions } from "../src/lib/types"; +import { DeepgramError, DeepgramApiError, DeepgramUnknownError } from "../src/lib/errors"; +import * as helpers from "../src/lib/helpers"; + +class TestRestClient extends AbstractRestClient { + constructor(options: DeepgramClientOptions) { + super(options); + } +} + +describe("AbstractRestClient", () => { + let restClient: TestRestClient; + const options: DeepgramClientOptions = { + key: "test-key", + global: { + fetch: { + options: { + url: "https://api.mock.deepgram.com", + }, + }, + }, + }; + + beforeEach(() => { + restClient = new TestRestClient(options); + }); + + it("should throw an error when running in the browser without a proxy", () => { + const isBrowserStub = sinon.stub(helpers, "isBrowser"); + isBrowserStub.returns(true); + expect(() => new TestRestClient(options)).to.throw(DeepgramError); + isBrowserStub.restore(); + }); + + it("should set the baseUrl correctly with a proxy", () => { + const options: DeepgramClientOptions = { + key: "proxy", + global: { + fetch: { + options: { + url: "https://api.mock.deepgram.com", + proxy: { + url: "https://proxy.mock.deepgram.com", + }, + }, + }, + }, + }; + + const restClient = new TestRestClient(options); + expect(restClient.baseUrl).to.equal("https://proxy.mock.deepgram.com"); + }); + + it("should set the baseUrl correctly without a proxy", () => { + expect(restClient.baseUrl).to.equal("https://api.mock.deepgram.com"); + }); + + it("should handle API errors correctly", async () => { + const mockResponse = new Response(JSON.stringify({ error: "Bad Request" }), { + status: 400, + }); + const fetchStub = sinon.stub().rejects(mockResponse); + // @ts-expect-error + const handleErrorSpy = sinon.spy(restClient, "_handleError"); + + try { + // @ts-expect-error + await restClient._handleRequest(fetchStub, "GET", "https://api.mock.deepgram.com"); + } catch (error: any) { + expect(handleErrorSpy.calledOnce).to.be.true; + expect(error).to.be.an.instanceOf(DeepgramApiError); + expect(error.message).to.equal("Bad Request"); + expect(error.status).to.equal(400); + } + }); + + it("should handle unknown errors correctly", async () => { + const mockError = new Error("Unknown error"); + const fetchStub = sinon.stub().rejects(mockError); + // @ts-expect-error + const handleErrorSpy = sinon.spy(restClient, "_handleError"); + + try { + // @ts-expect-error + await restClient._handleRequest(fetchStub, "GET", "https://api.mock.deepgram.com"); + } catch (error: any) { + expect(handleErrorSpy.calledOnce).to.be.true; + expect(error).to.be.an.instanceOf(DeepgramUnknownError); + expect(error.message).to.equal("Unknown error"); + } + }); + + it("should get request parameters correctly", () => { + const headers = { "Content-Type": "application/json", Authorization: "Token test-key" }; + // @ts-expect-error + const parameters: FetchParameters = { cache: "no-cache" }; + const body = JSON.stringify({ data: "test" }); + + // @ts-expect-error + const getParams = restClient._getRequestParams("GET", headers, parameters); + expect(getParams).to.deep.equal({ + method: "GET", + headers: { + Authorization: "Token test-key", + "Content-Type": "application/json", + }, + }); + + // @ts-expect-error + const postParams = restClient._getRequestParams("POST", headers, parameters, body); + expect(postParams).to.deep.equal({ + method: "POST", + headers: { + Authorization: "Token test-key", + "Content-Type": "application/json", + }, + body: body, + duplex: "half", + cache: "no-cache", + }); + }); + + it("should handle successful requests correctly", async () => { + const mockResponse = { + ok: true, + json: () => Promise.resolve({ data: "test" }), + }; + const fetchStub = sinon.stub().resolves(mockResponse); + + // @ts-expect-error + const result = await restClient._handleRequest( + fetchStub, + "GET", + "https://api.mock.deepgram.com" + ); + expect(result).to.deep.equal({ data: "test" }); + }); + + it("should handle raw requests correctly", async () => { + const mockResponse = { ok: true, text: () => Promise.resolve("test data") }; + const fetchStub = sinon.stub().resolves(mockResponse); + + // @ts-expect-error + const result = await restClient._handleRawRequest( + fetchStub, + "GET", + "https://api.mock.deepgram.com" + ); + expect(result).to.deep.equal(mockResponse); + }); + + it("should make GET requests correctly", async () => { + const mockResponse = { + ok: true, + json: () => Promise.resolve({ data: "test" }), + }; + const fetchStub = sinon.stub().resolves(mockResponse); + + // @ts-expect-error + const result = await restClient.get(fetchStub, "https://api.mock.deepgram.com"); + expect(result).to.deep.equal({ data: "test" }); + }); + + it("should make POST requests correctly", async () => { + const mockResponse = { + ok: true, + json: () => Promise.resolve({ data: "test" }), + }; + const fetchStub = sinon.stub().resolves(mockResponse); + const body = JSON.stringify({ data: "test" }); + + // @ts-expect-error + const result = await restClient.post(fetchStub, "https://api.mock.deepgram.com", body); + expect(result).to.deep.equal({ data: "test" }); + }); + + it("should make PUT requests correctly", async () => { + const mockResponse = { + ok: true, + json: () => Promise.resolve({ data: "test" }), + }; + const fetchStub = sinon.stub().resolves(mockResponse); + const body = JSON.stringify({ data: "test" }); + + // @ts-expect-error + const result = await restClient.put(fetchStub, "https://api.mock.deepgram.com", body); + expect(result).to.deep.equal({ data: "test" }); + }); + + it("should make PATCH requests correctly", async () => { + const mockResponse = { + ok: true, + json: () => Promise.resolve({ data: "test" }), + }; + const fetchStub = sinon.stub().resolves(mockResponse); + const body = JSON.stringify({ data: "test" }); + + // @ts-expect-error + const result = await restClient.patch(fetchStub, "https://api.mock.deepgram.com", body); + expect(result).to.deep.equal({ data: "test" }); + }); + + it("should make DELETE requests correctly", async () => { + const mockResponse = { + ok: true, + json: () => Promise.resolve({ data: "test" }), + }; + const fetchStub = sinon.stub().resolves(mockResponse); + + // @ts-expect-error + const result = await restClient.delete(fetchStub, "https://api.mock.deepgram.com"); + expect(result).to.deep.equal({ data: "test" }); + }); +}); diff --git a/test/ListenLiveClient.test.ts b/test/ListenLiveClient.test.ts new file mode 100644 index 00000000..60fd4670 --- /dev/null +++ b/test/ListenLiveClient.test.ts @@ -0,0 +1,75 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import { ListenLiveClient } from "../src/packages/ListenLiveClient"; +import { DeepgramClientOptions, LiveConfigOptions } from "../src/lib/types"; +import { LiveTranscriptionEvents } from "../src/lib/enums"; + +describe("ListenLiveClient", () => { + let liveClient: ListenLiveClient; + const options: DeepgramClientOptions = { + key: "test-key", + global: { + websocket: { + options: { + url: "wss://api.mock.deepgram.com", + }, + }, + }, + }; + + beforeEach(() => { + liveClient = new ListenLiveClient(options); + }); + + it("should set the namespace property correctly", () => { + expect(liveClient.namespace).to.equal("listen"); + }); + + it("should set up the connection event handlers", () => { + const eventSpy = sinon.spy(liveClient, "emit"); + + liveClient.setupConnection(); + + // Simulate connection events + // @ts-expect-error + liveClient.conn.onopen(); + // @ts-expect-error + liveClient.conn.onclose({}); + // @ts-expect-error + liveClient.conn.onmessage({ + data: JSON.stringify({ + type: LiveTranscriptionEvents.Transcript, + data: {}, + }), + }); + + expect(eventSpy.calledWith(LiveTranscriptionEvents.Open, liveClient)).to.be.true; + expect(eventSpy.calledWith(LiveTranscriptionEvents.Close, {})).to.be.true; + expect( + eventSpy.calledWith(LiveTranscriptionEvents.Transcript, { + type: LiveTranscriptionEvents.Transcript, + data: {}, + }) + ).to.be.true; + }); + + it("should configure the live client", () => { + const sendSpy = sinon.spy(liveClient, "send"); + const config: LiveConfigOptions = { numerals: true }; + liveClient.configure(config); + expect(sendSpy.calledWith(JSON.stringify({ type: "Configure", processors: config }))).to.be + .true; + }); + + it("should send a KeepAlive message", () => { + const sendSpy = sinon.spy(liveClient, "send"); + liveClient.keepAlive(); + expect(sendSpy.calledWith(JSON.stringify({ type: "KeepAlive" }))).to.be.true; + }); + + it("should request to close the connection", () => { + const sendSpy = sinon.spy(liveClient, "send"); + liveClient.requestClose(); + expect(sendSpy.calledWith(JSON.stringify({ type: "CloseStream" }))).to.be.true; + }); +}); diff --git a/test/client.test.ts b/test/client.test.ts deleted file mode 100644 index bc2fbd8f..00000000 --- a/test/client.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { createClient } from "../src"; -import { DEFAULT_URL } from "../src/lib/constants"; -import { expect, assert } from "chai"; -import { faker } from "@faker-js/faker"; -import { stripTrailingSlash } from "../src/lib/helpers"; -import DeepgramClient from "../src/DeepgramClient"; -import { ListenClient } from "../src/packages/ListenClient"; - -const deepgram = createClient(faker.string.alphanumeric(40)); - -describe("testing creation of a deepgram client object", () => { - it("it should create the client object", () => { - expect(deepgram).to.not.be.undefined; - expect(deepgram).is.instanceOf(DeepgramClient); - }); - - it("it should provide provide access to a transcription client", () => { - expect(deepgram.listen).to.not.be.undefined; - expect(deepgram.listen).is.instanceOf(ListenClient); - }); - - it("it should have the default URL when no custom URL option is provided", () => { - // @ts-ignore - const url = deepgram.namespaceOptions.fetch.options.url; - - expect(url).to.equal(DEFAULT_URL); - }); - - it("it should throw an error if no valid apiKey is provided", () => { - expect(() => createClient("")).to.throw("A deepgram API key is required"); - }); - - it("it should throw an error if invalid options are provided", () => { - // const client = createClient({ global: { fetch: { options: { url: "" } } } }); - // console.log(client); - // process.exit(1); - expect(() => createClient({ global: { fetch: { options: { url: "" } } } })).to.throw( - `A deepgram API key is required.` - ); - }); - - it("it should create the client object with legacy options", () => { - const mockUrl = faker.internet.url({ appendSlash: false }); - const mockKey = faker.string.alphanumeric(40); - const client = createClient(mockKey, { - global: { url: mockUrl }, - }); - - // @ts-ignore - const url = client.namespaceOptions.fetch.options.url; - - // @ts-ignore - const key = client.options.key; - - expect(client).is.instanceOf(DeepgramClient); - expect(url).to.equal(mockUrl); - expect(key).to.equal(mockKey); - }); - - it("it should create the client object with a custom url", () => { - const mockUrl = faker.internet.url({ appendSlash: false }); - const mockKey = faker.string.alphanumeric(40); - const client = createClient({ - key: mockKey, - global: { fetch: { options: { url: mockUrl } } }, - }); - - // @ts-ignore - const url = client.namespaceOptions.fetch.options.url; - - // @ts-ignore - const key = client.options.key; - - expect(client).is.instanceOf(DeepgramClient); - expect(url).to.equal(mockUrl); - expect(key).to.equal(mockKey); - }); - - it("it should allow for the supply of a custom header", () => { - const client = createClient(faker.string.alphanumeric(40), { - global: { - fetch: { options: { headers: { "X-dg-test": "testing" } } }, - }, - }); - - expect(client).is.instanceOf(DeepgramClient); - }); - - it("should use custom fetch when provided", async () => { - const fetch = async () => { - return new Response(JSON.stringify({ customFetch: true })); - }; - - const client = createClient(faker.string.alphanumeric(40), { - global: { - fetch: { client: fetch }, - }, - }); - - const { result, error } = await client.manage.getProjectBalances(faker.string.uuid()); - - console.log("result", result); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["customFetch"]); - }); -}); diff --git a/test/constants.test.ts b/test/constants.test.ts deleted file mode 100644 index 92c3bf01..00000000 --- a/test/constants.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { applyDefaults } from "../src/lib/helpers"; -import { DeepgramClientOptions } from "../src/lib/types/DeepgramClientOptions"; -import { DEFAULT_OPTIONS } from "../src/lib/constants"; -import { expect } from "chai"; -import { faker } from "@faker-js/faker"; - -describe("testing constants", () => { - it("DEFAULT_OPTIONS are valid options", () => { - const options: DeepgramClientOptions = DEFAULT_OPTIONS; - - expect(options).to.not.be.undefined; - }); - - it("DEFAULT_OPTIONS can be overridden", () => { - const options = { - global: { url: faker.internet.url({ appendSlash: false }) }, - }; - const settings = applyDefaults(options, DEFAULT_OPTIONS); - - expect(settings).is.not.deep.equal(options); - }); -}); diff --git a/test/errors.test.ts b/test/errors.test.ts deleted file mode 100644 index 61d77743..00000000 --- a/test/errors.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { expect } from "chai"; -import { - DeepgramApiError, - DeepgramError, - DeepgramUnknownError, - isDeepgramError, -} from "../src/lib/errors"; - -describe("testing errors", () => { - it("we can create an API error", () => { - const error = new DeepgramError("Testing an error"); - expect(error).to.not.be.undefined; - expect(isDeepgramError(error)).equals(true); - expect(error).is.instanceOf(DeepgramError); - }); - - it("an API error will convert to JSON", () => { - const error = new DeepgramApiError("Testing an error", 400); - expect(JSON.stringify(error)).equals( - '{"name":"DeepgramApiError","message":"Testing an error","status":400}' - ); - expect(error).is.instanceOf(DeepgramApiError); - }); - - it("an unknown error is still an error", () => { - const error = new Error("Testing an error"); - const dgError = new DeepgramUnknownError("Unknown error test", error); - expect(isDeepgramError(dgError)).equals(true); - expect(dgError).is.instanceOf(DeepgramUnknownError); - }); -}); diff --git a/test/helpers.test.ts b/test/helpers.test.ts deleted file mode 100644 index fd6b4deb..00000000 --- a/test/helpers.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect } from "chai"; -import { faker } from "@faker-js/faker"; -import { applyDefaults, stripTrailingSlash } from "../src/lib/helpers"; -import { DEFAULT_OPTIONS } from "../src/lib/constants"; - -describe("testing helpers", () => { - it("it should strip the trailing slash from a URL", () => { - const URL = faker.internet.url({ appendSlash: true }); - const expectedURL = URL.slice(0, -1); - expect(stripTrailingSlash(URL)).to.equal(expectedURL); - }); - - it("it should override defaults with options provided", () => { - const options = JSON.parse(JSON.stringify(DEFAULT_OPTIONS)); // deep copy DEFAULT_OPTIONS - options.global.url = faker.internet.url({ appendSlash: false }); - expect(applyDefaults(options, DEFAULT_OPTIONS)).to.deep.equal(options); - }); -}); diff --git a/test/legacy.test.ts b/test/legacy.test.ts deleted file mode 100644 index ecb1f872..00000000 --- a/test/legacy.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { assert, expect } from "chai"; -import { createClient, Deepgram, DeepgramVersionError } from "../src"; -import { faker } from "@faker-js/faker"; -import DeepgramClient from "../src/DeepgramClient"; - -const errorText = - "You are attempting to use an old format for a newer SDK version. Read more here: https://dpgr.am/js-v3"; - -describe("legacy error handling", () => { - let deepgram: DeepgramClient; - - beforeEach(() => { - deepgram = createClient(faker.string.alphanumeric(40), { - global: { url: "https://api.mock.deepgram.com" }, - }); - }); - - it("should create the correct client object", () => { - expect(deepgram).to.not.be.undefined; - expect(deepgram).is.instanceOf(DeepgramClient); - }); - - it("should error when using a v2 client object", async () => { - assert.throw( - () => { - new Deepgram(faker.string.alphanumeric(40)); - }, - DeepgramVersionError, - errorText - ); - }); - - it("should error when using an old v2 callstack for transcription", async () => { - assert.throw( - () => { - deepgram.transcription.preRecorded( - { - url: "https://dpgr.am/spacewalk.wav", - }, - { - model: "nova", - callback: "http://callback/endpoint", - } - ); - }, - DeepgramVersionError, - errorText - ); - }); - - it("should error when using an old v2 callstack for projects", async () => { - assert.throw( - () => { - deepgram.projects.list(); - }, - DeepgramVersionError, - errorText - ); - }); - - it("should error when using an old v2 callstack for keys", async () => { - assert.throw( - () => { - deepgram.keys.list("projectId"); - }, - DeepgramVersionError, - errorText - ); - }); - - it("should error when using an old v2 callstack for members", async () => { - assert.throw( - () => { - deepgram.members.listMembers("projectId"); - }, - DeepgramVersionError, - errorText - ); - }); - - it("should error when using an old v2 callstack for scopes", async () => { - assert.throw( - () => { - deepgram.scopes.get("projectId", "projectMemberId"); - }, - DeepgramVersionError, - errorText - ); - }); - - it("should error when using an old v2 callstack for invitation", async () => { - assert.throw( - () => { - deepgram.invitation.list("projectId"); - }, - DeepgramVersionError, - errorText - ); - }); - - it("should error when using an old v2 callstack for usage", async () => { - assert.throw( - () => { - deepgram.usage.listRequests("projectId", {}); - }, - DeepgramVersionError, - errorText - ); - }); - - it("should error when using an old v2 callstack for billing", async () => { - assert.throw( - () => { - deepgram.billing.listBalances("projectId"); - }, - DeepgramVersionError, - errorText - ); - }); -}); diff --git a/test/live.test.ts b/test/live.test.ts deleted file mode 100644 index fb52ef90..00000000 --- a/test/live.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -// import { assert, expect } from "chai"; -// import { createClient } from "../src"; -// import { faker } from "@faker-js/faker"; -// import DeepgramClient from "../src/DeepgramClient"; -// import { LiveConnectionState, LiveTranscriptionEvents } from "../src/lib/enums"; - -// describe("connecting to our transcription websocket", () => { -// let deepgram: DeepgramClient; - -// beforeEach(() => { -// deepgram = createClient(faker.string.alphanumeric(40), { -// global: { url: "wss://api.mock.deepgram.com" }, -// }); -// }); - -// it("should create the client object", () => { -// expect(deepgram).to.not.be.undefined; -// expect(deepgram).is.instanceOf(DeepgramClient); -// }); - -// it("should connect to the websocket", function (done) { -// const connection = deepgram.listen.live({ model: "general", tier: "enhanced" }); - -// connection.on(LiveTranscriptionEvents.Open, (event) => { -// expect(connection.getReadyState()).to.eq(LiveConnectionState.OPEN); - -// connection.on(LiveTranscriptionEvents.Metadata, (data) => { -// assert.isNotNull(data); -// assert.containsAllDeepKeys(data, ["request_id"]); - -// connection.finish(); -// done(); -// }); -// }); -// }); - -// it("should send data and recieve a transcription object back", function (done) { -// const connection = deepgram.listen.live({ model: "general", tier: "enhanced" }); - -// connection.on(LiveTranscriptionEvents.Open, () => { -// connection.on(LiveTranscriptionEvents.Metadata, (data) => { -// assert.isNotNull(data); -// assert.containsAllDeepKeys(data, ["request_id"]); -// }); - -// connection.on(LiveTranscriptionEvents.Transcript, (data) => { -// assert.isNotNull(data); -// assert.containsAllDeepKeys(data, ["channel"]); - -// connection.finish(); -// done(); -// }); - -// connection.send(new Uint8Array(100)); // mock ArrayBufferLike audio data -// }); -// }); - -// it("should receive a warning if trying to send zero-byte length data", function (done) { -// const connection = deepgram.listen.live({ model: "general", tier: "enhanced" }); - -// connection.on(LiveTranscriptionEvents.Open, () => { -// connection.on(LiveTranscriptionEvents.Warning, (data) => { -// assert.isNotNull(data); - -// expect(data).to.eq( -// "Zero-byte detected, skipping. Send `CloseStream` if trying to close the connection." -// ); - -// connection.finish(); -// done(); -// }); - -// connection.send(new Uint8Array(0)); -// }); -// }); -// }); diff --git a/test/manage.test.ts b/test/manage.test.ts deleted file mode 100644 index 7918d220..00000000 --- a/test/manage.test.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { assert, expect } from "chai"; -import { createClient } from "../src"; -import { faker } from "@faker-js/faker"; -import DeepgramClient from "../src/DeepgramClient"; - -describe("making manage requests", () => { - let deepgram: DeepgramClient; - - beforeEach(() => { - deepgram = createClient(faker.string.alphanumeric(40), { - global: { url: "https://api.mock.deepgram.com" }, - }); - }); - - it("should create the client object", () => { - expect(deepgram).to.not.be.undefined; - expect(deepgram).is.instanceOf(DeepgramClient); - }); - - it("should get all projects for a user", async () => { - const { result, error } = await deepgram.manage.getProjects(); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["projects"]); - }); - - it("should get a project", async () => { - const { result, error } = await deepgram.manage.getProject(faker.string.uuid()); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["project_id"]); - }); - - it("should update a project", async () => { - const { result, error } = await deepgram.manage.updateProject(faker.string.uuid(), { - name: "test", - }); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["message"]); - }); - - it("should delete a project", async () => { - const { error } = await deepgram.manage.deleteProject(faker.string.uuid()); - - assert.isNull(error); - }); - - it("should get all project key details", async () => { - const { result, error } = await deepgram.manage.getProjectKeys(faker.string.uuid()); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["api_keys"]); - }); - - it("should get a project key", async () => { - const { result, error } = await deepgram.manage.getProjectKey( - faker.string.uuid(), - faker.string.uuid() - ); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result?.api_key, ["api_key_id"]); - }); - - it("should create a project key", async () => { - const { result, error } = await deepgram.manage.createProjectKey(faker.string.uuid(), { - comment: faker.lorem.words(4), - scopes: [faker.lorem.word()], - }); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["key"]); - }); - - it("should delete a project key", async () => { - const { error } = await deepgram.manage.deleteProjectKey( - faker.string.uuid(), - faker.string.uuid() - ); - - assert.isNull(error); - }); - - it("should get all project members", async () => { - const { result, error } = await deepgram.manage.getProjectMembers(faker.string.uuid()); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["members"]); - }); - - it("should remove a project member", async () => { - const { error } = await deepgram.manage.removeProjectMember( - faker.string.uuid(), - faker.string.uuid() - ); - - assert.isNull(error); - }); - - it("should get all scopes for a project member", async () => { - const { result, error } = await deepgram.manage.getProjectMemberScopes( - faker.string.uuid(), - faker.string.uuid() - ); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["scopes"]); - }); - - it("should update a scope for a project member", async () => { - const { result, error } = await deepgram.manage.updateProjectMemberScope( - faker.string.uuid(), - faker.string.uuid(), - { - scope: faker.lorem.word(), - } - ); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["message"]); - }); - - it("should get all project invites", async () => { - const { result, error } = await deepgram.manage.getProjectInvites(faker.string.uuid()); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["invites"]); - }); - - it("should send a project invite", async () => { - const { result, error } = await deepgram.manage.sendProjectInvite(faker.string.uuid(), { - email: faker.internet.email(), - scope: faker.lorem.word(), - }); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["message"]); - }); - - it("should delete a project invite", async () => { - const { error } = await deepgram.manage.deleteProjectInvite( - faker.string.uuid(), - faker.internet.email() - ); - - assert.isNull(error); - }); - - it("should leave a project", async () => { - const { result, error } = await deepgram.manage.leaveProject(faker.string.uuid()); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["message"]); - }); - - it("should get all usage requests for a project", async () => { - const { result, error } = await deepgram.manage.getProjectUsageRequests(faker.string.uuid(), { - start: faker.date.anytime().toISOString(), - end: faker.date.anytime().toISOString(), - }); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["requests"]); - }); - - it("should get a usage request for a project", async () => { - const { result, error } = await deepgram.manage.getProjectUsageRequest( - faker.string.uuid(), - faker.string.uuid() - ); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["response"]); - }); - - it("should get the project usage summary", async () => { - const { result, error } = await deepgram.manage.getProjectUsageSummary(faker.string.uuid(), { - start: faker.date.anytime().toISOString(), - end: faker.date.anytime().toISOString(), - }); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["results"]); - }); - - it("should get project usage fields", async () => { - const { result, error } = await deepgram.manage.getProjectUsageFields(faker.string.uuid(), { - start: faker.date.anytime().toISOString(), - end: faker.date.anytime().toISOString(), - }); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["models"]); - }); - - it("should get all project balances", async () => { - const { result, error } = await deepgram.manage.getProjectBalances(faker.string.uuid()); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["balances"]); - }); - - it("should get a project balance", async () => { - const { result, error } = await deepgram.manage.getProjectBalance( - faker.string.uuid(), - faker.string.uuid() - ); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["balance_id"]); - }); -}); diff --git a/test/onprem.test.ts b/test/onprem.test.ts deleted file mode 100644 index 5a5a4e1c..00000000 --- a/test/onprem.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { assert, expect } from "chai"; -import { createClient } from "../src"; -import { faker } from "@faker-js/faker"; -import DeepgramClient from "../src/DeepgramClient"; - -describe("making onprem requests", () => { - let deepgram: DeepgramClient; - - beforeEach(() => { - deepgram = createClient(faker.string.alphanumeric(40), { - global: { url: "https://api.mock.deepgram.com" }, - }); - }); - - it("should create the client object", () => { - expect(deepgram).to.not.be.undefined; - expect(deepgram).is.instanceOf(DeepgramClient); - }); - - it("should list onprem credentials", async () => { - const { result, error } = await deepgram.onprem.listCredentials(faker.string.uuid()); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["distribution_credentials"]); - }); - - it("should get onprem credentials", async () => { - const { result, error } = await deepgram.onprem.getCredentials( - faker.string.uuid(), - faker.string.uuid() - ); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["member"]); - }); - - it("should create onprem credentials", async () => { - const { result, error } = await deepgram.onprem.createCredentials(faker.string.uuid(), { - comment: faker.lorem.paragraph(), - scopes: [faker.lorem.word()], - provider: faker.lorem.word(), - }); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["member"]); - }); - - it("should delete onprem credentials", async () => { - const { result, error } = await deepgram.onprem.deleteCredentials( - faker.string.uuid(), - faker.string.uuid() - ); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["message"]); - }); -}); diff --git a/test/prerecorded.test.ts b/test/prerecorded.test.ts deleted file mode 100644 index e726bf2a..00000000 --- a/test/prerecorded.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { assert, expect } from "chai"; -import { createClient } from "../src"; -import { faker } from "@faker-js/faker"; -import DeepgramClient from "../src/DeepgramClient"; -import { CallbackUrl } from "../src/lib/helpers"; -import { UrlSource } from "../src/lib/types"; - -const bufferSource: Buffer = Buffer.from("string"); - -const urlSource: UrlSource = { - url: faker.internet.url({ appendSlash: false }) + "/nasa.wav", -}; - -describe("making listen requests", () => { - let deepgram: DeepgramClient; - - beforeEach(() => { - deepgram = createClient(faker.string.alphanumeric(40), { - global: { url: "https://api.mock.deepgram.com" }, - }); - }); - - it("should create the client object", () => { - expect(deepgram).to.not.be.undefined; - expect(deepgram).is.instanceOf(DeepgramClient); - }); - - it("should transcribe a URL source synchronously", async () => { - const { result, error } = await deepgram.listen.prerecorded.transcribeUrl(urlSource); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result?.metadata, ["request_id"]); - }); - - it("should transcribe a file source synchronously", async () => { - const { result, error } = await deepgram.listen.prerecorded.transcribeFile(bufferSource); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result?.metadata, ["request_id"]); - }); - - it("should transcribe a URL source asynchronously", async () => { - const { result, error } = await deepgram.listen.prerecorded.transcribeUrlCallback( - urlSource, - new CallbackUrl("https://example.com/callback") - ); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["request_id"]); - }); - - it("should transcribe a file source asynchronously", async () => { - const { result, error } = await deepgram.listen.prerecorded.transcribeFileCallback( - bufferSource, - new CallbackUrl("https://example.com/callback") - ); - - assert.isNull(error); - assert.isNotNull(result); - assert.containsAllDeepKeys(result, ["request_id"]); - }); -});