diff --git a/CHANGELOG.md b/CHANGELOG.md index 7619ede41..4c09c7184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic +## [Unreleased] + +### Added + +- `trusted-set-session-storage-item` scriptlet [#426] + +[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v1.11.6...HEAD +[#426]: https://github.com/AdguardTeam/Scriptlets/issues/426 + ## [v1.11.6] - 2024-07-08 ### Added diff --git a/src/scriptlets/scriptlets-list.js b/src/scriptlets/scriptlets-list.js index 4b275d5b6..e2d18935b 100644 --- a/src/scriptlets/scriptlets-list.js +++ b/src/scriptlets/scriptlets-list.js @@ -55,6 +55,7 @@ export * from './trusted-set-cookie'; export * from './trusted-set-cookie-reload'; export * from './trusted-replace-fetch-response'; export * from './trusted-set-local-storage-item'; +export * from './trusted-set-session-storage-item'; export * from './trusted-set-constant'; export * from './inject-css-in-shadow-dom'; export * from './remove-node-text'; diff --git a/src/scriptlets/trusted-set-session-storage-item.ts b/src/scriptlets/trusted-set-session-storage-item.ts new file mode 100644 index 000000000..bd9753af6 --- /dev/null +++ b/src/scriptlets/trusted-set-session-storage-item.ts @@ -0,0 +1,103 @@ +import { + hit, + logMessage, + nativeIsNaN, + setStorageItem, + parseKeywordValue, +} from '../helpers/index'; + +/* eslint-disable max-len */ +/** + * @trustedScriptlet trusted-set-session-storage-item + * + * @description + * Adds item with arbitrary key and value to sessionStorage object, or updates the value of the key if it already exists. + * Scriptlet won't set item if storage is full. + * + * ### Syntax + * + * ```adblock + * example.com#%#//scriptlet('trusted-set-session-storage-item', 'key', 'value') + * ``` + * + * - `key` — required, key name to be set. + * - `value` — required, key value; possible values: + * - arbitrary value + * - `$now$` keyword for setting current time in ms, corresponds to `Date.now()` and `(new Date).getTime()` calls + * - `$currentDate$` keyword for setting string representation of the current date and time, + * corresponds to `Date()` and `(new Date).toString()` calls + * + * ### Examples + * + * 1. Set session storage item + * + * + * + * ```adblock + * example.org#%#//scriptlet('trusted-set-session-storage-item', 'player.live.current.mute', 'false') + * + * example.org#%#//scriptlet('trusted-set-session-storage-item', 'COOKIE_CONSENTS', '{"preferences":3,"flag":false}') + * + * example.org#%#//scriptlet('trusted-set-session-storage-item', 'providers', '[16364,88364]') + * + * example.org#%#//scriptlet('trusted-set-session-storage-item', 'providers', '{"providers":[123,456],"consent":"all"}') + * ``` + * + * + * + * 1. Set item with current time since unix epoch in ms + * + * ```adblock + * example.org#%#//scriptlet('trusted-set-session-storage-item', 'player.live.current.play', '$now$') + * ``` + * + * 1. Set item with current date, e.g 'Tue Nov 08 2022 13:53:19 GMT+0300' + * + * ```adblock + * example.org#%#//scriptlet('trusted-set-session-storage-item', 'player.live.current.play', '$currentDate$') + * ``` + * + * 1. Set item without value + * + * ```adblock + * example.org#%#//scriptlet('trusted-set-session-storage-item', 'ppu_main_none', '') + * ``` + * + * @added unknown. + */ +/* eslint-enable max-len */ + +export function trustedSetSessionStorageItem( + source: Source, + key: string, + value: string, +) { + if (typeof key === 'undefined') { + logMessage(source, 'Item key should be specified'); + return; + } + + if (typeof value === 'undefined') { + logMessage(source, 'Item value should be specified'); + return; + } + + const parsedValue = parseKeywordValue(value); + + const { sessionStorage } = window; + setStorageItem(source, sessionStorage, key, parsedValue); + hit(source); +} + +trustedSetSessionStorageItem.names = [ + 'trusted-set-session-storage-item', + // trusted scriptlets support no aliases +]; + +trustedSetSessionStorageItem.injections = [ + hit, + logMessage, + nativeIsNaN, + setStorageItem, + parseKeywordValue, +]; diff --git a/tests/scriptlets/index.test.js b/tests/scriptlets/index.test.js index 8ad40dcef..45cfc9f71 100644 --- a/tests/scriptlets/index.test.js +++ b/tests/scriptlets/index.test.js @@ -50,6 +50,7 @@ import './trusted-click-element.test'; import './trusted-set-cookie.test'; import './trusted-replace-fetch-response.test'; import './trusted-set-local-storage-item.test'; +import './trusted-set-session-storage-item.test'; import './trusted-set-constant.test'; import './inject-css-in-shadow-dom.test'; import './remove-node-text.test'; diff --git a/tests/scriptlets/trusted-set-session-storage-item.test.js b/tests/scriptlets/trusted-set-session-storage-item.test.js new file mode 100644 index 000000000..855a6ec7a --- /dev/null +++ b/tests/scriptlets/trusted-set-session-storage-item.test.js @@ -0,0 +1,140 @@ +/* eslint-disable no-underscore-dangle */ +import { runScriptlet, clearGlobalProps, isSafariBrowser } from '../helpers'; + +const { test, module } = QUnit; +const name = 'trusted-set-session-storage-item'; + +const beforeEach = () => { + window.__debug = () => { + window.hit = 'FIRED'; + }; +}; + +const afterEach = () => { + clearGlobalProps('hit', '__debug'); +}; + +module(name, { beforeEach, afterEach }); + +const clearStorageItem = (iName) => { + window.sessionStorage.removeItem(iName); +}; + +if (isSafariBrowser()) { + test('unsupported', (assert) => { + assert.ok(true, 'does not work in Safari 10 while browserstack auto tests run'); + }); +} else { + test('Set sessionStorage item', (assert) => { + let iName = '__test-item_true'; + let iValue = 'true'; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), 'true', 'sessionStorage item has been set'); + clearStorageItem(iName); + + iName = '__test-item_false'; + iValue = 'false'; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), 'false', 'sessionStorage item has been set'); + clearStorageItem(iName); + + iName = '__test-item_null'; + iValue = 'null'; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), 'null', 'sessionStorage item has been set'); + clearStorageItem(iName); + + iName = '__test-item_undefined'; + iValue = 'undefined'; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), 'undefined', 'sessionStorage item has been set'); + clearStorageItem(iName); + + iName = '__test-item_emptyStr'; + iValue = ''; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), '', 'sessionStorage item has been set'); + clearStorageItem(iName); + + iName = '__test-item_object'; + iValue = '{"preferences":3,"marketing":false}'; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), iValue, 'sessionStorage item has been set'); + clearStorageItem(iName); + + iName = '__test-item_array'; + iValue = '[1, 2, "test"]'; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), iValue, 'sessionStorage item has been set'); + clearStorageItem(iName); + + iName = '__test-item_string'; + iValue = 'some arbitrary item value 111'; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), iValue, 'sessionStorage item has been set'); + clearStorageItem(iName); + + iName = '__test-item_numbers'; + iValue = '123123'; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), iValue, 'sessionStorage item has been set'); + clearStorageItem(iName); + + iName = '__test-item_mix'; + iValue = '123string_!!:;@#$'; + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + assert.strictEqual(window.sessionStorage.getItem(iName), iValue, 'sessionStorage item has been set'); + clearStorageItem(iName); + }); + + test('Set sessionStorage item with $now$ keyword value', (assert) => { + const iName = '__test-item_now'; + const iValue = '$now$'; + + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + + // Some time will pass between calling scriptlet + // and qunit running assertion + const tolerance = 20; + const itemValue = window.sessionStorage.getItem(iName); + const currentTime = Date.now(); + const timeDiff = currentTime - itemValue; + + assert.ok(timeDiff < tolerance, 'Item value has been set to current time'); + + clearStorageItem(iName); + }); + + test('Set sessionStorage item with $currentDate$ keyword value', (assert) => { + const iName = '__test-item_current_date'; + const iValue = '$currentDate$'; + + runScriptlet(name, [iName, iValue]); + assert.strictEqual(window.hit, 'FIRED', 'Hit was fired'); + + const value = sessionStorage.getItem(iName); + const currentDate = new Date(); + const currentYear = currentDate.getFullYear(); + const currentMonth = currentDate.getMonth(); + const currentHour = currentDate.getHours(); + + const currentValue = new Date(value); + + assert.strictEqual(currentValue.getFullYear(), currentYear, 'Year matched'); + assert.strictEqual(currentValue.getMonth(), currentMonth, 'Month matched'); + assert.strictEqual(currentValue.getHours(), currentHour, 'Hour matched'); + + clearStorageItem(iName); + }); +}