@@ -49,61 +49,120 @@ export default class ConfigFeature {
49
49
* @protected
50
50
*/
51
51
matchConditionalFeatureSetting ( featureKeyName ) {
52
- const domains = this . _getFeatureSettings ( ) ?. [ featureKeyName ] || [ ] ;
53
- return domains . filter ( ( rule ) => {
52
+ const conditionalChanges = this . _getFeatureSettings ( ) ?. [ featureKeyName ] || [ ] ;
53
+ return conditionalChanges . filter ( ( rule ) => {
54
+ let condition = rule . condition ;
54
55
// Support shorthand for domain matching for backwards compatibility
55
- return this . matchConditionalChanges ( rule . condition || { domain : rule . domain } ) ;
56
+ if ( condition === undefined && 'domain' in rule ) {
57
+ condition = this . _domainToConditonBlocks ( rule . domain ) ;
58
+ }
59
+ return this . _matchConditionalBlockOrArray ( condition ) ;
56
60
} ) ;
57
61
}
58
62
63
+ /**
64
+ * Takes a list of domains and returns a list of condition blocks
65
+ * @param {string|string[] } domain
66
+ * @returns {ConditionBlock[] }
67
+ */
68
+ _domainToConditonBlocks ( domain ) {
69
+ if ( Array . isArray ( domain ) ) {
70
+ return domain . map ( ( domain ) => ( { domain } ) ) ;
71
+ } else {
72
+ return [ { domain } ] ;
73
+ }
74
+ }
75
+
59
76
/**
60
77
* Used to match conditional changes for a settings feature.
61
78
* @typedef {object } ConditionBlock
62
- * @property {string[] | string } domain?
63
- * @property {object } urlPattern?
79
+ * @property {string[] | string } [domain]
80
+ * @property {object } [urlPattern]
81
+ */
82
+
83
+ /**
84
+ * Takes multiple conditional blocks and returns true if any apply.
85
+ * @param {ConditionBlock|ConditionBlock[] } conditionBlock
86
+ * @returns {boolean }
64
87
*/
88
+ _matchConditionalBlockOrArray ( conditionBlock ) {
89
+ if ( Array . isArray ( conditionBlock ) ) {
90
+ return conditionBlock . some ( ( block ) => this . _matchConditionalBlock ( block ) ) ;
91
+ }
92
+ return this . _matchConditionalBlock ( conditionBlock ) ;
93
+ }
65
94
66
95
/**
67
96
* Takes a conditional block and returns true if it applies.
68
97
* All conditions must be met to return true.
69
98
* @param {ConditionBlock } conditionBlock
70
99
* @returns {boolean }
71
100
*/
72
- matchConditionalChanges ( conditionBlock ) {
101
+ _matchConditionalBlock ( conditionBlock ) {
102
+ // List of conditions that we support currently, these return truthy if the condition is met
103
+ /** @type {Record<string, (conditionBlock: ConditionBlock) => boolean> } */
104
+ const conditionChecks = {
105
+ domain : this . _matchDomainConditional ,
106
+ urlPattern : this . _matchUrlPatternConditional ,
107
+ } ;
108
+
73
109
for ( const key in conditionBlock ) {
74
- switch ( key ) {
75
- case 'domain' : {
76
- if ( ! this . _matchDomainConditional ( conditionBlock ) ) {
77
- return false ;
110
+ /*
111
+ Unsupported condition so fail for backwards compatibility
112
+ If you wish to support older clients you should create an old condition block
113
+ without the unsupported key also.
114
+ Such as:
115
+ [
116
+ {
117
+ condition: {
118
+ domain: 'example.com'
119
+ }
120
+ },
121
+ {
122
+ condition: {
123
+ domain: 'example.com',
124
+ newKey: 'value'
125
+ }
78
126
}
79
- break ;
80
- }
81
- case 'urlPattern' : {
82
- const pattern = new URLPattern ( conditionBlock . urlPattern ) ;
83
- if ( ! this . args ?. site . url || ! pattern . test ( this . args ?. site . url ) ) {
84
- return false ;
85
- }
86
- break ;
87
- }
88
- // For unknown keys we should assume the condition is not met
89
- default : {
90
- return false ;
91
- }
127
+ ]
128
+ */
129
+ if ( ! conditionChecks [ key ] ) {
130
+ return false ;
131
+ } else if ( ! conditionChecks [ key ] . call ( this , conditionBlock ) ) {
132
+ return false ;
92
133
}
93
134
}
94
-
95
- // All conditions are met
96
135
return true ;
97
136
}
98
137
138
+ /**
139
+ * Takes a condtion block and returns true if the current url matches the urlPattern.
140
+ * @param {ConditionBlock } conditionBlock
141
+ * @returns {boolean }
142
+ */
143
+ _matchUrlPatternConditional ( conditionBlock ) {
144
+ const url = this . args ?. site . url ;
145
+ if ( ! url ) return false ;
146
+ if ( typeof conditionBlock . urlPattern === 'string' ) {
147
+ // Use the current URL as the base for matching
148
+ return new URLPattern ( conditionBlock . urlPattern , url ) . test ( url ) ;
149
+ }
150
+ const pattern = new URLPattern ( conditionBlock . urlPattern ) ;
151
+ return pattern . test ( url ) ;
152
+ }
153
+
154
+ /**
155
+ * Takes a condition block and returns true if the current domain matches the domain.
156
+ * @param {ConditionBlock } conditionBlock
157
+ * @returns {boolean }
158
+ */
99
159
_matchDomainConditional ( conditionBlock ) {
100
160
if ( ! conditionBlock . domain ) return false ;
101
161
const domain = this . args ?. site . domain ;
102
162
if ( ! domain ) return false ;
103
163
if ( Array . isArray ( conditionBlock . domain ) ) {
104
- return conditionBlock . domain . some ( ( domainRule ) => {
105
- return matchHostname ( domain , domainRule ) ;
106
- } ) ;
164
+ // Explicitly check for an empty array as matchHostname will return true a single item array that matches
165
+ return false ;
107
166
}
108
167
return matchHostname ( domain , conditionBlock . domain ) ;
109
168
}
@@ -151,31 +210,71 @@ export default class ConfigFeature {
151
210
}
152
211
153
212
/**
154
- * Return a specific setting from the feature settings
155
- * If the "settings" key within the config has a "domains" key, it will be used to override the settings.
156
- * This uses JSONPatch to apply the patches to settings before getting the setting value.
157
- * For example.com getFeatureSettings('val') will return 1:
158
- * ```json
159
- * {
160
- * "settings": {
161
- * "domains": [
162
- * {
163
- * "domain": "example.com",
164
- * "patchSettings": [
165
- * { "op": "replace", "path": "/val", "value": 1 }
166
- * ]
167
- * }
168
- * ]
169
- * }
170
- * }
171
- * ```
172
- * "domain" can either be a string or an array of strings.
173
-
174
- * For boolean states you should consider using getFeatureSettingEnabled.
175
- * @param {string } featureKeyName
176
- * @param {string } [featureName]
177
- * @returns {any }
178
- */
213
+ * Return a specific setting from the feature settings
214
+ * If the "settings" key within the config has a "conditionalChanges" key, it will be used to override the settings.
215
+ * This uses JSONPatch to apply the patches to settings before getting the setting value.
216
+ * For example.com getFeatureSettings('val') will return 1:
217
+ * ```json
218
+ * {
219
+ * "settings": {
220
+ * "conditionalChanges": [
221
+ * {
222
+ * "domain": "example.com",
223
+ * "patchSettings": [
224
+ * { "op": "replace", "path": "/val", "value": 1 }
225
+ * ]
226
+ * }
227
+ * ]
228
+ * }
229
+ * }
230
+ * ```
231
+ * "domain" can either be a string or an array of strings.
232
+ * Additionally we support urlPattern for more complex matching.
233
+ * For example.com getFeatureSettings('val') will return 1:
234
+ * ```json
235
+ * {
236
+ * "settings": {
237
+ * "conditionalChanges": [
238
+ * {
239
+ * "condition": {
240
+ * "urlPattern": "https://example.com/*",
241
+ * },
242
+ * "patchSettings": [
243
+ * { "op": "replace", "path": "/val", "value": 1 }
244
+ * ]
245
+ * }
246
+ * ]
247
+ * }
248
+ * }
249
+ * ```
250
+ * We also support multiple conditions:
251
+ * ```json
252
+ * {
253
+ * "settings": {
254
+ * "conditionalChanges": [
255
+ * {
256
+ * "condition": [
257
+ * {
258
+ * "urlPattern": "https://example.com/*",
259
+ * },
260
+ * {
261
+ * "urlPattern": "https://other.com/path/something",
262
+ * },
263
+ * ],
264
+ * "patchSettings": [
265
+ * { "op": "replace", "path": "/val", "value": 1 }
266
+ * ]
267
+ * }
268
+ * ]
269
+ * }
270
+ * }
271
+ * ```
272
+ *
273
+ * For boolean states you should consider using getFeatureSettingEnabled.
274
+ * @param {string } featureKeyName
275
+ * @param {string } [featureName]
276
+ * @returns {any }
277
+ */
179
278
getFeatureSetting ( featureKeyName , featureName ) {
180
279
let result = this . _getFeatureSettings ( featureName ) ;
181
280
if ( featureKeyName in [ 'domains' , 'conditionalChanges' ] ) {
0 commit comments