whisperai-sdk 1.0.0 → 1.0.2

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/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ClientOptions, UserInfo, UsageInfo, InitMetaFile, InitChunkResponse, UploadChunkResponse, FinalizeChunkResponse, TranscriptionResponse, TranslateResponse, SummaryResponse, RecordingResponse, RecordingsQuery, RecordingsResponse, SubscriptionDetailsResponse } from "./types.js";
1
+ import type { ClientOptions, FinalizeChunkResponse, InitChunkResponse, InitMetaFile, RecordingResponse, RecordingsQuery, RecordingsResponse, SummaryResponse, SubscriptionDetailsResponse, TranscriptionResponse, TranslateResponse, UploadChunkResponse, UsageInfo, UserInfo } from "./types.js";
2
2
  export declare class WhisperClient {
3
3
  private cookies?;
4
4
  private readonly clientOptions;
@@ -7,15 +7,24 @@ export declare class WhisperClient {
7
7
  user(): Promise<UserInfo>;
8
8
  usage(): Promise<UsageInfo>;
9
9
  subscriptionDetails(): Promise<SubscriptionDetailsResponse>;
10
- upload(fileBuffer: Uint8Array, meta: InitMetaFile): Promise<FinalizeChunkResponse>;
10
+ upload(file: Uint8Array | ReadableStream<Uint8Array>, meta: InitMetaFile): Promise<FinalizeChunkResponse>;
11
11
  initChunk(fileBuffer: Uint8Array, meta: InitMetaFile): Promise<InitChunkResponse>;
12
- uploadChunk(fileBuffer: Uint8Array, recordingId: number, chunkIndex: number): Promise<UploadChunkResponse>;
12
+ uploadChunk(chunk: Uint8Array, recordingId: number, chunkIndex: number): Promise<UploadChunkResponse>;
13
13
  finalizeChunk(recordingId: number): Promise<FinalizeChunkResponse>;
14
14
  transcription(recordingId: number): Promise<TranscriptionResponse>;
15
15
  translate(recordingId: number, language: string): Promise<TranslateResponse>;
16
16
  recording(recordingId: number): Promise<RecordingResponse>;
17
17
  recordings(query?: RecordingsQuery): Promise<RecordingsResponse>;
18
18
  summary(): Promise<SummaryResponse>;
19
+ private uploadFromStream;
20
+ private uploadChunkSlice;
21
+ private initChunkRequest;
22
+ private uploadChunkRequest;
23
+ private recall;
24
+ private get;
25
+ private post;
26
+ private postForm;
27
+ private request;
19
28
  private get loginLink();
20
29
  private get userLink();
21
30
  private get usageLink();
@@ -25,11 +34,9 @@ export declare class WhisperClient {
25
34
  private get finalizeChunkLink();
26
35
  private get transcriptionLink();
27
36
  private get summaryLink();
37
+ private get recordingsLink();
28
38
  private translateLink;
29
39
  private recordingLink;
30
- private get recordingsLink();
40
+ private mergeCookies;
31
41
  private getCountChunks;
32
- private get requestOptions();
33
- private recall;
34
- private handleRequest;
35
42
  }
package/dist/index.js CHANGED
@@ -1,6 +1,3 @@
1
- // src/client.ts
2
- import axios from "axios";
3
-
4
1
  // src/errors.ts
5
2
  class WhisperError extends Error {
6
3
  constructor(message) {
@@ -47,65 +44,167 @@ class WhisperClient {
47
44
  };
48
45
  }
49
46
  login() {
50
- return this.handleRequest(axios.post(this.loginLink, this.clientOptions.login));
47
+ return this.post(this.loginLink, this.clientOptions.login);
51
48
  }
52
49
  user() {
53
- return this.recall(() => axios.get(this.userLink, this.requestOptions));
50
+ return this.recall(() => this.get(this.userLink));
54
51
  }
55
52
  usage() {
56
- return this.recall(() => axios.get(this.usageLink, this.requestOptions));
53
+ return this.recall(() => this.get(this.usageLink));
57
54
  }
58
55
  subscriptionDetails() {
59
- return this.recall(() => axios.get(this.subscriptionDetailsLink, this.requestOptions));
56
+ return this.recall(() => this.get(this.subscriptionDetailsLink));
57
+ }
58
+ async upload(file, meta) {
59
+ if (file instanceof Uint8Array) {
60
+ const totalChunks2 = this.getCountChunks(file.length);
61
+ const { recordingId: recordingId2 } = await this.recall(() => this.initChunkRequest(file.length, totalChunks2, meta));
62
+ await Promise.all(Array.from({ length: totalChunks2 }, (_, i) => this.uploadChunkSlice(file, recordingId2, i)));
63
+ return this.finalizeChunk(recordingId2);
64
+ }
65
+ if (meta.totalSize === undefined) {
66
+ const buffer = new Uint8Array(await new Response(file).arrayBuffer());
67
+ return this.upload(buffer, { ...meta, totalSize: buffer.length });
68
+ }
69
+ const totalChunks = this.getCountChunks(meta.totalSize);
70
+ const { recordingId } = await this.recall(() => this.initChunkRequest(meta.totalSize, totalChunks, meta));
71
+ await this.uploadFromStream(file, recordingId);
72
+ return this.finalizeChunk(recordingId);
60
73
  }
61
- upload(fileBuffer, meta) {
74
+ initChunk(fileBuffer, meta) {
62
75
  const totalChunks = this.getCountChunks(fileBuffer.length);
63
- return this.initChunk(fileBuffer, meta).then((response) => Promise.all(Array.from({ length: totalChunks }, (_, i) => this.uploadChunk(fileBuffer, response.recordingId, i))).then(() => this.finalizeChunk(response.recordingId)));
76
+ return this.recall(() => this.initChunkRequest(fileBuffer.length, totalChunks, meta));
64
77
  }
65
- initChunk(fileBuffer, meta) {
66
- return this.recall(() => axios.post(this.initChunkLink, {
78
+ uploadChunk(chunk, recordingId, chunkIndex) {
79
+ return this.recall(() => this.uploadChunkRequest(chunk, recordingId, chunkIndex));
80
+ }
81
+ finalizeChunk(recordingId) {
82
+ return this.recall(() => this.post(this.finalizeChunkLink, { recordingId }));
83
+ }
84
+ transcription(recordingId) {
85
+ return this.recall(() => this.post(this.transcriptionLink, { recordingId }));
86
+ }
87
+ translate(recordingId, language) {
88
+ return this.recall(() => this.post(this.translateLink(recordingId), { targetLanguage: language }));
89
+ }
90
+ recording(recordingId) {
91
+ return this.recall(() => this.get(this.recordingLink(recordingId)));
92
+ }
93
+ recordings(query) {
94
+ const params = new URLSearchParams({
95
+ page: String(query?.page ?? 1),
96
+ limit: String(query?.limit ?? 5)
97
+ });
98
+ return this.recall(() => this.get(`${this.recordingsLink}?${params}`));
99
+ }
100
+ summary() {
101
+ return this.recall(() => this.get(this.summaryLink));
102
+ }
103
+ async uploadFromStream(stream, recordingId) {
104
+ const reader = stream.getReader();
105
+ let buffer = new Uint8Array(0);
106
+ let chunkIndex = 0;
107
+ try {
108
+ while (true) {
109
+ const { done, value } = await reader.read();
110
+ if (done) {
111
+ if (buffer.length > 0) {
112
+ await this.uploadChunk(buffer, recordingId, chunkIndex);
113
+ }
114
+ break;
115
+ }
116
+ const merged = new Uint8Array(buffer.length + value.length);
117
+ merged.set(buffer);
118
+ merged.set(value, buffer.length);
119
+ buffer = merged;
120
+ while (buffer.length >= this.clientOptions.chunkSize) {
121
+ await this.uploadChunk(buffer.subarray(0, this.clientOptions.chunkSize), recordingId, chunkIndex);
122
+ buffer = buffer.slice(this.clientOptions.chunkSize);
123
+ chunkIndex++;
124
+ }
125
+ }
126
+ } finally {
127
+ reader.releaseLock();
128
+ }
129
+ }
130
+ uploadChunkSlice(fileBuffer, recordingId, chunkIndex) {
131
+ const start = chunkIndex * this.clientOptions.chunkSize;
132
+ const end = Math.min(start + this.clientOptions.chunkSize, fileBuffer.length);
133
+ return this.uploadChunk(fileBuffer.subarray(start, end), recordingId, chunkIndex);
134
+ }
135
+ initChunkRequest(totalSize, totalChunks, meta) {
136
+ return this.post(this.initChunkLink, {
67
137
  filename: meta.filename,
68
138
  durationSeconds: meta.durationSeconds,
69
- totalSize: fileBuffer.length,
139
+ totalSize,
70
140
  mimeType: meta.mimeType ?? "",
71
141
  title: meta.title ?? meta.filename,
72
142
  enableSpeakerDetection: meta.enableSpeakerDetection ?? false,
73
143
  speakerCount: meta.speakerCount ?? "auto",
74
144
  language: meta.language ?? "",
75
- totalChunks: this.getCountChunks(fileBuffer.length)
76
- }, this.requestOptions));
145
+ totalChunks
146
+ });
77
147
  }
78
- uploadChunk(fileBuffer, recordingId, chunkIndex) {
79
- const start = chunkIndex * this.clientOptions.chunkSize;
80
- const end = Math.min(start + this.clientOptions.chunkSize, fileBuffer.length);
81
- const chunk = fileBuffer.slice(start, end);
82
- const blob = new Blob([chunk], { type: "application/octet-stream" });
148
+ uploadChunkRequest(chunk, recordingId, chunkIndex) {
149
+ const buffer = new ArrayBuffer(chunk.byteLength);
150
+ new Uint8Array(buffer).set(chunk);
83
151
  const formData = new FormData;
84
- formData.append("chunk", blob, `chunk_${chunkIndex}`);
152
+ formData.append("chunk", new Blob([buffer], { type: "application/octet-stream" }), `chunk_${chunkIndex}`);
85
153
  formData.append("recordingId", recordingId.toString());
86
154
  formData.append("chunkIndex", chunkIndex.toString());
87
- return this.recall(() => axios.post(this.uploadChunkLink, formData, this.requestOptions));
88
- }
89
- finalizeChunk(recordingId) {
90
- return this.recall(() => axios.post(this.finalizeChunkLink, { recordingId }, this.requestOptions));
155
+ return this.postForm(this.uploadChunkLink, formData);
91
156
  }
92
- transcription(recordingId) {
93
- return this.recall(() => axios.post(this.transcriptionLink, { recordingId }, this.requestOptions));
157
+ async recall(call) {
158
+ try {
159
+ if (!this.cookies)
160
+ await this.login();
161
+ return await call();
162
+ } catch (error) {
163
+ if (!(error instanceof WhisperAuthError))
164
+ throw error;
165
+ await this.login();
166
+ return call();
167
+ }
94
168
  }
95
- translate(recordingId, language) {
96
- return this.recall(() => axios.post(this.translateLink(recordingId), { targetLanguage: language }, this.requestOptions));
169
+ get(url) {
170
+ return this.request(url, { method: "GET" });
97
171
  }
98
- recording(recordingId) {
99
- return this.recall(() => axios.get(this.recordingLink(recordingId), this.requestOptions));
172
+ post(url, body) {
173
+ return this.request(url, {
174
+ method: "POST",
175
+ body: JSON.stringify(body),
176
+ extraHeaders: { "Content-Type": "application/json" }
177
+ });
100
178
  }
101
- recordings(query) {
102
- return this.recall(() => axios.get(this.recordingsLink, {
103
- ...this.requestOptions,
104
- params: { page: 1, limit: 5, ...query ?? {} }
105
- }));
179
+ postForm(url, body) {
180
+ return this.request(url, { method: "POST", body });
106
181
  }
107
- summary() {
108
- return this.recall(() => axios.get(this.summaryLink, this.requestOptions));
182
+ async request(url, init) {
183
+ const { extraHeaders, ...fetchInit } = init;
184
+ const cookieHeader = this.cookies?.join("; ");
185
+ let response;
186
+ try {
187
+ response = await fetch(url, {
188
+ ...fetchInit,
189
+ headers: {
190
+ "cache-control": "no-cache",
191
+ ...cookieHeader ? { Cookie: cookieHeader } : {},
192
+ ...extraHeaders
193
+ }
194
+ });
195
+ } catch (cause) {
196
+ throw new WhisperNetworkError(cause);
197
+ }
198
+ const setCookies = response.headers.getSetCookie?.() ?? [];
199
+ if (setCookies.length > 0)
200
+ this.mergeCookies(setCookies);
201
+ if (!response.ok) {
202
+ if (response.status === 401 || response.status === 403)
203
+ throw new WhisperAuthError;
204
+ const data = await response.json().catch(() => null);
205
+ throw new WhisperApiError(response.status, data);
206
+ }
207
+ return response.json();
109
208
  }
110
209
  get loginLink() {
111
210
  return `${this.clientOptions.whisperUrl}/api/login`;
@@ -134,57 +233,29 @@ class WhisperClient {
134
233
  get summaryLink() {
135
234
  return `${this.clientOptions.whisperUrl}/api/analytics/summary`;
136
235
  }
236
+ get recordingsLink() {
237
+ return `${this.clientOptions.whisperUrl}/api/recordings-paginated`;
238
+ }
137
239
  translateLink(recordingId) {
138
240
  return `${this.clientOptions.whisperUrl}/api/v2/translation/${recordingId}/translate`;
139
241
  }
140
242
  recordingLink(recordingId) {
141
243
  return `${this.clientOptions.whisperUrl}/api/recordings/${recordingId}`;
142
244
  }
143
- get recordingsLink() {
144
- return `${this.clientOptions.whisperUrl}/api/recordings-paginated`;
245
+ mergeCookies(incoming) {
246
+ const map = new Map;
247
+ for (const cookie of [...this.cookies ?? [], ...incoming]) {
248
+ const pair = cookie.split(";")[0]?.trim();
249
+ if (!pair)
250
+ continue;
251
+ const name = pair.split("=")[0].trim();
252
+ map.set(name, pair);
253
+ }
254
+ this.cookies = [...map.values()];
145
255
  }
146
256
  getCountChunks(size) {
147
257
  return Math.ceil(size / this.clientOptions.chunkSize);
148
258
  }
149
- get requestOptions() {
150
- return {
151
- headers: {
152
- Cookie: this.cookies
153
- }
154
- };
155
- }
156
- async recall(call) {
157
- try {
158
- if (typeof this.cookies !== "string")
159
- await this.login();
160
- return await this.handleRequest(call());
161
- } catch (error) {
162
- if (error.code !== "AUTH_ERROR") {
163
- throw error;
164
- }
165
- await this.login();
166
- return this.handleRequest(call());
167
- }
168
- }
169
- async handleRequest(request) {
170
- try {
171
- const response = await request;
172
- this.cookies = (response.headers["set-cookie"] ?? []).join("; ");
173
- return response.data;
174
- } catch (err) {
175
- console.log("err", err);
176
- if (axios.isAxiosError(err)) {
177
- if (!err.response) {
178
- throw new WhisperNetworkError(err);
179
- }
180
- if (err.response.status === 401) {
181
- throw new WhisperAuthError;
182
- }
183
- throw new WhisperApiError(err.response.status, err.response.data);
184
- }
185
- throw err;
186
- }
187
- }
188
259
  }
189
260
  // src/constant.ts
190
261
  var WhisperStatus;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whisperai-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "TypeScript SDK for WhisperAI: methods and interfaces for interacting with the service without external runtime dependencies.",
5
5
  "keywords": [
6
6
  "whisper",
@@ -32,37 +32,33 @@
32
32
  "dist"
33
33
  ],
34
34
  "scripts": {
35
- "build": "npm run clean && bun build ./src/index.ts --outdir ./dist --target=node --format=esm --external axios && tsc --emitDeclarationOnly",
35
+ "build": "npm run clean && bun build ./src/index.ts --outdir ./dist --target=node --format=esm && tsc --emitDeclarationOnly",
36
36
  "format": "prettier --write \"src/**/*.ts\"",
37
37
  "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
38
38
  "clean": "rm -rf dist",
39
39
  "typecheck": "tsc -p tsconfig.json --noEmit",
40
40
  "release": "semantic-release"
41
41
  },
42
- "dependencies": {
43
- "axios": "^1.13.2"
44
- },
42
+ "dependencies": {},
45
43
  "devDependencies": {
46
- "@commitlint/cli": "20.2.0",
47
- "@commitlint/config-conventional": "20.2.0",
44
+ "@commitlint/cli": "20.5.0",
45
+ "@commitlint/config-conventional": "20.5.0",
48
46
  "@eslint/js": "9.39.2",
49
47
  "@semantic-release/changelog": "6.0.3",
50
48
  "@semantic-release/exec": "7.1.0",
51
49
  "@semantic-release/git": "10.0.1",
52
- "@semantic-release/github": "12.0.2",
53
- "@semantic-release/npm": "13.1.3",
54
- "@types/bun": "^1.3.5",
55
- "@types/node": "25.0.3",
56
- "@typescript-eslint/eslint-plugin": "8.50.1",
57
- "@typescript-eslint/parser": "8.50.1",
58
- "conventional-changelog-conventionalcommits": "9.1.0",
50
+ "@semantic-release/github": "12.0.6",
51
+ "@semantic-release/npm": "13.1.5",
52
+ "@types/bun": "^1.3.11",
53
+ "@types/node": "25.5.2",
54
+ "conventional-changelog-conventionalcommits": "9.3.1",
59
55
  "eslint": "9.39.2",
60
56
  "eslint-config-prettier": "10.1.8",
61
- "eslint-plugin-prettier": "5.5.4",
62
- "prettier": "3.7.4",
63
- "semantic-release": "25.0.2",
57
+ "eslint-plugin-prettier": "5.5.5",
58
+ "prettier": "3.8.1",
59
+ "semantic-release": "25.0.3",
64
60
  "typescript": "5.9.3",
65
- "typescript-eslint": "8.50.1"
61
+ "typescript-eslint": "8.58.0"
66
62
  },
67
63
  "engines": {
68
64
  "node": ">=22.0.0"