@@ -133,6 +133,10 @@ export class StorageTransport {
133133 reqOpts . projectId || ( await this . authClient . getProjectId ( ) ) ;
134134 }
135135
136+ if ( reqOpts . queryParameters && this . projectId ) {
137+ reqOpts . queryParameters . project = this . projectId ;
138+ }
139+
136140 // Header Construction
137141 const headers = this . #prepareHeaders( reqOpts ) ;
138142
@@ -145,7 +149,12 @@ export class StorageTransport {
145149 }
146150
147151 const urlString = reqOpts . url ?. toString ( ) || '' ;
148- const isAbsolute = urlString . startsWith ( 'http' ) ;
152+ const isAbsolute = this . #isValidUrl( urlString ) ;
153+
154+ // Determine the base URL for the request
155+ const requestUrl = isAbsolute
156+ ? urlString
157+ : new URL ( urlString , this . baseUrl ) . toString ( ) ;
149158
150159 try {
151160 const requestPromise = this . authClient . request < T > ( {
@@ -159,20 +168,22 @@ export class StorageTransport {
159168 } ,
160169 ...reqOpts ,
161170 data : reqOpts . body ,
162- params : isAbsolute ? undefined : reqOpts . queryParameters ,
171+ params : reqOpts . queryParameters ,
172+ paramsSerializer : this . #paramsSerializer,
163173 headers,
164- url : isAbsolute
165- ? urlString
166- : this . #buildUrl( reqOpts . url ?. toString ( ) , reqOpts . queryParameters ) ,
174+ url : requestUrl ,
167175 timeout : this . timeout ,
168176 // eslint-disable-next-line @typescript-eslint/no-explicit-any
169177 ...( { decompress : false } as any ) ,
170178 validateStatus : status => {
171- if ( urlString . includes ( 'uploadType=resumable' ) ) {
172- return ( status >= 200 && status < 300 ) || status === 308 ;
173- }
174- return status >= 200 && status < 300 ;
175- } , //status => status >= 200 && status < 300,
179+ const isResumable =
180+ reqOpts . queryParameters ?. uploadType === 'resumable' ||
181+ reqOpts . url ?. toString ( ) . includes ( 'uploadType=resumable' ) ;
182+
183+ return (
184+ ( status >= 200 && status < 300 ) || ( isResumable && status === 308 )
185+ ) ;
186+ } ,
176187 } ) ;
177188
178189 // Response Handling
@@ -218,25 +229,6 @@ export class StorageTransport {
218229 return finalHeaders ;
219230 }
220231
221- #buildUrl( pathUri = '' , queryParameters : StorageQueryParameters = { } ) : URL {
222- // Sync project ID in params if necessary
223- if (
224- 'project' in queryParameters &&
225- queryParameters . project !== this . projectId
226- ) {
227- queryParameters . project = this . projectId ;
228- }
229-
230- let url : URL ;
231- if ( this . #isValidUrl( pathUri ) ) {
232- url = new URL ( pathUri ) ;
233- } else {
234- url = new URL ( pathUri , this . baseUrl ) ; // Safer construction
235- }
236- url . search = this . #buildRequestQueryParams( queryParameters ) ;
237- return url ;
238- }
239-
240232 #isValidUrl( url : string ) : boolean {
241233 try {
242234 return Boolean ( new URL ( url ) ) ;
@@ -245,6 +237,26 @@ export class StorageTransport {
245237 }
246238 }
247239
240+ /**
241+ * Serializes query parameters into a string.
242+ * Specifically handles arrays by appending each value individually
243+ * to satisfy GCS "repeated key" requirements (e.g., for IAM permissions).
244+ */
245+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
246+ #paramsSerializer = ( params : Record < string , any > ) : string => {
247+ const searchParams = new URLSearchParams ( ) ;
248+ for ( const [ key , value ] of Object . entries ( params ) ) {
249+ if ( value === undefined ) continue ;
250+
251+ if ( Array . isArray ( value ) ) {
252+ value . forEach ( v => searchParams . append ( key , String ( v ) ) ) ;
253+ } else {
254+ searchParams . set ( key , String ( value ) ) ;
255+ }
256+ }
257+ return searchParams . toString ( ) ;
258+ } ;
259+
248260 #buildRequestHeaders( requestHeaders = { } ) {
249261 const headers = new Headers ( requestHeaders ) ;
250262 headers . set ( 'User-Agent' , this . #getUserAgentString( ) ) ;
@@ -255,21 +267,6 @@ export class StorageTransport {
255267 return headers ;
256268 }
257269
258- #buildRequestQueryParams( queryParameters : StorageQueryParameters ) : string {
259- const qp = new URLSearchParams ( ) ;
260- for ( const [ key , value ] of Object . entries ( queryParameters ) ) {
261- if ( value === undefined ) continue ;
262-
263- if ( Array . isArray ( value ) ) {
264- // This is the fix: append each item individually for repeated keys
265- value . forEach ( item => qp . append ( key , String ( item ) ) ) ;
266- } else {
267- qp . set ( key , String ( value ) ) ;
268- }
269- }
270- return qp . toString ( ) ;
271- }
272-
273270 #getUserAgentString( ) : string {
274271 const base = getUserAgentString ( ) ;
275272 return this . providedUserAgent ? `${ this . providedUserAgent } ${ base } ` : base ;
0 commit comments