[PR #16] [MERGED] feat: output.getDownloadUrl() + SDK User-Agent identification #27

Closed
opened 2026-10-31 22:03:57 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/comfy-typescript-sdk/pull/16
Author: @wei-hai
Created: 7/23/2026
Status: ✅ Merged
Merged: 7/23/2026
Merged by: @wei-hai

Base: main ← Head: feat/output-get-download-url


📝 Commits (4)

  • 1ae77a9 feat: output.getDownloadUrl() + SDK User-Agent identification
  • 005b499 harden: reject CR/LF in clientInfo to prevent header injection
  • d4a547c fix: release response body on getAssetContentUrl no-download paths; guard SDK_VERSION drift
  • f8869b4 Merge branch 'main' into feat/output-get-download-url

📊 Changes

8 files changed (+289 additions, -3 deletions)

View changed files

📝 README.md (+5 -0)
📝 src/low/index.ts (+1 -0)
📝 src/low/transport.test.ts (+89 -0)
📝 src/low/transport.ts (+121 -1)
📝 src/sdk/client.ts (+4 -0)
📝 src/sdk/outputs.test.ts (+36 -2)
📝 src/sdk/outputs.ts (+14 -0)
📝 test/support/stub-server.ts (+19 -0)

📄 Description

What

  • Output.getDownloadUrl(): Promise<{ url, expiresAt }> — a directly-fetchable URL for an output, so a caller (e.g. a Cloudflare/serverless worker) can hand the URL to a downstream consumer without streaming the bytes through itself.
    • Object-storage backends (Cloud/serverless): the self-authorizing signed URL (from the content endpoint's redirect Location) + its expiry.
    • Self-hosted (proxy serves inline): the content-endpoint URL, expiresAt: null.
    • Works on every backend and never throws (write-once/run-anywhere). Short-lived, self-authorizing bearer credential.
    • Per-output: a multi-output job resolves each output to its own distinct URL.
  • User-Agent on every request: comfy-sdk-typescript/{version} (node {version}), with an optional clientInfo appending app/{name}. Request metadata, not telemetry.

How

New getAssetContentUrl() fetches GET /assets/{id}/content with redirect: "manual" (the existing getAssetContent still uses follow), reads the Location without following it (bearer can't leak cross-origin), and parses GCS V4 X-Goog-Date/X-Goog-Expires for the expiry.

Tests

vitest run --coverage — 128 passed (incl. cloud-redirect, self-hosted-inline, multi-output-distinct-URLs, bearer-not-leaked, User-Agent + clientInfo). tsc + oxlint + oxfmt clean. Not added to OPERATION_IDS (a second low method over the same operation, not a new operationId).


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/comfy-typescript-sdk/pull/16 **Author:** [@wei-hai](https://github.com/wei-hai) **Created:** 7/23/2026 **Status:** ✅ Merged **Merged:** 7/23/2026 **Merged by:** [@wei-hai](https://github.com/wei-hai) **Base:** `main` ← **Head:** `feat/output-get-download-url` --- ### 📝 Commits (4) - [`1ae77a9`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/1ae77a98bc555428b50751518f8d310d187e792d) feat: output.getDownloadUrl() + SDK User-Agent identification - [`005b499`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/005b499edb2f040e6ad18323b1f110f6cec662d6) harden: reject CR/LF in clientInfo to prevent header injection - [`d4a547c`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/d4a547c2573ec0c02e755d506476905bcbbdb84a) fix: release response body on getAssetContentUrl no-download paths; guard SDK_VERSION drift - [`f8869b4`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/f8869b4ea082feb7daa126ec70f67a4a9a0170a2) Merge branch 'main' into feat/output-get-download-url ### 📊 Changes **8 files changed** (+289 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `README.md` (+5 -0) 📝 `src/low/index.ts` (+1 -0) 📝 `src/low/transport.test.ts` (+89 -0) 📝 `src/low/transport.ts` (+121 -1) 📝 `src/sdk/client.ts` (+4 -0) 📝 `src/sdk/outputs.test.ts` (+36 -2) 📝 `src/sdk/outputs.ts` (+14 -0) 📝 `test/support/stub-server.ts` (+19 -0) </details> ### 📄 Description ## What - **`Output.getDownloadUrl(): Promise<{ url, expiresAt }>`** — a directly-fetchable URL for an output, so a caller (e.g. a Cloudflare/serverless worker) can hand the URL to a downstream consumer without streaming the bytes through itself. - Object-storage backends (Cloud/serverless): the self-authorizing signed URL (from the content endpoint's redirect `Location`) + its expiry. - Self-hosted (proxy serves inline): the content-endpoint URL, `expiresAt: null`. - **Works on every backend and never throws** (write-once/run-anywhere). Short-lived, self-authorizing bearer credential. - Per-output: a multi-output job resolves each output to its own distinct URL. - **`User-Agent`** on every request: `comfy-sdk-typescript/{version} (node {version})`, with an optional `clientInfo` appending `app/{name}`. Request metadata, not telemetry. ## How New `getAssetContentUrl()` fetches `GET /assets/{id}/content` with `redirect: "manual"` (the existing `getAssetContent` still uses `follow`), reads the `Location` without following it (bearer can't leak cross-origin), and parses GCS V4 `X-Goog-Date`/`X-Goog-Expires` for the expiry. ## Tests `vitest run --coverage` — 128 passed (incl. cloud-redirect, self-hosted-inline, multi-output-distinct-URLs, bearer-not-leaked, User-Agent + `clientInfo`). `tsc` + `oxlint` + `oxfmt` clean. Not added to `OPERATION_IDS` (a second low method over the same operation, not a new operationId). --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 22:03:57 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/comfy-typescript-sdk#27
No description provided.