Skip to content

Commit

Permalink
Add support for local and session storage (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaskupczyk authored Oct 23, 2023
1 parent f532b4b commit 94112e3
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 24 deletions.
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

# react-hook-consent

React consent management solution and banner for cookies and (external) scripts.
React consent management solution and banner for cookies, local storage, session storage and (external) scripts.

![react-hook-consent Screenshot](/assets/screenshot.png)

## Features

- Provides the consent context to components
- Loads (external) scripts based on consent state
- Deletes cookies when consent declined
- Deletes cookies, local storage and session storage when consent declined
- Hook to retrieve and change the current consent
- Optional Banner with detailed settings to approve / decline consent
- Persists the selection to local storage
Expand Down Expand Up @@ -84,14 +84,16 @@ import { ConsentProvider } from 'react-hook-consent';

The `services` array can be configured as follows:

| Name | Type | Required | Description |
| ----------- | ----------------------------------- | -------- | ------------------------------------------------------------------- |
| id | string | yes | A unique id for the service, e.g. 'myid' |
| name | string | yes | The name of the service, e.g. 'My Service' |
| description | string | | The description of the service, e.g. 'My ID is a tracking service.' |
| scripts | Array<ScriptExternal \| ScriptCode> | | External script or code to load upon consent |
| cookies | Cookie[] | | Configuration of cookies to delete them upon decline |
| mandatory | boolean | | If true, the service is mandatory and cannot be declined |
| Name | Type | Required | Description |
| -------------- | ----------------------------------- | -------- | ------------------------------------------------------------------- |
| id | string | yes | A unique id for the service, e.g. 'myid' |
| name | string | yes | The name of the service, e.g. 'My Service' |
| description | string | | The description of the service, e.g. 'My ID is a tracking service.' |
| scripts | Array<ScriptExternal \| ScriptCode> | | External script or code to load upon consent |
| cookies | Cookie[] | | Configuration of cookies to delete them upon decline |
| localStorage | string[] | | Configuration of local storage to delete them upon decline |
| sessionStorage | string[] | | Configuration of session storage to delete them upon decline |
| mandatory | boolean | | If true, the service is mandatory and cannot be declined |

`ScriptExternal` has the following options:

Expand Down
10 changes: 10 additions & 0 deletions examples/react-v18/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,21 @@ function App() {
document.cookie = `example_cookie=helloworld`;
}, []);

const setExampleLocalStorage = useCallback(() => {
localStorage.setItem('example_local_storage', 'helloworld');
}, []);

const setExampleSessionStorage = useCallback(() => {
sessionStorage.setItem('example_session_storage', 'helloworld');
}, []);

return (
<div className="App">
<header className="App-header">
<button onClick={() => toggleBanner()}>Toggle Consent Banner</button>
<button onClick={setExampleCookie}>Set example cookie</button>
<button onClick={setExampleLocalStorage}>Set example local storage</button>
<button onClick={setExampleSessionStorage}>Set example session storage</button>
<p>Has "myid" consent: {hasConsent('myid') ? <>Yes</> : <>No</>}</p>
</header>
</div>
Expand Down
14 changes: 7 additions & 7 deletions examples/react-v18/src/ConsentWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const consentOptions: ConsentOptions = {
services: [
{
id: 'myid',
name: 'MyName',
name: 'External Script and inline code',
description: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.',
scripts: [
{ id: 'external-script', src: 'https://myscript.com/script.js' },
Expand All @@ -18,25 +18,25 @@ const consentOptions: ConsentOptions = {
},
{
id: 'myid2',
name: 'MyName 2',
name: 'Cookie',
cookies: [{ pattern: 'example_cookie' }],
},
{
id: 'myid3',
name: 'MyName 2',
name: 'External Script and inline code',
description:
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.',
scripts: [{ id: 'external-script', src: 'https://myscript2.com/script.js' }],
},
{
id: 'myid4',
name: 'MyName 2',
scripts: [{ id: 'external-script', src: 'https://myscript2.com/script.js' }],
name: 'Local Storage',
localStorage: ['example_local_storage'],
},
{
id: 'myid5',
name: 'MyName 2',
scripts: [{ id: 'external-script', src: 'https://myscript2.com/script.js' }],
name: 'Session Storage',
sessionStorage: ['example_session_storage'],
},
{
id: 'myid6',
Expand Down
17 changes: 12 additions & 5 deletions examples/react-v18/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1193,11 +1193,6 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36"
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==

"@digitalentities/react-hook-bem@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@digitalentities/react-hook-bem/-/react-hook-bem-1.0.5.tgz#456f44a00082b0e0514222751602caf6203f0e93"
integrity sha512-rzzvtIE6S1vVLuxVZqG4Ue+9ZYnwD/bdWR71zNHvEIpOnY01cNq/Mya3B0MnpuKOtOBTlYhC+4dx+V2MtONh2A==

"@eslint/eslintrc@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.0.tgz#8ec64e0df3e7a1971ee1ff5158da87389f167a63"
Expand Down Expand Up @@ -3069,6 +3064,11 @@ cjs-module-lexer@^1.0.0:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==

classnames@^2.2.5:
version "2.3.2"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==

clean-css@^5.2.2:
version "5.3.1"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.1.tgz#d0610b0b90d125196a2894d35366f734e5d7aa32"
Expand Down Expand Up @@ -7513,6 +7513,13 @@ [email protected]:
optionalDependencies:
fsevents "^2.3.2"

react-toggle@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.1.3.tgz#99193392cca8e495710860c49f55e74c4e6cf452"
integrity sha512-WoPrvbwfQSvoagbrDnXPrlsxwzuhQIrs+V0I162j/s+4XPgY/YDAUmHSeWiroznfI73wj+MBydvW95zX8ABbSg==
dependencies:
classnames "^2.2.5"

read-cache@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-hook-consent",
"version": "3.4.0",
"version": "3.5.0",
"description": "React consent management solution and banner for cookies and (external) scripts.",
"scripts": {
"build": "node esbuild.config.mjs production",
Expand Down
2 changes: 2 additions & 0 deletions src/Context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type ConsentOptionsService = {
description?: string;
scripts?: Array<ScriptExternal | ScriptCode>;
cookies?: Array<Cookie>;
localStorage?: Array<string>;
sessionStorage?: Array<string>;
mandatory?: boolean;
};

Expand Down
19 changes: 19 additions & 0 deletions src/core/local-storage/remove.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { removeLocalStorage } from './remove';

describe('removelocalStorage', () => {
beforeEach(() => {
localStorage.clear();
});

test('should remove all listed items from local storage', () => {
localStorage.setItem('foo', 'bar');
localStorage.setItem('baz', 'qux');
localStorage.setItem('quux', 'quuz');

removeLocalStorage(['foo', 'baz']);

expect(localStorage.getItem('foo')).toBeNull();
expect(localStorage.getItem('baz')).toBeNull();
expect(localStorage.getItem('quux')).not.toBeNull();
});
});
9 changes: 9 additions & 0 deletions src/core/local-storage/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ConsentOptionsService } from '../../Context';

export function removeLocalStorage(localStorageItems?: ConsentOptionsService['localStorage']) {
if (localStorageItems) {
for (const localStorageItem of localStorageItems) {
localStorage.removeItem(localStorageItem);
}
}
}
6 changes: 5 additions & 1 deletion src/core/remove-services.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ConsentOptionsService } from '../Context';
import { removeCookies } from './cookies/remove';
import { removeLocalStorage } from './local-storage/remove';
import { removeScripts } from './scripts/remove';
import { removeSessionStorage } from './session-storage/remove';

export function removeServices(services: ConsentOptionsService[]) {
services.forEach(({ id, scripts, cookies }) => {
services.forEach(({ id, scripts, cookies, localStorage, sessionStorage }) => {
removeScripts(id, scripts);
removeCookies(cookies);
removeLocalStorage(localStorage);
removeSessionStorage(sessionStorage);
});
}
19 changes: 19 additions & 0 deletions src/core/session-storage/remove.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { removeSessionStorage } from './remove';

describe('removeSessionStorage', () => {
beforeEach(() => {
sessionStorage.clear();
});

test('should remove all listed items from session storage', () => {
sessionStorage.setItem('foo', 'bar');
sessionStorage.setItem('baz', 'qux');
sessionStorage.setItem('quux', 'quuz');

removeSessionStorage(['foo', 'baz']);

expect(sessionStorage.getItem('foo')).toBeNull();
expect(sessionStorage.getItem('baz')).toBeNull();
expect(sessionStorage.getItem('quux')).not.toBeNull();
});
});
9 changes: 9 additions & 0 deletions src/core/session-storage/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ConsentOptionsService } from '../../Context';

export function removeSessionStorage(sessionStorageItems?: ConsentOptionsService['sessionStorage']) {
if (sessionStorageItems) {
for (const sessionStorageItem of sessionStorageItems) {
sessionStorage.removeItem(sessionStorageItem);
}
}
}

0 comments on commit 94112e3

Please sign in to comment.