Skip to content

Commit cb40c2f

Browse files
committed
Allow using localStorage for humanity check
1 parent 9ddffda commit cb40c2f

5 files changed

Lines changed: 92 additions & 7 deletions

File tree

spec/src/utils/humanity-check.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,77 @@ describe('ConstructorIO - Utils - Humanity Check', () => {
5353
});
5454
});
5555

56+
describe('humanityCheckLocation', () => {
57+
const storageKey = '_constructorio_is_human';
58+
let cleanup;
59+
60+
beforeEach(() => {
61+
global.CLIENT_VERSION = 'cio-mocha';
62+
63+
cleanup = jsdom();
64+
});
65+
66+
afterEach(() => {
67+
delete global.CLIENT_VERSION;
68+
cleanup();
69+
70+
helpers.clearStorage();
71+
});
72+
73+
it('Should write to sessionStorage by default (no option)', () => {
74+
const _ = new HumanityCheck();
75+
76+
helpers.triggerResize();
77+
expect(store.session.get(storageKey)).to.equal(true);
78+
expect(store.local.get(storageKey)).to.equal(null);
79+
});
80+
81+
it('Should write to sessionStorage when humanityCheckLocation is "session"', () => {
82+
const _ = new HumanityCheck({ humanityCheckLocation: 'session' });
83+
84+
helpers.triggerResize();
85+
expect(store.session.get(storageKey)).to.equal(true);
86+
expect(store.local.get(storageKey)).to.equal(null);
87+
});
88+
89+
it('Should write to localStorage when humanityCheckLocation is "local"', () => {
90+
const _ = new HumanityCheck({ humanityCheckLocation: 'local' });
91+
92+
helpers.triggerResize();
93+
expect(store.local.get(storageKey)).to.equal(true);
94+
expect(store.session.get(storageKey)).to.equal(null);
95+
});
96+
97+
it('Should read existing localStorage value on construction when location is "local"', () => {
98+
store.local.set(storageKey, true);
99+
const humanity = new HumanityCheck({ humanityCheckLocation: 'local' });
100+
101+
expect(humanity.hasPerformedHumanEvent).to.equal(true);
102+
});
103+
104+
it('Should NOT read sessionStorage when humanityCheckLocation is "local" (isolation)', () => {
105+
store.session.set(storageKey, true);
106+
const humanity = new HumanityCheck({ humanityCheckLocation: 'local' });
107+
108+
expect(humanity.hasPerformedHumanEvent).to.equal(false);
109+
});
110+
111+
it('Should return false from isBot after human action when using localStorage mode', () => {
112+
const humanity = new HumanityCheck({ humanityCheckLocation: 'local' });
113+
114+
expect(humanity.isBot()).to.equal(true);
115+
helpers.triggerResize();
116+
expect(humanity.isBot()).to.equal(false);
117+
});
118+
119+
it('Should support getIsHumanFromSessionStorage alias', () => {
120+
store.local.set(storageKey, true);
121+
const humanity = new HumanityCheck({ humanityCheckLocation: 'local' });
122+
123+
expect(humanity.getIsHumanFromSessionStorage()).to.equal(true);
124+
});
125+
});
126+
56127
describe('isBot', () => {
57128
const storageKey = '_constructorio_is_human';
58129
let cleanup;

src/constructorio.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class ConstructorIO {
5757
* @param {boolean} [parameters.eventDispatcher.waitForBeacon=true] - Wait for beacon before dispatching events
5858
* @param {object} [parameters.networkParameters] - Parameters relevant to network requests
5959
* @param {number} [parameters.networkParameters.timeout] - Request timeout (in milliseconds) - may be overridden within individual method calls
60+
* @param {string} [parameters.humanityCheckLocation='session'] - Storage location for the humanity check flag ('session' for sessionStorage, 'local' for localStorage)
6061
* @property {object} search - Interface to {@link module:search}
6162
* @property {object} browse - Interface to {@link module:browse}
6263
* @property {object} autocomplete - Interface to {@link module:autocomplete}
@@ -89,6 +90,7 @@ class ConstructorIO {
8990
idOptions,
9091
beaconMode,
9192
networkParameters,
93+
humanityCheckLocation,
9294
} = options;
9395

9496
if (!apiKey || typeof apiKey !== 'string') {
@@ -136,6 +138,7 @@ class ConstructorIO {
136138
eventDispatcher,
137139
beaconMode: (beaconMode === false) ? false : true, // Defaults to 'true',
138140
networkParameters: networkParameters || {},
141+
humanityCheckLocation: humanityCheckLocation || 'session',
139142
};
140143

141144
// Expose global modules

src/types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export interface ConstructorClientOptions {
6464
eventDispatcher?: EventDispatcherOptions;
6565
beaconMode?: boolean;
6666
networkParameters?: NetworkParameters;
67+
humanityCheckLocation?: 'session' | 'local';
6768
}
6869

6970
export interface RequestFeature extends Record<string, any> {

src/utils/humanity-check.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@ const humanEvents = [
1717
];
1818

1919
class HumanityCheck {
20-
constructor() {
20+
constructor(options = {}) {
21+
const { humanityCheckLocation } = options;
22+
23+
// Resolve storage backend: 'local' for localStorage, 'session' (default) for sessionStorage
24+
this.store = humanityCheckLocation === 'local' ? store.local : store.session;
25+
2126
// Check if a human event has been performed in the past
22-
this.hasPerformedHumanEvent = this.getIsHumanFromSessionStorage();
27+
this.hasPerformedHumanEvent = this.getIsHumanFromStorage();
2328

2429
// Humanity proved, remove handlers
2530
const remove = () => {
2631
this.hasPerformedHumanEvent = true;
2732

28-
store.session.set(storageKey, true);
33+
this.store.set(storageKey, true);
2934
humanEvents.forEach((eventType) => {
3035
helpers.removeEventListener(eventType, remove, true);
3136
});
@@ -39,9 +44,14 @@ class HumanityCheck {
3944
}
4045
}
4146

42-
// Helper function to grab the human variable from session storage
47+
// Helper function to grab the human variable from storage
48+
getIsHumanFromStorage() {
49+
return !!this.store.get(storageKey) || false;
50+
}
51+
52+
// Backward-compatible alias
4353
getIsHumanFromSessionStorage() {
44-
return !!store.session.get(storageKey) || false;
54+
return this.getIsHumanFromStorage();
4555
}
4656

4757
// Return boolean indicating if user is a bot
@@ -58,7 +68,7 @@ class HumanityCheck {
5868
}
5969

6070
// If the user hasn't performed a human event, it indicates it is a bot
61-
if (!this.getIsHumanFromSessionStorage()) {
71+
if (!this.getIsHumanFromStorage()) {
6272
return true;
6373
}
6474

src/utils/request-queue.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class RequestQueue {
1111
constructor(options, eventemitter) {
1212
this.options = options;
1313
this.eventemitter = eventemitter;
14-
this.humanity = new HumanityCheck();
14+
this.humanity = new HumanityCheck(options);
1515
this.requestPending = false;
1616
this.pageUnloading = false;
1717

0 commit comments

Comments
 (0)