-
Notifications
You must be signed in to change notification settings - Fork 529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(insights): introduce insights
middleware (1/4)
#4446
Changes from 30 commits
3963956
4331d24
77996c9
467a648
ec3f7bb
3ab96a2
020381e
a0cc96f
c642234
03e52c7
0ae09e2
2417c50
090fe28
c262700
2a49909
20254cf
10ce292
5a1fc34
963d305
daca533
000d708
602037a
8fd4698
b0b7add
e2a2743
21faadb
7f49f25
94e394b
ad0dafb
f34170d
ef95a87
183b1ca
e7c7e58
60af9e0
5067ec7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { getAppIdAndApiKey } from '../getAppIdAndApiKey'; | ||
import algoliasearchV4 from 'algoliasearch'; | ||
import algoliasearchV3 from 'algoliasearch-v3'; | ||
|
||
const APP_ID = 'myAppId'; | ||
const API_KEY = 'myApiKey'; | ||
|
||
describe('getAppIdAndApiKey', () => { | ||
it('gets appId and apiKey from searchClient@v4', () => { | ||
const searchClient = algoliasearchV4(APP_ID, API_KEY); | ||
const [appId, apiKey] = getAppIdAndApiKey(searchClient); | ||
expect(appId).toEqual(APP_ID); | ||
expect(apiKey).toEqual(API_KEY); | ||
}); | ||
|
||
it('gets appId and apiKey from searchClient@v3', () => { | ||
const searchClient = algoliasearchV3(APP_ID, API_KEY); | ||
const [appId, apiKey] = getAppIdAndApiKey(searchClient); | ||
expect(appId).toEqual(APP_ID); | ||
expect(apiKey).toEqual(API_KEY); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
export function getAppIdAndApiKey(searchClient) { | ||
if (searchClient.transporter) { | ||
// searchClient v4 | ||
const { headers, queryParameters } = searchClient.transporter; | ||
const APP_ID = 'x-algolia-application-id'; | ||
const API_KEY = 'x-algolia-api-key'; | ||
const appId = headers[APP_ID] || queryParameters[APP_ID]; | ||
const apiKey = headers[API_KEY] || queryParameters[API_KEY]; | ||
return [appId, apiKey]; | ||
} else { | ||
// searchClient v3 | ||
return [searchClient.applicationID, searchClient.apiKey]; | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { createInsightsMiddleware } from './insights'; | ||
export * from './createRouter'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We exports the types from the router middleware. Any opinions on aligning the two? (either exporting the types or not exporting the types) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's good to export all of them e7c7e58 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { InsightsClient, Middleware } from '../types'; | ||
import { getInsightsAnonymousUserToken } from '../helpers'; | ||
import { warning, noop, getAppIdAndApiKey } from '../lib/utils'; | ||
|
||
export type InsightsProps = { | ||
insightsClient: null | InsightsClient; | ||
}; | ||
|
||
export type CreateInsightsMiddleware = (props: InsightsProps) => Middleware; | ||
|
||
export const createInsightsMiddleware: CreateInsightsMiddleware = props => { | ||
const { insightsClient: _insightsClient } = props; | ||
if (_insightsClient !== null && !_insightsClient) { | ||
if (__DEV__) { | ||
throw new Error( | ||
"The `insightsClient` option is required if you want userToken to be automatically set in search calls. If you don't want this behaviour, set it to `null`." | ||
); | ||
} else { | ||
throw new Error( | ||
'The `insightsClient` option is required. To disable, set it to `null`.' | ||
); | ||
} | ||
} | ||
|
||
const hasInsightsClient = Boolean(_insightsClient); | ||
const insightsClient = | ||
_insightsClient === null ? (noop as InsightsClient) : _insightsClient; | ||
|
||
return ({ instantSearchInstance }) => { | ||
insightsClient('_get', '_hasCredentials', (hasCredentials: boolean) => { | ||
if (!hasCredentials) { | ||
const [appId, apiKey] = getAppIdAndApiKey(instantSearchInstance.client); | ||
insightsClient('_get', '_userToken', (userToken: string) => { | ||
warning( | ||
!userToken, | ||
`You set userToken before \`createInsightsMiddleware()\` and it is ignored. | ||
Please set the token after the \`createInsightsMiddleware()\` call. | ||
|
||
createInsightsMiddleware({ /* ... */ }); | ||
|
||
insightsClient('setUserToken', 'your-user-token'); | ||
// or | ||
aa('setUserToken', 'your-user-token'); | ||
` | ||
); | ||
}); | ||
insightsClient('init', { appId, apiKey }); | ||
} | ||
}); | ||
|
||
return { | ||
onStateChange() {}, | ||
subscribe() { | ||
const setUserTokenToSearch = (userToken?: string) => { | ||
// At the time this middleware is subscribed, `mainIndex.init()` is already called. | ||
// It means `mainIndex.getHelper()` exists. | ||
if (userToken) { | ||
instantSearchInstance.mainIndex | ||
.getHelper()! | ||
.setQueryParameter('userToken', userToken); | ||
} | ||
}; | ||
|
||
instantSearchInstance.mainIndex | ||
.getHelper()! | ||
.setQueryParameter('clickAnalytics', true); | ||
|
||
if (hasInsightsClient) { | ||
// When `aa('init', { ... })` is called, it creates an anonymous user token in cookie. | ||
// We can set it as userToken. | ||
setUserTokenToSearch(getInsightsAnonymousUserToken()); | ||
} | ||
|
||
if (Array.isArray((insightsClient as any).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, | ||
// it is stored in `aa.queue` and we are reading it to set userToken to search call. | ||
// This queue is meant to be consumed whenever `search-insights` is loaded and when it runs `processQueue()`. | ||
// But the reason why we handle it here is to prevent the first search API from being triggered | ||
// without userToken because search-insights is not loaded yet. | ||
(insightsClient as any).queue.forEach(([method, firstArgument]) => { | ||
if (method === 'setUserToken') { | ||
setUserTokenToSearch(firstArgument); | ||
} | ||
}); | ||
} | ||
|
||
// This updates userToken which is set explicitly by `aa('setUserToken', userToken)` | ||
insightsClient('onUserTokenChange', setUserTokenToSearch, { | ||
immediate: true, | ||
}); | ||
}, | ||
unsubscribe() { | ||
insightsClient('onUserTokenChange', undefined); | ||
}, | ||
}; | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import InstantSearch from '../lib/InstantSearch'; | ||
import { UiState } from './widget'; | ||
|
||
export type MiddlewareDefinition = { | ||
onStateChange(options: { uiState: UiState }): void; | ||
subscribe(): void; | ||
unsubscribe(): void; | ||
}; | ||
|
||
export type MiddlewareOptions = { | ||
instantSearchInstance: InstantSearch; | ||
}; | ||
|
||
export type Middleware = (options: MiddlewareOptions) => MiddlewareDefinition; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3573,6 +3573,27 @@ algoliasearch-helper@^3.2.2: | |
dependencies: | ||
events "^1.1.1" | ||
|
||
"algoliasearch-v3@npm:algoliasearch@3": | ||
version "3.35.1" | ||
resolved "https://registry.npmjs.org/algoliasearch/-/algoliasearch-3.35.1.tgz#297d15f534a3507cab2f5dfb996019cac7568f0c" | ||
integrity sha512-K4yKVhaHkXfJ/xcUnil04xiSrB8B8yHZoFEhWNpXg23eiCnqvTZw1tn/SqvdsANlYHLJlKl0qi3I/Q2Sqo7LwQ== | ||
dependencies: | ||
agentkeepalive "^2.2.0" | ||
debug "^2.6.9" | ||
envify "^4.0.0" | ||
es6-promise "^4.1.0" | ||
events "^1.1.0" | ||
foreach "^2.0.5" | ||
global "^4.3.2" | ||
inherits "^2.0.1" | ||
isarray "^2.0.1" | ||
load-script "^1.0.0" | ||
object-keys "^1.0.11" | ||
querystring-es3 "^0.2.1" | ||
reduce "^1.0.1" | ||
semver "^5.1.0" | ||
tunnel-agent "^0.6.0" | ||
|
||
[email protected]: | ||
version "4.3.1" | ||
resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.3.1.tgz#dea6ad87705e0439855cf3e5a4406b74e794b874" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be consistent with the file naming, we can rename the file
createInsightsMiddleware
(and perhapscreateRouterMiddleware
?).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
183b1ca good call!