diff --git a/packages/autocomplete-core/src/__tests__/createAutocomplete.test.ts b/packages/autocomplete-core/src/__tests__/createAutocomplete.test.ts index 0b4aa7b4f..0923fd364 100644 --- a/packages/autocomplete-core/src/__tests__/createAutocomplete.test.ts +++ b/packages/autocomplete-core/src/__tests__/createAutocomplete.test.ts @@ -137,7 +137,7 @@ describe('createAutocomplete', () => { insights: { insightsClient }, }); - expect(insightsClient).toHaveBeenCalledTimes(3); + expect(insightsClient).toHaveBeenCalledTimes(5); expect(insightsClient).toHaveBeenCalledWith( 'addAlgoliaAgent', 'insights-plugin' @@ -168,7 +168,7 @@ describe('createAutocomplete', () => { }); expect(defaultInsightsClient).toHaveBeenCalledTimes(0); - expect(userInsightsClient).toHaveBeenCalledTimes(3); + expect(userInsightsClient).toHaveBeenCalledTimes(5); expect(userInsightsClient).toHaveBeenCalledWith( 'addAlgoliaAgent', 'insights-plugin' diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index e3a498dbd..c0c9cf320 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -724,6 +724,7 @@ See: https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocom algoliaInsightsPlugin: expect.objectContaining({ insights: expect.objectContaining({ init: expect.any(Function), + setAuthenticatedUserToken: expect.any(Function), setUserToken: expect.any(Function), clickedObjectIDsAfterSearch: expect.any(Function), clickedObjectIDs: expect.any(Function), @@ -751,7 +752,7 @@ See: https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocom insights: { insightsClient: defaultInsightsClient }, }); - expect(defaultInsightsClient).toHaveBeenCalledTimes(3); + expect(defaultInsightsClient).toHaveBeenCalledTimes(5); expect(userInsightsClient).toHaveBeenCalledTimes(0); const insightsPlugin = createAlgoliaInsightsPlugin({ @@ -759,8 +760,8 @@ See: https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocom }); update({ plugins: [insightsPlugin] }); - expect(defaultInsightsClient).toHaveBeenCalledTimes(3); - expect(userInsightsClient).toHaveBeenCalledTimes(3); + expect(defaultInsightsClient).toHaveBeenCalledTimes(5); + expect(userInsightsClient).toHaveBeenCalledTimes(5); }); }); }); diff --git a/packages/autocomplete-plugin-algolia-insights/src/__tests__/createAlgoliaInsightsPlugin.test.ts b/packages/autocomplete-plugin-algolia-insights/src/__tests__/createAlgoliaInsightsPlugin.test.ts index acd36848a..fcdac3fa8 100644 --- a/packages/autocomplete-plugin-algolia-insights/src/__tests__/createAlgoliaInsightsPlugin.test.ts +++ b/packages/autocomplete-plugin-algolia-insights/src/__tests__/createAlgoliaInsightsPlugin.test.ts @@ -66,6 +66,7 @@ describe('createAlgoliaInsightsPlugin', () => { algoliaInsightsPlugin: expect.objectContaining({ insights: expect.objectContaining({ init: expect.any(Function), + setAuthenticatedUserToken: expect.any(Function), setUserToken: expect.any(Function), clickedObjectIDsAfterSearch: expect.any(Function), clickedObjectIDs: expect.any(Function), @@ -91,7 +92,7 @@ describe('createAlgoliaInsightsPlugin', () => { createPlayground(createAutocomplete, { plugins: [insightsPlugin] }); - expect(insightsClient).toHaveBeenCalledTimes(3); + expect(insightsClient).toHaveBeenCalledTimes(5); expect(insightsClient).toHaveBeenCalledWith( 'addAlgoliaAgent', 'insights-plugin' @@ -180,54 +181,6 @@ describe('createAlgoliaInsightsPlugin', () => { ]); }); - test('forwards `userToken` from Search Insights to Algolia API requests', async () => { - const insightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); - - const searchClient = createSearchClient({ - search: jest.fn(() => - Promise.resolve( - createMultiSearchResponse({ - hits: [{ objectID: '1' }], - }) - ) - ), - }); - - insightsClient('setUserToken', 'customUserToken'); - - const playground = createPlayground(createAutocomplete, { - plugins: [insightsPlugin], - getSources({ query }) { - return [ - { - sourceId: 'hits', - getItems() { - return getAlgoliaResults({ - searchClient, - queries: [{ indexName: 'indexName', query }], - }); - }, - templates: { - item({ item }) { - return item.objectID; - }, - }, - }, - ]; - }, - }); - - userEvent.type(playground.inputElement, 'a'); - await runAllMicroTasks(); - - expect(searchClient.search).toHaveBeenCalledTimes(1); - expect(searchClient.search).toHaveBeenCalledWith([ - expect.objectContaining({ - params: expect.objectContaining({ userToken: 'customUserToken' }), - }), - ]); - }); - test('does not call `init` if `insightsInitParams` not passed', () => { const insightsClient = jest.fn(); createAlgoliaInsightsPlugin({ @@ -250,6 +203,173 @@ describe('createAlgoliaInsightsPlugin', () => { }); }); + describe('user token', () => { + afterEach(() => { + insightsClient('setAuthenticatedUserToken', undefined); + }); + + test('forwards `userToken` from Search Insights to Algolia API requests', async () => { + const insightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); + + const searchClient = createSearchClient({ + search: jest.fn(() => + Promise.resolve( + createMultiSearchResponse({ + hits: [{ objectID: '1' }], + }) + ) + ), + }); + + insightsClient('setUserToken', 'customUserToken'); + + const playground = createPlayground(createAutocomplete, { + plugins: [insightsPlugin], + getSources({ query }) { + return [ + { + sourceId: 'hits', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [{ indexName: 'indexName', query }], + }); + }, + templates: { + item({ item }) { + return item.objectID; + }, + }, + }, + ]; + }, + }); + + userEvent.type(playground.inputElement, 'a'); + await runAllMicroTasks(); + + expect(searchClient.search).toHaveBeenCalledTimes(1); + expect(searchClient.search).toHaveBeenCalledWith([ + expect.objectContaining({ + params: expect.objectContaining({ userToken: 'customUserToken' }), + }), + ]); + }); + + test('forwards `authenticatedUserToken` from Search Insights to Algolia API requests', async () => { + const insightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); + + const searchClient = createSearchClient({ + search: jest.fn(() => + Promise.resolve( + createMultiSearchResponse({ + hits: [{ objectID: '1' }], + }) + ) + ), + }); + + insightsClient('setAuthenticatedUserToken', 'customAuthUserToken'); + + const playground = createPlayground(createAutocomplete, { + plugins: [insightsPlugin], + getSources({ query }) { + return [ + { + sourceId: 'hits', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [{ indexName: 'indexName', query }], + }); + }, + templates: { + item({ item }) { + return item.objectID; + }, + }, + }, + ]; + }, + }); + + userEvent.type(playground.inputElement, 'a'); + await runAllMicroTasks(); + + expect(searchClient.search).toHaveBeenCalledTimes(1); + expect(searchClient.search).toHaveBeenCalledWith([ + expect.objectContaining({ + params: expect.objectContaining({ userToken: 'customAuthUserToken' }), + }), + ]); + }); + + test('uses `authenticatedUserToken` in priority over `userToken`', async () => { + const insightsPlugin = createAlgoliaInsightsPlugin({ + insightsClient, + insightsInitParams: { + userToken: 'customUserToken', + }, + }); + + const searchClient = createSearchClient({ + search: jest.fn(() => + Promise.resolve( + createMultiSearchResponse({ + hits: [{ objectID: '1' }], + }) + ) + ), + }); + + insightsClient('setAuthenticatedUserToken', 'customAuthUserToken'); + + const playground = createPlayground(createAutocomplete, { + plugins: [insightsPlugin], + getSources({ query }) { + return [ + { + sourceId: 'hits', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [{ indexName: 'indexName', query }], + }); + }, + templates: { + item({ item }) { + return item.objectID; + }, + }, + }, + ]; + }, + }); + + userEvent.type(playground.inputElement, 'a'); + await runAllMicroTasks(); + + expect(searchClient.search).toHaveBeenCalledTimes(1); + expect(searchClient.search).toHaveBeenCalledWith([ + expect.objectContaining({ + params: expect.objectContaining({ userToken: 'customAuthUserToken' }), + }), + ]); + + insightsClient('setAuthenticatedUserToken', undefined); + + userEvent.type(playground.inputElement, 'b'); + await runAllMicroTasks(); + + expect(searchClient.search).toHaveBeenCalledTimes(2); + expect(searchClient.search).toHaveBeenLastCalledWith([ + expect.objectContaining({ + params: expect.objectContaining({ userToken: 'customUserToken' }), + }), + ]); + }); + }); + describe('automatic pulling', () => { const consoleError = jest .spyOn(console, 'error') diff --git a/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts b/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts index 084b645e4..28b1dba8c 100644 --- a/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts +++ b/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts @@ -20,6 +20,7 @@ import { AlgoliaInsightsHit, AutocompleteInsightsApi, InsightsClient, + InsightsEvent, InsightsMethodMap, OnActiveParams, OnItemsChangeParams, @@ -182,7 +183,7 @@ export function createAlgoliaInsightsPlugin( return { name: 'aa.algoliaInsightsPlugin', subscribe({ setContext, onSelect, onActive }) { - function setInsightsContext(userToken?: string | number) { + function setInsightsContext(userToken?: InsightsEvent['userToken']) { setContext({ algoliaInsightsPlugin: { __algoliaSearchParameters: { @@ -201,11 +202,36 @@ export function createAlgoliaInsightsPlugin( insightsClient('addAlgoliaAgent', 'insights-plugin'); setInsightsContext(); + + // Handles user token changes insightsClient('onUserTokenChange', setInsightsContext); insightsClient('getUserToken', null, (_error, userToken) => { setInsightsContext(userToken); }); + // Handles authenticated user token changes + insightsClient( + 'onAuthenticatedUserTokenChange', + (authenticatedUserToken) => { + if (authenticatedUserToken) { + setInsightsContext(authenticatedUserToken); + } else { + insightsClient('getUserToken', null, (_error, userToken) => + setInsightsContext(userToken) + ); + } + } + ); + insightsClient( + 'getAuthenticatedUserToken', + null, + (_error, authenticatedUserToken) => { + if (authenticatedUserToken) { + setInsightsContext(authenticatedUserToken); + } + } + ); + onSelect(({ item, state, event, source }) => { if (!isAlgoliaInsightsHit(item)) { return; @@ -323,6 +349,8 @@ function loadInsights(environment: typeof window) { * While `search-insights` supports both string and number user tokens, * the Search API only accepts strings. This function normalizes the user token. */ -function normalizeUserToken(userToken: string | number): string { +function normalizeUserToken( + userToken: NonNullable +): string { return typeof userToken === 'number' ? userToken.toString() : userToken; } diff --git a/packages/autocomplete-plugin-algolia-insights/src/createSearchInsightsApi.ts b/packages/autocomplete-plugin-algolia-insights/src/createSearchInsightsApi.ts index 778ea243a..2621c2a74 100644 --- a/packages/autocomplete-plugin-algolia-insights/src/createSearchInsightsApi.ts +++ b/packages/autocomplete-plugin-algolia-insights/src/createSearchInsightsApi.ts @@ -71,6 +71,15 @@ export function createSearchInsightsApi(searchInsights: InsightsClient) { init(appId: string, apiKey: string) { searchInsights('init', { appId, apiKey }); }, + /** + * Sets the authenticated user token to attach to events. + * Unsets the authenticated token by passing `undefined`. + * + * @link https://www.algolia.com/doc/api-reference/api-methods/set-authenticated-user-token/ + */ + setAuthenticatedUserToken(authenticatedUserToken: string | undefined) { + searchInsights('setAuthenticatedUserToken', authenticatedUserToken); + }, /** * Sets the user token to attach to events. */ diff --git a/packages/autocomplete-plugin-algolia-insights/src/types/InsightsClient.ts b/packages/autocomplete-plugin-algolia-insights/src/types/InsightsClient.ts index 58b6dcea8..a1a3fabec 100644 --- a/packages/autocomplete-plugin-algolia-insights/src/types/InsightsClient.ts +++ b/packages/autocomplete-plugin-algolia-insights/src/types/InsightsClient.ts @@ -9,6 +9,7 @@ export type { SetUserToken as InsightsSetUserToken, GetUserToken as InsightsGetUserToken, OnUserTokenChange as InsightsOnUserTokenChange, + InsightsEvent, } from 'search-insights'; export type InsightsMethodMap = _InsightsMethodMap;