nimiq-supply-calculator 0.0.1-security → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nimiq-supply-calculator might be problematic. Click here for more details.

Files changed (94) hide show
  1. package/.eslintignore +11 -0
  2. package/LICENSE.md +13 -0
  3. package/README.md +3 -3
  4. package/dist/CookieContext.d.ts +10 -0
  5. package/dist/CookieContext.js +182 -0
  6. package/dist/TrackingManagerContext.d.ts +6 -0
  7. package/dist/TrackingManagerContext.js +53 -0
  8. package/dist/analytics.js +119 -0
  9. package/dist/constants.d.ts +11 -0
  10. package/dist/constants.js +32 -0
  11. package/dist/examples/config.d.ts +28 -0
  12. package/dist/examples/config.js +77 -0
  13. package/dist/hooks/useHasConsent.d.ts +2 -0
  14. package/dist/hooks/useHasConsent.js +14 -0
  15. package/dist/hooks/useRequiredCategories.d.ts +3 -0
  16. package/dist/hooks/useRequiredCategories.js +10 -0
  17. package/dist/hooks/useSavedTrackingPreference.d.ts +3 -0
  18. package/dist/hooks/useSavedTrackingPreference.js +38 -0
  19. package/dist/hooks/useSetTrackingPreference.d.ts +3 -0
  20. package/dist/hooks/useSetTrackingPreference.js +27 -0
  21. package/dist/hooks/useTrackingPreference.d.ts +3 -0
  22. package/dist/hooks/useTrackingPreference.js +16 -0
  23. package/dist/index.d.ts +14 -0
  24. package/dist/index.js +39 -0
  25. package/dist/types.d.ts +62 -0
  26. package/dist/types.js +28 -0
  27. package/dist/utils/areCookiesEnabled.d.ts +2 -0
  28. package/dist/utils/areCookiesEnabled.js +14 -0
  29. package/dist/utils/getAllCookies.d.ts +3 -0
  30. package/dist/utils/getAllCookies.js +64 -0
  31. package/dist/utils/getDefaultTrackingPreference.d.ts +3 -0
  32. package/dist/utils/getDefaultTrackingPreference.js +28 -0
  33. package/dist/utils/getDomain.d.ts +2 -0
  34. package/dist/utils/getDomain.js +21 -0
  35. package/dist/utils/getTrackerCategory.d.ts +3 -0
  36. package/dist/utils/getTrackerCategory.js +11 -0
  37. package/dist/utils/getTrackerInfo.d.ts +3 -0
  38. package/dist/utils/getTrackerInfo.js +12 -0
  39. package/dist/utils/hasConsent.d.ts +11 -0
  40. package/dist/utils/hasConsent.js +29 -0
  41. package/dist/utils/isMaxKBSize.d.ts +2 -0
  42. package/dist/utils/isMaxKBSize.js +7 -0
  43. package/dist/utils/isOptOut.d.ts +16 -0
  44. package/dist/utils/isOptOut.js +25 -0
  45. package/dist/utils/persistMobileAppPreferences.d.ts +2 -0
  46. package/dist/utils/persistMobileAppPreferences.js +36 -0
  47. package/dist/utils/setGTMVariables.d.ts +3 -0
  48. package/dist/utils/setGTMVariables.js +14 -0
  49. package/dist/utils/setTrackingPreference.d.ts +4 -0
  50. package/dist/utils/setTrackingPreference.js +22 -0
  51. package/dist/utils/trackerMatches.d.ts +3 -0
  52. package/dist/utils/trackerMatches.js +12 -0
  53. package/jest.config.ts +204 -0
  54. package/package.json +30 -3
  55. package/src/CookieContext.test.tsx +105 -0
  56. package/src/CookieContext.tsx +215 -0
  57. package/src/TrackingManagerContext.tsx +25 -0
  58. package/src/analytics.ts +65 -0
  59. package/src/constants.ts +35 -0
  60. package/src/examples/config.ts +76 -0
  61. package/src/global.d.ts +3 -0
  62. package/src/hooks/useHasConsent.ts +11 -0
  63. package/src/hooks/useRequiredCaregories.test.tsx +43 -0
  64. package/src/hooks/useRequiredCategories.ts +11 -0
  65. package/src/hooks/useSavedTrackingPreference.ts +47 -0
  66. package/src/hooks/useSetTrackingPreference.test.tsx +82 -0
  67. package/src/hooks/useSetTrackingPreference.ts +31 -0
  68. package/src/hooks/useTrackingPreference.ts +15 -0
  69. package/src/index.ts +20 -0
  70. package/src/types.ts +71 -0
  71. package/src/utils/areCookiesEnabled.ts +13 -0
  72. package/src/utils/getAllCookies.test.ts +35 -0
  73. package/src/utils/getAllCookies.ts +68 -0
  74. package/src/utils/getDefaultTrackingPreference.test.ts +16 -0
  75. package/src/utils/getDefaultTrackingPreference.ts +28 -0
  76. package/src/utils/getDomain.test.ts +16 -0
  77. package/src/utils/getDomain.ts +19 -0
  78. package/src/utils/getTrackerCategory.test.ts +34 -0
  79. package/src/utils/getTrackerCategory.ts +11 -0
  80. package/src/utils/getTrackerInfo.test.ts +11 -0
  81. package/src/utils/getTrackerInfo.ts +10 -0
  82. package/src/utils/hasConsent.test.ts +64 -0
  83. package/src/utils/hasConsent.ts +32 -0
  84. package/src/utils/isMaxKBSize.test.ts +10 -0
  85. package/src/utils/isMaxKBSize.ts +7 -0
  86. package/src/utils/isOptOut.test.ts +14 -0
  87. package/src/utils/isOptOut.ts +28 -0
  88. package/src/utils/persistMobileAppPreferences.ts +32 -0
  89. package/src/utils/setGTMVariables.test.ts +20 -0
  90. package/src/utils/setGTMVariables.ts +17 -0
  91. package/src/utils/setTrackingPreference.ts +36 -0
  92. package/src/utils/trackerMatches.test.ts +13 -0
  93. package/src/utils/trackerMatches.ts +12 -0
  94. package/tsconfig.json +112 -0
@@ -0,0 +1,76 @@
1
+ import { Framework, Region, TrackerType, TrackingCategory } from '../types';
2
+
3
+ export default {
4
+ categories: [
5
+ {
6
+ id: TrackingCategory.NECESSARY,
7
+ required: true,
8
+ trackers: [
9
+ {
10
+ id: 'locale',
11
+ type: TrackerType.COOKIE,
12
+ },
13
+ ],
14
+ },
15
+ {
16
+ id: TrackingCategory.PERFORMANCE,
17
+ trackers: [
18
+ {
19
+ id: 'some_cookie',
20
+ type: TrackerType.COOKIE,
21
+ },
22
+ {
23
+ id: 'logged_in',
24
+ type: TrackerType.COOKIE,
25
+ },
26
+ {
27
+ id: 'device_id',
28
+ type: TrackerType.COOKIE,
29
+ },
30
+ ],
31
+ },
32
+ {
33
+ id: TrackingCategory.FUNCTIONAL,
34
+ trackers: [
35
+ {
36
+ id: 'mode',
37
+ // Used to remember if the user dismissed the Advanced mode NUX modal
38
+ type: TrackerType.COOKIE,
39
+ },
40
+ ],
41
+ },
42
+ {
43
+ id: TrackingCategory.TARGETING,
44
+ trackers: [
45
+ {
46
+ id: 'gclid',
47
+ type: TrackerType.COOKIE,
48
+ },
49
+ {
50
+ id: 'id-regex',
51
+ type: TrackerType.COOKIE,
52
+ regex: 'id(?:_[a-f0-9]{32}|undefined)(?:.*)',
53
+ },
54
+ ],
55
+ },
56
+ {
57
+ id: TrackingCategory.DELETE_IF_SEEN,
58
+ trackers: [
59
+ {
60
+ id: 'cgl_prog',
61
+ type: TrackerType.COOKIE,
62
+ },
63
+ ],
64
+ },
65
+ ],
66
+ geolocationRules: [
67
+ {
68
+ region: Region.DEFAULT,
69
+ framework: Framework.OPT_OUT,
70
+ },
71
+ {
72
+ region: Region.EU,
73
+ framework: Framework.OPT_IN,
74
+ },
75
+ ],
76
+ };
@@ -0,0 +1,3 @@
1
+ interface Window {
2
+ dataLayer: Record<string, any>[] | undefined;
3
+ }
@@ -0,0 +1,11 @@
1
+ import { useTrackingPreference } from '..';
2
+ import { useTrackingManager } from '../TrackingManagerContext';
3
+ import hasConsent from '../utils/hasConsent';
4
+
5
+ const useHasConsent = (tracker: string): boolean => {
6
+ const preference = useTrackingPreference();
7
+ const { config } = useTrackingManager();
8
+ return hasConsent(tracker, config, preference);
9
+ };
10
+
11
+ export default useHasConsent;
@@ -0,0 +1,43 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { log } from 'console';
3
+ import React from 'react';
4
+
5
+ import config from '../examples/config';
6
+ import { Provider } from '../TrackingManagerContext';
7
+ import { Region } from '../types';
8
+ import useRequiredCategories from './useRequiredCategories';
9
+
10
+ jest.mock('js-cookie', () => ({
11
+ __esModule: true,
12
+ default: {
13
+ get: jest.fn(),
14
+ set: jest.fn(),
15
+ remove: jest.fn(),
16
+ },
17
+ }));
18
+
19
+ describe('useRequiredCategories', () => {
20
+ const onError = jest.fn();
21
+ const onPreferenceChange = jest.fn();
22
+ const renderFormHook = () =>
23
+ renderHook(() => useRequiredCategories(), {
24
+ wrapper: ({ children }: { children: React.ReactNode }) => (
25
+ <Provider
26
+ onError={onError}
27
+ config={config}
28
+ locale="en"
29
+ projectName="consumer-www"
30
+ region={Region.DEFAULT}
31
+ log={log}
32
+ onPreferenceChange={onPreferenceChange}
33
+ >
34
+ {children}
35
+ </Provider>
36
+ ),
37
+ });
38
+
39
+ it('returns required categories', () => {
40
+ const { result } = renderFormHook();
41
+ expect(result.current).toEqual(['necessary']);
42
+ });
43
+ });
@@ -0,0 +1,11 @@
1
+ import { useTrackingManager } from '../TrackingManagerContext';
2
+ import { TrackingCategory } from '../types';
3
+
4
+ const useRequiredCategories = (): Array<TrackingCategory> => {
5
+ const { config } = useTrackingManager();
6
+ return config.categories
7
+ .filter((c) => c.required)
8
+ .reduce((prev, v) => [...prev, v.id], [] as Array<TrackingCategory>);
9
+ };
10
+
11
+ export default useRequiredCategories;
@@ -0,0 +1,47 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import {
4
+ DEFAULT_CONSENT_PREFERENCES_COOKIE,
5
+ EU_CONSENT_PREFERENCES_COOKIE,
6
+ IS_MOBILE_APP,
7
+ } from '../constants';
8
+ import { useCookie } from '../CookieContext';
9
+ import { useTrackingManager } from '../TrackingManagerContext';
10
+ import { Region, TrackingCategory, TrackingPreference } from '../types';
11
+ import { getIsMobileAppFromQueryParams } from '../utils/persistMobileAppPreferences';
12
+
13
+ export const useSavedTrackingPreferenceFromMobileApp = (): TrackingPreference | undefined => {
14
+ const { region } = useTrackingManager();
15
+
16
+ const [isMobileAppFromCookie] = useCookie(IS_MOBILE_APP);
17
+
18
+ const isMobileAppFromQueryParams = useMemo(() => getIsMobileAppFromQueryParams(), []);
19
+
20
+ const isMobileApp = isMobileAppFromCookie || isMobileAppFromQueryParams;
21
+
22
+ const mobileAppPreference: TrackingPreference | undefined = useMemo(() => {
23
+ if (isMobileApp)
24
+ return {
25
+ region,
26
+ consent: [TrackingCategory.NECESSARY],
27
+ };
28
+ }, [isMobileApp, region]);
29
+
30
+ return mobileAppPreference;
31
+ };
32
+
33
+ export const useSavedTrackingPreference = (): TrackingPreference | undefined => {
34
+ const { region, onError } = useTrackingManager();
35
+
36
+ const [euCookie] = useCookie(EU_CONSENT_PREFERENCES_COOKIE);
37
+ const [defaultCookie] = useCookie(DEFAULT_CONSENT_PREFERENCES_COOKIE);
38
+ const preference: TrackingPreference | undefined =
39
+ region === Region.EU ? euCookie : defaultCookie;
40
+ if (!preference) return;
41
+
42
+ if (!preference.region || !preference.consent) {
43
+ onError(new Error('Malformed preferences'));
44
+ return;
45
+ }
46
+ return preference;
47
+ };
@@ -0,0 +1,82 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { log } from 'console';
3
+ import Cookies from 'js-cookie';
4
+ import React from 'react';
5
+
6
+ import config from '../examples/config';
7
+ import { Provider } from '../TrackingManagerContext';
8
+ import { Region, TrackingCategory } from '../types';
9
+ import useSetTrackingPreference from './useSetTrackingPreference';
10
+
11
+ jest.mock('js-cookie', () => ({
12
+ __esModule: true,
13
+ default: {
14
+ get: jest.fn(),
15
+ set: jest.fn(),
16
+ remove: jest.fn(),
17
+ },
18
+ }));
19
+
20
+ describe('useSetTrackingPreference', () => {
21
+ const onError = jest.fn();
22
+ const onPreferenceChange = jest.fn();
23
+ const region = Region.DEFAULT;
24
+ const renderFormHook = () =>
25
+ renderHook(() => useSetTrackingPreference(), {
26
+ wrapper: ({ children }: { children: React.ReactNode }) => (
27
+ <Provider
28
+ onError={onError}
29
+ config={config}
30
+ locale="en"
31
+ projectName="consumer-www"
32
+ region={region}
33
+ log={log}
34
+ onPreferenceChange={onPreferenceChange}
35
+ >
36
+ {children}
37
+ </Provider>
38
+ ),
39
+ });
40
+
41
+ it('adds required category if not set', () => {
42
+ const { result } = renderFormHook();
43
+ act(() => {
44
+ result.current({
45
+ region: Region.DEFAULT,
46
+ consent: [TrackingCategory.FUNCTIONAL],
47
+ });
48
+ });
49
+
50
+ expect(onError).toHaveBeenCalled();
51
+ expect(onPreferenceChange).toHaveBeenCalledWith({
52
+ region: 'DEFAULT',
53
+ consent: ['functional', 'necessary'],
54
+ });
55
+ });
56
+
57
+ it('sets new consent', () => {
58
+ const mockGet = Cookies.get as jest.MockedFunction<typeof Cookies.get>;
59
+
60
+ mockGet.mockImplementation(() => ({
61
+ cm_default_preferences: JSON.stringify({
62
+ region: Region.DEFAULT,
63
+ consent: [TrackingCategory.NECESSARY, TrackingCategory.PERFORMANCE],
64
+ }),
65
+ }));
66
+
67
+ const { result } = renderFormHook();
68
+ const newPreference = {
69
+ region: Region.DEFAULT,
70
+ consent: [
71
+ TrackingCategory.NECESSARY,
72
+ TrackingCategory.PERFORMANCE,
73
+ TrackingCategory.FUNCTIONAL,
74
+ ],
75
+ };
76
+ act(() => {
77
+ result.current(newPreference);
78
+ });
79
+
80
+ expect(onPreferenceChange).toHaveBeenCalledWith(newPreference);
81
+ });
82
+ });
@@ -0,0 +1,31 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import { useSetCookie } from '../CookieContext';
4
+ import { useTrackingManager } from '../TrackingManagerContext';
5
+ import { TrackingPreference } from '../types';
6
+ import setTrackingPreference from '../utils/setTrackingPreference';
7
+ import useRequiredCategories from './useRequiredCategories';
8
+
9
+ const useSetTrackingPreference = (): ((preference: TrackingPreference) => void) => {
10
+ const { region, onPreferenceChange, onError } = useTrackingManager();
11
+ const setCookie = useSetCookie();
12
+ const requiredCategories = useRequiredCategories();
13
+
14
+ const changePreference = useCallback(
15
+ (preference: TrackingPreference) => {
16
+ const preferencesToSet = preference;
17
+ requiredCategories.forEach((c) => {
18
+ if (!preference.consent.includes(c)) {
19
+ onError(new Error(`Trying to remove category ${c} that is required`));
20
+ preferencesToSet.consent = [...preference.consent, c];
21
+ }
22
+ });
23
+ setTrackingPreference(setCookie, preferencesToSet, region, onPreferenceChange);
24
+ },
25
+ [onError, onPreferenceChange, region, requiredCategories, setCookie]
26
+ );
27
+
28
+ return changePreference;
29
+ };
30
+
31
+ export default useSetTrackingPreference;
@@ -0,0 +1,15 @@
1
+ import { useTrackingManager } from '../TrackingManagerContext';
2
+ import { TrackingPreference } from '../types';
3
+ import getDefaultTrackingPreference from '../utils/getDefaultTrackingPreference';
4
+ import { useSavedTrackingPreference } from './useSavedTrackingPreference';
5
+
6
+ const useTrackingPreference = (): TrackingPreference => {
7
+ const preference = useSavedTrackingPreference();
8
+ const { region, config } = useTrackingManager();
9
+
10
+ if (!preference) return getDefaultTrackingPreference(region, config);
11
+
12
+ return preference;
13
+ };
14
+
15
+ export default useTrackingPreference;
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ export { TRACKER_CATEGORIES } from './constants';
2
+ export { useCookie } from './CookieContext';
3
+ export { default as useHasConsent } from './hooks/useHasConsent';
4
+ export { default as useRequiredCategories } from './hooks/useRequiredCategories';
5
+ export {
6
+ useSavedTrackingPreference,
7
+ useSavedTrackingPreferenceFromMobileApp,
8
+ } from './hooks/useSavedTrackingPreference';
9
+ export { default as useSetTrackingPreference } from './hooks/useSetTrackingPreference';
10
+ export { default as useTrackingPreference } from './hooks/useTrackingPreference';
11
+ export { Provider } from './TrackingManagerContext';
12
+ export { useTrackingManager } from './TrackingManagerContext';
13
+ export { Framework, Region, TrackerType, TrackingCategory, TrackingPreference } from './types';
14
+ export { default as areCookiesEnabled } from './utils/areCookiesEnabled';
15
+ export { default as getDefaultTrackingPreference } from './utils/getDefaultTrackingPreference';
16
+ export { default as isOptOut } from './utils/isOptOut';
17
+ export {
18
+ getIsMobileAppFromQueryParams,
19
+ persistMobileAppPreferences,
20
+ } from './utils/persistMobileAppPreferences';
package/src/types.ts ADDED
@@ -0,0 +1,71 @@
1
+ import { CookieAttributes } from 'js-cookie';
2
+
3
+ export type ErrorFunction = (error: Error, metadata?: { [key: string]: any }) => void;
4
+
5
+ export type SetCookieFunction = (value: any, options?: CookieAttributes) => void;
6
+
7
+ export type LogFunction = (str: string, options?: Record<string, any>) => void;
8
+
9
+ export enum Region {
10
+ EU = 'EU',
11
+ DEFAULT = 'DEFAULT',
12
+ }
13
+
14
+ export enum TrackingCategory {
15
+ FUNCTIONAL = 'functional',
16
+ NECESSARY = 'necessary',
17
+ TARGETING = 'targeting',
18
+ PERFORMANCE = 'performance',
19
+ DELETE_IF_SEEN = 'delete-if-seen',
20
+ }
21
+
22
+ export type TrackingPreference = {
23
+ region: Region;
24
+ consent: Array<TrackingCategory>;
25
+ };
26
+
27
+ export enum Framework {
28
+ OPT_OUT = 'optOut',
29
+ OPT_IN = 'optIn',
30
+ }
31
+
32
+ export type GeolocationRule = { region: Region; framework: Framework };
33
+
34
+ export enum TrackerType {
35
+ COOKIE = 'cookie',
36
+ QUERY = 'query',
37
+ PIXEL = 'pixel',
38
+ BEACON = 'beacon',
39
+ }
40
+
41
+ export type Tracker = {
42
+ id: string;
43
+ type: TrackerType;
44
+ regex?: string;
45
+ };
46
+
47
+ export type ConfigCategoryInfo = {
48
+ id: TrackingCategory;
49
+ trackers: Array<Tracker>;
50
+ required?: boolean;
51
+ };
52
+
53
+ export type Config = {
54
+ categories: Array<ConfigCategoryInfo>;
55
+ };
56
+
57
+ export type TrackingManagerDependencies = {
58
+ onError: ErrorFunction;
59
+ projectName: string;
60
+ locale: string;
61
+ region: Region;
62
+ onPreferenceChange?: (preference: TrackingPreference) => void;
63
+ config: Config;
64
+ shadowMode?: boolean;
65
+ log: LogFunction;
66
+ };
67
+
68
+ export type AdTrackingPreference = {
69
+ value: boolean;
70
+ updated_at?: number;
71
+ };
@@ -0,0 +1,13 @@
1
+ import Cookies from 'js-cookie';
2
+
3
+ const TEST_COOKIE = 'test_cookie';
4
+
5
+ const areCookiesEnabled = (): boolean => {
6
+ Cookies.set(TEST_COOKIE, 'test');
7
+ const result = !!Cookies.get(TEST_COOKIE);
8
+ Cookies.remove(TEST_COOKIE);
9
+
10
+ return result;
11
+ };
12
+
13
+ export default areCookiesEnabled;
@@ -0,0 +1,35 @@
1
+ import Cookies from 'js-cookie';
2
+
3
+ import getAllCookies from './getAllCookies';
4
+
5
+ jest.mock('js-cookie', () => ({
6
+ __esModule: true,
7
+ default: {
8
+ get: jest.fn(),
9
+ set: jest.fn(),
10
+ remove: jest.fn(),
11
+ },
12
+ }));
13
+
14
+ describe('getAllCookies', () => {
15
+ it('deseralizes cookies of all types', () => {
16
+ const mockGet = Cookies.get as jest.MockedFunction<typeof Cookies.get>;
17
+ const value = {
18
+ region: 'DEFAULT',
19
+ consent: ['necessary', 'performance'],
20
+ };
21
+ const cookies = {
22
+ cm_default_preferences: JSON.stringify(value),
23
+ some_cookie: 'iamastring',
24
+ another_cookie: '5',
25
+ array_cookie: JSON.stringify(['item1', 'item2']),
26
+ };
27
+ mockGet.mockImplementation(jest.fn(() => cookies));
28
+ expect(getAllCookies({})).toEqual({
29
+ cm_default_preferences: value,
30
+ some_cookie: 'iamastring',
31
+ another_cookie: 5,
32
+ array_cookie: ['item1', 'item2'],
33
+ });
34
+ });
35
+ });
@@ -0,0 +1,68 @@
1
+ import Cookies from 'js-cookie';
2
+
3
+ export const deserializeCookies = (cookies: Record<string, string>) => {
4
+ const parsedCookies: Record<string, any> = {};
5
+
6
+ Object.keys(cookies).forEach((c) => {
7
+ try {
8
+ parsedCookies[c] = JSON.parse(cookies[c]);
9
+ } catch (e) {
10
+ parsedCookies[c] = cookies[c];
11
+ }
12
+ });
13
+ return parsedCookies;
14
+ };
15
+
16
+ export default function getAllCookies(initialCookies?: Record<string, string>) {
17
+ if (typeof window === 'undefined' && initialCookies) {
18
+ return deserializeCookies(initialCookies);
19
+ }
20
+ return deserializeCookies(Cookies.get() || {});
21
+ }
22
+
23
+ export function areRecordsEqual(
24
+ record1: Record<string, any>,
25
+ record2: Record<string, any>
26
+ ): boolean {
27
+ // Check if the number of keys is the same
28
+ const keys1 = Object.keys(record1);
29
+ const keys2 = Object.keys(record2);
30
+
31
+ if (keys1.length !== keys2.length) {
32
+ return false;
33
+ }
34
+
35
+ // Check if all keys and their values are equal
36
+ for (const key of keys1) {
37
+ if (!deepEqual(record1[key], record2[key])) {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ return true;
43
+ }
44
+
45
+ function deepEqual(value1: any, value2: any): boolean {
46
+ if (typeof value1 !== typeof value2) {
47
+ return false;
48
+ }
49
+
50
+ if (typeof value1 === 'object' && value1 !== null) {
51
+ const keys1 = Object.keys(value1);
52
+ const keys2 = Object.keys(value2);
53
+
54
+ if (keys1.length !== keys2.length) {
55
+ return false;
56
+ }
57
+
58
+ for (const key of keys1) {
59
+ if (!deepEqual(value1[key], value2[key])) {
60
+ return false;
61
+ }
62
+ }
63
+ } else if (value1 !== value2) {
64
+ return false;
65
+ }
66
+
67
+ return true;
68
+ }
@@ -0,0 +1,16 @@
1
+ import config from '../examples/config';
2
+ import { Region } from '../types';
3
+ import getDefaultTrackingPreference from './getDefaultTrackingPreference';
4
+
5
+ describe('getDefaultTrackingPreference', () => {
6
+ it('returns the correct default preferences', () => {
7
+ expect(getDefaultTrackingPreference(Region.DEFAULT, config)).toEqual({
8
+ region: Region.DEFAULT,
9
+ consent: ['necessary', 'performance', 'functional', 'targeting'],
10
+ });
11
+ expect(getDefaultTrackingPreference(Region.EU, config)).toEqual({
12
+ region: Region.EU,
13
+ consent: ['necessary'],
14
+ });
15
+ });
16
+ });
@@ -0,0 +1,28 @@
1
+ import { Config, Region, TrackingCategory, TrackingPreference } from '../types';
2
+ import isOptOut from './isOptOut';
3
+
4
+ const getDefaultTrackingPreference = (region: Region, config: Config): TrackingPreference => {
5
+ // In opt in regions, we only allow required trackers
6
+
7
+ if (!isOptOut(region)) {
8
+ const categories = config.categories
9
+ .map((c) => {
10
+ if (c.required) {
11
+ return c.id;
12
+ }
13
+ })
14
+ .filter((c) => !!c) as TrackingCategory[];
15
+ return { region, consent: categories };
16
+ }
17
+
18
+ const categories = config.categories
19
+ .map((c) => c.id)
20
+ .filter((id) => id !== TrackingCategory.DELETE_IF_SEEN);
21
+
22
+ return {
23
+ region,
24
+ consent: categories,
25
+ };
26
+ };
27
+
28
+ export default getDefaultTrackingPreference;
@@ -0,0 +1,16 @@
1
+ import { getDomainWithoutSubdomain } from './getDomain';
2
+
3
+ describe('getDomain', () => {
4
+ it('returns the correct domain', () => {
5
+ Object.defineProperty(window, 'location', {
6
+ value: { hostname: 'www.coinbase.com' },
7
+ writable: true,
8
+ });
9
+ expect(getDomainWithoutSubdomain()).toEqual('.coinbase.com');
10
+ window.location.hostname = 'pro.coinbase.com';
11
+ expect(getDomainWithoutSubdomain()).toEqual('.coinbase.com');
12
+ window.location.hostname = 'coinbase-dev.cbhq.net';
13
+
14
+ expect(getDomainWithoutSubdomain()).toEqual('.cbhq.net');
15
+ });
16
+ });
@@ -0,0 +1,19 @@
1
+ export function getHostname(): string | undefined {
2
+ if (typeof window !== 'undefined') {
3
+ return window.location.hostname;
4
+ }
5
+ }
6
+
7
+ export function getDomainWithoutSubdomain(): string | undefined {
8
+ const hostname = getHostname();
9
+ if (!hostname) {
10
+ return;
11
+ }
12
+
13
+ if (hostname === 'localhost') {
14
+ return hostname;
15
+ }
16
+
17
+ const [, ...domain] = hostname.split('.');
18
+ return `.${domain.join('.')}`;
19
+ }
@@ -0,0 +1,34 @@
1
+ import config from '../examples/config';
2
+ import { TrackingCategory } from '../types';
3
+ import getTrackerCategory from './getTrackerCategory';
4
+
5
+ const exampleSimpleTrackers = {
6
+ locale: TrackingCategory.NECESSARY,
7
+ device_id: TrackingCategory.PERFORMANCE,
8
+ mode: TrackingCategory.FUNCTIONAL,
9
+ gclid: TrackingCategory.TARGETING,
10
+ };
11
+
12
+ const exampleDynamicTrackers = {
13
+ 'idundefined.com': TrackingCategory.TARGETING,
14
+ id_ac7a5c3da45e3612b44543a702e42b01: TrackingCategory.TARGETING,
15
+ id_132e62b5953ce8d568137d5887b6b7ab_classic: TrackingCategory.TARGETING,
16
+ id_ac7a5c3da45e3612b44543a702e42b01_classic: TrackingCategory.TARGETING,
17
+ id_5e21e8a10d7c1553bc75ce1ff6d49a3f_realtime: TrackingCategory.TARGETING,
18
+ id_4031eff2d750cfe4385594be339acc56_realtime: TrackingCategory.TARGETING,
19
+ 'id_4031eff2d750cfe4385594be339acc56_realtimecoinbase.com': TrackingCategory.TARGETING,
20
+ 'id_132e62b5953ce8d568137d5887b6b7abcoinbase.com': TrackingCategory.TARGETING,
21
+ };
22
+
23
+ describe('getTrackerCategory', () => {
24
+ it('matches simple trackers via id', () => {
25
+ for (const [tracker, categoryId] of Object.entries(exampleSimpleTrackers)) {
26
+ expect(getTrackerCategory(tracker, config)?.id).toEqual(categoryId);
27
+ }
28
+ });
29
+ it('matches dynamic trackers not matching ids via regex', () => {
30
+ for (const [tracker, categoryId] of Object.entries(exampleDynamicTrackers)) {
31
+ expect(getTrackerCategory(tracker, config)?.id).toEqual(categoryId);
32
+ }
33
+ });
34
+ });
@@ -0,0 +1,11 @@
1
+ import { Config, ConfigCategoryInfo } from '../types';
2
+ import trackerMatches from './trackerMatches';
3
+
4
+ const getTrackerCategory = (trackerId: string, config: Config): ConfigCategoryInfo | undefined => {
5
+ const category = config.categories.find((category) =>
6
+ category.trackers.find((tracker) => trackerMatches(tracker, trackerId))
7
+ );
8
+ return category;
9
+ };
10
+
11
+ export default getTrackerCategory;
@@ -0,0 +1,11 @@
1
+ import config from '../examples/config';
2
+ import getTrackerInfo from './getTrackerInfo';
3
+
4
+ describe('getTrackerInfo', () => {
5
+ it('should return undefined if tracker is not found', () => {
6
+ expect(getTrackerInfo('unknown', config)).toBeUndefined();
7
+ });
8
+ it('should return tracker info if tracker is found', () => {
9
+ expect(getTrackerInfo('locale', config)).toEqual({ id: 'locale', type: 'cookie' });
10
+ });
11
+ });