Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions frontend/src/config/dynamic-layers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
LayerDefinitions,
getInvalidLayerIds,
registerDynamicLayer,
} from './utils';

describe('getInvalidLayerIds', () => {
const dynamicLayerId = 'openspp_TEST_DYNAMIC_adm0';

afterEach(() => {
delete (LayerDefinitions as Record<string, unknown>)[dynamicLayerId];
});

it('reports unknown layer ids as invalid', () => {
expect(getInvalidLayerIds([dynamicLayerId])).toEqual([dynamicLayerId]);
});

it('accepts statically configured layer ids', () => {
const staticId = Object.keys(LayerDefinitions)[0];
expect(staticId).toBeDefined();
expect(getInvalidLayerIds([staticId])).toEqual([]);
});

it('accepts layers registered at runtime via registerDynamicLayer', () => {
registerDynamicLayer(dynamicLayerId, {
id: dynamicLayerId,
type: 'openspp_report',
title: 'Test Dynamic Collection',
collectionId: 'TEST_DYNAMIC_adm0',
dataField: 'value',
opacity: 0.7,
legend: [],
legendText: 'Test Dynamic Collection',
} as any);
expect(getInvalidLayerIds([dynamicLayerId])).toEqual([]);
});

it('validates a mixed list, keeping only unknown ids', () => {
const staticId = Object.keys(LayerDefinitions)[0];
expect(getInvalidLayerIds([staticId, dynamicLayerId])).toEqual([
dynamicLayerId,
]);
});
});
9 changes: 9 additions & 0 deletions frontend/src/config/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,15 @@ export function registerDynamicLayer(key: string, layer: LayerType): void {
(LayerDefinitions as Record<string, LayerType>)[key] = layer;
}

/**
* Return the subset of the given layer ids that are not present in
* LayerDefinitions. Reads the live map at call time so layers added via
* registerDynamicLayer are recognized as valid.
*/
export function getInvalidLayerIds(layerIds: string[]): string[] {
return layerIds.filter(layerId => !(layerId in LayerDefinitions));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the in operator to check for keys in LayerDefinitions can be problematic if the layerId matches a built-in property on Object.prototype (e.g., toString, constructor, hasOwnProperty). Since layerIds are parsed from URL parameters, which are untrusted user inputs, it is safer to use Object.prototype.hasOwnProperty.call to ensure we only check for direct properties of the LayerDefinitions object.

Suggested change
return layerIds.filter(layerId => !(layerId in LayerDefinitions));
return layerIds.filter(layerId => !Object.prototype.hasOwnProperty.call(LayerDefinitions, layerId));

}

export function getBoundaryLayers(): BoundaryLayerProps[] {
return Object.values(LayerDefinitions)
.filter((layer): layer is BoundaryLayerProps => layer.type === 'boundary')
Expand Down
11 changes: 6 additions & 5 deletions frontend/src/utils/layers-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
AALayerIds,
LayerDefinitions,
getBoundaryLayerSingleton,
getInvalidLayerIds,
isAnticipatoryActionLayer,
isWindowedDates,
} from 'config/utils';
Expand Down Expand Up @@ -341,12 +342,12 @@ const useLayers = () => {
[serverAvailableDates],
);

const layerDefinitionIds = useMemo(() => Object.keys(LayerDefinitions), []);

// Check for invalid layer ids.
// Check for invalid layer ids against the live LayerDefinitions map, so
// layers registered at runtime (e.g., discovered OpenSPP collections) are
// recognized as valid.
const invalidLayersIds = useMemo(
() => urlLayerIds.filter(layerId => !layerDefinitionIds.includes(layerId)),
[layerDefinitionIds, urlLayerIds],
() => getInvalidLayerIds(urlLayerIds),
[urlLayerIds],
);

// Adds missing layers to existing map instance
Expand Down