Skip to content

Commit 346f898

Browse files
Fix tuple searchParams mutations leaking across init-hook requests (#861)
1 parent e9eeb35 commit 346f898

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

source/core/Ky.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,22 @@ const isRequestInstance = (value: unknown): value is Request =>
8484
const isResponseInstance = (value: unknown): value is Response =>
8585
value instanceof globalThis.Response || objectToString.call(value) === '[object Response]';
8686

87+
const cloneSearchParametersForInitHook = (searchParameters: SearchParamsOption | undefined): SearchParamsOption | undefined => {
88+
if (Array.isArray(searchParameters)) {
89+
return searchParameters.map(parameter => [...parameter]) as SearchParamsOption;
90+
}
91+
92+
return cloneShallow(searchParameters) as SearchParamsOption | undefined;
93+
};
94+
8795
// Shallow-clone mutable option properties so init hook mutations don't leak across requests.
8896
function cloneInitHookOptions(options: Options): Options {
8997
const clonedOptions: Options = {
9098
...options,
9199
json: cloneShallow(options.json),
92100
context: cloneShallow(options.context)!,
93101
headers: cloneShallow(options.headers)!,
94-
searchParams: cloneShallow(options.searchParams) as SearchParamsOption | undefined,
102+
searchParams: cloneSearchParametersForInitHook(options.searchParams),
95103
};
96104

97105
if (options.retry !== undefined) {

test/hooks.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4900,6 +4900,36 @@ test('init hook in-place mutations do not leak across requests', async t => {
49004900
t.deepEqual(seenRequestIdentifiers, ['1', '2']);
49014901
});
49024902

4903+
test('init hook tuple searchParams mutations do not leak across requests', async t => {
4904+
let requestIdentifier = 0;
4905+
const seenInitialValues: string[] = [];
4906+
const seenRequestIdentifiers: string[] = [];
4907+
4908+
const api = ky.extend({
4909+
searchParams: [['requestId', 'seed']],
4910+
hooks: {
4911+
init: [
4912+
options => {
4913+
const searchParameters = options.searchParams as string[][];
4914+
seenInitialValues.push(searchParameters[0]![1]!);
4915+
searchParameters[0]![1] = String(++requestIdentifier);
4916+
},
4917+
],
4918+
},
4919+
});
4920+
4921+
const fetch: typeof globalThis.fetch = async request => {
4922+
seenRequestIdentifiers.push(new URL(request.url).searchParams.get('requestId')!);
4923+
return new Response('ok');
4924+
};
4925+
4926+
await api.get('https://example.com', {fetch});
4927+
await api.get('https://example.com', {fetch});
4928+
4929+
t.deepEqual(seenInitialValues, ['seed', 'seed']);
4930+
t.deepEqual(seenRequestIdentifiers, ['1', '2']);
4931+
});
4932+
49034933
test('init hook in-place retry mutations do not leak across requests', async t => {
49044934
const seenLimits: number[] = [];
49054935

0 commit comments

Comments
 (0)