Skip to content

Commit

Permalink
fix(instantsearch.js): fix user token not being set in time for the f…
Browse files Browse the repository at this point in the history
…irst query (#6377)
  • Loading branch information
shaejaz authored Oct 23, 2024
1 parent c5f9a44 commit 22016b0
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 91 deletions.
6 changes: 3 additions & 3 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.production.min.js",
"maxSize": "83.25 kB"
"maxSize": "83.50 kB"
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
"maxSize": "180.25 kB"
"maxSize": "181.50 kB"
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
"maxSize": "51 kB"
"maxSize": "51.25 kB"
},
{
"path": "packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ describe('network requests', () => {
"params": {
"clickAnalytics": true,
"query": "",
"userToken": "cookie-key",
},
},
]
Expand Down Expand Up @@ -563,6 +564,7 @@ describe('network requests', () => {
"params": {
"clickAnalytics": true,
"query": "",
"userToken": "cookie-key",
},
},
]
Expand Down
15 changes: 15 additions & 0 deletions packages/instantsearch.js/src/lib/utils/uuid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Create UUID according to
* https://www.ietf.org/rfc/rfc4122.txt.
*
* @returns Generated UUID.
*/
export function createUUID(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
/* eslint-disable no-bitwise */
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
/* eslint-enable */
return v.toString(16);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { history } from '../../lib/routers';
import { warning } from '../../lib/utils';
import { dynamicWidgets, hits, refinementList } from '../../widgets';

import type { InsightsProps } from '..';
import type { SearchClient } from '../../index.es';
import type { PlainSearchParameters } from 'algoliasearch-helper';
import type { JSDOM } from 'jsdom';
Expand Down Expand Up @@ -50,6 +51,10 @@ describe('insights', () => {
searchClient = searchClientWithCredentials,
started = true,
insights = false,
}: {
searchClient?: SearchClient;
started?: boolean;
insights?: InsightsProps | boolean;
} = {}) => {
castToJestMock(searchClient.search).mockClear();
const { analytics, insightsClient } = createInsights();
Expand Down Expand Up @@ -437,37 +442,6 @@ describe('insights', () => {
);
});

it('warns when userToken is not set', () => {
const { insightsClient, instantSearchInstance } = createTestEnvironment();

instantSearchInstance.use(
createInsightsMiddleware({
insightsClient,
insightsInitParams: {
useCookie: false,
anonymousUserToken: false,
},
})
);

expect(() =>
instantSearchInstance.sendEventToInsights({
eventType: 'view',
insightsMethod: 'viewedObjectIDs',
payload: {
eventName: 'Hits Viewed',
index: '',
objectIDs: ['1', '2'],
},
widgetType: 'ais.hits',
})
).toWarnDev(
`[InstantSearch.js]: Cannot send event to Algolia Insights because \`userToken\` is not set.
See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-further/send-insights-events/js/#setting-the-usertoken`
);
});

it('applies clickAnalytics if $$automatic: undefined', () => {
const { insightsClient, instantSearchInstance } = createTestEnvironment();
instantSearchInstance.use(
Expand Down Expand Up @@ -778,37 +752,34 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
});

it('handles multiple setUserToken calls before search.start()', async () => {
const { insightsClient } = createInsights();
const indexName = 'my-index';
const instantSearchInstance = instantsearch({
searchClient: createSearchClient({
// @ts-expect-error only available in search client v4
transporter: {
headers: {
'x-algolia-application-id': 'myAppId',
'x-algolia-api-key': 'myApiKey',
},
},
}),
indexName,
});
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();

const middleware = createInsightsMiddleware({
insightsClient,
});
instantSearchInstance.use(middleware);
instantSearchInstance.use(
createInsightsMiddleware({
insightsClient,
})
);

insightsClient('setUserToken', 'abc');
insightsClient('setUserToken', 'def');

instantSearchInstance.start();
expect(getUserToken()).toEqual('def');

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

expect(
(instantSearchInstance.mainHelper!.state as PlainSearchParameters)
.userToken
).toEqual('def');
await wait(0);
expect(instantSearchInstance.client.search).toHaveBeenCalledTimes(1);
expect(instantSearchInstance.client.search).toHaveBeenLastCalledWith([
{
indexName: 'my-index',
params: {
clickAnalytics: true,
query: '',
userToken: getUserToken(),
},
},
]);
});

it('searches once per unique userToken', async () => {
Expand Down Expand Up @@ -836,7 +807,8 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
});

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

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

Expand Down Expand Up @@ -866,8 +838,8 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
indexName: 'my-index',
params: {
clickAnalytics: true,

query: '',
userToken: getUserToken(),
},
},
]);
Expand All @@ -878,6 +850,81 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
expect(instantSearchInstance.client.search).toHaveBeenCalledTimes(2);
});

it('sets an anonymous token as the userToken if none given', async () => {
const { instantSearchInstance, getUserToken } = createTestEnvironment({
insights: true,
});

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

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

expect(getUserToken()).toEqual(expect.stringMatching(/^anonymous-/));
});

it('saves an anonymous token to a cookie if useCookie is true in insights init props', () => {
const { getUserToken } = createTestEnvironment({
insights: {
insightsInitParams: {
useCookie: true,
},
},
});

const userToken = getUserToken();
expect(userToken).toEqual(expect.stringMatching(/^anonymous-/));
expect(document.cookie).toBe(`_ALGOLIA=${userToken}`);
});

it('saves an anonymous token to a cookie if useCookie is true insights init method', async () => {
const { instantSearchInstance, insightsClient, getUserToken } =
createTestEnvironment({
insights: true,
started: false,
});

insightsClient('init', { partial: true, useCookie: true });

instantSearchInstance.start();

await wait(0);
const userToken = getUserToken();
expect(userToken).toEqual(expect.stringMatching(/^anonymous-/));
expect(document.cookie).toBe(`_ALGOLIA=${userToken}`);
});

it('uses `userToken` from insights init props over anything else', async () => {
document.cookie = '_ALGOLIA=abc';

const { instantSearchInstance, insightsClient, getUserToken } =
createTestEnvironment({
insights: {
insightsInitParams: {
userToken: 'def',
},
},
started: false,
});

insightsClient('init', { partial: true, userToken: 'ghi' });

instantSearchInstance.start();

await wait(0);
expect(getUserToken()).toBe('def');
});

describe('authenticatedUserToken', () => {
describe('before `init`', () => {
it('uses the `authenticatedUserToken` as the `userToken` when defined', () => {
Expand Down Expand Up @@ -921,6 +968,39 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f

expect(getUserToken()).toEqual('abc');
});

it('uses the `authenticatedUserToken` when a `userToken` is set after', () => {
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();

insightsClient('setAuthenticatedUserToken', 'def');

instantSearchInstance.use(
createInsightsMiddleware({ insightsClient })
);

insightsClient('setUserToken', 'abc');

expect(getUserToken()).toEqual('def');
});

it('resets the token to the `userToken` when `authenticatedUserToken` is set as undefined', () => {
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();

insightsClient('setUserToken', 'abc');
insightsClient('setAuthenticatedUserToken', 'def');

instantSearchInstance.use(
createInsightsMiddleware({ insightsClient })
);

expect(getUserToken()).toEqual('def');

insightsClient('setAuthenticatedUserToken', undefined);

expect(getUserToken()).toEqual('abc');
});
});

describe('after `init`', () => {
Expand Down Expand Up @@ -1361,13 +1441,15 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f

// Dynamic widgets will trigger 2 searches. To avoid missing the cache on the second search, createInsightsMiddleware delays setting the userToken.

const userToken = getUserToken();

expect(searchClient.search).toHaveBeenCalledTimes(2);
expect(
searchClient.search.mock.calls[0][0][0].params.userToken
).toBeUndefined();
expect(
searchClient.search.mock.calls[1][0][0].params.userToken
).toBeUndefined();
expect(searchClient.search.mock.calls[0][0][0].params.userToken).toBe(
userToken
);
expect(searchClient.search.mock.calls[1][0][0].params.userToken).toBe(
userToken
);

await wait(0);

Expand All @@ -1383,7 +1465,7 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
expect(searchClient.search).toHaveBeenCalledTimes(3);
expect(searchClient.search).toHaveBeenLastCalledWith([
expect.objectContaining({
params: expect.objectContaining({ userToken: getUserToken() }),
params: expect.objectContaining({ userToken }),
}),
]);
});
Expand Down
Loading

0 comments on commit 22016b0

Please sign in to comment.