Skip to content

Commit 14a4c5c

Browse files
committed
fix(functions): avoid mutating httpsCallable options when normalizing timeout
1 parent bf92962 commit 14a4c5c

2 files changed

Lines changed: 40 additions & 25 deletions

File tree

packages/functions/__tests__/httpsCallable-timeout-native.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,18 @@ describe('httpsCallable timeout units on native platforms', function () {
5959
expect(httpsCallableNative).toHaveBeenCalledTimes(1);
6060
expect(timeoutFromNativeCall(httpsCallableNative)).toBe(30);
6161
});
62+
63+
it('does not mutate a reused options object on native platforms', async function () {
64+
const sharedOptions = { timeout: 30000 };
65+
const runnerA = firebase.app().functions().httpsCallable('exampleA', sharedOptions);
66+
const runnerB = firebase.app().functions().httpsCallable('exampleB', sharedOptions);
67+
68+
await runnerA();
69+
await runnerB();
70+
71+
expect(sharedOptions.timeout).toBe(30000);
72+
expect(httpsCallableNative).toHaveBeenCalledTimes(2);
73+
expect((httpsCallableNative.mock.calls[0]?.[4] as { timeout?: number }).timeout).toBe(30);
74+
expect((httpsCallableNative.mock.calls[1]?.[4] as { timeout?: number }).timeout).toBe(30);
75+
});
6276
});

packages/functions/lib/namespaced.ts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,24 @@ const statics = {
8383
HttpsErrorCode,
8484
};
8585

86+
function normalizeHttpsCallableTimeoutOptions(
87+
options: HttpsCallableOptions,
88+
): HttpsCallableOptions {
89+
if (!options.timeout) {
90+
return options;
91+
}
92+
if (!isNumber(options.timeout)) {
93+
throw new Error('HttpsCallableOptions.timeout expected a Number in milliseconds');
94+
}
95+
if (isOther) {
96+
return options;
97+
}
98+
return {
99+
...options,
100+
timeout: options.timeout / 1000,
101+
};
102+
}
103+
86104
let _id_functions_streaming_event = 0;
87105

88106
class FirebaseFunctionsModule extends FirebaseModule {
@@ -278,16 +296,7 @@ class FirebaseFunctionsModule extends FirebaseModule {
278296
}
279297

280298
httpsCallable(name: string, options: HttpsCallableOptions = {}) {
281-
if (options.timeout) {
282-
if (isNumber(options.timeout)) {
283-
// Native Android/iOS expect seconds; Firebase JS (web/macos) expects milliseconds.
284-
if (!isOther) {
285-
options.timeout = options.timeout / 1000;
286-
}
287-
} else {
288-
throw new Error('HttpsCallableOptions.timeout expected a Number in milliseconds');
289-
}
290-
}
299+
const normalizedOptions = normalizeHttpsCallableTimeoutOptions(options);
291300

292301
const callableFunction = ((data?: unknown) => {
293302
const nativePromise = this.native.httpsCallable(
@@ -297,7 +306,7 @@ class FirebaseFunctionsModule extends FirebaseModule {
297306
{
298307
data,
299308
},
300-
options,
309+
normalizedOptions,
301310
);
302311
return nativePromise.catch((nativeError: NativeError) => {
303312
const { code, message, details } = nativeError.userInfo || {};
@@ -317,9 +326,9 @@ class FirebaseFunctionsModule extends FirebaseModule {
317326
streamOptions?: HttpsCallableStreamOptions,
318327
) => {
319328
const platformOptions = !isOther
320-
? options
329+
? normalizedOptions
321330
: ({
322-
...options,
331+
...normalizedOptions,
323332
httpsCallableStreamOptions: streamOptions || {},
324333
} as CustomHttpsCallableOptions);
325334
return this._createStreamHandler(listenerId => {
@@ -338,15 +347,7 @@ class FirebaseFunctionsModule extends FirebaseModule {
338347
}
339348

340349
httpsCallableFromUrl(url: string, options: HttpsCallableOptions = {}) {
341-
if (options.timeout) {
342-
if (isNumber(options.timeout)) {
343-
if (!isOther) {
344-
options.timeout = options.timeout / 1000;
345-
}
346-
} else {
347-
throw new Error('HttpsCallableOptions.timeout expected a Number in milliseconds');
348-
}
349-
}
350+
const normalizedOptions = normalizeHttpsCallableTimeoutOptions(options);
350351

351352
const callableFunction = ((data?: unknown) => {
352353
const nativePromise = this.native.httpsCallableFromUrl(
@@ -356,7 +357,7 @@ class FirebaseFunctionsModule extends FirebaseModule {
356357
{
357358
data,
358359
},
359-
options,
360+
normalizedOptions,
360361
);
361362
return nativePromise.catch((nativeError: NativeError) => {
362363
const { code, message, details } = nativeError.userInfo || {};
@@ -376,9 +377,9 @@ class FirebaseFunctionsModule extends FirebaseModule {
376377
streamOptions?: HttpsCallableStreamOptions,
377378
) => {
378379
const platformOptions = !isOther
379-
? options
380+
? normalizedOptions
380381
: ({
381-
...options,
382+
...normalizedOptions,
382383
httpsCallableStreamOptions: streamOptions || {},
383384
} as CustomHttpsCallableOptions);
384385
return this._createStreamHandler(listenerId => {

0 commit comments

Comments
 (0)