@@ -145,7 +145,12 @@ export class StorageTransport {
145145 }
146146
147147 const urlString = reqOpts . url ?. toString ( ) || '' ;
148- const isAbsolute = urlString . startsWith ( 'http' ) ;
148+ const isAbsolute = this . #isValidUrl( urlString ) ;
149+
150+ // Determine the base URL for the request
151+ const requestUrl = isAbsolute
152+ ? urlString
153+ : new URL ( urlString , this . baseUrl ) . toString ( ) ;
149154
150155 try {
151156 const requestPromise = this . authClient . request < T > ( {
@@ -159,20 +164,22 @@ export class StorageTransport {
159164 } ,
160165 ...reqOpts ,
161166 data : reqOpts . body ,
162- params : isAbsolute ? undefined : reqOpts . queryParameters ,
167+ params : reqOpts . queryParameters ,
168+ paramsSerializer : this . #paramsSerializer,
163169 headers,
164- url : isAbsolute
165- ? urlString
166- : this . #buildUrl( reqOpts . url ?. toString ( ) , reqOpts . queryParameters ) ,
170+ url : requestUrl ,
167171 timeout : this . timeout ,
168172 // eslint-disable-next-line @typescript-eslint/no-explicit-any
169173 ...( { decompress : false } as any ) ,
170174 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,
175+ const isResumable =
176+ reqOpts . queryParameters ?. uploadType === 'resumable' ||
177+ reqOpts . url ?. toString ( ) . includes ( 'uploadType=resumable' ) ;
178+
179+ return (
180+ ( status >= 200 && status < 300 ) || ( isResumable && status === 308 )
181+ ) ;
182+ } ,
176183 } ) ;
177184
178185 // Response Handling
@@ -218,25 +225,6 @@ export class StorageTransport {
218225 return finalHeaders ;
219226 }
220227
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-
240228 #isValidUrl( url : string ) : boolean {
241229 try {
242230 return Boolean ( new URL ( url ) ) ;
@@ -245,6 +233,26 @@ export class StorageTransport {
245233 }
246234 }
247235
236+ /**
237+ * Serializes query parameters into a string.
238+ * Specifically handles arrays by appending each value individually
239+ * to satisfy GCS "repeated key" requirements (e.g., for IAM permissions).
240+ */
241+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
242+ #paramsSerializer = ( params : Record < string , any > ) : string => {
243+ const searchParams = new URLSearchParams ( ) ;
244+ for ( const [ key , value ] of Object . entries ( params ) ) {
245+ if ( value === undefined ) continue ;
246+
247+ if ( Array . isArray ( value ) ) {
248+ value . forEach ( v => searchParams . append ( key , String ( v ) ) ) ;
249+ } else {
250+ searchParams . set ( key , String ( value ) ) ;
251+ }
252+ }
253+ return searchParams . toString ( ) ;
254+ } ;
255+
248256 #buildRequestHeaders( requestHeaders = { } ) {
249257 const headers = new Headers ( requestHeaders ) ;
250258 headers . set ( 'User-Agent' , this . #getUserAgentString( ) ) ;
@@ -255,21 +263,6 @@ export class StorageTransport {
255263 return headers ;
256264 }
257265
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-
273266 #getUserAgentString( ) : string {
274267 const base = getUserAgentString ( ) ;
275268 return this . providedUserAgent ? `${ this . providedUserAgent } ${ base } ` : base ;
0 commit comments