diff --git a/CHANGELOG.md b/CHANGELOG.md index 02055dc..0b08791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [4.4.7] + +- Rename `TranscriptService.redactions` function to `TranscriptService.redactedAudio`. +- Add `TranscriptService.redactedAudioFile` function. +- Add `workerd` export to fix `cache` issue with `fetch` on Cloudflare Workers. + ## [4.4.6] - Fix Rollup exports so \_\_SDK_VERSION\_\_ is properly replaced with the version of the SDK. diff --git a/package.json b/package.json index 5494e40..51c03c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "assemblyai", - "version": "4.4.6", + "version": "4.4.7", "description": "The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, as well as the latest LeMUR models.", "engines": { "node": ">=18" @@ -16,7 +16,7 @@ "types": "./dist/exports/index.d.ts", "default": "./dist/deno.mjs" }, - "workerd": "./dist/index.mjs", + "workerd": "./dist/workerd.mjs", "browser": "./dist/browser.mjs", "react-native": "./dist/browser.mjs", "node": { @@ -38,6 +38,10 @@ "./package.json": "./package.json" }, "imports": { + "#fetch": { + "workerd": "./src/polyfills/fetch/workerd.ts", + "default": "./src/polyfills/fetch/default.ts" + }, "#fs": { "node": "./src/polyfills/fs/node.ts", "bun": "./src/polyfills/fs/bun.ts", diff --git a/rollup.config.js b/rollup.config.js index 7346530..b5a3bb5 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -117,6 +117,21 @@ module.exports = [ }, ], }, + { + input: "src/exports/index.ts", + plugins: [ + replacePlugin, + ts({ compilerOptions: { customConditions: ["workerd"] } }), + ], + external: ["ws"], + output: [ + { + file: `./dist/workerd.mjs`, + format: "es", + exports: "named", + }, + ], + }, // Browser ESM build { input: "src/exports/index.ts", diff --git a/src/polyfills/fetch/default.ts b/src/polyfills/fetch/default.ts new file mode 100644 index 0000000..cdbd5c0 --- /dev/null +++ b/src/polyfills/fetch/default.ts @@ -0,0 +1,3 @@ +export const DEFAULT_FETCH_INIT: Record = { + cache: "no-store", +}; diff --git a/src/polyfills/fetch/workerd.ts b/src/polyfills/fetch/workerd.ts new file mode 100644 index 0000000..3e05f02 --- /dev/null +++ b/src/polyfills/fetch/workerd.ts @@ -0,0 +1 @@ +export const DEFAULT_FETCH_INIT: Record = {}; diff --git a/src/services/base.ts b/src/services/base.ts index 2356417..3b6684c 100644 --- a/src/services/base.ts +++ b/src/services/base.ts @@ -1,5 +1,5 @@ -import { BaseServiceParams } from ".."; -import { Error as JsonError } from ".."; +import { DEFAULT_FETCH_INIT } from "#fetch"; +import { BaseServiceParams, Error as JsonError } from ".."; import { buildUserAgent } from "../utils/userAgent"; /** @@ -22,13 +22,14 @@ export abstract class BaseService { input: string, init?: RequestInit | undefined, ): Promise { - init = init ?? {}; - let headers = init.headers ?? {}; - headers = { + init = { ...DEFAULT_FETCH_INIT, ...init }; + let headers = { Authorization: this.params.apiKey, "Content-Type": "application/json", - ...init.headers, }; + if (DEFAULT_FETCH_INIT?.headers) + headers = { ...headers, ...DEFAULT_FETCH_INIT.headers }; + if (init?.headers) headers = { ...headers, ...init.headers }; if (this.userAgent) { (headers as Record)["User-Agent"] = this.userAgent; @@ -40,7 +41,6 @@ export abstract class BaseService { } init.headers = headers; - init.cache = "no-store"; if (!input.startsWith("http")) input = this.params.baseUrl + input; const response = await fetch(input, init); diff --git a/src/services/transcripts/index.ts b/src/services/transcripts/index.ts index e986b73..8e717d0 100644 --- a/src/services/transcripts/index.ts +++ b/src/services/transcripts/index.ts @@ -16,6 +16,7 @@ import { TranscribeOptions, SubmitParams, SpeechModel, + RedactedAudioFile, } from "../.."; import { FileService } from "../files"; import { getPath } from "../../utils/path"; @@ -244,15 +245,47 @@ export class TranscriptService extends BaseService { } /** - * Retrieve redactions of a transcript. + * Retrieve the redacted audio URL of a transcript. * @param id - The identifier of the transcript. - * @returns A promise that resolves to the subtitles text. + * @returns A promise that resolves to the details of the redacted audio. + * @deprecated Use `redactedAudio` instead. */ redactions(id: string): Promise { + return this.redactedAudio(id); + } + + /** + * Retrieve the redacted audio URL of a transcript. + * @param id - The identifier of the transcript. + * @returns A promise that resolves to the details of the redacted audio. + */ + redactedAudio(id: string): Promise { return this.fetchJson( `/v2/transcript/${id}/redacted-audio`, ); } + + /** + * Retrieve the redacted audio file of a transcript. + * @param id - The identifier of the transcript. + * @returns A promise that resolves to the fetch HTTP response of the redacted audio file. + */ + async redactedAudioFile(id: string): Promise { + const { redacted_audio_url, status } = await this.redactedAudio(id); + if (status !== "redacted_audio_ready") { + throw new Error(`Redacted audio status is ${status}`); + } + const response = await fetch(redacted_audio_url); + if (!response.ok) { + throw new Error(`Failed to fetch redacted audio: ${response.statusText}`); + } + return { + arrayBuffer: response.arrayBuffer.bind(response), + blob: response.blob.bind(response), + body: response.body, + bodyUsed: response.bodyUsed, + }; + } } function deprecateConformer2(params: { speech_model?: SpeechModel | null }) { diff --git a/src/types/transcripts/index.ts b/src/types/transcripts/index.ts index af06e03..e6c37d0 100644 --- a/src/types/transcripts/index.ts +++ b/src/types/transcripts/index.ts @@ -54,3 +54,13 @@ export type SubmitParams = TranscribeParams; * The options to transcribe an audio file, including polling options. */ export type TranscribeOptions = PollingOptions; + +/** + * The PII redacted audio file, transmitted over the network. + */ +export type RedactedAudioFile = { + arrayBuffer: () => Promise; + blob: () => Promise; + body: ReadableStream | null; + bodyUsed: boolean; +}; diff --git a/tests/integration/transcript.test.ts b/tests/integration/transcript.test.ts index a85f557..efb544e 100644 --- a/tests/integration/transcript.test.ts +++ b/tests/integration/transcript.test.ts @@ -123,7 +123,7 @@ describe("transcript", () => { expect(subtitle).toBeTruthy(); }); - it("should get redactions", async () => { + it("should get redacted audio", async () => { const transcript = await client.transcripts.transcribe({ audio: remoteAudioUrl, redact_pii: true, @@ -132,19 +132,33 @@ describe("transcript", () => { redact_pii_policies: ["medical_condition"], redact_pii_sub: "hash", }); - const redactedAudioResponse = await client.transcripts.redactions( + const redactedAudioResponse = await client.transcripts.redactedAudio( transcript.id, ); expect(redactedAudioResponse.status).toBe("redacted_audio_ready"); expect(redactedAudioResponse.redacted_audio_url).toBeTruthy(); - }); + }, 30_000); + + it("should get redacted audio file", async () => { + const transcript = await client.transcripts.transcribe({ + audio: remoteAudioUrl, + redact_pii: true, + redact_pii_audio: true, + redact_pii_audio_quality: "wav", + redact_pii_policies: ["medical_condition"], + redact_pii_sub: "hash", + }); + const redactedAudioResponse = await client.transcripts.redactedAudioFile( + transcript.id, + ); + expect((await redactedAudioResponse.blob()).size).toBeGreaterThan(0); + }, 60_000); it("should word search", async () => { const searchResponse = await client.transcripts.wordSearch( knownTranscriptId, ["Giants"], ); - console.log(searchResponse); expect(searchResponse.id).toBe(knownTranscriptId); expect(searchResponse.total_count).toBeGreaterThan(0); expect(Array.isArray(searchResponse.matches)).toBeTruthy(); diff --git a/tests/unit/transcript.test.ts b/tests/unit/transcript.test.ts index 229792d..1346cfc 100644 --- a/tests/unit/transcript.test.ts +++ b/tests/unit/transcript.test.ts @@ -419,7 +419,7 @@ describe("transcript", () => { expect(subtitle).toBeTruthy(); }); - it("should get redactions", async () => { + it("should get redacted audio", async () => { fetchMock.doMockOnceIf( requestMatches({ url: `/v2/transcript/${transcriptId}/redacted-audio`, @@ -431,11 +431,28 @@ describe("transcript", () => { }), ); const redactedAudioResponse = - await assembly.transcripts.redactions(transcriptId); + await assembly.transcripts.redactedAudio(transcriptId); expect(redactedAudioResponse.status).toBe("redacted_audio_ready"); expect(redactedAudioResponse.redacted_audio_url).toBeTruthy(); }); + it("should get redacted audio file", async () => { + fetchMock.doMockOnceIf( + requestMatches({ + url: `/v2/transcript/${transcriptId}/redacted-audio`, + method: "GET", + }), + JSON.stringify({ + status: "redacted_audio_ready", + redacted_audio_url: "https://some-url.com", + }), + ); + fetchMock.doMockOnceIf("https://some-url.com/", "audio data"); + const redactedAudioResponse = + await assembly.transcripts.redactedAudioFile(transcriptId); + expect(redactedAudioResponse).toBeTruthy(); + }); + it("should word search", async () => { fetchMock.mockResponseOnce( JSON.stringify({