@hyperserve/hyperserve-js 0.1.0 → 0.2.0

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @hyperserve/hyperserve-js
2
2
 
3
- TypeScript SDK for the [Hyperserve](https://hyperserve.io) video infrastructure API. Works in Node.js, browsers, and React Native.
3
+ TypeScript SDK for the [Hyperserve](https://hyperserve.io?utm_source=github&utm_medium=readme&utm_campaign=hyperserve-js) video infrastructure API. Works in Node.js, browsers, and React Native.
4
4
 
5
5
  ## Installation
6
6
 
@@ -32,7 +32,6 @@ const hyperserve = new HyperserveClient({ apiKey: process.env.HYPERSERVE_API_KEY
32
32
  // In your API route or server action
33
33
  const upload = await hyperserve.createVideo({
34
34
  filename: 'promo.mp4',
35
- fileSizeBytes: 10_485_760,
36
35
  resolutions: ['480p', '1080p'],
37
36
  isPublic: true,
38
37
  });
@@ -48,7 +47,7 @@ import { putVideoToStorage } from '@hyperserve/hyperserve-js/browser';
48
47
 
49
48
  const { videoId, uploadUrl, contentType } = await fetch('/api/create-upload', {
50
49
  method: 'POST',
51
- body: JSON.stringify({ filename: file.name, fileSizeBytes: file.size }),
50
+ body: JSON.stringify({ filename: file.name }),
52
51
  }).then(r => r.json());
53
52
 
54
53
  await putVideoToStorage({
@@ -70,7 +69,7 @@ import { putVideoToStorage } from '@hyperserve/hyperserve-js/react-native';
70
69
  // asset from expo-image-picker, react-native-image-picker, etc.
71
70
  const { videoId, uploadUrl, contentType } = await fetch('https://your-api.com/create-upload', {
72
71
  method: 'POST',
73
- body: JSON.stringify({ filename: asset.fileName, fileSizeBytes: asset.fileSize }),
72
+ body: JSON.stringify({ filename: asset.fileName }),
74
73
  }).then(r => r.json());
75
74
 
76
75
  await putVideoToStorage({
@@ -116,7 +115,6 @@ Creates a video record and returns a presigned upload URL.
116
115
  ```typescript
117
116
  const upload = await hyperserve.createVideo({
118
117
  filename: 'clip.mp4', // required — extension determines content type
119
- fileSizeBytes: 5_242_880, // required
120
118
  resolutions: ['720p', '1080p'],// required — at least one
121
119
  isPublic: true, // required
122
120
  thumbnailTimestampsSeconds: [5, 30, 60], // optional
@@ -187,15 +185,29 @@ await hyperserve.deleteResolution(resolutionId);
187
185
  Wraps `createVideo`, the storage PUT, and `completeUpload` into a single call. Intended for scripts and server-to-server use cases where the server holds the file. **Not suitable for the browser proxy pattern.**
188
186
 
189
187
  ```typescript
190
- import { readFileSync, statSync } from 'fs';
188
+ import { readFileSync } from 'fs';
189
+
190
+ const result = await hyperserve.uploadVideo({
191
+ file: readFileSync('./promo.mp4'), // Blob | Buffer | ReadableStream
192
+ filename: 'promo.mp4',
193
+ resolutions: ['1080p'],
194
+ isPublic: false,
195
+ });
196
+ ```
197
+
198
+ For a `ReadableStream`, pass `fileSizeBytes` — the size cannot be inferred, and it is
199
+ needed to set `Content-Length` on the storage PUT:
200
+
201
+ ```typescript
202
+ import { createReadStream, statSync } from 'fs';
203
+ import { Readable } from 'stream';
191
204
 
192
- const buffer = readFileSync('./promo.mp4');
193
205
  const { size } = statSync('./promo.mp4');
194
206
 
195
- const result = await hyperserve.uploadVideo({
196
- file: buffer, // Blob | Buffer | ReadableStream
207
+ await hyperserve.uploadVideo({
208
+ file: Readable.toWeb(createReadStream('./promo.mp4')) as ReadableStream,
197
209
  filename: 'promo.mp4',
198
- fileSizeBytes: size, // required for ReadableStream, inferred for Blob/Buffer
210
+ fileSizeBytes: size, // required for ReadableStream
199
211
  resolutions: ['1080p'],
200
212
  isPublic: false,
201
213
  });
package/dist/browser.cjs CHANGED
@@ -26,18 +26,25 @@ var HyperserveTimeoutError = class extends HyperserveError {
26
26
  };
27
27
 
28
28
  // src/storage.ts
29
- async function putToStorage(uploadUrl, contentType, body, onProgress) {
29
+ async function putToStorage(uploadUrl, contentType, body, options = {}) {
30
+ const { contentLength, onProgress } = options;
30
31
  if (onProgress !== void 0 && typeof XMLHttpRequest !== "undefined" && !(body instanceof ReadableStream)) {
31
32
  return putWithXhr(uploadUrl, contentType, body, onProgress);
32
33
  }
33
- return putWithFetch(uploadUrl, contentType, body);
34
+ return putWithFetch(uploadUrl, contentType, body, contentLength);
34
35
  }
35
- function putWithFetch(uploadUrl, contentType, body) {
36
+ function putWithFetch(uploadUrl, contentType, body, contentLength) {
37
+ const isStream = body instanceof ReadableStream;
36
38
  return fetch(uploadUrl, {
37
39
  method: "PUT",
38
- headers: { "Content-Type": contentType },
40
+ headers: {
41
+ "Content-Type": contentType,
42
+ // A stream body would otherwise go out chunked, which S3-compatible
43
+ // storage rejects with 411. Blob bodies get Content-Length from fetch.
44
+ ...isStream && contentLength !== void 0 ? { "Content-Length": String(contentLength) } : {}
45
+ },
39
46
  // duplex is required for ReadableStream bodies in some runtimes (Node 18)
40
- ...body instanceof ReadableStream ? { duplex: "half" } : {},
47
+ ...isStream ? { duplex: "half" } : {},
41
48
  body
42
49
  }).then((response) => {
43
50
  if (!response.ok) {
@@ -80,7 +87,9 @@ function putWithXhr(uploadUrl, contentType, body, onProgress) {
80
87
 
81
88
  // src/browser.ts
82
89
  async function putVideoToStorage(options) {
83
- return putToStorage(options.uploadUrl, options.contentType, options.file, options.onProgress);
90
+ return putToStorage(options.uploadUrl, options.contentType, options.file, {
91
+ ...options.onProgress !== void 0 && { onProgress: options.onProgress }
92
+ });
84
93
  }
85
94
 
86
95
  exports.HyperserveError = HyperserveError;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/storage.ts","../src/browser.ts"],"names":[],"mappings":"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","file":"browser.cjs","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tonProgress?: (percent: number) => void,\n): Promise<void> {\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n): Promise<void> {\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: { \"Content-Type\": contentType },\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(body instanceof ReadableStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","/**\n * Browser-only utilities for the Hyperserve SDK.\n *\n * Import from '@hyperserve/hyperserve-js/browser' — this entry point contains no API key\n * logic and is safe to bundle into client-side code.\n *\n * Usage:\n * import { putVideoToStorage } from '@hyperserve/hyperserve-js/browser';\n *\n * // uploadUrl and contentType come from your own backend\n * await putVideoToStorage({ uploadUrl, contentType, file, onProgress });\n */\n\nexport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\nexport type { PutVideoToStorageOptions, VideoResolution, VideoStatus } from \"./types.js\";\n\nimport { putToStorage } from \"./storage.js\";\nimport type { PutVideoToStorageOptions } from \"./types.js\";\n\n/**\n * PUT a video file to the presigned storage URL obtained from your backend.\n * No API key required — this call goes directly to storage, not to the Hyperserve API.\n *\n * @example\n * const { uploadUrl, contentType } = await fetch('/api/create-upload', { ... }).then(r => r.json());\n * await putVideoToStorage({ uploadUrl, contentType, file, onProgress: (p) => console.log(p) });\n * await fetch('/api/complete-upload', { method: 'POST', body: JSON.stringify({ videoId }) });\n */\nexport async function putVideoToStorage(options: PutVideoToStorageOptions): Promise<void> {\n\treturn putToStorage(options.uploadUrl, options.contentType, options.file, options.onProgress);\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/storage.ts","../src/browser.ts"],"names":[],"mappings":"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","file":"browser.cjs","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport interface PutToStorageOptions {\n\t/**\n\t * Byte length of the body. Only used for ReadableStream bodies, which are sent\n\t * with chunked transfer encoding unless Content-Length is set explicitly — and\n\t * S3-compatible storage rejects a chunked PUT with 411 MissingContentLength.\n\t * Blob bodies carry their own length, so this is ignored for them.\n\t */\n\tcontentLength?: number;\n\tonProgress?: (percent: number) => void;\n}\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\toptions: PutToStorageOptions = {},\n): Promise<void> {\n\tconst { contentLength, onProgress } = options;\n\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body, contentLength);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tcontentLength?: number,\n): Promise<void> {\n\tconst isStream = body instanceof ReadableStream;\n\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: {\n\t\t\t\"Content-Type\": contentType,\n\t\t\t// A stream body would otherwise go out chunked, which S3-compatible\n\t\t\t// storage rejects with 411. Blob bodies get Content-Length from fetch.\n\t\t\t...(isStream && contentLength !== undefined\n\t\t\t\t? { \"Content-Length\": String(contentLength) }\n\t\t\t\t: {}),\n\t\t},\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(isStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","/**\n * Browser-only utilities for the Hyperserve SDK.\n *\n * Import from '@hyperserve/hyperserve-js/browser' — this entry point contains no API key\n * logic and is safe to bundle into client-side code.\n *\n * Usage:\n * import { putVideoToStorage } from '@hyperserve/hyperserve-js/browser';\n *\n * // uploadUrl and contentType come from your own backend\n * await putVideoToStorage({ uploadUrl, contentType, file, onProgress });\n */\n\nexport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\nexport type { PutVideoToStorageOptions, VideoResolution, VideoStatus } from \"./types.js\";\n\nimport { putToStorage } from \"./storage.js\";\nimport type { PutVideoToStorageOptions } from \"./types.js\";\n\n/**\n * PUT a video file to the presigned storage URL obtained from your backend.\n * No API key required — this call goes directly to storage, not to the Hyperserve API.\n *\n * @example\n * const { uploadUrl, contentType } = await fetch('/api/create-upload', { ... }).then(r => r.json());\n * await putVideoToStorage({ uploadUrl, contentType, file, onProgress: (p) => console.log(p) });\n * await fetch('/api/complete-upload', { method: 'POST', body: JSON.stringify({ videoId }) });\n */\nexport async function putVideoToStorage(options: PutVideoToStorageOptions): Promise<void> {\n\treturn putToStorage(options.uploadUrl, options.contentType, options.file, {\n\t\t...(options.onProgress !== undefined && { onProgress: options.onProgress }),\n\t});\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { P as PutVideoToStorageOptions } from './errors-C89laaKB.cjs';
2
- export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploadError, k as VideoResolution, m as VideoStatus } from './errors-C89laaKB.cjs';
1
+ import { P as PutVideoToStorageOptions } from './errors-DJGPqrI5.cjs';
2
+ export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploadError, k as VideoResolution, m as VideoStatus } from './errors-DJGPqrI5.cjs';
3
3
 
4
4
  /**
5
5
  * Browser-only utilities for the Hyperserve SDK.
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as PutVideoToStorageOptions } from './errors-C89laaKB.js';
2
- export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploadError, k as VideoResolution, m as VideoStatus } from './errors-C89laaKB.js';
1
+ import { P as PutVideoToStorageOptions } from './errors-DJGPqrI5.js';
2
+ export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploadError, k as VideoResolution, m as VideoStatus } from './errors-DJGPqrI5.js';
3
3
 
4
4
  /**
5
5
  * Browser-only utilities for the Hyperserve SDK.
package/dist/browser.js CHANGED
@@ -24,18 +24,25 @@ var HyperserveTimeoutError = class extends HyperserveError {
24
24
  };
25
25
 
26
26
  // src/storage.ts
27
- async function putToStorage(uploadUrl, contentType, body, onProgress) {
27
+ async function putToStorage(uploadUrl, contentType, body, options = {}) {
28
+ const { contentLength, onProgress } = options;
28
29
  if (onProgress !== void 0 && typeof XMLHttpRequest !== "undefined" && !(body instanceof ReadableStream)) {
29
30
  return putWithXhr(uploadUrl, contentType, body, onProgress);
30
31
  }
31
- return putWithFetch(uploadUrl, contentType, body);
32
+ return putWithFetch(uploadUrl, contentType, body, contentLength);
32
33
  }
33
- function putWithFetch(uploadUrl, contentType, body) {
34
+ function putWithFetch(uploadUrl, contentType, body, contentLength) {
35
+ const isStream = body instanceof ReadableStream;
34
36
  return fetch(uploadUrl, {
35
37
  method: "PUT",
36
- headers: { "Content-Type": contentType },
38
+ headers: {
39
+ "Content-Type": contentType,
40
+ // A stream body would otherwise go out chunked, which S3-compatible
41
+ // storage rejects with 411. Blob bodies get Content-Length from fetch.
42
+ ...isStream && contentLength !== void 0 ? { "Content-Length": String(contentLength) } : {}
43
+ },
37
44
  // duplex is required for ReadableStream bodies in some runtimes (Node 18)
38
- ...body instanceof ReadableStream ? { duplex: "half" } : {},
45
+ ...isStream ? { duplex: "half" } : {},
39
46
  body
40
47
  }).then((response) => {
41
48
  if (!response.ok) {
@@ -78,7 +85,9 @@ function putWithXhr(uploadUrl, contentType, body, onProgress) {
78
85
 
79
86
  // src/browser.ts
80
87
  async function putVideoToStorage(options) {
81
- return putToStorage(options.uploadUrl, options.contentType, options.file, options.onProgress);
88
+ return putToStorage(options.uploadUrl, options.contentType, options.file, {
89
+ ...options.onProgress !== void 0 && { onProgress: options.onProgress }
90
+ });
82
91
  }
83
92
 
84
93
  export { HyperserveError, HyperserveTimeoutError, HyperserveUploadError, putVideoToStorage };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/storage.ts","../src/browser.ts"],"names":[],"mappings":"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","file":"browser.js","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tonProgress?: (percent: number) => void,\n): Promise<void> {\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n): Promise<void> {\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: { \"Content-Type\": contentType },\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(body instanceof ReadableStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","/**\n * Browser-only utilities for the Hyperserve SDK.\n *\n * Import from '@hyperserve/hyperserve-js/browser' — this entry point contains no API key\n * logic and is safe to bundle into client-side code.\n *\n * Usage:\n * import { putVideoToStorage } from '@hyperserve/hyperserve-js/browser';\n *\n * // uploadUrl and contentType come from your own backend\n * await putVideoToStorage({ uploadUrl, contentType, file, onProgress });\n */\n\nexport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\nexport type { PutVideoToStorageOptions, VideoResolution, VideoStatus } from \"./types.js\";\n\nimport { putToStorage } from \"./storage.js\";\nimport type { PutVideoToStorageOptions } from \"./types.js\";\n\n/**\n * PUT a video file to the presigned storage URL obtained from your backend.\n * No API key required — this call goes directly to storage, not to the Hyperserve API.\n *\n * @example\n * const { uploadUrl, contentType } = await fetch('/api/create-upload', { ... }).then(r => r.json());\n * await putVideoToStorage({ uploadUrl, contentType, file, onProgress: (p) => console.log(p) });\n * await fetch('/api/complete-upload', { method: 'POST', body: JSON.stringify({ videoId }) });\n */\nexport async function putVideoToStorage(options: PutVideoToStorageOptions): Promise<void> {\n\treturn putToStorage(options.uploadUrl, options.contentType, options.file, options.onProgress);\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/storage.ts","../src/browser.ts"],"names":[],"mappings":"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","file":"browser.js","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport interface PutToStorageOptions {\n\t/**\n\t * Byte length of the body. Only used for ReadableStream bodies, which are sent\n\t * with chunked transfer encoding unless Content-Length is set explicitly — and\n\t * S3-compatible storage rejects a chunked PUT with 411 MissingContentLength.\n\t * Blob bodies carry their own length, so this is ignored for them.\n\t */\n\tcontentLength?: number;\n\tonProgress?: (percent: number) => void;\n}\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\toptions: PutToStorageOptions = {},\n): Promise<void> {\n\tconst { contentLength, onProgress } = options;\n\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body, contentLength);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tcontentLength?: number,\n): Promise<void> {\n\tconst isStream = body instanceof ReadableStream;\n\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: {\n\t\t\t\"Content-Type\": contentType,\n\t\t\t// A stream body would otherwise go out chunked, which S3-compatible\n\t\t\t// storage rejects with 411. Blob bodies get Content-Length from fetch.\n\t\t\t...(isStream && contentLength !== undefined\n\t\t\t\t? { \"Content-Length\": String(contentLength) }\n\t\t\t\t: {}),\n\t\t},\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(isStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","/**\n * Browser-only utilities for the Hyperserve SDK.\n *\n * Import from '@hyperserve/hyperserve-js/browser' — this entry point contains no API key\n * logic and is safe to bundle into client-side code.\n *\n * Usage:\n * import { putVideoToStorage } from '@hyperserve/hyperserve-js/browser';\n *\n * // uploadUrl and contentType come from your own backend\n * await putVideoToStorage({ uploadUrl, contentType, file, onProgress });\n */\n\nexport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\nexport type { PutVideoToStorageOptions, VideoResolution, VideoStatus } from \"./types.js\";\n\nimport { putToStorage } from \"./storage.js\";\nimport type { PutVideoToStorageOptions } from \"./types.js\";\n\n/**\n * PUT a video file to the presigned storage URL obtained from your backend.\n * No API key required — this call goes directly to storage, not to the Hyperserve API.\n *\n * @example\n * const { uploadUrl, contentType } = await fetch('/api/create-upload', { ... }).then(r => r.json());\n * await putVideoToStorage({ uploadUrl, contentType, file, onProgress: (p) => console.log(p) });\n * await fetch('/api/complete-upload', { method: 'POST', body: JSON.stringify({ videoId }) });\n */\nexport async function putVideoToStorage(options: PutVideoToStorageOptions): Promise<void> {\n\treturn putToStorage(options.uploadUrl, options.contentType, options.file, {\n\t\t...(options.onProgress !== undefined && { onProgress: options.onProgress }),\n\t});\n}\n"]}
@@ -46,8 +46,6 @@ interface VerifyWebhookSignatureOptions {
46
46
  interface CreateVideoOptions {
47
47
  /** Original filename including extension (e.g. "promo.mp4"). Used server-side to derive content type. */
48
48
  filename: string;
49
- /** File size in bytes. */
50
- fileSizeBytes: number;
51
49
  /** At least one resolution is required. */
52
50
  resolutions: [VideoResolution, ...VideoResolution[]];
53
51
  /** Controls whether playback URLs are public or time-limited signed URLs. */
@@ -81,7 +79,13 @@ interface UploadVideoOptions {
81
79
  file: Blob | Buffer | ReadableStream;
82
80
  /** Filename including extension (e.g. "clip.mp4"). */
83
81
  filename: string;
84
- /** Required when file is a ReadableStream (cannot be inferred). Inferred automatically for Blob/Buffer. */
82
+ /**
83
+ * Byte length of the file. Required when file is a ReadableStream, where it sets
84
+ * Content-Length on the storage PUT — a stream body would otherwise be sent chunked,
85
+ * which storage rejects with 411. Inferred and then ignored for Blob/Buffer, which
86
+ * carry their own length; supplying it for those has no effect on the request.
87
+ * Never sent to the Hyperserve API.
88
+ */
85
89
  fileSizeBytes?: number;
86
90
  resolutions: [VideoResolution, ...VideoResolution[]];
87
91
  isPublic: boolean;
@@ -104,6 +108,8 @@ interface VideoResult {
104
108
  id: string;
105
109
  status: VideoStatus;
106
110
  isPublic: boolean;
111
+ /** Arbitrary key/value bag stored against the video at creation. Keys are returned verbatim. Null when none was set. */
112
+ customMetadata?: Record<string, unknown> | null;
107
113
  resolutions: Partial<Record<VideoResolution, VideoResolutionResult>>;
108
114
  }
109
115
  interface PutVideoToStorageOptions {
@@ -46,8 +46,6 @@ interface VerifyWebhookSignatureOptions {
46
46
  interface CreateVideoOptions {
47
47
  /** Original filename including extension (e.g. "promo.mp4"). Used server-side to derive content type. */
48
48
  filename: string;
49
- /** File size in bytes. */
50
- fileSizeBytes: number;
51
49
  /** At least one resolution is required. */
52
50
  resolutions: [VideoResolution, ...VideoResolution[]];
53
51
  /** Controls whether playback URLs are public or time-limited signed URLs. */
@@ -81,7 +79,13 @@ interface UploadVideoOptions {
81
79
  file: Blob | Buffer | ReadableStream;
82
80
  /** Filename including extension (e.g. "clip.mp4"). */
83
81
  filename: string;
84
- /** Required when file is a ReadableStream (cannot be inferred). Inferred automatically for Blob/Buffer. */
82
+ /**
83
+ * Byte length of the file. Required when file is a ReadableStream, where it sets
84
+ * Content-Length on the storage PUT — a stream body would otherwise be sent chunked,
85
+ * which storage rejects with 411. Inferred and then ignored for Blob/Buffer, which
86
+ * carry their own length; supplying it for those has no effect on the request.
87
+ * Never sent to the Hyperserve API.
88
+ */
85
89
  fileSizeBytes?: number;
86
90
  resolutions: [VideoResolution, ...VideoResolution[]];
87
91
  isPublic: boolean;
@@ -104,6 +108,8 @@ interface VideoResult {
104
108
  id: string;
105
109
  status: VideoStatus;
106
110
  isPublic: boolean;
111
+ /** Arbitrary key/value bag stored against the video at creation. Keys are returned verbatim. Null when none was set. */
112
+ customMetadata?: Record<string, unknown> | null;
107
113
  resolutions: Partial<Record<VideoResolution, VideoResolutionResult>>;
108
114
  }
109
115
  interface PutVideoToStorageOptions {
package/dist/index.cjs CHANGED
@@ -155,15 +155,25 @@ function deriveTypeHint(filename) {
155
155
  }
156
156
 
157
157
  // src/storage.ts
158
- async function putToStorage(uploadUrl, contentType, body, onProgress) {
159
- return putWithFetch(uploadUrl, contentType, body);
158
+ async function putToStorage(uploadUrl, contentType, body, options = {}) {
159
+ const { contentLength, onProgress } = options;
160
+ if (onProgress !== void 0 && typeof XMLHttpRequest !== "undefined" && !(body instanceof ReadableStream)) {
161
+ return putWithXhr(uploadUrl, contentType, body, onProgress);
162
+ }
163
+ return putWithFetch(uploadUrl, contentType, body, contentLength);
160
164
  }
161
- function putWithFetch(uploadUrl, contentType, body) {
165
+ function putWithFetch(uploadUrl, contentType, body, contentLength) {
166
+ const isStream = body instanceof ReadableStream;
162
167
  return fetch(uploadUrl, {
163
168
  method: "PUT",
164
- headers: { "Content-Type": contentType },
169
+ headers: {
170
+ "Content-Type": contentType,
171
+ // A stream body would otherwise go out chunked, which S3-compatible
172
+ // storage rejects with 411. Blob bodies get Content-Length from fetch.
173
+ ...isStream && contentLength !== void 0 ? { "Content-Length": String(contentLength) } : {}
174
+ },
165
175
  // duplex is required for ReadableStream bodies in some runtimes (Node 18)
166
- ...body instanceof ReadableStream ? { duplex: "half" } : {},
176
+ ...isStream ? { duplex: "half" } : {},
167
177
  body
168
178
  }).then((response) => {
169
179
  if (!response.ok) {
@@ -174,6 +184,35 @@ function putWithFetch(uploadUrl, contentType, body) {
174
184
  }
175
185
  });
176
186
  }
187
+ function putWithXhr(uploadUrl, contentType, body, onProgress) {
188
+ return new Promise((resolve, reject) => {
189
+ const xhr = new XMLHttpRequest();
190
+ xhr.open("PUT", uploadUrl);
191
+ xhr.setRequestHeader("Content-Type", contentType);
192
+ xhr.upload.addEventListener("progress", (event) => {
193
+ if (event.lengthComputable) {
194
+ onProgress(Math.round(event.loaded / event.total * 100));
195
+ }
196
+ });
197
+ xhr.addEventListener("load", () => {
198
+ if (xhr.status >= 200 && xhr.status < 300) {
199
+ onProgress(100);
200
+ resolve();
201
+ } else {
202
+ reject(
203
+ new HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status)
204
+ );
205
+ }
206
+ });
207
+ xhr.addEventListener("timeout", () => {
208
+ reject(new HyperserveTimeoutError("Storage PUT timed out"));
209
+ });
210
+ xhr.addEventListener("error", () => {
211
+ reject(new HyperserveUploadError("Storage PUT failed due to a network error"));
212
+ });
213
+ xhr.send(body);
214
+ });
215
+ }
177
216
 
178
217
  // src/client.ts
179
218
  var DEFAULT_BASE_URL = "https://api.hyperserve.io/api";
@@ -199,14 +238,13 @@ var HyperserveClient = class {
199
238
  retries: this.retries,
200
239
  body: {
201
240
  filename: options.filename,
202
- fileSizeBytes: options.fileSizeBytes,
203
241
  resolutions: options.resolutions,
204
242
  isPublic: options.isPublic,
205
243
  ...options.thumbnailTimestampsSeconds !== void 0 && {
206
- thumbnail_timestamps_seconds: options.thumbnailTimestampsSeconds
244
+ thumbnailTimestampsSeconds: options.thumbnailTimestampsSeconds
207
245
  },
208
246
  ...options.customMetadata !== void 0 && {
209
- custom_user_metadata: options.customMetadata
247
+ customMetadata: options.customMetadata
210
248
  }
211
249
  }
212
250
  });
@@ -280,13 +318,14 @@ var HyperserveClient = class {
280
318
  const normalized = normalizeFile(file, filename, options.fileSizeBytes);
281
319
  const upload = await this.createVideo({
282
320
  filename,
283
- fileSizeBytes: normalized.size,
284
321
  resolutions,
285
322
  isPublic,
286
323
  ...thumbnailTimestampsSeconds !== void 0 && { thumbnailTimestampsSeconds },
287
324
  ...customMetadata !== void 0 && { customMetadata }
288
325
  });
289
- await putToStorage(upload.uploadUrl, upload.contentType, normalized.body);
326
+ await putToStorage(upload.uploadUrl, upload.contentType, normalized.body, {
327
+ contentLength: normalized.size
328
+ });
290
329
  return this.completeUpload(upload.id);
291
330
  }
292
331
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/normalize.ts","../src/storage.ts","../src/client.ts","../src/webhook.ts"],"names":["size"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import {\n\tHyperserveApiError,\n\tHyperserveError,\n\tHyperserveNotFoundError,\n\tHyperserveTimeoutError,\n\tHyperserveValidationError,\n} from \"./errors.js\";\n\ninterface RequestOptions {\n\tmethod: \"GET\" | \"POST\" | \"DELETE\";\n\turl: string;\n\tapiKey: string;\n\ttimeoutMs: number;\n\tbody?: unknown;\n\tretries?: number;\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isRetryable(err: unknown): boolean {\n\t// Retry on 5xx API errors only — not 4xx, not timeouts\n\tif (err instanceof HyperserveApiError && err.statusCode !== undefined && err.statusCode >= 500)\n\t\treturn true;\n\t// Retry on network/infrastructure errors that aren't SDK-typed (e.g. TypeError: Failed to fetch)\n\tif (err instanceof Error && !(err instanceof HyperserveError)) return true;\n\treturn false;\n}\n\nexport async function apiRequest<T>(options: RequestOptions): Promise<T> {\n\tconst { retries = 0 } = options;\n\tlet attempt = 0;\n\n\twhile (true) {\n\t\ttry {\n\t\t\treturn await attemptRequest<T>(options);\n\t\t} catch (err) {\n\t\t\tif (attempt >= retries || !isRetryable(err)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\t// Full jitter: random delay up to min(10s, 100ms × 2^attempt)\n\t\t\tconst delay = Math.random() * Math.min(10_000, 100 * 2 ** attempt);\n\t\t\tawait sleep(delay);\n\t\t\tattempt++;\n\t\t}\n\t}\n}\n\nasync function attemptRequest<T>(options: RequestOptions): Promise<T> {\n\tconst { method, url, apiKey, timeoutMs, body } = options;\n\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\n\tlet response: Response;\n\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t\"X-API-KEY\": apiKey,\n\t\t\t\t...(body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n\t\t\t},\n\t\t\t// Omit body entirely when not present — passing body: null on DELETE requests\n\t\t\t// can be treated differently by some proxies and intermediaries.\n\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t\tsignal: controller.signal,\n\t\t});\n\t} catch (err) {\n\t\tif (err instanceof Error && err.name === \"AbortError\") {\n\t\t\tthrow new HyperserveTimeoutError(`Request to ${url} timed out after ${timeoutMs}ms`);\n\t\t}\n\t\tthrow err;\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n\n\tif (response.ok) {\n\t\t// 204 No Content\n\t\tif (response.status === 204) {\n\t\t\treturn undefined as T;\n\t\t}\n\t\ttry {\n\t\t\treturn (await response.json()) as T;\n\t\t} catch {\n\t\t\tthrow new HyperserveApiError(`Failed to parse response from ${url}`, response.status);\n\t\t}\n\t}\n\n\tlet errorBody: { message?: string } = {};\n\ttry {\n\t\terrorBody = (await response.json()) as { message?: string };\n\t} catch {\n\t\t// ignore parse failure — use status text\n\t}\n\n\tconst message = errorBody.message ?? response.statusText;\n\n\tif (response.status === 404) {\n\t\tthrow new HyperserveNotFoundError(message);\n\t}\n\n\tif (response.status >= 400 && response.status < 500) {\n\t\tthrow new HyperserveValidationError(message, response.status, errorBody);\n\t}\n\n\tthrow new HyperserveApiError(message, response.status);\n}\n","/**\n * Normalizes the various accepted file input types into a { body, size } pair\n * suitable for use as a fetch/XHR request body.\n *\n * Size inference rules:\n * Blob / File → blob.size\n * Buffer → buffer.byteLength\n * ReadableStream → must be provided via fileSizeBytes\n */\nexport interface NormalizedFile {\n\tbody: Blob | ReadableStream;\n\tsize: number;\n}\n\nexport function normalizeFile(\n\tfile: Blob | Buffer | ReadableStream,\n\tfilename: string,\n\tfileSizeBytes?: number,\n): NormalizedFile {\n\tif (file instanceof ReadableStream) {\n\t\tif (fileSizeBytes === undefined) {\n\t\t\tthrow new TypeError(\n\t\t\t\t\"fileSizeBytes is required when file is a ReadableStream (size cannot be inferred)\",\n\t\t\t);\n\t\t}\n\t\treturn { body: file, size: fileSizeBytes };\n\t}\n\n\t// Node.js Buffer. `Buffer` is a Node global; all explicitly supported edge runtimes\n\t// (Cloudflare Workers, Vercel Edge) ship a Buffer compatibility layer, so this is safe\n\t// for the stated server targets. Pure browser or RN bundles never reach this branch\n\t// because normalize.ts is not imported by the browser or react-native entry points.\n\tif (Buffer.isBuffer(file)) {\n\t\tconst size = fileSizeBytes ?? file.byteLength;\n\t\t// Wrap in a Blob so fetch/XHR handle it uniformly\n\t\t// Copy into a plain ArrayBuffer to avoid SharedArrayBuffer assignability issues\n\t\tconst blob = new Blob([new Uint8Array(file)], { type: deriveTypeHint(filename) });\n\t\treturn { body: blob, size };\n\t}\n\n\t// Blob / File\n\tconst size = fileSizeBytes ?? file.size;\n\treturn { body: file, size };\n}\n\nfunction deriveTypeHint(filename: string): string {\n\t// Minimal hint — the actual Content-Type for the presigned PUT always\n\t// comes from the server, not from this inference. This is only used\n\t// so the Blob is constructed with a reasonable type attribute.\n\tconst ext = filename.slice(filename.lastIndexOf(\".\") + 1).toLowerCase();\n\tconst map: Record<string, string> = {\n\t\tmp4: \"video/mp4\",\n\t\tmov: \"video/quicktime\",\n\t\twebm: \"video/webm\",\n\t\tavi: \"video/x-msvideo\",\n\t\tmkv: \"video/x-matroska\",\n\t\tm4v: \"video/x-m4v\",\n\t};\n\treturn map[ext] ?? \"application/octet-stream\";\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tonProgress?: (percent: number) => void,\n): Promise<void> {\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n): Promise<void> {\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: { \"Content-Type\": contentType },\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(body instanceof ReadableStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","import { apiRequest } from \"./http.js\";\nimport { normalizeFile } from \"./normalize.js\";\nimport { putToStorage } from \"./storage.js\";\nimport type {\n\tCompleteUploadResult,\n\tCreateVideoOptions,\n\tCreateVideoResult,\n\tGetVideoOptions,\n\tHyperserveClientOptions,\n\tUploadVideoOptions,\n\tVideoResult,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.hyperserve.io/api\";\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport class HyperserveClient {\n\tprivate readonly apiKey: string;\n\tprivate readonly baseUrl: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly retries: number;\n\n\tconstructor(options: HyperserveClientOptions) {\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.baseUrl = options.baseUrl?.replace(/\\/$/, \"\") ?? DEFAULT_BASE_URL;\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\tthis.retries = options.retries ?? 0;\n\t}\n\n\t/**\n\t * Creates a video record and returns a presigned upload URL.\n\t * Pass uploadUrl and contentType to your frontend so it can PUT the file directly to storage.\n\t * Call completeUpload once the frontend confirms the PUT is done.\n\t */\n\tasync createVideo(options: CreateVideoOptions): Promise<CreateVideoResult> {\n\t\treturn apiRequest<CreateVideoResult>({\n\t\t\tmethod: \"POST\",\n\t\t\turl: `${this.baseUrl}/video`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t\tbody: {\n\t\t\t\tfilename: options.filename,\n\t\t\t\tfileSizeBytes: options.fileSizeBytes,\n\t\t\t\tresolutions: options.resolutions,\n\t\t\t\tisPublic: options.isPublic,\n\t\t\t\t...(options.thumbnailTimestampsSeconds !== undefined && {\n\t\t\t\t\tthumbnail_timestamps_seconds: options.thumbnailTimestampsSeconds,\n\t\t\t\t}),\n\t\t\t\t...(options.customMetadata !== undefined && {\n\t\t\t\t\tcustom_user_metadata: options.customMetadata,\n\t\t\t\t}),\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Notifies Hyperserve that the file has been uploaded to the presigned URL.\n\t * Hyperserve verifies the object and queues transcoding.\n\t * Call this after your frontend confirms the storage PUT is complete.\n\t */\n\tasync completeUpload(videoId: string): Promise<CompleteUploadResult> {\n\t\treturn apiRequest<CompleteUploadResult>({\n\t\t\tmethod: \"POST\",\n\t\t\turl: `${this.baseUrl}/video/${videoId}/complete-upload`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Retrieves the current state of a video, including per-resolution status and playback URLs.\n\t *\n\t * @param videoId - The video ID returned by createVideo or uploadVideo.\n\t * @param options.private - Return time-limited signed URLs instead of public URLs.\n\t * @param options.expirationSeconds - Signed URL TTL when private is true. Defaults to 3600.\n\t */\n\tasync getVideo(videoId: string, options?: GetVideoOptions): Promise<VideoResult> {\n\t\tconst isPrivate = options?.private === true;\n\t\tconst expiration = options?.expirationSeconds ?? 3600;\n\n\t\tconst url = isPrivate\n\t\t\t? `${this.baseUrl}/video/${videoId}/private/${expiration}`\n\t\t\t: `${this.baseUrl}/video/${videoId}/public`;\n\n\t\treturn apiRequest<VideoResult>({\n\t\t\tmethod: \"GET\",\n\t\t\turl,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Deletes a video and all associated resolutions and thumbnails.\n\t */\n\tasync deleteVideo(videoId: string): Promise<void> {\n\t\treturn apiRequest<void>({\n\t\t\tmethod: \"DELETE\",\n\t\t\turl: `${this.baseUrl}/video/${videoId}`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Deletes a single resolution for a video.\n\t */\n\tasync deleteResolution(resolutionId: string): Promise<void> {\n\t\treturn apiRequest<void>({\n\t\t\tmethod: \"DELETE\",\n\t\t\turl: `${this.baseUrl}/video/resolution/${resolutionId}`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Convenience method for server-side / script use cases.\n\t * Wraps createVideo, the storage PUT, and completeUpload into a single call.\n\t *\n\t * Not suitable for the browser proxy pattern — use createVideo + putVideoToStorage\n\t * from '@hyperserve/hyperserve-js/browser' + completeUpload separately for that flow.\n\t */\n\tasync uploadVideo(options: UploadVideoOptions): Promise<CompleteUploadResult> {\n\t\tconst { file, filename, resolutions, isPublic, thumbnailTimestampsSeconds, customMetadata } =\n\t\t\toptions;\n\n\t\tconst normalized = normalizeFile(file, filename, options.fileSizeBytes);\n\n\t\tconst upload = await this.createVideo({\n\t\t\tfilename,\n\t\t\tfileSizeBytes: normalized.size,\n\t\t\tresolutions,\n\t\t\tisPublic,\n\t\t\t...(thumbnailTimestampsSeconds !== undefined && { thumbnailTimestampsSeconds }),\n\t\t\t...(customMetadata !== undefined && { customMetadata }),\n\t\t});\n\n\t\tawait putToStorage(upload.uploadUrl, upload.contentType, normalized.body);\n\n\t\treturn this.completeUpload(upload.id);\n\t}\n}\n","import type { VerifyWebhookSignatureOptions } from \"./types.js\";\n\nconst DEFAULT_TOLERANCE_MS = 300_000; // 5 minutes — matches server-side enforcement\n\n/**\n * Verifies the x-hyperserve-signature header on an incoming webhook request.\n *\n * The signature header has the format \"{timestampMs}.{hmac-sha256-hex}\", where the HMAC\n * is computed over \"{timestampMs}.{rawBody}\" using your webhook signing secret. This proves\n * both when the request was sent (replay protection) and that the body was not tampered with\n * (integrity). Any modification to the body or timestamp will invalidate the signature.\n *\n * Returns true if the signature is valid and the timestamp is within the tolerance window.\n * Returns false if the signature is invalid, the timestamp has expired, or the header is malformed.\n *\n * Uses the Web Crypto API for a constant-time HMAC comparison — safe on Node 18+, Bun, Deno,\n * Cloudflare Workers, Vercel Edge, and all other supported server environments.\n *\n * IMPORTANT: pass the raw request body string exactly as received. Do not parse and re-serialize\n * JSON — any whitespace difference will invalidate the signature.\n *\n * @example\n * import { verifyWebhookSignature } from '@hyperserve/hyperserve-js';\n *\n * // Express (use express.raw, not express.json, so you get the raw body)\n * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {\n * const isValid = await verifyWebhookSignature({\n * signature: req.headers['x-hyperserve-signature'] ?? '',\n * secret: process.env.HYPERSERVE_WEBHOOK_SECRET,\n * body: req.body.toString(),\n * });\n * if (!isValid) return res.status(401).end();\n * });\n *\n * // Next.js App Router\n * const body = await request.text();\n * const isValid = await verifyWebhookSignature({\n * signature: request.headers.get('x-hyperserve-signature') ?? '',\n * secret: process.env.HYPERSERVE_WEBHOOK_SECRET!,\n * body,\n * });\n */\nexport async function verifyWebhookSignature(\n\toptions: VerifyWebhookSignatureOptions,\n): Promise<boolean> {\n\tconst { signature, secret, body, toleranceMs = DEFAULT_TOLERANCE_MS } = options;\n\n\tconst dotIndex = signature.indexOf(\".\");\n\tif (dotIndex === -1) return false;\n\n\tconst timestampStr = signature.slice(0, dotIndex);\n\tconst receivedHex = signature.slice(dotIndex + 1);\n\n\t// Validate timestamp is a non-negative integer\n\tconst timestamp = Number(timestampStr);\n\tif (!Number.isInteger(timestamp) || timestamp < 0) return false;\n\n\t// Reject if timestamp is outside the tolerance window.\n\t// Math.abs handles future timestamps (clock skew or forged headers) — without it,\n\t// a negative difference would never exceed toleranceMs, accepting the signature forever.\n\tif (Math.abs(Date.now() - timestamp) > toleranceMs) return false;\n\n\tconst receivedBytes = hexToBytes(receivedHex);\n\tif (receivedBytes === null) return false;\n\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"verify\"],\n\t);\n\n\t// The signed message is \"{timestampMs}.{rawBody}\" — covers both freshness and integrity.\n\t// crypto.subtle.verify performs a constant-time comparison.\n\treturn crypto.subtle.verify(\n\t\t\"HMAC\",\n\t\tkey,\n\t\treceivedBytes,\n\t\tencoder.encode(`${timestampStr}.${body}`),\n\t);\n}\n\nfunction hexToBytes(hex: string): Uint8Array<ArrayBuffer> | null {\n\tif (hex.length === 0 || hex.length % 2 !== 0) return null;\n\tconst bytes = new Uint8Array(new ArrayBuffer(hex.length / 2));\n\tfor (let i = 0; i < hex.length; i += 2) {\n\t\tconst value = parseInt(hex.slice(i, i + 2), 16);\n\t\tif (Number.isNaN(value)) return null;\n\t\tbytes[i / 2] = value;\n\t}\n\treturn bytes;\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/normalize.ts","../src/storage.ts","../src/client.ts","../src/webhook.ts"],"names":["size"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import {\n\tHyperserveApiError,\n\tHyperserveError,\n\tHyperserveNotFoundError,\n\tHyperserveTimeoutError,\n\tHyperserveValidationError,\n} from \"./errors.js\";\n\ninterface RequestOptions {\n\tmethod: \"GET\" | \"POST\" | \"DELETE\";\n\turl: string;\n\tapiKey: string;\n\ttimeoutMs: number;\n\tbody?: unknown;\n\tretries?: number;\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isRetryable(err: unknown): boolean {\n\t// Retry on 5xx API errors only — not 4xx, not timeouts\n\tif (err instanceof HyperserveApiError && err.statusCode !== undefined && err.statusCode >= 500)\n\t\treturn true;\n\t// Retry on network/infrastructure errors that aren't SDK-typed (e.g. TypeError: Failed to fetch)\n\tif (err instanceof Error && !(err instanceof HyperserveError)) return true;\n\treturn false;\n}\n\nexport async function apiRequest<T>(options: RequestOptions): Promise<T> {\n\tconst { retries = 0 } = options;\n\tlet attempt = 0;\n\n\twhile (true) {\n\t\ttry {\n\t\t\treturn await attemptRequest<T>(options);\n\t\t} catch (err) {\n\t\t\tif (attempt >= retries || !isRetryable(err)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\t// Full jitter: random delay up to min(10s, 100ms × 2^attempt)\n\t\t\tconst delay = Math.random() * Math.min(10_000, 100 * 2 ** attempt);\n\t\t\tawait sleep(delay);\n\t\t\tattempt++;\n\t\t}\n\t}\n}\n\nasync function attemptRequest<T>(options: RequestOptions): Promise<T> {\n\tconst { method, url, apiKey, timeoutMs, body } = options;\n\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\n\tlet response: Response;\n\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t\"X-API-KEY\": apiKey,\n\t\t\t\t...(body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n\t\t\t},\n\t\t\t// Omit body entirely when not present — passing body: null on DELETE requests\n\t\t\t// can be treated differently by some proxies and intermediaries.\n\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t\tsignal: controller.signal,\n\t\t});\n\t} catch (err) {\n\t\tif (err instanceof Error && err.name === \"AbortError\") {\n\t\t\tthrow new HyperserveTimeoutError(`Request to ${url} timed out after ${timeoutMs}ms`);\n\t\t}\n\t\tthrow err;\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n\n\tif (response.ok) {\n\t\t// 204 No Content\n\t\tif (response.status === 204) {\n\t\t\treturn undefined as T;\n\t\t}\n\t\ttry {\n\t\t\treturn (await response.json()) as T;\n\t\t} catch {\n\t\t\tthrow new HyperserveApiError(`Failed to parse response from ${url}`, response.status);\n\t\t}\n\t}\n\n\tlet errorBody: { message?: string } = {};\n\ttry {\n\t\terrorBody = (await response.json()) as { message?: string };\n\t} catch {\n\t\t// ignore parse failure — use status text\n\t}\n\n\tconst message = errorBody.message ?? response.statusText;\n\n\tif (response.status === 404) {\n\t\tthrow new HyperserveNotFoundError(message);\n\t}\n\n\tif (response.status >= 400 && response.status < 500) {\n\t\tthrow new HyperserveValidationError(message, response.status, errorBody);\n\t}\n\n\tthrow new HyperserveApiError(message, response.status);\n}\n","/**\n * Normalizes the various accepted file input types into a { body, size } pair\n * suitable for use as a fetch/XHR request body. The size is only consumed for a\n * ReadableStream body, where it becomes the Content-Length header on the storage\n * PUT — S3-compatible storage rejects a chunked PUT with 411. Blob bodies carry\n * their own length, so the size is ignored for them.\n *\n * Size inference rules:\n * Blob / File → blob.size\n * Buffer → buffer.byteLength\n * ReadableStream → must be provided via fileSizeBytes\n */\nexport interface NormalizedFile {\n\tbody: Blob | ReadableStream;\n\tsize: number;\n}\n\nexport function normalizeFile(\n\tfile: Blob | Buffer | ReadableStream,\n\tfilename: string,\n\tfileSizeBytes?: number,\n): NormalizedFile {\n\tif (file instanceof ReadableStream) {\n\t\tif (fileSizeBytes === undefined) {\n\t\t\tthrow new TypeError(\n\t\t\t\t\"fileSizeBytes is required when file is a ReadableStream (size cannot be inferred)\",\n\t\t\t);\n\t\t}\n\t\treturn { body: file, size: fileSizeBytes };\n\t}\n\n\t// Node.js Buffer. `Buffer` is a Node global; all explicitly supported edge runtimes\n\t// (Cloudflare Workers, Vercel Edge) ship a Buffer compatibility layer, so this is safe\n\t// for the stated server targets. Pure browser or RN bundles never reach this branch\n\t// because normalize.ts is not imported by the browser or react-native entry points.\n\tif (Buffer.isBuffer(file)) {\n\t\tconst size = fileSizeBytes ?? file.byteLength;\n\t\t// Wrap in a Blob so fetch/XHR handle it uniformly\n\t\t// Copy into a plain ArrayBuffer to avoid SharedArrayBuffer assignability issues\n\t\tconst blob = new Blob([new Uint8Array(file)], { type: deriveTypeHint(filename) });\n\t\treturn { body: blob, size };\n\t}\n\n\t// Blob / File\n\tconst size = fileSizeBytes ?? file.size;\n\treturn { body: file, size };\n}\n\nfunction deriveTypeHint(filename: string): string {\n\t// Minimal hint — the actual Content-Type for the presigned PUT always\n\t// comes from the server, not from this inference. This is only used\n\t// so the Blob is constructed with a reasonable type attribute.\n\tconst ext = filename.slice(filename.lastIndexOf(\".\") + 1).toLowerCase();\n\tconst map: Record<string, string> = {\n\t\tmp4: \"video/mp4\",\n\t\tmov: \"video/quicktime\",\n\t\twebm: \"video/webm\",\n\t\tavi: \"video/x-msvideo\",\n\t\tmkv: \"video/x-matroska\",\n\t\tm4v: \"video/x-m4v\",\n\t};\n\treturn map[ext] ?? \"application/octet-stream\";\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport interface PutToStorageOptions {\n\t/**\n\t * Byte length of the body. Only used for ReadableStream bodies, which are sent\n\t * with chunked transfer encoding unless Content-Length is set explicitly — and\n\t * S3-compatible storage rejects a chunked PUT with 411 MissingContentLength.\n\t * Blob bodies carry their own length, so this is ignored for them.\n\t */\n\tcontentLength?: number;\n\tonProgress?: (percent: number) => void;\n}\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\toptions: PutToStorageOptions = {},\n): Promise<void> {\n\tconst { contentLength, onProgress } = options;\n\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body, contentLength);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tcontentLength?: number,\n): Promise<void> {\n\tconst isStream = body instanceof ReadableStream;\n\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: {\n\t\t\t\"Content-Type\": contentType,\n\t\t\t// A stream body would otherwise go out chunked, which S3-compatible\n\t\t\t// storage rejects with 411. Blob bodies get Content-Length from fetch.\n\t\t\t...(isStream && contentLength !== undefined\n\t\t\t\t? { \"Content-Length\": String(contentLength) }\n\t\t\t\t: {}),\n\t\t},\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(isStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","import { apiRequest } from \"./http.js\";\nimport { normalizeFile } from \"./normalize.js\";\nimport { putToStorage } from \"./storage.js\";\nimport type {\n\tCompleteUploadResult,\n\tCreateVideoOptions,\n\tCreateVideoResult,\n\tGetVideoOptions,\n\tHyperserveClientOptions,\n\tUploadVideoOptions,\n\tVideoResult,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.hyperserve.io/api\";\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport class HyperserveClient {\n\tprivate readonly apiKey: string;\n\tprivate readonly baseUrl: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly retries: number;\n\n\tconstructor(options: HyperserveClientOptions) {\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.baseUrl = options.baseUrl?.replace(/\\/$/, \"\") ?? DEFAULT_BASE_URL;\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\tthis.retries = options.retries ?? 0;\n\t}\n\n\t/**\n\t * Creates a video record and returns a presigned upload URL.\n\t * Pass uploadUrl and contentType to your frontend so it can PUT the file directly to storage.\n\t * Call completeUpload once the frontend confirms the PUT is done.\n\t */\n\tasync createVideo(options: CreateVideoOptions): Promise<CreateVideoResult> {\n\t\treturn apiRequest<CreateVideoResult>({\n\t\t\tmethod: \"POST\",\n\t\t\turl: `${this.baseUrl}/video`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t\tbody: {\n\t\t\t\tfilename: options.filename,\n\t\t\t\tresolutions: options.resolutions,\n\t\t\t\tisPublic: options.isPublic,\n\t\t\t\t...(options.thumbnailTimestampsSeconds !== undefined && {\n\t\t\t\t\tthumbnailTimestampsSeconds: options.thumbnailTimestampsSeconds,\n\t\t\t\t}),\n\t\t\t\t...(options.customMetadata !== undefined && {\n\t\t\t\t\tcustomMetadata: options.customMetadata,\n\t\t\t\t}),\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Notifies Hyperserve that the file has been uploaded to the presigned URL.\n\t * Hyperserve verifies the object and queues transcoding.\n\t * Call this after your frontend confirms the storage PUT is complete.\n\t */\n\tasync completeUpload(videoId: string): Promise<CompleteUploadResult> {\n\t\treturn apiRequest<CompleteUploadResult>({\n\t\t\tmethod: \"POST\",\n\t\t\turl: `${this.baseUrl}/video/${videoId}/complete-upload`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Retrieves the current state of a video, including per-resolution status and playback URLs.\n\t *\n\t * @param videoId - The video ID returned by createVideo or uploadVideo.\n\t * @param options.private - Return time-limited signed URLs instead of public URLs.\n\t * @param options.expirationSeconds - Signed URL TTL when private is true. Defaults to 3600.\n\t */\n\tasync getVideo(videoId: string, options?: GetVideoOptions): Promise<VideoResult> {\n\t\tconst isPrivate = options?.private === true;\n\t\tconst expiration = options?.expirationSeconds ?? 3600;\n\n\t\tconst url = isPrivate\n\t\t\t? `${this.baseUrl}/video/${videoId}/private/${expiration}`\n\t\t\t: `${this.baseUrl}/video/${videoId}/public`;\n\n\t\treturn apiRequest<VideoResult>({\n\t\t\tmethod: \"GET\",\n\t\t\turl,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Deletes a video and all associated resolutions and thumbnails.\n\t */\n\tasync deleteVideo(videoId: string): Promise<void> {\n\t\treturn apiRequest<void>({\n\t\t\tmethod: \"DELETE\",\n\t\t\turl: `${this.baseUrl}/video/${videoId}`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Deletes a single resolution for a video.\n\t */\n\tasync deleteResolution(resolutionId: string): Promise<void> {\n\t\treturn apiRequest<void>({\n\t\t\tmethod: \"DELETE\",\n\t\t\turl: `${this.baseUrl}/video/resolution/${resolutionId}`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Convenience method for server-side / script use cases.\n\t * Wraps createVideo, the storage PUT, and completeUpload into a single call.\n\t *\n\t * Not suitable for the browser proxy pattern — use createVideo + putVideoToStorage\n\t * from '@hyperserve/hyperserve-js/browser' + completeUpload separately for that flow.\n\t */\n\tasync uploadVideo(options: UploadVideoOptions): Promise<CompleteUploadResult> {\n\t\tconst { file, filename, resolutions, isPublic, thumbnailTimestampsSeconds, customMetadata } =\n\t\t\toptions;\n\n\t\tconst normalized = normalizeFile(file, filename, options.fileSizeBytes);\n\n\t\tconst upload = await this.createVideo({\n\t\t\tfilename,\n\t\t\tresolutions,\n\t\t\tisPublic,\n\t\t\t...(thumbnailTimestampsSeconds !== undefined && { thumbnailTimestampsSeconds }),\n\t\t\t...(customMetadata !== undefined && { customMetadata }),\n\t\t});\n\n\t\tawait putToStorage(upload.uploadUrl, upload.contentType, normalized.body, {\n\t\t\tcontentLength: normalized.size,\n\t\t});\n\n\t\treturn this.completeUpload(upload.id);\n\t}\n}\n","import type { VerifyWebhookSignatureOptions } from \"./types.js\";\n\nconst DEFAULT_TOLERANCE_MS = 300_000; // 5 minutes — matches server-side enforcement\n\n/**\n * Verifies the x-hyperserve-signature header on an incoming webhook request.\n *\n * The signature header has the format \"{timestampMs}.{hmac-sha256-hex}\", where the HMAC\n * is computed over \"{timestampMs}.{rawBody}\" using your webhook signing secret. This proves\n * both when the request was sent (replay protection) and that the body was not tampered with\n * (integrity). Any modification to the body or timestamp will invalidate the signature.\n *\n * Returns true if the signature is valid and the timestamp is within the tolerance window.\n * Returns false if the signature is invalid, the timestamp has expired, or the header is malformed.\n *\n * Uses the Web Crypto API for a constant-time HMAC comparison — safe on Node 18+, Bun, Deno,\n * Cloudflare Workers, Vercel Edge, and all other supported server environments.\n *\n * IMPORTANT: pass the raw request body string exactly as received. Do not parse and re-serialize\n * JSON — any whitespace difference will invalidate the signature.\n *\n * @example\n * import { verifyWebhookSignature } from '@hyperserve/hyperserve-js';\n *\n * // Express (use express.raw, not express.json, so you get the raw body)\n * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {\n * const isValid = await verifyWebhookSignature({\n * signature: req.headers['x-hyperserve-signature'] ?? '',\n * secret: process.env.HYPERSERVE_WEBHOOK_SECRET,\n * body: req.body.toString(),\n * });\n * if (!isValid) return res.status(401).end();\n * });\n *\n * // Next.js App Router\n * const body = await request.text();\n * const isValid = await verifyWebhookSignature({\n * signature: request.headers.get('x-hyperserve-signature') ?? '',\n * secret: process.env.HYPERSERVE_WEBHOOK_SECRET!,\n * body,\n * });\n */\nexport async function verifyWebhookSignature(\n\toptions: VerifyWebhookSignatureOptions,\n): Promise<boolean> {\n\tconst { signature, secret, body, toleranceMs = DEFAULT_TOLERANCE_MS } = options;\n\n\tconst dotIndex = signature.indexOf(\".\");\n\tif (dotIndex === -1) return false;\n\n\tconst timestampStr = signature.slice(0, dotIndex);\n\tconst receivedHex = signature.slice(dotIndex + 1);\n\n\t// Validate timestamp is a non-negative integer\n\tconst timestamp = Number(timestampStr);\n\tif (!Number.isInteger(timestamp) || timestamp < 0) return false;\n\n\t// Reject if timestamp is outside the tolerance window.\n\t// Math.abs handles future timestamps (clock skew or forged headers) — without it,\n\t// a negative difference would never exceed toleranceMs, accepting the signature forever.\n\tif (Math.abs(Date.now() - timestamp) > toleranceMs) return false;\n\n\tconst receivedBytes = hexToBytes(receivedHex);\n\tif (receivedBytes === null) return false;\n\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"verify\"],\n\t);\n\n\t// The signed message is \"{timestampMs}.{rawBody}\" — covers both freshness and integrity.\n\t// crypto.subtle.verify performs a constant-time comparison.\n\treturn crypto.subtle.verify(\n\t\t\"HMAC\",\n\t\tkey,\n\t\treceivedBytes,\n\t\tencoder.encode(`${timestampStr}.${body}`),\n\t);\n}\n\nfunction hexToBytes(hex: string): Uint8Array<ArrayBuffer> | null {\n\tif (hex.length === 0 || hex.length % 2 !== 0) return null;\n\tconst bytes = new Uint8Array(new ArrayBuffer(hex.length / 2));\n\tfor (let i = 0; i < hex.length; i += 2) {\n\t\tconst value = parseInt(hex.slice(i, i + 2), 16);\n\t\tif (Number.isNaN(value)) return null;\n\t\tbytes[i / 2] = value;\n\t}\n\treturn bytes;\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { H as HyperserveClientOptions, C as CreateVideoOptions, a as CreateVideoResult, b as CompleteUploadResult, G as GetVideoOptions, V as VideoResult, U as UploadVideoOptions, c as VerifyWebhookSignatureOptions } from './errors-C89laaKB.cjs';
2
- export { d as HyperserveApiError, e as HyperserveError, f as HyperserveNotFoundError, g as HyperserveTimeoutError, h as HyperserveUploadError, i as HyperserveValidationError, P as PutVideoToStorageOptions, j as PutVideoToStorageRNOptions, k as VideoResolution, l as VideoResolutionResult, m as VideoStatus } from './errors-C89laaKB.cjs';
1
+ import { H as HyperserveClientOptions, C as CreateVideoOptions, a as CreateVideoResult, b as CompleteUploadResult, G as GetVideoOptions, V as VideoResult, U as UploadVideoOptions, c as VerifyWebhookSignatureOptions } from './errors-DJGPqrI5.cjs';
2
+ export { d as HyperserveApiError, e as HyperserveError, f as HyperserveNotFoundError, g as HyperserveTimeoutError, h as HyperserveUploadError, i as HyperserveValidationError, P as PutVideoToStorageOptions, j as PutVideoToStorageRNOptions, k as VideoResolution, l as VideoResolutionResult, m as VideoStatus } from './errors-DJGPqrI5.cjs';
3
3
 
4
4
  declare class HyperserveClient {
5
5
  private readonly apiKey;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { H as HyperserveClientOptions, C as CreateVideoOptions, a as CreateVideoResult, b as CompleteUploadResult, G as GetVideoOptions, V as VideoResult, U as UploadVideoOptions, c as VerifyWebhookSignatureOptions } from './errors-C89laaKB.js';
2
- export { d as HyperserveApiError, e as HyperserveError, f as HyperserveNotFoundError, g as HyperserveTimeoutError, h as HyperserveUploadError, i as HyperserveValidationError, P as PutVideoToStorageOptions, j as PutVideoToStorageRNOptions, k as VideoResolution, l as VideoResolutionResult, m as VideoStatus } from './errors-C89laaKB.js';
1
+ import { H as HyperserveClientOptions, C as CreateVideoOptions, a as CreateVideoResult, b as CompleteUploadResult, G as GetVideoOptions, V as VideoResult, U as UploadVideoOptions, c as VerifyWebhookSignatureOptions } from './errors-DJGPqrI5.js';
2
+ export { d as HyperserveApiError, e as HyperserveError, f as HyperserveNotFoundError, g as HyperserveTimeoutError, h as HyperserveUploadError, i as HyperserveValidationError, P as PutVideoToStorageOptions, j as PutVideoToStorageRNOptions, k as VideoResolution, l as VideoResolutionResult, m as VideoStatus } from './errors-DJGPqrI5.js';
3
3
 
4
4
  declare class HyperserveClient {
5
5
  private readonly apiKey;
package/dist/index.js CHANGED
@@ -153,15 +153,25 @@ function deriveTypeHint(filename) {
153
153
  }
154
154
 
155
155
  // src/storage.ts
156
- async function putToStorage(uploadUrl, contentType, body, onProgress) {
157
- return putWithFetch(uploadUrl, contentType, body);
156
+ async function putToStorage(uploadUrl, contentType, body, options = {}) {
157
+ const { contentLength, onProgress } = options;
158
+ if (onProgress !== void 0 && typeof XMLHttpRequest !== "undefined" && !(body instanceof ReadableStream)) {
159
+ return putWithXhr(uploadUrl, contentType, body, onProgress);
160
+ }
161
+ return putWithFetch(uploadUrl, contentType, body, contentLength);
158
162
  }
159
- function putWithFetch(uploadUrl, contentType, body) {
163
+ function putWithFetch(uploadUrl, contentType, body, contentLength) {
164
+ const isStream = body instanceof ReadableStream;
160
165
  return fetch(uploadUrl, {
161
166
  method: "PUT",
162
- headers: { "Content-Type": contentType },
167
+ headers: {
168
+ "Content-Type": contentType,
169
+ // A stream body would otherwise go out chunked, which S3-compatible
170
+ // storage rejects with 411. Blob bodies get Content-Length from fetch.
171
+ ...isStream && contentLength !== void 0 ? { "Content-Length": String(contentLength) } : {}
172
+ },
163
173
  // duplex is required for ReadableStream bodies in some runtimes (Node 18)
164
- ...body instanceof ReadableStream ? { duplex: "half" } : {},
174
+ ...isStream ? { duplex: "half" } : {},
165
175
  body
166
176
  }).then((response) => {
167
177
  if (!response.ok) {
@@ -172,6 +182,35 @@ function putWithFetch(uploadUrl, contentType, body) {
172
182
  }
173
183
  });
174
184
  }
185
+ function putWithXhr(uploadUrl, contentType, body, onProgress) {
186
+ return new Promise((resolve, reject) => {
187
+ const xhr = new XMLHttpRequest();
188
+ xhr.open("PUT", uploadUrl);
189
+ xhr.setRequestHeader("Content-Type", contentType);
190
+ xhr.upload.addEventListener("progress", (event) => {
191
+ if (event.lengthComputable) {
192
+ onProgress(Math.round(event.loaded / event.total * 100));
193
+ }
194
+ });
195
+ xhr.addEventListener("load", () => {
196
+ if (xhr.status >= 200 && xhr.status < 300) {
197
+ onProgress(100);
198
+ resolve();
199
+ } else {
200
+ reject(
201
+ new HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status)
202
+ );
203
+ }
204
+ });
205
+ xhr.addEventListener("timeout", () => {
206
+ reject(new HyperserveTimeoutError("Storage PUT timed out"));
207
+ });
208
+ xhr.addEventListener("error", () => {
209
+ reject(new HyperserveUploadError("Storage PUT failed due to a network error"));
210
+ });
211
+ xhr.send(body);
212
+ });
213
+ }
175
214
 
176
215
  // src/client.ts
177
216
  var DEFAULT_BASE_URL = "https://api.hyperserve.io/api";
@@ -197,14 +236,13 @@ var HyperserveClient = class {
197
236
  retries: this.retries,
198
237
  body: {
199
238
  filename: options.filename,
200
- fileSizeBytes: options.fileSizeBytes,
201
239
  resolutions: options.resolutions,
202
240
  isPublic: options.isPublic,
203
241
  ...options.thumbnailTimestampsSeconds !== void 0 && {
204
- thumbnail_timestamps_seconds: options.thumbnailTimestampsSeconds
242
+ thumbnailTimestampsSeconds: options.thumbnailTimestampsSeconds
205
243
  },
206
244
  ...options.customMetadata !== void 0 && {
207
- custom_user_metadata: options.customMetadata
245
+ customMetadata: options.customMetadata
208
246
  }
209
247
  }
210
248
  });
@@ -278,13 +316,14 @@ var HyperserveClient = class {
278
316
  const normalized = normalizeFile(file, filename, options.fileSizeBytes);
279
317
  const upload = await this.createVideo({
280
318
  filename,
281
- fileSizeBytes: normalized.size,
282
319
  resolutions,
283
320
  isPublic,
284
321
  ...thumbnailTimestampsSeconds !== void 0 && { thumbnailTimestampsSeconds },
285
322
  ...customMetadata !== void 0 && { customMetadata }
286
323
  });
287
- await putToStorage(upload.uploadUrl, upload.contentType, normalized.body);
324
+ await putToStorage(upload.uploadUrl, upload.contentType, normalized.body, {
325
+ contentLength: normalized.size
326
+ });
288
327
  return this.completeUpload(upload.id);
289
328
  }
290
329
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/normalize.ts","../src/storage.ts","../src/client.ts","../src/webhook.ts"],"names":["size"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import {\n\tHyperserveApiError,\n\tHyperserveError,\n\tHyperserveNotFoundError,\n\tHyperserveTimeoutError,\n\tHyperserveValidationError,\n} from \"./errors.js\";\n\ninterface RequestOptions {\n\tmethod: \"GET\" | \"POST\" | \"DELETE\";\n\turl: string;\n\tapiKey: string;\n\ttimeoutMs: number;\n\tbody?: unknown;\n\tretries?: number;\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isRetryable(err: unknown): boolean {\n\t// Retry on 5xx API errors only — not 4xx, not timeouts\n\tif (err instanceof HyperserveApiError && err.statusCode !== undefined && err.statusCode >= 500)\n\t\treturn true;\n\t// Retry on network/infrastructure errors that aren't SDK-typed (e.g. TypeError: Failed to fetch)\n\tif (err instanceof Error && !(err instanceof HyperserveError)) return true;\n\treturn false;\n}\n\nexport async function apiRequest<T>(options: RequestOptions): Promise<T> {\n\tconst { retries = 0 } = options;\n\tlet attempt = 0;\n\n\twhile (true) {\n\t\ttry {\n\t\t\treturn await attemptRequest<T>(options);\n\t\t} catch (err) {\n\t\t\tif (attempt >= retries || !isRetryable(err)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\t// Full jitter: random delay up to min(10s, 100ms × 2^attempt)\n\t\t\tconst delay = Math.random() * Math.min(10_000, 100 * 2 ** attempt);\n\t\t\tawait sleep(delay);\n\t\t\tattempt++;\n\t\t}\n\t}\n}\n\nasync function attemptRequest<T>(options: RequestOptions): Promise<T> {\n\tconst { method, url, apiKey, timeoutMs, body } = options;\n\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\n\tlet response: Response;\n\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t\"X-API-KEY\": apiKey,\n\t\t\t\t...(body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n\t\t\t},\n\t\t\t// Omit body entirely when not present — passing body: null on DELETE requests\n\t\t\t// can be treated differently by some proxies and intermediaries.\n\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t\tsignal: controller.signal,\n\t\t});\n\t} catch (err) {\n\t\tif (err instanceof Error && err.name === \"AbortError\") {\n\t\t\tthrow new HyperserveTimeoutError(`Request to ${url} timed out after ${timeoutMs}ms`);\n\t\t}\n\t\tthrow err;\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n\n\tif (response.ok) {\n\t\t// 204 No Content\n\t\tif (response.status === 204) {\n\t\t\treturn undefined as T;\n\t\t}\n\t\ttry {\n\t\t\treturn (await response.json()) as T;\n\t\t} catch {\n\t\t\tthrow new HyperserveApiError(`Failed to parse response from ${url}`, response.status);\n\t\t}\n\t}\n\n\tlet errorBody: { message?: string } = {};\n\ttry {\n\t\terrorBody = (await response.json()) as { message?: string };\n\t} catch {\n\t\t// ignore parse failure — use status text\n\t}\n\n\tconst message = errorBody.message ?? response.statusText;\n\n\tif (response.status === 404) {\n\t\tthrow new HyperserveNotFoundError(message);\n\t}\n\n\tif (response.status >= 400 && response.status < 500) {\n\t\tthrow new HyperserveValidationError(message, response.status, errorBody);\n\t}\n\n\tthrow new HyperserveApiError(message, response.status);\n}\n","/**\n * Normalizes the various accepted file input types into a { body, size } pair\n * suitable for use as a fetch/XHR request body.\n *\n * Size inference rules:\n * Blob / File → blob.size\n * Buffer → buffer.byteLength\n * ReadableStream → must be provided via fileSizeBytes\n */\nexport interface NormalizedFile {\n\tbody: Blob | ReadableStream;\n\tsize: number;\n}\n\nexport function normalizeFile(\n\tfile: Blob | Buffer | ReadableStream,\n\tfilename: string,\n\tfileSizeBytes?: number,\n): NormalizedFile {\n\tif (file instanceof ReadableStream) {\n\t\tif (fileSizeBytes === undefined) {\n\t\t\tthrow new TypeError(\n\t\t\t\t\"fileSizeBytes is required when file is a ReadableStream (size cannot be inferred)\",\n\t\t\t);\n\t\t}\n\t\treturn { body: file, size: fileSizeBytes };\n\t}\n\n\t// Node.js Buffer. `Buffer` is a Node global; all explicitly supported edge runtimes\n\t// (Cloudflare Workers, Vercel Edge) ship a Buffer compatibility layer, so this is safe\n\t// for the stated server targets. Pure browser or RN bundles never reach this branch\n\t// because normalize.ts is not imported by the browser or react-native entry points.\n\tif (Buffer.isBuffer(file)) {\n\t\tconst size = fileSizeBytes ?? file.byteLength;\n\t\t// Wrap in a Blob so fetch/XHR handle it uniformly\n\t\t// Copy into a plain ArrayBuffer to avoid SharedArrayBuffer assignability issues\n\t\tconst blob = new Blob([new Uint8Array(file)], { type: deriveTypeHint(filename) });\n\t\treturn { body: blob, size };\n\t}\n\n\t// Blob / File\n\tconst size = fileSizeBytes ?? file.size;\n\treturn { body: file, size };\n}\n\nfunction deriveTypeHint(filename: string): string {\n\t// Minimal hint — the actual Content-Type for the presigned PUT always\n\t// comes from the server, not from this inference. This is only used\n\t// so the Blob is constructed with a reasonable type attribute.\n\tconst ext = filename.slice(filename.lastIndexOf(\".\") + 1).toLowerCase();\n\tconst map: Record<string, string> = {\n\t\tmp4: \"video/mp4\",\n\t\tmov: \"video/quicktime\",\n\t\twebm: \"video/webm\",\n\t\tavi: \"video/x-msvideo\",\n\t\tmkv: \"video/x-matroska\",\n\t\tm4v: \"video/x-m4v\",\n\t};\n\treturn map[ext] ?? \"application/octet-stream\";\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tonProgress?: (percent: number) => void,\n): Promise<void> {\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n): Promise<void> {\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: { \"Content-Type\": contentType },\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(body instanceof ReadableStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","import { apiRequest } from \"./http.js\";\nimport { normalizeFile } from \"./normalize.js\";\nimport { putToStorage } from \"./storage.js\";\nimport type {\n\tCompleteUploadResult,\n\tCreateVideoOptions,\n\tCreateVideoResult,\n\tGetVideoOptions,\n\tHyperserveClientOptions,\n\tUploadVideoOptions,\n\tVideoResult,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.hyperserve.io/api\";\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport class HyperserveClient {\n\tprivate readonly apiKey: string;\n\tprivate readonly baseUrl: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly retries: number;\n\n\tconstructor(options: HyperserveClientOptions) {\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.baseUrl = options.baseUrl?.replace(/\\/$/, \"\") ?? DEFAULT_BASE_URL;\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\tthis.retries = options.retries ?? 0;\n\t}\n\n\t/**\n\t * Creates a video record and returns a presigned upload URL.\n\t * Pass uploadUrl and contentType to your frontend so it can PUT the file directly to storage.\n\t * Call completeUpload once the frontend confirms the PUT is done.\n\t */\n\tasync createVideo(options: CreateVideoOptions): Promise<CreateVideoResult> {\n\t\treturn apiRequest<CreateVideoResult>({\n\t\t\tmethod: \"POST\",\n\t\t\turl: `${this.baseUrl}/video`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t\tbody: {\n\t\t\t\tfilename: options.filename,\n\t\t\t\tfileSizeBytes: options.fileSizeBytes,\n\t\t\t\tresolutions: options.resolutions,\n\t\t\t\tisPublic: options.isPublic,\n\t\t\t\t...(options.thumbnailTimestampsSeconds !== undefined && {\n\t\t\t\t\tthumbnail_timestamps_seconds: options.thumbnailTimestampsSeconds,\n\t\t\t\t}),\n\t\t\t\t...(options.customMetadata !== undefined && {\n\t\t\t\t\tcustom_user_metadata: options.customMetadata,\n\t\t\t\t}),\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Notifies Hyperserve that the file has been uploaded to the presigned URL.\n\t * Hyperserve verifies the object and queues transcoding.\n\t * Call this after your frontend confirms the storage PUT is complete.\n\t */\n\tasync completeUpload(videoId: string): Promise<CompleteUploadResult> {\n\t\treturn apiRequest<CompleteUploadResult>({\n\t\t\tmethod: \"POST\",\n\t\t\turl: `${this.baseUrl}/video/${videoId}/complete-upload`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Retrieves the current state of a video, including per-resolution status and playback URLs.\n\t *\n\t * @param videoId - The video ID returned by createVideo or uploadVideo.\n\t * @param options.private - Return time-limited signed URLs instead of public URLs.\n\t * @param options.expirationSeconds - Signed URL TTL when private is true. Defaults to 3600.\n\t */\n\tasync getVideo(videoId: string, options?: GetVideoOptions): Promise<VideoResult> {\n\t\tconst isPrivate = options?.private === true;\n\t\tconst expiration = options?.expirationSeconds ?? 3600;\n\n\t\tconst url = isPrivate\n\t\t\t? `${this.baseUrl}/video/${videoId}/private/${expiration}`\n\t\t\t: `${this.baseUrl}/video/${videoId}/public`;\n\n\t\treturn apiRequest<VideoResult>({\n\t\t\tmethod: \"GET\",\n\t\t\turl,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Deletes a video and all associated resolutions and thumbnails.\n\t */\n\tasync deleteVideo(videoId: string): Promise<void> {\n\t\treturn apiRequest<void>({\n\t\t\tmethod: \"DELETE\",\n\t\t\turl: `${this.baseUrl}/video/${videoId}`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Deletes a single resolution for a video.\n\t */\n\tasync deleteResolution(resolutionId: string): Promise<void> {\n\t\treturn apiRequest<void>({\n\t\t\tmethod: \"DELETE\",\n\t\t\turl: `${this.baseUrl}/video/resolution/${resolutionId}`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Convenience method for server-side / script use cases.\n\t * Wraps createVideo, the storage PUT, and completeUpload into a single call.\n\t *\n\t * Not suitable for the browser proxy pattern — use createVideo + putVideoToStorage\n\t * from '@hyperserve/hyperserve-js/browser' + completeUpload separately for that flow.\n\t */\n\tasync uploadVideo(options: UploadVideoOptions): Promise<CompleteUploadResult> {\n\t\tconst { file, filename, resolutions, isPublic, thumbnailTimestampsSeconds, customMetadata } =\n\t\t\toptions;\n\n\t\tconst normalized = normalizeFile(file, filename, options.fileSizeBytes);\n\n\t\tconst upload = await this.createVideo({\n\t\t\tfilename,\n\t\t\tfileSizeBytes: normalized.size,\n\t\t\tresolutions,\n\t\t\tisPublic,\n\t\t\t...(thumbnailTimestampsSeconds !== undefined && { thumbnailTimestampsSeconds }),\n\t\t\t...(customMetadata !== undefined && { customMetadata }),\n\t\t});\n\n\t\tawait putToStorage(upload.uploadUrl, upload.contentType, normalized.body);\n\n\t\treturn this.completeUpload(upload.id);\n\t}\n}\n","import type { VerifyWebhookSignatureOptions } from \"./types.js\";\n\nconst DEFAULT_TOLERANCE_MS = 300_000; // 5 minutes — matches server-side enforcement\n\n/**\n * Verifies the x-hyperserve-signature header on an incoming webhook request.\n *\n * The signature header has the format \"{timestampMs}.{hmac-sha256-hex}\", where the HMAC\n * is computed over \"{timestampMs}.{rawBody}\" using your webhook signing secret. This proves\n * both when the request was sent (replay protection) and that the body was not tampered with\n * (integrity). Any modification to the body or timestamp will invalidate the signature.\n *\n * Returns true if the signature is valid and the timestamp is within the tolerance window.\n * Returns false if the signature is invalid, the timestamp has expired, or the header is malformed.\n *\n * Uses the Web Crypto API for a constant-time HMAC comparison — safe on Node 18+, Bun, Deno,\n * Cloudflare Workers, Vercel Edge, and all other supported server environments.\n *\n * IMPORTANT: pass the raw request body string exactly as received. Do not parse and re-serialize\n * JSON — any whitespace difference will invalidate the signature.\n *\n * @example\n * import { verifyWebhookSignature } from '@hyperserve/hyperserve-js';\n *\n * // Express (use express.raw, not express.json, so you get the raw body)\n * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {\n * const isValid = await verifyWebhookSignature({\n * signature: req.headers['x-hyperserve-signature'] ?? '',\n * secret: process.env.HYPERSERVE_WEBHOOK_SECRET,\n * body: req.body.toString(),\n * });\n * if (!isValid) return res.status(401).end();\n * });\n *\n * // Next.js App Router\n * const body = await request.text();\n * const isValid = await verifyWebhookSignature({\n * signature: request.headers.get('x-hyperserve-signature') ?? '',\n * secret: process.env.HYPERSERVE_WEBHOOK_SECRET!,\n * body,\n * });\n */\nexport async function verifyWebhookSignature(\n\toptions: VerifyWebhookSignatureOptions,\n): Promise<boolean> {\n\tconst { signature, secret, body, toleranceMs = DEFAULT_TOLERANCE_MS } = options;\n\n\tconst dotIndex = signature.indexOf(\".\");\n\tif (dotIndex === -1) return false;\n\n\tconst timestampStr = signature.slice(0, dotIndex);\n\tconst receivedHex = signature.slice(dotIndex + 1);\n\n\t// Validate timestamp is a non-negative integer\n\tconst timestamp = Number(timestampStr);\n\tif (!Number.isInteger(timestamp) || timestamp < 0) return false;\n\n\t// Reject if timestamp is outside the tolerance window.\n\t// Math.abs handles future timestamps (clock skew or forged headers) — without it,\n\t// a negative difference would never exceed toleranceMs, accepting the signature forever.\n\tif (Math.abs(Date.now() - timestamp) > toleranceMs) return false;\n\n\tconst receivedBytes = hexToBytes(receivedHex);\n\tif (receivedBytes === null) return false;\n\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"verify\"],\n\t);\n\n\t// The signed message is \"{timestampMs}.{rawBody}\" — covers both freshness and integrity.\n\t// crypto.subtle.verify performs a constant-time comparison.\n\treturn crypto.subtle.verify(\n\t\t\"HMAC\",\n\t\tkey,\n\t\treceivedBytes,\n\t\tencoder.encode(`${timestampStr}.${body}`),\n\t);\n}\n\nfunction hexToBytes(hex: string): Uint8Array<ArrayBuffer> | null {\n\tif (hex.length === 0 || hex.length % 2 !== 0) return null;\n\tconst bytes = new Uint8Array(new ArrayBuffer(hex.length / 2));\n\tfor (let i = 0; i < hex.length; i += 2) {\n\t\tconst value = parseInt(hex.slice(i, i + 2), 16);\n\t\tif (Number.isNaN(value)) return null;\n\t\tbytes[i / 2] = value;\n\t}\n\treturn bytes;\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/normalize.ts","../src/storage.ts","../src/client.ts","../src/webhook.ts"],"names":["size"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import {\n\tHyperserveApiError,\n\tHyperserveError,\n\tHyperserveNotFoundError,\n\tHyperserveTimeoutError,\n\tHyperserveValidationError,\n} from \"./errors.js\";\n\ninterface RequestOptions {\n\tmethod: \"GET\" | \"POST\" | \"DELETE\";\n\turl: string;\n\tapiKey: string;\n\ttimeoutMs: number;\n\tbody?: unknown;\n\tretries?: number;\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isRetryable(err: unknown): boolean {\n\t// Retry on 5xx API errors only — not 4xx, not timeouts\n\tif (err instanceof HyperserveApiError && err.statusCode !== undefined && err.statusCode >= 500)\n\t\treturn true;\n\t// Retry on network/infrastructure errors that aren't SDK-typed (e.g. TypeError: Failed to fetch)\n\tif (err instanceof Error && !(err instanceof HyperserveError)) return true;\n\treturn false;\n}\n\nexport async function apiRequest<T>(options: RequestOptions): Promise<T> {\n\tconst { retries = 0 } = options;\n\tlet attempt = 0;\n\n\twhile (true) {\n\t\ttry {\n\t\t\treturn await attemptRequest<T>(options);\n\t\t} catch (err) {\n\t\t\tif (attempt >= retries || !isRetryable(err)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\t// Full jitter: random delay up to min(10s, 100ms × 2^attempt)\n\t\t\tconst delay = Math.random() * Math.min(10_000, 100 * 2 ** attempt);\n\t\t\tawait sleep(delay);\n\t\t\tattempt++;\n\t\t}\n\t}\n}\n\nasync function attemptRequest<T>(options: RequestOptions): Promise<T> {\n\tconst { method, url, apiKey, timeoutMs, body } = options;\n\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\n\tlet response: Response;\n\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t\"X-API-KEY\": apiKey,\n\t\t\t\t...(body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n\t\t\t},\n\t\t\t// Omit body entirely when not present — passing body: null on DELETE requests\n\t\t\t// can be treated differently by some proxies and intermediaries.\n\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t\tsignal: controller.signal,\n\t\t});\n\t} catch (err) {\n\t\tif (err instanceof Error && err.name === \"AbortError\") {\n\t\t\tthrow new HyperserveTimeoutError(`Request to ${url} timed out after ${timeoutMs}ms`);\n\t\t}\n\t\tthrow err;\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n\n\tif (response.ok) {\n\t\t// 204 No Content\n\t\tif (response.status === 204) {\n\t\t\treturn undefined as T;\n\t\t}\n\t\ttry {\n\t\t\treturn (await response.json()) as T;\n\t\t} catch {\n\t\t\tthrow new HyperserveApiError(`Failed to parse response from ${url}`, response.status);\n\t\t}\n\t}\n\n\tlet errorBody: { message?: string } = {};\n\ttry {\n\t\terrorBody = (await response.json()) as { message?: string };\n\t} catch {\n\t\t// ignore parse failure — use status text\n\t}\n\n\tconst message = errorBody.message ?? response.statusText;\n\n\tif (response.status === 404) {\n\t\tthrow new HyperserveNotFoundError(message);\n\t}\n\n\tif (response.status >= 400 && response.status < 500) {\n\t\tthrow new HyperserveValidationError(message, response.status, errorBody);\n\t}\n\n\tthrow new HyperserveApiError(message, response.status);\n}\n","/**\n * Normalizes the various accepted file input types into a { body, size } pair\n * suitable for use as a fetch/XHR request body. The size is only consumed for a\n * ReadableStream body, where it becomes the Content-Length header on the storage\n * PUT — S3-compatible storage rejects a chunked PUT with 411. Blob bodies carry\n * their own length, so the size is ignored for them.\n *\n * Size inference rules:\n * Blob / File → blob.size\n * Buffer → buffer.byteLength\n * ReadableStream → must be provided via fileSizeBytes\n */\nexport interface NormalizedFile {\n\tbody: Blob | ReadableStream;\n\tsize: number;\n}\n\nexport function normalizeFile(\n\tfile: Blob | Buffer | ReadableStream,\n\tfilename: string,\n\tfileSizeBytes?: number,\n): NormalizedFile {\n\tif (file instanceof ReadableStream) {\n\t\tif (fileSizeBytes === undefined) {\n\t\t\tthrow new TypeError(\n\t\t\t\t\"fileSizeBytes is required when file is a ReadableStream (size cannot be inferred)\",\n\t\t\t);\n\t\t}\n\t\treturn { body: file, size: fileSizeBytes };\n\t}\n\n\t// Node.js Buffer. `Buffer` is a Node global; all explicitly supported edge runtimes\n\t// (Cloudflare Workers, Vercel Edge) ship a Buffer compatibility layer, so this is safe\n\t// for the stated server targets. Pure browser or RN bundles never reach this branch\n\t// because normalize.ts is not imported by the browser or react-native entry points.\n\tif (Buffer.isBuffer(file)) {\n\t\tconst size = fileSizeBytes ?? file.byteLength;\n\t\t// Wrap in a Blob so fetch/XHR handle it uniformly\n\t\t// Copy into a plain ArrayBuffer to avoid SharedArrayBuffer assignability issues\n\t\tconst blob = new Blob([new Uint8Array(file)], { type: deriveTypeHint(filename) });\n\t\treturn { body: blob, size };\n\t}\n\n\t// Blob / File\n\tconst size = fileSizeBytes ?? file.size;\n\treturn { body: file, size };\n}\n\nfunction deriveTypeHint(filename: string): string {\n\t// Minimal hint — the actual Content-Type for the presigned PUT always\n\t// comes from the server, not from this inference. This is only used\n\t// so the Blob is constructed with a reasonable type attribute.\n\tconst ext = filename.slice(filename.lastIndexOf(\".\") + 1).toLowerCase();\n\tconst map: Record<string, string> = {\n\t\tmp4: \"video/mp4\",\n\t\tmov: \"video/quicktime\",\n\t\twebm: \"video/webm\",\n\t\tavi: \"video/x-msvideo\",\n\t\tmkv: \"video/x-matroska\",\n\t\tm4v: \"video/x-m4v\",\n\t};\n\treturn map[ext] ?? \"application/octet-stream\";\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport interface PutToStorageOptions {\n\t/**\n\t * Byte length of the body. Only used for ReadableStream bodies, which are sent\n\t * with chunked transfer encoding unless Content-Length is set explicitly — and\n\t * S3-compatible storage rejects a chunked PUT with 411 MissingContentLength.\n\t * Blob bodies carry their own length, so this is ignored for them.\n\t */\n\tcontentLength?: number;\n\tonProgress?: (percent: number) => void;\n}\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\toptions: PutToStorageOptions = {},\n): Promise<void> {\n\tconst { contentLength, onProgress } = options;\n\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body, contentLength);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tcontentLength?: number,\n): Promise<void> {\n\tconst isStream = body instanceof ReadableStream;\n\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: {\n\t\t\t\"Content-Type\": contentType,\n\t\t\t// A stream body would otherwise go out chunked, which S3-compatible\n\t\t\t// storage rejects with 411. Blob bodies get Content-Length from fetch.\n\t\t\t...(isStream && contentLength !== undefined\n\t\t\t\t? { \"Content-Length\": String(contentLength) }\n\t\t\t\t: {}),\n\t\t},\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(isStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","import { apiRequest } from \"./http.js\";\nimport { normalizeFile } from \"./normalize.js\";\nimport { putToStorage } from \"./storage.js\";\nimport type {\n\tCompleteUploadResult,\n\tCreateVideoOptions,\n\tCreateVideoResult,\n\tGetVideoOptions,\n\tHyperserveClientOptions,\n\tUploadVideoOptions,\n\tVideoResult,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.hyperserve.io/api\";\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport class HyperserveClient {\n\tprivate readonly apiKey: string;\n\tprivate readonly baseUrl: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly retries: number;\n\n\tconstructor(options: HyperserveClientOptions) {\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.baseUrl = options.baseUrl?.replace(/\\/$/, \"\") ?? DEFAULT_BASE_URL;\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\tthis.retries = options.retries ?? 0;\n\t}\n\n\t/**\n\t * Creates a video record and returns a presigned upload URL.\n\t * Pass uploadUrl and contentType to your frontend so it can PUT the file directly to storage.\n\t * Call completeUpload once the frontend confirms the PUT is done.\n\t */\n\tasync createVideo(options: CreateVideoOptions): Promise<CreateVideoResult> {\n\t\treturn apiRequest<CreateVideoResult>({\n\t\t\tmethod: \"POST\",\n\t\t\turl: `${this.baseUrl}/video`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t\tbody: {\n\t\t\t\tfilename: options.filename,\n\t\t\t\tresolutions: options.resolutions,\n\t\t\t\tisPublic: options.isPublic,\n\t\t\t\t...(options.thumbnailTimestampsSeconds !== undefined && {\n\t\t\t\t\tthumbnailTimestampsSeconds: options.thumbnailTimestampsSeconds,\n\t\t\t\t}),\n\t\t\t\t...(options.customMetadata !== undefined && {\n\t\t\t\t\tcustomMetadata: options.customMetadata,\n\t\t\t\t}),\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Notifies Hyperserve that the file has been uploaded to the presigned URL.\n\t * Hyperserve verifies the object and queues transcoding.\n\t * Call this after your frontend confirms the storage PUT is complete.\n\t */\n\tasync completeUpload(videoId: string): Promise<CompleteUploadResult> {\n\t\treturn apiRequest<CompleteUploadResult>({\n\t\t\tmethod: \"POST\",\n\t\t\turl: `${this.baseUrl}/video/${videoId}/complete-upload`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Retrieves the current state of a video, including per-resolution status and playback URLs.\n\t *\n\t * @param videoId - The video ID returned by createVideo or uploadVideo.\n\t * @param options.private - Return time-limited signed URLs instead of public URLs.\n\t * @param options.expirationSeconds - Signed URL TTL when private is true. Defaults to 3600.\n\t */\n\tasync getVideo(videoId: string, options?: GetVideoOptions): Promise<VideoResult> {\n\t\tconst isPrivate = options?.private === true;\n\t\tconst expiration = options?.expirationSeconds ?? 3600;\n\n\t\tconst url = isPrivate\n\t\t\t? `${this.baseUrl}/video/${videoId}/private/${expiration}`\n\t\t\t: `${this.baseUrl}/video/${videoId}/public`;\n\n\t\treturn apiRequest<VideoResult>({\n\t\t\tmethod: \"GET\",\n\t\t\turl,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Deletes a video and all associated resolutions and thumbnails.\n\t */\n\tasync deleteVideo(videoId: string): Promise<void> {\n\t\treturn apiRequest<void>({\n\t\t\tmethod: \"DELETE\",\n\t\t\turl: `${this.baseUrl}/video/${videoId}`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Deletes a single resolution for a video.\n\t */\n\tasync deleteResolution(resolutionId: string): Promise<void> {\n\t\treturn apiRequest<void>({\n\t\t\tmethod: \"DELETE\",\n\t\t\turl: `${this.baseUrl}/video/resolution/${resolutionId}`,\n\t\t\tapiKey: this.apiKey,\n\t\t\ttimeoutMs: this.timeoutMs,\n\t\t\tretries: this.retries,\n\t\t});\n\t}\n\n\t/**\n\t * Convenience method for server-side / script use cases.\n\t * Wraps createVideo, the storage PUT, and completeUpload into a single call.\n\t *\n\t * Not suitable for the browser proxy pattern — use createVideo + putVideoToStorage\n\t * from '@hyperserve/hyperserve-js/browser' + completeUpload separately for that flow.\n\t */\n\tasync uploadVideo(options: UploadVideoOptions): Promise<CompleteUploadResult> {\n\t\tconst { file, filename, resolutions, isPublic, thumbnailTimestampsSeconds, customMetadata } =\n\t\t\toptions;\n\n\t\tconst normalized = normalizeFile(file, filename, options.fileSizeBytes);\n\n\t\tconst upload = await this.createVideo({\n\t\t\tfilename,\n\t\t\tresolutions,\n\t\t\tisPublic,\n\t\t\t...(thumbnailTimestampsSeconds !== undefined && { thumbnailTimestampsSeconds }),\n\t\t\t...(customMetadata !== undefined && { customMetadata }),\n\t\t});\n\n\t\tawait putToStorage(upload.uploadUrl, upload.contentType, normalized.body, {\n\t\t\tcontentLength: normalized.size,\n\t\t});\n\n\t\treturn this.completeUpload(upload.id);\n\t}\n}\n","import type { VerifyWebhookSignatureOptions } from \"./types.js\";\n\nconst DEFAULT_TOLERANCE_MS = 300_000; // 5 minutes — matches server-side enforcement\n\n/**\n * Verifies the x-hyperserve-signature header on an incoming webhook request.\n *\n * The signature header has the format \"{timestampMs}.{hmac-sha256-hex}\", where the HMAC\n * is computed over \"{timestampMs}.{rawBody}\" using your webhook signing secret. This proves\n * both when the request was sent (replay protection) and that the body was not tampered with\n * (integrity). Any modification to the body or timestamp will invalidate the signature.\n *\n * Returns true if the signature is valid and the timestamp is within the tolerance window.\n * Returns false if the signature is invalid, the timestamp has expired, or the header is malformed.\n *\n * Uses the Web Crypto API for a constant-time HMAC comparison — safe on Node 18+, Bun, Deno,\n * Cloudflare Workers, Vercel Edge, and all other supported server environments.\n *\n * IMPORTANT: pass the raw request body string exactly as received. Do not parse and re-serialize\n * JSON — any whitespace difference will invalidate the signature.\n *\n * @example\n * import { verifyWebhookSignature } from '@hyperserve/hyperserve-js';\n *\n * // Express (use express.raw, not express.json, so you get the raw body)\n * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {\n * const isValid = await verifyWebhookSignature({\n * signature: req.headers['x-hyperserve-signature'] ?? '',\n * secret: process.env.HYPERSERVE_WEBHOOK_SECRET,\n * body: req.body.toString(),\n * });\n * if (!isValid) return res.status(401).end();\n * });\n *\n * // Next.js App Router\n * const body = await request.text();\n * const isValid = await verifyWebhookSignature({\n * signature: request.headers.get('x-hyperserve-signature') ?? '',\n * secret: process.env.HYPERSERVE_WEBHOOK_SECRET!,\n * body,\n * });\n */\nexport async function verifyWebhookSignature(\n\toptions: VerifyWebhookSignatureOptions,\n): Promise<boolean> {\n\tconst { signature, secret, body, toleranceMs = DEFAULT_TOLERANCE_MS } = options;\n\n\tconst dotIndex = signature.indexOf(\".\");\n\tif (dotIndex === -1) return false;\n\n\tconst timestampStr = signature.slice(0, dotIndex);\n\tconst receivedHex = signature.slice(dotIndex + 1);\n\n\t// Validate timestamp is a non-negative integer\n\tconst timestamp = Number(timestampStr);\n\tif (!Number.isInteger(timestamp) || timestamp < 0) return false;\n\n\t// Reject if timestamp is outside the tolerance window.\n\t// Math.abs handles future timestamps (clock skew or forged headers) — without it,\n\t// a negative difference would never exceed toleranceMs, accepting the signature forever.\n\tif (Math.abs(Date.now() - timestamp) > toleranceMs) return false;\n\n\tconst receivedBytes = hexToBytes(receivedHex);\n\tif (receivedBytes === null) return false;\n\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"verify\"],\n\t);\n\n\t// The signed message is \"{timestampMs}.{rawBody}\" — covers both freshness and integrity.\n\t// crypto.subtle.verify performs a constant-time comparison.\n\treturn crypto.subtle.verify(\n\t\t\"HMAC\",\n\t\tkey,\n\t\treceivedBytes,\n\t\tencoder.encode(`${timestampStr}.${body}`),\n\t);\n}\n\nfunction hexToBytes(hex: string): Uint8Array<ArrayBuffer> | null {\n\tif (hex.length === 0 || hex.length % 2 !== 0) return null;\n\tconst bytes = new Uint8Array(new ArrayBuffer(hex.length / 2));\n\tfor (let i = 0; i < hex.length; i += 2) {\n\t\tconst value = parseInt(hex.slice(i, i + 2), 16);\n\t\tif (Number.isNaN(value)) return null;\n\t\tbytes[i / 2] = value;\n\t}\n\treturn bytes;\n}\n"]}
@@ -26,18 +26,25 @@ var HyperserveTimeoutError = class extends HyperserveError {
26
26
  };
27
27
 
28
28
  // src/storage.ts
29
- async function putToStorage(uploadUrl, contentType, body, onProgress) {
29
+ async function putToStorage(uploadUrl, contentType, body, options = {}) {
30
+ const { contentLength, onProgress } = options;
30
31
  if (onProgress !== void 0 && typeof XMLHttpRequest !== "undefined" && !(body instanceof ReadableStream)) {
31
32
  return putWithXhr(uploadUrl, contentType, body, onProgress);
32
33
  }
33
- return putWithFetch(uploadUrl, contentType, body);
34
+ return putWithFetch(uploadUrl, contentType, body, contentLength);
34
35
  }
35
- function putWithFetch(uploadUrl, contentType, body) {
36
+ function putWithFetch(uploadUrl, contentType, body, contentLength) {
37
+ const isStream = body instanceof ReadableStream;
36
38
  return fetch(uploadUrl, {
37
39
  method: "PUT",
38
- headers: { "Content-Type": contentType },
40
+ headers: {
41
+ "Content-Type": contentType,
42
+ // A stream body would otherwise go out chunked, which S3-compatible
43
+ // storage rejects with 411. Blob bodies get Content-Length from fetch.
44
+ ...isStream && contentLength !== void 0 ? { "Content-Length": String(contentLength) } : {}
45
+ },
39
46
  // duplex is required for ReadableStream bodies in some runtimes (Node 18)
40
- ...body instanceof ReadableStream ? { duplex: "half" } : {},
47
+ ...isStream ? { duplex: "half" } : {},
41
48
  body
42
49
  }).then((response) => {
43
50
  if (!response.ok) {
@@ -86,7 +93,9 @@ async function putVideoToStorage(options) {
86
93
  throw new HyperserveUploadError(`Failed to read local file: ${uri}`);
87
94
  }
88
95
  const blob = await localResponse.blob();
89
- return putToStorage(uploadUrl, contentType, blob, onProgress);
96
+ return putToStorage(uploadUrl, contentType, blob, {
97
+ ...onProgress !== void 0 && { onProgress }
98
+ });
90
99
  }
91
100
 
92
101
  exports.HyperserveError = HyperserveError;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/storage.ts","../src/react-native.ts"],"names":[],"mappings":"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","file":"react-native.cjs","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tonProgress?: (percent: number) => void,\n): Promise<void> {\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n): Promise<void> {\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: { \"Content-Type\": contentType },\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(body instanceof ReadableStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","/**\n * React Native utilities for the Hyperserve SDK.\n *\n * Import from '@hyperserve/hyperserve-js/react-native' — this entry point contains no\n * API key logic and is safe to bundle into your React Native app.\n *\n * Usage:\n * import { putVideoToStorage } from '@hyperserve/hyperserve-js/react-native';\n *\n * // uri comes from expo-image-picker, react-native-image-picker, etc.\n * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress });\n *\n * Note: your backend (not this utility) should hold the API key and call\n * createVideo / completeUpload on your app's behalf.\n */\n\nimport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport type { PutVideoToStorageRNOptions, VideoResolution, VideoStatus } from \"./types.js\";\nexport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError };\n\nimport { putToStorage } from \"./storage.js\";\nimport type { PutVideoToStorageRNOptions } from \"./types.js\";\n\n/**\n * PUT a video file to the presigned storage URL obtained from your backend.\n * Accepts a local file URI from a React Native video/image picker.\n * No API key required — this call goes directly to storage, not to the Hyperserve API.\n *\n * @example\n * const { uploadUrl, contentType } = await fetch('https://your-api.com/create-upload', {\n * method: 'POST',\n * body: JSON.stringify({ filename: asset.fileName, fileSizeBytes: asset.fileSize }),\n * }).then(r => r.json());\n *\n * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress: (p) => setProgress(p) });\n *\n * await fetch('https://your-api.com/complete-upload', {\n * method: 'POST',\n * body: JSON.stringify({ videoId }),\n * });\n */\nexport async function putVideoToStorage(options: PutVideoToStorageRNOptions): Promise<void> {\n\tconst { uploadUrl, contentType, uri, onProgress } = options;\n\n\t// Fetch the local file URI to obtain a Blob. React Native's fetch implementation\n\t// supports file:// URIs, allowing us to read local files from the device.\n\tconst localResponse = await fetch(uri);\n\tif (!localResponse.ok) {\n\t\tthrow new HyperserveUploadError(`Failed to read local file: ${uri}`);\n\t}\n\tconst blob = await localResponse.blob();\n\n\treturn putToStorage(uploadUrl, contentType, blob, onProgress);\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/storage.ts","../src/react-native.ts"],"names":[],"mappings":"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","file":"react-native.cjs","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport interface PutToStorageOptions {\n\t/**\n\t * Byte length of the body. Only used for ReadableStream bodies, which are sent\n\t * with chunked transfer encoding unless Content-Length is set explicitly — and\n\t * S3-compatible storage rejects a chunked PUT with 411 MissingContentLength.\n\t * Blob bodies carry their own length, so this is ignored for them.\n\t */\n\tcontentLength?: number;\n\tonProgress?: (percent: number) => void;\n}\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\toptions: PutToStorageOptions = {},\n): Promise<void> {\n\tconst { contentLength, onProgress } = options;\n\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body, contentLength);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tcontentLength?: number,\n): Promise<void> {\n\tconst isStream = body instanceof ReadableStream;\n\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: {\n\t\t\t\"Content-Type\": contentType,\n\t\t\t// A stream body would otherwise go out chunked, which S3-compatible\n\t\t\t// storage rejects with 411. Blob bodies get Content-Length from fetch.\n\t\t\t...(isStream && contentLength !== undefined\n\t\t\t\t? { \"Content-Length\": String(contentLength) }\n\t\t\t\t: {}),\n\t\t},\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(isStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","/**\n * React Native utilities for the Hyperserve SDK.\n *\n * Import from '@hyperserve/hyperserve-js/react-native' — this entry point contains no\n * API key logic and is safe to bundle into your React Native app.\n *\n * Usage:\n * import { putVideoToStorage } from '@hyperserve/hyperserve-js/react-native';\n *\n * // uri comes from expo-image-picker, react-native-image-picker, etc.\n * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress });\n *\n * Note: your backend (not this utility) should hold the API key and call\n * createVideo / completeUpload on your app's behalf.\n */\n\nimport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport type { PutVideoToStorageRNOptions, VideoResolution, VideoStatus } from \"./types.js\";\nexport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError };\n\nimport { putToStorage } from \"./storage.js\";\nimport type { PutVideoToStorageRNOptions } from \"./types.js\";\n\n/**\n * PUT a video file to the presigned storage URL obtained from your backend.\n * Accepts a local file URI from a React Native video/image picker.\n * No API key required — this call goes directly to storage, not to the Hyperserve API.\n *\n * @example\n * const { uploadUrl, contentType } = await fetch('https://your-api.com/create-upload', {\n * method: 'POST',\n * body: JSON.stringify({ filename: asset.fileName }),\n * }).then(r => r.json());\n *\n * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress: (p) => setProgress(p) });\n *\n * await fetch('https://your-api.com/complete-upload', {\n * method: 'POST',\n * body: JSON.stringify({ videoId }),\n * });\n */\nexport async function putVideoToStorage(options: PutVideoToStorageRNOptions): Promise<void> {\n\tconst { uploadUrl, contentType, uri, onProgress } = options;\n\n\t// Fetch the local file URI to obtain a Blob. React Native's fetch implementation\n\t// supports file:// URIs, allowing us to read local files from the device.\n\tconst localResponse = await fetch(uri);\n\tif (!localResponse.ok) {\n\t\tthrow new HyperserveUploadError(`Failed to read local file: ${uri}`);\n\t}\n\tconst blob = await localResponse.blob();\n\n\treturn putToStorage(uploadUrl, contentType, blob, {\n\t\t...(onProgress !== undefined && { onProgress }),\n\t});\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { j as PutVideoToStorageRNOptions } from './errors-C89laaKB.cjs';
2
- export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploadError, k as VideoResolution, m as VideoStatus } from './errors-C89laaKB.cjs';
1
+ import { j as PutVideoToStorageRNOptions } from './errors-DJGPqrI5.cjs';
2
+ export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploadError, k as VideoResolution, m as VideoStatus } from './errors-DJGPqrI5.cjs';
3
3
 
4
4
  /**
5
5
  * React Native utilities for the Hyperserve SDK.
@@ -25,7 +25,7 @@ export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploa
25
25
  * @example
26
26
  * const { uploadUrl, contentType } = await fetch('https://your-api.com/create-upload', {
27
27
  * method: 'POST',
28
- * body: JSON.stringify({ filename: asset.fileName, fileSizeBytes: asset.fileSize }),
28
+ * body: JSON.stringify({ filename: asset.fileName }),
29
29
  * }).then(r => r.json());
30
30
  *
31
31
  * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress: (p) => setProgress(p) });
@@ -1,5 +1,5 @@
1
- import { j as PutVideoToStorageRNOptions } from './errors-C89laaKB.js';
2
- export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploadError, k as VideoResolution, m as VideoStatus } from './errors-C89laaKB.js';
1
+ import { j as PutVideoToStorageRNOptions } from './errors-DJGPqrI5.js';
2
+ export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploadError, k as VideoResolution, m as VideoStatus } from './errors-DJGPqrI5.js';
3
3
 
4
4
  /**
5
5
  * React Native utilities for the Hyperserve SDK.
@@ -25,7 +25,7 @@ export { e as HyperserveError, g as HyperserveTimeoutError, h as HyperserveUploa
25
25
  * @example
26
26
  * const { uploadUrl, contentType } = await fetch('https://your-api.com/create-upload', {
27
27
  * method: 'POST',
28
- * body: JSON.stringify({ filename: asset.fileName, fileSizeBytes: asset.fileSize }),
28
+ * body: JSON.stringify({ filename: asset.fileName }),
29
29
  * }).then(r => r.json());
30
30
  *
31
31
  * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress: (p) => setProgress(p) });
@@ -24,18 +24,25 @@ var HyperserveTimeoutError = class extends HyperserveError {
24
24
  };
25
25
 
26
26
  // src/storage.ts
27
- async function putToStorage(uploadUrl, contentType, body, onProgress) {
27
+ async function putToStorage(uploadUrl, contentType, body, options = {}) {
28
+ const { contentLength, onProgress } = options;
28
29
  if (onProgress !== void 0 && typeof XMLHttpRequest !== "undefined" && !(body instanceof ReadableStream)) {
29
30
  return putWithXhr(uploadUrl, contentType, body, onProgress);
30
31
  }
31
- return putWithFetch(uploadUrl, contentType, body);
32
+ return putWithFetch(uploadUrl, contentType, body, contentLength);
32
33
  }
33
- function putWithFetch(uploadUrl, contentType, body) {
34
+ function putWithFetch(uploadUrl, contentType, body, contentLength) {
35
+ const isStream = body instanceof ReadableStream;
34
36
  return fetch(uploadUrl, {
35
37
  method: "PUT",
36
- headers: { "Content-Type": contentType },
38
+ headers: {
39
+ "Content-Type": contentType,
40
+ // A stream body would otherwise go out chunked, which S3-compatible
41
+ // storage rejects with 411. Blob bodies get Content-Length from fetch.
42
+ ...isStream && contentLength !== void 0 ? { "Content-Length": String(contentLength) } : {}
43
+ },
37
44
  // duplex is required for ReadableStream bodies in some runtimes (Node 18)
38
- ...body instanceof ReadableStream ? { duplex: "half" } : {},
45
+ ...isStream ? { duplex: "half" } : {},
39
46
  body
40
47
  }).then((response) => {
41
48
  if (!response.ok) {
@@ -84,7 +91,9 @@ async function putVideoToStorage(options) {
84
91
  throw new HyperserveUploadError(`Failed to read local file: ${uri}`);
85
92
  }
86
93
  const blob = await localResponse.blob();
87
- return putToStorage(uploadUrl, contentType, blob, onProgress);
94
+ return putToStorage(uploadUrl, contentType, blob, {
95
+ ...onProgress !== void 0 && { onProgress }
96
+ });
88
97
  }
89
98
 
90
99
  export { HyperserveError, HyperserveTimeoutError, HyperserveUploadError, putVideoToStorage };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/storage.ts","../src/react-native.ts"],"names":[],"mappings":"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","file":"react-native.js","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tonProgress?: (percent: number) => void,\n): Promise<void> {\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n): Promise<void> {\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: { \"Content-Type\": contentType },\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(body instanceof ReadableStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","/**\n * React Native utilities for the Hyperserve SDK.\n *\n * Import from '@hyperserve/hyperserve-js/react-native' — this entry point contains no\n * API key logic and is safe to bundle into your React Native app.\n *\n * Usage:\n * import { putVideoToStorage } from '@hyperserve/hyperserve-js/react-native';\n *\n * // uri comes from expo-image-picker, react-native-image-picker, etc.\n * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress });\n *\n * Note: your backend (not this utility) should hold the API key and call\n * createVideo / completeUpload on your app's behalf.\n */\n\nimport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport type { PutVideoToStorageRNOptions, VideoResolution, VideoStatus } from \"./types.js\";\nexport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError };\n\nimport { putToStorage } from \"./storage.js\";\nimport type { PutVideoToStorageRNOptions } from \"./types.js\";\n\n/**\n * PUT a video file to the presigned storage URL obtained from your backend.\n * Accepts a local file URI from a React Native video/image picker.\n * No API key required — this call goes directly to storage, not to the Hyperserve API.\n *\n * @example\n * const { uploadUrl, contentType } = await fetch('https://your-api.com/create-upload', {\n * method: 'POST',\n * body: JSON.stringify({ filename: asset.fileName, fileSizeBytes: asset.fileSize }),\n * }).then(r => r.json());\n *\n * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress: (p) => setProgress(p) });\n *\n * await fetch('https://your-api.com/complete-upload', {\n * method: 'POST',\n * body: JSON.stringify({ videoId }),\n * });\n */\nexport async function putVideoToStorage(options: PutVideoToStorageRNOptions): Promise<void> {\n\tconst { uploadUrl, contentType, uri, onProgress } = options;\n\n\t// Fetch the local file URI to obtain a Blob. React Native's fetch implementation\n\t// supports file:// URIs, allowing us to read local files from the device.\n\tconst localResponse = await fetch(uri);\n\tif (!localResponse.ok) {\n\t\tthrow new HyperserveUploadError(`Failed to read local file: ${uri}`);\n\t}\n\tconst blob = await localResponse.blob();\n\n\treturn putToStorage(uploadUrl, contentType, blob, onProgress);\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/storage.ts","../src/react-native.ts"],"names":[],"mappings":"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","file":"react-native.js","sourcesContent":["/**\n * Base class for all Hyperserve SDK errors.\n */\nexport class HyperserveError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveError\";\n\t\t// Maintain proper prototype chain in transpiled environments\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 4xx response.\n * Typically indicates a validation problem: unsupported file format,\n * file too large, invalid resolutions, video not in expected state, etc.\n */\nexport class HyperserveValidationError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\tpublic readonly detail?: unknown,\n\t) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveValidationError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 404 response.\n */\nexport class HyperserveNotFoundError extends HyperserveError {\n\tconstructor(message = \"Resource not found\") {\n\t\tsuper(message, 404);\n\t\tthis.name = \"HyperserveNotFoundError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The Hyperserve API returned a 5xx response.\n */\nexport class HyperserveApiError extends HyperserveError {\n\tconstructor(message: string, statusCode: number) {\n\t\tsuper(message, statusCode);\n\t\tthis.name = \"HyperserveApiError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * The storage PUT request failed.\n */\nexport class HyperserveUploadError extends HyperserveError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly uploadStatus?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveUploadError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n\n/**\n * A request exceeded the configured timeoutMs.\n */\nexport class HyperserveTimeoutError extends HyperserveError {\n\tconstructor(message = \"Request timed out\") {\n\t\tsuper(message);\n\t\tthis.name = \"HyperserveTimeoutError\";\n\t\tObject.setPrototypeOf(this, new.target.prototype);\n\t}\n}\n","import { HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport interface PutToStorageOptions {\n\t/**\n\t * Byte length of the body. Only used for ReadableStream bodies, which are sent\n\t * with chunked transfer encoding unless Content-Length is set explicitly — and\n\t * S3-compatible storage rejects a chunked PUT with 411 MissingContentLength.\n\t * Blob bodies carry their own length, so this is ignored for them.\n\t */\n\tcontentLength?: number;\n\tonProgress?: (percent: number) => void;\n}\n\n/**\n * PUT a file to a presigned S3 URL.\n * Used internally by uploadVideo (server) and exported as putVideoToStorage (browser).\n *\n * When onProgress is provided, uses XMLHttpRequest for upload progress events.\n * Falls back to fetch otherwise.\n */\nexport async function putToStorage(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\toptions: PutToStorageOptions = {},\n): Promise<void> {\n\tconst { contentLength, onProgress } = options;\n\n\t// XHR is used for progress reporting but does not support ReadableStream bodies.\n\t// Fall back to fetch (no progress) when the body is a stream.\n\tif (\n\t\tonProgress !== undefined &&\n\t\ttypeof XMLHttpRequest !== \"undefined\" &&\n\t\t!(body instanceof ReadableStream)\n\t) {\n\t\treturn putWithXhr(uploadUrl, contentType, body, onProgress);\n\t}\n\treturn putWithFetch(uploadUrl, contentType, body, contentLength);\n}\n\nfunction putWithFetch(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob | ReadableStream,\n\tcontentLength?: number,\n): Promise<void> {\n\tconst isStream = body instanceof ReadableStream;\n\n\treturn fetch(uploadUrl, {\n\t\tmethod: \"PUT\",\n\t\theaders: {\n\t\t\t\"Content-Type\": contentType,\n\t\t\t// A stream body would otherwise go out chunked, which S3-compatible\n\t\t\t// storage rejects with 411. Blob bodies get Content-Length from fetch.\n\t\t\t...(isStream && contentLength !== undefined\n\t\t\t\t? { \"Content-Length\": String(contentLength) }\n\t\t\t\t: {}),\n\t\t},\n\t\t// duplex is required for ReadableStream bodies in some runtimes (Node 18)\n\t\t...(isStream ? { duplex: \"half\" } : {}),\n\t\tbody: body as BodyInit,\n\t}).then((response) => {\n\t\tif (!response.ok) {\n\t\t\tthrow new HyperserveUploadError(\n\t\t\t\t`Storage PUT failed with status ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction putWithXhr(\n\tuploadUrl: string,\n\tcontentType: string,\n\tbody: Blob,\n\tonProgress: (percent: number) => void,\n): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\n\t\txhr.open(\"PUT\", uploadUrl);\n\t\txhr.setRequestHeader(\"Content-Type\", contentType);\n\n\t\txhr.upload.addEventListener(\"progress\", (event) => {\n\t\t\tif (event.lengthComputable) {\n\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100));\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"load\", () => {\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tonProgress(100);\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew HyperserveUploadError(`Storage PUT failed with status ${xhr.status}`, xhr.status),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\txhr.addEventListener(\"timeout\", () => {\n\t\t\treject(new HyperserveTimeoutError(\"Storage PUT timed out\"));\n\t\t});\n\n\t\txhr.addEventListener(\"error\", () => {\n\t\t\treject(new HyperserveUploadError(\"Storage PUT failed due to a network error\"));\n\t\t});\n\n\t\txhr.send(body);\n\t});\n}\n","/**\n * React Native utilities for the Hyperserve SDK.\n *\n * Import from '@hyperserve/hyperserve-js/react-native' — this entry point contains no\n * API key logic and is safe to bundle into your React Native app.\n *\n * Usage:\n * import { putVideoToStorage } from '@hyperserve/hyperserve-js/react-native';\n *\n * // uri comes from expo-image-picker, react-native-image-picker, etc.\n * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress });\n *\n * Note: your backend (not this utility) should hold the API key and call\n * createVideo / completeUpload on your app's behalf.\n */\n\nimport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError } from \"./errors.js\";\n\nexport type { PutVideoToStorageRNOptions, VideoResolution, VideoStatus } from \"./types.js\";\nexport { HyperserveError, HyperserveTimeoutError, HyperserveUploadError };\n\nimport { putToStorage } from \"./storage.js\";\nimport type { PutVideoToStorageRNOptions } from \"./types.js\";\n\n/**\n * PUT a video file to the presigned storage URL obtained from your backend.\n * Accepts a local file URI from a React Native video/image picker.\n * No API key required — this call goes directly to storage, not to the Hyperserve API.\n *\n * @example\n * const { uploadUrl, contentType } = await fetch('https://your-api.com/create-upload', {\n * method: 'POST',\n * body: JSON.stringify({ filename: asset.fileName }),\n * }).then(r => r.json());\n *\n * await putVideoToStorage({ uploadUrl, contentType, uri: asset.uri, onProgress: (p) => setProgress(p) });\n *\n * await fetch('https://your-api.com/complete-upload', {\n * method: 'POST',\n * body: JSON.stringify({ videoId }),\n * });\n */\nexport async function putVideoToStorage(options: PutVideoToStorageRNOptions): Promise<void> {\n\tconst { uploadUrl, contentType, uri, onProgress } = options;\n\n\t// Fetch the local file URI to obtain a Blob. React Native's fetch implementation\n\t// supports file:// URIs, allowing us to read local files from the device.\n\tconst localResponse = await fetch(uri);\n\tif (!localResponse.ok) {\n\t\tthrow new HyperserveUploadError(`Failed to read local file: ${uri}`);\n\t}\n\tconst blob = await localResponse.blob();\n\n\treturn putToStorage(uploadUrl, contentType, blob, {\n\t\t...(onProgress !== undefined && { onProgress }),\n\t});\n}\n"]}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@hyperserve/hyperserve-js",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "TypeScript SDK for the Hyperserve video infrastructure API",
5
5
  "license": "MIT",
6
6
  "author": "Ryan Trann <admin@hyperserve.io>",
7
- "homepage": "https://hyperserve.io",
7
+ "homepage": "https://hyperserve.io?utm_source=npm&utm_medium=package_page&utm_campaign=hyperserve-js",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/hyper-serve/hyperserve-js"