Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a677be0
refactor: replace customEndpoint with getEndpoint method for dynamic …
thiyaguk09 Oct 14, 2026
c96363a
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Oct 14, 2026
50cc2e6
fix: avoid using PassThroughClient when explicit credentials or key f…
thiyaguk09 Oct 15, 2026
073360b
fix: support asynchronous endpoint resolution for universe domain in …
thiyaguk09 Oct 15, 2026
1183b9e
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Oct 15, 2026
90f185a
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Oct 19, 2026
5b2a854
feat: add deprecated customEndpoint and useAuthWithCustomEndpoint opt…
thiyaguk09 Oct 19, 2026
2452bca
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Oct 20, 2026
10daec0
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Oct 22, 2026
e65b97f
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Oct 23, 2026
5e17369
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Oct 26, 2026
0fc2345
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Oct 30, 2026
026c174
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Oct 30, 2026
aadb18f
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Nov 2, 2026
9eece36
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Nov 2, 2026
70f65b7
fix: support dynamic storage endpoints and universe domain resolution…
thiyaguk09 Nov 2, 2026
370ce32
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Nov 2, 2026
6b9b632
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Nov 3, 2026
c2af206
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Nov 3, 2026
4b8eadf
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Nov 4, 2026
d448a49
Merge branch 'main' into 7828-refactor-revamp-apiendpoint
thiyaguk09 Nov 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 133 additions & 92 deletions handwritten/storage/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2027,31 +2027,41 @@ class File extends ServiceObject<File, FileMetadata> {
retryOptions.autoRetry = false;
}

resumableUpload.createURI(
{
authClient: this.storage.storageTransport.authClient,
apiEndpoint: this.storage.apiEndpoint,
bucket: this.bucket.name,
file: this.name,
generation: this.generation,
key: this.encryptionKey === null ? undefined : this.encryptionKey,
kmsKeyName: this.kmsKeyName,
metadata: options.metadata,
offset: options.offset,
origin: options.origin,
predefinedAcl: options.predefinedAcl,
private: options.private,
public: options.public,
userProject: options.userProject || this.userProject,
retryOptions: retryOptions,
params: options?.preconditionOpts || this.instancePreconditionOpts,
universeDomain: this.bucket.storage.universeDomain,
useAuthWithCustomEndpoint: this.storage.useAuthWithCustomEndpoint,
[GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
},
callback!,
);
this.storage.retryOptions.autoRetry = this.instanceRetryValue;
void (async () => {
try {
const apiEndpoint =
(typeof this.storage.getEndpoint === 'function'
? await this.storage.getEndpoint()
: undefined) || this.storage['apiEndpoint'];
resumableUpload.createURI(
{
authClient: this.storage.storageTransport.authClient,
apiEndpoint,
bucket: this.bucket.name,
file: this.name,
generation: this.generation,
key: this.encryptionKey === null ? undefined : this.encryptionKey,
kmsKeyName: this.kmsKeyName,
metadata: options.metadata,
offset: options.offset,
origin: options.origin,
predefinedAcl: options.predefinedAcl,
private: options.private,
public: options.public,
userProject: options.userProject || this.userProject,
retryOptions: {...retryOptions},
params: options?.preconditionOpts || this.instancePreconditionOpts,
universeDomain: this.bucket.storage.universeDomain,
[GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
},
callback!,
);
} catch (err) {
callback!(err as Error);
} finally {
this.storage.retryOptions.autoRetry = this.instanceRetryValue;
}
})();
}

/**
Expand Down Expand Up @@ -2437,7 +2447,7 @@ class File extends ServiceObject<File, FileMetadata> {
);

if (options.resumable === false) {
this.startSimpleUpload_(
void this.startSimpleUpload_(
fileWriteStream,
options as CreateWriteStreamOptionsInternal,
);
Expand Down Expand Up @@ -3272,14 +3282,26 @@ class File extends ServiceObject<File, FileMetadata> {
options.signingEndpoint,
);
const signatureHex = Buffer.from(signature, 'base64').toString('hex');
const universe = this.parent.storage.universeDomain;
fields['policy'] = policyBase64;
fields['x-goog-signature'] = signatureHex;

let url: string;

if (this.storage.customEndpoint) {
url = this.storage.apiEndpoint;
const EMULATOR_HOST = process.env.STORAGE_EMULATOR_HOST;
const endpoint =
(typeof this.storage.getEndpoint === 'function'
? await this.storage.getEndpoint()
: undefined) || this.storage['apiEndpoint'];
const universe =
this.parent.storage.universeDomain ||
this.storage.universeDomain ||
'googleapis.com';
const defaultEndpoint = `https://storage.${universe}`;

if (endpoint !== defaultEndpoint && typeof EMULATOR_HOST === 'string') {
Comment thread
thiyaguk09 marked this conversation as resolved.
Resolved
url = `${endpoint}/${this.bucket.name}`;
} else if (endpoint !== defaultEndpoint) {
url = endpoint;
} else if (options.virtualHostedStyle) {
url = `https://${this.bucket.name}.storage.${universe}/`;
} else if (options.bucketBoundHostname) {
Expand Down Expand Up @@ -3610,11 +3632,6 @@ class File extends ServiceObject<File, FileMetadata> {
undefined,
callback,
);
const baseUrl = this.storage.apiEndpoint.startsWith('http')
? this.storage.apiEndpoint
: `https://${this.storage.apiEndpoint}`;

const url = `${baseUrl}/storage/v1/b/${this.bucket.name}/o/${encodeURIComponent(this.name)}`;

const gaxios = new Gaxios();
const storageInterceptors = this.storage?.interceptors || [];
Expand All @@ -3627,6 +3644,16 @@ class File extends ServiceObject<File, FileMetadata> {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
(async () => {
try {
const endpoint =
(typeof this.storage.getEndpoint === 'function'
? await this.storage.getEndpoint()
: undefined) || this.storage['apiEndpoint'];
const baseUrl = endpoint.startsWith('http')
? endpoint
: `https://${endpoint}`;

const url = `${baseUrl}/storage/v1/b/${this.bucket.name}/o/${encodeURIComponent(this.name)}`;

await gaxios.request({
method: 'GET',
url,
Expand Down Expand Up @@ -3823,7 +3850,7 @@ class File extends ServiceObject<File, FileMetadata> {
* ```
*/
publicUrl(): string {
return `${this.storage.apiEndpoint}/${
return `${this.storage['apiEndpoint']}/${
this.bucket.name
}/${encodeURIComponent(this.name)}`;
}
Expand Down Expand Up @@ -4755,59 +4782,64 @@ class File extends ServiceObject<File, FileMetadata> {
) {
retryOptions.autoRetry = false;
}
const cfg = {
authClient: this.storage.storageTransport.authClient,
apiEndpoint: this.storage.apiEndpoint,
bucket: this.bucket.name,
file: this.name,
generation: this.generation,
isPartialUpload: options.isPartialUpload,
key: this.encryptionKey === null ? undefined : this.encryptionKey,
kmsKeyName: this.kmsKeyName,
metadata: options.metadata,
offset: options.offset,
predefinedAcl: options.predefinedAcl,
private: options.private,
public: options.public,
uri: options.uri,
userProject: options.userProject || this.userProject,
retryOptions: {...retryOptions},
params: options?.preconditionOpts || this.instancePreconditionOpts,
chunkSize: options?.chunkSize,
highWaterMark: options?.highWaterMark,
universeDomain: this.bucket.storage.universeDomain,
useAuthWithCustomEndpoint: this.storage.useAuthWithCustomEndpoint,
[GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
};
const uploadRetryOptions = {...retryOptions};
this.storage.retryOptions.autoRetry = this.instanceRetryValue;

let uploadStream: resumableUpload.Upload;
void (async () => {
let uploadStream: resumableUpload.Upload;

try {
uploadStream = resumableUpload.upload(cfg);
} catch (error) {
dup.destroy(error as Error);
this.storage.retryOptions.autoRetry = this.instanceRetryValue;
return;
}
try {
const apiEndpoint =
(typeof this.storage.getEndpoint === 'function'
? await this.storage.getEndpoint()
: undefined) || this.storage['apiEndpoint'];
const cfg = {
authClient: this.storage.storageTransport.authClient,
apiEndpoint,
bucket: this.bucket.name,
file: this.name,
generation: this.generation,
isPartialUpload: options.isPartialUpload,
key: this.encryptionKey === null ? undefined : this.encryptionKey,
kmsKeyName: this.kmsKeyName,
metadata: options.metadata,
offset: options.offset,
predefinedAcl: options.predefinedAcl,
private: options.private,
public: options.public,
uri: options.uri,
userProject: options.userProject || this.userProject,
retryOptions: uploadRetryOptions,
params: options?.preconditionOpts || this.instancePreconditionOpts,
chunkSize: options?.chunkSize,
highWaterMark: options?.highWaterMark,
universeDomain: this.bucket.storage.universeDomain,
[GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
};
uploadStream = resumableUpload.upload(cfg);
} catch (error) {
dup.destroy(error as Error);
return;
}

uploadStream
.on('response', resp => {
dup.emit('response', resp);
})
.on('uri', uri => {
dup.emit('uri', uri);
})
.on('metadata', metadata => {
this.metadata = metadata;
dup.emit('metadata');
})
.on('finish', () => {
dup.emit('complete');
})
.on('progress', evt => dup.emit('progress', evt));
uploadStream
.on('response', resp => {
dup.emit('response', resp);
})
.on('uri', uri => {
dup.emit('uri', uri);
})
.on('metadata', metadata => {
this.metadata = metadata;
dup.emit('metadata');
})
.on('finish', () => {
dup.emit('complete');
})
.on('progress', evt => dup.emit('progress', evt));

dup.setWritable(uploadStream);
this.storage.retryOptions.autoRetry = this.instanceRetryValue;
dup.setWritable(uploadStream);
})();
}

/**
Expand All @@ -4820,13 +4852,26 @@ class File extends ServiceObject<File, FileMetadata> {
*
* @private
*/
startSimpleUpload_(
async startSimpleUpload_(
dup: Duplexify,
options: CreateWriteStreamOptionsInternal = {},
): void {
): Promise<void> {
options.metadata ??= {};

const apiEndpoint = this.storage.apiEndpoint;
const writeStream = new ProgressStream();
writeStream.on('progress', evt => dup.emit('progress', evt));
dup.setWritable(writeStream);

let apiEndpoint: string;
try {
apiEndpoint =
(typeof this.storage.getEndpoint === 'function'
? await this.storage.getEndpoint()
: undefined) || this.storage['apiEndpoint'];
} catch (err) {
dup.destroy(err as Error);
return;
}
const bucketName = this.bucket.name;
const url = `${apiEndpoint}/upload/storage/v1/b/${bucketName}/o`;

Expand Down Expand Up @@ -4873,10 +4918,6 @@ class File extends ServiceObject<File, FileMetadata> {
options.preconditionOpts,
);

const writeStream = new ProgressStream();
writeStream.on('progress', evt => dup.emit('progress', evt));
dup.setWritable(writeStream);

reqOpts.multipart = [
{
headers: new Headers({'Content-Type': 'application/json'}),
Expand Down Expand Up @@ -4914,7 +4955,7 @@ class File extends ServiceObject<File, FileMetadata> {
dup.emit('response', resp);
dup.emit('complete');
})
.catch(err => dup.destroy(err));
?.catch(err => dup.destroy(err));
}

disableAutoRetryConditionallyIdempotent_(
Expand Down
2 changes: 2 additions & 0 deletions handwritten/storage/src/nodejs-common/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@ export interface MakeAuthenticatedRequestFactoryConfig extends Omit<

/**
* If true, just return the provided request options. Default: false.
* @deprecated
*/
customEndpoint?: boolean;

/**
* If true, will authenticate when using a custom endpoint. Default: false.
* @deprecated
*/
useAuthWithCustomEndpoint?: boolean;

Expand Down
29 changes: 1 addition & 28 deletions handwritten/storage/src/resumable-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
GaxiosResponse,
GaxiosError,
} from 'gaxios';
import * as gaxios from 'gaxios';
import {
DEFAULT_UNIVERSE,
GoogleAuth,
Expand Down Expand Up @@ -247,6 +246,7 @@ export interface UploadConfig extends Pick<WritableOptions, 'highWaterMark'> {

/**
* Controls whether or not to use authentication when using a custom endpoint.
* @deprecated
*/
useAuthWithCustomEndpoint?: boolean;

Expand Down Expand Up @@ -385,33 +385,6 @@ export class Upload extends Writable {
this.apiEndpoint = `https://storage.${universe}`;
if (cfg.apiEndpoint && cfg.apiEndpoint !== this.apiEndpoint) {
this.apiEndpoint = this.sanitizeEndpoint(cfg.apiEndpoint);

const hostname = new URL(this.apiEndpoint).hostname;

// check if it is a domain of a known universe
const isDomain = hostname === universe;
const isDefaultUniverseDomain = hostname === DEFAULT_UNIVERSE;

// check if it is a subdomain of a known universe
// by checking a last (universe's length + 1) of a hostname
const isSubDomainOfUniverse =
hostname.slice(-(universe.length + 1)) === `.${universe}`;
const isSubDomainOfDefaultUniverse =
hostname.slice(-(DEFAULT_UNIVERSE.length + 1)) ===
`.${DEFAULT_UNIVERSE}`;

if (
!isDomain &&
!isDefaultUniverseDomain &&
!isSubDomainOfUniverse &&
!isSubDomainOfDefaultUniverse
) {
// a custom, non-universe domain,
// use gaxios only if useAuthWithCustomEndpoint is not true
if (cfg.useAuthWithCustomEndpoint !== true) {
this.authClient = gaxios;
}
}
}

this.baseURI = `${this.apiEndpoint}/upload/storage/v1/b`;
Expand Down
Loading
Loading