From ef21cab99bd88872bf45373675f99941c0b8d55b Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Tue, 30 Jun 2026 08:38:04 +0000 Subject: [PATCH 1/7] docs(storage): add comprehensive JSDoc documentation to GetSignedUrlConfig interface properties --- handwritten/storage/src/file.ts | 87 +++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts index 27765d935a99..bb8b2488bc60 100644 --- a/handwritten/storage/src/file.ts +++ b/handwritten/storage/src/file.ts @@ -151,19 +151,98 @@ export interface SignedPostPolicyV4Output { export interface GetSignedUrlConfig extends Pick { + /** + * The date and time when the signed URL becomes active/usable. + * Maps to the `X-Goog-Date` query parameter in V4 signed URLs. + * If not specified, the current time is used by default. This is useful for pre-generating + * URLs that are only valid starting at a future point in time, or for pinning a static date + * to ensure consistent URL generation across identical calls (beneficial for caching). + */ + accessibleAt?: string | number | Date; + + /** + * The action/operation permitted by the signed URL. + * Supported values are: + * - `'read'`: Allows retrieving file data (HTTP GET method). + * - `'write'`: Allows uploading or overwriting file data (HTTP PUT method). + * - `'delete'`: Allows deleting the file (HTTP DELETE method). + * - `'resumable'`: Allows starting or resuming a resumable upload (HTTP POST method). + */ action: 'read' | 'write' | 'delete' | 'resumable'; - version?: 'v2' | 'v4'; - virtualHostedStyle?: boolean; + + /** + * The Custom Domain Name (CNAME) to use instead of the default GCS hostname. + * If you have configured a custom domain for your bucket, specifying this option + * will format the generated URL with your custom domain. + */ cname?: string; + + /** + * The MD5 digest of the content as a base64-encoded string. + * Required if you want to enforce integrity checking on upload. The client + * must send the exact matching `Content-MD5` header when using the signed URL. + */ contentMd5?: string; + + /** + * The expected MIME type (Content-Type) of the file. + * Useful when using `'write'` or `'resumable'` actions to enforce that the client upload + * carries a specific `Content-Type` header matching this value. + */ contentType?: string; + + /** + * The expiration date/time when the signed URL will no longer be valid. + * The value can be a JavaScript `Date` object, an epoch timestamp (number of milliseconds), + * or a date string. The maximum validity period is 7 days for V4 signed URLs. + */ expires: string | number | Date; - accessibleAt?: string | number | Date; + + /** + * Custom HTTP extension headers to be included as signed headers in the signed URL. + * Keys must be header names (e.g., `x-goog-meta-custom`) and values must be their expected values. + * The user must send these exact headers when making the HTTP request using the signed URL. + */ extensionHeaders?: http.OutgoingHttpHeaders; + + /** + * A convenience option to prompt the browser to save the downloaded file with a specific filename. + * This is a friendly shortcut that automatically formats the `responseDisposition` parameter + * under the hood to `attachment; filename=""`. + */ promptSaveAs?: string; + + /** + * Additional query parameters to include in the signature and append to the final signed URL. + */ + queryParams?: Query; + + /** + * Overrides the `Content-Disposition` response header returned by GCS on download. + * For example, setting this to `attachment; filename="new-name.png"` forces the browser + * to download the file with the specified filename. + */ responseDisposition?: string; + + /** + * Overrides the `Content-Type` response header returned by GCS on download. + * Useful if you want GCS to serve a file with a specific MIME type when accessed + * via this signed URL, overriding its stored metadata. + */ responseType?: string; - queryParams?: Query; + + /** + * The signature version to use when generating the signed URL. + * - `'v4'`: Google's recommended SHA256-based signing mechanism (default). + * - `'v2'`: The legacy SHA1-based signing mechanism. + */ + version?: 'v2' | 'v4'; + + /** + * If true, uses virtual hosted-style URLs (e.g. `https://bucket-name.storage.googleapis.com/object-name`) + * instead of path-style URLs (e.g. `https://storage.googleapis.com/bucket-name/object-name`). + */ + virtualHostedStyle?: boolean; } export interface GetFileMetadataOptions { From 75488cb9c20cec278fdb951a92fc6dd495df7e5d Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Tue, 30 Jun 2026 09:50:03 +0000 Subject: [PATCH 2/7] docs: clarify precedence and signature rules for storage file download options --- handwritten/storage/src/file.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts index bb8b2488bc60..be63a25a6e42 100644 --- a/handwritten/storage/src/file.ts +++ b/handwritten/storage/src/file.ts @@ -209,11 +209,15 @@ export interface GetSignedUrlConfig * A convenience option to prompt the browser to save the downloaded file with a specific filename. * This is a friendly shortcut that automatically formats the `responseDisposition` parameter * under the hood to `attachment; filename=""`. + * Note: If both `promptSaveAs` and `responseDisposition` are specified, `promptSaveAs` takes precedence + * and overrides `responseDisposition`. */ promptSaveAs?: string; /** - * Additional query parameters to include in the signature and append to the final signed URL. + * Additional query parameters to append to the final signed URL. + * Note: For V4 signed URLs, only query parameters starting with `x-goog-` are included in the signature + * (and thus protected against tampering). Other custom query parameters are appended to the URL but not signed. */ queryParams?: Query; @@ -221,6 +225,7 @@ export interface GetSignedUrlConfig * Overrides the `Content-Disposition` response header returned by GCS on download. * For example, setting this to `attachment; filename="new-name.png"` forces the browser * to download the file with the specified filename. + * Note: This option will be overridden if `promptSaveAs` is also specified. */ responseDisposition?: string; From 1db8522b8fb750beabacdc64873d0531883e6ed0 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Tue, 7 Jul 2026 07:09:02 +0000 Subject: [PATCH 3/7] refactor: clean up GetSignedUrlConfig interface documentation and property ordering --- handwritten/storage/src/file.ts | 101 ++++++++++++++------------------ 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts index be63a25a6e42..2124771f047d 100644 --- a/handwritten/storage/src/file.ts +++ b/handwritten/storage/src/file.ts @@ -148,106 +148,93 @@ export interface SignedPostPolicyV4Output { url: string; fields: PolicyFields; } - export interface GetSignedUrlConfig extends Pick { + /** - * The date and time when the signed URL becomes active/usable. - * Maps to the `X-Goog-Date` query parameter in V4 signed URLs. - * If not specified, the current time is used by default. This is useful for pre-generating - * URLs that are only valid starting at a future point in time, or for pinning a static date - * to ensure consistent URL generation across identical calls (beneficial for caching). + * The action to permit with the signed URL. + * - `'read'`: Allows downloading/viewing the file (HTTP GET). + * - `'write'`: Allows uploading/overwriting the file (HTTP PUT). + * - `'delete'`: Allows removing the file (HTTP DELETE). + * - `'resumable'`: Allows resumable uploads (HTTP POST). + * Note: When using `'resumable'`, the header `X-Goog-Resumable: start` must be sent in the client request. */ - accessibleAt?: string | number | Date; + action: 'read' | 'write' | 'delete' | 'resumable'; /** - * The action/operation permitted by the signed URL. - * Supported values are: - * - `'read'`: Allows retrieving file data (HTTP GET method). - * - `'write'`: Allows uploading or overwriting file data (HTTP PUT method). - * - `'delete'`: Allows deleting the file (HTTP DELETE method). - * - `'resumable'`: Allows starting or resuming a resumable upload (HTTP POST method). + * The signing version to use. + * @default 'v2' */ - action: 'read' | 'write' | 'delete' | 'resumable'; + version?: 'v2' | 'v4'; /** - * The Custom Domain Name (CNAME) to use instead of the default GCS hostname. - * If you have configured a custom domain for your bucket, specifying this option - * will format the generated URL with your custom domain. + * Use virtual hosted-style URLs (e.g., `https://mybucket.storage.googleapis.com/...`) + * instead of path-style URLs (e.g., `https://storage.googleapis.com/mybucket/...`). + * Virtual hosted-style URLs are generally preferred. + * @default false + */ + virtualHostedStyle?: boolean; + + /** + * The custom domain name (CNAME) mapped to this bucket (e.g., `"https://cdn.example.com"`). */ cname?: string; /** - * The MD5 digest of the content as a base64-encoded string. - * Required if you want to enforce integrity checking on upload. The client - * must send the exact matching `Content-MD5` header when using the signed URL. + * The MD5 digest value in base64. If provided, the client request **must** * include an identical `Content-MD5` HTTP header. + * If omitted, the client request must not include this header. */ contentMd5?: string; /** - * The expected MIME type (Content-Type) of the file. - * Useful when using `'write'` or `'resumable'` actions to enforce that the client upload - * carries a specific `Content-Type` header matching this value. + * The expected Content-Type of the file. If provided, the client request **must** * include an identical `Content-Type` HTTP header. + * If omitted, the client request must not include this header. */ contentType?: string; /** - * The expiration date/time when the signed URL will no longer be valid. - * The value can be a JavaScript `Date` object, an epoch timestamp (number of milliseconds), - * or a date string. The maximum validity period is 7 days for V4 signed URLs. + * The expiration timestamp for the link. Any provided value is passed directly to `new Date()`. + * * @throws {Error} If an expiration timestamp from the past is given. + * @note `'v4'` signing supports a maximum duration of 7 days (604,800 seconds) from the creation time. */ expires: string | number | Date; /** - * Custom HTTP extension headers to be included as signed headers in the signed URL. - * Keys must be header names (e.g., `x-goog-meta-custom`) and values must be their expected values. - * The user must send these exact headers when making the HTTP request using the signed URL. + * The timestamp when this link becomes usable. Any provided value is passed directly to `new Date()`. + * @default Date.now() + * @note Only supported/applicable when `version` is set to `'v4'`. */ - extensionHeaders?: http.OutgoingHttpHeaders; + accessibleAt?: string | number | Date; /** - * A convenience option to prompt the browser to save the downloaded file with a specific filename. - * This is a friendly shortcut that automatically formats the `responseDisposition` parameter - * under the hood to `attachment; filename=""`. - * Note: If both `promptSaveAs` and `responseDisposition` are specified, `promptSaveAs` takes precedence - * and overrides `responseDisposition`. + * Canonical extension headers that the server will validate against the client's request. + * * Requirements: + * - Header names must be prefixed with `x-goog-` and must be entirely lowercase. + * - Multi-valued headers passed as an array are converted into a comma-separated string (no spaces). + * The client must format them identically to prevent signature mismatches. */ - promptSaveAs?: string; + extensionHeaders?: http.OutgoingHttpHeaders; /** - * Additional query parameters to append to the final signed URL. - * Note: For V4 signed URLs, only query parameters starting with `x-goog-` are included in the signature - * (and thus protected against tampering). Other custom query parameters are appended to the URL but not signed. + * The filename to prompt the browser/user to save the file as upon access. + * Note: This option is ignored if `responseDisposition` is explicitly set. */ - queryParams?: Query; + promptSaveAs?: string; /** - * Overrides the `Content-Disposition` response header returned by GCS on download. - * For example, setting this to `attachment; filename="new-name.png"` forces the browser - * to download the file with the specified filename. - * Note: This option will be overridden if `promptSaveAs` is also specified. + * Maps to the `response-content-disposition` query parameter in the signed URL. */ responseDisposition?: string; /** - * Overrides the `Content-Type` response header returned by GCS on download. - * Useful if you want GCS to serve a file with a specific MIME type when accessed - * via this signed URL, overriding its stored metadata. + * Maps to the `response-content-type` query parameter in the signed URL. */ responseType?: string; /** - * The signature version to use when generating the signed URL. - * - `'v4'`: Google's recommended SHA256-based signing mechanism (default). - * - `'v2'`: The legacy SHA1-based signing mechanism. - */ - version?: 'v2' | 'v4'; - - /** - * If true, uses virtual hosted-style URLs (e.g. `https://bucket-name.storage.googleapis.com/object-name`) - * instead of path-style URLs (e.g. `https://storage.googleapis.com/bucket-name/object-name`). + * Additional query parameters to include natively in the generated signed URL. */ - virtualHostedStyle?: boolean; + queryParams?: Query; } export interface GetFileMetadataOptions { From 70ef8701a00ba8661ba935246e8725aa2a4eb7ab Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Thu, 9 Jul 2026 11:43:43 +0530 Subject: [PATCH 4/7] docs: update virtualHostedStyle JSDoc description docs: update virtualHostedStyle JSDoc description --- handwritten/storage/src/file.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts index 2124771f047d..8fc346c8aefa 100644 --- a/handwritten/storage/src/file.ts +++ b/handwritten/storage/src/file.ts @@ -168,8 +168,9 @@ export interface GetSignedUrlConfig version?: 'v2' | 'v4'; /** - * Use virtual hosted-style URLs (e.g., `https://mybucket.storage.googleapis.com/...`) - * instead of path-style URLs (e.g., `https://storage.googleapis.com/mybucket/...`). + * Determines the URL structure for accessing bucket resources. + * - `true`: Uses virtual hosted-style URLs (e.g., `https://mybucket.storage.googleapis.com/...`) + * - `false`: Uses path-style URLs (e.g., `https://storage.googleapis.com/mybucket/...`). * Virtual hosted-style URLs are generally preferred. * @default false */ From 728e56a99efaf6ad43832f19206571b74578c895 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Tue, 4 Aug 2026 12:21:21 +0000 Subject: [PATCH 5/7] docs: fix formatting and whitespace in GetSignedUrlConfig interface documentation --- handwritten/storage/src/file.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts index 8fc346c8aefa..ab96ed13f3d3 100644 --- a/handwritten/storage/src/file.ts +++ b/handwritten/storage/src/file.ts @@ -150,27 +150,26 @@ export interface SignedPostPolicyV4Output { } export interface GetSignedUrlConfig extends Pick { - /** * The action to permit with the signed URL. * - `'read'`: Allows downloading/viewing the file (HTTP GET). * - `'write'`: Allows uploading/overwriting the file (HTTP PUT). * - `'delete'`: Allows removing the file (HTTP DELETE). - * - `'resumable'`: Allows resumable uploads (HTTP POST). + * - `'resumable'`: Allows resumable uploads (HTTP POST). * Note: When using `'resumable'`, the header `X-Goog-Resumable: start` must be sent in the client request. */ action: 'read' | 'write' | 'delete' | 'resumable'; /** - * The signing version to use. + * The signing version to use. * @default 'v2' */ version?: 'v2' | 'v4'; /** * Determines the URL structure for accessing bucket resources. - * - `true`: Uses virtual hosted-style URLs (e.g., `https://mybucket.storage.googleapis.com/...`) - * - `false`: Uses path-style URLs (e.g., `https://storage.googleapis.com/mybucket/...`). + * - `true`: Uses virtual hosted-style URLs (e.g., `https://mybucket.storage.googleapis.com/...`) + * - `false`: Uses path-style URLs (e.g., `https://storage.googleapis.com/mybucket/...`). * Virtual hosted-style URLs are generally preferred. * @default false */ @@ -182,20 +181,22 @@ export interface GetSignedUrlConfig cname?: string; /** - * The MD5 digest value in base64. If provided, the client request **must** * include an identical `Content-MD5` HTTP header. + * The MD5 digest value in base64. If provided, the client request **must** + * include an identical `Content-MD5` HTTP header. * If omitted, the client request must not include this header. */ contentMd5?: string; /** - * The expected Content-Type of the file. If provided, the client request **must** * include an identical `Content-Type` HTTP header. + * The expected Content-Type of the file. If provided, the client request **must** + * include an identical `Content-Type` HTTP header. * If omitted, the client request must not include this header. */ contentType?: string; /** * The expiration timestamp for the link. Any provided value is passed directly to `new Date()`. - * * @throws {Error} If an expiration timestamp from the past is given. + * @throws {Error} If an expiration timestamp from the past is given. * @note `'v4'` signing supports a maximum duration of 7 days (604,800 seconds) from the creation time. */ expires: string | number | Date; @@ -209,9 +210,9 @@ export interface GetSignedUrlConfig /** * Canonical extension headers that the server will validate against the client's request. - * * Requirements: + * Requirements: * - Header names must be prefixed with `x-goog-` and must be entirely lowercase. - * - Multi-valued headers passed as an array are converted into a comma-separated string (no spaces). + * - Multi-valued headers passed as an array are converted into a comma-separated string (no spaces). * The client must format them identically to prevent signature mismatches. */ extensionHeaders?: http.OutgoingHttpHeaders; From 3aebb88dd8cc574624ababf02e704cd54c0bc746 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Tue, 4 Aug 2026 12:26:17 +0000 Subject: [PATCH 6/7] docs: update JSDoc note format for storage file configuration options --- handwritten/storage/src/file.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts index ab96ed13f3d3..b3d1713de701 100644 --- a/handwritten/storage/src/file.ts +++ b/handwritten/storage/src/file.ts @@ -197,14 +197,14 @@ export interface GetSignedUrlConfig /** * The expiration timestamp for the link. Any provided value is passed directly to `new Date()`. * @throws {Error} If an expiration timestamp from the past is given. - * @note `'v4'` signing supports a maximum duration of 7 days (604,800 seconds) from the creation time. + * Note: `'v4'` signing supports a maximum duration of 7 days (604,800 seconds) from the creation time. */ expires: string | number | Date; /** * The timestamp when this link becomes usable. Any provided value is passed directly to `new Date()`. * @default Date.now() - * @note Only supported/applicable when `version` is set to `'v4'`. + * Note: Only supported/applicable when `version` is set to `'v4'`. */ accessibleAt?: string | number | Date; From b001807a82616521680baf5429fec6af639aeb58 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Wed, 5 Aug 2026 05:49:25 +0000 Subject: [PATCH 7/7] fix(storage): pass signingEndpoint to URLSigner in file.getSignedUrl (#8982) Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/{{metadata['repo']['name']}}/issues) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) Fixes #8829 --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- handwritten/storage/src/file.ts | 1 + handwritten/storage/src/signer.ts | 2 +- handwritten/storage/test/file.ts | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts index 27765d935a99..d5aa40bf96e1 100644 --- a/handwritten/storage/src/file.ts +++ b/handwritten/storage/src/file.ts @@ -3236,6 +3236,7 @@ class File extends ServiceObject { contentMd5: cfg.contentMd5, contentType: cfg.contentType, host: cfg.host, + signingEndpoint: cfg.signingEndpoint, }; if (cfg.cname) { diff --git a/handwritten/storage/src/signer.ts b/handwritten/storage/src/signer.ts index a657cef6133d..ba5c17c04b75 100644 --- a/handwritten/storage/src/signer.ts +++ b/handwritten/storage/src/signer.ts @@ -25,7 +25,7 @@ type GoogleAuthLike = Pick; * @deprecated Use {@link GoogleAuth} instead */ export interface AuthClient { - sign(blobToSign: string): Promise; + sign(blobToSign: string, signingEndpoint?: string): Promise; getCredentials(): Promise<{ client_email?: string; }>; diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts index 26823995b907..d9f9185a16e8 100644 --- a/handwritten/storage/test/file.ts +++ b/handwritten/storage/test/file.ts @@ -3794,11 +3794,30 @@ describe('File', () => { contentType: config.contentType, cname: CNAME, virtualHostedStyle: true, + signingEndpoint: undefined, }); done(); }); }); + it('should pass signingEndpoint to URLSigner', done => { + const signingEndpoint = 'https://my-endpoint.com'; + const config = { + ...SIGNED_URL_CONFIG, + signingEndpoint, + }; + + file.getSignedUrl(config, (err: Error | null) => { + assert.ifError(err); + const getSignedUrlArgs = signerGetSignedUrlStub.getCall(0).args; + assert.strictEqual( + getSignedUrlArgs[0]['signingEndpoint'], + signingEndpoint + ); + done(); + }); + }); + it('should add "x-goog-resumable: start" header if action is resumable', done => { SIGNED_URL_CONFIG.action = 'resumable'; SIGNED_URL_CONFIG.extensionHeaders = {