Skip to content

Commit

Permalink
feat(createInsightsMiddleware): support authenticatedUserToken (#5997)
Browse files Browse the repository at this point in the history
  • Loading branch information
sarahdayan authored Jan 16, 2024
1 parent 4e99d12 commit a20715c
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 24 deletions.
4 changes: 2 additions & 2 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
"maxSize": "167.25 kB"
"maxSize": "167.5 kB"
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
Expand All @@ -30,7 +30,7 @@
},
{
"path": "packages/vue-instantsearch/vue3/umd/index.js",
"maxSize": "65 kB"
"maxSize": "65.25 kB"
},
{
"path": "packages/vue-instantsearch/vue2/cjs/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,166 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
expect(instantSearchInstance.client.search).toHaveBeenCalledTimes(2);
});

describe('authenticatedUserToken', () => {
describe('before `init`', () => {
it('uses the `authenticatedUserToken` as the `userToken` when defined', () => {
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();

insightsClient('setAuthenticatedUserToken', 'abc');

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

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

it('uses the `authenticatedUserToken` as the `userToken` when both are defined', () => {
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();

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

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

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

it('reverts to the `userToken` when unsetting the `authenticatedUserToken`', () => {
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();

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

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

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

describe('after `init`', () => {
it('uses the `authenticatedUserToken` as the `userToken` when defined', async () => {
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();
instantSearchInstance.use(
createInsightsMiddleware({ insightsClient })
);

insightsClient('setAuthenticatedUserToken', 'abc');

await wait(0);

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

it('uses the `authenticatedUserToken` as the `userToken` when both are defined', async () => {
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();
instantSearchInstance.use(
createInsightsMiddleware({ insightsClient })
);

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

await wait(0);

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

it('reverts to the `userToken` when unsetting the `authenticatedUserToken`', async () => {
const { insightsClient, instantSearchInstance, getUserToken } =
createTestEnvironment();
instantSearchInstance.use(
createInsightsMiddleware({ insightsClient })
);

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

await wait(0);

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

describe('from queue', () => {
it('uses the `authenticatedUserToken` as the `userToken` when defined', () => {
const {
insightsClient,
libraryLoadedAndProcessQueue,
instantSearchInstance,
getUserToken,
} = createUmdTestEnvironment();

insightsClient('init', { appId: 'myAppId', apiKey: 'myApiKey' });
insightsClient('setAuthenticatedUserToken', 'abc');

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

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

it('uses the `authenticatedUserToken` as the `userToken` when both are defined', () => {
const {
insightsClient,
libraryLoadedAndProcessQueue,
instantSearchInstance,
getUserToken,
} = createUmdTestEnvironment();

insightsClient('init', { appId: 'myAppId', apiKey: 'myApiKey' });
insightsClient('setUserToken', 'abc');
insightsClient('setAuthenticatedUserToken', 'def');

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

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

it('reverts to the `userToken` when unsetting the `authenticatedUserToken`', () => {
const {
insightsClient,
libraryLoadedAndProcessQueue,
instantSearchInstance,
getUserToken,
} = createUmdTestEnvironment();

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

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

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

describe('umd', () => {
it('applies userToken from queue if exists', () => {
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,13 @@ export function createInsightsMiddleware<
);

let queuedUserToken: string | undefined = undefined;
let queuedAuthenticatedUserToken: string | undefined = undefined;
let userTokenBeforeInit: string | undefined = undefined;
let authenticatedUserTokenBeforeInit: string | undefined = undefined;

if (Array.isArray(insightsClient.queue)) {
const { queue } = insightsClient;

if (Array.isArray(queue)) {
// Context: The umd build of search-insights is asynchronously loaded by the snippet.
//
// When user calls `aa('setUserToken', 'my-user-token')` before `search-insights` is loaded,
Expand All @@ -125,21 +129,30 @@ export function createInsightsMiddleware<
// At this point, even though `search-insights` is not loaded yet,
// we still want to read the token from the queue.
// Otherwise, the first search call will be fired without the token.
[, queuedUserToken] =
find(
insightsClient.queue.slice().reverse(),
([method]) => method === 'setUserToken'
) || [];
[queuedUserToken, queuedAuthenticatedUserToken] = [
'setUserToken',
'setAuthenticatedUserToken',
].map((key) => {
const [, value] =
find(queue.slice().reverse(), ([method]) => method === key) || [];

return value;
});
}

// If user called `aa('setUserToken')` or `aa('setAuthenticatedUserToken')`
// before creating the Insights middleware, we temporarily store the token
// and set it later on.
//
// Otherwise, the `init` call might override them with anonymous user token.
insightsClient('getUserToken', null, (_error, userToken) => {
// If user has called `aa('setUserToken', 'my-user-token')` before creating
// the `insights` middleware, we store them temporarily and
// set it later on.
//
// Otherwise, the `init` call might override it with anonymous user token.
userTokenBeforeInit = normalizeUserToken(userToken);
});

insightsClient('getAuthenticatedUserToken', null, (_error, userToken) => {
authenticatedUserTokenBeforeInit = normalizeUserToken(userToken);
});

// Only `init` if the `insightsInitParams` option is passed or
// if the `insightsClient` version doesn't supports optional `init` calling.
if (insightsInitParams || !isModernInsightsClient(insightsClient)) {
Expand Down Expand Up @@ -241,21 +254,64 @@ export function createInsightsMiddleware<
setUserTokenToSearch(anonymousUserToken, true);
}

// 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, true);
insightsClient('setUserToken', userTokenBeforeInit);
} else if (queuedUserToken) {
setUserTokenToSearch(queuedUserToken, true);
insightsClient('setUserToken', queuedUserToken);
function setUserToken(
token: string | number,
userToken?: string | number,
authenticatedUserToken?: string | number
) {
setUserTokenToSearch(token, true);

if (userToken) {
insightsClient('setUserToken', userToken);
}
if (authenticatedUserToken) {
insightsClient('setAuthenticatedUserToken', authenticatedUserToken);
}
}

// We consider the `userToken` or `authenticatedUserToken` before an
// `init` call of higher importance than one from the queue.
const tokenBeforeInit =
authenticatedUserTokenBeforeInit || userTokenBeforeInit;
const queuedToken = queuedAuthenticatedUserToken || queuedUserToken;

if (tokenBeforeInit) {
setUserToken(
tokenBeforeInit,
userTokenBeforeInit,
authenticatedUserTokenBeforeInit
);
} else if (queuedToken) {
setUserToken(
queuedToken,
queuedUserToken,
queuedAuthenticatedUserToken
);
}

// This updates userToken which is set explicitly by `aa('setUserToken', userToken)`
insightsClient('onUserTokenChange', setUserTokenToSearch, {
immediate: true,
});

// This updates userToken which is set explicitly by `aa('setAuthenticatedtUserToken', authenticatedUserToken)`
insightsClient(
'onAuthenticatedUserTokenChange',
(authenticatedUserToken) => {
// If we're unsetting the `authenticatedUserToken`, we revert to the `userToken`
if (!authenticatedUserToken) {
insightsClient('getUserToken', null, (_, userToken) => {
setUserTokenToSearch(userToken);
});
}

setUserTokenToSearch(authenticatedUserToken);
},
{
immediate: true,
}
);

type InsightsClientWithLocalCredentials = <
TMethod extends InsightsMethod
>(
Expand Down Expand Up @@ -323,6 +379,7 @@ See documentation: https://www.algolia.com/doc/guides/building-search-ui/going-f
},
unsubscribe() {
insightsClient('onUserTokenChange', undefined);
insightsClient('onAuthenticatedUserTokenChange', undefined);
instantSearchInstance.sendEventToInsights = noop;
if (helper && initialParameters) {
helper.overrideStateWithoutTriggeringChangeEvent({
Expand Down
6 changes: 3 additions & 3 deletions tests/common/shared/insights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function createInsightsTests(
});

// initial calls because the middleware is attached
expect(window.aa).toHaveBeenCalledTimes(4);
expect(window.aa).toHaveBeenCalledTimes(6);
expect(window.aa).toHaveBeenCalledWith(
'addAlgoliaAgent',
'insights-middleware'
Expand Down Expand Up @@ -151,7 +151,7 @@ export function createInsightsTests(
await setup(options);

// initial calls because the middleware is attached
expect(window.aa).toHaveBeenCalledTimes(3);
expect(window.aa).toHaveBeenCalledTimes(5);
expect(window.aa).toHaveBeenCalledWith(
'addAlgoliaAgent',
'insights-middleware'
Expand All @@ -164,7 +164,7 @@ export function createInsightsTests(
});

// Once result is available
expect(window.aa).toHaveBeenCalledTimes(4);
expect(window.aa).toHaveBeenCalledTimes(6);
expect(window.aa).toHaveBeenCalledWith(
'viewedObjectIDs',
{
Expand Down

0 comments on commit a20715c

Please sign in to comment.