Skip to content

Commit

Permalink
fix(insights): ensure the same token is used when rendered multiple t…
Browse files Browse the repository at this point in the history
…imes server side (#6456)

* fix(insights): ensure _same_ token is used when rendered multiple times server side

in insights middleware: ensure we read from the _initialResults (as insights starts before index is init'ed
in getInitialResults: add the parameters to every index explicitly (it's not ui state so was missing otherwise)

other changes in this PR:
- something in the dependencies to prevent babel/babel#16689
- use a cache in the server side react examples
- type `userToken` in SearchParameters

* bundlesize

* fix test

* v4

* app router example

* v4 again

* simpler test

* v4

* simplify

---------

Co-authored-by: Dhaya <[email protected]>
  • Loading branch information
Haroenv and dhayab authored Dec 9, 2024
1 parent 9bc841a commit c3a1c70
Show file tree
Hide file tree
Showing 18 changed files with 279 additions and 107 deletions.
4 changes: 2 additions & 2 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
"maxSize": "182 kB"
"maxSize": "185 kB"
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
"maxSize": "51.25 kB"
},
{
"path": "packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js",
"maxSize": "65 kB"
"maxSize": "65.50 kB"
},
{
"path": "packages/vue-instantsearch/vue2/umd/index.js",
Expand Down
4 changes: 1 addition & 3 deletions examples/react/next-app-router/app/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { Hit as AlgoliaHit } from 'instantsearch.js';
import React from 'react';
import {
Expand All @@ -13,8 +12,7 @@ import {
import { InstantSearchNext } from 'react-instantsearch-nextjs';

import { Panel } from '../components/Panel';

const client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76');
import { client } from '../lib/client';

type HitProps = {
hit: AlgoliaHit<{
Expand Down
4 changes: 4 additions & 0 deletions examples/react/next-app-router/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import React from 'react';

import { responsesCache } from '../lib/client';

import Search from './Search';

export const dynamic = 'force-dynamic';

export default function Page() {
responsesCache.clear();

return <Search />;
}
9 changes: 9 additions & 0 deletions examples/react/next-app-router/lib/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createMemoryCache } from '@algolia/client-common';
import { liteClient as algoliasearch } from 'algoliasearch/lite';

export const responsesCache = createMemoryCache();
export const client = algoliasearch(
'latency',
'6be0576ff61c053d5f9a3225e2a90f76',
{ responsesCache }
);
7 changes: 6 additions & 1 deletion examples/react/next-routing/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createMemoryCache } from '@algolia/client-common';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { Hit as AlgoliaHit } from 'instantsearch.js';
import { GetServerSideProps } from 'next';
Expand All @@ -21,7 +22,10 @@ import { createInstantSearchRouterNext } from 'react-instantsearch-router-nextjs

import { Panel } from '../components/Panel';

const client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76');
const requestsCache = createMemoryCache();
const client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76', {
requestsCache,
});

type HitProps = {
hit: AlgoliaHit<{
Expand Down Expand Up @@ -99,6 +103,7 @@ export const getServerSideProps: GetServerSideProps<HomePageProps> =
const serverState = await getServerState(<HomePage url={url} />, {
renderToString,
});
requestsCache.clear();

return {
props: {
Expand Down
7 changes: 6 additions & 1 deletion examples/react/next-routing/pages/test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// This is only to test `onStateChange` does not get called twice
import { createMemoryCache } from '@algolia/client-common';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
Expand All @@ -16,7 +17,10 @@ import {
} from 'react-instantsearch';
import { createInstantSearchRouterNext } from 'react-instantsearch-router-nextjs';

const client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76');
const requestsCache = createMemoryCache();
const client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76', {
requestsCache,
});

type HomePageProps = {
serverState?: InstantSearchServerState;
Expand Down Expand Up @@ -74,6 +78,7 @@ export const getServerSideProps: GetServerSideProps<HomePageProps> =
const serverState = await getServerState(<HomePage url={url} />, {
renderToString,
});
requestsCache.clear();

return {
props: {
Expand Down
11 changes: 10 additions & 1 deletion examples/react/next/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createMemoryCache } from '@algolia/client-common';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { Hit as AlgoliaHit } from 'instantsearch.js';
import { GetServerSideProps } from 'next';
Expand All @@ -20,7 +21,10 @@ import { createInstantSearchRouterNext } from 'react-instantsearch-router-nextjs

import { Panel } from '../components/Panel';

const client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76');
const responsesCache = createMemoryCache();
const client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76', {
responsesCache,
});

type HitProps = {
hit: AlgoliaHit<{
Expand Down Expand Up @@ -63,6 +67,9 @@ export default function HomePage({ serverState, url }: HomePageProps) {
}),
}}
insights={true}
future={{
preserveSharedStateOnUnmount: true,
}}
>
<div className="Container">
<div>
Expand Down Expand Up @@ -94,6 +101,8 @@ export const getServerSideProps: GetServerSideProps<HomePageProps> =
renderToString,
});

responsesCache.clear();

return {
props: {
serverState,
Expand Down
7 changes: 6 additions & 1 deletion examples/react/ssr/src/searchClient.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { createMemoryCache } from '@algolia/client-common';
import { liteClient as algoliasearch } from 'algoliasearch/lite';

export const requestsCache = createMemoryCache();
export const searchClient = algoliasearch(
'latency',
'6be0576ff61c053d5f9a3225e2a90f76'
'6be0576ff61c053d5f9a3225e2a90f76',
{
requestsCache,
}
);
2 changes: 2 additions & 0 deletions examples/react/ssr/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { renderToString } from 'react-dom/server';
import { getServerState } from 'react-instantsearch';

import App from './App';
import { requestsCache } from './searchClient';

const app = express();

Expand All @@ -18,6 +19,7 @@ app.get('/', async (req, res) => {
const serverState = await getServerState(<App location={location} />, {
renderToString,
});
requestsCache.clear();
const html = renderToString(
<App serverState={serverState} location={location} />
);
Expand Down
6 changes: 6 additions & 0 deletions packages/algoliasearch-helper/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,12 @@ declare namespace algoliasearchHelper {
* https://www.algolia.com/doc/api-reference/api-parameters/optionalFilters/
*/
optionalFilters?: Array<string | string[]>;
/**
* Unique pseudonymous or anonymous user identifier.
* This helps with analytics and click and conversion events.
* For more information, see [user token](https://www.algolia.com/doc/guides/sending-events/concepts/usertoken/).
*/
userToken?: string;
/**
* If set to false, this query will not be taken into account in the analytics feature.
* default true
Expand Down
40 changes: 40 additions & 0 deletions packages/instantsearch.js/src/lib/__tests__/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,4 +421,44 @@ describe('getInitialResults', () => {
]
`);
});

test('injects clickAnalytics and userToken into the state', async () => {
const search = instantsearch({
indexName: 'indexName',
searchClient: createSearchClient(),
initialUiState: {
indexName: {
query: 'apple',
},
},
insights: true,
});

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

search.start();

const requestParams = await waitForResults(search);

expect(requestParams).toEqual([
{
clickAnalytics: true,
query: 'apple',
userToken: expect.stringMatching(/^anonymous-/),
},
]);

const initialResults = getInitialResults(search.mainIndex, requestParams);

const indexRequestParams = initialResults.indexName!.requestParams![0];
expect(indexRequestParams.clickAnalytics).toBe(true);
expect(indexRequestParams.userToken).toMatch(/^anonymous-/);

const indexState = initialResults.indexName!.state!;
expect(indexState.clickAnalytics).toBe(true);
expect(indexState.userToken).toMatch(/^anonymous-/);

expect(indexRequestParams.userToken).toEqual(requestParams[0].userToken);
expect(indexState.userToken).toEqual(indexRequestParams.userToken);
});
});
6 changes: 5 additions & 1 deletion packages/instantsearch.js/src/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,11 @@ export function getInitialResults(
// We convert the Helper state to a plain object to pass parsable data
// structures from server to client.
...(searchResults && {
state: { ...searchResults._state },
state: {
...searchResults._state,
clickAnalytics: requestParams?.[0]?.clickAnalytics,
userToken: requestParams?.[0]?.userToken,
},
results: searchResults._rawResults,
}),
...(recommendResults && {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,28 @@ describe('insights', () => {
expect(getUserToken()).toBe('def');
});

it('uses `userToken` from initial results', () => {
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();

instantSearchInstance._initialResults = {
[instantSearchInstance.indexName]: {
state: {
userToken: 'from-initial-results',
clickAnalytics: true,
},
},
};

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

expect(getUserToken()).toEqual('from-initial-results');
});

describe('authenticatedUserToken', () => {
describe('before `init`', () => {
it('does not use `authenticatedUserToken` as the `userToken` when defined', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
InsightsMethod,
InsightsMethodMap,
InternalMiddleware,
InstantSearch,
} from '../types';
import type {
AlgoliaSearchHelper,
Expand Down Expand Up @@ -208,10 +209,7 @@ export function createInsightsMiddleware<
);
}

initialParameters = {
userToken: (helper.state as PlainSearchParameters).userToken,
clickAnalytics: helper.state.clickAnalytics,
};
initialParameters = getInitialParameters(instantSearchInstance);

// We don't want to force clickAnalytics when the insights is enabled from the search response.
// This means we don't enable insights for indices that don't opt in
Expand Down Expand Up @@ -432,6 +430,23 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
};
}

function getInitialParameters(
instantSearchInstance: InstantSearch
): PlainSearchParameters {
// in SSR, the initial state we use in this domain is set on the main index
const stateFromInitialResults =
instantSearchInstance._initialResults?.[instantSearchInstance.indexName]
?.state || {};

const stateFromHelper = instantSearchInstance.mainHelper!.state;

return {
userToken: stateFromInitialResults.userToken || stateFromHelper.userToken,
clickAnalytics:
stateFromInitialResults.clickAnalytics || stateFromHelper.clickAnalytics,
};
}

function saveTokenAsCookie(token: string, cookieDuration?: number) {
const MONTH = 30 * 24 * 60 * 60 * 1000;
const d = new Date();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ exports[`getServerState returns initialResults 1`] = `
},
],
"state": {
"clickAnalytics": undefined,
"disjunctiveFacets": [
"brand",
],
Expand All @@ -77,6 +78,7 @@ exports[`getServerState returns initialResults 1`] = `
"numericRefinements": {},
"query": "iphone",
"tagRefinements": [],
"userToken": undefined,
},
},
"instant_search_price_asc": {
Expand Down Expand Up @@ -134,6 +136,7 @@ exports[`getServerState returns initialResults 1`] = `
},
],
"state": {
"clickAnalytics": undefined,
"disjunctiveFacets": [],
"disjunctiveFacetsRefinements": {},
"facets": [],
Expand All @@ -146,6 +149,7 @@ exports[`getServerState returns initialResults 1`] = `
"index": "instant_search_price_asc",
"numericRefinements": {},
"tagRefinements": [],
"userToken": undefined,
},
},
"instant_search_price_desc": {
Expand Down Expand Up @@ -203,6 +207,7 @@ exports[`getServerState returns initialResults 1`] = `
},
],
"state": {
"clickAnalytics": undefined,
"disjunctiveFacets": [],
"disjunctiveFacetsRefinements": {},
"facets": [],
Expand All @@ -215,6 +220,7 @@ exports[`getServerState returns initialResults 1`] = `
"index": "instant_search_price_desc",
"numericRefinements": {},
"tagRefinements": [],
"userToken": undefined,
},
},
"instant_search_rating_desc": {
Expand Down Expand Up @@ -272,6 +278,7 @@ exports[`getServerState returns initialResults 1`] = `
},
],
"state": {
"clickAnalytics": undefined,
"disjunctiveFacets": [],
"disjunctiveFacetsRefinements": {},
"facets": [],
Expand All @@ -284,6 +291,7 @@ exports[`getServerState returns initialResults 1`] = `
"index": "instant_search_rating_desc",
"numericRefinements": {},
"tagRefinements": [],
"userToken": undefined,
},
},
}
Expand Down
Loading

0 comments on commit c3a1c70

Please sign in to comment.