react-threat-map 0.1.0
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/DECISIONS.md +275 -0
- package/LICENSE +21 -0
- package/README.md +645 -0
- package/dist/chunk-43FECBAY.js +202 -0
- package/dist/chunk-PM2OVFS5.js +9 -0
- package/dist/chunk-S6KRDFNO.cjs +13 -0
- package/dist/chunk-Y7WYSA4G.cjs +208 -0
- package/dist/countries-F7ATTG3H.cjs +20 -0
- package/dist/countries-NSR2XH7C.js +11 -0
- package/dist/geo.cjs +69 -0
- package/dist/geo.d.cts +56 -0
- package/dist/geo.d.ts +56 -0
- package/dist/geo.js +47 -0
- package/dist/index.cjs +1351 -0
- package/dist/index.d.cts +301 -0
- package/dist/index.d.ts +301 -0
- package/dist/index.js +1321 -0
- package/dist/regions-neHSPgtu.d.cts +765 -0
- package/dist/regions-neHSPgtu.d.ts +765 -0
- package/dist/small-countries-6O4AI3HD.js +8 -0
- package/dist/small-countries-D4QUXHH3.cjs +14 -0
- package/dist/states-DZ3SOMAC.js +11 -0
- package/dist/states-H54VC3H5.cjs +20 -0
- package/package.json +91 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { T as ThreatMapProps, A as AnimationConfig, L as LineStyleConfig, c as RegionsConfig, d as ThreatMapTheme, e as AggregationConfig, a as RegionIndex, f as Attack, h as Threat, I as IntensityScale, S as Severity, i as ResolvedRegion, j as AggregationGranularity, k as LatLng, m as AttackLocation } from './regions-neHSPgtu.cjs';
|
|
2
|
+
export { n as AggregationGroupBy, o as AggregationKeyFn, p as AggregationSeverityFn, C as Color, E as EasingName, G as GeoData, q as GeoFeature, r as GeoFeatureCollection, s as GeoGeometry, t as GeoProjectionLike, P as ProjectionName, u as ProjectionSpec, v as RegionCode, R as RegionEntry, w as RegionKind, x as RegionRenderContext, y as RegionRenderer, z as ThreatMapError, B as ThreatRenderContext, D as ThreatRenderer, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-neHSPgtu.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The `<ThreatMap>` component.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A world map with animated cyberattack threats drawn over it.
|
|
12
|
+
*
|
|
13
|
+
* In the simplest case, pass attacks and nothing else — the map sizes itself to
|
|
14
|
+
* its container, loads its own geography, aggregates by origin region, and
|
|
15
|
+
* animates:
|
|
16
|
+
*
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <ThreatMap attacks={[{ from: 'CN', to: 'US-CA', severity: 'high' }]} />
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Every layer is then customizable via props. See {@link ThreatMapProps}, and
|
|
22
|
+
* the README for the full guide.
|
|
23
|
+
*
|
|
24
|
+
* **Layout**: the component renders a positioned wrapper containing two stacked
|
|
25
|
+
* canvases. Give the wrapper a size via `style`/`className`, or pass explicit
|
|
26
|
+
* `width`/`height`. With neither, it fills its container's width and derives a
|
|
27
|
+
* height from the projection's aspect ratio.
|
|
28
|
+
*
|
|
29
|
+
* @example Streaming feed with custom theme and state borders
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <ThreatMap
|
|
32
|
+
* attacks={attacks}
|
|
33
|
+
* regions={{ showStates: true }}
|
|
34
|
+
* theme={{ ocean: '#000', severityColors: { critical: '#f0f' } }}
|
|
35
|
+
* animation={{ speed: 1.2 }}
|
|
36
|
+
* onThreatClick={(threat) => console.log(threat.count, 'attacks from', threat.fromRegion.name)}
|
|
37
|
+
* />
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function ThreatMap<TMeta = unknown>(props: ThreatMapProps<TMeta>): JSX.Element;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Default theme and configuration objects.
|
|
44
|
+
*
|
|
45
|
+
* Every one is exported and frozen, so you can read them, spread them, or derive
|
|
46
|
+
* your own variants:
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { defaultTheme } from 'react-threat-map';
|
|
50
|
+
* const lightTheme = { ...defaultTheme, ocean: '#f4f6fa', land: '#d8dfe8' };
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @packageDocumentation
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The default dark theme.
|
|
58
|
+
*
|
|
59
|
+
* Tuned for a SOC wall display: a desaturated navy map so that saturated threat
|
|
60
|
+
* lines carry all the visual salience. Severity runs cyan → amber → orange → red,
|
|
61
|
+
* which reads as escalating for viewers with the common forms of colour-vision
|
|
62
|
+
* deficiency because it varies lightness as well as hue.
|
|
63
|
+
*/
|
|
64
|
+
declare const defaultTheme: ThreatMapTheme;
|
|
65
|
+
/** Default arc geometry and styling. */
|
|
66
|
+
declare const defaultLineStyle: LineStyleConfig;
|
|
67
|
+
/** Default animation settings. */
|
|
68
|
+
declare const defaultAnimation: AnimationConfig;
|
|
69
|
+
/** Default boundary-drawing settings. */
|
|
70
|
+
declare const defaultRegions: RegionsConfig;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The aggregation stage: many {@link Attack}s in, few {@link Threat}s out.
|
|
74
|
+
*
|
|
75
|
+
* A pure function. No React, no canvas, no async, no geometry — which is what
|
|
76
|
+
* makes it exhaustively unit-testable and reusable outside the component (for a
|
|
77
|
+
* table view, a CSV export, a test assertion).
|
|
78
|
+
*
|
|
79
|
+
* @packageDocumentation
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* An {@link AggregationConfig} with every default filled in.
|
|
84
|
+
*
|
|
85
|
+
* `key` and `maxGroups` stay optional because "absent" is a meaningful state for
|
|
86
|
+
* both — there is no sentinel value that means "no custom key function" or
|
|
87
|
+
* "no cap" as naturally as their absence does.
|
|
88
|
+
*/
|
|
89
|
+
type ResolvedAggregationConfig<TMeta = unknown> = Required<Omit<AggregationConfig<TMeta>, 'key' | 'maxGroups'>> & {
|
|
90
|
+
readonly key?: AggregationConfig<TMeta>['key'];
|
|
91
|
+
readonly maxGroups?: number;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Defaults for {@link AggregationConfig}.
|
|
95
|
+
*
|
|
96
|
+
* Exported so you can read or extend them:
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* import { defaultAggregation } from 'react-threat-map';
|
|
100
|
+
* const scale = defaultAggregation.scale; // the built-in log ramp
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare const defaultAggregation: ResolvedAggregationConfig;
|
|
104
|
+
/** Options for {@link aggregateAttacks} beyond the aggregation config itself. */
|
|
105
|
+
interface AggregateOptions<TMeta = unknown> {
|
|
106
|
+
/** Aggregation settings. Merged over {@link defaultAggregation}. Pass `false` to disable. */
|
|
107
|
+
readonly config?: Partial<AggregationConfig<TMeta>> | false;
|
|
108
|
+
/** Boundary index, enabling reverse resolution of bare coordinates. Optional. */
|
|
109
|
+
readonly index?: RegionIndex | null;
|
|
110
|
+
/** Reports attacks that could not be resolved. */
|
|
111
|
+
readonly onError?: (message: string, attack: Attack<TMeta>) => void;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Aggregate attacks into renderable threats.
|
|
115
|
+
*
|
|
116
|
+
* Pipeline:
|
|
117
|
+
* 1. **Resolve** each attack's endpoints to coordinates + regions. Unresolvable
|
|
118
|
+
* attacks are reported via `onError` and dropped.
|
|
119
|
+
* 2. **Group** by key — derived from the origin region (and destination, by
|
|
120
|
+
* default), or from a custom `key` function.
|
|
121
|
+
* 3. **Split out undersized groups.** Groups below `minCount` are emitted as
|
|
122
|
+
* individual threats: merging two attacks into a "group of 2" adds visual
|
|
123
|
+
* weight without adding information.
|
|
124
|
+
* 4. **Collapse** each surviving group into one threat, summing weight, taking
|
|
125
|
+
* max severity, and scaling intensity by count.
|
|
126
|
+
* 5. **Cap** to `maxGroups`, keeping the heaviest.
|
|
127
|
+
*
|
|
128
|
+
* Output order is deterministic — descending `totalWeight`, ties broken by `id` —
|
|
129
|
+
* so renders are stable across calls and snapshot tests do not flake.
|
|
130
|
+
*
|
|
131
|
+
* @param attacks - The attacks to aggregate.
|
|
132
|
+
* @param options - See {@link AggregateOptions}.
|
|
133
|
+
* @returns Threats to render, heaviest first.
|
|
134
|
+
*
|
|
135
|
+
* @example Aggregation on (the default)
|
|
136
|
+
* ```ts
|
|
137
|
+
* const threats = aggregateAttacks([
|
|
138
|
+
* { from: { lat: 34.0, lng: -118.2, region: 'US-CA' }, to: 'FR' },
|
|
139
|
+
* { from: { lat: 37.8, lng: -122.4, region: 'US-CA' }, to: 'FR' },
|
|
140
|
+
* { from: 'US-TX', to: 'FR' },
|
|
141
|
+
* ]);
|
|
142
|
+
* // 2 threats: California→France (count 2, heavier) and Texas→France (count 1).
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @example Aggregation off
|
|
146
|
+
* ```ts
|
|
147
|
+
* const threats = aggregateAttacks(attacks, { config: false });
|
|
148
|
+
* // one threat per attack, every count === 1
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
declare function aggregateAttacks<TMeta = unknown>(attacks: readonly Attack<TMeta>[], options?: AggregateOptions<TMeta>): Threat<TMeta>[];
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Mapping attack counts to visual weight.
|
|
155
|
+
*
|
|
156
|
+
* @packageDocumentation
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/** Upper bound on intensity, so one runaway group cannot black out the map. */
|
|
160
|
+
declare const MAX_INTENSITY = 6;
|
|
161
|
+
/**
|
|
162
|
+
* The default count → visual weight ramp: `1 + log2(count) * 0.5`, clamped to
|
|
163
|
+
* `[1, 6]`.
|
|
164
|
+
*
|
|
165
|
+
* Logarithmic rather than linear, because attack volume per region is heavily
|
|
166
|
+
* long-tailed. Linear scaling on a feed where one region sends 500 attacks and
|
|
167
|
+
* another sends 5 gives you a 500 px-wide smear next to a hairline — the busiest
|
|
168
|
+
* region erases the map it is drawn on. The log ramp keeps the ordering legible:
|
|
169
|
+
*
|
|
170
|
+
* | count | intensity |
|
|
171
|
+
* | ----- | --------- |
|
|
172
|
+
* | 1 | 1.0 |
|
|
173
|
+
* | 2 | 1.5 |
|
|
174
|
+
* | 10 | 2.7 |
|
|
175
|
+
* | 100 | 4.3 |
|
|
176
|
+
* | 500 | 5.5 |
|
|
177
|
+
* | 5000 | 6.0 (clamped) |
|
|
178
|
+
*
|
|
179
|
+
* Swap it for anything you like via {@link AggregationConfig.scale} — e.g.
|
|
180
|
+
* `(count) => 1 + count / 10` for a linear ramp, or `() => 1` to size every
|
|
181
|
+
* threat identically while still merging them.
|
|
182
|
+
*
|
|
183
|
+
* @param count - Attacks in the group, `>= 1`.
|
|
184
|
+
* @returns A multiplier in `[1, 6]`.
|
|
185
|
+
*/
|
|
186
|
+
declare const defaultIntensityScale: IntensityScale;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Grouping-key derivation — the rule that decides what "the same threat" means.
|
|
190
|
+
*
|
|
191
|
+
* @packageDocumentation
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
/** Severity order, low → critical. Index doubles as the numeric rank. */
|
|
195
|
+
declare const SEVERITY_ORDER: readonly Severity[];
|
|
196
|
+
/**
|
|
197
|
+
* Rank a severity for comparison.
|
|
198
|
+
*
|
|
199
|
+
* @param severity - A known or custom severity.
|
|
200
|
+
* @returns `0`–`3` for known severities. Unknown/custom values rank as `medium`
|
|
201
|
+
* (`1`), matching how they fall back for color — a custom severity should not
|
|
202
|
+
* silently outrank `critical` or sink below `low`.
|
|
203
|
+
*/
|
|
204
|
+
declare function severityRank(severity: Severity): number;
|
|
205
|
+
/**
|
|
206
|
+
* Reduce a group's severities to the one it renders as: the **maximum**.
|
|
207
|
+
*
|
|
208
|
+
* A bucket holding one `critical` and forty `low` renders `critical`. For a
|
|
209
|
+
* security display, hiding the worst event in a group is the more dangerous
|
|
210
|
+
* failure — under-reporting a real incident beats over-reporting a benign one.
|
|
211
|
+
* Override via {@link AggregationConfig.severity}.
|
|
212
|
+
*
|
|
213
|
+
* @param severities - Member severities. Must not be empty.
|
|
214
|
+
* @returns The highest-ranked severity present.
|
|
215
|
+
*/
|
|
216
|
+
declare function maxSeverity(severities: readonly Severity[]): Severity;
|
|
217
|
+
/**
|
|
218
|
+
* Reduce a resolved region to the id aggregation groups on, at a given granularity.
|
|
219
|
+
*
|
|
220
|
+
* This is the function that makes US states first-class origins:
|
|
221
|
+
*
|
|
222
|
+
* | granularity | region | key |
|
|
223
|
+
* | ----------- | ------------- | ------ |
|
|
224
|
+
* | `'auto'` | `US-CA` state | `US-CA` |
|
|
225
|
+
* | `'auto'` | `FR` country | `FR` |
|
|
226
|
+
* | `'country'` | `US-CA` state | `US` |
|
|
227
|
+
* | `'state'` | `FR` country | `FR` |
|
|
228
|
+
*
|
|
229
|
+
* `'auto'` — the default — keeps California and Texas apart while leaving the
|
|
230
|
+
* rest of the world at country level, which is the behavior a threat map wants:
|
|
231
|
+
* US-internal detail without shattering every other country into subdivisions we
|
|
232
|
+
* do not have data for.
|
|
233
|
+
*
|
|
234
|
+
* @param region - The resolved region.
|
|
235
|
+
* @param granularity - How specific to be.
|
|
236
|
+
* @returns The group key component for this region.
|
|
237
|
+
*/
|
|
238
|
+
declare function regionKey(region: ResolvedRegion, granularity: AggregationGranularity): string;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Turning an {@link AttackLocation} into a coordinate plus a region.
|
|
242
|
+
*
|
|
243
|
+
* Pure and synchronous. The one async ingredient — boundary geometry, needed
|
|
244
|
+
* only to reverse-resolve bare coordinates — is passed in as an optional
|
|
245
|
+
* {@link RegionIndex}, so this module and everything downstream of it (all of
|
|
246
|
+
* aggregation) stays testable without loading a single byte of geometry.
|
|
247
|
+
*
|
|
248
|
+
* @packageDocumentation
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
/** A location resolved to a drawable point and a groupable region. */
|
|
252
|
+
interface ResolvedLocation {
|
|
253
|
+
/** Where to draw. */
|
|
254
|
+
readonly point: LatLng;
|
|
255
|
+
/** What to group by. `kind: 'unknown'` when the region could not be determined. */
|
|
256
|
+
readonly region: ResolvedRegion;
|
|
257
|
+
}
|
|
258
|
+
/** Returned instead of throwing when a location cannot be resolved at all. */
|
|
259
|
+
interface ResolveFailure {
|
|
260
|
+
readonly ok: false;
|
|
261
|
+
/** Why it failed, suitable for an `onError` message. */
|
|
262
|
+
readonly reason: string;
|
|
263
|
+
}
|
|
264
|
+
/** A successful resolution. */
|
|
265
|
+
interface ResolveSuccess {
|
|
266
|
+
readonly ok: true;
|
|
267
|
+
readonly value: ResolvedLocation;
|
|
268
|
+
}
|
|
269
|
+
/** Result of {@link resolveLocation}. */
|
|
270
|
+
type ResolveResult = ResolveSuccess | ResolveFailure;
|
|
271
|
+
/**
|
|
272
|
+
* Resolve a location to a point and a region.
|
|
273
|
+
*
|
|
274
|
+
* Three input shapes, in increasing cost:
|
|
275
|
+
*
|
|
276
|
+
* - **Region code** (`"US-CA"`) — one Map lookup. The anchor coordinate is used
|
|
277
|
+
* as the point.
|
|
278
|
+
* - **Coordinates + region** (`{lat, lng, region: 'US-CA'}`) — one Map lookup,
|
|
279
|
+
* exact point. The cheapest option that also gives pixel-accurate placement.
|
|
280
|
+
* - **Bare coordinates** (`{lat, lng}`) — needs `index` to reverse-resolve via
|
|
281
|
+
* point-in-polygon. Without an index the point still renders, but its region
|
|
282
|
+
* is `unknown`, so it will not aggregate with anything.
|
|
283
|
+
*
|
|
284
|
+
* Never throws. Invalid input returns a {@link ResolveFailure} the caller can
|
|
285
|
+
* surface through `onError`, so one malformed row cannot take down the map.
|
|
286
|
+
*
|
|
287
|
+
* @param location - The location to resolve.
|
|
288
|
+
* @param index - Boundary index for reverse lookup. Omit if geometry is not loaded yet.
|
|
289
|
+
* @param preferStates - Resolve bare coordinates to US states where possible.
|
|
290
|
+
* Ignored for explicit region codes, which are already as specific as they are.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* resolveLocation('US-CA'); // ok, point = California anchor
|
|
295
|
+
* resolveLocation({ lat: 34, lng: -118 }, index, true); // ok, region = US-CA
|
|
296
|
+
* resolveLocation('NOPE'); // { ok: false, reason: ... }
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
declare function resolveLocation(location: AttackLocation, index?: RegionIndex | null, preferStates?: boolean): ResolveResult;
|
|
300
|
+
|
|
301
|
+
export { type AggregateOptions, AggregationConfig, AggregationGranularity, AnimationConfig, Attack, AttackLocation, IntensityScale, LatLng, LineStyleConfig, MAX_INTENSITY, RegionsConfig, type ResolveResult, type ResolvedAggregationConfig, type ResolvedLocation, ResolvedRegion, SEVERITY_ORDER, Severity, Threat, ThreatMap, ThreatMapProps, ThreatMapTheme, aggregateAttacks, defaultAggregation, defaultAnimation, defaultIntensityScale, defaultLineStyle, defaultRegions, defaultTheme, maxSeverity, regionKey, resolveLocation, severityRank };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { T as ThreatMapProps, A as AnimationConfig, L as LineStyleConfig, c as RegionsConfig, d as ThreatMapTheme, e as AggregationConfig, a as RegionIndex, f as Attack, h as Threat, I as IntensityScale, S as Severity, i as ResolvedRegion, j as AggregationGranularity, k as LatLng, m as AttackLocation } from './regions-neHSPgtu.js';
|
|
2
|
+
export { n as AggregationGroupBy, o as AggregationKeyFn, p as AggregationSeverityFn, C as Color, E as EasingName, G as GeoData, q as GeoFeature, r as GeoFeatureCollection, s as GeoGeometry, t as GeoProjectionLike, P as ProjectionName, u as ProjectionSpec, v as RegionCode, R as RegionEntry, w as RegionKind, x as RegionRenderContext, y as RegionRenderer, z as ThreatMapError, B as ThreatRenderContext, D as ThreatRenderer, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-neHSPgtu.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The `<ThreatMap>` component.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A world map with animated cyberattack threats drawn over it.
|
|
12
|
+
*
|
|
13
|
+
* In the simplest case, pass attacks and nothing else — the map sizes itself to
|
|
14
|
+
* its container, loads its own geography, aggregates by origin region, and
|
|
15
|
+
* animates:
|
|
16
|
+
*
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <ThreatMap attacks={[{ from: 'CN', to: 'US-CA', severity: 'high' }]} />
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Every layer is then customizable via props. See {@link ThreatMapProps}, and
|
|
22
|
+
* the README for the full guide.
|
|
23
|
+
*
|
|
24
|
+
* **Layout**: the component renders a positioned wrapper containing two stacked
|
|
25
|
+
* canvases. Give the wrapper a size via `style`/`className`, or pass explicit
|
|
26
|
+
* `width`/`height`. With neither, it fills its container's width and derives a
|
|
27
|
+
* height from the projection's aspect ratio.
|
|
28
|
+
*
|
|
29
|
+
* @example Streaming feed with custom theme and state borders
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <ThreatMap
|
|
32
|
+
* attacks={attacks}
|
|
33
|
+
* regions={{ showStates: true }}
|
|
34
|
+
* theme={{ ocean: '#000', severityColors: { critical: '#f0f' } }}
|
|
35
|
+
* animation={{ speed: 1.2 }}
|
|
36
|
+
* onThreatClick={(threat) => console.log(threat.count, 'attacks from', threat.fromRegion.name)}
|
|
37
|
+
* />
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function ThreatMap<TMeta = unknown>(props: ThreatMapProps<TMeta>): JSX.Element;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Default theme and configuration objects.
|
|
44
|
+
*
|
|
45
|
+
* Every one is exported and frozen, so you can read them, spread them, or derive
|
|
46
|
+
* your own variants:
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { defaultTheme } from 'react-threat-map';
|
|
50
|
+
* const lightTheme = { ...defaultTheme, ocean: '#f4f6fa', land: '#d8dfe8' };
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @packageDocumentation
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The default dark theme.
|
|
58
|
+
*
|
|
59
|
+
* Tuned for a SOC wall display: a desaturated navy map so that saturated threat
|
|
60
|
+
* lines carry all the visual salience. Severity runs cyan → amber → orange → red,
|
|
61
|
+
* which reads as escalating for viewers with the common forms of colour-vision
|
|
62
|
+
* deficiency because it varies lightness as well as hue.
|
|
63
|
+
*/
|
|
64
|
+
declare const defaultTheme: ThreatMapTheme;
|
|
65
|
+
/** Default arc geometry and styling. */
|
|
66
|
+
declare const defaultLineStyle: LineStyleConfig;
|
|
67
|
+
/** Default animation settings. */
|
|
68
|
+
declare const defaultAnimation: AnimationConfig;
|
|
69
|
+
/** Default boundary-drawing settings. */
|
|
70
|
+
declare const defaultRegions: RegionsConfig;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The aggregation stage: many {@link Attack}s in, few {@link Threat}s out.
|
|
74
|
+
*
|
|
75
|
+
* A pure function. No React, no canvas, no async, no geometry — which is what
|
|
76
|
+
* makes it exhaustively unit-testable and reusable outside the component (for a
|
|
77
|
+
* table view, a CSV export, a test assertion).
|
|
78
|
+
*
|
|
79
|
+
* @packageDocumentation
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* An {@link AggregationConfig} with every default filled in.
|
|
84
|
+
*
|
|
85
|
+
* `key` and `maxGroups` stay optional because "absent" is a meaningful state for
|
|
86
|
+
* both — there is no sentinel value that means "no custom key function" or
|
|
87
|
+
* "no cap" as naturally as their absence does.
|
|
88
|
+
*/
|
|
89
|
+
type ResolvedAggregationConfig<TMeta = unknown> = Required<Omit<AggregationConfig<TMeta>, 'key' | 'maxGroups'>> & {
|
|
90
|
+
readonly key?: AggregationConfig<TMeta>['key'];
|
|
91
|
+
readonly maxGroups?: number;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Defaults for {@link AggregationConfig}.
|
|
95
|
+
*
|
|
96
|
+
* Exported so you can read or extend them:
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* import { defaultAggregation } from 'react-threat-map';
|
|
100
|
+
* const scale = defaultAggregation.scale; // the built-in log ramp
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare const defaultAggregation: ResolvedAggregationConfig;
|
|
104
|
+
/** Options for {@link aggregateAttacks} beyond the aggregation config itself. */
|
|
105
|
+
interface AggregateOptions<TMeta = unknown> {
|
|
106
|
+
/** Aggregation settings. Merged over {@link defaultAggregation}. Pass `false` to disable. */
|
|
107
|
+
readonly config?: Partial<AggregationConfig<TMeta>> | false;
|
|
108
|
+
/** Boundary index, enabling reverse resolution of bare coordinates. Optional. */
|
|
109
|
+
readonly index?: RegionIndex | null;
|
|
110
|
+
/** Reports attacks that could not be resolved. */
|
|
111
|
+
readonly onError?: (message: string, attack: Attack<TMeta>) => void;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Aggregate attacks into renderable threats.
|
|
115
|
+
*
|
|
116
|
+
* Pipeline:
|
|
117
|
+
* 1. **Resolve** each attack's endpoints to coordinates + regions. Unresolvable
|
|
118
|
+
* attacks are reported via `onError` and dropped.
|
|
119
|
+
* 2. **Group** by key — derived from the origin region (and destination, by
|
|
120
|
+
* default), or from a custom `key` function.
|
|
121
|
+
* 3. **Split out undersized groups.** Groups below `minCount` are emitted as
|
|
122
|
+
* individual threats: merging two attacks into a "group of 2" adds visual
|
|
123
|
+
* weight without adding information.
|
|
124
|
+
* 4. **Collapse** each surviving group into one threat, summing weight, taking
|
|
125
|
+
* max severity, and scaling intensity by count.
|
|
126
|
+
* 5. **Cap** to `maxGroups`, keeping the heaviest.
|
|
127
|
+
*
|
|
128
|
+
* Output order is deterministic — descending `totalWeight`, ties broken by `id` —
|
|
129
|
+
* so renders are stable across calls and snapshot tests do not flake.
|
|
130
|
+
*
|
|
131
|
+
* @param attacks - The attacks to aggregate.
|
|
132
|
+
* @param options - See {@link AggregateOptions}.
|
|
133
|
+
* @returns Threats to render, heaviest first.
|
|
134
|
+
*
|
|
135
|
+
* @example Aggregation on (the default)
|
|
136
|
+
* ```ts
|
|
137
|
+
* const threats = aggregateAttacks([
|
|
138
|
+
* { from: { lat: 34.0, lng: -118.2, region: 'US-CA' }, to: 'FR' },
|
|
139
|
+
* { from: { lat: 37.8, lng: -122.4, region: 'US-CA' }, to: 'FR' },
|
|
140
|
+
* { from: 'US-TX', to: 'FR' },
|
|
141
|
+
* ]);
|
|
142
|
+
* // 2 threats: California→France (count 2, heavier) and Texas→France (count 1).
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @example Aggregation off
|
|
146
|
+
* ```ts
|
|
147
|
+
* const threats = aggregateAttacks(attacks, { config: false });
|
|
148
|
+
* // one threat per attack, every count === 1
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
declare function aggregateAttacks<TMeta = unknown>(attacks: readonly Attack<TMeta>[], options?: AggregateOptions<TMeta>): Threat<TMeta>[];
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Mapping attack counts to visual weight.
|
|
155
|
+
*
|
|
156
|
+
* @packageDocumentation
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/** Upper bound on intensity, so one runaway group cannot black out the map. */
|
|
160
|
+
declare const MAX_INTENSITY = 6;
|
|
161
|
+
/**
|
|
162
|
+
* The default count → visual weight ramp: `1 + log2(count) * 0.5`, clamped to
|
|
163
|
+
* `[1, 6]`.
|
|
164
|
+
*
|
|
165
|
+
* Logarithmic rather than linear, because attack volume per region is heavily
|
|
166
|
+
* long-tailed. Linear scaling on a feed where one region sends 500 attacks and
|
|
167
|
+
* another sends 5 gives you a 500 px-wide smear next to a hairline — the busiest
|
|
168
|
+
* region erases the map it is drawn on. The log ramp keeps the ordering legible:
|
|
169
|
+
*
|
|
170
|
+
* | count | intensity |
|
|
171
|
+
* | ----- | --------- |
|
|
172
|
+
* | 1 | 1.0 |
|
|
173
|
+
* | 2 | 1.5 |
|
|
174
|
+
* | 10 | 2.7 |
|
|
175
|
+
* | 100 | 4.3 |
|
|
176
|
+
* | 500 | 5.5 |
|
|
177
|
+
* | 5000 | 6.0 (clamped) |
|
|
178
|
+
*
|
|
179
|
+
* Swap it for anything you like via {@link AggregationConfig.scale} — e.g.
|
|
180
|
+
* `(count) => 1 + count / 10` for a linear ramp, or `() => 1` to size every
|
|
181
|
+
* threat identically while still merging them.
|
|
182
|
+
*
|
|
183
|
+
* @param count - Attacks in the group, `>= 1`.
|
|
184
|
+
* @returns A multiplier in `[1, 6]`.
|
|
185
|
+
*/
|
|
186
|
+
declare const defaultIntensityScale: IntensityScale;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Grouping-key derivation — the rule that decides what "the same threat" means.
|
|
190
|
+
*
|
|
191
|
+
* @packageDocumentation
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
/** Severity order, low → critical. Index doubles as the numeric rank. */
|
|
195
|
+
declare const SEVERITY_ORDER: readonly Severity[];
|
|
196
|
+
/**
|
|
197
|
+
* Rank a severity for comparison.
|
|
198
|
+
*
|
|
199
|
+
* @param severity - A known or custom severity.
|
|
200
|
+
* @returns `0`–`3` for known severities. Unknown/custom values rank as `medium`
|
|
201
|
+
* (`1`), matching how they fall back for color — a custom severity should not
|
|
202
|
+
* silently outrank `critical` or sink below `low`.
|
|
203
|
+
*/
|
|
204
|
+
declare function severityRank(severity: Severity): number;
|
|
205
|
+
/**
|
|
206
|
+
* Reduce a group's severities to the one it renders as: the **maximum**.
|
|
207
|
+
*
|
|
208
|
+
* A bucket holding one `critical` and forty `low` renders `critical`. For a
|
|
209
|
+
* security display, hiding the worst event in a group is the more dangerous
|
|
210
|
+
* failure — under-reporting a real incident beats over-reporting a benign one.
|
|
211
|
+
* Override via {@link AggregationConfig.severity}.
|
|
212
|
+
*
|
|
213
|
+
* @param severities - Member severities. Must not be empty.
|
|
214
|
+
* @returns The highest-ranked severity present.
|
|
215
|
+
*/
|
|
216
|
+
declare function maxSeverity(severities: readonly Severity[]): Severity;
|
|
217
|
+
/**
|
|
218
|
+
* Reduce a resolved region to the id aggregation groups on, at a given granularity.
|
|
219
|
+
*
|
|
220
|
+
* This is the function that makes US states first-class origins:
|
|
221
|
+
*
|
|
222
|
+
* | granularity | region | key |
|
|
223
|
+
* | ----------- | ------------- | ------ |
|
|
224
|
+
* | `'auto'` | `US-CA` state | `US-CA` |
|
|
225
|
+
* | `'auto'` | `FR` country | `FR` |
|
|
226
|
+
* | `'country'` | `US-CA` state | `US` |
|
|
227
|
+
* | `'state'` | `FR` country | `FR` |
|
|
228
|
+
*
|
|
229
|
+
* `'auto'` — the default — keeps California and Texas apart while leaving the
|
|
230
|
+
* rest of the world at country level, which is the behavior a threat map wants:
|
|
231
|
+
* US-internal detail without shattering every other country into subdivisions we
|
|
232
|
+
* do not have data for.
|
|
233
|
+
*
|
|
234
|
+
* @param region - The resolved region.
|
|
235
|
+
* @param granularity - How specific to be.
|
|
236
|
+
* @returns The group key component for this region.
|
|
237
|
+
*/
|
|
238
|
+
declare function regionKey(region: ResolvedRegion, granularity: AggregationGranularity): string;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Turning an {@link AttackLocation} into a coordinate plus a region.
|
|
242
|
+
*
|
|
243
|
+
* Pure and synchronous. The one async ingredient — boundary geometry, needed
|
|
244
|
+
* only to reverse-resolve bare coordinates — is passed in as an optional
|
|
245
|
+
* {@link RegionIndex}, so this module and everything downstream of it (all of
|
|
246
|
+
* aggregation) stays testable without loading a single byte of geometry.
|
|
247
|
+
*
|
|
248
|
+
* @packageDocumentation
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
/** A location resolved to a drawable point and a groupable region. */
|
|
252
|
+
interface ResolvedLocation {
|
|
253
|
+
/** Where to draw. */
|
|
254
|
+
readonly point: LatLng;
|
|
255
|
+
/** What to group by. `kind: 'unknown'` when the region could not be determined. */
|
|
256
|
+
readonly region: ResolvedRegion;
|
|
257
|
+
}
|
|
258
|
+
/** Returned instead of throwing when a location cannot be resolved at all. */
|
|
259
|
+
interface ResolveFailure {
|
|
260
|
+
readonly ok: false;
|
|
261
|
+
/** Why it failed, suitable for an `onError` message. */
|
|
262
|
+
readonly reason: string;
|
|
263
|
+
}
|
|
264
|
+
/** A successful resolution. */
|
|
265
|
+
interface ResolveSuccess {
|
|
266
|
+
readonly ok: true;
|
|
267
|
+
readonly value: ResolvedLocation;
|
|
268
|
+
}
|
|
269
|
+
/** Result of {@link resolveLocation}. */
|
|
270
|
+
type ResolveResult = ResolveSuccess | ResolveFailure;
|
|
271
|
+
/**
|
|
272
|
+
* Resolve a location to a point and a region.
|
|
273
|
+
*
|
|
274
|
+
* Three input shapes, in increasing cost:
|
|
275
|
+
*
|
|
276
|
+
* - **Region code** (`"US-CA"`) — one Map lookup. The anchor coordinate is used
|
|
277
|
+
* as the point.
|
|
278
|
+
* - **Coordinates + region** (`{lat, lng, region: 'US-CA'}`) — one Map lookup,
|
|
279
|
+
* exact point. The cheapest option that also gives pixel-accurate placement.
|
|
280
|
+
* - **Bare coordinates** (`{lat, lng}`) — needs `index` to reverse-resolve via
|
|
281
|
+
* point-in-polygon. Without an index the point still renders, but its region
|
|
282
|
+
* is `unknown`, so it will not aggregate with anything.
|
|
283
|
+
*
|
|
284
|
+
* Never throws. Invalid input returns a {@link ResolveFailure} the caller can
|
|
285
|
+
* surface through `onError`, so one malformed row cannot take down the map.
|
|
286
|
+
*
|
|
287
|
+
* @param location - The location to resolve.
|
|
288
|
+
* @param index - Boundary index for reverse lookup. Omit if geometry is not loaded yet.
|
|
289
|
+
* @param preferStates - Resolve bare coordinates to US states where possible.
|
|
290
|
+
* Ignored for explicit region codes, which are already as specific as they are.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* resolveLocation('US-CA'); // ok, point = California anchor
|
|
295
|
+
* resolveLocation({ lat: 34, lng: -118 }, index, true); // ok, region = US-CA
|
|
296
|
+
* resolveLocation('NOPE'); // { ok: false, reason: ... }
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
declare function resolveLocation(location: AttackLocation, index?: RegionIndex | null, preferStates?: boolean): ResolveResult;
|
|
300
|
+
|
|
301
|
+
export { type AggregateOptions, AggregationConfig, AggregationGranularity, AnimationConfig, Attack, AttackLocation, IntensityScale, LatLng, LineStyleConfig, MAX_INTENSITY, RegionsConfig, type ResolveResult, type ResolvedAggregationConfig, type ResolvedLocation, ResolvedRegion, SEVERITY_ORDER, Severity, Threat, ThreatMap, ThreatMapProps, ThreatMapTheme, aggregateAttacks, defaultAggregation, defaultAnimation, defaultIntensityScale, defaultLineStyle, defaultRegions, defaultTheme, maxSeverity, regionKey, resolveLocation, severityRank };
|