assemblyai 3.1.3 → 4.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/compat.md ADDED
@@ -0,0 +1,59 @@
1
+ # SDK Compatibility
2
+
3
+ The JavaScript SDK is developed for Node.js but is also compatible with other runtimes
4
+ such as the browser, Deno, Bun, Cloudflare Workers, etc.
5
+
6
+ ## Browser compatibility
7
+
8
+ To make the SDK compatible with the browser, the SDK aims to use web standards as much as possible.
9
+ However, there are still incompatibilities between Node.js and the browser.
10
+
11
+ - `RealtimeService` doesn't support the AssemblyAI API key in the browser.
12
+ Instead, you have to generate a temporary auth token using `client.realtime.createTemporaryToken`, and pass in the resulting token to the real-time transcriber.
13
+
14
+ Generate a temporary auth token on the server.
15
+
16
+ ```js
17
+ import { AssemblyAI } from "assemblyai"
18
+ // Ideally, to avoid embedding your API key client side,
19
+ // you generate this token on the server, and pass it to the client via an API.
20
+ const client = new AssemblyAI({ apiKey: "YOUR_API_KEY" });
21
+ const token = await client.realtime.createTemporaryToken({ expires_in = 480 });
22
+ ```
23
+
24
+ > [!NOTE]
25
+ > We recommend generating the token on the server, so you don't embed your AssemblyAI API key in your client app.
26
+ > If you embed the API key on the client, everyone can see it and use it for themselves.
27
+
28
+ Then pass the token via an API to the client.
29
+ On the client, create an instance of `RealtimeService` using the token.
30
+
31
+ ```js
32
+ import { RealtimeService } from "assemblyai";
33
+ // or the following if you're using UMD
34
+ // const { RealtimeService } = assemblyai;
35
+
36
+ const token = getToken(); // getToken is a function for you to implement
37
+
38
+ const rt = new RealtimeService({
39
+ token: token,
40
+ });
41
+ ```
42
+
43
+ - You can't pass local audio file paths to `client.files.upload`, `client.transcripts.transcribe`, and `client.transcripts.submit`. If you do, you'll get the following error: "'fs' is not supported in this environment.".
44
+ If you want to transcribe audio files, you must use a public URL, a stream, or a buffer.
45
+
46
+ > [!WARNING]
47
+ > The SDK is usable from the browser, but we strongly recommend you don't embed the AssemblyAI API key into your client apps.
48
+ > If you embed the API key on the client, everyone can see it and use it for themselves.
49
+ > Instead, create use the SDK on the server and provide APIs for your client to call.
50
+
51
+ ## Deno, Bun, Cloudflare Workers, etc.
52
+
53
+ Most server-side JavaScript runtimes include a compatibility layer with Node.js.
54
+ Our SDK is developed for Node.js, which makes it compatible with other runtimes through their compatibility layer.
55
+ The bugs in these compatibility layers may introduce issues in our SDK.
56
+
57
+ ## Report issues
58
+
59
+ If you find any (undocumented) bugs when using the SDK, [submit a GitHub issue](https://github.com/AssemblyAI/assemblyai-node-sdk). We'll try to fix it or at least document the compatibility issue.
package/package.json CHANGED
@@ -1,10 +1,27 @@
1
1
  {
2
2
  "name": "assemblyai",
3
- "version": "3.1.3",
4
- "description": "The AssemblyAI Node.js 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.",
3
+ "version": "4.0.0-beta.1",
4
+ "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.",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
8
+ "node": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.node.mjs",
11
+ "require": "./dist/index.node.cjs"
12
+ },
13
+ "bun": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.node.mjs",
16
+ "require": "./dist/index.node.cjs"
17
+ },
18
+ "deno": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.node.mjs",
21
+ "require": "./dist/index.node.cjs"
22
+ },
23
+ "workerd": "./dist/index.mjs",
24
+ "browser": "./dist/index.mjs",
8
25
  "import": "./dist/index.mjs",
9
26
  "require": "./dist/index.cjs",
10
27
  "default": "./dist/index.cjs"
@@ -12,10 +29,11 @@
12
29
  "./package.json": "./package.json"
13
30
  },
14
31
  "type": "commonjs",
15
- "main": "dist/index.cjs",
16
- "module": "dist/index.mjs",
17
- "types": "dist/index.d.ts",
18
- "typings": "dist/index.d.ts",
32
+ "main": "./dist/index.cjs",
33
+ "require": "./dist/index.cjs",
34
+ "module": "./dist/index.mjs",
35
+ "types": "./dist/index.d.ts",
36
+ "typings": "./dist/index.d.ts",
19
37
  "repository": {
20
38
  "type": "git",
21
39
  "url": "git+https://github.com/AssemblyAI/assemblyai-node-sdk.git"
@@ -29,8 +47,7 @@
29
47
  "build": "pnpm clean && pnpm rollup -c",
30
48
  "clean": "rimraf dist",
31
49
  "lint": "eslint -c .eslintrc.json '{src,tests}/**/*.{js,ts}' && publint",
32
- "test": "pnpm lint && pnpm test:unit",
33
- "test:unit": "jest --config jest.config.rollup.ts",
50
+ "test": "jest --config jest.config.rollup.ts",
34
51
  "format": "prettier '**/*' --write",
35
52
  "generate-types": "tsx ./scripts/generate-types.ts && pnpm format",
36
53
  "copybara:dry-run": "./copybara.sh dry_run --init-history",
@@ -48,11 +65,19 @@
48
65
  "homepage": "https://www.assemblyai.com/docs",
49
66
  "files": [
50
67
  "dist",
51
- "src"
68
+ "src",
69
+ "package.json",
70
+ "README.md",
71
+ "CHANGELOG.md",
72
+ "docs"
52
73
  ],
53
74
  "devDependencies": {
75
+ "@rollup/plugin-alias": "^5.0.1",
76
+ "@rollup/plugin-node-resolve": "^15.2.3",
77
+ "@rollup/plugin-terser": "^0.4.4",
54
78
  "@types/jest": "^29.5.5",
55
79
  "@types/node": "^20.5.7",
80
+ "@types/websocket": "^1.0.8",
56
81
  "@types/ws": "^8.5.5",
57
82
  "@typescript-eslint/eslint-plugin": "^6.7.5",
58
83
  "dotenv": "^16.3.1",
@@ -78,6 +103,8 @@
78
103
  "typescript": "^5.2.2"
79
104
  },
80
105
  "dependencies": {
106
+ "@swimburger/isomorphic-streams": "^1.1.1",
107
+ "isomorphic-ws": "^5.0.0",
81
108
  "ws": "^8.13.0"
82
109
  }
83
- }
110
+ }
@@ -0,0 +1,8 @@
1
+ function throwError() {
2
+ throw new Error("'fs' is not supported in this environment.");
3
+ }
4
+
5
+ export const createReadStream = throwError;
6
+ export default {
7
+ createReadStream,
8
+ };
@@ -1,5 +1,3 @@
1
- // import the fs module instead if specific named exports
2
- // to keep the assemblyai module more compatible. Some fs polyfills don't include `createReadStream`.
3
1
  import fs from "fs";
4
2
  import { BaseService } from "../base";
5
3
  import { UploadedFile, FileUploadParams, FileUploadData } from "../..";
@@ -1,4 +1,6 @@
1
- import WebSocket from "ws";
1
+ import { WritableStream } from "@swimburger/isomorphic-streams";
2
+ import WebSocket from "isomorphic-ws";
3
+ import { ErrorEvent, MessageEvent, CloseEvent } from "ws";
2
4
  import {
3
5
  RealtimeEvents,
4
6
  RealtimeListeners,
@@ -14,7 +16,6 @@ import {
14
16
  RealtimeErrorMessages,
15
17
  RealtimeErrorType,
16
18
  } from "../../utils/errors";
17
- import Stream from "stream";
18
19
 
19
20
  const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";
20
21
 
@@ -32,8 +33,8 @@ export class RealtimeService {
32
33
  this.realtimeUrl = params.realtimeUrl ?? defaultRealtimeUrl;
33
34
  this.sampleRate = params.sampleRate ?? 16_000;
34
35
  this.wordBoost = params.wordBoost;
35
- if ("apiKey" in params) this.apiKey = params.apiKey;
36
- if ("token" in params) this.token = params.token;
36
+ if ("apiKey" in params && params.apiKey) this.apiKey = params.apiKey;
37
+ if ("token" in params && params.token) this.token = params.token;
37
38
 
38
39
  if (!(this.apiKey || this.token)) {
39
40
  throw new Error("API key or temporary token is required.");
@@ -88,16 +89,15 @@ export class RealtimeService {
88
89
 
89
90
  const url = this.connectionUrl();
90
91
 
91
- let headers;
92
92
  if (this.token) {
93
- headers = undefined;
94
- } else if (this.apiKey) {
95
- headers = { Authorization: this.apiKey };
93
+ this.socket = new WebSocket(url.toString());
94
+ } else {
95
+ this.socket = new WebSocket(url.toString(), {
96
+ headers: { Authorization: this.apiKey },
97
+ });
96
98
  }
97
99
 
98
- this.socket = new WebSocket(url.toString(), { headers });
99
-
100
- this.socket.onclose = ({ code, reason }: WebSocket.CloseEvent) => {
100
+ this.socket.onclose = ({ code, reason }: CloseEvent) => {
101
101
  if (!reason) {
102
102
  if (code in RealtimeErrorType) {
103
103
  reason = RealtimeErrorMessages[code as RealtimeErrorType];
@@ -106,12 +106,12 @@ export class RealtimeService {
106
106
  this.listeners.close?.(code, reason);
107
107
  };
108
108
 
109
- this.socket.onerror = (errorEvent: WebSocket.ErrorEvent) => {
110
- if (errorEvent.error) this.listeners.error?.(errorEvent.error as Error);
111
- else this.listeners.error?.(new Error(errorEvent.message));
109
+ this.socket.onerror = (event: ErrorEvent) => {
110
+ if (event.error) this.listeners.error?.(event.error as Error);
111
+ else this.listeners.error?.(new Error(event.message));
112
112
  };
113
113
 
114
- this.socket.onmessage = ({ data }: WebSocket.MessageEvent) => {
114
+ this.socket.onmessage = ({ data }: MessageEvent) => {
115
115
  const message = JSON.parse(data.toString()) as RealtimeMessage;
116
116
  if ("error" in message) {
117
117
  this.listeners.error?.(new RealtimeError(message.error));
@@ -150,25 +150,35 @@ export class RealtimeService {
150
150
  });
151
151
  }
152
152
 
153
- sendAudio(audio: ArrayBuffer) {
153
+ sendAudio(audio: ArrayBufferLike) {
154
154
  if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
155
155
  throw new Error("Socket is not open for communication");
156
156
  }
157
-
157
+ let audioData;
158
+ if (typeof Buffer !== "undefined") {
159
+ audioData = Buffer.from(audio).toString("base64");
160
+ } else {
161
+ // Buffer is not available in the browser by default
162
+ // https://stackoverflow.com/a/42334410/2919731
163
+ audioData = btoa(
164
+ new Uint8Array(audio).reduce(
165
+ (data, byte) => data + String.fromCharCode(byte),
166
+ ""
167
+ )
168
+ );
169
+ }
158
170
  const payload = {
159
- audio_data: Buffer.from(audio).toString("base64"),
171
+ audio_data: audioData,
160
172
  };
161
173
  this.socket.send(JSON.stringify(payload));
162
174
  }
163
175
 
164
- stream(): NodeJS.WritableStream {
165
- const stream = new Stream.Writable({
166
- write: (chunk: Buffer, encoding, next) => {
176
+ stream(): WritableStream<ArrayBufferLike> {
177
+ return new WritableStream<ArrayBufferLike>({
178
+ write: (chunk: ArrayBufferLike) => {
167
179
  this.sendAudio(chunk);
168
- next();
169
180
  },
170
181
  });
171
- return stream;
172
182
  }
173
183
 
174
184
  async close(waitForSessionTermination = true) {
@@ -185,7 +195,7 @@ export class RealtimeService {
185
195
  this.socket.send(terminateSessionMessage);
186
196
  }
187
197
  }
188
- this.socket.removeAllListeners();
198
+ if ("removeAllListeners" in this.socket) this.socket.removeAllListeners();
189
199
  this.socket.close();
190
200
  }
191
201