• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 15829819958

23 Jun 2025 04:29PM UTC coverage: 76.979% (+0.03%) from 76.95%
15829819958

Pull #11183

github

web-flow
Merge 124f321fe into 7cde38ac9
Pull Request #11183: #11165: Option to deny app context for normal users

31124 of 48441 branches covered (64.25%)

14 of 16 new or added lines in 5 files covered. (87.5%)

1401 existing lines in 128 files now uncovered.

38752 of 50341 relevant lines covered (76.98%)

36.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.38
/web/client/utils/ConfigUtils.js
1
/**
2
 * Copyright 2015, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8
import Proj4js from 'proj4';
9
import PropTypes from 'prop-types';
10
import url from 'url';
11
import axios from 'axios';
12
import { castArray, isArray, isObject, endsWith, isNil, get, mergeWith } from 'lodash';
13
import { Promise } from 'es6-promise';
14
import isMobile from 'ismobilejs';
15
import {mergeConfigsPatch} from "@mapstore/patcher";
16

17
const epsg4326 = Proj4js ? new Proj4js.Proj('EPSG:4326') : null;
1!
18
const centerPropType = PropTypes.shape({
1✔
19
    x: PropTypes.number.isRequired,
20
    y: PropTypes.number.isRequired,
21
    crs: PropTypes.string
22
});
23

24
const urlQuery = url.parse(window.location.href, true).query;
1✔
25

26
let localConfigFile = 'configs/localConfig.json';
1✔
27

28
let defaultConfig = {
1✔
29
    // TODO: these should be changed tp relative paths, without /mapstore/ or / (to avoid the needing of overriding in default cases)
30
    proxyUrl: "/mapstore/proxy/?url=",
31
    geoStoreUrl: "/rest/geostore/",
32
    printUrl: "/mapstore/print/info.json",
33
    translationsPath: "translations",
34
    extensionsRegistry: "extensions/extensions.json",
35
    extensionsFolder: "extensions/",
36
    configurationFolder: "configs/",
37
    contextPluginsConfiguration: "configs/pluginsConfig.json",
38
    projectionDefs: [],
39
    themePrefix: "ms2",
40
    bingApiKey: null,
41
    mapquestApiKey: null,
42
    mapboxAccessToken: '',
43
    defaultSourceType: "gxp_wmssource",
44
    backgroundGroup: "background",
45
    userSessions: {
46
        enabled: false
47
    }
48
};
49

50
export const getConfigurationOptions = function(query, defaultName, extension, geoStoreBase) {
1✔
51
    const mapId = query.mapId;
22✔
52
    let configUrl;
53
    if (mapId) {
22✔
54
        configUrl = ( geoStoreBase || defaultConfig.geoStoreUrl ) + "data/" + mapId;
12✔
55
    } else {
56
        configUrl = defaultConfig.configurationFolder + (query.config || defaultName || 'config') + '.' + (extension || 'json');
10✔
57
    }
58
    return {
22✔
59
        configUrl: configUrl,
60
        legacy: !!mapId
61
    };
62
};
63
/**
64
 * WORKAROUND: it removes the extra ? when the authkey param is present
65
 * the url was like         http......?authkey=....?service=....&otherparam
66
 * that should become like  http......?authkey=....&service=....&otherparam
67
 *
68
 * The problem happens when you have this in the Record.properties
69
 * file of the csw folder in GeoServer, with csw plugin installed:
70
 * references.scheme='OGC:WMS'
71
 * references.value=strConcat('${url.wms}?service=WMS&request=GetMap&layers=', prefixedName)
72
 * That is ok when you are not using an authkey, but If you have the authkey
73
 * module installed and you get record with a proper authkey parameter the
74
 * ${url.wms} URL will have also ?authkey=... and so the final URL is something like:
75
 * http://domain.org/geoserver/?autkey=abcdefghijklmnopqrstuvz1234567890?service=WMS&request=GetMap&layers=LAYER_NAME
76
 * ${url.wms} is replaced with the wms URL after the execution of strConcat,
77
 * so the url can not be parsed in any way to solve the problem via configuration,
78
 * because you can not know if ${url.wms} contains ? or not.
79
*/
80
export const cleanDuplicatedQuestionMarks = (urlToNormalize) => {
1✔
81
    const urlParts = urlToNormalize.split("?");
836✔
82
    if (urlParts.length > 2) {
836✔
83
        let newUrlParts = urlParts.slice(1);
3✔
84
        return urlParts[0] + "?" + newUrlParts.join("&");
3✔
85
    }
86
    return urlToNormalize;
833✔
87
};
88
/**
89
 * it removes some params from the query string
90
 * return the shrinked url
91
*/
92
export const getUrlWithoutParameters = (urlToFilter, skip) => {
1✔
93
    const urlparts = cleanDuplicatedQuestionMarks(urlToFilter).split('?');
419✔
94
    let paramsFiltered = "";
419✔
95
    if (urlparts.length >= 2 && urlparts[1]) {
419✔
96
        const pars = urlparts[1].split(/[&;]/g).filter( p => !!p);
373✔
97
        pars.forEach((par, i) => {
60✔
98
            const param = par.split('=');
372✔
99
            if (skip.indexOf(param[0].toLowerCase()) === -1) {
372✔
100
                let addAnd = i === (pars.length - 1) ? "" : "&";
317✔
101
                paramsFiltered += param.join("=") + addAnd;
317✔
102
            }
103
        });
104
    }
105
    return !!paramsFiltered ? urlparts[0] + "?" + paramsFiltered : urlparts[0];
419✔
106
};
107

108
export const filterUrlParams = (urlToFilter, params = []) => {
1!
109
    if (isNil(urlToFilter) || urlToFilter === "") {
365✔
110
        return null;
1✔
111
    }
112
    return getUrlWithoutParameters(cleanDuplicatedQuestionMarks(urlToFilter), params);
364✔
113
};
114

115
export const getParsedUrl = (urlToParse, options, params = []) => {
1✔
116
    if (urlToParse) {
17✔
117
        const parsed = url.parse(filterUrlParams(urlToParse, params), true);
2✔
118
        let newPathname = null;
2✔
119
        if (endsWith(parsed.pathname, "wfs") || endsWith(parsed.pathname, "wms") || endsWith(parsed.pathname, "ows")) {
2✔
120
            newPathname = parsed.pathname.replace(/(wms|ows|wfs|wps)$/, "wps");
1✔
121
            return url.format(Object.assign({}, parsed, {search: null, pathname: newPathname }, {
1✔
122
                query: Object.assign({
123
                    service: "WPS",
124
                    ...options
125
                }, parsed.query)
126
            }));
127
        }
128
    }
129
    return null;
16✔
130
};
131
export const getDefaults = function() {
1✔
132
    return {...defaultConfig};
185✔
133
};
134
export const setLocalConfigurationFile = function(file) {
1✔
135
    localConfigFile = file;
115✔
136
};
137

138
export const loadConfiguration = function() {
1✔
139
    if (localConfigFile) {
28✔
140
        const configFiles = castArray(localConfigFile);
27✔
141
        return axios.all(configFiles.map(config =>
27✔
142
            axios.get(config)
28✔
143
                .then(response => response.data)
26✔
144
                .catch(() => null)
2✔
145
        )).then(configs => {
146
            const [main, ...patches] = configs;
27✔
147
            if (!main) throw new Error("local configuration file is broken");
27✔
148
            const merged = mergeConfigsPatch(main, patches.filter(c => c && typeof c === "object"));
25✔
149
            defaultConfig = merged ? {...defaultConfig, ...merged} : defaultConfig;
25!
150
            return {...defaultConfig};
25✔
151
        });
152
    }
153
    return new Promise((resolve) => {
1✔
154
        resolve({...defaultConfig});
1✔
155
    });
156
};
157

158
export const getCenter = function(center, projection) {
1✔
159
    const point = isArray(center) ? {x: center[0], y: center[1]} : center;
50✔
160
    const crs = center.crs || projection || 'EPSG:4326';
50✔
161
    const transformed = crs !== 'EPSG:4326' ? Proj4js.transform(new Proj4js.Proj(crs), epsg4326, point) : point;
50✔
162
    return Object.assign({}, transformed, {crs: "EPSG:4326"});
50✔
163
};
164

165
export const setApiKeys = function(layer) {
1✔
166
    if (layer.type === 'bing') {
51✔
167
        layer.apiKey = defaultConfig.bingApiKey;
1✔
168
    }
169
    if (layer.type === 'mapquest') {
51✔
170
        layer.apiKey = defaultConfig.mapquestApiKey;
4✔
171
    }
172
    if (layer.type === 'tileprovider' && ['MapBoxStyle', 'MapBox'].includes(layer.provider)) {
51!
173
        // include an empty string if missing to avoid errors in the layer url template
UNCOV
174
        layer.accessToken = defaultConfig.mapboxAccessToken;
×
175
    }
176
    return layer;
51✔
177
};
178

179
export const setLayerId = function(layer, i) {
1✔
180
    if (!layer.id) {
51✔
181
        layer.id = layer.name + "__" + i;
47✔
182
    }
183
    return layer;
51✔
184
};
185

186
export const replacePlaceholders = function(inputUrl) {
1✔
187
    let currentUrl = inputUrl;
41✔
188
    (currentUrl.match(/\{.*?\}/g) || []).forEach((placeholder) => {
41✔
189
        const replacement = defaultConfig[placeholder.substring(1, placeholder.length - 1)];
7✔
190
        // replacement must exist, or the URL is intended as a real template for the URL (e.g REST URLs of WMTS)
191
        if (replacement !== undefined) {
7✔
192
            currentUrl = currentUrl.replace(placeholder, replacement || '');
6!
193
        }
194
    });
195
    return currentUrl;
41✔
196
};
197

198
export const setUrlPlaceholders = function(layer) {
1✔
199
    if (layer.url) {
54✔
200
        if (isArray(layer.url)) {
18✔
201
            layer.url = layer.url.map((currentUrl) => {
5✔
202
                return replacePlaceholders(currentUrl);
26✔
203
            });
204
        } else {
205
            layer.url = replacePlaceholders(layer.url);
13✔
206
        }
207
    }
208
    return layer;
54✔
209
};
210

211
export const normalizeConfig = function(config) {
1✔
212
    const {layers, groups, plugins, ...other} = config;
24✔
213
    other.center = getCenter(other.center);
24✔
214
    return {
24✔
215
        map: other,
216
        layers: layers.map(setApiKeys, config).map(setLayerId).map(setUrlPlaceholders),
217
        groups: groups,
218
        plugins: plugins
219
    };
220
};
221

222
export const getUserConfiguration = function(defaultName, extension, geoStoreBase) {
1✔
223
    return getConfigurationOptions(urlQuery, defaultName, extension, geoStoreBase);
1✔
224
};
225

226
export const getConfigUrl = ({mapId, config}) => {
1✔
227
    let id = mapId;
13✔
228
    let configUrl = config;
13✔
229
    // if mapId is a string, is the name of the config to load
230
    try {
13✔
231
        let mapIdNumber = parseInt(id, 10);
13✔
232
        if (isNaN(mapIdNumber)) {
13✔
233
            configUrl = mapId;
3✔
234
            id = null;
3✔
235
        }
236
    } catch (e) {
UNCOV
237
        configUrl = mapId;
×
238
        id = null;
×
239
    }
240
    return getConfigurationOptions({mapId: id, config: configUrl});
13✔
241
};
242

243
/**
244
 * set default wms source
245
 */
246
export const setupSources = function(sources, defaultSourceType) {
1✔
247
    var defType = defaultSourceType;
4✔
248
    var source;
249
    if (!defaultSourceType) {
4!
250
        defType = defaultConfig.defaultSourceType;
4✔
251
    }
252
    for (source in sources) {
4✔
253
        if (sources.hasOwnProperty(source)) {
28!
254
            if (!sources[source].ptype) {
28✔
255
                sources[source].ptype = defType;
8✔
256
            }
257
        }
258
    }
259
};
260

261
export const normalizeSourceUrl = function(sourceUrl) {
1✔
262
    if (sourceUrl && sourceUrl.indexOf('?') !== -1) {
24✔
263
        return sourceUrl.split('?')[0];
4✔
264
    }
265
    return sourceUrl;
20✔
266
};
267

268
/**
269
 * Copy important source options to layer options.
270
 */
271
export const copySourceOptions = function(layer, source) {
1✔
272
    layer.baseParams = source.baseParams;
24✔
273
    if (source.url) {
24✔
274
        let sourceParts = url.parse(source.url, true);
8✔
275
        for (let k in sourceParts.query) {
8✔
276
            if (k.toUpperCase() === "REQUEST" ) {
4!
UNCOV
277
                delete sourceParts.query[k];
×
278
            }
279
        }
280
        layer.baseParams = Object.assign({}, layer.baseParams, sourceParts.query);
8✔
281
    }
282
    layer.url = normalizeSourceUrl(source.url);
24✔
283
};
284

285
/**
286
 * Setup the layer visibility for the background group.
287
 * if background layers are not visible, sets the last one
288
 */
289
export const setupLayers = function(layers, sources, supportedSourceTypes) {
1✔
290
    // setup background visibility
291
    var candidateVisible;
292
    var i; var layer; var source;
293
    for (i = 0; i < layers.length; i++) {
4✔
294
        layer = layers[i];
24✔
295
        source = sources[layer.source];
24✔
296
        if (source) {
24!
297
            copySourceOptions(layer, source);
24✔
298
        }
299

300
        let type = source.ptype;
24✔
301
        if (type) {
24!
302
            layer.type = type.replace(/^gxp_(.*)source$/i, "$1");
24✔
303
        } else {
UNCOV
304
            layer.type = 'unknown';
×
305
        }
306
        if (layer) {
24!
307
            if (supportedSourceTypes.indexOf(source && source.ptype) >= 0) {
24!
308
                if (layer.group === defaultConfig.backgroundGroup) {
24✔
309
                    // force to false if undefined
310
                    layer.visibility = layer.visibility || false;
16✔
311
                    if (candidateVisible && candidateVisible.visibility) {
16✔
312
                        /* if more than one layer is visible in the background group
313
                            shows only the last one hiding the previous.
314
                        */
315
                        if (layer.visibility) {
4!
316
                            candidateVisible.visibility = false;
4✔
317
                            candidateVisible = layer;
4✔
318
                        }
319
                    } else {
320
                        candidateVisible = layer;
12✔
321
                    }
322
                }
323
            } else {
UNCOV
324
                layer.visibility = false;
×
325
            }
326
        }
327
    }
328
    // set the candidate visible
329
    if (candidateVisible) {
4!
330
        candidateVisible.visibility = true;
4✔
331
    }
332
};
333

334
export const convertFromLegacy = function(config) {
1✔
335
    var mapConfig = config.map;
4✔
336
    var sources = config.gsSources || config.sources;
4!
337
    var layers = mapConfig.layers.filter(layer => sources[layer.source]);
24✔
338
    var latLng = getCenter(mapConfig.center, mapConfig.projection);
4✔
339
    var zoom = mapConfig.zoom;
4✔
340
    var maxExtent = mapConfig.maxExtent || mapConfig.extent;
4!
341

342
    // setup layers and sources with defaults
343
    setupSources(sources, config.defaultSourceType);
4✔
344
    setupLayers(layers, sources, ["gxp_osmsource", "gxp_wmssource", "gxp_googlesource", "gxp_bingsource", "gxp_mapquestsource", "gxp_olsource"]);
4✔
345
    return normalizeConfig({
4✔
346
        center: latLng,
347
        zoom: zoom,
348
        maxExtent: maxExtent, // TODO convert maxExtent
349
        layers: layers,
350
        projection: mapConfig.projection || 'EPSG:3857'
4!
351
    });
352
};
353

354
/**
355
 * Utility to merge different configs
356
 */
357
export const mergeConfigs = function(baseConfig, mapConfig) {
1✔
358
    baseConfig.map = mapConfig.map;
1✔
359
    baseConfig.gsSources = mapConfig.gsSources || mapConfig.sources;
1✔
360
    return baseConfig;
1✔
361
};
362
export const getProxyUrl = function(config) {
1✔
363
    return config?.proxyUrl ? config.proxyUrl : defaultConfig.proxyUrl;
578✔
364
};
365

366
export const getProxiedUrl = function(uri, config = {}) {
1✔
367
    let sameOrigin = !(uri.indexOf("http") === 0);
4✔
368
    let urlParts = !sameOrigin && uri.match(/([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/);
4✔
369
    // ajax.addAuthenticationToAxios(config);
370
    if (urlParts) {
4!
371
        let location = window.location;
4✔
372
        sameOrigin =
4✔
373
            urlParts[1] === location.protocol &&
8✔
374
            urlParts[3] === location.hostname;
375
        let uPort = urlParts[4];
4✔
376
        let lPort = location.port;
4✔
377
        let defaultPort = location.protocol.indexOf("https") === 0 ? 443 : 80;
4!
378
        uPort = uPort === "" ? defaultPort + "" : uPort + "";
4!
379
        lPort = lPort === "" ? defaultPort + "" : lPort + "";
4!
380
        sameOrigin = sameOrigin && uPort === lPort;
4!
381
    }
382
    if (!sameOrigin) {
4!
383
        let proxyUrl = getProxyUrl(config);
4✔
384
        if (proxyUrl) {
4!
385
            let useCORS = [];
4✔
386
            if (isObject(proxyUrl)) {
4✔
387
                useCORS = proxyUrl.useCORS || [];
1!
388
                proxyUrl = proxyUrl.url;
1✔
389
            }
390
            const isCORS = useCORS.reduce((found, current) => found || uri.indexOf(current) === 0, false);
4✔
391
            if (!isCORS) {
4✔
392
                return proxyUrl + encodeURIComponent(uri);
3✔
393
            }
394
        }
395
    }
396
    return uri;
1✔
397
};
398
/**
399
* Utility to detect browser properties.
400
* Code from leaflet-src.js
401
*/
402
export const getBrowserProperties = function() {
1✔
403

404
    let ie = 'ActiveXObject' in window;
575✔
405
    let ielt9 = ie && !document.addEventListener;
575!
406
    let ie11 = ie && (window.location.hash === !!window.MSInputMethodContext && !!document.documentMode);
575!
407

408
    // terrible browser detection to work around Safari / iOS / Android browser bugs
409
    let ua = navigator.userAgent.toLowerCase();
575✔
410
    let webkit = ua.indexOf('webkit') !== -1;
575✔
411
    let chrome = ua.indexOf('chrome') !== -1;
575✔
412
    let safari = ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1;
575✔
413
    let phantomjs = ua.indexOf('phantom') !== -1;
575✔
414
    let android = ua.indexOf('android') !== -1;
575✔
415
    let android23 = ua.search('android [23]') !== -1;
575✔
416
    let gecko = ua.indexOf('gecko') !== -1;
575✔
417

418
    let mobile = isMobile.any; // typeof window.orientation !== undefined + '';
575✔
419
    let msPointer = !window.PointerEvent && window.MSPointerEvent;
575!
420
    let pointer = window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints ||
575!
421
                msPointer;
422
    let retina = 'devicePixelRatio' in window && window.devicePixelRatio > 1 ||
575✔
423
                'matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
424
                window.matchMedia('(min-resolution:144dpi)').matches;
425

426
    let doc = document.documentElement;
575✔
427
    let ie3d = ie && 'transition' in doc.style;
575!
428
    let webkit3d = 'WebKitCSSMatrix' in window && 'm11' in new window.WebKitCSSMatrix() && !android23;
575✔
429
    let gecko3d = 'MozPerspective' in doc.style;
575✔
430
    let opera3d = 'OTransition' in doc.style;
575✔
431
    let any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
575!
432

433
    let touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window ||
575!
434
    window.DocumentTouch && document instanceof window.DocumentTouch);
435

436
    return {
575✔
437
        ie: ie,
438
        ie11: ie11,
439
        ielt9: ielt9,
440
        webkit: webkit,
441
        gecko: gecko && !webkit && !window.opera && !ie,
1,150!
442

443
        android: android,
444
        android23: android23,
445

446
        chrome: chrome,
447

448
        safari,
449

450
        ie3d: ie3d,
451
        webkit3d: webkit3d,
452
        gecko3d: gecko3d,
453
        opera3d: opera3d,
454
        any3d: any3d,
455

456
        mobile: mobile,
457
        mobileWebkit: mobile && webkit,
575!
458
        mobileWebkit3d: mobile && webkit3d,
575!
459
        mobileOpera: mobile && window.opera,
575!
460

461
        touch: touch,
462
        msPointer: msPointer,
463
        pointer: pointer,
464

465
        retina: retina
466
    };
467
};
468

469
export const getConfigProp = function(prop) {
1✔
470
    return defaultConfig[prop];
8,408✔
471
};
472
export const setConfigProp = function(prop, value) {
1✔
473
    defaultConfig[prop] = value;
811✔
474
};
475
export const removeConfigProp = function(prop) {
1✔
476
    delete defaultConfig[prop];
13✔
477
};
478

479
/**
480
 * Get misc settings from localConfig
481
 * @param {string} name of the prop to find
482
 * @param {any} defaultVal
483
 * @returns {any} value configured in misc settings
484
 */
485
export const getMiscSetting = (name, defaultVal) => {
1✔
486
    return get(getConfigProp('miscSettings') ?? {}, name, defaultVal);
5✔
487
};
488

489
// IDs for userSession
490
export const SESSION_IDS = {
1✔
491
    EVERYTHING: "everything",
492
    MAP: "map",
493
    MAP_POS: "map_pos",
494
    VISUALIZATION_MODE: "visualization_mode",
495
    LAYERS: "layers",
496
    ANNOTATIONS_LAYER: "annotations_layer",
497
    MEASUREMENTS_LAYER: "measurements_layer",
498
    BACKGROUND_LAYERS: "background_layers",
499
    OTHER_LAYERS: "other_layers",
500
    CATALOG_SERVICES: "catalog_services",
501
    WIDGETS: "widgets",
502
    SEARCH: "search",
503
    TEXT_SEARCH_SERVICES: "text_search_services",
504
    BOOKMARKS: "bookmarks",
505
    FEATURE_GRID: "feature_grid",
506
    OTHER: "other",
507
    USER_PLUGINS: "userPlugins"
508
};
509

510
/*
511
 * Function to update overrideConfig to clean specific settings
512
 * After the introduction of individual reset of the session, override Config is updated here. Previously it was clearing all session.
513
 * This function handles to update the overrideConfig(from session) to clean specific settings
514
 *
515
 * Layers are handled differently as they are partially updating properties(annotations, measurements, backgrounds, others) and merging function from loadash has problem in merging arrays of objects(it merges all properties of object from both arrays) which is used in `applyOverrides` function in this same file below
516
 * So for layers merge of original Config and overrideConfig  happens here. And applyOverrides use the value of overrideConfig
517
 * No Problem for arrays that gets entirely reset
518
 *
519
 * @param {object} override - current overrideConfig
520
 * @param {Array} thingsToClear - IDs of settings to be cleared
521
 * @param {object} originalConfig - original config
522
 * @param {object} customHandlers - session Saved By registerCustomSaveHandler
523
 * @returns {object} updated overrideConfig
524
*/
525
export const updateOverrideConfig = (override = {}, thingsToClear = [], originalConfig = {}, customHandlers = []) => {
1!
526
    let overrideConfig = JSON.parse(JSON.stringify(override));
19✔
527

528
    if (thingsToClear?.includes(SESSION_IDS.EVERYTHING)) {
19✔
529
        overrideConfig = {};
1✔
530
        return overrideConfig;
1✔
531
    }
532

533
    // zoom and center
534
    if (thingsToClear.includes(SESSION_IDS.MAP_POS)) {
18✔
535
        delete overrideConfig.map.zoom;
2✔
536
        delete overrideConfig.map.center;
2✔
537
    }
538
    // visualization mode
539
    if (thingsToClear.includes(SESSION_IDS.VISUALIZATION_MODE)) {
18✔
540
        delete overrideConfig.map.visualizationMode;
1✔
541
    }
542

543
    // layers is the only case that partially gets reset, and there is problem to merge arrays with different index(it merges properties from two array on same index)
544
    // so merging original layers here. And while merging override config with original config: Override config is given priority. Check applyOverrides function below in this file.
545

546
    // annotation layers
547
    if (thingsToClear?.includes(SESSION_IDS.ANNOTATIONS_LAYER)) {
18✔
548
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l.id?.includes('annotations')), ...originalConfig?.map?.layers.filter((l)=>l.id?.includes('annotations'))];
7✔
549
    }
550
    // measurements layers
551
    if (thingsToClear?.includes(SESSION_IDS.MEASUREMENTS_LAYER)) {
18✔
552
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l?.name?.includes('measurements')), ...originalConfig?.map?.layers.filter(l=> l?.name?.includes('measurements'))];
4✔
553
    }
554
    // background layers
555
    if (thingsToClear?.includes(SESSION_IDS.BACKGROUND_LAYERS)) {
18✔
556
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l?.group?.includes('background')), ...originalConfig?.map?.layers.filter(l=> l?.group?.includes('background'))];
10✔
557
    }
558
    // other layers
559
    if (thingsToClear?.includes(SESSION_IDS.OTHER_LAYERS)) {
18!
UNCOV
560
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter(l=> l?.id?.includes('annotations') || l?.name?.includes('measurements') || l.group?.includes("background")), ...originalConfig?.map?.layers?.filter(l=> !l?.id?.includes('annotations') && !l?.name?.includes('measurements') && !l.group?.includes("background"))];
×
561
    }
562

563
    // reorder layers based on existing order of layers: since layers are modified in different orders, so sorting is important
564
    overrideConfig.map.layers = overrideConfig.map.layers.sort((a, b) =>
18✔
565
        override?.map?.layers.findIndex(item => item.id === a.id) - override?.map?.layers?.findIndex(item => item.id === b.id)
156✔
566
    );
567
    // catalog services
568
    if (thingsToClear?.includes(SESSION_IDS.CATALOG_SERVICES)) {
18✔
569
        delete overrideConfig.catalogServices;
1✔
570
    }
571
    // widgets
572
    if (thingsToClear?.includes(SESSION_IDS.WIDGETS)) {
18✔
573
        delete overrideConfig.widgetsConfig;
1✔
574
    }
575

576
    // search services
577
    if (thingsToClear?.includes(SESSION_IDS.TEXT_SEARCH_SERVICES)) {
18✔
578
        delete overrideConfig.map.text_search_config;
1✔
579
    }
580

581
    // bookmarks
582
    if (thingsToClear?.includes(SESSION_IDS.BOOKMARKS)) {
18✔
583
        delete overrideConfig.map.bookmark_search_config;
1✔
584
    }
585

586
    // feature grid
587
    if (thingsToClear?.includes(SESSION_IDS.FEATURE_GRID)) {
18✔
588
        // each properties are updated dynamically in featureGrid reducer(MAP_CONFIG_LOADED), so each attribute of featureGrid should be reset
589
        Object.keys(overrideConfig?.featureGrid ?? {}).forEach((fg)=>{
1!
590
            overrideConfig.featureGrid[fg] = {}; // all properties's value are in object
1✔
591
        });
592
    }
593

594
    if (thingsToClear?.includes(SESSION_IDS.USER_PLUGINS)) {
18✔
595
        delete overrideConfig.context.userPlugins;
1✔
596
    }
597

598
    // handle config from registerCustomSaveConfig
599
    customHandlers?.forEach((k) => {
18✔
600
        if (thingsToClear?.includes(k)) {
25✔
601
            delete overrideConfig[k];
1✔
602
        }
603
    });
604

605

606
    return overrideConfig;
18✔
607

608
};
609
/**
610
* Merge two configurations
611
* While overriding, overrideConfig properties and original config has arrays then overrideConfig gets more priority
612
* merge from loadash has problem while merging arrays of objects(it merges properties of objects from both), So merge logic has been changed
613
 * @param {object} config the configuration to override
614
 * @param {object} override the data to use for override
615
 * @returns {object}
616
 */
617
export const applyOverrides = (config, override) => {
1✔
618
    const merged = mergeWith({}, config, override, (objValue, srcValue) => {
27✔
619
        // Till now layers is the only case that get partially reset and has case where two array with objects tries to merge, so merging with original config happens in updateOverrideConfig(above in the this file) while reset
620
        if (Array.isArray(objValue) && Array.isArray(srcValue)) {
429✔
621
            // Give priority if there are some elements in override
622
            if (srcValue.length > 0) {
8✔
623
                return [...srcValue];
6✔
624
            }
625
            return [...objValue];
2✔
626

627
        }
628
        // default merge rules for other cases
629
        // eslint-disable-next-line consistent-return
630
        return undefined;
421✔
631
    });
632
    return merged;
27✔
633
};
634
const ConfigUtils = {
1✔
635
    PropTypes: {
636
        center: centerPropType,
637
        config: PropTypes.shape({
638
            center: centerPropType,
639
            zoom: PropTypes.number.isRequired
640
        }),
641
        mapStateSource: PropTypes.string
642
    },
643
    getParsedUrl,
644
    getDefaults,
645
    setLocalConfigurationFile,
646
    loadConfiguration,
647
    getCenter,
648
    normalizeConfig,
649
    getUserConfiguration,
650
    getConfigurationOptions,
651
    getConfigUrl,
652
    convertFromLegacy,
653
    setupSources,
654
    normalizeSourceUrl,
655
    copySourceOptions,
656
    setupLayers,
657
    mergeConfigs,
658
    getProxyUrl,
659
    cleanDuplicatedQuestionMarks,
660
    getUrlWithoutParameters,
661
    filterUrlParams,
662
    getProxiedUrl,
663
    getBrowserProperties,
664
    setApiKeys,
665
    setUrlPlaceholders,
666
    replacePlaceholders,
667
    setLayerId,
668
    getConfigProp,
669
    setConfigProp,
670
    removeConfigProp,
671
    getMiscSetting
672
};
673

674
export default ConfigUtils;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc