Skip to content

Commit 6f3d8d1

Browse files
committed
(wip) refactor: move utility function to a separate class
1 parent dc37f94 commit 6f3d8d1

File tree

1 file changed

+176
-145
lines changed

1 file changed

+176
-145
lines changed

injected/src/content-feature.js

Lines changed: 176 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,186 @@ import { extensionConstructMessagingConfig } from './sendmessage-transport.js';
2121
* @property {string[]} [enabledFeatures]
2222
*/
2323

24-
export default class ContentFeature {
24+
class ConfigParser {
2525
/** @type {import('./utils.js').RemoteConfig | undefined} */
2626
#bundledConfig;
27+
28+
/** @type {import('./content-scope-features.js').LoadArgs} */
29+
#loadArgs;
30+
31+
/** @type {Record<string, unknown> | undefined} */
32+
#featureSettings;
33+
34+
/** @type {any} */
35+
name;
36+
2737
/** @type {object | undefined} */
2838
#trackerLookup;
39+
2940
/** @type {boolean | undefined} */
3041
#documentOriginIsTracker;
31-
/** @type {Record<string, unknown> | undefined} */
32-
// eslint-disable-next-line no-unused-private-class-members
33-
#bundledfeatureSettings;
42+
43+
/**
44+
* @param {any} name
45+
*/
46+
constructor(name) {
47+
this.name = name;
48+
}
49+
50+
/**
51+
* @param {import('./content-scope-features.js').LoadArgs} loadArgs
52+
*/
53+
initConfig(loadArgs) {
54+
const { bundledConfig, site, platform } = loadArgs;
55+
this.#bundledConfig = bundledConfig;
56+
// If we have a bundled config, treat it as a regular config
57+
// This will be overriden by the remote config if it is available
58+
if (this.#bundledConfig) {
59+
const enabledFeatures = computeEnabledFeatures(bundledConfig, site.domain, platform.version);
60+
this.#featureSettings = parseFeatureSettings(bundledConfig, enabledFeatures);
61+
}
62+
this.#trackerLookup = loadArgs.trackerLookup;
63+
this.#documentOriginIsTracker = loadArgs.documentOriginIsTracker;
64+
}
65+
66+
/**
67+
* Given a config key, interpret the value as a list of domain overrides, and return the elements that match the current page
68+
* Consider using patchSettings instead as per `getFeatureSetting`.
69+
* @param {string} featureKeyName
70+
* @return {any[]}
71+
* @protected
72+
*/
73+
matchDomainFeatureSetting(featureKeyName) {
74+
const domain = this.#loadArgs?.site.domain;
75+
if (!domain) return [];
76+
const domains = this._getFeatureSettings()?.[featureKeyName] || [];
77+
return domains.filter((rule) => {
78+
if (Array.isArray(rule.domain)) {
79+
return rule.domain.some((domainRule) => {
80+
return matchHostname(domain, domainRule);
81+
});
82+
}
83+
return matchHostname(domain, rule.domain);
84+
});
85+
}
86+
87+
/**
88+
* Return the settings object for a feature
89+
* @param {string} [featureName] - The name of the feature to get the settings for; defaults to the name of the feature
90+
* @returns {any}
91+
*/
92+
_getFeatureSettings(featureName) {
93+
const camelFeatureName = featureName ?? camelcase(this.name);
94+
return this.#featureSettings?.[camelFeatureName];
95+
}
96+
97+
/**
98+
* For simple boolean settings, return true if the setting is 'enabled'
99+
* For objects, verify the 'state' field is 'enabled'.
100+
* This allows for future forwards compatibility with more complex settings if required.
101+
* For example:
102+
* ```json
103+
* {
104+
* "toggle": "enabled"
105+
* }
106+
* ```
107+
* Could become later (without breaking changes):
108+
* ```json
109+
* {
110+
* "toggle": {
111+
* "state": "enabled",
112+
* "someOtherKey": 1
113+
* }
114+
* }
115+
* ```
116+
* This also supports domain overrides as per `getFeatureSetting`.
117+
* @param {string} featureKeyName
118+
* @param {string} [featureName]
119+
* @returns {boolean}
120+
*/
121+
getFeatureSettingEnabled(featureKeyName, featureName) {
122+
const result = this.getFeatureSetting(featureKeyName, featureName);
123+
if (typeof result === 'object') {
124+
return result.state === 'enabled';
125+
}
126+
return result === 'enabled';
127+
}
128+
129+
/**
130+
* Return a specific setting from the feature settings
131+
* If the "settings" key within the config has a "domains" key, it will be used to override the settings.
132+
* This uses JSONPatch to apply the patches to settings before getting the setting value.
133+
* For example.com getFeatureSettings('val') will return 1:
134+
* ```json
135+
* {
136+
* "settings": {
137+
* "domains": [
138+
* {
139+
* "domain": "example.com",
140+
* "patchSettings": [
141+
* { "op": "replace", "path": "/val", "value": 1 }
142+
* ]
143+
* }
144+
* ]
145+
* }
146+
* }
147+
* ```
148+
* "domain" can either be a string or an array of strings.
149+
150+
* For boolean states you should consider using getFeatureSettingEnabled.
151+
* @param {string} featureKeyName
152+
* @param {string} [featureName]
153+
* @returns {any}
154+
*/
155+
getFeatureSetting(featureKeyName, featureName) {
156+
let result = this._getFeatureSettings(featureName);
157+
if (featureKeyName === 'domains') {
158+
throw new Error('domains is a reserved feature setting key name');
159+
}
160+
const domainMatch = [...this.matchDomainFeatureSetting('domains')].sort((a, b) => {
161+
return a.domain.length - b.domain.length;
162+
});
163+
for (const match of domainMatch) {
164+
if (match.patchSettings === undefined) {
165+
continue;
166+
}
167+
try {
168+
result = immutableJSONPatch(result, match.patchSettings);
169+
} catch (e) {
170+
console.error('Error applying patch settings', e);
171+
}
172+
}
173+
return result?.[featureKeyName];
174+
}
175+
176+
/**
177+
* @returns {object}
178+
**/
179+
get trackerLookup() {
180+
return this.#trackerLookup || {};
181+
}
182+
183+
/**
184+
* @returns {import('./utils.js').RemoteConfig | undefined}
185+
**/
186+
get bundledConfig() {
187+
return this.#bundledConfig;
188+
}
189+
190+
/**
191+
* @returns {boolean}
192+
*/
193+
get documentOriginIsTracker() {
194+
return !!this.#documentOriginIsTracker;
195+
}
196+
197+
set documentOriginIsTracker(value) {
198+
this.#documentOriginIsTracker = value;
199+
}
200+
}
201+
202+
export default class ContentFeature extends ConfigParser {
203+
/** @type {import('./utils.js').RemoteConfig | undefined} */
34204
/** @type {import('../../messaging').Messaging} */
35205
// eslint-disable-next-line no-unused-private-class-members
36206
#messaging;
@@ -41,7 +211,7 @@ export default class ContentFeature {
41211
#args;
42212

43213
constructor(featureName) {
44-
this.name = featureName;
214+
super(featureName);
45215
this.#args = null;
46216
this.monitor = new PerformanceMonitor();
47217
}
@@ -77,27 +247,6 @@ export default class ContentFeature {
77247
return this.#args?.assets;
78248
}
79249

80-
/**
81-
* @returns {boolean}
82-
*/
83-
get documentOriginIsTracker() {
84-
return !!this.#documentOriginIsTracker;
85-
}
86-
87-
/**
88-
* @returns {object}
89-
**/
90-
get trackerLookup() {
91-
return this.#trackerLookup || {};
92-
}
93-
94-
/**
95-
* @returns {import('./utils.js').RemoteConfig | undefined}
96-
**/
97-
get bundledConfig() {
98-
return this.#bundledConfig;
99-
}
100-
101250
/**
102251
* @deprecated as we should make this internal to the class and not used externally
103252
* @return {MessagingContext}
@@ -144,116 +293,6 @@ export default class ContentFeature {
144293
return processAttr(configSetting, defaultValue);
145294
}
146295

147-
/**
148-
* Return a specific setting from the feature settings
149-
* If the "settings" key within the config has a "domains" key, it will be used to override the settings.
150-
* This uses JSONPatch to apply the patches to settings before getting the setting value.
151-
* For example.com getFeatureSettings('val') will return 1:
152-
* ```json
153-
* {
154-
* "settings": {
155-
* "domains": [
156-
* {
157-
* "domain": "example.com",
158-
* "patchSettings": [
159-
* { "op": "replace", "path": "/val", "value": 1 }
160-
* ]
161-
* }
162-
* ]
163-
* }
164-
* }
165-
* ```
166-
* "domain" can either be a string or an array of strings.
167-
168-
* For boolean states you should consider using getFeatureSettingEnabled.
169-
* @param {string} featureKeyName
170-
* @param {string} [featureName]
171-
* @returns {any}
172-
*/
173-
getFeatureSetting(featureKeyName, featureName) {
174-
let result = this._getFeatureSettings(featureName);
175-
if (featureKeyName === 'domains') {
176-
throw new Error('domains is a reserved feature setting key name');
177-
}
178-
const domainMatch = [...this.matchDomainFeatureSetting('domains')].sort((a, b) => {
179-
return a.domain.length - b.domain.length;
180-
});
181-
for (const match of domainMatch) {
182-
if (match.patchSettings === undefined) {
183-
continue;
184-
}
185-
try {
186-
result = immutableJSONPatch(result, match.patchSettings);
187-
} catch (e) {
188-
console.error('Error applying patch settings', e);
189-
}
190-
}
191-
return result?.[featureKeyName];
192-
}
193-
194-
/**
195-
* Return the settings object for a feature
196-
* @param {string} [featureName] - The name of the feature to get the settings for; defaults to the name of the feature
197-
* @returns {any}
198-
*/
199-
_getFeatureSettings(featureName) {
200-
const camelFeatureName = featureName || camelcase(this.name);
201-
return this.#args?.featureSettings?.[camelFeatureName];
202-
}
203-
204-
/**
205-
* For simple boolean settings, return true if the setting is 'enabled'
206-
* For objects, verify the 'state' field is 'enabled'.
207-
* This allows for future forwards compatibility with more complex settings if required.
208-
* For example:
209-
* ```json
210-
* {
211-
* "toggle": "enabled"
212-
* }
213-
* ```
214-
* Could become later (without breaking changes):
215-
* ```json
216-
* {
217-
* "toggle": {
218-
* "state": "enabled",
219-
* "someOtherKey": 1
220-
* }
221-
* }
222-
* ```
223-
* This also supports domain overrides as per `getFeatureSetting`.
224-
* @param {string} featureKeyName
225-
* @param {string} [featureName]
226-
* @returns {boolean}
227-
*/
228-
getFeatureSettingEnabled(featureKeyName, featureName) {
229-
const result = this.getFeatureSetting(featureKeyName, featureName);
230-
if (typeof result === 'object') {
231-
return result.state === 'enabled';
232-
}
233-
return result === 'enabled';
234-
}
235-
236-
/**
237-
* Given a config key, interpret the value as a list of domain overrides, and return the elements that match the current page
238-
* Consider using patchSettings instead as per `getFeatureSetting`.
239-
* @param {string} featureKeyName
240-
* @return {any[]}
241-
* @private
242-
*/
243-
matchDomainFeatureSetting(featureKeyName) {
244-
const domain = this.#args?.site.domain;
245-
if (!domain) return [];
246-
const domains = this._getFeatureSettings()?.[featureKeyName] || [];
247-
return domains.filter((rule) => {
248-
if (Array.isArray(rule.domain)) {
249-
return rule.domain.some((domainRule) => {
250-
return matchHostname(domain, domainRule);
251-
});
252-
}
253-
return matchHostname(domain, rule.domain);
254-
});
255-
}
256-
257296
init(args) {}
258297

259298
callInit(args) {
@@ -313,15 +352,7 @@ export default class ContentFeature {
313352
const mark = this.monitor.mark(this.name + 'CallLoad');
314353
this.#args = args;
315354
this.platform = args.platform;
316-
this.#bundledConfig = args.bundledConfig;
317-
// If we have a bundled config, treat it as a regular config
318-
// This will be overriden by the remote config if it is available
319-
if (this.#bundledConfig && this.#args) {
320-
const enabledFeatures = computeEnabledFeatures(args.bundledConfig, args.site.domain, this.platform.version);
321-
this.#args.featureSettings = parseFeatureSettings(args.bundledConfig, enabledFeatures);
322-
}
323-
this.#trackerLookup = args.trackerLookup;
324-
this.#documentOriginIsTracker = args.documentOriginIsTracker;
355+
this.initConfig(args);
325356
this.load(args);
326357
mark.end();
327358
}

0 commit comments

Comments
 (0)