diff --git a/examples/node-live/index.js b/examples/node-live/index.js index bcbefe83..bd84405c 100644 --- a/examples/node-live/index.js +++ b/examples/node-live/index.js @@ -1,8 +1,4 @@ -const { - createClient, - LiveTranscriptionEvents, - LiveTranscriptionEvent, -} = require("../../dist/main/index"); +const { createClient, LiveTranscriptionEvents } = require("../../dist/main/index"); const fetch = require("cross-fetch"); const live = async () => { @@ -10,10 +6,20 @@ const live = async () => { const deepgram = createClient(process.env.DEEPGRAM_API_KEY); + // We will collect the is_final=true messages here so we can use them when the person finishes speaking + let is_finals = []; + const connection = deepgram.listen.live({ model: "nova-2", - utterance_end_ms: 1500, + language: "en-US", + // Apply smart formatting to the output + smart_format: true, + // To get UtteranceEnd, the following must be set: interim_results: true, + utterance_end_ms: 1000, + vad_events: true, + // Time in milliseconds of silence to wait for before finalizing speech + endpointing: 300, }); connection.on(LiveTranscriptionEvents.Open, () => { @@ -22,19 +28,45 @@ const live = async () => { }); connection.on(LiveTranscriptionEvents.Metadata, (data) => { - console.log(data); + console.log(`Deepgram Metadata: ${data}`); }); connection.on(LiveTranscriptionEvents.Transcript, (data) => { - console.log(data.channel); + const sentence = data.channel.alternatives[0].transcript; + + // Ignore empty transcripts + if (sentence.length == 0) { + return; + } + if (data.is_final) { + // We need to collect these and concatenate them together when we get a speech_final=true + // See docs: https://developers.deepgram.com/docs/understand-endpointing-interim-results + is_finals.push(sentence); + + // Speech final means we have detected sufficent silence to consider this end of speech + // Speech final is the lowest latency result as it triggers as soon an the endpointing value has triggered + if (data.speech_final) { + const utterance = is_finals.join(" "); + console.log(`Speech Final: ${utterance}`); + is_finals = []; + } else { + // These are useful if you need real time captioning and update what the Interim Results produced + console.log(`Is Final: ${sentence}`); + } + } else { + // These are useful if you need real time captioning of what is being spoken + console.log(`Interim Results: ${sentence}`); + } }); connection.on(LiveTranscriptionEvents.UtteranceEnd, (data) => { - console.log(data); + const utterance = is_finals.join(" "); + console.log(`Deepgram UtteranceEnd: ${utterance}`); + is_finals = []; }); connection.on(LiveTranscriptionEvents.SpeechStarted, (data) => { - console.log(data); + // console.log("Deepgram SpeechStarted"); }); connection.on(LiveTranscriptionEvents.Error, (err) => { diff --git a/package-lock.json b/package-lock.json index dd5f11f9..f71e9bf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2540,13 +2540,14 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "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": { @@ -2609,6 +2610,25 @@ "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", @@ -2652,6 +2672,15 @@ "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", diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 008bbcce..f19cb922 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,11 +1,14 @@ import { isBrowser } from "./helpers"; +import { DeepgramClientOptions } from "./types/DeepgramClientOptions"; import { FetchOptions } from "./types/Fetch"; import { version } from "./version"; +export const NODE_VERSION = process.versions.node; + export const DEFAULT_HEADERS = { "Content-Type": `application/json`, "X-Client-Info": `@deepgram/sdk; ${isBrowser() ? "browser" : "server"}; v${version}`, - "User-Agent": `@deepgram/sdk/${version}`, + "User-Agent": `@deepgram/sdk/${version} ${isBrowser() ? "javascript" : `node/${NODE_VERSION}`}`, }; export const DEFAULT_URL = "https://api.deepgram.com"; @@ -18,7 +21,7 @@ export const DEFAULT_FETCH_OPTIONS: FetchOptions = { headers: DEFAULT_HEADERS, }; -export const DEFAULT_OPTIONS = { +export const DEFAULT_OPTIONS: DeepgramClientOptions = { global: DEFAULT_GLOBAL_OPTIONS, fetch: DEFAULT_FETCH_OPTIONS, }; diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 0d65b632..37f7e594 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -2,9 +2,11 @@ import crossFetch from "cross-fetch"; import { resolveHeadersConstructor } from "./helpers"; import type { Fetch } from "./types/Fetch"; -export const resolveFetch = (): Fetch => { +export const resolveFetch = (customFetch?: Fetch): Fetch => { let _fetch: Fetch; - if (typeof fetch === "undefined") { + if (customFetch) { + _fetch = customFetch; + } else if (typeof fetch === "undefined") { _fetch = crossFetch as unknown as Fetch; } else { _fetch = fetch; @@ -12,8 +14,8 @@ export const resolveFetch = (): Fetch => { return (...args) => _fetch(...args); }; -export const fetchWithAuth = (apiKey: string): Fetch => { - const fetch = resolveFetch(); +export const fetchWithAuth = (apiKey: string, customFetch?: Fetch): Fetch => { + const fetch = resolveFetch(customFetch); const HeadersConstructor = resolveHeadersConstructor(); return async (input, init) => { diff --git a/src/lib/types/CreateProjectKeyResponse.ts b/src/lib/types/CreateProjectKeyResponse.ts index 4a6bf0b4..73b83814 100644 --- a/src/lib/types/CreateProjectKeyResponse.ts +++ b/src/lib/types/CreateProjectKeyResponse.ts @@ -5,4 +5,5 @@ export interface CreateProjectKeyResponse { scopes: string[]; tags?: string[]; created: string; + expiration_date?: string; } diff --git a/src/lib/types/DeepgramClientOptions.ts b/src/lib/types/DeepgramClientOptions.ts index 8ba12c5a..4b4d40ce 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 interface DeepgramClientOptions { global?: { @@ -13,6 +13,7 @@ export interface DeepgramClientOptions { url?: string; }; fetch?: FetchOptions; + _experimentalCustomFetch?: Fetch; restProxy?: { url: null | string; }; diff --git a/src/lib/types/GetTokenDetailsResponse.ts b/src/lib/types/GetTokenDetailsResponse.ts new file mode 100644 index 00000000..23367434 --- /dev/null +++ b/src/lib/types/GetTokenDetailsResponse.ts @@ -0,0 +1,3 @@ +export interface GetTokenDetailsResponse { + [key: string]: unknown; +} diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 305e4071..95ceb017 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -26,6 +26,7 @@ export type { export type { GetProjectUsageRequestsSchema } from "./GetProjectUsageRequestsSchema"; export type { GetProjectUsageSummarySchema } from "./GetProjectUsageSummarySchema"; export type { GetProjectUsageSummaryResponse } from "./GetProjectUsageSummaryResponse"; +export type { GetTokenDetailsResponse } from "./GetTokenDetailsResponse"; export type { ListOnPremCredentialsResponse, OnPremCredentialResponse, diff --git a/src/packages/AbstractRestfulClient.ts b/src/packages/AbstractRestfulClient.ts index 61319426..1dd36cb7 100644 --- a/src/packages/AbstractRestfulClient.ts +++ b/src/packages/AbstractRestfulClient.ts @@ -18,7 +18,7 @@ export abstract class AbstractRestfulClient extends AbstractClient { ); } - this.fetch = fetchWithAuth(this.key); + this.fetch = fetchWithAuth(this.key, options._experimentalCustomFetch); } protected _getErrorMessage(err: any): string { diff --git a/src/packages/ManageClient.ts b/src/packages/ManageClient.ts index f0ab141f..5278a416 100644 --- a/src/packages/ManageClient.ts +++ b/src/packages/ManageClient.ts @@ -27,9 +27,32 @@ import type { UpdateProjectMemberScopeSchema, UpdateProjectSchema, VoidResponse, + GetTokenDetailsResponse, } from "../lib/types"; export class ManageClient extends AbstractRestfulClient { + /** + * @see https://developers.deepgram.com/docs/authenticating#test-request + */ + async getTokenDetails( + endpoint = "v1/auth/token" + ): Promise> { + try { + const url = new URL(this.baseUrl); + url.pathname = endpoint; + + const result: GetTokenDetailsResponse = await this.get(this.fetch as Fetch, url); + + return { result, error: null }; + } catch (error) { + if (isDeepgramError(error)) { + return { result: null, error }; + } + + throw error; + } + } + /** * @see https://developers.deepgram.com/reference/get-projects */ diff --git a/test/client.test.ts b/test/client.test.ts index 04c9b300..426edf8c 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -1,6 +1,6 @@ import { createClient } from "../src"; import { DEFAULT_URL } from "../src/lib/constants"; -import { expect } from "chai"; +import { expect, assert } from "chai"; import { faker } from "@faker-js/faker"; import { stripTrailingSlash } from "../src/lib/helpers"; import DeepgramClient from "../src/DeepgramClient"; @@ -82,4 +82,21 @@ describe("testing creation of a deepgram client object", () => { 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: { url: "https://api.mock.deepgram.com" }, + _experimentalCustomFetch: fetch, + }); + + const { result, error } = await client.manage.getProjectBalances(faker.string.uuid()); + + assert.isNull(error); + assert.isNotNull(result); + assert.containsAllDeepKeys(result, ["customFetch"]); + }); });