Skip to content

Commit

Permalink
fix(userToken): prevent search waterfall (#5512)
Browse files Browse the repository at this point in the history
This makes sure when the same userToken is set multiple times, it doesn't cause multiple search requests, as could be the case if something would call setUserToken without condition.

Discovered in #5500, but should be merged separately.

Co-authored-by: Fabien Motte <fabien.motte@algolia.com>
  • Loading branch information
Haroenv and FabienMotte committed Apr 24, 2023
1 parent cff723f commit 85dfbc9
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import {
createInsightsUmdVersion,
createSearchClient,
} from '@instantsearch/mocks';
import { castToJestMock } from '@instantsearch/testutils';
import { wait } from '@instantsearch/testutils/wait';
import { fireEvent } from '@testing-library/dom';

import { createInsightsMiddleware } from '..';
import { createInstantSearch } from '../../../test/createInstantSearch';
import { connectSearchBox } from '../../connectors';
import instantsearch from '../../index.es';
import { history } from '../../lib/routers';
import { warning } from '../../lib/utils';
Expand Down Expand Up @@ -45,6 +47,7 @@ describe('insights', () => {
started = true,
insights = false,
} = {}) => {
castToJestMock(searchClient.search).mockClear();
const { analytics, insightsClient } = createInsights();
const indexName = 'my-index';
const instantSearchInstance = instantsearch({
Expand Down Expand Up @@ -109,6 +112,8 @@ describe('insights', () => {
(window as any).aa = undefined;

document.body.innerHTML = '';

document.cookie = '_ALGOLIA=;';
});

describe('usage', () => {
Expand Down Expand Up @@ -623,6 +628,76 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
).toEqual('def');
});

it('searches once per unique userToken', async () => {
const { insightsClient, instantSearchInstance } = createTestEnvironment();

instantSearchInstance.addWidgets([connectSearchBox(() => ({}))({})]);

await wait(0);
expect(instantSearchInstance.client.search).toHaveBeenCalledTimes(1);

insightsClient('setUserToken', 'abc');
instantSearchInstance.use(
createInsightsMiddleware({
insightsClient,
})
);

await wait(0);
expect(instantSearchInstance.client.search).toHaveBeenCalledTimes(2);

insightsClient('setUserToken', 'abc');

await wait(0);
expect(instantSearchInstance.client.search).toHaveBeenCalledTimes(2);
});

it("doesn't search when userToken is falsy", async () => {
const { insightsClient, instantSearchInstance } = createTestEnvironment();

instantSearchInstance.addWidgets([connectSearchBox(() => ({}))({})]);

await wait(0);
expect(instantSearchInstance.client.search).toHaveBeenCalledTimes(1);
expect(instantSearchInstance.client.search).toHaveBeenLastCalledWith([
{
indexName: 'my-index',
params: {
facets: [],
query: '',
tagFilters: '',
},
},
]);

insightsClient('setUserToken', 0);
instantSearchInstance.use(
createInsightsMiddleware({
insightsClient,
insightsInitParams: { useCookie: false },
})
);

await wait(0);
expect(instantSearchInstance.client.search).toHaveBeenCalledTimes(2);
expect(instantSearchInstance.client.search).toHaveBeenLastCalledWith([
{
indexName: 'my-index',
params: {
clickAnalytics: true,
facets: [],
query: '',
tagFilters: '',
},
},
]);

insightsClient('setUserToken', undefined);

await wait(0);
expect(instantSearchInstance.client.search).toHaveBeenCalledTimes(2);
});

describe('umd', () => {
it('applies userToken from queue if exists', () => {
const {
Expand Down Expand Up @@ -818,8 +893,6 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
await wait(100);
// url should not get cleared
expect(document.location.href).toEqual(url);

document.cookie = '';
});

test('does not throw error when document or cookie are undefined', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,21 @@ export function createInsightsMiddleware<
}

const setUserTokenToSearch = (userToken?: string) => {
if (!userToken) {
return;
}

const existingToken = (helper.state as PlainSearchParameters)
.userToken;

helper.overrideStateWithoutTriggeringChangeEvent({
...helper.state,
userToken,
});

instantSearchInstance.scheduleSearch();
if (existingToken && existingToken !== userToken) {
instantSearchInstance.scheduleSearch();
}
};

const anonymousUserToken = getInsightsAnonymousUserTokenInternal();
Expand All @@ -205,8 +214,10 @@ export function createInsightsMiddleware<
// We consider the `userToken` coming from a `init` call to have a higher
// importance than the one coming from the queue.
if (userTokenBeforeInit) {
setUserTokenToSearch(userTokenBeforeInit);
insightsClient('setUserToken', userTokenBeforeInit);
} else if (queuedUserToken) {
setUserTokenToSearch(queuedUserToken);
insightsClient('setUserToken', queuedUserToken);
}

Expand Down

0 comments on commit 85dfbc9

Please sign in to comment.