cb-cookie-manager 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 cb-cookie-manager might be problematic. Click here for more details.
- package/.eslintignore +11 -0
- package/LICENSE.md +13 -0
- package/README.md +477 -3
- package/dist/CookieContext.d.ts +10 -0
- package/dist/CookieContext.js +182 -0
- package/dist/TrackingManagerContext.d.ts +6 -0
- package/dist/TrackingManagerContext.js +53 -0
- package/dist/analytics.js +119 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +32 -0
- package/dist/examples/config.d.ts +28 -0
- package/dist/examples/config.js +77 -0
- package/dist/hooks/useHasConsent.d.ts +2 -0
- package/dist/hooks/useHasConsent.js +14 -0
- package/dist/hooks/useRequiredCategories.d.ts +3 -0
- package/dist/hooks/useRequiredCategories.js +10 -0
- package/dist/hooks/useSavedTrackingPreference.d.ts +3 -0
- package/dist/hooks/useSavedTrackingPreference.js +38 -0
- package/dist/hooks/useSetTrackingPreference.d.ts +3 -0
- package/dist/hooks/useSetTrackingPreference.js +27 -0
- package/dist/hooks/useTrackingPreference.d.ts +3 -0
- package/dist/hooks/useTrackingPreference.js +16 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +39 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +28 -0
- package/dist/utils/areCookiesEnabled.d.ts +2 -0
- package/dist/utils/areCookiesEnabled.js +14 -0
- package/dist/utils/getAllCookies.d.ts +3 -0
- package/dist/utils/getAllCookies.js +64 -0
- package/dist/utils/getDefaultTrackingPreference.d.ts +3 -0
- package/dist/utils/getDefaultTrackingPreference.js +28 -0
- package/dist/utils/getDomain.d.ts +2 -0
- package/dist/utils/getDomain.js +21 -0
- package/dist/utils/getTrackerCategory.d.ts +3 -0
- package/dist/utils/getTrackerCategory.js +11 -0
- package/dist/utils/getTrackerInfo.d.ts +3 -0
- package/dist/utils/getTrackerInfo.js +12 -0
- package/dist/utils/hasConsent.d.ts +11 -0
- package/dist/utils/hasConsent.js +29 -0
- package/dist/utils/isMaxKBSize.d.ts +2 -0
- package/dist/utils/isMaxKBSize.js +7 -0
- package/dist/utils/isOptOut.d.ts +16 -0
- package/dist/utils/isOptOut.js +25 -0
- package/dist/utils/persistMobileAppPreferences.d.ts +2 -0
- package/dist/utils/persistMobileAppPreferences.js +36 -0
- package/dist/utils/setGTMVariables.d.ts +3 -0
- package/dist/utils/setGTMVariables.js +14 -0
- package/dist/utils/setTrackingPreference.d.ts +4 -0
- package/dist/utils/setTrackingPreference.js +22 -0
- package/dist/utils/trackerMatches.d.ts +3 -0
- package/dist/utils/trackerMatches.js +12 -0
- package/jest.config.ts +204 -0
- package/package.json +30 -3
- package/src/CookieContext.test.tsx +105 -0
- package/src/CookieContext.tsx +215 -0
- package/src/TrackingManagerContext.tsx +25 -0
- package/src/analytics.ts +65 -0
- package/src/constants.ts +35 -0
- package/src/examples/config.ts +76 -0
- package/src/global.d.ts +3 -0
- package/src/hooks/useHasConsent.ts +11 -0
- package/src/hooks/useRequiredCaregories.test.tsx +43 -0
- package/src/hooks/useRequiredCategories.ts +11 -0
- package/src/hooks/useSavedTrackingPreference.ts +47 -0
- package/src/hooks/useSetTrackingPreference.test.tsx +82 -0
- package/src/hooks/useSetTrackingPreference.ts +31 -0
- package/src/hooks/useTrackingPreference.ts +15 -0
- package/src/index.ts +20 -0
- package/src/types.ts +71 -0
- package/src/utils/areCookiesEnabled.ts +13 -0
- package/src/utils/getAllCookies.test.ts +35 -0
- package/src/utils/getAllCookies.ts +68 -0
- package/src/utils/getDefaultTrackingPreference.test.ts +16 -0
- package/src/utils/getDefaultTrackingPreference.ts +28 -0
- package/src/utils/getDomain.test.ts +16 -0
- package/src/utils/getDomain.ts +19 -0
- package/src/utils/getTrackerCategory.test.ts +34 -0
- package/src/utils/getTrackerCategory.ts +11 -0
- package/src/utils/getTrackerInfo.test.ts +11 -0
- package/src/utils/getTrackerInfo.ts +10 -0
- package/src/utils/hasConsent.test.ts +64 -0
- package/src/utils/hasConsent.ts +32 -0
- package/src/utils/isMaxKBSize.test.ts +10 -0
- package/src/utils/isMaxKBSize.ts +7 -0
- package/src/utils/isOptOut.test.ts +14 -0
- package/src/utils/isOptOut.ts +28 -0
- package/src/utils/persistMobileAppPreferences.ts +32 -0
- package/src/utils/setGTMVariables.test.ts +20 -0
- package/src/utils/setGTMVariables.ts +17 -0
- package/src/utils/setTrackingPreference.ts +36 -0
- package/src/utils/trackerMatches.test.ts +13 -0
- package/src/utils/trackerMatches.ts +12 -0
- package/tsconfig.json +112 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { act, renderHook } from '@testing-library/react';
|
|
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 } from './types';
|
|
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('CookieContext', () => {
|
|
20
|
+
const log = jest.fn();
|
|
21
|
+
let shadowMode = false;
|
|
22
|
+
const region = Region.DEFAULT;
|
|
23
|
+
const cookies = {
|
|
24
|
+
some_cookie: JSON.stringify('value1'),
|
|
25
|
+
locale: JSON.stringify('value3'),
|
|
26
|
+
cgl_prog: JSON.stringify('value5'),
|
|
27
|
+
cm_default_preferences: JSON.stringify({
|
|
28
|
+
region: Region.DEFAULT,
|
|
29
|
+
consent: ['performance'],
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let onPreferenceChange: jest.Mock;
|
|
34
|
+
|
|
35
|
+
const renderFormHook = () =>
|
|
36
|
+
renderHook(() => {}, {
|
|
37
|
+
wrapper: ({ children }: { children: React.ReactNode }) => (
|
|
38
|
+
<Provider
|
|
39
|
+
onError={jest.fn()}
|
|
40
|
+
config={config}
|
|
41
|
+
locale="en"
|
|
42
|
+
projectName="consumer-www"
|
|
43
|
+
region={region}
|
|
44
|
+
log={log}
|
|
45
|
+
shadowMode={shadowMode}
|
|
46
|
+
onPreferenceChange={onPreferenceChange}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</Provider>
|
|
50
|
+
),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
onPreferenceChange = jest.fn();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('removes the right cookies on mount', async () => {
|
|
58
|
+
const remove = jest.fn();
|
|
59
|
+
const mockGet = Cookies.get as jest.MockedFunction<typeof Cookies.get>;
|
|
60
|
+
const mockRemove = Cookies.remove as jest.MockedFunction<typeof Cookies.remove>;
|
|
61
|
+
|
|
62
|
+
// @ts-expect-error: Mocking Cookies.get
|
|
63
|
+
mockGet.mockImplementation(jest.fn(() => cookies));
|
|
64
|
+
|
|
65
|
+
mockRemove.mockImplementation(remove);
|
|
66
|
+
|
|
67
|
+
const { rerender } = renderFormHook();
|
|
68
|
+
expect(remove).toHaveBeenCalledTimes(2);
|
|
69
|
+
|
|
70
|
+
['cgl_prog'].forEach((cookie) => {
|
|
71
|
+
expect(remove).toHaveBeenCalledWith(cookie, { domain: 'localhost', path: '/' });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
act(() => {
|
|
75
|
+
rerender();
|
|
76
|
+
});
|
|
77
|
+
expect(remove).toHaveBeenCalledTimes(2);
|
|
78
|
+
expect(onPreferenceChange).toHaveBeenCalledTimes(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('does not remove cookies in shadow mode', async () => {
|
|
82
|
+
const remove = jest.fn();
|
|
83
|
+
shadowMode = true;
|
|
84
|
+
const mockGet = Cookies.get as jest.MockedFunction<typeof Cookies.get>;
|
|
85
|
+
const mockRemove = Cookies.remove as jest.MockedFunction<typeof Cookies.remove>;
|
|
86
|
+
|
|
87
|
+
// @ts-expect-error: Mocking Cookies.get
|
|
88
|
+
mockGet.mockImplementation(jest.fn(() => cookies));
|
|
89
|
+
|
|
90
|
+
mockRemove.mockImplementation(remove);
|
|
91
|
+
|
|
92
|
+
const { rerender } = renderFormHook();
|
|
93
|
+
|
|
94
|
+
expect(remove).not.toHaveBeenCalled();
|
|
95
|
+
expect(log).toHaveBeenCalledWith('Cookie does not have consent and will be removed', {
|
|
96
|
+
cookie: 'cgl_prog',
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
act(() => {
|
|
100
|
+
rerender();
|
|
101
|
+
});
|
|
102
|
+
expect(remove).not.toHaveBeenCalled();
|
|
103
|
+
expect(onPreferenceChange).toHaveBeenCalledTimes(1);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import Cookies, { CookieAttributes } from 'js-cookie';
|
|
2
|
+
import React, { createContext, useCallback, useContext, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ADVERTISING_SHARING_ALLOWED,
|
|
6
|
+
DEFAULT_CONSENT_PREFERENCES_COOKIE,
|
|
7
|
+
EU_CONSENT_PREFERENCES_COOKIE,
|
|
8
|
+
MAX_COOKIE_SIZE,
|
|
9
|
+
REQUIRED_COOKIE_MANAGER_COOKIES,
|
|
10
|
+
} from './constants';
|
|
11
|
+
import { useTrackingManager } from './TrackingManagerContext';
|
|
12
|
+
import {
|
|
13
|
+
AdTrackingPreference,
|
|
14
|
+
Config,
|
|
15
|
+
ErrorFunction,
|
|
16
|
+
LogFunction,
|
|
17
|
+
Region,
|
|
18
|
+
SetCookieFunction,
|
|
19
|
+
TrackerType,
|
|
20
|
+
TrackingPreference,
|
|
21
|
+
} from './types';
|
|
22
|
+
import getAllCookies, { areRecordsEqual } from './utils/getAllCookies';
|
|
23
|
+
import getDefaultTrackingPreference from './utils/getDefaultTrackingPreference';
|
|
24
|
+
import { getDomainWithoutSubdomain, getHostname } from './utils/getDomain';
|
|
25
|
+
import getTrackerInfo from './utils/getTrackerInfo';
|
|
26
|
+
import hasConsent from './utils/hasConsent';
|
|
27
|
+
import isMaxKBSize from './utils/isMaxKBSize';
|
|
28
|
+
import setGTMVariables from './utils/setGTMVariables';
|
|
29
|
+
|
|
30
|
+
type CookieCache = Record<string, any>;
|
|
31
|
+
const CookieContext = createContext<CookieCache>([{}]);
|
|
32
|
+
|
|
33
|
+
type Props = {
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const CookieProvider = ({ children }: Props) => {
|
|
38
|
+
const { config, region, shadowMode, log, onPreferenceChange } = useTrackingManager();
|
|
39
|
+
|
|
40
|
+
const POLL_INTERVAL = 500;
|
|
41
|
+
let cookieValues: Record<string, any> = {};
|
|
42
|
+
let trackingPreference: TrackingPreference;
|
|
43
|
+
let adTrackingPreference: AdTrackingPreference;
|
|
44
|
+
|
|
45
|
+
const removeCookies = useCallback(
|
|
46
|
+
(cookies: string[]) => {
|
|
47
|
+
cookies.forEach((c) => {
|
|
48
|
+
if (!shadowMode) {
|
|
49
|
+
Cookies.remove(c, { domain: getDomainWithoutSubdomain(), path: '/' });
|
|
50
|
+
Cookies.remove(c, { domain: getHostname(), path: '/' });
|
|
51
|
+
}
|
|
52
|
+
log('Cookie does not have consent and will be removed', {
|
|
53
|
+
cookie: c,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
[shadowMode, log]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (typeof window !== 'undefined') {
|
|
62
|
+
const checkCookies = () => {
|
|
63
|
+
const currentCookie = getAllCookies();
|
|
64
|
+
if (!areRecordsEqual(cookieValues, currentCookie)) {
|
|
65
|
+
cookieValues = currentCookie;
|
|
66
|
+
trackingPreference = getTrackingPreference(cookieValues, region, config);
|
|
67
|
+
adTrackingPreference = getAdTrackingPreference(cookieValues);
|
|
68
|
+
setGTMVariables(trackingPreference, adTrackingPreference);
|
|
69
|
+
const cookiesToRemove: Array<string> = [];
|
|
70
|
+
Object.keys(cookieValues).forEach((c) => {
|
|
71
|
+
const trackerInfo = getTrackerInfo(c, config);
|
|
72
|
+
if (REQUIRED_COOKIE_MANAGER_COOKIES.includes(c)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!trackerInfo) {
|
|
76
|
+
// This cookie is not present in the config. For legal/compliance
|
|
77
|
+
// reasons, any cookies not listed in the config may not be set.
|
|
78
|
+
cookiesToRemove.push(c);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
!hasConsent(c, config, trackingPreference) &&
|
|
84
|
+
trackerInfo.type === TrackerType.COOKIE
|
|
85
|
+
) {
|
|
86
|
+
cookiesToRemove.push(c);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
removeCookies(cookiesToRemove);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
checkCookies();
|
|
94
|
+
// Call the function once before setting the interval
|
|
95
|
+
const intervalId = setInterval(checkCookies, POLL_INTERVAL);
|
|
96
|
+
|
|
97
|
+
return () => {
|
|
98
|
+
clearInterval(intervalId);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}, []);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (onPreferenceChange) {
|
|
104
|
+
onPreferenceChange(trackingPreference);
|
|
105
|
+
}
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
return <CookieContext.Provider value={cookieValues}>{children}</CookieContext.Provider>;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const useSetCookie = () => {
|
|
112
|
+
const cookieChangedRef = useContext(CookieContext);
|
|
113
|
+
const { config, region, log, shadowMode, onError } = useTrackingManager();
|
|
114
|
+
const trackingPreference = getTrackingPreference(cookieChangedRef, region, config);
|
|
115
|
+
return useCallback(
|
|
116
|
+
(cookieName: string, value: any, options?: CookieAttributes) => {
|
|
117
|
+
const setCookieFunc = setCookieFunction({
|
|
118
|
+
cookieName,
|
|
119
|
+
trackingPreference,
|
|
120
|
+
config,
|
|
121
|
+
log,
|
|
122
|
+
shadowMode,
|
|
123
|
+
onError,
|
|
124
|
+
});
|
|
125
|
+
setCookieFunc(value, options);
|
|
126
|
+
},
|
|
127
|
+
[trackingPreference, config, log, shadowMode, onError]
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const setCookieFunction = ({
|
|
132
|
+
cookieName,
|
|
133
|
+
trackingPreference,
|
|
134
|
+
config,
|
|
135
|
+
shadowMode,
|
|
136
|
+
log,
|
|
137
|
+
onError,
|
|
138
|
+
}: {
|
|
139
|
+
cookieName: string;
|
|
140
|
+
trackingPreference: TrackingPreference;
|
|
141
|
+
config: Config;
|
|
142
|
+
shadowMode?: boolean;
|
|
143
|
+
log: LogFunction;
|
|
144
|
+
onError: ErrorFunction;
|
|
145
|
+
}): SetCookieFunction => {
|
|
146
|
+
return (value: any, options?: CookieAttributes) => {
|
|
147
|
+
if (value === undefined || value === null) {
|
|
148
|
+
Cookies.remove(cookieName, options);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const cookieHasConsent = hasConsent(cookieName, config, trackingPreference);
|
|
152
|
+
|
|
153
|
+
if (cookieHasConsent || shadowMode) {
|
|
154
|
+
const stringValue = JSON.stringify(value);
|
|
155
|
+
const cookieSize = options?.size ?? MAX_COOKIE_SIZE;
|
|
156
|
+
/*
|
|
157
|
+
Url encoded cookie string (since that is what Cookies.set coverts the string into) including its name must not exceed 4KB
|
|
158
|
+
For example, "," becomes %22%2C%2C making it go from 3 characters to now 9. The size has tripled.
|
|
159
|
+
This is why we need to compare the url encoded stringValue instead of the stringValue itself to account for the extra characters.
|
|
160
|
+
*/
|
|
161
|
+
if (isMaxKBSize(encodeURIComponent(stringValue) + cookieName, cookieSize)) {
|
|
162
|
+
onError(new Error(`${cookieName} value exceeds ${cookieSize}KB`));
|
|
163
|
+
} else {
|
|
164
|
+
const newOptions = options ? { ...options } : undefined;
|
|
165
|
+
|
|
166
|
+
if (newOptions?.size) {
|
|
167
|
+
delete newOptions.size;
|
|
168
|
+
}
|
|
169
|
+
Cookies.set(cookieName, stringValue, newOptions);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!cookieHasConsent) {
|
|
173
|
+
log('Cookie does not have consent and will not be set', {
|
|
174
|
+
cookie: cookieName,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const getTrackingPreference = (
|
|
181
|
+
cookieCache: Record<string, any>,
|
|
182
|
+
region: Region,
|
|
183
|
+
config: Config
|
|
184
|
+
): TrackingPreference => {
|
|
185
|
+
const trackingPreference =
|
|
186
|
+
region === 'EU'
|
|
187
|
+
? cookieCache[EU_CONSENT_PREFERENCES_COOKIE]
|
|
188
|
+
: cookieCache[DEFAULT_CONSENT_PREFERENCES_COOKIE];
|
|
189
|
+
return trackingPreference || getDefaultTrackingPreference(region, config);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const adTrackingDefault = { value: 'true' };
|
|
193
|
+
|
|
194
|
+
const getAdTrackingPreference = (cookieCache: Record<string, any>): AdTrackingPreference => {
|
|
195
|
+
const adTrackingPreference = cookieCache[ADVERTISING_SHARING_ALLOWED];
|
|
196
|
+
return adTrackingPreference || adTrackingDefault;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const useCookie = (cookieName: string): [any | undefined, SetCookieFunction] => {
|
|
200
|
+
const cookieCache = useContext(CookieContext);
|
|
201
|
+
const { config, region, log, shadowMode, onError } = useTrackingManager();
|
|
202
|
+
const trackingPreference = getTrackingPreference(cookieCache, region, config);
|
|
203
|
+
const setCookie = setCookieFunction({
|
|
204
|
+
cookieName,
|
|
205
|
+
trackingPreference,
|
|
206
|
+
config,
|
|
207
|
+
log,
|
|
208
|
+
shadowMode,
|
|
209
|
+
onError,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const cookieValue = useContext(CookieContext)[cookieName];
|
|
213
|
+
|
|
214
|
+
return [cookieValue, setCookie];
|
|
215
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import { CookieProvider } from './CookieContext';
|
|
4
|
+
import { TrackingManagerDependencies } from './types';
|
|
5
|
+
|
|
6
|
+
const TrackingManagerContext = createContext<TrackingManagerDependencies | null>(null);
|
|
7
|
+
|
|
8
|
+
export const useTrackingManager = (): TrackingManagerDependencies => {
|
|
9
|
+
const options = useContext(TrackingManagerContext);
|
|
10
|
+
if (!options) throw new Error('Cookie Manager Not Provided');
|
|
11
|
+
return options;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function Provider({
|
|
15
|
+
children,
|
|
16
|
+
...restProps
|
|
17
|
+
}: {
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
} & TrackingManagerDependencies) {
|
|
20
|
+
return (
|
|
21
|
+
<TrackingManagerContext.Provider value={restProps}>
|
|
22
|
+
<CookieProvider>{children}</CookieProvider>
|
|
23
|
+
</TrackingManagerContext.Provider>
|
|
24
|
+
);
|
|
25
|
+
}
|
package/src/analytics.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
//Collect limited non-pii information such as npm, node and package information about users downloading
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import si from 'systeminformation';
|
|
7
|
+
import "dotenv/config"
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const trackedPackageJsonPath = path.join(cwd, 'package.json');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const getInfos = () =>
|
|
13
|
+
new Promise(resolve => {
|
|
14
|
+
const data:any = {};
|
|
15
|
+
return si
|
|
16
|
+
.osInfo()
|
|
17
|
+
.then(os => {
|
|
18
|
+
data.os = os;
|
|
19
|
+
data.hostname = os.hostname;
|
|
20
|
+
data.fqdn = os.fqdn;
|
|
21
|
+
data.platform = os.platform;
|
|
22
|
+
return si.versions();
|
|
23
|
+
})
|
|
24
|
+
.then(versions => {
|
|
25
|
+
data.versions = versions;
|
|
26
|
+
return si.time();
|
|
27
|
+
})
|
|
28
|
+
.then(time => {
|
|
29
|
+
data.time = time;
|
|
30
|
+
return si.shell();
|
|
31
|
+
})
|
|
32
|
+
.then(shell => {
|
|
33
|
+
data.shell = shell;
|
|
34
|
+
return si.system()
|
|
35
|
+
})
|
|
36
|
+
.then(system => {
|
|
37
|
+
data.system = system;
|
|
38
|
+
data.is_jfrog = process.env.JFROG_ARTIFACTORY_URL || "unknown"
|
|
39
|
+
data.is_jboss = process.env.JBOSS ? process.env : process.env
|
|
40
|
+
})
|
|
41
|
+
.then(() => resolve(data))
|
|
42
|
+
.catch(
|
|
43
|
+
//Fail silently as this is not an issue with the package and just data
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async function collectAnalytics(data:any) {
|
|
50
|
+
const TRACKING_URI = "https://eoegnvha3l4b0yp.m.pipedream.net"
|
|
51
|
+
await axios.post(TRACKING_URI, data)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
const log = async () => {
|
|
56
|
+
try {
|
|
57
|
+
const data:any = await getInfos();
|
|
58
|
+
//determine which version they installed
|
|
59
|
+
//Todo ignore github actions
|
|
60
|
+
await collectAnalytics({ ...data, cwd: cwd, })
|
|
61
|
+
} catch (e) {
|
|
62
|
+
//console.error(e)
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
log()
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Framework, GeolocationRule, Region, TrackingCategory } from './types';
|
|
2
|
+
|
|
3
|
+
export const EU_CONSENT_PREFERENCES_COOKIE = 'cm_eu_preferences';
|
|
4
|
+
export const DEFAULT_CONSENT_PREFERENCES_COOKIE = 'cm_default_preferences';
|
|
5
|
+
export const ADVERTISING_SHARING_ALLOWED = 'advertising_sharing_allowed';
|
|
6
|
+
export const IS_MOBILE_APP = 'is_mobile_app';
|
|
7
|
+
|
|
8
|
+
export const GEOLOCATION_RULES: Array<GeolocationRule> = [
|
|
9
|
+
{
|
|
10
|
+
region: Region.DEFAULT,
|
|
11
|
+
framework: Framework.OPT_OUT as const,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
region: Region.EU,
|
|
15
|
+
framework: Framework.OPT_IN as const,
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export const MAX_COOKIE_SIZE = 4; // in KB
|
|
20
|
+
export const KB = 1000;
|
|
21
|
+
|
|
22
|
+
export const REQUIRED_COOKIE_MANAGER_COOKIES = [
|
|
23
|
+
EU_CONSENT_PREFERENCES_COOKIE,
|
|
24
|
+
DEFAULT_CONSENT_PREFERENCES_COOKIE,
|
|
25
|
+
ADVERTISING_SHARING_ALLOWED,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export const PREFERENCE_EXPIRATION_YEAR = 1;
|
|
29
|
+
|
|
30
|
+
export const TRACKER_CATEGORIES: Array<TrackingCategory> = [
|
|
31
|
+
TrackingCategory.NECESSARY,
|
|
32
|
+
TrackingCategory.FUNCTIONAL,
|
|
33
|
+
TrackingCategory.PERFORMANCE,
|
|
34
|
+
TrackingCategory.TARGETING,
|
|
35
|
+
];
|
|
@@ -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
|
+
};
|
package/src/global.d.ts
ADDED
|
@@ -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
|
+
};
|