Skip to content

Commit

Permalink
redo cookie parsing and matching, update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
stanislav-atr committed Oct 11, 2022
1 parent ae82d2d commit 991d55d
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 30 deletions.
29 changes: 29 additions & 0 deletions src/helpers/prepare-cookie.js → src/helpers/cookie-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,32 @@ export const prepareCookie = (name, value) => {

return cookieData;
};

/**
* Parses cookie string into object
* @param {string} cookieString string that conforms to document.cookie format
* @returns {object} key:value object that corresponds with incoming cookies keys and values
*/
export const parseCookieString = (cookieString) => {
const COOKIE_DELIMITER = '=';
const COOKIE_PAIRS_DELIMITER = ';';

return cookieString
.split(COOKIE_PAIRS_DELIMITER)
.reduce((result, string) => {
const delimiterIndex = string.indexOf(COOKIE_DELIMITER);
let cookieKey;
let cookieValue;
if (delimiterIndex === -1) {
cookieKey = string.trim();
} else {
cookieKey = string.slice(0, delimiterIndex).trim();
cookieValue = string.slice(delimiterIndex + 1, string.length);
}

return Object.defineProperty(result, cookieKey, {
value: cookieValue || null,
enumerable: true,
});
}, {});
};
2 changes: 1 addition & 1 deletion src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export * from './observer';
export * from './match-stack';
export * from './open-shadow-dom-utils';
export * from './array-utils';
export * from './prepare-cookie';
export * from './cookie-utils';
export * from './number-utils';
export * from './adjust-set-utils';
export * from './fetch-utils';
Expand Down
74 changes: 55 additions & 19 deletions src/scriptlets/trusted-click-element.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
hit,
toRegExp,
parseCookieString,
} from '../helpers/index';

/* eslint-disable max-len */
Expand All @@ -19,11 +20,11 @@ import {
* - `selectors` — required, string with query selectors delimited by comma
* - `extraMatch` — optional, extra condition to check on a page; allows to match `cookie` and `localStorage`; can be set as `name:key[=value]` where `value` is optional.
* Multiple conditions are allowed inside one `extraMatch` but they should be delimited by comma and each of them should match the syntax. Possible `name`s:
* - `cookie` - test substring against document.cookie string
* - `cookie` - test string or regex against cookies on a page
* - `localStorage` - check if localStorage item is present
* - 'delay' - optional, time in ms to delay scriptlet execution, defaults to instant execution.
* **Examples**
* 1. Click elements by selector
* 1. Click single element by selector
* ```
* example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"]')
* ```
Expand All @@ -35,22 +36,27 @@ import {
*
* 3. Click multiple elements by selector with a delay
* ```
* example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"], button[name='check"], input[type="submit"][value="akkoord"]', '500')
* example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"], button[name='check"], input[type="submit"][value="akkoord"]', '', '500')
* ```
*
* 4. Match by cookie strings and click multiple elements by selector
* 4. Match cookies by keys using regex and string
* ```
* example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"], input[type="submit"][value="akkoord"]', '', 'cookie:userConsentCommunity, cookie:cmpconsent=1')
* example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"]', 'cookie:userConsentCommunity, cookie:/cmpconsent|cmp/', '')
* ```
*
* 5. Match by localStorage item 'promo' key
* 5. Match by cookie key=value pairs using regex and string
* ```
* example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"]', 'cookie:userConsentCommunity=true, cookie:/cmpconsent|cmp/=/[a-z]{1,5}/', '')
* ```
*
* 6. Match by localStorage item 'promo' key
* ```
* example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"]', '', 'localStorage:promo')
* ```
*
* 6. Click multiple elements with delay and matching by both cookie string and localStorage item
* 7. Click multiple elements with delay and matching by both cookie string and localStorage item
* ```
* example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"], input[type="submit"][value="akkoord"]', '250', 'cookie:cmpconsent, localStorage:promo')
* example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"], input[type="submit"][value="akkoord"]', 'cookie:cmpconsent, localStorage:promo', '250')
* ```
*/
/* eslint-enable max-len */
Expand All @@ -66,6 +72,7 @@ export function trustedClickElement(source, selectors, extraMatch = '', delay =
const COOKIE_MATCH_MARKER = 'cookie:';
const LOCAL_STORAGE_MATCH_MARKER = 'localStorage:';
const COMMON_DELIMITER = ',';
const COOKIE_STRING_DELIMITER = ';';

let parsedDelay;
if (delay) {
Expand All @@ -79,8 +86,8 @@ export function trustedClickElement(source, selectors, extraMatch = '', delay =

let canClick = !parsedDelay;

const cookieMatch = [];
const localStorageMatch = [];
const cookieMatches = [];
const localStorageMatches = [];

if (extraMatch) {
// Get all match marker:value pairs from argument
Expand All @@ -91,25 +98,53 @@ export function trustedClickElement(source, selectors, extraMatch = '', delay =
// Filter match pairs by marker
parsedExtraMatch.forEach((matchStr) => {
if (matchStr.indexOf(COOKIE_MATCH_MARKER) > -1) {
const cookieMatchValue = matchStr.replace(COOKIE_MATCH_MARKER, '');
cookieMatch.push(cookieMatchValue);
const cookieMatch = matchStr.replace(COOKIE_MATCH_MARKER, '');
cookieMatches.push(cookieMatch);
}
if (matchStr.indexOf(LOCAL_STORAGE_MATCH_MARKER) > -1) {
const localStorageMatchValue = matchStr.replace(LOCAL_STORAGE_MATCH_MARKER, '');
localStorageMatch.push(localStorageMatchValue);
const localStorageMatch = matchStr.replace(LOCAL_STORAGE_MATCH_MARKER, '');
localStorageMatches.push(localStorageMatch);
}
});
}

if (cookieMatch.length > 0) {
const cookieMatched = cookieMatch.every((str) => document.cookie.indexOf(str) !== -1);
if (!cookieMatched) {
if (cookieMatches.length > 0) {
const parsedCookieMatches = parseCookieString(cookieMatches.join(COOKIE_STRING_DELIMITER));
const parsedCookies = parseCookieString(document.cookie);
const cookieKeys = Object.keys(parsedCookies);
if (cookieKeys.length === 0) {
return;
}

const cookiesMatched = Object.keys(parsedCookieMatches).every((key) => {
// Avoid getting /.?/ result from toRegExp on undefined
// as cookie may be set without value,
// on which cookie parsing will return cookieKey:undefined pair
const valueMatch = parsedCookieMatches[key] ? toRegExp(parsedCookieMatches[key]) : null;
const keyMatch = toRegExp(key);

return cookieKeys.some((key) => {
const keysMatched = keyMatch.test(key);
if (!keysMatched) {
return false;
}

// Key matching is enough if cookie value match is not specified
if (!valueMatch) {
return true;
}

return valueMatch.test(parsedCookies[key]);
});
});

if (!cookiesMatched) {
return;
}
}

if (localStorageMatch.length > 0) {
const localStorageMatched = localStorageMatch
if (localStorageMatches.length > 0) {
const localStorageMatched = localStorageMatches
.every((str) => {
const itemValue = window.localStorage.getItem(str);
return itemValue || itemValue === '';
Expand Down Expand Up @@ -260,4 +295,5 @@ trustedClickElement.names = [
trustedClickElement.injections = [
hit,
toRegExp,
parseCookieString,
];
48 changes: 38 additions & 10 deletions tests/scriptlets/trusted-click-element.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,10 @@ test('Multiple elements clicked, non-ordered render', (assert) => {
});

test('extraMatch - single cookie match, matched', (assert) => {
const cName = 'first';
const cookieData = prepareCookie(cName, 'true');
const cookieKey1 = 'first';
const cookieData = prepareCookie(cookieKey1, 'true');
document.cookie = cookieData;
const EXTRA_MATCH_STR = `cookie:${cName}`;
const EXTRA_MATCH_STR = `cookie:${cookieKey1}`;

const ELEM_COUNT = 1;
// Check elements for being clicked and hit func execution
Expand All @@ -184,15 +184,15 @@ test('extraMatch - single cookie match, matched', (assert) => {
assert.strictEqual(window.hit, 'FIRED', 'hit func executed');
done();
}, 150);
clearCookie();
clearCookie(cookieKey1);
});

test('extraMatch - single cookie match, not matched', (assert) => {
const cName = 'first';
const cName2 = 'second';
const cookieData = prepareCookie(cName, 'true');
const cookieKey1 = 'first';
const cookieKey2 = 'second';
const cookieData = prepareCookie(cookieKey1, 'true');
document.cookie = cookieData;
const EXTRA_MATCH_STR = `cookie:${cName2}`;
const EXTRA_MATCH_STR = `cookie:${cookieKey2}`;

const ELEM_COUNT = 1;
// Check elements for being clicked and hit func execution
Expand All @@ -209,10 +209,38 @@ test('extraMatch - single cookie match, not matched', (assert) => {

setTimeout(() => {
assert.notOk(clickable.getAttribute('clicked'), 'Element should not be clicked');
assert.strictEqual(window.hit, undefined, 'hit shoud not fire');
assert.strictEqual(window.hit, undefined, 'hit should not fire');
done();
}, 150);
clearCookie(cookieKey1);
});

test('extraMatch - string+regex cookie input, matched', (assert) => {
const cookieKey1 = 'first';
const cookieVal1 = 'true';
const cookieData1 = prepareCookie(cookieKey1, cookieVal1);
document.cookie = cookieData1;
const EXTRA_MATCH_STR = 'cookie:/firs/=true';

const ELEM_COUNT = 1;
// Check elements for being clicked and hit func execution
const ASSERTIONS = ELEM_COUNT + 1;
assert.expect(ASSERTIONS);
const done = assert.async();

const selectorsString = `#${PANEL_ID} > #${CLICKABLE_NAME}${ELEM_COUNT}`;

runScriptlet(name, [selectorsString, EXTRA_MATCH_STR]);
const panel = createPanel();
const clickable = createClickable(1);
panel.appendChild(clickable);

setTimeout(() => {
assert.ok(clickable.getAttribute('clicked'), 'Element should be clicked');
assert.strictEqual(window.hit, 'FIRED', 'hit func executed');
done();
}, 150);
clearCookie();
clearCookie(cookieKey1);
});

test('extraMatch - single localStorage match, matched', (assert) => {
Expand Down

0 comments on commit 991d55d

Please sign in to comment.