@dotcms/experiments 0.0.1-alpha.39 → 0.0.1-alpha.41
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.
- package/index.esm.d.ts +1 -0
- package/index.esm.js +7174 -0
- package/package.json +10 -6
- package/src/lib/components/{DotExperimentHandlingComponent.tsx → DotExperimentHandlingComponent.d.ts} +3 -20
- package/src/lib/components/{DotExperimentsProvider.tsx → DotExperimentsProvider.d.ts} +3 -41
- package/src/lib/components/withExperiments.d.ts +20 -0
- package/src/lib/contexts/{DotExperimentsContext.tsx → DotExperimentsContext.d.ts} +2 -5
- package/src/lib/dot-experiments.d.ts +289 -0
- package/src/lib/hooks/useExperimentVariant.d.ts +21 -0
- package/src/lib/hooks/useExperiments.d.ts +14 -0
- package/src/lib/shared/{constants.ts → constants.d.ts} +18 -35
- package/src/lib/shared/mocks/mock.d.ts +43 -0
- package/src/lib/shared/{models.ts → models.d.ts} +2 -35
- package/src/lib/shared/parser/parser.d.ts +54 -0
- package/src/lib/shared/persistence/index-db-database-handler.d.ts +87 -0
- package/src/lib/shared/utils/DotLogger.d.ts +15 -0
- package/src/lib/shared/utils/memoize.d.ts +7 -0
- package/src/lib/shared/utils/utils.d.ts +73 -0
- package/src/lib/standalone.d.ts +7 -0
- package/.babelrc +0 -12
- package/.eslintrc.json +0 -26
- package/jest.config.ts +0 -11
- package/project.json +0 -55
- package/src/lib/components/DotExperimentsProvider.spec.tsx +0 -62
- package/src/lib/components/withExperiments.tsx +0 -52
- package/src/lib/contexts/DotExperimentsContext.spec.tsx +0 -42
- package/src/lib/dot-experiments.spec.ts +0 -285
- package/src/lib/dot-experiments.ts +0 -716
- package/src/lib/hooks/useExperimentVariant.spec.tsx +0 -111
- package/src/lib/hooks/useExperimentVariant.ts +0 -55
- package/src/lib/hooks/useExperiments.ts +0 -90
- package/src/lib/shared/mocks/mock.ts +0 -209
- package/src/lib/shared/parser/parse.spec.ts +0 -187
- package/src/lib/shared/parser/parser.ts +0 -171
- package/src/lib/shared/persistence/index-db-database-handler.spec.ts +0 -100
- package/src/lib/shared/persistence/index-db-database-handler.ts +0 -218
- package/src/lib/shared/utils/DotLogger.ts +0 -57
- package/src/lib/shared/utils/memoize.spec.ts +0 -49
- package/src/lib/shared/utils/memoize.ts +0 -49
- package/src/lib/shared/utils/utils.spec.ts +0 -142
- package/src/lib/shared/utils/utils.ts +0 -203
- package/src/lib/standalone.spec.ts +0 -36
- package/src/lib/standalone.ts +0 -28
- package/tsconfig.json +0 -20
- package/tsconfig.lib.json +0 -20
- package/tsconfig.spec.json +0 -9
- package/vite.config.ts +0 -41
- /package/src/{index.ts → index.d.ts} +0 -0
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { Experiment, ExperimentParsed, FetchExperiments } from '../models';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* This arrow function parses a given set of assigned experiments for analytics.
|
|
5
|
-
*
|
|
6
|
-
* This process involves iterating over the experiments, which are currently in the "Running" state as received from the DotCMS endpoint,
|
|
7
|
-
* analyzing each experiment's relevant data such as running ID, variant name, and look back window value.
|
|
8
|
-
* It also performs regular expression verification for both 'isExperimentPage' and 'isTargetPage' against the current URL.
|
|
9
|
-
*
|
|
10
|
-
* The parsed data is useful for tracking and understanding the user's interaction with the experiment-targeted components during their visit.
|
|
11
|
-
*
|
|
12
|
-
* Contains an object with experiments information.
|
|
13
|
-
*
|
|
14
|
-
* @param experiments
|
|
15
|
-
* @param {Location} location - This parameter is the object representing the current location (URL) of the user.
|
|
16
|
-
* Mostly employed for matching the regular expressions to detect whether the current page is an 'ExperimentPage' or a 'TargetPage'.
|
|
17
|
-
*
|
|
18
|
-
* @returns {ExperimentParsed} - The function returns an object with the original URL and an array of each experiment's comprehensive detail.
|
|
19
|
-
* The return object is suitable for further analytical operations. Each experiment's detail includes the experiment ID, running ID, variant name,
|
|
20
|
-
* look back window value, and booleans that represent whether current URL is 'isExperimentPage' or 'isTargetPage' for the respective experiment.
|
|
21
|
-
*/
|
|
22
|
-
export const parseDataForAnalytics = (
|
|
23
|
-
experiments: Experiment[],
|
|
24
|
-
location: Location
|
|
25
|
-
): ExperimentParsed => {
|
|
26
|
-
const currentHref = location.href;
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
href: currentHref,
|
|
30
|
-
experiments: experiments.map((experiment) => ({
|
|
31
|
-
experiment: experiment.id,
|
|
32
|
-
runningId: experiment.runningId,
|
|
33
|
-
variant: experiment.variant.name,
|
|
34
|
-
lookBackWindow: experiment.lookBackWindow.value,
|
|
35
|
-
isExperimentPage: verifyRegex(experiment.regexs.isExperimentPage, currentHref),
|
|
36
|
-
isTargetPage: verifyRegex(experiment.regexs.isTargetPage, currentHref)
|
|
37
|
-
}))
|
|
38
|
-
};
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* This utility function performs regular expression (regex) matching on a supplied URL.
|
|
43
|
-
*
|
|
44
|
-
* @param {string | null} regexToCheck - The regular expression to match against the URL.
|
|
45
|
-
* @param {string} href - This is the target URL, which is aimed to be matched against the provided regular expression.
|
|
46
|
-
* @returns {boolean} -The function returns a Boolean value.
|
|
47
|
-
*/
|
|
48
|
-
export const verifyRegex = (regexToCheck: string | null, href: string): boolean => {
|
|
49
|
-
if (regexToCheck === null || href === null) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const regexExp = new RegExp(regexToCheck);
|
|
55
|
-
|
|
56
|
-
const url = new URL(href);
|
|
57
|
-
|
|
58
|
-
const sanitizedHref = `${url.origin}${url.pathname.toLowerCase()}${url.search}`;
|
|
59
|
-
|
|
60
|
-
return regexExp.test(sanitizedHref);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.warn(`The regex ${regexToCheck} it is not a valid regex to check. ${error}`);
|
|
63
|
-
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* This function merges newly fetched data with the data stored from IndexedDB, preparing it for re-storage in IndexedDB.
|
|
70
|
-
*
|
|
71
|
-
* @param { AssignedExperiments | null } fetchExperiments - The experiment data fetched from the API.
|
|
72
|
-
* @param { AssignedExperiments | null } storedExperiments - The experiment data currently stored in IndexedDB.
|
|
73
|
-
*
|
|
74
|
-
* @returns { AssignedExperiments } - The parsed experiment data ready for storing.
|
|
75
|
-
*
|
|
76
|
-
* Following cases are handled -
|
|
77
|
-
* 1) When new Data is received without Old data. This is assumed to be the first time data is received.
|
|
78
|
-
* 2) When only old data is received, implying that the timestamp hasn't expired and the data is available in IndexedDB.
|
|
79
|
-
* The data is verified and any expired experiment is removed before being assigned to dataToStorage.
|
|
80
|
-
* 3) When both old data and new data is present. This implies that the record existed in IndexedDB but was fetched because the flag had expired.
|
|
81
|
-
* Additional operations are performed to add expiry time to experiments, merging all experiments, and storing them.
|
|
82
|
-
*
|
|
83
|
-
* There could be scenarios where none of these conditions are met, in that case, dataToStorage will be the default empty object.
|
|
84
|
-
*/
|
|
85
|
-
export const parseData = (
|
|
86
|
-
fetchExperiments: FetchExperiments,
|
|
87
|
-
storedExperiments: Experiment[] | undefined
|
|
88
|
-
): Experiment[] => {
|
|
89
|
-
let dataToStorage: Experiment[] = {} as Experiment[];
|
|
90
|
-
|
|
91
|
-
const { excludedExperimentIdsEnded } = fetchExperiments;
|
|
92
|
-
|
|
93
|
-
if (fetchExperiments && !storedExperiments) {
|
|
94
|
-
// TODO: Use fetchExperiment instead fetchExperimentsNoNoneExperimentID when the endpoint dont retrieve NONE experiment
|
|
95
|
-
// https://github.com/dotCMS/core/issues/27905
|
|
96
|
-
const fetchExperimentsNoNoneExperimentID: Experiment[] = fetchExperiments.experiments
|
|
97
|
-
? fetchExperiments.experiments.filter((experiment) => experiment.id !== 'NONE')
|
|
98
|
-
: [];
|
|
99
|
-
|
|
100
|
-
dataToStorage = addExpireTimeToExperiments(fetchExperimentsNoNoneExperimentID);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!fetchExperiments && storedExperiments) {
|
|
104
|
-
dataToStorage = getUnexpiredExperiments(storedExperiments, excludedExperimentIdsEnded);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (fetchExperiments && storedExperiments) {
|
|
108
|
-
// TODO: Use fetchExperiment instead fetchExperimentsNoNoneExperimentID when the endpoint dont retrieve NONE experiment
|
|
109
|
-
// https://github.com/dotCMS/core/issues/27905
|
|
110
|
-
const fetchExperimentsNoNoneExperimentID: Experiment[] = fetchExperiments.experiments
|
|
111
|
-
? fetchExperiments.experiments.filter((experiment) => experiment.id !== 'NONE')
|
|
112
|
-
: [];
|
|
113
|
-
|
|
114
|
-
dataToStorage = [
|
|
115
|
-
...addExpireTimeToExperiments(fetchExperimentsNoNoneExperimentID),
|
|
116
|
-
...getUnexpiredExperiments(storedExperiments, excludedExperimentIdsEnded)
|
|
117
|
-
];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return dataToStorage;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Retrieves the array of experiment IDs from the given AssignedExperiments..
|
|
125
|
-
*
|
|
126
|
-
* @returns {string[]} Returns an array of experiment IDs if available, otherwise an empty array.
|
|
127
|
-
* @param experiments
|
|
128
|
-
*/
|
|
129
|
-
export const getExperimentsIds = (experiments: Experiment[]): string[] =>
|
|
130
|
-
experiments.map((experiment: Experiment) => experiment.id) || [];
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Sets the expire time for new experiments based on the current time.
|
|
134
|
-
* The expire time is calculated by adding the expireMillis value of each experiment's lookBackWindow to the current time (Date.now()).
|
|
135
|
-
*
|
|
136
|
-
* @param {Array<Experiment>} experiments - An array of experiments to set the expire time for.
|
|
137
|
-
* @returns {Array<Experiment>} - An updated array of experiments with expire time set.
|
|
138
|
-
*/
|
|
139
|
-
const addExpireTimeToExperiments = (experiments: Experiment[]): Experiment[] => {
|
|
140
|
-
const now = Date.now();
|
|
141
|
-
|
|
142
|
-
return experiments.map((experiment) => ({
|
|
143
|
-
...experiment,
|
|
144
|
-
lookBackWindow: {
|
|
145
|
-
...experiment.lookBackWindow,
|
|
146
|
-
expireTime: now + experiment.lookBackWindow.expireMillis
|
|
147
|
-
}
|
|
148
|
-
}));
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Returns an array of experiments that have not expired yet.
|
|
153
|
-
*
|
|
154
|
-
* @param {Experiment[]} experiments - An array of experiments to filter.
|
|
155
|
-
* @param excludedExperimentIdsEnded - Array of Experiments ids that have been manually ended.
|
|
156
|
-
* @returns {Experiment[]} An array of unexpired experiments.
|
|
157
|
-
*/
|
|
158
|
-
const getUnexpiredExperiments = (
|
|
159
|
-
experiments: Experiment[],
|
|
160
|
-
excludedExperimentIdsEnded: string[]
|
|
161
|
-
): Experiment[] => {
|
|
162
|
-
const now = Date.now();
|
|
163
|
-
|
|
164
|
-
return experiments.filter((experiment) => {
|
|
165
|
-
const expireTime = experiment.lookBackWindow?.expireTime;
|
|
166
|
-
|
|
167
|
-
return expireTime
|
|
168
|
-
? expireTime > now && !excludedExperimentIdsEnded.includes(experiment.id)
|
|
169
|
-
: false;
|
|
170
|
-
});
|
|
171
|
-
};
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import fakeIndexedDB from 'fake-indexeddb';
|
|
2
|
-
|
|
3
|
-
import { IndexDBDatabaseHandler } from './index-db-database-handler';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
EXPERIMENT_ALREADY_CHECKED_KEY,
|
|
7
|
-
EXPERIMENT_DB_KEY_PATH,
|
|
8
|
-
EXPERIMENT_DB_STORE_NAME
|
|
9
|
-
} from '../constants';
|
|
10
|
-
import { IsUserIncludedResponse } from '../mocks/mock';
|
|
11
|
-
import { checkFlagExperimentAlreadyChecked } from '../utils/utils';
|
|
12
|
-
|
|
13
|
-
if (!globalThis.structuredClone) {
|
|
14
|
-
globalThis.structuredClone = function (obj) {
|
|
15
|
-
return JSON.parse(JSON.stringify(obj));
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let persistDatabaseHandler: IndexDBDatabaseHandler;
|
|
20
|
-
|
|
21
|
-
beforeAll(() => {
|
|
22
|
-
Object.defineProperty(window, 'indexedDB', {
|
|
23
|
-
writable: true,
|
|
24
|
-
value: fakeIndexedDB
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
persistDatabaseHandler = new IndexDBDatabaseHandler({
|
|
28
|
-
db_store: EXPERIMENT_DB_STORE_NAME,
|
|
29
|
-
db_name: EXPERIMENT_DB_STORE_NAME,
|
|
30
|
-
db_key_path: EXPERIMENT_DB_KEY_PATH
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('IndexedDB tests', () => {
|
|
35
|
-
it('saveData successfully saves data to the store', async () => {
|
|
36
|
-
const key = await persistDatabaseHandler.persistData(IsUserIncludedResponse.entity);
|
|
37
|
-
|
|
38
|
-
expect(key).toBe(EXPERIMENT_DB_KEY_PATH);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('getDataByKey successfully retrieves data from the store', async () => {
|
|
42
|
-
await persistDatabaseHandler.persistData(IsUserIncludedResponse.entity);
|
|
43
|
-
|
|
44
|
-
const data = await persistDatabaseHandler.getData();
|
|
45
|
-
|
|
46
|
-
expect(data).toEqual(IsUserIncludedResponse.entity);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('SessionStorage EXPERIMENT_ALREADY_CHECKED_KEY handle', () => {
|
|
51
|
-
Object.defineProperty(window, 'sessionStorage', {
|
|
52
|
-
value: {
|
|
53
|
-
setItem: jest.fn(),
|
|
54
|
-
getItem: jest.fn()
|
|
55
|
-
},
|
|
56
|
-
writable: true
|
|
57
|
-
});
|
|
58
|
-
it('should set true to sessionStorage key `EXPERIMENT_ALREADY_CHECKED_KEY` ', () => {
|
|
59
|
-
const value = 'true';
|
|
60
|
-
|
|
61
|
-
persistDatabaseHandler.setFlagExperimentAlreadyChecked();
|
|
62
|
-
|
|
63
|
-
expect(window.sessionStorage.setItem).toHaveBeenLastCalledWith(
|
|
64
|
-
EXPERIMENT_ALREADY_CHECKED_KEY,
|
|
65
|
-
value
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should set true to sessionStorage key `EXPERIMENT_ALREADY_CHECKED_KEY` ', () => {
|
|
70
|
-
const value = 'true';
|
|
71
|
-
|
|
72
|
-
persistDatabaseHandler.setFlagExperimentAlreadyChecked();
|
|
73
|
-
|
|
74
|
-
expect(window.sessionStorage.setItem).toHaveBeenLastCalledWith(
|
|
75
|
-
EXPERIMENT_ALREADY_CHECKED_KEY,
|
|
76
|
-
value
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe('checkFlagExperimentAlreadyChecked', () => {
|
|
81
|
-
const getItemMock = window.sessionStorage.getItem as jest.MockedFunction<
|
|
82
|
-
typeof window.sessionStorage.getItem
|
|
83
|
-
>;
|
|
84
|
-
|
|
85
|
-
const testCases = [
|
|
86
|
-
{ value: '', expected: false, description: 'sessionStorage value is ""' },
|
|
87
|
-
{ value: 'true', expected: true, description: 'sessionStorage value is "true"' },
|
|
88
|
-
{ value: null, expected: false, description: 'sessionStorage value is null' }
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
testCases.forEach(({ description, value, expected }) => {
|
|
92
|
-
it(`returns ${expected} when ${description}`, () => {
|
|
93
|
-
getItemMock.mockReturnValue(value);
|
|
94
|
-
|
|
95
|
-
expect(checkFlagExperimentAlreadyChecked()).toBe(expected);
|
|
96
|
-
expect(getItemMock).toHaveBeenCalledWith(EXPERIMENT_ALREADY_CHECKED_KEY);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
});
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Represents the configuration for a database connection.
|
|
3
|
-
* @interface
|
|
4
|
-
*/
|
|
5
|
-
import {
|
|
6
|
-
EXPERIMENT_ALREADY_CHECKED_KEY,
|
|
7
|
-
EXPERIMENT_FETCH_EXPIRE_TIME_KEY,
|
|
8
|
-
LOCAL_STORAGE_TIME_DURATION_MILLISECONDS
|
|
9
|
-
} from '../constants';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Represents the configuration for a database connection.
|
|
13
|
-
* @interface
|
|
14
|
-
*/
|
|
15
|
-
interface DbConfig {
|
|
16
|
-
db_name: string;
|
|
17
|
-
db_store: string;
|
|
18
|
-
db_key_path: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The default version of the database.
|
|
23
|
-
*
|
|
24
|
-
* @type {number}
|
|
25
|
-
* @constant
|
|
26
|
-
*/
|
|
27
|
-
const DB_DEFAULT_VERSION = 1;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The `DatabaseHandler` class offers specific methods to store and get data
|
|
31
|
-
* from IndexedDB.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* // To fetch data from the IndexedDB
|
|
35
|
-
* const data = await DatabaseHandler.getData();
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* // To store an object of type AssignedExperiments to IndexedDB
|
|
39
|
-
* await DatabaseHandler.persistData(anAssignedExperiment);
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* // To get an object of type AssignedExperiments to IndexedDB
|
|
43
|
-
* await DatabaseHandler.persistData(anAssignedExperiment);
|
|
44
|
-
*
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
export class IndexDBDatabaseHandler {
|
|
48
|
-
constructor(private config: DbConfig) {
|
|
49
|
-
if (!config) {
|
|
50
|
-
throw new Error('Config is required');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const { db_name, db_store, db_key_path } = config;
|
|
54
|
-
|
|
55
|
-
if (!db_name) {
|
|
56
|
-
throw new Error("'db_name' is required in config");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!db_store) {
|
|
60
|
-
throw new Error("'db_store' is required in config");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!db_key_path) {
|
|
64
|
-
throw new Error("'db_key_path' is required in config");
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Saves the provided data to indexDB.
|
|
70
|
-
*
|
|
71
|
-
* @async
|
|
72
|
-
* @param {AssignedExperiments} data - The data to be saved.
|
|
73
|
-
* @returns {Promise<any>} - The result of the save operation.
|
|
74
|
-
*/
|
|
75
|
-
public async persistData<T>(data: T): Promise<IDBValidKey> {
|
|
76
|
-
const db = await this.openDB();
|
|
77
|
-
|
|
78
|
-
return await new Promise((resolve, reject) => {
|
|
79
|
-
const transaction = db.transaction([this.config.db_store], 'readwrite');
|
|
80
|
-
|
|
81
|
-
const store = transaction.objectStore(this.config.db_store);
|
|
82
|
-
|
|
83
|
-
const clearRequest = store.clear();
|
|
84
|
-
|
|
85
|
-
clearRequest.onerror = () => reject(clearRequest.error);
|
|
86
|
-
clearRequest.onsuccess = () => {
|
|
87
|
-
const request = store.put(data, this.config.db_key_path);
|
|
88
|
-
|
|
89
|
-
request.onsuccess = () => resolve(request.result);
|
|
90
|
-
request.onerror = () => reject(request.error);
|
|
91
|
-
};
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Retrieves data from the database using a specific key.
|
|
97
|
-
*
|
|
98
|
-
* @async
|
|
99
|
-
* @returns {Promise<any>} A promise that resolves with the data retrieved from the database.
|
|
100
|
-
*/
|
|
101
|
-
public async getData<T>(): Promise<T> {
|
|
102
|
-
const db = await this.openDB();
|
|
103
|
-
|
|
104
|
-
return await new Promise((resolve, reject) => {
|
|
105
|
-
const transaction = db.transaction([this.config.db_store], 'readonly');
|
|
106
|
-
|
|
107
|
-
const store = transaction.objectStore(this.config.db_store);
|
|
108
|
-
|
|
109
|
-
const request = store.get(this.config.db_key_path);
|
|
110
|
-
|
|
111
|
-
request.onsuccess = () => resolve(request.result as T);
|
|
112
|
-
request.onerror = () => reject(request.error);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Deletes all the data from the IndexedDB store.
|
|
118
|
-
*
|
|
119
|
-
* @async
|
|
120
|
-
* @returns {Promise<void>} - The result of the delete operation.
|
|
121
|
-
*/
|
|
122
|
-
public async clearData(): Promise<void> {
|
|
123
|
-
const db = await this.openDB();
|
|
124
|
-
|
|
125
|
-
return await new Promise((resolve, reject) => {
|
|
126
|
-
const transaction = db.transaction([this.config.db_store], 'readwrite');
|
|
127
|
-
|
|
128
|
-
const store = transaction.objectStore(this.config.db_store);
|
|
129
|
-
|
|
130
|
-
const request = store.clear();
|
|
131
|
-
|
|
132
|
-
request.onsuccess = () => resolve();
|
|
133
|
-
request.onerror = () => reject(request.error);
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Sets the flag indicating that the experiment has already been checked.
|
|
139
|
-
*
|
|
140
|
-
* @function setFlagExperimentAlreadyChecked
|
|
141
|
-
* @returns {void}
|
|
142
|
-
*/
|
|
143
|
-
setFlagExperimentAlreadyChecked(): void {
|
|
144
|
-
sessionStorage.setItem(EXPERIMENT_ALREADY_CHECKED_KEY, 'true');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Sets the fetch expired time in the local storage.
|
|
149
|
-
*
|
|
150
|
-
* @return {void}
|
|
151
|
-
*/
|
|
152
|
-
setFetchExpiredTime(): void {
|
|
153
|
-
const expireTime = Date.now() + LOCAL_STORAGE_TIME_DURATION_MILLISECONDS;
|
|
154
|
-
|
|
155
|
-
localStorage.setItem(EXPERIMENT_FETCH_EXPIRE_TIME_KEY, expireTime.toString());
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Builds an error message based on the provided error object.
|
|
160
|
-
* @param {DOMException | null} error - The error object to build the message from.
|
|
161
|
-
* @returns {string} The constructed error message.
|
|
162
|
-
*/
|
|
163
|
-
private getOnErrorMessage(error: DOMException | null): string {
|
|
164
|
-
let errorMessage =
|
|
165
|
-
'A database error occurred while using IndexedDB. Your browser may not support IndexedDB or IndexedDB might not be enabled.';
|
|
166
|
-
|
|
167
|
-
if (error) {
|
|
168
|
-
errorMessage += error.name ? ` Error Name: ${error.name}` : '';
|
|
169
|
-
errorMessage += error.message ? ` Error Message: ${error.message}` : '';
|
|
170
|
-
errorMessage += error.code ? ` Error Code: ${error.code}` : '';
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return errorMessage;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Creates or opens a IndexedDB database with the specified version.
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* @returns {Promise<IDBDatabase>} A promise that resolves to the opened database.
|
|
181
|
-
* The promise will be rejected with an error message if there was a database error.
|
|
182
|
-
*/
|
|
183
|
-
private openDB(): Promise<IDBDatabase> {
|
|
184
|
-
return new Promise<IDBDatabase>((resolve, reject) => {
|
|
185
|
-
const request = indexedDB.open(this.config.db_name, DB_DEFAULT_VERSION);
|
|
186
|
-
|
|
187
|
-
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
|
188
|
-
const db = this.getRequestResult(event);
|
|
189
|
-
|
|
190
|
-
if (!db.objectStoreNames.contains(this.config.db_store)) {
|
|
191
|
-
db.createObjectStore(this.config.db_store);
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
request.onerror = (event) => {
|
|
196
|
-
const errorMsj = this.getOnErrorMessage((event.target as IDBRequest).error);
|
|
197
|
-
|
|
198
|
-
reject(errorMsj);
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
request.onsuccess = (event: Event) => {
|
|
202
|
-
const db = this.getRequestResult(event);
|
|
203
|
-
|
|
204
|
-
resolve(db);
|
|
205
|
-
};
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Retrieves the result of a database request from an Event object.
|
|
211
|
-
*
|
|
212
|
-
* @param {Event} event - The Event object containing the database request.
|
|
213
|
-
* @returns {IDBDatabase} - The result of the database request, casted as an IDBDatabase object.
|
|
214
|
-
*/
|
|
215
|
-
private getRequestResult(event: Event): IDBDatabase {
|
|
216
|
-
return (event.target as IDBRequest).result as IDBDatabase;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Logger for the dotCMS SDK
|
|
3
|
-
*/
|
|
4
|
-
export class DotLogger {
|
|
5
|
-
private readonly isDebug: boolean;
|
|
6
|
-
private readonly packageName: string;
|
|
7
|
-
|
|
8
|
-
constructor(isDebug: boolean, packageName: string) {
|
|
9
|
-
this.isDebug = isDebug;
|
|
10
|
-
this.packageName = packageName;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
public group(label: string) {
|
|
14
|
-
if (this.isDebug) {
|
|
15
|
-
// eslint-disable-next-line no-console
|
|
16
|
-
console.group(label);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
public groupEnd() {
|
|
21
|
-
if (this.isDebug) {
|
|
22
|
-
// eslint-disable-next-line no-console
|
|
23
|
-
console.groupEnd();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public time(label: string) {
|
|
28
|
-
if (this.isDebug) {
|
|
29
|
-
// eslint-disable-next-line no-console
|
|
30
|
-
console.time(label);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public timeEnd(label: string) {
|
|
35
|
-
if (this.isDebug) {
|
|
36
|
-
// eslint-disable-next-line no-console
|
|
37
|
-
console.timeEnd(label);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
public log(message: string) {
|
|
42
|
-
if (this.isDebug) {
|
|
43
|
-
// eslint-disable-next-line no-console
|
|
44
|
-
console.log(`[dotCMS ${this.packageName}] ${message}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
public warn(message: string) {
|
|
49
|
-
if (this.isDebug) {
|
|
50
|
-
console.warn(`[dotCMS ${this.packageName}] ${message}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public error(message: string) {
|
|
55
|
-
console.error(`[dotCMS ${this.packageName}] ${message}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { renderHook } from '@testing-library/react-hooks';
|
|
2
|
-
|
|
3
|
-
import { useMemoizedObject } from './memoize';
|
|
4
|
-
|
|
5
|
-
describe('useMemoizedObject', () => {
|
|
6
|
-
it('should return the same object if it has not changed', () => {
|
|
7
|
-
const initialObject = { a: 1, b: 2 };
|
|
8
|
-
|
|
9
|
-
const { result, rerender } = renderHook(({ obj }) => useMemoizedObject(obj), {
|
|
10
|
-
initialProps: { obj: initialObject }
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const firstRenderResult = result.current;
|
|
14
|
-
|
|
15
|
-
rerender({ obj: initialObject });
|
|
16
|
-
expect(result.current).toBe(firstRenderResult);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should return a new object if it has changed', () => {
|
|
20
|
-
const initialObject = { a: 1, b: 2 };
|
|
21
|
-
|
|
22
|
-
const newObject = { a: 1, b: 3 };
|
|
23
|
-
|
|
24
|
-
const { result, rerender } = renderHook(({ obj }) => useMemoizedObject(obj), {
|
|
25
|
-
initialProps: { obj: initialObject }
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const firstRenderResult = result.current;
|
|
29
|
-
|
|
30
|
-
rerender({ obj: newObject });
|
|
31
|
-
expect(result.current).not.toBe(firstRenderResult);
|
|
32
|
-
expect(result.current).toBe(newObject);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should return the same object if a deeply equal but different reference is passed', () => {
|
|
36
|
-
const initialObject = { a: 1, b: 2 };
|
|
37
|
-
|
|
38
|
-
const newObject = { a: 1, b: 2 };
|
|
39
|
-
|
|
40
|
-
const { result, rerender } = renderHook(({ obj }) => useMemoizedObject(obj), {
|
|
41
|
-
initialProps: { obj: initialObject }
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const firstRenderResult = result.current;
|
|
45
|
-
|
|
46
|
-
rerender({ obj: newObject });
|
|
47
|
-
expect(result.current).toBe(firstRenderResult);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useRef } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Compares two objects and returns true if they are equal, false otherwise.
|
|
5
|
-
* @param objA The first object to compare.
|
|
6
|
-
* @param objB The second object to compare.
|
|
7
|
-
* @returns
|
|
8
|
-
*/
|
|
9
|
-
function shallowEqual<T>(objA: T, objB: T): boolean {
|
|
10
|
-
if (Object.is(objA, objB)) {
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const keysA = Object.keys(objA) as Array<keyof T>;
|
|
19
|
-
|
|
20
|
-
const keysB = Object.keys(objB) as Array<keyof T>;
|
|
21
|
-
|
|
22
|
-
if (keysA.length !== keysB.length) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
for (const key of keysA) {
|
|
27
|
-
if (!Object.prototype.hasOwnProperty.call(objB, key) || !Object.is(objA[key], objB[key])) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Memoizes an object and returns the memoized object.
|
|
37
|
-
* Mantaing the same reference if the object is the same independently if is called inside any component.
|
|
38
|
-
* @param object
|
|
39
|
-
* @returns
|
|
40
|
-
*/
|
|
41
|
-
export function useMemoizedObject<T extends object>(object: T): T {
|
|
42
|
-
const ref = useRef<T>(object);
|
|
43
|
-
|
|
44
|
-
if (!shallowEqual(ref.current, object)) {
|
|
45
|
-
ref.current = object;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return ref.current;
|
|
49
|
-
}
|