@liqhtworks/sophon-sdk 0.1.3 → 0.1.5

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.
@@ -0,0 +1,6 @@
1
+ # This repository is generated from Liqhtworks/sophon-api at every
2
+ # release. Source-side changes (helpers, OpenAPI spec, generator
3
+ # config) belong in that repo, not here. Filing issues / PRs against
4
+ # this repo is fine — Liqhtworks engineers triage them and route the
5
+ # fix upstream as needed.
6
+ * @Liqhtworks/engineering
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something the SDK does that contradicts the docs or its types.
4
+ title: "[bug] "
5
+ labels: bug
6
+ ---
7
+
8
+ ## What happened
9
+
10
+ <!-- What you tried to do, what the SDK did instead. -->
11
+
12
+ ## Reproducer
13
+
14
+ ```ts
15
+ // Minimum code that reproduces. Strip secrets.
16
+ import { Configuration, JobsApi } from "@liqhtworks/sophon-sdk";
17
+ // …
18
+ ```
19
+
20
+ ## Environment
21
+
22
+ - `@liqhtworks/sophon-sdk` version: `0.1.x`
23
+ - Node / Bun / Deno version: `…`
24
+ - OS: `…`
25
+ - Running in: server-side / browser / edge runtime
26
+
27
+ ## Expected vs. actual
28
+
29
+ - Expected: `…`
30
+ - Actual: `…` (paste error message + stack trace inside a fenced block)
31
+
32
+ ## Anything else
33
+
34
+ <!-- Logs, X-Request-Id headers from the response, network captures, etc. -->
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: SOPHON API documentation
4
+ url: https://registry.scalar.com/@liqhtworks/apis/sophon-encoding-api/latest
5
+ about: Reference for every endpoint the SDK wraps.
6
+ - name: API + spec issues (sophon-api)
7
+ url: https://github.com/Liqhtworks/sophon-api/issues
8
+ about: For server-side bugs or OpenAPI questions, file there. SDK bugs stay here.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Feature request
3
+ about: A capability you want from the SDK that isn't shipped.
4
+ title: "[feature] "
5
+ labels: enhancement
6
+ ---
7
+
8
+ ## What would you like the SDK to do
9
+
10
+ <!-- Describe the customer-facing behavior, not the implementation. -->
11
+
12
+ ## What you tried instead
13
+
14
+ <!-- Working around it today, or which competitor / hand-rolled tool you'd
15
+ otherwise reach for. -->
16
+
17
+ ## Is this an SDK-level fit, or upstream?
18
+
19
+ - [ ] SDK-level (helper, type, ergonomic)
20
+ - [ ] Spec / API-level (would also need a sophon-api change)
package/CHANGELOG.md ADDED
@@ -0,0 +1,51 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@liqhtworks/sophon-sdk` are recorded here. The
4
+ package follows [SemVer](https://semver.org/) — see `README.md` for the
5
+ versioning policy applied during the v0.x pre-1.0 phase.
6
+
7
+ ## [0.1.4] — 2026-05-08
8
+
9
+ - `JobSource.upload(uploadId)` constructor — typed alternative to the
10
+ fragile `{ type: "upload", upload_id: "..." }` literal.
11
+ - Generated exports tightened so all helpers and discriminated-union
12
+ constructors are reachable at the top level.
13
+ - Build-test coverage extended over the new surface.
14
+
15
+ ## [0.1.3] — 2026-05-08
16
+
17
+ - `UploadsApiLike` helper interface narrowed to only the fields the
18
+ helpers actually read. The previous declaration typed `expires_at`
19
+ as `string`, but the generated `UploadsApi` returns `Date`, so the
20
+ helper compiled in isolation but not against the real client. Fixed.
21
+
22
+ ## [0.1.2] — 2026-04-23
23
+
24
+ - Per-route idempotency keys in `uploadFile`. Earlier releases reused
25
+ one key for both `createUpload` and `completeUpload`; SOPHON scopes
26
+ idempotency keys per route and rejected the second call with HTTP 409.
27
+ Now derives `${idem}/create` and `${idem}/complete` from the caller's
28
+ seed so retries still reach the server's idempotent path.
29
+ - Build-test fixtures regenerate as real ffprobe-able media via ffmpeg.
30
+
31
+ ## [0.1.0] — 2026-04-23
32
+
33
+ Initial public release.
34
+
35
+ - Generated transport (`Configuration`, `JobsApi`, `UploadsApi`,
36
+ `WebhooksApi`, `DownloadsApi`, `HealthApi`) from the SOPHON OpenAPI
37
+ spec.
38
+ - Hand-written helpers spliced on top of the generated client:
39
+ - `uploadFile` — chunked, concurrent, resumable upload with progress
40
+ reporting and bounded retry.
41
+ - `waitForJob` — typed terminal-state polling with backoff and
42
+ timeout.
43
+ - `verifyWebhookSignature` — constant-time HMAC-SHA256 verification
44
+ with a default replay window. Uses Web Crypto so it runs on Node
45
+ 18+ and modern browsers.
46
+ - Provenance signed via npm sigstore on every publish.
47
+
48
+ [0.1.4]: https://github.com/Liqhtworks/sophon-sdk-typescript/releases/tag/v0.1.4
49
+ [0.1.3]: https://github.com/Liqhtworks/sophon-sdk-typescript/releases/tag/v0.1.3
50
+ [0.1.2]: https://github.com/Liqhtworks/sophon-sdk-typescript/releases/tag/v0.1.2
51
+ [0.1.0]: https://github.com/Liqhtworks/sophon-sdk-typescript/releases/tag/v0.1.0
package/README.md CHANGED
@@ -1,164 +1,166 @@
1
- # @liqhtworks/sophon-sdk@0.1.0
1
+ # @liqhtworks/sophon-sdk
2
2
 
3
- A TypeScript SDK client for the api.liqhtworks.xyz API.
3
+ Official TypeScript SDK for the SOPHON Encoding API.
4
4
 
5
- ## Usage
5
+ This repository is generated from `Liqhtworks/sophon-api`. The curated
6
+ `README.md` and `examples/` directory are preserved across SDK regeneration.
6
7
 
7
- First, install the SDK from npm.
8
+ ## Install
8
9
 
9
10
  ```bash
10
- npm install @liqhtworks/sophon-sdk --save
11
+ npm install @liqhtworks/sophon-sdk
11
12
  ```
12
13
 
13
- Next, try it out.
14
+ Requires Node 18+ or a runtime with `fetch`, `Blob`, `AbortController`, and Web
15
+ Crypto.
14
16
 
17
+ ## Get an API key
18
+
19
+ 1. Sign in at <https://liqhtworks.xyz/account/general>.
20
+ 2. In **API keys**, create a key for your server-side integration.
21
+ 3. Copy the `xt_live_...` token when it is shown. It is only shown once.
22
+ 4. Store it as an environment variable:
23
+
24
+ ```bash
25
+ export SOPHON_API_KEY=xt_live_...
26
+ export SOPHON_BASE_URL=https://api.liqhtworks.xyz
27
+ ```
28
+
29
+ Keep API keys on the server. Do not ship them in browser bundles, mobile apps,
30
+ public repos, logs, or analytics events.
31
+
32
+ ## Quick Start
33
+
34
+ This is the smallest complete server-side flow: upload a local video, create an
35
+ encode job, wait for completion, and download the MP4 output.
15
36
 
16
37
  ```ts
17
38
  import {
18
39
  Configuration,
19
- DownloadsApi,
20
- } from '@liqhtworks/sophon-sdk';
21
- import type { DownloadRequest } from '@liqhtworks/sophon-sdk';
22
-
23
- async function example() {
24
- console.log("🚀 Testing @liqhtworks/sophon-sdk SDK...");
25
- const api = new DownloadsApi();
26
-
27
- const body = {
28
- // string | HMAC-signed download token encoding the object key and expiry.
29
- token: token_example,
30
- } satisfies DownloadRequest;
31
-
32
- try {
33
- const data = await api.download(body);
34
- console.log(data);
35
- } catch (error) {
36
- console.error(error);
37
- }
40
+ JobProfile,
41
+ JobSource,
42
+ JobStatus,
43
+ JobsApi,
44
+ UploadsApi,
45
+ uploadFile,
46
+ waitForJob,
47
+ } from "@liqhtworks/sophon-sdk";
48
+ import { Blob } from "node:buffer";
49
+ import { randomUUID } from "node:crypto";
50
+ import { readFile, writeFile } from "node:fs/promises";
51
+ import { basename } from "node:path";
52
+
53
+ const inputPath = process.argv[2] ?? "./source.mov";
54
+ const apiKey = process.env.SOPHON_API_KEY;
55
+ if (!apiKey) throw new Error("SOPHON_API_KEY is required");
56
+
57
+ const basePath = process.env.SOPHON_BASE_URL ?? "https://api.liqhtworks.xyz";
58
+ const config = new Configuration({
59
+ basePath,
60
+ accessToken: apiKey,
61
+ });
62
+
63
+ const uploads = new UploadsApi(config);
64
+ const jobs = new JobsApi(config);
65
+
66
+ const bytes = await readFile(inputPath);
67
+ const mimeType = inputPath.endsWith(".mov") ? "video/quicktime" : "video/mp4";
68
+ const source = new Blob([bytes], { type: mimeType });
69
+
70
+ const upload = await uploadFile({
71
+ api: uploads,
72
+ source,
73
+ fileName: basename(inputPath),
74
+ mimeType,
75
+ concurrency: 4,
76
+ onProgress: (p) => console.log(`${p.partsDone}/${p.partsTotal} parts`),
77
+ });
78
+
79
+ const job = await jobs.createJob({
80
+ idempotencyKey: randomUUID(),
81
+ createJobRequest: {
82
+ source: JobSource.upload(upload.uploadId),
83
+ profile: JobProfile.SOPHON_ESPRESSO,
84
+ },
85
+ });
86
+
87
+ const final = await waitForJob({
88
+ api: jobs,
89
+ jobId: job.id,
90
+ timeoutMs: 30 * 60 * 1000,
91
+ });
92
+ if (final.status !== JobStatus.COMPLETED) {
93
+ throw new Error(`job ended in ${final.status}`);
38
94
  }
39
95
 
40
- // Run the test
41
- example().catch(console.error);
96
+ const redirect = await fetch(`${basePath}/v1/jobs/${final.id}/output`, {
97
+ headers: { authorization: `Bearer ${apiKey}` },
98
+ redirect: "manual",
99
+ });
100
+ const location = redirect.headers.get("location");
101
+ if (!location) throw new Error("missing output redirect");
102
+
103
+ const download = await fetch(new URL(location, basePath));
104
+ await writeFile("sophon-output.mp4", Buffer.from(await download.arrayBuffer()));
105
+
106
+ console.log(`wrote sophon-output.mp4 from ${final.id}`);
42
107
  ```
43
108
 
109
+ For a runnable copy of this flow, see
110
+ [`examples/encode-file.mjs`](./examples/encode-file.mjs).
44
111
 
45
- ## Documentation
46
-
47
- ### API Endpoints
48
-
49
- All URIs are relative to *https://api.liqhtworks.xyz*
50
-
51
- | Class | Method | HTTP request | Description
52
- | ----- | ------ | ------------ | -------------
53
- *DownloadsApi* | [**download**](docs/DownloadsApi.md#download) | **GET** /v1/downloads/{token} | Download an output file via signed token
54
- *HealthApi* | [**healthz**](docs/HealthApi.md#healthz) | **GET** /healthz | Liveness probe
55
- *HealthApi* | [**readyz**](docs/HealthApi.md#readyz) | **GET** /readyz | Readiness probe
56
- *JobsApi* | [**cancelJob**](docs/JobsApi.md#canceljob) | **DELETE** /v1/jobs/{id} | Cancel a job
57
- *JobsApi* | [**createJob**](docs/JobsApi.md#createjoboperation) | **POST** /v1/jobs | Submit an encoding job
58
- *JobsApi* | [**getJob**](docs/JobsApi.md#getjob) | **GET** /v1/jobs/{id} | Get a single job by ID
59
- *JobsApi* | [**getJobOutput**](docs/JobsApi.md#getjoboutput) | **GET** /v1/jobs/{id}/output | Get the encoded output file
60
- *JobsApi* | [**listJobs**](docs/JobsApi.md#listjobs) | **GET** /v1/jobs | List jobs with cursor pagination
61
- *UploadsApi* | [**cancelUpload**](docs/UploadsApi.md#cancelupload) | **DELETE** /v1/uploads/{id} | Cancel an upload session
62
- *UploadsApi* | [**completeUpload**](docs/UploadsApi.md#completeupload) | **POST** /v1/uploads/{id}/complete | Finalize a chunked upload
63
- *UploadsApi* | [**createUpload**](docs/UploadsApi.md#createuploadoperation) | **POST** /v1/uploads | Initialize a chunked upload session
64
- *UploadsApi* | [**getUpload**](docs/UploadsApi.md#getupload) | **GET** /v1/uploads/{id} | Get upload session status
65
- *UploadsApi* | [**uploadPart**](docs/UploadsApi.md#uploadpart) | **PUT** /v1/uploads/{id}/parts/{part_number} | Upload a single chunk
66
- *WebhooksApi* | [**createWebhook**](docs/WebhooksApi.md#createwebhookoperation) | **POST** /v1/webhooks | Register a webhook endpoint
67
- *WebhooksApi* | [**deleteWebhook**](docs/WebhooksApi.md#deletewebhook) | **DELETE** /v1/webhooks/{id} | Soft-delete a webhook endpoint
68
- *WebhooksApi* | [**listWebhooks**](docs/WebhooksApi.md#listwebhooks) | **GET** /v1/webhooks | List active webhook endpoints
69
-
70
-
71
- ### Models
72
-
73
- - [CompleteUploadResponse](docs/CompleteUploadResponse.md)
74
- - [CreateJobOutputOptions](docs/CreateJobOutputOptions.md)
75
- - [CreateJobRequest](docs/CreateJobRequest.md)
76
- - [CreateUploadRequest](docs/CreateUploadRequest.md)
77
- - [CreateUploadResponse](docs/CreateUploadResponse.md)
78
- - [CreateWebhookRequest](docs/CreateWebhookRequest.md)
79
- - [ErrorBody](docs/ErrorBody.md)
80
- - [ErrorEnvelope](docs/ErrorEnvelope.md)
81
- - [JobOutputInfo](docs/JobOutputInfo.md)
82
- - [JobProfile](docs/JobProfile.md)
83
- - [JobProgress](docs/JobProgress.md)
84
- - [JobResponse](docs/JobResponse.md)
85
- - [JobSourceInfo](docs/JobSourceInfo.md)
86
- - [JobSourceType](docs/JobSourceType.md)
87
- - [JobStatus](docs/JobStatus.md)
88
- - [ListJobsResponse](docs/ListJobsResponse.md)
89
- - [OutputContainer](docs/OutputContainer.md)
90
- - [ReadyResponse](docs/ReadyResponse.md)
91
- - [UploadJobSource](docs/UploadJobSource.md)
92
- - [UploadPartResponse](docs/UploadPartResponse.md)
93
- - [UploadStatusResponse](docs/UploadStatusResponse.md)
94
- - [WebhookDeliveryPayload](docs/WebhookDeliveryPayload.md)
95
- - [WebhookListItem](docs/WebhookListItem.md)
96
- - [WebhookListResponse](docs/WebhookListResponse.md)
97
- - [WebhookResponse](docs/WebhookResponse.md)
98
-
99
- ### Authorization
100
-
101
-
102
- Authentication schemes defined for the API:
103
- <a id="bearerApiKey"></a>
104
- #### bearerApiKey
105
-
106
-
107
- - **Type**: HTTP Bearer Token authentication
108
- <a id="sessionCookie"></a>
109
- #### sessionCookie
110
-
111
-
112
- - **Type**: API key
113
- - **API key parameter name**: `sophon_api_session`
114
- - **Location**:
115
-
116
- ## About
117
-
118
- This TypeScript SDK client supports the [Fetch API](https://fetch.spec.whatwg.org/)
119
- and is automatically generated by the
120
- [OpenAPI Generator](https://openapi-generator.tech) project:
121
-
122
- - API version: `1.0.0`
123
- - Package version: `0.1.0`
124
- - Generator version: `7.21.0`
125
- - Build package: `org.openapitools.codegen.languages.TypeScriptFetchClientCodegen`
126
-
127
- The generated npm module supports the following:
128
-
129
- - Environments
130
- * Node.js
131
- * Webpack
132
- * Browserify
133
- - Language levels
134
- * ES5 - you must have a Promises/A+ library installed
135
- * ES6
136
- - Module systems
137
- * CommonJS
138
- * ES6 module system
139
-
140
- For more information, please visit [https://liqhtworks.xyz](https://liqhtworks.xyz)
112
+ For upload-only integration work, see
113
+ [`examples/upload-node-path.mjs`](./examples/upload-node-path.mjs).
141
114
 
142
- ## Development
115
+ ### Profile choice
143
116
 
144
- ### Building
117
+ Use `sophon-auto` for production unless you need deterministic encoder
118
+ settings. The quickstart uses `sophon-espresso` because it is the fastest
119
+ smoke-test profile and always produces a new encoded output.
145
120
 
146
- To build the TypeScript source code, you need to have Node.js and npm installed.
147
- After cloning the repository, navigate to the project directory and run:
121
+ ## Webhooks
122
+
123
+ Use `verifyWebhookSignature` with the raw request body before JSON parsing.
124
+
125
+ See [`examples/webhook-server`](./examples/webhook-server) for an Express
126
+ handler that preserves the raw body, verifies `X-Turbo-Signature-256`, and only
127
+ then parses JSON.
128
+
129
+ ## Helpers
130
+
131
+ | Helper | Purpose |
132
+ |---|---|
133
+ | `uploadFile` | Chunked upload orchestration with bounded concurrency, retries, resume, and progress callbacks. |
134
+ | `waitForJob` | Poll until terminal status with timeout and typed errors. |
135
+ | `verifyWebhookSignature` | Constant-time HMAC verification plus replay-window enforcement. |
136
+
137
+ ## API Docs
138
+
139
+ Generated endpoint/model docs live under [`docs/`](./docs).
140
+
141
+ ## Development
148
142
 
149
143
  ```bash
150
144
  npm install
151
145
  npm run build
152
146
  ```
153
147
 
154
- ### Publishing
148
+ ## Versioning
155
149
 
156
- Once you've built the package, you can publish it to npm:
150
+ `@liqhtworks/sophon-sdk` follows [SemVer](https://semver.org/), with one
151
+ pre-1.0 caveat: while we are at `v0.x`, **minor bumps may include
152
+ breaking changes**. Pin a tilde range until 1.0:
157
153
 
158
154
  ```bash
159
- npm publish
155
+ npm install @liqhtworks/sophon-sdk@~0.1
160
156
  ```
161
157
 
158
+ Patch releases (`0.1.x`) are always backward-compatible — they ship bug
159
+ fixes, helper-layer improvements, and additive types. Once we cut
160
+ `v1.0.0`, regular SemVer applies and breaking changes only land on
161
+ major bumps. See [`CHANGELOG.md`](./CHANGELOG.md) for the per-release
162
+ log.
163
+
162
164
  ## License
163
165
 
164
- [Proprietary]()
166
+ Proprietary. See [`LICENSE`](./LICENSE).
@@ -1,3 +1,4 @@
1
1
  export * from "./uploads";
2
2
  export * from "./jobs";
3
+ export * from "./sources";
3
4
  export * from "./webhooks";
@@ -1,3 +1,4 @@
1
1
  export * from "./uploads";
2
2
  export * from "./jobs";
3
+ export * from "./sources";
3
4
  export * from "./webhooks";
@@ -0,0 +1,8 @@
1
+ export interface UploadJobSourceLike {
2
+ type: any;
3
+ upload_id: string;
4
+ }
5
+ export declare function uploadJobSource(uploadId: string): UploadJobSourceLike;
6
+ export declare const JobSource: Readonly<{
7
+ upload: typeof uploadJobSource;
8
+ }>;
@@ -0,0 +1,6 @@
1
+ export function uploadJobSource(uploadId) {
2
+ return { type: "upload", upload_id: uploadId };
3
+ }
4
+ export const JobSource = Object.freeze({
5
+ upload: uploadJobSource,
6
+ });
@@ -1,3 +1,4 @@
1
1
  export * from "./uploads";
2
2
  export * from "./jobs";
3
+ export * from "./sources";
3
4
  export * from "./webhooks";
@@ -16,4 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./uploads"), exports);
18
18
  __exportStar(require("./jobs"), exports);
19
+ __exportStar(require("./sources"), exports);
19
20
  __exportStar(require("./webhooks"), exports);
@@ -0,0 +1,8 @@
1
+ export interface UploadJobSourceLike {
2
+ type: any;
3
+ upload_id: string;
4
+ }
5
+ export declare function uploadJobSource(uploadId: string): UploadJobSourceLike;
6
+ export declare const JobSource: Readonly<{
7
+ upload: typeof uploadJobSource;
8
+ }>;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JobSource = void 0;
4
+ exports.uploadJobSource = uploadJobSource;
5
+ function uploadJobSource(uploadId) {
6
+ return { type: "upload", upload_id: uploadId };
7
+ }
8
+ exports.JobSource = Object.freeze({
9
+ upload: uploadJobSource,
10
+ });
@@ -0,0 +1,70 @@
1
+ import {
2
+ Configuration,
3
+ JobProfile,
4
+ JobSource,
5
+ JobStatus,
6
+ JobsApi,
7
+ UploadsApi,
8
+ uploadFile,
9
+ waitForJob,
10
+ } from "@liqhtworks/sophon-sdk";
11
+ import { Blob } from "node:buffer";
12
+ import { randomUUID } from "node:crypto";
13
+ import { readFile, writeFile } from "node:fs/promises";
14
+ import { basename } from "node:path";
15
+
16
+ const inputPath = process.argv[2];
17
+ if (!inputPath) {
18
+ throw new Error("usage: node examples/encode-file.mjs /path/to/video.mov");
19
+ }
20
+
21
+ const apiKey = process.env.SOPHON_API_KEY;
22
+ if (!apiKey) throw new Error("SOPHON_API_KEY is required");
23
+
24
+ const basePath = process.env.SOPHON_BASE_URL ?? "https://api.liqhtworks.xyz";
25
+ const config = new Configuration({ basePath, accessToken: apiKey });
26
+ const uploads = new UploadsApi(config);
27
+ const jobs = new JobsApi(config);
28
+
29
+ const bytes = await readFile(inputPath);
30
+ const mimeType = inputPath.endsWith(".mov") ? "video/quicktime" : "video/mp4";
31
+ const upload = await uploadFile({
32
+ api: uploads,
33
+ source: new Blob([bytes], { type: mimeType }),
34
+ fileName: basename(inputPath),
35
+ mimeType,
36
+ concurrency: 4,
37
+ onProgress: (p) => console.log(`upload ${p.partsDone}/${p.partsTotal}`),
38
+ });
39
+
40
+ const job = await jobs.createJob({
41
+ idempotencyKey: randomUUID(),
42
+ createJobRequest: {
43
+ source: JobSource.upload(upload.uploadId),
44
+ profile: JobProfile.SOPHON_ESPRESSO,
45
+ },
46
+ });
47
+ console.log(`created ${job.id}`);
48
+
49
+ const final = await waitForJob({
50
+ api: jobs,
51
+ jobId: job.id,
52
+ timeoutMs: 30 * 60 * 1000,
53
+ onProgress: (j) => console.log(`job ${j.id}: ${j.status}`),
54
+ });
55
+ if (final.status !== JobStatus.COMPLETED) {
56
+ throw new Error(`job ended in ${final.status}`);
57
+ }
58
+
59
+ const redirect = await fetch(`${basePath}/v1/jobs/${final.id}/output`, {
60
+ headers: { authorization: `Bearer ${apiKey}` },
61
+ redirect: "manual",
62
+ });
63
+ const location = redirect.headers.get("location");
64
+ if (!location) throw new Error("missing output redirect");
65
+
66
+ const download = await fetch(new URL(location, basePath));
67
+ if (!download.ok) throw new Error(`download failed: ${download.status}`);
68
+
69
+ await writeFile("sophon-output.mp4", Buffer.from(await download.arrayBuffer()));
70
+ console.log("wrote sophon-output.mp4");
@@ -0,0 +1,32 @@
1
+ import {
2
+ Configuration,
3
+ UploadsApi,
4
+ uploadFile,
5
+ } from "@liqhtworks/sophon-sdk";
6
+ import { Blob } from "node:buffer";
7
+ import { basename } from "node:path";
8
+ import { readFile } from "node:fs/promises";
9
+
10
+ const path = process.argv[2];
11
+ if (!path) {
12
+ throw new Error("usage: node examples/upload-node-path.mjs /path/to/video.mov");
13
+ }
14
+
15
+ const config = new Configuration({
16
+ basePath: process.env.SOPHON_BASE_URL ?? "https://api.liqhtworks.xyz",
17
+ accessToken: process.env.SOPHON_API_KEY,
18
+ });
19
+
20
+ const bytes = await readFile(path);
21
+ const source = new Blob([bytes], { type: "video/quicktime" });
22
+ const uploads = new UploadsApi(config);
23
+
24
+ const upload = await uploadFile({
25
+ api: uploads,
26
+ source,
27
+ fileName: basename(path),
28
+ mimeType: "video/quicktime",
29
+ concurrency: 4,
30
+ });
31
+
32
+ console.log(upload.uploadId);
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "sophon-webhook-server-example",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "node src/server.mjs",
7
+ "check": "node --check src/server.mjs"
8
+ },
9
+ "dependencies": {
10
+ "@liqhtworks/sophon-sdk": "latest",
11
+ "express": "^4.19.2"
12
+ }
13
+ }
@@ -0,0 +1,51 @@
1
+ import express from "express";
2
+ import {
3
+ WebhookSignatureError,
4
+ verifyWebhookSignature,
5
+ } from "@liqhtworks/sophon-sdk";
6
+
7
+ const secret = process.env.SOPHON_WEBHOOK_SECRET;
8
+ if (!secret) {
9
+ throw new Error("SOPHON_WEBHOOK_SECRET is required");
10
+ }
11
+
12
+ const app = express();
13
+
14
+ app.post(
15
+ "/webhooks/sophon",
16
+ express.raw({ type: "application/json", limit: "2mb" }),
17
+ async (req, res) => {
18
+ if (!Buffer.isBuffer(req.body)) {
19
+ res.status(400).send("raw body required");
20
+ return;
21
+ }
22
+
23
+ try {
24
+ await verifyWebhookSignature({
25
+ rawBody: req.body,
26
+ signatureHeader: req.get("X-Turbo-Signature-256"),
27
+ timestampHeader: req.get("X-Turbo-Timestamp"),
28
+ secret,
29
+ });
30
+ } catch (err) {
31
+ if (err instanceof WebhookSignatureError) {
32
+ console.warn("rejected SOPHON webhook", { reason: err.reason });
33
+ }
34
+ res.status(401).send("invalid signature");
35
+ return;
36
+ }
37
+
38
+ const event = JSON.parse(req.body.toString("utf8"));
39
+ console.log("accepted SOPHON webhook", {
40
+ type: event.type,
41
+ id: event.id,
42
+ });
43
+
44
+ res.sendStatus(204);
45
+ },
46
+ );
47
+
48
+ const port = Number(process.env.PORT ?? 3000);
49
+ app.listen(port, () => {
50
+ console.log(`listening on :${port}`);
51
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liqhtworks/sophon-sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "OpenAPI client for @liqhtworks/sophon-sdk",
5
5
  "author": "OpenAPI-Generator",
6
6
  "repository": {
@@ -1,3 +1,4 @@
1
1
  export * from "./uploads";
2
2
  export * from "./jobs";
3
+ export * from "./sources";
3
4
  export * from "./webhooks";
@@ -0,0 +1,14 @@
1
+ export interface UploadJobSourceLike {
2
+ // `any` keeps the helper assignable to the generated string enum type while
3
+ // still centralizing the only valid runtime discriminator value.
4
+ type: any;
5
+ upload_id: string;
6
+ }
7
+
8
+ export function uploadJobSource(uploadId: string): UploadJobSourceLike {
9
+ return { type: "upload", upload_id: uploadId };
10
+ }
11
+
12
+ export const JobSource = Object.freeze({
13
+ upload: uploadJobSource,
14
+ });