Skip to content

Commit c1cbaf4

Browse files
author
Eunjae Lee
authoredMar 9, 2021
fix(bindEvent): escape payload correctly (#4670)
* fix(bindEvent): qoute the value * . * remove console.log * update snapshot * fix type error
1 parent 4497a93 commit c1cbaf4

File tree

10 files changed

+47
-31
lines changed

10 files changed

+47
-31
lines changed
 

‎src/connectors/hits/__tests__/connectHits-test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import algoliasearchHelper, {
22
SearchParameters,
33
SearchResults,
44
} from 'algoliasearch-helper';
5-
import { TAG_PLACEHOLDER } from '../../../lib/utils';
5+
import { TAG_PLACEHOLDER, deserializePayload } from '../../../lib/utils';
66
import connectHits from '../connectHits';
77
import {
88
createInitOptions,
@@ -844,7 +844,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/#co
844844
const payload = bindEvent('click', hits[0], 'Product Added');
845845
expect(payload.startsWith('data-insights-event=')).toBe(true);
846846
expect(
847-
JSON.parse(atob(payload.substr('data-insights-event='.length)))
847+
deserializePayload(payload.substr('data-insights-event='.length))
848848
).toEqual({
849849
eventType: 'click',
850850
hits: [
@@ -875,7 +875,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/#co
875875
const payload = bindEvent('conversion', hits[1], 'Product Ordered');
876876
expect(payload.startsWith('data-insights-event=')).toBe(true);
877877
expect(
878-
JSON.parse(atob(payload.substr('data-insights-event='.length)))
878+
deserializePayload(payload.substr('data-insights-event='.length))
879879
).toEqual({
880880
eventType: 'conversion',
881881
hits: [

‎src/connectors/infinite-hits/__tests__/connectInfiniteHits-test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
createRenderOptions,
1010
} from '../../../../test/mock/createWidget';
1111
import { createSingleSearchResponse } from '../../../../test/mock/createAPIResponse';
12-
import { TAG_PLACEHOLDER } from '../../../lib/utils';
12+
import { TAG_PLACEHOLDER, deserializePayload } from '../../../lib/utils';
1313
import connectInfiniteHits from '../connectInfiniteHits';
1414
import { createSearchClient } from '../../../../test/mock/createSearchClient';
1515

@@ -1436,7 +1436,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/infinite-hi
14361436
const payload = bindEvent('click', hits[0], 'Product Added');
14371437
expect(payload.startsWith('data-insights-event=')).toBe(true);
14381438
expect(
1439-
JSON.parse(atob(payload.substr('data-insights-event='.length)))
1439+
deserializePayload(payload.substr('data-insights-event='.length))
14401440
).toEqual({
14411441
eventType: 'click',
14421442
hits: [
@@ -1467,7 +1467,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/infinite-hi
14671467
const payload = bindEvent('conversion', hits[1], 'Product Ordered');
14681468
expect(payload.startsWith('data-insights-event=')).toBe(true);
14691469
expect(
1470-
JSON.parse(atob(payload.substr('data-insights-event='.length)))
1470+
deserializePayload(payload.substr('data-insights-event='.length))
14711471
).toEqual({
14721472
eventType: 'conversion',
14731473
hits: [

‎src/helpers/__tests__/insights-test.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import insights, {
33
readDataAttributes,
44
hasDataAttributes,
55
} from '../insights';
6-
import { warning } from '../../lib/utils';
6+
import { warning, serializePayload } from '../../lib/utils';
77

88
const makeDomElement = (html: string): HTMLElement => {
99
const div = document.createElement('div');
@@ -19,7 +19,7 @@ describe('insights', () => {
1919
eventName: 'Add to Cart',
2020
})
2121
).toMatchInlineSnapshot(
22-
`"data-insights-method=\\"clickedObjectIDsAfterSearch\\" data-insights-payload=\\"eyJvYmplY3RJRHMiOlsiMyJdLCJldmVudE5hbWUiOiJBZGQgdG8gQ2FydCJ9\\""`
22+
`"data-insights-method=\\"clickedObjectIDsAfterSearch\\" data-insights-payload=\\"JTdCJTIyb2JqZWN0SURzJTIyJTNBJTVCJTIyMyUyMiU1RCUyQyUyMmV2ZW50TmFtZSUyMiUzQSUyMkFkZCUyMHRvJTIwQ2FydCUyMiU3RA==\\""`
2323
);
2424
});
2525

@@ -50,7 +50,7 @@ describe('writeDataAttributes', () => {
5050
},
5151
})
5252
).toMatchInlineSnapshot(
53-
`"data-insights-method=\\"clickedObjectIDsAfterSearch\\" data-insights-payload=\\"eyJvYmplY3RJRHMiOlsiMyJdLCJldmVudE5hbWUiOiJBZGQgdG8gQ2FydCJ9\\""`
53+
`"data-insights-method=\\"clickedObjectIDsAfterSearch\\" data-insights-payload=\\"JTdCJTIyb2JqZWN0SURzJTIyJTNBJTVCJTIyMyUyMiU1RCUyQyUyMmV2ZW50TmFtZSUyMiUzQSUyMkFkZCUyMHRvJTIwQ2FydCUyMiU3RA==\\""`
5454
);
5555
});
5656
it('should reject undefined payloads', () => {
@@ -115,9 +115,10 @@ describe('readDataAttributes', () => {
115115
let domElement: HTMLElement;
116116

117117
beforeEach(() => {
118-
const payload = btoa(
119-
JSON.stringify({ objectIDs: ['3'], eventName: 'Add to Cart' })
120-
);
118+
const payload = serializePayload({
119+
objectIDs: ['3'],
120+
eventName: 'Add to Cart',
121+
});
121122
domElement = makeDomElement(
122123
`<button
123124
data-insights-method="clickedObjectIDsAfterSearch"

‎src/helpers/insights.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { InsightsClientMethod, InsightsClientPayload } from '../types';
2-
import { warning } from '../lib/utils';
2+
import { warning, serializePayload, deserializePayload } from '../lib/utils';
33

44
export function readDataAttributes(
55
domElement: HTMLElement
@@ -20,8 +20,8 @@ export function readDataAttributes(
2020
}
2121

2222
try {
23-
const payload: Partial<InsightsClientPayload> = JSON.parse(
24-
atob(serializedPayload)
23+
const payload: Partial<InsightsClientPayload> = deserializePayload(
24+
serializedPayload
2525
);
2626
return { method, payload };
2727
} catch (error) {
@@ -49,7 +49,7 @@ export function writeDataAttributes({
4949
let serializedPayload: string;
5050

5151
try {
52-
serializedPayload = btoa(JSON.stringify(payload));
52+
serializedPayload = serializePayload(payload);
5353
} catch (error) {
5454
throw new Error(`Could not JSON serialize the payload object.`);
5555
}

‎src/lib/__tests__/insights-listener-test.tsx

+13-9
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
import { h } from 'preact';
44
import { render, fireEvent } from '@testing-library/preact';
55
import withInsightsListener from '../insights/listener';
6+
import { serializePayload } from '../../lib/utils';
67

78
describe('withInsightsListener', () => {
89
it('should capture clicks performed on inner elements with data-insights-method defined', () => {
9-
const payload = btoa(
10-
JSON.stringify({ objectIDs: ['1'], eventName: 'Add to Cart' })
11-
);
10+
const payload = serializePayload({
11+
objectIDs: ['1'],
12+
eventName: 'Add to Cart',
13+
});
1214

1315
const Hits = () => (
1416
<div>
@@ -62,9 +64,10 @@ describe('withInsightsListener', () => {
6264
});
6365

6466
it('should capture clicks performed on inner elements whose parents have data-insights-method defined', () => {
65-
const payload = btoa(
66-
JSON.stringify({ objectIDs: ['1'], eventName: 'Product Clicked' })
67-
);
67+
const payload = serializePayload({
68+
objectIDs: ['1'],
69+
eventName: 'Product Clicked',
70+
});
6871

6972
const Hits = () => (
7073
<div>
@@ -118,9 +121,10 @@ describe('withInsightsListener', () => {
118121
});
119122

120123
it('should not capture clicks performed on inner elements with no data-insights-method defined', () => {
121-
const payload = btoa(
122-
JSON.stringify({ objectIDs: ['1'], eventName: 'Add to Cart' })
123-
);
124+
const payload = serializePayload({
125+
objectIDs: ['1'],
126+
eventName: 'Add to Cart',
127+
});
124128

125129
const Hits = () => (
126130
<div>

‎src/lib/insights/listener.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** @jsx h */
22

33
import { h } from 'preact';
4+
import { deserializePayload } from '../utils';
45
import { readDataAttributes, hasDataAttributes } from '../../helpers/insights';
56
import { InsightsClientWrapper } from '../../types';
67
import { InsightsEvent } from '../../middlewares/createInsightsMiddleware';
@@ -26,7 +27,9 @@ const findInsightsTarget = (
2627
return element;
2728
};
2829

29-
const parseInsightsEvent = element => {
30+
type ParseInsightsEvent = (element: HTMLElement) => InsightsEvent;
31+
32+
const parseInsightsEvent: ParseInsightsEvent = element => {
3033
const serializedPayload = element.getAttribute('data-insights-event');
3134

3235
if (typeof serializedPayload !== 'string') {
@@ -36,7 +39,7 @@ const parseInsightsEvent = element => {
3639
}
3740

3841
try {
39-
return JSON.parse(atob(serializedPayload));
42+
return deserializePayload(serializedPayload) as InsightsEvent;
4043
} catch (error) {
4144
throw new Error(
4245
'The insights middleware was unable to parse `data-insights-event`.'

‎src/lib/utils/__tests__/createSendEventForHits-test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
createBindEventForHits,
44
createSendEventForHits,
55
} from '../createSendEventForHits';
6+
import { deserializePayload } from '../../utils';
67

78
const createTestEnvironment = () => {
89
const instantSearchInstance = createInstantSearch();
@@ -227,7 +228,7 @@ describe('createSendEventForHits', () => {
227228
describe('createBindEventForHits', () => {
228229
function parsePayload(payload) {
229230
expect(payload.startsWith('data-insights-event=')).toBe(true);
230-
return JSON.parse(atob(payload.substr('data-insights-event='.length)));
231+
return deserializePayload(payload.substr('data-insights-event='.length));
231232
}
232233

233234
it('returns a payload for click event', () => {

‎src/lib/utils/createSendEventForHits.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { InstantSearch, Hit, Hits, EscapedHits } from '../../types';
2+
import { serializePayload } from '../../lib/utils';
23
import { InsightsEvent } from '../../middlewares/createInsightsMiddleware';
34

45
type BuiltInSendEventForHits = (
@@ -162,9 +163,7 @@ export function createBindEventForHits({
162163
methodName: 'bindEvent',
163164
args,
164165
});
165-
return payload
166-
? `data-insights-event=${btoa(JSON.stringify(payload))}`
167-
: '';
166+
return payload ? `data-insights-event=${serializePayload(payload)}` : '';
168167
};
169168
return bindEventForHits;
170169
}

‎src/lib/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@ export { getAppIdAndApiKey } from './getAppIdAndApiKey';
5454
export { convertNumericRefinementsToFilters } from './convertNumericRefinementsToFilters';
5555
export { createConcurrentSafePromise } from './createConcurrentSafePromise';
5656
export { debounce } from './debounce';
57+
export { serializePayload, deserializePayload } from './serializer';

‎src/lib/utils/serializer.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function serializePayload(payload: object): string {
2+
return btoa(encodeURIComponent(JSON.stringify(payload)));
3+
}
4+
5+
export function deserializePayload(payload: string): object {
6+
return JSON.parse(decodeURIComponent(atob(payload)));
7+
}

0 commit comments

Comments
 (0)
Please sign in to comment.